diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7c7e135..48b73198 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,8 +41,10 @@ jobs: with: { python-version: '${{ matrix.python }}' } - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 with: { version: '0.11.26' } - - run: python -m modiff.install --accelerator cpu --backend-only --non-interactive --json + - run: uv run --no-project --no-sync --python 3.12 -m modiff.dev plan --accelerator cpu --backend-only --json + - run: uv run --no-project --no-sync --python 3.12 -m modiff.dev setup --accelerator cpu --backend-only --non-interactive --json - run: uv pip install --python ${{ matrix.managed-python }} -r requirements/test.txt - run: uv pip check --python ${{ matrix.managed-python }} - - run: ${{ matrix.managed-python }} -m modiff.preflight --json --check-port 8088 --fail-on-error + - run: uv run --no-project --no-sync --python 3.12 -m modiff.dev check --json --check-port 8088 --fail-on-error + - run: ${{ matrix.managed-python }} scripts/smoke_service_package.py - run: ${{ matrix.managed-python }} -m pytest -q diff --git a/.github/workflows/qualify-optional-runtime-macos.yml b/.github/workflows/qualify-optional-runtime-macos.yml index 32946d9a..0b5200b8 100644 --- a/.github/workflows/qualify-optional-runtime-macos.yml +++ b/.github/workflows/qualify-optional-runtime-macos.yml @@ -42,9 +42,9 @@ jobs: "torchsde>=0.2.6", "torchvision>=0.21.0", - "transformers>=4.49.0; sys_platform == 'darwin' or platform_machine == 'aarch64' or platform_machine == 'arm64' or platform_machine == 'ARM64'", - "diffusers @ git+https://github.com/huggingface/diffusers.git@2f7e0154a9db246e95c9ede43edba7db5b130805", + "diffusers @ git+https://github.com/huggingface/diffusers.git@fbf49e7f35857f76bc57b177e26f12b03687c668", "ftfy>=6.3.1", - "huggingface-hub>=1.23.0,<2.0", + "huggingface-hub>=1.31.0,<2.0", PATCH git apply --check "$RUNNER_TEMP/prospective-base.patch" git apply "$RUNNER_TEMP/prospective-base.patch" diff --git a/.gitignore b/.gitignore index 35b739c5..ca2837a4 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ custom/* data/* !data/model-artifact-catalog.json +!data/image-prototyping-readiness.v1.json !data/huggingface-cluster-promotion-receipts.v1.json !data/huggingface-cluster-promotion-receipts.v2.json !data/huggingface-cluster-catalog-gates.v1.json diff --git a/AGENTS.md b/AGENTS.md index f6bac9d7..9339e9af 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,6 +36,12 @@ These rules apply to AI-assisted work in this repository. `CONTRIBUTING.md` is t - Never commit `config.ini`, tokens, local paths, generated outputs, model caches, qualification workspaces, virtual environments, logs, or template media. - Public template media belongs in the configured public Hugging Face Dataset repository. Keep only its versioned source descriptor, hashes, and documentation in Git. +## Custom extensions + +- Read [Custom node development](docs/custom-nodes.md) before changing extension discovery, staging, enable, reload, or execution. Keep approvals outside source packages and bind them to inspected source and declared dependency versions. +- Executable custom Python uses explicit Add/Load/Reload and the existing executor. Bind source/dependency hashes internally; discovery, preview and graph import are not consent. Contract-only Blocks remain non-executable. +- A custom resource declaration is operator-reviewed code metadata, not catalog or hardware qualification. Do not execute custom suppliers during Auto inspection or assume their Python references are safe for early model eviction. + ## Quality And Evidence - Add a regression test that fails for the original defect and covers related instances of the same pattern. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da3f92fc..9be8b49d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,8 @@ integrating another machine's work, read and follow ## Development setup +For script-free `uv`/`npm` setup, use [Developer setup](docs/developer-setup.md). + Use Python 3.12 and create the same managed CPU profile used by baseline CI: ```bash @@ -26,7 +28,7 @@ uv pip install --python .venv/Scripts/python.exe -r requirements/test.txt .\.venv\Scripts\python.exe -m modiff.preflight --json --check-port 8088 --fail-on-error ``` -Choose the qualified accelerator profile relevant to a hardware-specific change and report that validation separately. Do not use `uv sync` or `uv run`: the project is intentionally `uv`-unmanaged because the installer, not the generic resolver, owns the executable Torch profile. +Choose the qualified accelerator profile relevant to a hardware-specific change and report that validation separately. Do not use `uv sync` or ordinary `uv run` (the documented `uv run --no-project --no-sync ... -m modiff.dev` bootstrap is the explicit exception): the project is intentionally `uv`-unmanaged because the installer, not the generic resolver, owns the executable Torch profile. Do not commit `config.ini`, `.env` files, model caches, generated outputs, local logs, virtual environments, or test caches. @@ -38,6 +40,10 @@ Do not commit `config.ini`, `.env` files, model caches, generated outputs, local - Treat file access, custom-module installation, remote code, token handling, and mutating routes as security-sensitive changes. - Avoid importing the full model registry from lightweight diagnostics such as preflight. +### Custom node development + +For local/Git Python nodes and pinned Hub Modular blocks, follow [Custom node development](docs/custom-nodes.md). Stage and inspect without imports, explicitly enable the exact code hash, then review/reload after edits. Do not add an unconditional startup import or install dependencies from discovery. Test stale code, relative helper isolation, approval rejection, import diagnostics, and cache ownership through ordinary graph dispatch. + ### Adding a node module Built-in node packages live under `modules//` and normally contain: @@ -100,6 +106,17 @@ and child generators. Generated `__pycache__` files inside a sealed overlay are integrity drift, not files to whitelist. Keep base-gate and optional-runtime results separate; skipped model-library tests are not execution coverage. +Pytest redirects the default extension store to a temporary directory before +collection so registry imports cannot execute or change the operator's installed +custom sources. Extension tests use explicit temporary roots. Subprocess tests +must also isolate extension discovery; they do not inherit Python monkeypatches. + +The checked-in image readiness inventory uses a fixed Linux/x86_64 reference +target; `scripts/generate_image_prototyping_readiness.py --check` must reproduce +it on every host. This static inventory is not local runtime readiness. Live +execution profiles continue to resolve the current OS/architecture and installed +runtime through the normal readiness boundary. + On a host with Git Bash or a POSIX shell: ```bash diff --git a/README.md b/README.md index 01331fdb..6a2f8099 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,57 @@ MoDiff is a local client/server application for building and running node-based machine-learning workflows with a focus on [Hugging Face Diffusers](https://github.com/huggingface/diffusers). The backend discovers Python node modules, executes graphs, manages models and generated media, and serves a bundled web client from `web/`. +The [Qwen-Image 2.1 integration guide](docs/qwen-image-21.md) describes its generic +image nodes, attention-context reuse, runtime requirements and qualification status. + +The [image demo guide](docs/image-demo.md) lists the tested workflows, settings, +measured reuse behavior and remaining qualification work. + +The [modularity walkthrough](docs/modularity-demo.md) covers editable generation +stages, a custom image-and-mask node, connected refinement, model switching and +saved-workflow restoration. + > [!CAUTION] > MoDiff is early-stage software. It is not a production service, a multi-user platform, or a security sandbox. The server has no authentication and can execute model workflows, import custom Python modules, and access files inside its configured working directory. Keep it bound to `127.0.0.1`, install only code you trust, and read [SECURITY.md](SECURITY.md) before changing its network exposure. +## Developer setup with uv and npm + +Install Git, [uv `0.11.26`](https://docs.astral.sh/uv/getting-started/installation/), +Node.js `24.12.0`, and npm `11.6.2`. uv can provision Python 3.12. +Use two terminals for the backend and the editable frontend. + +**Terminal 1 — backend:** clone both repositories into the same parent directory, +then start the backend. These commands work in Linux shells and Windows PowerShell. + +```text +git clone https://github.com/sdevil7th/MoDiff.git MoDiff +git clone https://github.com/sdevil7th/MoDiff-client.git MoDiff-client +cd MoDiff +uv run --no-project --no-sync --python 3.12 -m modiff.dev plan --accelerator cpu --backend-only --json +uv run --no-project --no-sync --python 3.12 -m modiff.dev setup --accelerator cpu --backend-only --non-interactive +uv run --no-project --no-sync --python 3.12 -m modiff.dev check --json --check-port 8088 --fail-on-error +uv run --no-project --no-sync --python 3.12 -m modiff.dev run +``` + +The CPU profile is for API/UI development. For NVIDIA inference, replace `cpu` +with `nvidia` in both `plan` and `setup`; other accelerators are covered in the +[full setup guide](docs/developer-setup.md). Setup preserves an existing `.venv` and +does not download model weights. Use the guide for deliberate environment repair; +ordinary `uv sync` is not supported. + +**Terminal 2 — frontend:** from the same parent directory, run: + +```text +cd MoDiff-client +npm ci +npm run dev +``` + +Keep the backend running at and open the URL printed by +Vite for the editable frontend. Press `Ctrl+C` in each terminal to stop it. +See the [full developer setup guide](docs/developer-setup.md) for accelerator prerequisites, +optional runtimes, repair, and bundled-app setup. + ## Before you install MoDiff is distributed as paired backend and client source checkouts. For normal @@ -194,18 +242,39 @@ cp config.example.ini config.ini `config.ini` is intentionally ignored because it may contain a Hugging Face token and machine-local paths. +## Developer tools and service prototyping + +For installation, use the [uv/npm quick start](#developer-setup-with-uv-and-npm) +above and the [full setup guide](docs/developer-setup.md). +See [service prototyping](docs/service-prototyping.md) to export a named service +interface from the editor. Services reuse the existing API graph and local runtime. + +The frontend has one developer-first editor. Start with **Workflows** for connected +task stages or choose **Templates**. Inspect implementation/docs and export graph +JSON or services without changing modes. **Memory: Automatic / Custom** remains +independent per workflow. + +Use **Add image / audio input** on a loader, or drag a media output onto an +operation. A dropdown lists supported roles; required stages are added inside +the existing graph, preserving prompts and branches as one Undo operation. +Video simplification is deferred. + +Generic Modular loader and node-field updates can run while another workflow is +generating without borrowing its model cache. See the [field-action ownership +contract](docs/api-reference.md#graph-execution-and-queue-state). + ## Managed installation profiles -MoDiff's installer owns the executable Python/Torch environment. The project is intentionally marked `uv`-unmanaged, so `uv sync` and `uv run` are not supported setup or launch commands. The installer stages a fresh environment, checks its package policy and a real device tensor, then atomically promotes it to `.venv/` while retaining the previous environment for rollback. When the sibling client is installed, setup also downloads and SHA-256 verifies the pinned rights-approved Template Gallery snapshot and bundles it under `web/template-gallery` so normal use does not wait on Hub media requests. Four permission-dependent preview files are currently unavailable; their templates remain usable and do not request those files. +MoDiff's installer owns the executable Python/Torch environment. The project is intentionally marked `uv`-unmanaged, so ordinary `uv sync` and `uv run` are not supported setup or launch commands. The explicit `uv run --no-project --no-sync ... -m modiff.dev` bootstrap described above delegates to this same installer without project resolution. The installer stages a fresh environment, checks its package policy and a real device tensor, then atomically promotes it to `.venv/` while retaining the previous environment for rollback. When the sibling client is installed, setup also downloads and SHA-256 verifies the pinned rights-approved Template Gallery snapshot and bundles it under `web/template-gallery` so normal use does not wait on Hub media requests. Four permission-dependent preview files are currently unavailable; their templates remain usable and do not request those files. -| Installer choice | Managed profile | Current scope | -| --- | --- | --- | -| `auto` | Host-dependent | Selects a qualified profile or a safe CPU fallback. | -| `nvidia` | `nvidia-cuda` | Linux/Windows NVIDIA with the reviewed CUDA 12.8 PyTorch profile. | -| `amd` | OS-dependent AMD profile | Qualified Linux AMD/ROCm hosts. Windows is a conditional official platform, but MoDiff blocks installation until the complete SDK wheel set and physical proof are pinned. | -| `intel` | `intel-xpu` | Preview PyTorch XPU profile for supported Intel Arc and integrated graphics on x86-64 Linux/Windows. | -| `mps` | `apple-mps` | Apple Silicon using the reviewed MPS-capable PyTorch profile. | -| `cpu` | `cpu` | Portable CPU environment for development and fallback. | +| Installer choice | Managed profile | Current scope | +| ---------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `auto` | Host-dependent | Selects a qualified profile or a safe CPU fallback. | +| `nvidia` | `nvidia-cuda` | Linux/Windows NVIDIA with the reviewed CUDA 12.8 PyTorch profile. | +| `amd` | OS-dependent AMD profile | Qualified Linux AMD/ROCm hosts. Windows is a conditional official platform, but MoDiff blocks installation until the complete SDK wheel set and physical proof are pinned. | +| `intel` | `intel-xpu` | Preview PyTorch XPU profile for supported Intel Arc and integrated graphics on x86-64 Linux/Windows. | +| `mps` | `apple-mps` | Apple Silicon using the reviewed MPS-capable PyTorch profile. | +| `cpu` | `cpu` | Portable CPU environment for development and fallback. | If an installation was interrupted, resume its external journal instead of starting unrelated setup work: @@ -290,6 +359,27 @@ See [docs/api-reference.md](docs/api-reference.md) for route groups and trust im The Modular Diffusers integration is documented in [modules/ModularDiffusers/README.md](modules/ModularDiffusers/README.md). MoDiff owns the pipeline configuration schema used by its dynamic node contracts while relying on upstream Diffusers for model and pipeline execution. +For the current task browser, picker behavior and qualification limits, see +[Workflow authoring and model selection](docs/workflow-authoring-ux.md). + +## Custom nodes + +Open **Nodes → Add custom node** for Local, Hugging Face or Git. Intentional Add/Load +validates and enables code in one action; remote revisions are pinned internally. +Drop a structured Python node file onto the canvas to import and insert it. +Files/packages in `custom/` appear automatically without executing; management +provides Load/Reload/Disable. Python runs with backend permissions, so only load +trusted code. Dependencies are checked, not installed automatically. +See [Developing custom nodes](docs/custom-nodes.md) for contracts and examples. + +Approved Modular blocks without model ports receive a **Models** input when their +Python contract requires components. Connect **Load Models → Pipeline Components** +to reuse compatible loaded weights. The [VAE reconstruction example](examples/custom_nodes/ModularImageReconstruction) +demonstrates this without separate family-specific nodes or implicit model downloads. +For additional weights, approved blocks with official component types also expose +**Load Models — [block name]**. Select pinned, downloaded component sources and use +**Custom** memory policy; connect the resulting components to the block's Models input. + ## Updating and recovery Stop the foreground application with `Ctrl+C` and update both sibling @@ -339,7 +429,9 @@ Do not edit `web/assets/index.js` or `web/assets/index.css` by hand. They are ge The normal installer materializes `web/template-gallery/` for that installation. Treat it as downloaded runtime data: do not add it to Git or a -normal remote-asset release package. +normal remote-asset release package. Gallery status and repair use the same +immutable Dataset manifest in both the remote release and installer-built +local bundle. For adjacent checkouts, an exact mirror can be performed with a platform tool after confirming both paths: diff --git a/SECURITY.md b/SECURITY.md index 1f7c4fae..417f9da5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,9 +12,9 @@ Do not expose MoDiff directly to an untrusted LAN, the public internet, a shared MoDiff is designed to execute Python and model code: -- Custom-module installation can clone a Git repository or copy a local directory into `custom/`, then import it into the live registry. +- Intentional Add/Load/Reload authorizes custom Python with backend permissions in one action, binding its exact source/dependency hash. Discovery, refresh and workflow imports do not grant permission. Failed imports remain disabled. Custom web fields share the code identity and browser-origin permissions. - Reviewed model-execution libraries maintained by Hugging Face run in the backend process with the same filesystem, network, CPU, and accelerator access as MoDiff. Official maintenance reduces neither package supply-chain risk nor the need to review the selected version and integration. -- Repository-supplied Python would run with backend-process permissions. Current custom Modular Diffusers paths are `contract_only` and reject `trust_remote_code` before model construction; exact cached 40-character commits are still required for Hub contract preview. Do not weaken that fail-closed boundary or accept moving branches/tags if executable support is added later. +- Repository-supplied Python would run with backend-process permissions. The historical custom Modular Diffusers paths are `contract_only` and reject `trust_remote_code` before model construction; exact cached 40-character commits are still required for Hub contract preview. That historical contract-only path remains fail closed. The separate [Custom nodes flow](docs/custom-nodes.md) can execute operator-approved, content-bound local copies of immutable Hub blocks; graph trust flags cannot authorize it. The Add flow resolves branches/tags to immutable commits before downloading or enabling code. - Model deserialization and optional native/CUDA packages have their own supply-chain and memory-safety risks. - Workflows can allocate substantial CPU, RAM, accelerator memory, disk, and network bandwidth. diff --git a/data/diffusers-operation-inventory.v1.json b/data/diffusers-operation-inventory.v1.json new file mode 100644 index 00000000..fa7f6ac9 --- /dev/null +++ b/data/diffusers-operation-inventory.v1.json @@ -0,0 +1,4708 @@ +{ + "contentHash": "sha256:7c657a6caa8dfa6ca1468dfa5a06c76b704811db2910c2bc9104f0b27a69e293", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "diffusersVersion": "0.41.0.dev0", + "pipelines": [ + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AceStepPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AllegroPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "AltDiffusionImg2ImgPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "AltDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "AmusedImg2ImgPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "AmusedInpaintPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "AmusedPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AnimaModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "image_to_image", + "workflowId": "img2img" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AnimateDiffControlNetPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AnimateDiffPAGPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AnimateDiffPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "AnimateDiffSDXLPipeline", + "reason": "The class requires an SDXL dual-text-encoder and compatible motion-adapter assembly, not the admitted SD1.5 AnimateDiff artifact contract.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "AnimateDiffSparseControlNetPipeline", + "reason": "The class requires a SparseControlNetModel, sparse frame indices, and an immutable sparse-control artifact selection that MoDiff has not admitted.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AnimateDiffVideoToVideoControlNetPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AnimateDiffVideoToVideoPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "AnyFlowFARPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_video", + "workflowId": null + }, + { + "source": "auto", + "task": "video_to_video", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "AnyFlowPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_video", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "AudioDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AudioLDM2Pipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_audio", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "AudioLDMPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "AuraFlowPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "BlipDiffusionControlNetPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "BlipDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "BriaFiboEditPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "BriaFiboPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "BriaPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ChromaImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ChromaInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ChromaPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "ChronoEditPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "CogVideoXFunControlPipeline", + "reason": "The class requires a CogVideoX-Fun control-video artifact and control contract; the admitted CogVideoX-2B selection and action are text-to-video only.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "CogVideoXImageToVideoPipeline", + "reason": "The admitted CogVideoX-2B artifact is text-to-video only and does not establish an image-conditioned transformer selection or image-to-video action contract.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "CogVideoXPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "CogVideoXVideoToVideoPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "CogView3PlusPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "CogView4ControlPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "CogView4Pipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "ConsisIDPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ConsistencyModelPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Cosmos2TextToImagePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Cosmos2VideoToWorldPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Cosmos2_5_PredictBasePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Cosmos2_5_TransferPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Cosmos3DistilledModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "text_to_video", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video", + "workflowId": "image2video" + }, + { + "source": "modular", + "task": "video_to_video", + "workflowId": "video2video" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Cosmos3OmniModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "text_to_video", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video", + "workflowId": "image2video" + }, + { + "source": "modular", + "task": "video_to_video", + "workflowId": "video2video" + }, + { + "source": "modular", + "task": "text_to_video_with_audio", + "workflowId": "text2video_with_sound" + }, + { + "source": "modular", + "task": "image_to_video_with_audio", + "workflowId": "image2video_with_sound" + }, + { + "source": "modular", + "task": "video_to_video_with_audio", + "workflowId": "video2video_with_sound" + }, + { + "source": "modular", + "task": "action_policy", + "workflowId": "action_policy" + }, + { + "source": "modular", + "task": "action_forward_dynamics", + "workflowId": "action_forward_dynamics" + }, + { + "source": "modular", + "task": "action_inverse_dynamics", + "workflowId": "action_inverse_dynamics" + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Cosmos3OmniPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "condition_to_video", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "CosmosTextToWorldPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "CosmosVideoToWorldPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "CycleDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "DDIMPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "DDPMPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "DanceDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "DiTPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "DiffusionGemmaPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "DiffusionPipeline", + "reason": "Generic factory/base class; it is not an exact task adapter.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "DreamLiteMobilePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "DreamLitePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "EasyAnimateControlPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "EasyAnimateInpaintPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "EasyAnimatePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ErnieImageModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ErnieImagePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Flux2KleinBaseModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "edit_image", + "workflowId": "image_conditioned" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Flux2KleinInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Flux2KleinKVPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [ + "Flux2KleinPipeline" + ], + "pipelineClass": "Flux2KleinModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "edit_image", + "workflowId": "image_conditioned" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Flux2KleinPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + }, + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Flux2ModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "edit_image", + "workflowId": "image_conditioned" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Flux2Pipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + }, + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxControlImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxControlInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxControlNetImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxControlNetInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxControlNetPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxControlPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxFillPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxKontextInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [ + "FluxKontextPipeline" + ], + "pipelineClass": "FluxKontextModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "edit_image", + "workflowId": "image_conditioned" + }, + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxKontextPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + }, + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [ + "FluxPipeline", + "FluxImg2ImgPipeline" + ], + "pipelineClass": "FluxModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "image_to_image", + "workflowId": "image2image" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "FluxPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "FluxReduxPipeline" + ], + "pipelineClass": "FluxPriorReduxPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "GlmImagePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "HeliosModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video", + "workflowId": "image2video" + }, + { + "source": "modular", + "task": "video_to_video", + "workflowId": "video2video" + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HeliosPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "HeliosPyramidDistilledModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video", + "workflowId": "image2video" + }, + { + "source": "modular", + "task": "video_to_video", + "workflowId": "video2video" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "HeliosPyramidModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video", + "workflowId": "image2video" + }, + { + "source": "modular", + "task": "video_to_video", + "workflowId": "video2video" + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HeliosPyramidPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HiDreamImagePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "HunyuanDiTControlNetPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "HunyuanDiTPAGPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "HunyuanDiTPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HunyuanImagePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HunyuanImageRefinerPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HunyuanSkyreelsImageToVideoPipeline", + "reason": "This SkyReels-specific Hunyuan image-to-video path has no exact immutable artifact, component recipe, or MoDiff action/workflow admission.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HunyuanVideo15ImageToVideoPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "HunyuanVideo15ModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video", + "workflowId": "image2video" + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HunyuanVideo15Pipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "HunyuanVideoFramepackPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HunyuanVideoImageToVideoPipeline", + "reason": "The classic HunyuanVideo image-to-video class is distinct from the admitted FramePack assembly and the separately reviewed HunyuanVideo-1.5 family; no exact artifact/action contract exists.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "HunyuanVideoPipeline", + "reason": "The classic HunyuanVideo text-to-video class is distinct from the admitted FramePack assembly and the separately reviewed HunyuanVideo-1.5 family; no exact artifact/action contract exists.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "I2VGenXLPipeline", + "reason": "The exact reviewed pin marks this video implementation with DeprecatedPipelineMixin, whose contract receives no further fixes or feature updates; MoDiff does not add new workflows for it.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "IFImg2ImgPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "IFImg2ImgSuperResolutionPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "IFInpaintingPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "IFInpaintingSuperResolutionPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "IFPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "IFSuperResolutionPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "contract-only", + "equivalentTo": [], + "pipelineClass": "Ideogram4ModularPipeline", + "reason": "Reviewed no-weight action/workflow truth exists, but no exact runnable mode is admitted.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Ideogram4Pipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "JoyImageEditPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "JoyImageEditPlusPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Kandinsky3Img2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Kandinsky3Pipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Kandinsky5I2IPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Kandinsky5I2VPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Kandinsky5T2IPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Kandinsky5T2VPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyCombinedPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyImg2ImgCombinedPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyImg2ImgPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyInpaintCombinedPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyInpaintPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyPriorPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22CombinedPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22ControlnetImg2ImgPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22ControlnetPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22Img2ImgCombinedPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22Img2ImgPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22InpaintCombinedPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22InpaintPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22Pipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22PriorEmb2EmbPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KandinskyV22PriorPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "KarrasVePipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KolorsImg2ImgPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KolorsPAGPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "KolorsPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "contract-only", + "equivalentTo": [], + "pipelineClass": "Krea2ModularPipeline", + "reason": "Reviewed no-weight action/workflow truth exists, but no exact runnable mode is admitted.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "Krea2Pipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "contract-only", + "equivalentTo": [], + "pipelineClass": "Krea2TurboModularPipeline", + "reason": "Reviewed no-weight action/workflow truth exists, but no exact runnable mode is admitted.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "LDMPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "LDMSuperResolutionPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "LDMTextToImagePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "LLaDA2Pipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "contract-only", + "equivalentTo": [], + "pipelineClass": "LTX25ModularPipeline", + "reason": "Reviewed no-weight action/workflow truth exists, but no exact runnable mode is admitted.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video_with_audio", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video_with_audio", + "workflowId": "image2video" + }, + { + "source": "modular", + "task": "condition_to_video_with_audio", + "workflowId": "condition" + }, + { + "source": "modular", + "task": "in_context_to_video_with_audio", + "workflowId": "in_context" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LTX2ConditionPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "LTX2DFRPipeline", + "reason": "Video diffusion-frame refinement is deferred from the current image/audio release scope.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "LTX2DFRTemporalRefinePipeline", + "reason": "Video temporal refinement is deferred from the current image/audio release scope.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "LTX2HDRPipeline", + "reason": "The class consumes an SDR reference video and an HDR IC-LoRA, then emits linear HDR video; MoDiff lacks that immutable adapter selection, HDR output contract, and exact action/workflow.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "LTX2ConditionPipeline" + ], + "pipelineClass": "LTX2ImageToVideoPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LTX2InContextPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "LTX2LatentUpsamplePipeline", + "reason": "The latent upsampler is a separate second-stage component; MoDiff has no immutable upsampler selection or bounded latent-input action/workflow for this class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "LTX2ConditionPipeline", + "LTX2InContextPipeline" + ], + "pipelineClass": "LTX2ModularPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video_with_audio", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video_with_audio", + "workflowId": "image2video" + }, + { + "source": "modular", + "task": "condition_to_video_with_audio", + "workflowId": "condition" + }, + { + "source": "modular", + "task": "in_context_to_video_with_audio", + "workflowId": "in_context" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LTX2Pipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "LTX2VideoDiffusionDecodePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LTXConditionPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LTXI2VLongMultiPromptPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "LTXConditionPipeline" + ], + "pipelineClass": "LTXImageToVideoPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_video", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "LTXLatentUpsamplePipeline", + "reason": "The latent upsampler is a separate second-stage component; MoDiff has no immutable upsampler selection or bounded latent-input action/workflow for this class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "LTXConditionPipeline" + ], + "pipelineClass": "LTXModularPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video", + "workflowId": "text2video" + }, + { + "source": "modular", + "task": "image_to_video", + "workflowId": "image2video" + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "LTXConditionPipeline" + ], + "pipelineClass": "LTXPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_video", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LatentConsistencyModelImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LatentConsistencyModelPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LattePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LongCatAudioDiTPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_audio", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LongCatImageEditPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LongCatImagePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "LucyEditPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "Lumina2Pipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "Lumina2Pipeline" + ], + "pipelineClass": "Lumina2Text2ImgPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "LuminaPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "LuminaPipeline" + ], + "pipelineClass": "LuminaText2ImgPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "MarigoldDepthPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "MarigoldIntrinsicsPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "MarigoldNormalsPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "MiniMaxH3ModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video_with_audio", + "workflowId": "t2va" + }, + { + "source": "modular", + "task": "first_last_frame_to_video_with_audio", + "workflowId": "fl2va" + }, + { + "source": "modular", + "task": "reference_to_video_with_audio", + "workflowId": "ref2va" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "MiniMaxMusic3ModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_audio", + "workflowId": "default" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "MochiPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "ModularPipeline", + "reason": "Generic composition base class; reviewed subclasses are inventoried separately.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "MotifVideoImage2VideoPipeline", + "reason": "The Motif-Video image-to-video family, guider, and model artifacts have no immutable MoDiff admission, resource envelope, or exact action/workflow.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "MotifVideoPipeline", + "reason": "The Motif-Video text-to-video family, guider, and model artifacts have no immutable MoDiff admission, resource envelope, or exact action/workflow.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "MusicLDMPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "NucleusMoEImagePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "OmniGenPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "OnnxStableDiffusionImg2ImgPipeline", + "reason": "ONNX is outside MoDiff's reviewed local execution dependency boundary.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "OnnxStableDiffusionInpaintPipeline", + "reason": "ONNX is outside MoDiff's reviewed local execution dependency boundary.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "OnnxStableDiffusionPipeline", + "reason": "ONNX is outside MoDiff's reviewed local execution dependency boundary.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "OnnxStableDiffusionUpscalePipeline", + "reason": "ONNX is outside MoDiff's reviewed local execution dependency boundary.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "OvisImagePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "PIAPipeline", + "reason": "The exact reviewed pin marks this video implementation with DeprecatedPipelineMixin, whose contract receives no further fixes or feature updates; MoDiff does not add new workflows for it.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "PNDMPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "PRXPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "PRXPixelPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "PaintByExamplePipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "PixArtAlphaPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "PixArtSigmaPAGPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "PixArtSigmaPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImage21Pipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "QwenImageControlNetInpaintPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageControlNetPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageEditInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageEditModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "edit_image", + "workflowId": "image_conditioned" + }, + { + "source": "modular", + "task": "inpaint", + "workflowId": "image_conditioned_inpainting" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageEditPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageEditPlusModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "default", + "workflowId": "default" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageEditPlusPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageLayeredModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "layer_decomposition", + "workflowId": "default" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageLayeredPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImageModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "image_to_image", + "workflowId": "image2image" + }, + { + "source": "modular", + "task": "inpaint", + "workflowId": "inpainting" + }, + { + "source": "modular", + "task": "control_image", + "workflowId": "controlnet_text2image" + }, + { + "source": "modular", + "task": "controlnet_image2image", + "workflowId": "controlnet_image2image" + }, + { + "source": "modular", + "task": "controlnet_inpainting", + "workflowId": "controlnet_inpainting" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "QwenImagePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "RePaintPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "SanaControlNetPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "SanaImageToVideoPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "SanaPAGPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "SanaPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "SanaSprintImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "SanaSprintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "SanaVideoPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "ScoreSdeVePipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "SemanticStableDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ShapEImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ShapEPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "SkyReelsV2DiffusionForcingImageToVideoPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "SkyReelsV2DiffusionForcingPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "SkyReelsV2DiffusionForcingVideoToVideoPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "SkyReelsV2ImageToVideoPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "SkyReelsV2Pipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "SpectrogramDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableAudio3AudioToAudioPipeline", + "reason": "The exact reviewed Diffusers source defines this bounded audio task, but Stability AI's gated checkpoints are not published in Diffusers format. Upstream requires a local conversion, so MoDiff has no immutable app-downloadable pipeline artifact or live qualification. CPU and MPS must remain float32 unless later exact evidence qualifies another dtype.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableAudio3InpaintPipeline", + "reason": "The exact reviewed Diffusers source defines this bounded audio task, but Stability AI's gated checkpoints are not published in Diffusers format. Upstream requires a local conversion, so MoDiff has no immutable app-downloadable pipeline artifact or live qualification. CPU and MPS must remain float32 unless later exact evidence qualifies another dtype.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableAudio3Pipeline", + "reason": "The exact reviewed Diffusers source defines this bounded audio task, but Stability AI's gated checkpoints are not published in Diffusers format. Upstream requires a local conversion, so MoDiff has no immutable app-downloadable pipeline artifact or live qualification. CPU and MPS must remain float32 unless later exact evidence qualifies another dtype.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_audio", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableAudioPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_audio", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableCascadeCombinedPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableCascadeDecoderPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableCascadePriorPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusion3ControlNetInpaintingPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusion3ControlNetPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusion3Img2ImgPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusion3InpaintPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "contract-only", + "equivalentTo": [], + "pipelineClass": "StableDiffusion3ModularPipeline", + "reason": "Reviewed no-weight action/workflow truth exists, but no exact runnable mode is admitted.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "image_to_image", + "workflowId": "image2image" + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusion3PAGImg2ImgPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusion3PAGPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusion3Pipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionAdapterPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionAttendAndExcitePipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionControlNetImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionControlNetInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionControlNetPAGInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionControlNetPAGPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionControlNetPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionControlNetXSPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionDepth2ImgPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionDiffEditPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionGLIGENPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionGLIGENTextImagePipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionImageVariationPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionInstructPix2PixPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionLDM3DPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionLatentUpscalePipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionModelEditingPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionOnnxPipeline", + "reason": "ONNX is outside MoDiff's reviewed local execution dependency boundary.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionPAGImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionPAGInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionPAGPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionPanoramaPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionParadigmsPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionPix2PixZeroPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionSAGPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionUpscalePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLAdapterPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetPAGImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetPAGPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetUnionImg2ImgPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetUnionInpaintPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetUnionPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLControlNetXSPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLInstructPix2PixPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "image_to_image", + "workflowId": "image2image" + }, + { + "source": "modular", + "task": "inpaint", + "workflowId": "inpainting" + }, + { + "source": "modular", + "task": "control_image", + "workflowId": "controlnet_text2image" + }, + { + "source": "modular", + "task": "control_edit_image", + "workflowId": "controlnet_image2image" + }, + { + "source": "modular", + "task": "control_inpaint", + "workflowId": "controlnet_inpainting" + }, + { + "source": "modular", + "task": "controlnet_union_text2image", + "workflowId": "controlnet_union_text2image" + }, + { + "source": "modular", + "task": "controlnet_union_image2image", + "workflowId": "controlnet_union_image2image" + }, + { + "source": "modular", + "task": "controlnet_union_inpainting", + "workflowId": "controlnet_union_inpainting" + }, + { + "source": "modular", + "task": "ip_adapter_text2image", + "workflowId": "ip_adapter_text2image" + }, + { + "source": "modular", + "task": "ip_adapter_image2image", + "workflowId": "ip_adapter_image2image" + }, + { + "source": "modular", + "task": "ip_adapter_inpainting", + "workflowId": "ip_adapter_inpainting" + }, + { + "source": "modular", + "task": "ip_adapter_controlnet_text2image", + "workflowId": "ip_adapter_controlnet_text2image" + }, + { + "source": "modular", + "task": "ip_adapter_controlnet_image2image", + "workflowId": "ip_adapter_controlnet_image2image" + }, + { + "source": "modular", + "task": "ip_adapter_controlnet_inpainting", + "workflowId": "ip_adapter_controlnet_inpainting" + }, + { + "source": "modular", + "task": "ip_adapter_controlnet_union_text2image", + "workflowId": "ip_adapter_controlnet_union_text2image" + }, + { + "source": "modular", + "task": "ip_adapter_controlnet_union_image2image", + "workflowId": "ip_adapter_controlnet_union_image2image" + }, + { + "source": "modular", + "task": "ip_adapter_controlnet_union_inpainting", + "workflowId": "ip_adapter_controlnet_union_inpainting" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLPAGImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLPAGInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLPAGPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableDiffusionXLPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableUnCLIPImg2ImgPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "StableUnCLIPPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "StableVideoDiffusionPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "TextToVideoSDPipeline", + "reason": "The exact reviewed pin marks this video implementation with DeprecatedPipelineMixin, whose contract receives no further fixes or feature updates; MoDiff does not add new workflows for it.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "TextToVideoZeroPipeline", + "reason": "The exact reviewed pin marks this video implementation with DeprecatedPipelineMixin, whose contract receives no further fixes or feature updates; MoDiff does not add new workflows for it.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "TextToVideoZeroSDXLPipeline", + "reason": "The exact reviewed pin marks this video implementation with DeprecatedPipelineMixin, whose contract receives no further fixes or feature updates; MoDiff does not add new workflows for it.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "UnCLIPImageVariationPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "UnCLIPPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "UniDiffuserPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "VQDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "VersatileDiffusionDualGuidedPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "VersatileDiffusionImageVariationPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "VersatileDiffusionPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "VersatileDiffusionTextToImagePipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "VideoToVideoSDPipeline", + "reason": "The exact reviewed pin marks this video implementation with DeprecatedPipelineMixin, whose contract receives no further fixes or feature updates; MoDiff does not add new workflows for it.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "VisualClozeGenerationPipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "VisualClozePipeline", + "reason": "Immutable source/artifact research exists, but executable admission remains blocked or pending.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "WanImageToVideoPipeline" + ], + "pipelineClass": "Wan22Image2VideoModularPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "modular", + "task": "image_to_video", + "workflowId": "default" + } + ] + }, + { + "coverage": "equivalent", + "equivalentTo": [ + "WanPipeline" + ], + "pipelineClass": "Wan22ModularPipeline", + "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video", + "workflowId": "default" + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "Wan22VaceModularPipeline", + "reason": "This newly exported video composition is deferred from the current image/audio release scope.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "WanAnimate2DistilledModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "character_animate", + "workflowId": "default" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "WanAnimate2ModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "character_animate", + "workflowId": "default" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "WanAnimatePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "WanImage2VideoModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "image_to_video", + "workflowId": "image2video" + }, + { + "source": "modular", + "task": "flf2v", + "workflowId": "flf2v" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "WanImageToVideoPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_video", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [ + "WanPipeline" + ], + "pipelineClass": "WanModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_video", + "workflowId": "default" + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "WanPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_video", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "WanVACEPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "WanVideoToVideoPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "video_to_video", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "WuerstchenCombinedPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "WuerstchenDecoderPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "intentionally-excluded", + "equivalentTo": [], + "pipelineClass": "WuerstchenPriorPipeline", + "reason": "The exact reviewed pin exports this implementation from Diffusers' deprecated namespace; MoDiff does not admit new workflows against upstream-deprecated pipeline surfaces.", + "upstreamTasks": [ + { + "source": "export", + "task": "pipeline_call", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "ZImageControlNetInpaintPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "ZImageControlNetPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ZImageImg2ImgPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "image_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ZImageInpaintPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "inpaint", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [ + "ZImagePipeline", + "ZImageImg2ImgPipeline" + ], + "pipelineClass": "ZImageModularPipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "modular", + "task": "text_to_image", + "workflowId": "text2image" + }, + { + "source": "modular", + "task": "image_to_image", + "workflowId": "image2image" + } + ] + }, + { + "coverage": "research-blocked", + "equivalentTo": [], + "pipelineClass": "ZImageOmniPipeline", + "reason": "The exact reviewed source was triaged, but this class/mode has no exact MoDiff execution specification backed by a reviewed immutable artifact selection; support for a related family is not equivalence.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + }, + { + "coverage": "executable", + "equivalentTo": [], + "pipelineClass": "ZImagePipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "upstreamTasks": [ + { + "source": "auto", + "task": "text_to_image", + "workflowId": null + } + ] + } + ], + "schemaVersion": 1, + "sources": { + "autoTasks": "sha256:fc1c531e9dbf7e2fc62791180430eb6b909bb827a0eaa029991da7d8beb2d205", + "exports": "sha256:f9536d3cd5f2f5df8992fa4b4b0fb53a407eea15744fcc288760624ce9111251", + "modularTasks": "sha256:8dad3f4ef31b456ef9c6e46718be9a17c023ae4306fdba129c72eeeed6573dc3" + } +} diff --git a/data/image-prototyping-readiness.v1.json b/data/image-prototyping-readiness.v1.json new file mode 100644 index 00000000..6472629f --- /dev/null +++ b/data/image-prototyping-readiness.v1.json @@ -0,0 +1,16019 @@ +{ + "contentHash": "sha256:9877eca76b75cf9cd639437487570a1dc28826509ff159bb493b25fa9c10f3d1", + "coverageBoundary": { + "backendArtifactPins": "snapshotted", + "backendExecutionProfiles": "snapshotted", + "backendModelCapabilities": "snapshotted", + "clientTemplatesAndTaskChoosers": "requires_paired_client_gate", + "customFirstPartyExamples": "requires_paired_custom_e2e", + "upstreamModularWorkflows": "snapshotted" + }, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "imageTasks": [ + "control_edit_image", + "control_image", + "control_inpaint", + "control_union_edit_image", + "control_union_image", + "control_union_inpaint", + "depth_estimation", + "edit_image", + "image_adjustment", + "image_channels", + "image_crop", + "image_filter", + "image_stitch", + "image_tile", + "image_to_image", + "image_to_text", + "image_upscale", + "inpaint", + "inpainting", + "ip_adapter_control_edit_image", + "ip_adapter_control_image", + "ip_adapter_control_inpaint", + "ip_adapter_control_union_edit_image", + "ip_adapter_control_union_image", + "ip_adapter_control_union_inpaint", + "ip_adapter_edit_image", + "ip_adapter_image", + "ip_adapter_inpaint", + "layer_decomposition", + "mask_composite", + "modular_image_to_image", + "modular_inpainting", + "modular_text_to_image", + "multi_image_reference_edit", + "outpaint", + "text_to_image", + "unconditional_image" + ], + "kind": "image_prototyping_readiness", + "routes": [ + { + "advertisementEntryPoints": [ + "execution_profile:anima:official-modular-workflow", + "model_capability:AnimaModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:anima:modular-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 16, + "exactBytes": 5642032794, + "gated": false, + "inventoryState": "exact_bytes", + "license": "circlestone-labs-non-commercial-license-v1.0", + "repository": "circlestone-labs/Anima-Base-v1.0-Diffusers", + "revision": "073c3a9db359c31ad0e8aa268d15775473c2176c" + }, + "artifactVariantOf": null, + "canonicalTask": "image_to_image", + "displayName": "Anima Base v1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "AnimaModularPipeline", + "profileId": "anima:official-modular-workflow" + }, + "integratedNativeAlternativeProfileIds": [ + "anima:official-modular-workflow" + ], + "knownDefects": [], + "modelType": "AnimaModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "anima:official-modular-workflow" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "anima:official-modular-workflow/image_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "image_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "AnimaModularPipeline", + "nativeWorkflowId": "img2img", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:anima:official-modular-workflow", + "model_capability:AnimaModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:anima:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 16, + "exactBytes": 5642032794, + "gated": false, + "inventoryState": "exact_bytes", + "license": "circlestone-labs-non-commercial-license-v1.0", + "repository": "circlestone-labs/Anima-Base-v1.0-Diffusers", + "revision": "073c3a9db359c31ad0e8aa268d15775473c2176c" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Anima Base v1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "AnimaModularPipeline", + "profileId": "anima:official-modular-workflow" + }, + "integratedNativeAlternativeProfileIds": [ + "anima:official-modular-workflow" + ], + "knownDefects": [], + "modelType": "AnimaModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "anima:official-modular-workflow" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "anima:official-modular-workflow/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "AnimaModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:auraflow-v0.3:direct", + "model_capability:AuraFlowPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:auraflow-v0.3:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 18, + "exactBytes": 16837479394, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "fal/AuraFlow-v0.3", + "revision": "2cd8588f04c886002be4571697d84654a50e3af3" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "AuraFlow v0.3 1536px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "AuraFlowPipeline", + "profileId": "auraflow-v0.3:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "AuraFlowPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "auraflow-v0.3:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "auraflow-v0.3:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:builtin-image-operations:direct", + "model_capability:BuiltinImageOperation", + "node_model_dropdown:modules.ImageOperations.ProcessImage", + "studio_execution_spec:builtin-image-operations:image-adjustment:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": null, + "repository": "builtin://modiff/image-operations/v1", + "revision": null + }, + "artifactVariantOf": null, + "canonicalTask": "image_adjustment", + "displayName": "Built-in Image Operations", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "builtin-image-operation", + "loader": "modules.ImageOperations.ProcessImage", + "pipelineClass": "BuiltinImageOperationV1", + "profileId": "builtin-image-operations:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "BuiltinImageOperation", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "builtin-image-operations:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "builtin-image-operations:direct/image_adjustment", + "standardAndNativeAlternativesCoexist": false, + "task": "image_adjustment", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:builtin-image-operations:direct", + "model_capability:BuiltinImageOperation", + "node_model_dropdown:modules.ImageOperations.ProcessImage", + "studio_execution_spec:builtin-image-operations:image-channels:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": null, + "repository": "builtin://modiff/image-operations/v1", + "revision": null + }, + "artifactVariantOf": null, + "canonicalTask": "image_channels", + "displayName": "Built-in Image Operations", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "builtin-image-operation", + "loader": "modules.ImageOperations.ProcessImage", + "pipelineClass": "BuiltinImageOperationV1", + "profileId": "builtin-image-operations:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "BuiltinImageOperation", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "builtin-image-operations:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "builtin-image-operations:direct/image_channels", + "standardAndNativeAlternativesCoexist": false, + "task": "image_channels", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:builtin-image-operations:direct", + "model_capability:BuiltinImageOperation", + "node_model_dropdown:modules.ImageOperations.ProcessImage", + "studio_execution_spec:builtin-image-operations:image-crop:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": null, + "repository": "builtin://modiff/image-operations/v1", + "revision": null + }, + "artifactVariantOf": null, + "canonicalTask": "image_crop", + "displayName": "Built-in Image Operations", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "builtin-image-operation", + "loader": "modules.ImageOperations.ProcessImage", + "pipelineClass": "BuiltinImageOperationV1", + "profileId": "builtin-image-operations:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "BuiltinImageOperation", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "builtin-image-operations:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "builtin-image-operations:direct/image_crop", + "standardAndNativeAlternativesCoexist": false, + "task": "image_crop", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:builtin-image-operations:direct", + "model_capability:BuiltinImageOperation", + "node_model_dropdown:modules.ImageOperations.ProcessImage", + "studio_execution_spec:builtin-image-operations:image-filter:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": null, + "repository": "builtin://modiff/image-operations/v1", + "revision": null + }, + "artifactVariantOf": null, + "canonicalTask": "image_filter", + "displayName": "Built-in Image Operations", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "builtin-image-operation", + "loader": "modules.ImageOperations.ProcessImage", + "pipelineClass": "BuiltinImageOperationV1", + "profileId": "builtin-image-operations:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "BuiltinImageOperation", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "builtin-image-operations:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "builtin-image-operations:direct/image_filter", + "standardAndNativeAlternativesCoexist": false, + "task": "image_filter", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:builtin-image-operations:direct", + "model_capability:BuiltinImageOperation", + "node_model_dropdown:modules.ImageOperations.ProcessImage", + "studio_execution_spec:builtin-image-operations:image-stitch:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": null, + "repository": "builtin://modiff/image-operations/v1", + "revision": null + }, + "artifactVariantOf": null, + "canonicalTask": "image_stitch", + "displayName": "Built-in Image Operations", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "builtin-image-operation", + "loader": "modules.ImageOperations.ProcessImage", + "pipelineClass": "BuiltinImageOperationV1", + "profileId": "builtin-image-operations:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "BuiltinImageOperation", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "builtin-image-operations:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "builtin-image-operations:direct/image_stitch", + "standardAndNativeAlternativesCoexist": false, + "task": "image_stitch", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:builtin-image-operations:direct", + "model_capability:BuiltinImageOperation", + "node_model_dropdown:modules.ImageOperations.ProcessImage", + "studio_execution_spec:builtin-image-operations:image-tile:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": null, + "repository": "builtin://modiff/image-operations/v1", + "revision": null + }, + "artifactVariantOf": null, + "canonicalTask": "image_tile", + "displayName": "Built-in Image Operations", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "builtin-image-operation", + "loader": "modules.ImageOperations.ProcessImage", + "pipelineClass": "BuiltinImageOperationV1", + "profileId": "builtin-image-operations:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "BuiltinImageOperation", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "builtin-image-operations:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "builtin-image-operations:direct/image_tile", + "standardAndNativeAlternativesCoexist": false, + "task": "image_tile", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:builtin-image-operations:direct", + "model_capability:BuiltinImageOperation", + "node_model_dropdown:modules.ImageOperations.ProcessImage", + "studio_execution_spec:builtin-image-operations:image-upscale:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": null, + "repository": "builtin://modiff/image-operations/v1", + "revision": null + }, + "artifactVariantOf": null, + "canonicalTask": "image_upscale", + "displayName": "Built-in Image Operations", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "builtin-image-operation", + "loader": "modules.ImageOperations.ProcessImage", + "pipelineClass": "BuiltinImageOperationV1", + "profileId": "builtin-image-operations:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "BuiltinImageOperation", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "builtin-image-operations:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "builtin-image-operations:direct/image_upscale", + "standardAndNativeAlternativesCoexist": false, + "task": "image_upscale", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:builtin-image-operations:direct", + "model_capability:BuiltinImageOperation", + "node_model_dropdown:modules.ImageOperations.ProcessImage", + "studio_execution_spec:builtin-image-operations:mask-composite:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": null, + "repository": "builtin://modiff/image-operations/v1", + "revision": null + }, + "artifactVariantOf": null, + "canonicalTask": "mask_composite", + "displayName": "Built-in Image Operations", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "builtin-image-operation", + "loader": "modules.ImageOperations.ProcessImage", + "pipelineClass": "BuiltinImageOperationV1", + "profileId": "builtin-image-operations:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "BuiltinImageOperation", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "builtin-image-operations:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "builtin-image-operations:direct/mask_composite", + "standardAndNativeAlternativesCoexist": false, + "task": "mask_composite", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:chroma1-hd-img2img:direct", + "model_capability:ChromaImg2ImgPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:chroma1-hd-img2img:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 18, + "exactBytes": 27493360428, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "lodestones/Chroma1-HD", + "revision": "0e0c60ece1e82b17cb7f77342d765ba5024c40c0" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "Chroma1-HD Image-to-Image", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ChromaImg2ImgPipeline", + "profileId": "chroma1-hd-img2img:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "ChromaImg2ImgPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "chroma1-hd-img2img:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "chroma1-hd-img2img:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:chroma1-hd-inpaint:direct", + "model_capability:ChromaInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:chroma1-hd-inpaint:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 18, + "exactBytes": 27493360428, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "lodestones/Chroma1-HD", + "revision": "0e0c60ece1e82b17cb7f77342d765ba5024c40c0" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "Chroma1-HD Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ChromaInpaintPipeline", + "profileId": "chroma1-hd-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "ChromaInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "chroma1-hd-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "chroma1-hd-inpaint:direct/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:chroma1-hd-inpaint:direct", + "model_capability:ChromaInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:chroma1-hd-inpaint:outpaint:v1" + ], + "artifact": { + "declaredFileCount": 18, + "exactBytes": 27493360428, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "lodestones/Chroma1-HD", + "revision": "0e0c60ece1e82b17cb7f77342d765ba5024c40c0" + }, + "artifactVariantOf": null, + "canonicalTask": "outpaint", + "displayName": "Chroma1-HD Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ChromaInpaintPipeline", + "profileId": "chroma1-hd-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "ChromaInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "chroma1-hd-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "chroma1-hd-inpaint:direct/outpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "outpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:chroma1-hd:direct", + "model_capability:ChromaPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:chroma1-hd:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 18, + "exactBytes": 27493360428, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "lodestones/Chroma1-HD", + "revision": "0e0c60ece1e82b17cb7f77342d765ba5024c40c0" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Chroma1-HD 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ChromaPipeline", + "profileId": "chroma1-hd:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "ChromaPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "chroma1-hd:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "chroma1-hd:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:cogview3-plus-3b:direct", + "model_capability:CogView3PlusPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:cogview3-plus-3b:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 20, + "exactBytes": 25560117047, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "zai-org/CogView3-Plus-3B", + "revision": "5d70e40732ac0efac98524c51a7fa9c82707f1e5" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "CogView3 Plus 3B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "CogView3PlusPipeline", + "profileId": "cogview3-plus-3b:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "CogView3PlusPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "cogview3-plus-3b:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "cogview3-plus-3b:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:cogview4-6b:direct", + "model_capability:CogView4Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:cogview4-6b:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 31129025003, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "zai-org/CogView4-6B", + "revision": "63a52b7f6dace7033380cd6da14d0915eab3e6b5" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "CogView4 6B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "CogView4Pipeline", + "profileId": "cogview4-6b:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "CogView4Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "cogview4-6b:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "cogview4-6b:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:consistency-imagenet64:direct", + "model_capability:ConsistencyModelPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:consistency-imagenet64:unconditional-image:v1" + ], + "artifact": { + "declaredFileCount": 6, + "exactBytes": 1183678409, + "gated": false, + "inventoryState": "exact_bytes", + "license": "mit", + "repository": "openai/diffusers-cd_imagenet64_l2", + "revision": "5f462e4403fc37b72ec6004e806c71805db22387" + }, + "artifactVariantOf": null, + "canonicalTask": "unconditional_image", + "displayName": "Consistency Model ImageNet 64x64", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ConsistencyModelPipeline", + "profileId": "consistency-imagenet64:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "ConsistencyModelPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "consistency-imagenet64:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "consistency-imagenet64:direct/unconditional_image", + "standardAndNativeAlternativesCoexist": false, + "task": "unconditional_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:cosmos3-distilled-text-to-image:official-modular-workflow", + "model_capability:Cosmos3DistilledModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:cosmos3-distilled:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 44, + "exactBytes": 131407991643, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openmdw-1.1", + "repository": "nvidia/Cosmos3-Super-Text2Image-4Step", + "revision": "aa0d5a57b7b045d68daa60fbacd84ec723c7cb7b" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Cosmos3-Super 4-Step", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Cosmos3DistilledModularPipeline", + "profileId": "cosmos3-distilled-text-to-image:official-modular-workflow" + }, + "integratedNativeAlternativeProfileIds": [ + "cosmos3-distilled-text-to-image:official-modular-workflow" + ], + "knownDefects": [], + "modelType": "Cosmos3DistilledModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "cosmos3-distilled-text-to-image:official-modular-workflow" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "cosmos3-distilled-text-to-image:official-modular-workflow/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Cosmos3OmniModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:cosmos3-nano:official-modular-workflow", + "model_capability:Cosmos3OmniModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:cosmos3-nano:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 24, + "exactBytes": 33758023657, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openmdw-1.1", + "repository": "nvidia/Cosmos3-Nano", + "revision": "7a312c868bcce8e40b3eb40861300a9d0ba3fde1" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Cosmos3-Nano", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Cosmos3OmniModularPipeline", + "profileId": "cosmos3-nano:official-modular-workflow" + }, + "integratedNativeAlternativeProfileIds": [ + "cosmos3-nano:official-modular-workflow" + ], + "knownDefects": [], + "modelType": "Cosmos3OmniModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "cosmos3-nano:official-modular-workflow" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "cosmos3-nano:official-modular-workflow/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Cosmos3OmniModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt", + "postprocess_image" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:ddim-cifar10:direct", + "model_capability:DDIMPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:ddim-cifar10:unconditional-image:v1" + ], + "artifact": { + "declaredFileCount": 6, + "exactBytes": 143025496, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "google/ddpm-cifar10-32", + "revision": "267b167dc01f0e4e61923ea244e8b988f84deb80" + }, + "artifactVariantOf": null, + "canonicalTask": "unconditional_image", + "displayName": "DDIM CIFAR-10 32x32", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "DDIMPipeline", + "profileId": "ddim-cifar10:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "DDIMPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "ddim-cifar10:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "ddim-cifar10:direct/unconditional_image", + "standardAndNativeAlternativesCoexist": false, + "task": "unconditional_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:ddpm-cifar10:direct", + "model_capability:DDPMPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:ddpm-cifar10:unconditional-image:v1" + ], + "artifact": { + "declaredFileCount": 6, + "exactBytes": 143025496, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "google/ddpm-cifar10-32", + "revision": "267b167dc01f0e4e61923ea244e8b988f84deb80" + }, + "artifactVariantOf": null, + "canonicalTask": "unconditional_image", + "displayName": "DDPM CIFAR-10 32x32", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "DDPMPipeline", + "profileId": "ddpm-cifar10:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "DDPMPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "ddpm-cifar10:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "ddpm-cifar10:direct/unconditional_image", + "standardAndNativeAlternativesCoexist": false, + "task": "unconditional_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:depth-anything-v2-metric-outdoor-small:direct", + "model_capability:DepthAnythingV2MetricModel", + "node_model_dropdown:modules.HuggingFaceTransformers.LoadDepthEstimationModel", + "studio_execution_spec:depth-anything-v2-metric-outdoor-small:depth-estimation:v1" + ], + "artifact": { + "declaredFileCount": 3, + "exactBytes": 99175160, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "depth-anything/Depth-Anything-V2-Metric-Outdoor-Small-hf", + "revision": "fd2c22027eaf20374204f14099b8341e1925ad39" + }, + "artifactVariantOf": null, + "canonicalTask": "depth_estimation", + "displayName": "Depth Anything V2 Metric Outdoor Small", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-huggingface-transformers-depth", + "loader": "modules.HuggingFaceTransformers.LoadDepthEstimationModel", + "pipelineClass": "AutoModelForDepthEstimation", + "profileId": "depth-anything-v2-metric-outdoor-small:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "DepthAnythingV2MetricModel", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "depth-anything-v2-metric-outdoor-small:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "depth-anything-v2-metric-outdoor-small:direct/depth_estimation", + "standardAndNativeAlternativesCoexist": false, + "task": "depth_estimation", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:depth-anything-v2-small:direct", + "model_capability:DepthAnythingV2Model", + "node_model_dropdown:modules.HuggingFaceTransformers.LoadDepthEstimationModel", + "studio_execution_spec:depth-anything-v2-small:depth-estimation:v1" + ], + "artifact": { + "declaredFileCount": 3, + "exactBytes": 99175385, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "depth-anything/Depth-Anything-V2-Small-hf", + "revision": "5426e4f0f36572d16453bbda7a8389317b1bef99" + }, + "artifactVariantOf": null, + "canonicalTask": "depth_estimation", + "displayName": "Depth Anything V2 Small", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-huggingface-transformers-depth", + "loader": "modules.HuggingFaceTransformers.LoadDepthEstimationModel", + "pipelineClass": "AutoModelForDepthEstimation", + "profileId": "depth-anything-v2-small:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "DepthAnythingV2Model", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "depth-anything-v2-small:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "depth-anything-v2-small:direct/depth_estimation", + "standardAndNativeAlternativesCoexist": false, + "task": "depth_estimation", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:dreamlite-base:direct", + "model_capability:DreamLitePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:dreamlite-base:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 27, + "exactBytes": 5071894178, + "gated": false, + "inventoryState": "exact_bytes", + "license": "cc-by-nc-4.0", + "repository": "carlofkl/DreamLite-base", + "revision": "751cb8dbb9072a8c8ffd8684e0f254b50f20531b" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "DreamLite Base 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "DreamLitePipeline", + "profileId": "dreamlite-base:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "DreamLitePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "dreamlite-base:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "dreamlite-base:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:dreamlite-base:edit-direct", + "model_capability:DreamLitePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:dreamlite-base:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 27, + "exactBytes": 5071894178, + "gated": false, + "inventoryState": "exact_bytes", + "license": "cc-by-nc-4.0", + "repository": "carlofkl/DreamLite-base", + "revision": "751cb8dbb9072a8c8ffd8684e0f254b50f20531b" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "DreamLite Base 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "DreamLitePipeline", + "profileId": "dreamlite-base:edit-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "DreamLitePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "dreamlite-base:edit-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "dreamlite-base:edit-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:dreamlite-mobile:direct", + "model_capability:DreamLiteMobilePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:dreamlite-mobile:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 27, + "exactBytes": 5071894300, + "gated": false, + "inventoryState": "exact_bytes", + "license": "cc-by-nc-4.0", + "repository": "carlofkl/DreamLite-mobile", + "revision": "6695c3f4be230f0493fa5dbf78be3bc4d3bb2ab4" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "DreamLite Mobile 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "DreamLiteMobilePipeline", + "profileId": "dreamlite-mobile:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "DreamLiteMobilePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "dreamlite-mobile:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "dreamlite-mobile:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:dreamlite-mobile:edit-direct", + "model_capability:DreamLiteMobilePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:dreamlite-mobile:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 27, + "exactBytes": 5071894300, + "gated": false, + "inventoryState": "exact_bytes", + "license": "cc-by-nc-4.0", + "repository": "carlofkl/DreamLite-mobile", + "revision": "6695c3f4be230f0493fa5dbf78be3bc4d3bb2ab4" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "DreamLite Mobile 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "DreamLiteMobilePipeline", + "profileId": "dreamlite-mobile:edit-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "DreamLiteMobilePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "dreamlite-mobile:edit-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "dreamlite-mobile:edit-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:ernie-image-turbo:direct", + "model_capability:ErnieImagePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:ernie-image-turbo:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 24, + "exactBytes": 31648078746, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "baidu/ERNIE-Image-Turbo", + "revision": "bc68c81e2a1730a394d5fc9fae70713dee940140" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "ERNIE Image Turbo 1024px", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ErnieImagePipeline", + "profileId": "ernie-image-turbo:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "ernie-image-turbo:official-modular-workflow" + ], + "knownDefects": [], + "modelType": "ErnieImagePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "ernie-image-turbo:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "ernie-image-turbo:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "ErnieImageModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:ernie-image-turbo:official-modular-workflow", + "model_capability:ErnieImageModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:ernie-image-turbo:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 24, + "exactBytes": 31648078746, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "baidu/ERNIE-Image-Turbo", + "revision": "bc68c81e2a1730a394d5fc9fae70713dee940140" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "ERNIE Image Turbo — Editable Stages", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "ErnieImageModularPipeline", + "profileId": "ernie-image-turbo:official-modular-workflow" + }, + "integratedNativeAlternativeProfileIds": [ + "ernie-image-turbo:official-modular-workflow" + ], + "knownDefects": [], + "modelType": "ErnieImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "ernie-image-turbo:official-modular-workflow" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "ernie-image-turbo:official-modular-workflow/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "ErnieImageModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt", + "rewrite_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-canny:direct", + "model_capability:FluxCannyPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-canny:control-image:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685182867, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Canny-dev", + "revision": "27c3d8bdc17509b47cf4fd9ba25ab1c7508a69a2" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "FLUX.1-Canny-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlPipeline", + "profileId": "flux-canny:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxCannyPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-canny:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-canny:direct/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-canny:direct", + "execution_profile_artifact:fuliucansheng/FLUX.1-Canny-dev-diffusers", + "model_capability:FluxCannyPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-canny:control-image:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685182228, + "gated": false, + "inventoryState": "exact_bytes", + "license": "other", + "repository": "fuliucansheng/FLUX.1-Canny-dev-diffusers", + "revision": "24df2ba1c46a8c735589cc8f81302678797c9821" + }, + "artifactVariantOf": "flux-canny:direct/control_image", + "canonicalTask": "control_image", + "displayName": "FLUX.1-Canny-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlPipeline", + "profileId": "flux-canny:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxCannyPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-canny:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-canny:direct/control_image@fuliucansheng/FLUX.1-Canny-dev-diffusers", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-canny:img2img-direct", + "model_capability:FluxCannyPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-canny:control-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685182867, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Canny-dev", + "revision": "27c3d8bdc17509b47cf4fd9ba25ab1c7508a69a2" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "FLUX.1-Canny-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlImg2ImgPipeline", + "profileId": "flux-canny:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxCannyPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-canny:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-canny:img2img-direct/control_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-canny:img2img-direct", + "execution_profile_artifact:fuliucansheng/FLUX.1-Canny-dev-diffusers", + "model_capability:FluxCannyPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-canny:control-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685182228, + "gated": false, + "inventoryState": "exact_bytes", + "license": "other", + "repository": "fuliucansheng/FLUX.1-Canny-dev-diffusers", + "revision": "24df2ba1c46a8c735589cc8f81302678797c9821" + }, + "artifactVariantOf": "flux-canny:img2img-direct/control_edit_image", + "canonicalTask": "control_edit_image", + "displayName": "FLUX.1-Canny-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlImg2ImgPipeline", + "profileId": "flux-canny:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxCannyPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-canny:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-canny:img2img-direct/control_edit_image@fuliucansheng/FLUX.1-Canny-dev-diffusers", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-canny:inpaint-direct", + "model_capability:FluxCannyPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-canny:control-inpaint:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685182867, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Canny-dev", + "revision": "27c3d8bdc17509b47cf4fd9ba25ab1c7508a69a2" + }, + "artifactVariantOf": null, + "canonicalTask": "control_inpaint", + "displayName": "FLUX.1-Canny-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlInpaintPipeline", + "profileId": "flux-canny:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxCannyPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-canny:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-canny:inpaint-direct/control_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-canny:inpaint-direct", + "execution_profile_artifact:fuliucansheng/FLUX.1-Canny-dev-diffusers", + "model_capability:FluxCannyPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-canny:control-inpaint:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685182228, + "gated": false, + "inventoryState": "exact_bytes", + "license": "other", + "repository": "fuliucansheng/FLUX.1-Canny-dev-diffusers", + "revision": "24df2ba1c46a8c735589cc8f81302678797c9821" + }, + "artifactVariantOf": "flux-canny:inpaint-direct/control_inpaint", + "canonicalTask": "control_inpaint", + "displayName": "FLUX.1-Canny-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlInpaintPipeline", + "profileId": "flux-canny:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxCannyPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-canny:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-canny:inpaint-direct/control_inpaint@fuliucansheng/FLUX.1-Canny-dev-diffusers", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-controlnet:control_edit_image:direct", + "model_capability:FluxControlNetImg2ImgPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-controlnet:control-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746408222, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "FLUX.1-dev + Canny ControlNet", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlNetImg2ImgPipeline", + "profileId": "flux-controlnet:control_edit_image:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxControlNetImg2ImgPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-controlnet:control_edit_image:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-controlnet:control_edit_image:direct/control_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-controlnet:control_image:direct", + "model_capability:FluxControlNetPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-controlnet:control-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746408222, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "FLUX.1-dev + Canny ControlNet", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlNetPipeline", + "profileId": "flux-controlnet:control_image:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxControlNetPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-controlnet:control_image:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-controlnet:control_image:direct/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-controlnet:control_inpaint:direct", + "model_capability:FluxControlNetInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-controlnet:control-inpaint:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746408222, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + }, + "artifactVariantOf": null, + "canonicalTask": "control_inpaint", + "displayName": "FLUX.1-dev + Canny ControlNet", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlNetInpaintPipeline", + "profileId": "flux-controlnet:control_inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxControlNetInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-controlnet:control_inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-controlnet:control_inpaint:direct/control_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-depth:direct", + "model_capability:FluxDepthPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-depth:control-image:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685183091, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Depth-dev", + "revision": "fb5e9b1bae41b8c8adcea4ea2a87b74dd298f07a" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "FLUX.1-Depth-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlPipeline", + "profileId": "flux-depth:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxDepthPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-depth:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-depth:direct/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-depth:img2img-direct", + "model_capability:FluxDepthPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-depth:control-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685183091, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Depth-dev", + "revision": "fb5e9b1bae41b8c8adcea4ea2a87b74dd298f07a" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "FLUX.1-Depth-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlImg2ImgPipeline", + "profileId": "flux-depth:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxDepthPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-depth:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-depth:img2img-direct/control_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-depth:inpaint-direct", + "model_capability:FluxDepthPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-depth:control-inpaint:v1" + ], + "artifact": { + "declaredFileCount": 28, + "exactBytes": 43685183091, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Depth-dev", + "revision": "fb5e9b1bae41b8c8adcea4ea2a87b74dd298f07a" + }, + "artifactVariantOf": null, + "canonicalTask": "control_inpaint", + "displayName": "FLUX.1-Depth-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxControlInpaintPipeline", + "profileId": "flux-depth:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxDepthPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-depth:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-depth:inpaint-direct/control_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:direct", + "model_capability:FluxDevPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-dev:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746408222, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-dev", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxPipeline", + "profileId": "flux-dev:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-dev:modular" + ], + "knownDefects": [], + "modelType": "FluxDevPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "FluxModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:direct", + "execution_profile_artifact:black-forest-labs/FLUX.1-dev-FP8", + "model_capability:FluxDevPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-dev:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev-FP8", + "revision": "2fcc6a7ddee78972c8834226b37a09cebff1b6de" + }, + "artifactVariantOf": "flux-dev:direct/text_to_image", + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-dev", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxPipeline", + "profileId": "flux-dev:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-dev:modular" + ], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxDevPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:direct/text_to_image@black-forest-labs/FLUX.1-dev-FP8", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "FluxModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:img2img-direct", + "model_capability:FluxDevPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-dev:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746408222, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FLUX.1-dev", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "The standard edit_image task names the native image2image workflow, not Kontext editing." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "flux-dev:modular", + "nativeTask": "image_to_image", + "source": "modular_pipelines/flux/modular_blocks_flux.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxImg2ImgPipeline", + "profileId": "flux-dev:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-dev:modular" + ], + "knownDefects": [], + "modelType": "FluxDevPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "FluxModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:img2img-direct", + "execution_profile_artifact:black-forest-labs/FLUX.1-dev-FP8", + "model_capability:FluxDevPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-dev:edit-image:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev-FP8", + "revision": "2fcc6a7ddee78972c8834226b37a09cebff1b6de" + }, + "artifactVariantOf": "flux-dev:img2img-direct/edit_image", + "canonicalTask": "edit_image", + "displayName": "FLUX.1-dev", + "disposition": "blocked", + "dispositionReason": "root_checkpoint_has_no_reviewed_modular_component_layout", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "artifact_blocked", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "A root FP8/NVFP4 checkpoint is not a Diffusers component repository. No reviewed native conversion or dequantization recipe is declared; do not substitute base weights." + ], + "liveQualification": "not_established_by_review", + "reason": "root_checkpoint_has_no_reviewed_modular_component_layout", + "source": "modules/ModularDiffusers/loaders.py:_validate_reviewed_pipeline_index", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxImg2ImgPipeline", + "profileId": "flux-dev:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog", + "root_checkpoint_has_no_reviewed_modular_component_layout" + ], + "modelType": "FluxDevPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:img2img-direct/edit_image@black-forest-labs/FLUX.1-dev-FP8", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "FluxModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:inpaint-direct", + "model_capability:FluxDevPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-dev:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746408222, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "FLUX.1-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxInpaintPipeline", + "profileId": "flux-dev:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxDevPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:inpaint-direct/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:inpaint-direct", + "execution_profile_artifact:black-forest-labs/FLUX.1-dev-FP8", + "model_capability:FluxDevPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-dev:inpaint:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev-FP8", + "revision": "2fcc6a7ddee78972c8834226b37a09cebff1b6de" + }, + "artifactVariantOf": "flux-dev:inpaint-direct/inpaint", + "canonicalTask": "inpaint", + "displayName": "FLUX.1-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxInpaintPipeline", + "profileId": "flux-dev:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxDevPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:inpaint-direct/inpaint@black-forest-labs/FLUX.1-dev-FP8", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:modular", + "model_capability:FluxModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux-dev:modular-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746408222, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + }, + "artifactVariantOf": null, + "canonicalTask": "image_to_image", + "displayName": "FLUX.1-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxModularPipeline", + "profileId": "flux-dev:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-dev:modular" + ], + "knownDefects": [], + "modelType": "FluxModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:modular/image_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "image_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:modular", + "model_capability:FluxModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux-dev:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746408222, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxModularPipeline", + "profileId": "flux-dev:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-dev:modular" + ], + "knownDefects": [], + "modelType": "FluxModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:modular", + "execution_profile_artifact:black-forest-labs/FLUX.1-dev-FP8", + "model_capability:FluxModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux-dev:modular-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev-FP8", + "revision": "2fcc6a7ddee78972c8834226b37a09cebff1b6de" + }, + "artifactVariantOf": "flux-dev:modular/image_to_image", + "canonicalTask": "image_to_image", + "displayName": "FLUX.1-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxModularPipeline", + "profileId": "flux-dev:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-dev:modular" + ], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:modular/image_to_image@black-forest-labs/FLUX.1-dev-FP8", + "standardAndNativeAlternativesCoexist": false, + "task": "image_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-dev:modular", + "execution_profile_artifact:black-forest-labs/FLUX.1-dev-FP8", + "model_capability:FluxModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux-dev:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-dev-FP8", + "revision": "2fcc6a7ddee78972c8834226b37a09cebff1b6de" + }, + "artifactVariantOf": "flux-dev:modular/text_to_image", + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxModularPipeline", + "profileId": "flux-dev:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-dev:modular" + ], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-dev:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-dev:modular/text_to_image@black-forest-labs/FLUX.1-dev-FP8", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-fill:direct", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-fill:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33916013136, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Fill-dev", + "revision": "358293da0354175698b67ec8299acf928313a78a" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "FluxFillPipeline", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxFillPipeline", + "profileId": "flux-fill:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxFillPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-fill:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-fill:direct/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-fill:direct", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-fill:outpaint:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33916013136, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Fill-dev", + "revision": "358293da0354175698b67ec8299acf928313a78a" + }, + "artifactVariantOf": null, + "canonicalTask": "outpaint", + "displayName": "FluxFillPipeline", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxFillPipeline", + "profileId": "flux-fill:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxFillPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-fill:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-fill:direct/outpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "outpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext-inpaint:direct", + "model_capability:FluxKontextInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-kontext-inpaint-direct:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746413969, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev", + "revision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "FLUX.1-Kontext-dev Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxKontextInpaintPipeline", + "profileId": "flux-kontext-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxKontextInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext-inpaint:direct/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext-inpaint:direct", + "model_capability:FluxKontextInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-kontext-inpaint-direct:outpaint:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746413969, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev", + "revision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + }, + "artifactVariantOf": null, + "canonicalTask": "outpaint", + "displayName": "FLUX.1-Kontext-dev Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxKontextInpaintPipeline", + "profileId": "flux-kontext-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxKontextInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext-inpaint:direct/outpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "outpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext-inpaint:direct", + "execution_profile_artifact:black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "model_capability:FluxKontextInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-kontext-inpaint-direct:inpaint:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "revision": "7e9dee453a3454251216a394697a6dcea44f54f8" + }, + "artifactVariantOf": "flux-kontext-inpaint:direct/inpaint", + "canonicalTask": "inpaint", + "displayName": "FLUX.1-Kontext-dev Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxKontextInpaintPipeline", + "profileId": "flux-kontext-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxKontextInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext-inpaint:direct/inpaint@black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext-inpaint:direct", + "execution_profile_artifact:black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "model_capability:FluxKontextInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-kontext-inpaint-direct:outpaint:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "revision": "7e9dee453a3454251216a394697a6dcea44f54f8" + }, + "artifactVariantOf": "flux-kontext-inpaint:direct/outpaint", + "canonicalTask": "outpaint", + "displayName": "FLUX.1-Kontext-dev Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxKontextInpaintPipeline", + "profileId": "flux-kontext-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxKontextInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext-inpaint:direct/outpaint@black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "standardAndNativeAlternativesCoexist": false, + "task": "outpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:direct", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-kontext:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746413969, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev", + "revision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FluxKontextPipeline", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxKontextPipeline", + "profileId": "flux-kontext:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [], + "modelType": "FluxKontextPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:direct/edit_image", + "standardAndNativeAlternativesCoexist": true, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "FluxKontextModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:direct", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-kontext:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746413969, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev", + "revision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "FluxKontextPipeline", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Visible bounded equal-height horizontal composition precedes the native VAE encoder. Native lists alone represent a batch, not multiple references for one result." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "flux-kontext:modular", + "nativeTask": "multi_image_reference_edit", + "source": "modules/ImageOperations/main.py:stitch_reference_images", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxKontextPipeline", + "profileId": "flux-kontext:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [], + "modelType": "FluxKontextPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": true, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": "FluxKontextModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:direct", + "execution_profile_artifact:black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-kontext:edit-image:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "revision": "7e9dee453a3454251216a394697a6dcea44f54f8" + }, + "artifactVariantOf": "flux-kontext:direct/edit_image", + "canonicalTask": "edit_image", + "displayName": "FluxKontextPipeline", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxKontextPipeline", + "profileId": "flux-kontext:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxKontextPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:direct/edit_image@black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "standardAndNativeAlternativesCoexist": true, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "FluxKontextModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:direct", + "execution_profile_artifact:black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-kontext:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "revision": "7e9dee453a3454251216a394697a6dcea44f54f8" + }, + "artifactVariantOf": "flux-kontext:direct/multi_image_reference_edit", + "canonicalTask": "multi_image_reference_edit", + "displayName": "FluxKontextPipeline", + "disposition": "blocked", + "dispositionReason": "root_checkpoint_has_no_reviewed_modular_component_layout", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "artifact_blocked", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "A root FP8/NVFP4 checkpoint is not a Diffusers component repository. No reviewed native conversion or dequantization recipe is declared; do not substitute base weights." + ], + "liveQualification": "not_established_by_review", + "reason": "root_checkpoint_has_no_reviewed_modular_component_layout", + "source": "modules/ModularDiffusers/loaders.py:_validate_reviewed_pipeline_index", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxKontextPipeline", + "profileId": "flux-kontext:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog", + "root_checkpoint_has_no_reviewed_modular_component_layout" + ], + "modelType": "FluxKontextPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:direct/multi_image_reference_edit@black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "standardAndNativeAlternativesCoexist": true, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": "FluxKontextModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:modular", + "model_capability:FluxKontextModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux-kontext:modular-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746413969, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev", + "revision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FLUX.1-Kontext-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxKontextModularPipeline", + "profileId": "flux-kontext:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [], + "modelType": "FluxKontextModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:modular/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:modular", + "model_capability:FluxKontextModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746413969, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev", + "revision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "FLUX.1-Kontext-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxKontextModularPipeline", + "profileId": "flux-kontext:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [], + "modelType": "FluxKontextModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:modular/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:modular", + "model_capability:FluxKontextModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux-kontext:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746413969, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev", + "revision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-Kontext-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxKontextModularPipeline", + "profileId": "flux-kontext:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [], + "modelType": "FluxKontextModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:modular", + "execution_profile_artifact:black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "model_capability:FluxKontextModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux-kontext:modular-edit-image:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "revision": "7e9dee453a3454251216a394697a6dcea44f54f8" + }, + "artifactVariantOf": "flux-kontext:modular/edit_image", + "canonicalTask": "edit_image", + "displayName": "FLUX.1-Kontext-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxKontextModularPipeline", + "profileId": "flux-kontext:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxKontextModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:modular/edit_image@black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:modular", + "execution_profile_artifact:black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "model_capability:FluxKontextModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "revision": "7e9dee453a3454251216a394697a6dcea44f54f8" + }, + "artifactVariantOf": "flux-kontext:modular/multi_image_reference_edit", + "canonicalTask": "multi_image_reference_edit", + "displayName": "FLUX.1-Kontext-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxKontextModularPipeline", + "profileId": "flux-kontext:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxKontextModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:modular/multi_image_reference_edit@black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-kontext:modular", + "execution_profile_artifact:black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "model_capability:FluxKontextModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux-kontext:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": null, + "exactBytes": null, + "gated": null, + "inventoryState": "unknown", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "revision": "7e9dee453a3454251216a394697a6dcea44f54f8" + }, + "artifactVariantOf": "flux-kontext:modular/text_to_image", + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-Kontext-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxKontextModularPipeline", + "profileId": "flux-kontext:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-kontext:modular" + ], + "knownDefects": [ + "exact_artifact_byte_inventory_not_in_catalog" + ], + "modelType": "FluxKontextModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-kontext:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-kontext:modular/text_to_image@black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-krea:direct", + "model_capability:FluxKreaPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-krea:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746412027, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Krea-dev", + "revision": "8162a9c7b05a641be098422bf2fcf335615c2f28" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-Krea-dev", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Separate Krea artifact; 28 steps and guidance 3.5; no inherited dev output proof." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "flux-krea:modular", + "nativeTask": "text_to_image", + "source": "modular_pipelines/flux/before_denoise.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxPipeline", + "profileId": "flux-krea:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-krea:modular" + ], + "knownDefects": [], + "modelType": "FluxKreaPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-krea:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-krea:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "FluxModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-krea:modular", + "model_capability:FluxKreaPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 26, + "exactBytes": 33746412027, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Krea-dev", + "revision": "8162a9c7b05a641be098422bf2fcf335615c2f28" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-Krea-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxModularPipeline", + "profileId": "flux-krea:modular" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxKreaPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-krea:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-krea:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-redux:direct", + "model_capability:FluxReduxPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-redux:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 9, + "exactBytes": 985539827, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Redux-dev", + "revision": "c95859fbf7703ca4d6824b4da4407d7cd0434f81" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FLUX.1-Redux-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxReduxPipeline", + "profileId": "flux-redux:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxReduxPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-redux:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-redux:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-redux:direct", + "model_capability:FluxReduxPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-redux:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 9, + "exactBytes": 985539827, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "other", + "repository": "black-forest-labs/FLUX.1-Redux-dev", + "revision": "c95859fbf7703ca4d6824b4da4407d7cd0434f81" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "FLUX.1-Redux-dev", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxReduxPipeline", + "profileId": "flux-redux:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxReduxPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-redux:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-redux:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-schnell:direct", + "model_capability:FluxSchnellPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux-schnell:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 25, + "exactBytes": 33725928351, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.1-schnell", + "revision": "741f7c3ce8b383c54771c7003378a50191e9efe9" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-schnell", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Four steps, guidance zero, 256-token encoder limit; guidance_embeds comes from the transformer config." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "flux-schnell:modular", + "nativeTask": "text_to_image", + "source": "modular_pipelines/flux/before_denoise.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "FluxPipeline", + "profileId": "flux-schnell:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux-schnell:modular" + ], + "knownDefects": [], + "modelType": "FluxSchnellPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-schnell:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-schnell:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "FluxModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux-schnell:modular", + "model_capability:FluxSchnellPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 25, + "exactBytes": 33725928351, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.1-schnell", + "revision": "741f7c3ce8b383c54771c7003378a50191e9efe9" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.1-schnell", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "FluxModularPipeline", + "profileId": "flux-schnell:modular" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "FluxSchnellPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux-schnell:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux-schnell:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "FluxModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-dev:direct", + "model_capability:Flux2Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-dev:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 112823045100, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-dev", + "revision": "26afe3a78bb242c0a8bb181dcc8937bb16e5c66c" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "FLUX.2-dev", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "One to eight ordered independent references, not an image batch or stitched canvas." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "flux2:modular", + "nativeTask": "multi_image_reference_edit", + "source": "modular_pipelines/flux2/encoders.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2Pipeline", + "profileId": "flux2-dev:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2:modular" + ], + "knownDefects": [], + "modelType": "Flux2Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-dev:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-dev:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": true, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": "Flux2ModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-dev:direct", + "model_capability:Flux2Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-dev:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 112823045100, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-dev", + "revision": "26afe3a78bb242c0a8bb181dcc8937bb16e5c66c" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.2-dev", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2Pipeline", + "profileId": "flux2-dev:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2:modular" + ], + "knownDefects": [], + "modelType": "Flux2Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-dev:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-dev:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "Flux2ModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein-base:modular", + "model_capability:Flux2KleinBaseModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux2-klein-base:modular-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152959, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-base-4B", + "revision": "a3b4f4849157f664bdbc776fd7453c2783562f4d" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FLUX.2-klein-base-4B", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2KleinBaseModularPipeline", + "profileId": "flux2-klein-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein-base:modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinBaseModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein-base:modular/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2KleinModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein-base:modular", + "model_capability:Flux2KleinBaseModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux2-klein-base:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152959, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-base-4B", + "revision": "a3b4f4849157f664bdbc776fd7453c2783562f4d" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.2-klein-base-4B", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2KleinBaseModularPipeline", + "profileId": "flux2-klein-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein-base:modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinBaseModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein-base:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2KleinModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein-inpaint:direct", + "model_capability:Flux2KleinInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-klein-inpaint-direct:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152900, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "FLUX.2-klein-4B Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2KleinInpaintPipeline", + "profileId": "flux2-klein-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "Flux2KleinInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein-inpaint:direct/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein-inpaint:direct", + "model_capability:Flux2KleinInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-klein-inpaint-direct:outpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152900, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + }, + "artifactVariantOf": null, + "canonicalTask": "outpaint", + "displayName": "FLUX.2-klein-4B Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2KleinInpaintPipeline", + "profileId": "flux2-klein-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "Flux2KleinInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein-inpaint:direct/outpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "outpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein-kv:direct", + "model_capability:Flux2KleinKVPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-klein-kv:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 25, + "exactBytes": 34722797902, + "gated": true, + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-klein-9b-kv", + "revision": "a6dfb36eca3a3906eb2fd460795adfb844e5fcce" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FLUX.2-klein-9b-kv", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "pinned_modular_klein_has_no_kv_extract_cached_path", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "upstream_exception", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "The standard KV pipeline extracts reference K/V once and reuses it on later steps. Pinned modular Klein does not implement kv_cache_mode=extract/cached; keep the exact standard KV executor for reference tasks." + ], + "liveQualification": "not_established_by_review", + "reason": "pinned_modular_klein_has_no_kv_extract_cached_path", + "source": "pipelines/flux2/pipeline_flux2_klein_kv.py", + "status": "reviewed" + }, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2KleinKVPipeline", + "profileId": "flux2-klein-kv:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "Flux2KleinKVPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein-kv:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein-kv:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "Flux2KleinModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein-kv:direct", + "model_capability:Flux2KleinKVPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-klein-kv:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 25, + "exactBytes": 34722797902, + "gated": true, + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-klein-9b-kv", + "revision": "a6dfb36eca3a3906eb2fd460795adfb844e5fcce" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "FLUX.2-klein-9b-kv", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "pinned_modular_klein_has_no_kv_extract_cached_path", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "upstream_exception", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "The standard KV pipeline extracts reference K/V once and reuses it on later steps. Pinned modular Klein does not implement kv_cache_mode=extract/cached; keep the exact standard KV executor for reference tasks." + ], + "liveQualification": "not_established_by_review", + "reason": "pinned_modular_klein_has_no_kv_extract_cached_path", + "source": "pipelines/flux2/pipeline_flux2_klein_kv.py", + "status": "reviewed" + }, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2KleinKVPipeline", + "profileId": "flux2-klein-kv:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "Flux2KleinKVPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein-kv:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein-kv:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": "Flux2KleinModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein-kv:direct", + "model_capability:Flux2KleinKVPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-klein-kv:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 25, + "exactBytes": 34722797902, + "gated": true, + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-klein-9b-kv", + "revision": "a6dfb36eca3a3906eb2fd460795adfb844e5fcce" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.2-klein-9b-kv", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Text-only takes the standard-forward branch, with no reference K/V cache. Exact 9B KV index declares Flux2KleinPipeline, is_distilled=true, and the normal Flux2 transformer/VAE; this does not admit its cached-reference tasks or inherit 4B memory qualification." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "flux2-klein-kv:t2i-modular", + "nativeTask": "text_to_image", + "source": "pipelines/flux2/pipeline_flux2_klein_kv.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2KleinKVPipeline", + "profileId": "flux2-klein-kv:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein-kv:t2i-modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinKVPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein-kv:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein-kv:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "Flux2KleinModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein-kv:t2i-modular", + "model_capability:Flux2KleinKVPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 25, + "exactBytes": 34722797902, + "gated": true, + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-klein-9b-kv", + "revision": "a6dfb36eca3a3906eb2fd460795adfb844e5fcce" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.2-klein-9b-kv", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2KleinModularPipeline", + "profileId": "flux2-klein-kv:t2i-modular" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "Flux2KleinKVPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein-kv:t2i-modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein-kv:t2i-modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2KleinModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein:direct", + "model_capability:Flux2KleinPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-klein:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152900, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FLUX.2-klein-4B", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2KleinPipeline", + "profileId": "flux2-klein:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein:modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein:direct/edit_image", + "standardAndNativeAlternativesCoexist": true, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "Flux2KleinModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein:direct", + "model_capability:Flux2KleinPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-klein:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152900, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "FLUX.2-klein-4B", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Distilled 4B weights; one to eight ordered independent references; no Klein KV equivalence." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "flux2-klein:modular", + "nativeTask": "multi_image_reference_edit", + "source": "modular_pipelines/flux2/encoders.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2KleinPipeline", + "profileId": "flux2-klein:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein:modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": true, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": "Flux2KleinModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein:direct", + "model_capability:Flux2KleinPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:flux2-klein:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152900, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.2-klein-4B", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Flux2KleinPipeline", + "profileId": "flux2-klein:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein:modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "Flux2KleinModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein:modular", + "model_capability:Flux2KleinModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux2-klein:modular-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152900, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FLUX.2-klein-4B", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2KleinModularPipeline", + "profileId": "flux2-klein:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein:modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein:modular/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2KleinModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein:modular", + "model_capability:Flux2KleinModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152900, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "FLUX.2-klein-4B", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2KleinModularPipeline", + "profileId": "flux2-klein:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein:modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein:modular/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2KleinModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2-klein:modular", + "model_capability:Flux2KleinModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux2-klein:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 15980152900, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.2-klein-4B", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2KleinModularPipeline", + "profileId": "flux2-klein:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2-klein:modular" + ], + "knownDefects": [], + "modelType": "Flux2KleinModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2-klein:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2-klein:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2KleinModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2:modular", + "model_capability:Flux2ModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux2:modular-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 112823045100, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-dev", + "revision": "26afe3a78bb242c0a8bb181dcc8937bb16e5c66c" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "FLUX.2-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2ModularPipeline", + "profileId": "flux2:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2:modular" + ], + "knownDefects": [], + "modelType": "Flux2ModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2:modular/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2ModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2:modular", + "model_capability:Flux2ModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 112823045100, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-dev", + "revision": "26afe3a78bb242c0a8bb181dcc8937bb16e5c66c" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "FLUX.2-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2ModularPipeline", + "profileId": "flux2:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2:modular" + ], + "knownDefects": [], + "modelType": "Flux2ModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2:modular/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2ModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:flux2:modular", + "model_capability:Flux2ModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:flux2:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 112823045100, + "gated": "auto", + "inventoryState": "exact_bytes", + "license": "flux-non-commercial-license", + "repository": "black-forest-labs/FLUX.2-dev", + "revision": "26afe3a78bb242c0a8bb181dcc8937bb16e5c66c" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "FLUX.2-dev", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "Flux2ModularPipeline", + "profileId": "flux2:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "flux2:modular" + ], + "knownDefects": [], + "modelType": "Flux2ModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "flux2:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "flux2:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "Flux2ModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:glm-image:direct", + "model_capability:GlmImagePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:glm-image:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 27, + "exactBytes": 35789282228, + "gated": false, + "inventoryState": "exact_bytes", + "license": "mit", + "repository": "zai-org/GLM-Image", + "revision": "2c433cc0cbc293bde2ac8ca9624f279b5d23fcf4" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "GLM-Image 16B 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "GlmImagePipeline", + "profileId": "glm-image:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "GlmImagePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "glm-image:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "glm-image:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:hunyuan-dit-v1-2-controlnet-canny:direct", + "model_capability:HunyuanDiTControlNetPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:hunyuan-dit-v1-2-controlnet-canny:control-image:v1" + ], + "artifact": { + "declaredFileCount": 20, + "exactBytes": 14427314721, + "gated": false, + "inventoryState": "exact_bytes", + "license": "tencent-hunyuan-community", + "repository": "Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers-Distilled", + "revision": "ba991d1546d8c50936c4c16398ed0a87b9b99fb1" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "Hunyuan-DiT v1.2 Canny ControlNet", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "HunyuanDiTControlNetPipeline", + "profileId": "hunyuan-dit-v1-2-controlnet-canny:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "HunyuanDiTControlNetPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "hunyuan-dit-v1-2-controlnet-canny:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "hunyuan-dit-v1-2-controlnet-canny:direct/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:hunyuan-dit-v1-2-distilled-pag:direct", + "model_capability:HunyuanDiTPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:hunyuan-dit-v1-2-distilled-pag:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 20, + "exactBytes": 14427314721, + "gated": false, + "inventoryState": "exact_bytes", + "license": "tencent-hunyuan-community", + "repository": "Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers-Distilled", + "revision": "ba991d1546d8c50936c4c16398ed0a87b9b99fb1" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Hunyuan-DiT v1.2 Distilled PAG", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "HunyuanDiTPAGPipeline", + "profileId": "hunyuan-dit-v1-2-distilled-pag:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "HunyuanDiTPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "hunyuan-dit-v1-2-distilled-pag:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "hunyuan-dit-v1-2-distilled-pag:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:hunyuan-dit-v1-2-distilled:direct", + "model_capability:HunyuanDiTPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:hunyuan-dit-v1-2-distilled:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 20, + "exactBytes": 14427314721, + "gated": false, + "inventoryState": "exact_bytes", + "license": "tencent-hunyuan-community", + "repository": "Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers-Distilled", + "revision": "ba991d1546d8c50936c4c16398ed0a87b9b99fb1" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Hunyuan-DiT v1.2 Distilled", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "HunyuanDiTPipeline", + "profileId": "hunyuan-dit-v1-2-distilled:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "HunyuanDiTPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "hunyuan-dit-v1-2-distilled:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "hunyuan-dit-v1-2-distilled:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:janus-pro-1b:direct", + "model_capability:HuggingFaceAnyToAnyModel", + "node_model_dropdown:modules.HuggingFaceTransformers.LoadAnyToAnyModel", + "studio_execution_spec:janus-pro-1b:image-to-text:v1" + ], + "artifact": { + "declaredFileCount": 11, + "exactBytes": 4161125359, + "gated": false, + "inventoryState": "exact_bytes", + "license": "deepseek-model-license-v1.0", + "repository": "deepseek-community/Janus-Pro-1B", + "revision": "1655280bb75959cc1cb85529a2a8b26e7016072e" + }, + "artifactVariantOf": null, + "canonicalTask": "image_to_text", + "displayName": "Janus-Pro-1B", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-huggingface-transformers-any-to-any", + "loader": "modules.HuggingFaceTransformers.LoadAnyToAnyModel", + "pipelineClass": "JanusForConditionalGeneration", + "profileId": "janus-pro-1b:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "HuggingFaceAnyToAnyModel", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "janus-pro-1b:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "janus-pro-1b:direct/image_to_text", + "standardAndNativeAlternativesCoexist": false, + "task": "image_to_text", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:janus-pro-1b:direct", + "model_capability:HuggingFaceAnyToAnyModel", + "node_model_dropdown:modules.HuggingFaceTransformers.LoadAnyToAnyModel", + "studio_execution_spec:janus-pro-1b:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 11, + "exactBytes": 4161125359, + "gated": false, + "inventoryState": "exact_bytes", + "license": "deepseek-model-license-v1.0", + "repository": "deepseek-community/Janus-Pro-1B", + "revision": "1655280bb75959cc1cb85529a2a8b26e7016072e" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Janus-Pro-1B", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-huggingface-transformers-any-to-any", + "loader": "modules.HuggingFaceTransformers.LoadAnyToAnyModel", + "pipelineClass": "JanusForConditionalGeneration", + "profileId": "janus-pro-1b:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "HuggingFaceAnyToAnyModel", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "janus-pro-1b:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "janus-pro-1b:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:joyimage-edit-plus:direct", + "model_capability:JoyImageEditPlusPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:joyimage-edit-plus:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 29, + "exactBytes": 50338627834, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "jdopensource/JoyAI-Image-Edit-Plus-Diffusers", + "revision": "c2686460c7b64d8aa11bc4d0da423fb316b33f9e" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "JoyAI Image Edit Plus 16B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "JoyImageEditPlusPipeline", + "profileId": "joyimage-edit-plus:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "JoyImageEditPlusPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "joyimage-edit-plus:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "joyimage-edit-plus:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:joyimage-edit-plus:direct", + "model_capability:JoyImageEditPlusPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:joyimage-edit-plus:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 29, + "exactBytes": 50338627834, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "jdopensource/JoyAI-Image-Edit-Plus-Diffusers", + "revision": "c2686460c7b64d8aa11bc4d0da423fb316b33f9e" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "JoyAI Image Edit Plus 16B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "JoyImageEditPlusPipeline", + "profileId": "joyimage-edit-plus:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "JoyImageEditPlusPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "joyimage-edit-plus:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "joyimage-edit-plus:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:joyimage-edit:direct", + "model_capability:JoyImageEditPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:joyimage-edit:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 38, + "exactBytes": 50347541462, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "jdopensource/JoyAI-Image-Edit-Diffusers", + "revision": "4b41fb25d961f37668750178ccbb380da326201c" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "JoyAI Image Edit 16B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "JoyImageEditPipeline", + "profileId": "joyimage-edit:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "JoyImageEditPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "joyimage-edit:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "joyimage-edit:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:joyimage-edit:direct", + "model_capability:JoyImageEditPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:joyimage-edit:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 38, + "exactBytes": 50347541462, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "jdopensource/JoyAI-Image-Edit-Diffusers", + "revision": "4b41fb25d961f37668750178ccbb380da326201c" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "JoyAI Image Edit 16B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "JoyImageEditPipeline", + "profileId": "joyimage-edit:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "JoyImageEditPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "joyimage-edit:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "joyimage-edit:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:kandinsky3:direct", + "model_capability:Kandinsky3Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:kandinsky3:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 19, + "exactBytes": 28391690623, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "kandinsky-community/kandinsky-3", + "revision": "bf79e6c219da8a94abb50235fdc4567eb8fb4632" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Kandinsky 3", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Kandinsky3Pipeline", + "profileId": "kandinsky3:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "Kandinsky3Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "kandinsky3:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "kandinsky3:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:kandinsky3:img2img-direct", + "model_capability:Kandinsky3Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:kandinsky3:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 19, + "exactBytes": 28391690623, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "kandinsky-community/kandinsky-3", + "revision": "bf79e6c219da8a94abb50235fdc4567eb8fb4632" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "Kandinsky 3", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Kandinsky3Img2ImgPipeline", + "profileId": "kandinsky3:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "Kandinsky3Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "kandinsky3:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "kandinsky3:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:lcm-dreamshaper-v7:direct", + "model_capability:LatentConsistencyModelPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:lcm-dreamshaper-v7:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 17, + "exactBytes": 5482979343, + "gated": false, + "inventoryState": "exact_bytes", + "license": "mit", + "repository": "SimianLuo/LCM_Dreamshaper_v7", + "revision": "a85df6a8bd976cdd08b4fd8f3b73f229c9e54df5" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "LCM DreamShaper v7", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "LatentConsistencyModelPipeline", + "profileId": "lcm-dreamshaper-v7:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "LatentConsistencyModelPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "lcm-dreamshaper-v7:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "lcm-dreamshaper-v7:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:lcm-dreamshaper-v7:img2img-direct", + "model_capability:LatentConsistencyModelPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:lcm-dreamshaper-v7:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 17, + "exactBytes": 5482979343, + "gated": false, + "inventoryState": "exact_bytes", + "license": "mit", + "repository": "SimianLuo/LCM_Dreamshaper_v7", + "revision": "a85df6a8bd976cdd08b4fd8f3b73f229c9e54df5" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "LCM DreamShaper v7", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "LatentConsistencyModelImg2ImgPipeline", + "profileId": "lcm-dreamshaper-v7:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "LatentConsistencyModelPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "lcm-dreamshaper-v7:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "lcm-dreamshaper-v7:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:longcat-image-edit:direct", + "model_capability:LongCatImageEditPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:longcat-image-edit:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 32, + "exactBytes": 29316540813, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "meituan-longcat/LongCat-Image-Edit", + "revision": "7b54ef423aa7854be7861600024be5c56ab7875a" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "LongCat Image Edit 6B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "LongCatImageEditPipeline", + "profileId": "longcat-image-edit:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "LongCatImageEditPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "longcat-image-edit:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "longcat-image-edit:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:longcat-image:direct", + "model_capability:LongCatImagePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:longcat-image:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 31, + "exactBytes": 29316541013, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "meituan-longcat/LongCat-Image", + "revision": "d2ea50b79a930074c37b9b97ce45e3b2ea8cf4d8" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "LongCat Image 6B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "LongCatImagePipeline", + "profileId": "longcat-image:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "LongCatImagePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "longcat-image:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "longcat-image:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:lumina-next:direct", + "model_capability:LuminaPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:lumina-next:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 16, + "exactBytes": 8878690722, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Alpha-VLLM/Lumina-Next-SFT-diffusers", + "revision": "0ee5ec90043acf5cb41fe96274af36eb7fad8d95" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Lumina Next SFT 2B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "LuminaPipeline", + "profileId": "lumina-next:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "LuminaPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "lumina-next:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "lumina-next:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:lumina2:direct", + "model_capability:Lumina2Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:lumina2:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 17, + "exactBytes": 21253711141, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Alpha-VLLM/Lumina-Image-2.0", + "revision": "53504abd8178b30685b6c4c7a4cd181ff78b73e9" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Lumina Image 2.0 2.6B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "Lumina2Pipeline", + "profileId": "lumina2:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "Lumina2Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "lumina2:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "lumina2:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:marigold-depth-lcm-v1-0:direct", + "model_capability:MarigoldDepthPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:marigold-depth-lcm-v1-0:depth-estimation:v1" + ], + "artifact": { + "declaredFileCount": 14, + "exactBytes": 5161610352, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "prs-eth/marigold-depth-lcm-v1-0", + "revision": "04a73502f7fd8fc5e59947b9df3b2266d71d6849" + }, + "artifactVariantOf": null, + "canonicalTask": "depth_estimation", + "displayName": "Marigold Depth LCM v1.0", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "MarigoldDepthPipeline", + "profileId": "marigold-depth-lcm-v1-0:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "MarigoldDepthPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "marigold-depth-lcm-v1-0:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "marigold-depth-lcm-v1-0:direct/depth_estimation", + "standardAndNativeAlternativesCoexist": false, + "task": "depth_estimation", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:nucleus-image:direct", + "model_capability:NucleusMoEImagePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:nucleus-image:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 38, + "exactBytes": 51656728957, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0-declared-license-file-absent", + "repository": "NucleusAI/Nucleus-Image", + "revision": "5e963db4fd0a65c7e4faf53ca2d4eca567c4dcfa" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Nucleus Image 17B MoE", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "NucleusMoEImagePipeline", + "profileId": "nucleus-image:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "NucleusMoEImagePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "nucleus-image:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "nucleus-image:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:omnigen-v1:direct", + "model_capability:OmniGenPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:omnigen-v1:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 11, + "exactBytes": 8088956424, + "gated": false, + "inventoryState": "exact_bytes", + "license": "mit", + "repository": "Shitao/OmniGen-v1-diffusers", + "revision": "016e2f61d12a98303f6bbdf122687694d7984268" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "OmniGen v1", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "OmniGenPipeline", + "profileId": "omnigen-v1:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "OmniGenPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "omnigen-v1:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "omnigen-v1:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:omnigen-v1:direct", + "model_capability:OmniGenPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:omnigen-v1:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 11, + "exactBytes": 8088956424, + "gated": false, + "inventoryState": "exact_bytes", + "license": "mit", + "repository": "Shitao/OmniGen-v1-diffusers", + "revision": "016e2f61d12a98303f6bbdf122687694d7984268" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "OmniGen v1", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "OmniGenPipeline", + "profileId": "omnigen-v1:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "OmniGenPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "omnigen-v1:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "omnigen-v1:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:omnigen-v1:direct", + "model_capability:OmniGenPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:omnigen-v1:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 11, + "exactBytes": 8088956424, + "gated": false, + "inventoryState": "exact_bytes", + "license": "mit", + "repository": "Shitao/OmniGen-v1-diffusers", + "revision": "016e2f61d12a98303f6bbdf122687694d7984268" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "OmniGen v1", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "OmniGenPipeline", + "profileId": "omnigen-v1:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "OmniGenPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "omnigen-v1:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "omnigen-v1:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:ovis-image-7b:direct", + "model_capability:OvisImagePipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:ovis-image-7b:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 21806937901, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "ATH-MaaS/Ovis-Image-7B", + "revision": "41be1c5821a92c970d63d7eb595a2fd3fe32b22e" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Ovis Image 7B", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "OvisImagePipeline", + "profileId": "ovis-image-7b:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "OvisImagePipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "ovis-image-7b:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "ovis-image-7b:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:pixart-sigma-1024-pag:direct", + "model_capability:PixArtSigmaPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:pixart-sigma-1024-pag:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 15, + "exactBytes": 21828231839, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", + "revision": "e102b3591cc82e97071b8b4cb90d834d0c487207" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "PixArt Sigma XL 1024px PAG", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "PixArtSigmaPAGPipeline", + "profileId": "pixart-sigma-1024-pag:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "PixArtSigmaPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "pixart-sigma-1024-pag:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "pixart-sigma-1024-pag:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:pixart-sigma-1024:direct", + "model_capability:PixArtSigmaPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:pixart-sigma-1024:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 15, + "exactBytes": 21828231839, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", + "revision": "e102b3591cc82e97071b8b4cb90d834d0c487207" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "PixArt Sigma XL 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "PixArtSigmaPipeline", + "profileId": "pixart-sigma-1024:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "PixArtSigmaPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "pixart-sigma-1024:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "pixart-sigma-1024:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:prx-512-sft:direct", + "model_capability:PRXPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:prx-512-sft:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 19, + "exactBytes": 15514188109, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Photoroom/prx-512-t2i-sft", + "revision": "2996423bc26e8eaca48774fac1797484214dfea0" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "PRX 512 SFT", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "PRXPipeline", + "profileId": "prx-512-sft:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "PRXPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "prx-512-sft:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "prx-512-sft:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-edit-plus:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-edit-plus:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 57720463453, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit-2511", + "revision": "6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "QwenImageEditPlusModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageEditPlusModularPipeline", + "profileId": "qwen-edit-plus:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-edit-plus:modular" + ], + "knownDefects": [], + "modelType": "QwenImageEditPlusModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-edit-plus:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-edit-plus:modular/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageEditPlusModularPipeline", + "nativeWorkflowId": "default", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-edit-plus:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-edit-plus:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 57720463453, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit-2511", + "revision": "6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "QwenImageEditPlusModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageEditPlusModularPipeline", + "profileId": "qwen-edit-plus:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-edit-plus:modular" + ], + "knownDefects": [], + "modelType": "QwenImageEditPlusModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-edit-plus:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-edit-plus:modular/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageEditPlusModularPipeline", + "nativeWorkflowId": "default", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-edit:direct-inpaint", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-edit:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 39, + "exactBytes": 57720467613, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit", + "revision": "ac7f9318f633fc4b5778c59367c8128225f1e3de" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "QwenImageEditModularPipeline", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageEditInpaintPipeline", + "profileId": "qwen-edit:direct-inpaint" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-edit:modular" + ], + "knownDefects": [], + "modelType": "QwenImageEditModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-edit:direct-inpaint" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-edit:direct-inpaint/inpaint", + "standardAndNativeAlternativesCoexist": true, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageEditModularPipeline", + "nativeWorkflowId": "image_conditioned_inpainting", + "requiredCoreRolesPresent": false, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-edit:direct-inpaint", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-edit:outpaint:v1" + ], + "artifact": { + "declaredFileCount": 39, + "exactBytes": 57720467613, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit", + "revision": "ac7f9318f633fc4b5778c59367c8128225f1e3de" + }, + "artifactVariantOf": null, + "canonicalTask": "outpaint", + "displayName": "QwenImageEditModularPipeline", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageEditInpaintPipeline", + "profileId": "qwen-edit:direct-inpaint" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "QwenImageEditModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-edit:direct-inpaint" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-edit:direct-inpaint/outpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "outpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-edit:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-edit:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 39, + "exactBytes": 57720467613, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit", + "revision": "ac7f9318f633fc4b5778c59367c8128225f1e3de" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "QwenImageEditModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageEditModularPipeline", + "profileId": "qwen-edit:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-edit:modular" + ], + "knownDefects": [], + "modelType": "QwenImageEditModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-edit:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-edit:modular/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageEditModularPipeline", + "nativeWorkflowId": "image_conditioned", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-edit:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-edit:modular-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 39, + "exactBytes": 57720467613, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit", + "revision": "ac7f9318f633fc4b5778c59367c8128225f1e3de" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "QwenImageEditModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageEditModularPipeline", + "profileId": "qwen-edit:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-edit:modular" + ], + "knownDefects": [], + "modelType": "QwenImageEditModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-edit:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-edit:modular/modular_inpainting", + "standardAndNativeAlternativesCoexist": false, + "task": "modular_inpainting", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageEditModularPipeline", + "nativeWorkflowId": "image_conditioned_inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image-21:direct", + "model_capability:QwenImage21Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-21:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 27, + "exactBytes": 33131616412, + "gated": false, + "inventoryState": "exact_bytes", + "license": "qwen-research", + "repository": "Qwen/Qwen-Image-2.1", + "revision": "790c92633540aa0cb11d9abf19eb46d861714758" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "Qwen-Image-2.1", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImage21Pipeline", + "profileId": "qwen-image-21:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "QwenImage21Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image-21:direct" + ], + "profileIds": [ + "huggingface-transformers-peft-5.17.0-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image-21:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image-21:direct", + "model_capability:QwenImage21Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-21:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 27, + "exactBytes": 33131616412, + "gated": false, + "inventoryState": "exact_bytes", + "license": "qwen-research", + "repository": "Qwen/Qwen-Image-2.1", + "revision": "790c92633540aa0cb11d9abf19eb46d861714758" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "Qwen-Image-2.1", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImage21Pipeline", + "profileId": "qwen-image-21:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "QwenImage21Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image-21:direct" + ], + "profileIds": [ + "huggingface-transformers-peft-5.17.0-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image-21:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": false, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image-21:direct", + "model_capability:QwenImage21Pipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-21:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 27, + "exactBytes": 33131616412, + "gated": false, + "inventoryState": "exact_bytes", + "license": "qwen-research", + "repository": "Qwen/Qwen-Image-2.1", + "revision": "790c92633540aa0cb11d9abf19eb46d861714758" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Qwen-Image-2.1", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImage21Pipeline", + "profileId": "qwen-image-21:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "QwenImage21Pipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image-21:direct" + ], + "profileIds": [ + "huggingface-transformers-peft-5.17.0-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image-21:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image-controlnet:direct", + "model_capability:QwenImageControlNetPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-controlnet-direct:control-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "Qwen-Image-2512 + ControlNet Union", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageControlNetPipeline", + "profileId": "qwen-image-controlnet:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageControlNetPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image-controlnet:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image-controlnet:direct/control_image", + "standardAndNativeAlternativesCoexist": true, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image-edit-plus:direct", + "model_capability:QwenImageEditPlusPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-edit-plus-direct:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 57720463453, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit-2511", + "revision": "6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "Qwen-Image-Edit-2511", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageEditPlusPipeline", + "profileId": "qwen-image-edit-plus:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-edit-plus:modular" + ], + "knownDefects": [], + "modelType": "QwenImageEditPlusPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image-edit-plus:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image-edit-plus:direct/edit_image", + "standardAndNativeAlternativesCoexist": true, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "QwenImageEditPlusModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image-edit-plus:direct", + "model_capability:QwenImageEditPlusPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-edit-plus-direct:multi-image-reference-edit:v1" + ], + "artifact": { + "declaredFileCount": 35, + "exactBytes": 57720463453, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit-2511", + "revision": "6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9" + }, + "artifactVariantOf": null, + "canonicalTask": "multi_image_reference_edit", + "displayName": "Qwen-Image-Edit-2511", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageEditPlusPipeline", + "profileId": "qwen-image-edit-plus:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-edit-plus:modular" + ], + "knownDefects": [], + "modelType": "QwenImageEditPlusPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image-edit-plus:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image-edit-plus:direct/multi_image_reference_edit", + "standardAndNativeAlternativesCoexist": true, + "task": "multi_image_reference_edit", + "upstream": { + "candidateNativePipelineClass": "QwenImageEditPlusModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image-edit:direct", + "model_capability:QwenImageEditPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-edit-direct:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 39, + "exactBytes": 57720467613, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Edit", + "revision": "ac7f9318f633fc4b5778c59367c8128225f1e3de" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "Qwen-Image-Edit", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageEditPipeline", + "profileId": "qwen-image-edit:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-edit:modular" + ], + "knownDefects": [], + "modelType": "QwenImageEditPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image-edit:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image-edit:direct/edit_image", + "standardAndNativeAlternativesCoexist": true, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "QwenImageEditModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image-layered:direct", + "model_capability:QwenImageLayeredPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-layered-direct:layer-decomposition:v1" + ], + "artifact": { + "declaredFileCount": 33, + "exactBytes": 57720484610, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Layered", + "revision": "8f0ca708dfff6ba1dd5f2d85d78f8c108a040bcf" + }, + "artifactVariantOf": null, + "canonicalTask": "layer_decomposition", + "displayName": "Qwen-Image-Layered", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageLayeredPipeline", + "profileId": "qwen-image-layered:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-layered:modular" + ], + "knownDefects": [], + "modelType": "QwenImageLayeredPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image-layered:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image-layered:direct/layer_decomposition", + "standardAndNativeAlternativesCoexist": true, + "task": "layer_decomposition", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:img2img-direct", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-2512:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "QwenImageModularPipeline", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageImg2ImgPipeline", + "profileId": "qwen-image:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:inpaint-direct", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-2512:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "QwenImageModularPipeline", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImageInpaintPipeline", + "profileId": "qwen-image:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:inpaint-direct/inpaint", + "standardAndNativeAlternativesCoexist": true, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "inpainting", + "requiredCoreRolesPresent": false, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-control-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/control_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "controlnet_image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:controlnet_vae_encoder" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:control-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "controlnet_text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt", + "upstream:controlnet_vae_encoder" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-control-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "control_inpaint", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/control_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "controlnet_inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:controlnet_vae_encoder" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "image_to_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/image_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "image_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/inpainting", + "standardAndNativeAlternativesCoexist": false, + "task": "inpainting", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/modular_text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "modular_text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "execution_profile_artifact:Qwen/Qwen-Image", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-control-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704592226, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image", + "revision": "75e0b4be04f60ec59a75f475837eced720f823b6" + }, + "artifactVariantOf": "qwen-image:modular/control_edit_image", + "canonicalTask": "control_edit_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/control_edit_image@Qwen/Qwen-Image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "controlnet_image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:controlnet_vae_encoder" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "execution_profile_artifact:Qwen/Qwen-Image", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:control-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704592226, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image", + "revision": "75e0b4be04f60ec59a75f475837eced720f823b6" + }, + "artifactVariantOf": "qwen-image:modular/control_image", + "canonicalTask": "control_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/control_image@Qwen/Qwen-Image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "controlnet_text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt", + "upstream:controlnet_vae_encoder" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "execution_profile_artifact:Qwen/Qwen-Image", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-control-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704592226, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image", + "revision": "75e0b4be04f60ec59a75f475837eced720f823b6" + }, + "artifactVariantOf": "qwen-image:modular/control_inpaint", + "canonicalTask": "control_inpaint", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/control_inpaint@Qwen/Qwen-Image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "controlnet_inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:controlnet_vae_encoder" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "execution_profile_artifact:Qwen/Qwen-Image", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704592226, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image", + "revision": "75e0b4be04f60ec59a75f475837eced720f823b6" + }, + "artifactVariantOf": "qwen-image:modular/image_to_image", + "canonicalTask": "image_to_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/image_to_image@Qwen/Qwen-Image", + "standardAndNativeAlternativesCoexist": false, + "task": "image_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "execution_profile_artifact:Qwen/Qwen-Image", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704592226, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image", + "revision": "75e0b4be04f60ec59a75f475837eced720f823b6" + }, + "artifactVariantOf": "qwen-image:modular/inpainting", + "canonicalTask": "inpaint", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/inpainting@Qwen/Qwen-Image", + "standardAndNativeAlternativesCoexist": false, + "task": "inpainting", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "execution_profile_artifact:Qwen/Qwen-Image", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704592226, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image", + "revision": "75e0b4be04f60ec59a75f475837eced720f823b6" + }, + "artifactVariantOf": "qwen-image:modular/modular_text_to_image", + "canonicalTask": "text_to_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/modular_text_to_image@Qwen/Qwen-Image", + "standardAndNativeAlternativesCoexist": false, + "task": "modular_text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:modular", + "execution_profile_artifact:unsloth/Qwen-Image-2512-unsloth-bnb-4bit", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-2512:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 18666097049, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "unsloth/Qwen-Image-2512-unsloth-bnb-4bit", + "revision": "f50b8c24fe21e9265509b15113b7cca82d0a4443" + }, + "artifactVariantOf": "qwen-image:modular/modular_text_to_image", + "canonicalTask": "text_to_image", + "displayName": "QwenImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageModularPipeline", + "profileId": "qwen-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:modular/modular_text_to_image@unsloth/Qwen-Image-2512-unsloth-bnb-4bit", + "standardAndNativeAlternativesCoexist": false, + "task": "modular_text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:t2i-direct", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-2512:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 30, + "exactBytes": 57704595735, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "QwenImageModularPipeline", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImagePipeline", + "profileId": "qwen-image:t2i-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:t2i-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:t2i-direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "QwenImageModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": false, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-image:t2i-direct", + "execution_profile_artifact:unsloth/Qwen-Image-2512-unsloth-bnb-4bit", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:qwen-image-2512:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 18666097049, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "unsloth/Qwen-Image-2512-unsloth-bnb-4bit", + "revision": "f50b8c24fe21e9265509b15113b7cca82d0a4443" + }, + "artifactVariantOf": "qwen-image:t2i-direct/text_to_image", + "canonicalTask": "text_to_image", + "displayName": "QwenImageModularPipeline", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "QwenImagePipeline", + "profileId": "qwen-image:t2i-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-image:modular" + ], + "knownDefects": [], + "modelType": "QwenImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-image:t2i-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-image:t2i-direct/text_to_image@unsloth/Qwen-Image-2512-unsloth-bnb-4bit", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "QwenImageModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": false, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:qwen-layered:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:qwen-image-layered:layer-decomposition:v1" + ], + "artifact": { + "declaredFileCount": 33, + "exactBytes": 57720484610, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Qwen/Qwen-Image-Layered", + "revision": "8f0ca708dfff6ba1dd5f2d85d78f8c108a040bcf" + }, + "artifactVariantOf": null, + "canonicalTask": "layer_decomposition", + "displayName": "QwenImageLayeredModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "QwenImageLayeredModularPipeline", + "profileId": "qwen-layered:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "qwen-layered:modular" + ], + "knownDefects": [], + "modelType": "QwenImageLayeredModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "qwen-layered:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "qwen-layered:modular/layer_decomposition", + "standardAndNativeAlternativesCoexist": false, + "task": "layer_decomposition", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "QwenImageLayeredModularPipeline", + "nativeWorkflowId": "default", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:real-esrgan-x2-image-upscale:direct", + "model_capability:SpandrelImageUpscale", + "node_model_dropdown:modules.Spandrel.Upscaler", + "studio_execution_spec:real-esrgan-x2-image-upscale:v1" + ], + "artifact": { + "declaredFileCount": 1, + "exactBytes": 67061725, + "gated": false, + "inventoryState": "exact_bytes", + "license": "bsd-3-clause", + "repository": "nateraw/real-esrgan", + "revision": "42efb9c3eeed1f5c0c8a626cf5f7f4481dfbb094" + }, + "artifactVariantOf": null, + "canonicalTask": "image_upscale", + "displayName": "Real-ESRGAN x2 Image Upscale", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "spandrel-image-upscale", + "loader": "modules.Spandrel.Upscaler", + "pipelineClass": "SpandrelImageUpscaleV1", + "profileId": "real-esrgan-x2-image-upscale:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "SpandrelImageUpscale", + "optionalRuntime": { + "delivery": "base", + "executionProfileIds": [ + "real-esrgan-x2-image-upscale:direct" + ], + "profileIds": [], + "reason": "no_optional_runtime_required", + "requiredNow": false, + "schemaVersion": 1, + "state": "base_satisfied" + }, + "routeId": "real-esrgan-x2-image-upscale:direct/image_upscale", + "standardAndNativeAlternativesCoexist": false, + "task": "image_upscale", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sana-600m-pag:direct", + "model_capability:SanaPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sana-600m-pag:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 17, + "exactBytes": 7700017758, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Efficient-Large-Model/Sana_600M_1024px_diffusers", + "revision": "28f3af7689de15f3883d5863059a2fca0aa9b829" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Sana 0.6B 1024px PAG", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "SanaPAGPipeline", + "profileId": "sana-600m-pag:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "SanaPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sana-600m-pag:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sana-600m-pag:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sana-600m:direct", + "model_capability:SanaPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sana-600m:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 17, + "exactBytes": 7700017758, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Efficient-Large-Model/Sana_600M_1024px_diffusers", + "revision": "28f3af7689de15f3883d5863059a2fca0aa9b829" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Sana 0.6B 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "SanaPipeline", + "profileId": "sana-600m:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "SanaPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sana-600m:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sana-600m:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sana-sprint-600m:direct", + "model_capability:SanaSprintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sana-sprint-600m:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 17, + "exactBytes": 7703536042, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Efficient-Large-Model/Sana_Sprint_0.6B_1024px_diffusers", + "revision": "aa76e7f4f4928f378716b6716a2130fba3caf5b1" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Sana Sprint 0.6B 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "SanaSprintPipeline", + "profileId": "sana-sprint-600m:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "SanaSprintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sana-sprint-600m:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sana-sprint-600m:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sana-sprint-600m:img2img-direct", + "model_capability:SanaSprintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sana-sprint-600m:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 17, + "exactBytes": 7703536042, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Efficient-Large-Model/Sana_Sprint_0.6B_1024px_diffusers", + "revision": "aa76e7f4f4928f378716b6716a2130fba3caf5b1" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "Sana Sprint 0.6B 1024px", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "SanaSprintImg2ImgPipeline", + "profileId": "sana-sprint-600m:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "SanaSprintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sana-sprint-600m:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sana-sprint-600m:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-base:direct", + "model_capability:StableDiffusionPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-base:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "stable-diffusion-v1-5", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionPipeline", + "profileId": "sd15-base:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-base:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-base:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-base:img2img-direct", + "model_capability:StableDiffusionPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-base:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "stable-diffusion-v1-5", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionImg2ImgPipeline", + "profileId": "sd15-base:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-base:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-base:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-base:inpaint-direct", + "model_capability:StableDiffusionPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-base:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "stable-diffusion-v1-5", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionInpaintPipeline", + "profileId": "sd15-base:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-base:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-base:inpaint-direct/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-controlnet-canny:direct", + "model_capability:StableDiffusionPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-controlnet-canny:control-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "stable-diffusion-v1-5", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionControlNetPipeline", + "profileId": "sd15-controlnet-canny:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-controlnet-canny:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-controlnet-canny:direct/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-controlnet-canny:img2img-direct", + "model_capability:StableDiffusionPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-controlnet-canny:control-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "stable-diffusion-v1-5", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionControlNetImg2ImgPipeline", + "profileId": "sd15-controlnet-canny:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-controlnet-canny:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-controlnet-canny:img2img-direct/control_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-controlnet-canny:inpaint-direct", + "model_capability:StableDiffusionPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-controlnet-canny:control-inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "control_inpaint", + "displayName": "stable-diffusion-v1-5", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionControlNetInpaintPipeline", + "profileId": "sd15-controlnet-canny:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-controlnet-canny:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-controlnet-canny:inpaint-direct/control_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-pag-controlnet-canny:direct", + "model_capability:StableDiffusionPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-pag-controlnet-canny:control-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "Stable Diffusion 1.5 PAG", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionControlNetPAGPipeline", + "profileId": "sd15-pag-controlnet-canny:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-pag-controlnet-canny:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-pag-controlnet-canny:direct/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-pag-controlnet-canny:inpaint-direct", + "model_capability:StableDiffusionPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-pag-controlnet-canny:control-inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "control_inpaint", + "displayName": "Stable Diffusion 1.5 PAG", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionControlNetPAGInpaintPipeline", + "profileId": "sd15-pag-controlnet-canny:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-pag-controlnet-canny:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-pag-controlnet-canny:inpaint-direct/control_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-pag:direct", + "model_capability:StableDiffusionPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-pag:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "Stable Diffusion 1.5 PAG", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionPAGPipeline", + "profileId": "sd15-pag:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-pag:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-pag:direct/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-pag:img2img-direct", + "model_capability:StableDiffusionPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-pag:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "Stable Diffusion 1.5 PAG", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionPAGImg2ImgPipeline", + "profileId": "sd15-pag:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-pag:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-pag:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sd15-pag:inpaint-direct", + "model_capability:StableDiffusionPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sd15-pag:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 8223292159, + "gated": false, + "inventoryState": "exact_bytes", + "license": "creativeml-openrail-m", + "repository": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "Stable Diffusion 1.5 PAG", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionPAGInpaintPipeline", + "profileId": "sd15-pag:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sd15-pag:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sd15-pag:inpaint-direct/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:direct", + "model_capability:StableDiffusionXLPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-base:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLPipeline", + "profileId": "sdxl-base:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "StableDiffusionXLModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:img2img-direct", + "model_capability:StableDiffusionXLPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-base:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLImg2ImgPipeline", + "profileId": "sdxl-base:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": true, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "StableDiffusionXLModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:inpaint-direct", + "model_capability:StableDiffusionXLPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-base:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLInpaintPipeline", + "profileId": "sdxl-base:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:inpaint-direct/inpaint", + "standardAndNativeAlternativesCoexist": true, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": "StableDiffusionXLModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-controlnet-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/control_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "controlnet_image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-controlnet-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "controlnet_text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-controlnet-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_inpaint", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/control_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "controlnet_inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-controlnet-union-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_union_edit_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/control_union_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_union_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "controlnet_union_image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-controlnet-union-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_union_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/control_union_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_union_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "controlnet_union_text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-controlnet-union-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_union_inpaint", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/control_union_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "control_union_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "controlnet_union_inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-controlnet-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_control_edit_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_control_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_controlnet_image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-controlnet-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_control_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_controlnet_text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-controlnet-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_control_inpaint", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_control_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_controlnet_inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-controlnet-union-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_control_union_edit_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_control_union_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_control_union_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_controlnet_union_image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-controlnet-union-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_control_union_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_control_union_image", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_control_union_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_controlnet_union_text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-controlnet-union-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_control_union_inpaint", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_control_union_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_control_union_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_controlnet_union_inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_edit_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_image", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-ip-adapter-inpainting:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "ip_adapter_inpaint", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/ip_adapter_inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "ip_adapter_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "ip_adapter_inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt", + "upstream:ip_adapter" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-base:modular", + "model_capability:StableDiffusionXLModularPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:sdxl-base:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "stable-diffusion-xl-base-1.0", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-base:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-base:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-base:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-controlnet-canny:direct", + "model_capability:StableDiffusionXLControlNetPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-controlnet-canny:control-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "SDXL ControlNet Canny", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLControlNetPipeline", + "profileId": "sdxl-controlnet-canny:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLControlNetPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-controlnet-canny:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-controlnet-canny:direct/control_image", + "standardAndNativeAlternativesCoexist": true, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-controlnet-canny:img2img-direct", + "model_capability:StableDiffusionXLControlNetPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-controlnet-canny:control-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "SDXL ControlNet Canny", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLControlNetImg2ImgPipeline", + "profileId": "sdxl-controlnet-canny:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLControlNetPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-controlnet-canny:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-controlnet-canny:img2img-direct/control_edit_image", + "standardAndNativeAlternativesCoexist": true, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-controlnet-canny:inpaint-direct", + "model_capability:StableDiffusionXLControlNetPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-controlnet-canny:control-inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_inpaint", + "displayName": "SDXL ControlNet Canny", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLControlNetInpaintPipeline", + "profileId": "sdxl-controlnet-canny:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLControlNetPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-controlnet-canny:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-controlnet-canny:inpaint-direct/control_inpaint", + "standardAndNativeAlternativesCoexist": true, + "task": "control_inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-instruct-pix2pix:direct", + "model_capability:StableDiffusionXLInstructPix2PixPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-instruct-pix2pix:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 20, + "exactBytes": 12243474539, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "diffusers/sdxl-instructpix2pix-768", + "revision": "06653d47f8d22f2c2205a5884d6a24c5e76d2ca7" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "SDXL InstructPix2Pix 768", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLInstructPix2PixPipeline", + "profileId": "sdxl-instruct-pix2pix:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionXLInstructPix2PixPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-instruct-pix2pix:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-instruct-pix2pix:direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag-controlnet-canny:direct", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-pag-controlnet-canny:control-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Native PAG uses the pinned guider's exclusive start boundary: step zero is unperturbed.", + "Fixed PAG scale only; adaptive PAG scale remains a standard-pipeline feature.", + "Explicit mid-block preset targets all ten SDXL transformer layers; not bitwise equivalence." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "sdxl-pag:modular", + "nativeTask": "control_image", + "source": "guiders/perturbed_attention_guidance.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLControlNetPAGPipeline", + "profileId": "sdxl-pag-controlnet-canny:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-pag:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag-controlnet-canny:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag-controlnet-canny:direct/control_image", + "standardAndNativeAlternativesCoexist": true, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag-controlnet-canny:img2img-direct", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-pag-controlnet-canny:control-edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Native PAG uses the pinned guider's exclusive start boundary: step zero is unperturbed.", + "Fixed PAG scale only; adaptive PAG scale remains a standard-pipeline feature.", + "Explicit mid-block preset targets all ten SDXL transformer layers; not bitwise equivalence." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "sdxl-pag:modular", + "nativeTask": "control_edit_image", + "source": "guiders/perturbed_attention_guidance.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLControlNetPAGImg2ImgPipeline", + "profileId": "sdxl-pag-controlnet-canny:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-pag:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag-controlnet-canny:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag-controlnet-canny:img2img-direct/control_edit_image", + "standardAndNativeAlternativesCoexist": true, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": "StableDiffusionXLModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag:direct", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-pag:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Native PAG uses the pinned guider's exclusive start boundary: step zero is unperturbed.", + "Fixed PAG scale only; adaptive PAG scale remains a standard-pipeline feature.", + "Explicit mid-block preset targets all ten SDXL transformer layers; not bitwise equivalence." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "sdxl-pag:modular", + "nativeTask": "text_to_image", + "source": "guiders/perturbed_attention_guidance.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLPAGPipeline", + "profileId": "sdxl-pag:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-pag:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "StableDiffusionXLModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag:img2img-direct", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-pag:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Native PAG uses the pinned guider's exclusive start boundary: step zero is unperturbed.", + "Fixed PAG scale only; adaptive PAG scale remains a standard-pipeline feature.", + "Explicit mid-block preset targets all ten SDXL transformer layers; not bitwise equivalence." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "sdxl-pag:modular", + "nativeTask": "image_to_image", + "source": "guiders/perturbed_attention_guidance.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLPAGImg2ImgPipeline", + "profileId": "sdxl-pag:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-pag:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": true, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": "StableDiffusionXLModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag:inpaint-direct", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-pag:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "Native PAG uses the pinned guider's exclusive start boundary: step zero is unperturbed.", + "Fixed PAG scale only; adaptive PAG scale remains a standard-pipeline feature.", + "Explicit mid-block preset targets all ten SDXL transformer layers; not bitwise equivalence." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "sdxl-pag:modular", + "nativeTask": "inpaint", + "source": "guiders/perturbed_attention_guidance.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLPAGInpaintPipeline", + "profileId": "sdxl-pag:inpaint-direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-pag:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag:inpaint-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag:inpaint-direct/inpaint", + "standardAndNativeAlternativesCoexist": true, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": "StableDiffusionXLModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag:modular", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_edit_image", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-pag:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag:modular/control_edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "controlnet_image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag:modular", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-pag:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag:modular/control_image", + "standardAndNativeAlternativesCoexist": false, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "controlnet_text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag:modular", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "image_to_image", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-pag:modular" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag:modular/image_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "image_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag:modular", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-pag:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag:modular/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "inpainting", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-pag:modular", + "model_capability:StableDiffusionXLPAGPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "SDXL Perturbed-Attention Guidance", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-pag:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLPAGPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-pag:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-pag:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-t2i-adapter-canny:direct", + "model_capability:StableDiffusionXLAdapterPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-t2i-adapter-canny:control-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941211875, + "gated": false, + "inventoryState": "exact_bytes", + "license": "openrail++", + "repository": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b" + }, + "artifactVariantOf": null, + "canonicalTask": "control_image", + "displayName": "SDXL T2I-Adapter Canny", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLAdapterPipeline", + "profileId": "sdxl-t2i-adapter-canny:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-base:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLAdapterPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-t2i-adapter-canny:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-t2i-adapter-canny:direct/control_image", + "standardAndNativeAlternativesCoexist": true, + "task": "control_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-turbo:direct", + "model_capability:StableDiffusionXLTurboPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:sdxl-turbo:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941207118, + "gated": false, + "inventoryState": "exact_bytes", + "license": "sai-nc-community", + "repository": "stabilityai/sdxl-turbo", + "revision": "71153311d3dbb46851df1931d3ca6e939de83304" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "SDXL Turbo", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": { + "decision": "native_recipe", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "512px, one step, CFG disabled, repository scheduler and exact fp16 weight variant." + ], + "liveQualification": "not_established_by_review", + "nativeProfileId": "sdxl-turbo:modular", + "nativeTask": "text_to_image", + "source": "modular_pipelines/stable_diffusion_xl/denoise.py", + "status": "reviewed" + }, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "StableDiffusionXLTurboPipeline", + "profileId": "sdxl-turbo:direct" + }, + "integratedNativeAlternativeProfileIds": [ + "sdxl-turbo:modular" + ], + "knownDefects": [], + "modelType": "StableDiffusionXLTurboPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-turbo:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-turbo:direct/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "StableDiffusionXLModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:sdxl-turbo:modular", + "model_capability:StableDiffusionXLTurboPipeline", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 6941207118, + "gated": false, + "inventoryState": "exact_bytes", + "license": "sai-nc-community", + "repository": "stabilityai/sdxl-turbo", + "revision": "71153311d3dbb46851df1931d3ca6e939de83304" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "SDXL Turbo", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "StableDiffusionXLModularPipeline", + "profileId": "sdxl-turbo:modular" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "StableDiffusionXLTurboPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "sdxl-turbo:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "sdxl-turbo:modular/text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "StableDiffusionXLModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:smolvlm-256m-instruct:direct", + "model_capability:HuggingFaceImageTextToTextModel", + "node_model_dropdown:modules.HuggingFaceTransformers.LoadImageTextToTextModel", + "studio_execution_spec:smolvlm-256m-instruct:image-to-text:v1" + ], + "artifact": { + "declaredFileCount": 14, + "exactBytes": 517904565, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "HuggingFaceTB/SmolVLM-256M-Instruct", + "revision": "7e3e67edbbed1bf9888184d9df282b700a323964" + }, + "artifactVariantOf": null, + "canonicalTask": "image_to_text", + "displayName": "SmolVLM 256M Instruct", + "disposition": "non_diffusion_image_task", + "dispositionReason": "task_has_no_diffusion_stage_semantics", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-huggingface-transformers-image-text", + "loader": "modules.HuggingFaceTransformers.LoadImageTextToTextModel", + "pipelineClass": "AutoModelForImageTextToText", + "profileId": "smolvlm-256m-instruct:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "HuggingFaceImageTextToTextModel", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "smolvlm-256m-instruct:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "smolvlm-256m-instruct:direct/image_to_text", + "standardAndNativeAlternativesCoexist": false, + "task": "image_to_text", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": null, + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:z-image-inpaint:direct", + "model_capability:ZImageInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:z-image-inpaint-direct:inpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 32848321404, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Tongyi-MAI/Z-Image-Turbo", + "revision": "f332072aa78be7aecdf3ee76d5c247082da564a6" + }, + "artifactVariantOf": null, + "canonicalTask": "inpaint", + "displayName": "Z-Image-Turbo Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ZImageInpaintPipeline", + "profileId": "z-image-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "ZImageInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "z-image-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "z-image-inpaint:direct/inpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "inpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:z-image-inpaint:direct", + "model_capability:ZImageInpaintPipeline", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:z-image-inpaint-direct:outpaint:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 32848321404, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Tongyi-MAI/Z-Image-Turbo", + "revision": "f332072aa78be7aecdf3ee76d5c247082da564a6" + }, + "artifactVariantOf": null, + "canonicalTask": "outpaint", + "displayName": "Z-Image-Turbo Inpaint", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ZImageInpaintPipeline", + "profileId": "z-image-inpaint:direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "ZImageInpaintPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "z-image-inpaint:direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "z-image-inpaint:direct/outpaint", + "standardAndNativeAlternativesCoexist": false, + "task": "outpaint", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:z-image:auto", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:z-image:text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 32848321404, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Tongyi-MAI/Z-Image-Turbo", + "revision": "f332072aa78be7aecdf3ee76d5c247082da564a6" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "ZImageModularPipeline", + "disposition": "preserved_standard_alternative", + "dispositionReason": "exact_artifact_task_has_integrated_native_alternative", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ZImagePipeline", + "profileId": "z-image:auto" + }, + "integratedNativeAlternativeProfileIds": [ + "z-image:modular" + ], + "knownDefects": [], + "modelType": "ZImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "z-image:auto" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "z-image:auto/text_to_image", + "standardAndNativeAlternativesCoexist": true, + "task": "text_to_image", + "upstream": { + "candidateNativePipelineClass": "ZImageModularPipeline", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": "ZImageModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": false, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:z-image:img2img-direct", + "node_model_dropdown:modules.DiffusersImage.LoadPipeline", + "studio_execution_spec:z-image:edit-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 32848321404, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Tongyi-MAI/Z-Image-Turbo", + "revision": "f332072aa78be7aecdf3ee76d5c247082da564a6" + }, + "artifactVariantOf": null, + "canonicalTask": "edit_image", + "displayName": "ZImageModularPipeline", + "disposition": "documented_whole_pipeline_exception", + "dispositionReason": "no_pinned_modular_workflow_for_exact_profile", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "not_applicable_or_missing" + }, + "exactVariantReview": null, + "exception": { + "checkedDiffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract" + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change" + }, + "implementation": { + "executionPath": "direct-diffusers-image", + "loader": "modules.DiffusersImage.LoadPipeline", + "pipelineClass": "ZImageImg2ImgPipeline", + "profileId": "z-image:img2img-direct" + }, + "integratedNativeAlternativeProfileIds": [], + "knownDefects": [], + "modelType": "ZImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "z-image:img2img-direct" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "z-image:img2img-direct/edit_image", + "standardAndNativeAlternativesCoexist": false, + "task": "edit_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": false, + "inventoryCoverage": "executable", + "nativePipelineClass": null, + "nativeWorkflowId": null, + "requiredCoreRolesPresent": false, + "stageRoles": [] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:z-image:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:z-image:modular-image-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 32848321404, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Tongyi-MAI/Z-Image-Turbo", + "revision": "f332072aa78be7aecdf3ee76d5c247082da564a6" + }, + "artifactVariantOf": null, + "canonicalTask": "image_to_image", + "displayName": "ZImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "ZImageModularPipeline", + "profileId": "z-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "z-image:modular" + ], + "knownDefects": [], + "modelType": "ZImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "z-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "z-image:modular/modular_image_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "modular_image_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "ZImageModularPipeline", + "nativeWorkflowId": "image2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_image", + "encode_prompt" + ] + } + }, + { + "advertisementEntryPoints": [ + "execution_profile:z-image:modular", + "node_model_dropdown:modules.ModularDiffusers.ModelsLoader", + "studio_execution_spec:z-image:modular-text-to-image:v1" + ], + "artifact": { + "declaredFileCount": 21, + "exactBytes": 32848321404, + "gated": false, + "inventoryState": "exact_bytes", + "license": "apache-2.0", + "repository": "Tongyi-MAI/Z-Image-Turbo", + "revision": "f332072aa78be7aecdf3ee76d5c247082da564a6" + }, + "artifactVariantOf": null, + "canonicalTask": "text_to_image", + "displayName": "ZImageModularPipeline", + "disposition": "native_integrated", + "dispositionReason": "complete_native_stage_chain_declared", + "evidence": { + "browser": "pending_final_candidate", + "nativeOutput": "pending", + "parameterConsumption": "pending", + "platform": "pending", + "reuse": "pending", + "service": "pending", + "structural": "implemented_awaiting_qualification" + }, + "exactVariantReview": null, + "exception": null, + "implementation": { + "executionPath": "modular-diffusers", + "loader": "modules.ModularDiffusers.ModelsLoader", + "pipelineClass": "ZImageModularPipeline", + "profileId": "z-image:modular" + }, + "integratedNativeAlternativeProfileIds": [ + "z-image:modular" + ], + "knownDefects": [], + "modelType": "ZImageModularPipeline", + "optionalRuntime": { + "delivery": "optional_overlay", + "executionProfileIds": [ + "z-image:modular" + ], + "profileIds": [ + "huggingface-transformers-main-96fe6dce-peft-0.20.0" + ], + "reason": "optional_runtime_status_required", + "requiredNow": true, + "schemaVersion": 1, + "state": "unavailable" + }, + "routeId": "z-image:modular/modular_text_to_image", + "standardAndNativeAlternativesCoexist": false, + "task": "modular_text_to_image", + "upstream": { + "candidateNativePipelineClass": null, + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "fullStageChain": true, + "inventoryCoverage": "executable", + "nativePipelineClass": "ZImageModularPipeline", + "nativeWorkflowId": "text2image", + "requiredCoreRolesPresent": true, + "stageRoles": [ + "decode_latents", + "denoise", + "encode_prompt" + ] + } + } + ], + "schemaVersion": 1, + "sources": { + "exactVariantReviews": "sha256:59206575f988315da524f206822ca6186bf713a46325ce130918825008c9a2aa", + "modelArtifactCatalog": "sha256:5f3fa06b5e8bd1d5ee2a1fa1c618779e52399c94789d52d4f7fe6ad405c2726e", + "modularWorkflowSnapshot": "sha256:8dad3f4ef31b456ef9c6e46718be9a17c023ae4306fdba129c72eeeed6573dc3", + "operationInventory": "sha256:b5bb015392d8fec47716842bbf896ef977312039dfef1a987ad75295dc860158", + "publicExecutionProfiles": "sha256:35ef2af6b65bd70f8ee5e8f85ddb147f547fc4dc591c745e60360cf2eb2bdc79" + }, + "summary": { + "denominatorFrozen": false, + "dispositions": { + "blocked": 2, + "documented_whole_pipeline_exception": 89, + "native_integrated": 70, + "non_diffusion_image_task": 14, + "preserved_standard_alternative": 38 + }, + "freezeBlockers": [ + "paired_client_advertisement_gate_not_recorded", + "custom_first_party_examples_not_recorded" + ], + "knownDefectRouteCount": 12, + "nativeIntegrationMissingCount": 0, + "profileCount": 124, + "routeCount": 213 + } +} diff --git a/data/model-artifact-catalog.json b/data/model-artifact-catalog.json index a3fbc91c..698d6000 100644 --- a/data/model-artifact-catalog.json +++ b/data/model-artifact-catalog.json @@ -1,4 +1,7226 @@ { + "downloadInventories": [ + { + "repo": "black-forest-labs/FLUX.2-klein-9b-kv", + "revision": "a6dfb36eca3a3906eb2fd460795adfb844e5fcce", + "checkedAt": "2026-09-24T01:50:46.374898+00:00", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.2-klein-9b-kv/revision/a6dfb36eca3a3906eb2fd460795adfb844e5fcce", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1723, + "gitBlobId": "b9de196b9068ce82b4c935df3ba214a4a247983f" + }, + { + "path": "LICENSE", + "byteSize": 18157, + "gitBlobId": "828139a03d16cc28f8b8c33d2467659d0d0924ea" + }, + { + "path": "README.md", + "byteSize": 5446, + "gitBlobId": "a24e340354ad17d2f95518faa401877bd2d8d465" + }, + { + "path": "model_index.json", + "byteSize": 446, + "gitBlobId": "554a39bf1019138dd0b28809b745df8ef33657c8" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 486, + "gitBlobId": "7e53605e2643440b310fa554f087641893883c5f" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1538, + "gitBlobId": "dd0d909c489845c5042d866f0d9f258784a90304" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 214, + "gitBlobId": "f5af93d0c7764fa38e3b1cd3aaba1b6307246d3b" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4902257696, + "sha256": "c0dc64934ae0f730ddc80d99af44968d01a89e8454df07d762096ea1356446bc" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4915960368, + "sha256": "d58533b468c31caa0222540a8aefddea1d74dc6e1fee928da8556d3d85729d6e" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4983068496, + "sha256": "74927fec432e050365bf757bb30348f560a44394efac89f492680b7c910b64fd" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 1580230264, + "sha256": "cb73f466fade5716702bda38d4e3b321c9358c39889e46fb9d613fb038bfcb2f" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 32914, + "gitBlobId": "991332e2a7dada9479948259f715fe1f6c69db54" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 4168, + "gitBlobId": "01be9b307daa2d425f7c168c9fb145a286e0afb4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 5404, + "gitBlobId": "ddaf69808214a44fdd26d3785b66c1367c78277a" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 468, + "gitBlobId": "ee730d550f26efb76dc171816c4476c82cc63820" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00002.safetensors", + "byteSize": 9801069272, + "sha256": "e5ae6b23b578d60cf74fe787e2eca638ac80e8f7fb90b80d9fd46d9c040ba706" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00002.safetensors", + "byteSize": 8356121608, + "sha256": "9f34c39d1fccc36048040bdcb6129065c3e87c0b050d441fad4e80f0d997325d" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 24875, + "gitBlobId": "53b7e151560e69b6b242208b98da4eb25b0111c6" + }, + { + "path": "vae/config.json", + "byteSize": 821, + "gitBlobId": "c3f38eb4c188a96a462519159356bd0fcc28cd14" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 168120878, + "sha256": "ca70d2202afe6415bdbcb8793ba8cd99fd159cfe6192381504d6c4d3036e0f04" + } + ] + }, + { + "repo": "Qwen/Qwen-Image", + "revision": "75e0b4be04f60ec59a75f475837eced720f823b6", + "checkedAt": "2026-09-24T01:50:46.635456+00:00", + "source": "https://huggingface.co/api/models/Qwen/Qwen-Image/revision/75e0b4be04f60ec59a75f475837eced720f823b6", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "LICENSE", + "byteSize": 11343, + "gitBlobId": "6634c8cc3133b3848ec74b9f275acaaa1ea618ab" + }, + { + "path": "README.md", + "byteSize": 6778, + "gitBlobId": "c43f73115c31ce1b53bd31140c41eae7a4c4b964" + }, + { + "path": "model_index.json", + "byteSize": 443, + "gitBlobId": "602665a351bf4afe6aa02a8a7f52f26fd99b88f8" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 485, + "gitBlobId": "637ac12d395c92a8dd54c87441e352c93c609dfc" + }, + { + "path": "text_encoder/config.json", + "byteSize": 3217, + "gitBlobId": "27d57dbd1b133179a359b6969f5d28d78e633910" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 244, + "gitBlobId": "fb9a91993c52bd7a752938452eaee2e73b23df4d" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4968243304, + "sha256": "d725335e4ea2399be706469e4b8807716a8fa64bd03468252e9f7acf2415fee4" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4991495816, + "sha256": "b1830db6908dcc76df3a71492acbcf2b8cac130114cf1f3c2d9edae8de8c6de3" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4932751040, + "sha256": "09c1807c6d00d7cab94f7db39d4c02ebb8537225ccde383861ac48db97945aa6" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 1691924384, + "sha256": "5dd068336d14d45ffb43cef374d286cc6ba9d8741b028f90a7d040d847961f4a" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 57655, + "gitBlobId": "92b467639266f4aa72a9b5ab73dbdf5a1f936ee4" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 4686, + "gitBlobId": "eaed590d62aaf0ba31e284b66ddcb18222f066c2" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 3383407, + "gitBlobId": "6bce3a0a3866c4791a74d83d78f6824c3af64ec3" + }, + { + "path": "transformer/config.json", + "byteSize": 371, + "gitBlobId": "4bff1e57a145b5b55dbca14d39af45c9c1b7dab7" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00009.safetensors", + "byteSize": 4989364312, + "sha256": "9f33a59093af3abcc2836d4cf4b7bd122c238ca70a26c70f34fdde64646b3bcd" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00009.safetensors", + "byteSize": 4984214160, + "sha256": "49d580e85b932e15c226e68a9d5549629878d3a128da7662d28401956cee0238" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00009.safetensors", + "byteSize": 4946470000, + "sha256": "0d72f0cbd0d6c2bdc2e1e73e41ac143452fc9e4570d1362203498832302de69f" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00009.safetensors", + "byteSize": 4984213736, + "sha256": "ee87991bfa1bc8e7553034b07099151532524101ca1581c4f3a86292814e9f0d" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00009.safetensors", + "byteSize": 4946471896, + "sha256": "83291a65be26fe688db4cf81383749831b623a6fcb1a71c851bafb55ddc0058d" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00009.safetensors", + "byteSize": 4946451560, + "sha256": "61e936a995e7f4ce08d44b9d6ffb56dc65dd61200288876ee2fda72da322583d" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00009.safetensors", + "byteSize": 4908690520, + "sha256": "854bc9e4a65c11c256f2fef31606a6407e9a425e8bde394db8be71416c515ead" + }, + { + "path": "transformer/diffusion_pytorch_model-00008-of-00009.safetensors", + "byteSize": 4984232856, + "sha256": "caedc7cc2914ab113cfbb3684cf072350201182ae8de1a7308e419385987ae40" + }, + { + "path": "transformer/diffusion_pytorch_model-00009-of-00009.safetensors", + "byteSize": 1170918840, + "sha256": "ae0fef149890f61d54572baa1e806de97156c7ec5b7bbbbbbf27a2d87f33af61" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 198887, + "gitBlobId": "056fddcb79864327ada8808ab3b584b6f484355d" + }, + { + "path": "vae/config.json", + "byteSize": 730, + "gitBlobId": "91a8e175d85b57be6e0d8cdef1bec11e63836c57" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "0c8bc8b758c649abef9ea407b95408389a3b2f610d0d10fcb054fe171d0a8344" + } + ] + }, + { + "repo": "unsloth/Qwen-Image-2512-unsloth-bnb-4bit", + "revision": "f50b8c24fe21e9265509b15113b7cca82d0a4443", + "checkedAt": "2026-09-24T01:50:46.909596+00:00", + "source": "https://huggingface.co/api/models/unsloth/Qwen-Image-2512-unsloth-bnb-4bit/revision/f50b8c24fe21e9265509b15113b7cca82d0a4443", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1573, + "gitBlobId": "72cfa368a409793b3bc69b4f8dd7803c5058f3f5" + }, + { + "path": "README.md", + "byteSize": 21186, + "gitBlobId": "023898e33a730ca48a77c15c1545c8624b151bd0" + }, + { + "path": "model_index.json", + "byteSize": 481, + "gitBlobId": "2682a458ce3d3a1d081dc69a32ec716ce4d3bf80" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 480, + "gitBlobId": "84c19ce09b07a67c76d23ff007242904f013484e" + }, + { + "path": "text_encoder/config.json", + "byteSize": 6077, + "gitBlobId": "cc3366dd35f1b8620c544486b66597409a538f4f" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 237, + "gitBlobId": "ecc93934f3c8e4623460916f86717c3203b69413" + }, + { + "path": "text_encoder/model-00001-of-00002.safetensors", + "byteSize": 4983743979, + "sha256": "4ce76d3a8b5d90df9f44c892eb2a9ce844069edbe682b79af471d15b492f1b97" + }, + { + "path": "text_encoder/model-00002-of-00002.safetensors", + "byteSize": 1916653043, + "sha256": "2c90f42d3fa889e1a20649ff0afdc87b55ab896b9f4ba264c4a73c21fc159873" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 152392, + "gitBlobId": "a63fb3c64c9f368df3256f1e3f6464bab1efdfb0" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 4686, + "gitBlobId": "eaed590d62aaf0ba31e284b66ddcb18222f066c2" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 3383407, + "gitBlobId": "6bce3a0a3866c4791a74d83d78f6824c3af64ec3" + }, + { + "path": "transformer/config.json", + "byteSize": 1830, + "gitBlobId": "97d7c74bfef2765c7a418300591aad462c1d3a83" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 4991806817, + "sha256": "c9b83188655ca7252182f9bd69972990b2ba675770ce6bad612fac476a00b9e1" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 4981560151, + "sha256": "96156c6c242556a7689736f384fc39d4361c9725cd6721bab6db05f89f6ba371" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 1532591728, + "sha256": "d966f133a98dc13462126cc5d5f9fc073f7a4aff9f993f0cb0c85f819ee1f8d0" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 688062, + "gitBlobId": "17eac6ed2bf4b745646afc7cbf997fa1edfd82d4" + }, + { + "path": "vae/config.json", + "byteSize": 883, + "gitBlobId": "ace0f6c47587daf69f7610f7d4eedcf728d295e4" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "0c8bc8b758c649abef9ea407b95408389a3b2f610d0d10fcb054fe171d0a8344" + } + ] + }, + { + "repo": "fuliucansheng/FLUX.1-Canny-dev-diffusers", + "revision": "24df2ba1c46a8c735589cc8f81302678797c9821", + "checkedAt": "2026-09-24T01:50:47.193373+00:00", + "source": "https://huggingface.co/api/models/fuliucansheng/FLUX.1-Canny-dev-diffusers/revision/24df2ba1c46a8c735589cc8f81302678797c9821", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 2410, + "gitBlobId": "ba159b1bda89648e36db95ace6afed6eb7d7ec74" + }, + { + "path": "LICENSE.md", + "byteSize": 17091, + "gitBlobId": "2e071518f6b079b9b571f56e51d6732bda5e1500" + }, + { + "path": "README.md", + "byteSize": 4518, + "gitBlobId": "10e10c64c0b4019fa32de3cf632c46ea42347adf" + }, + { + "path": "model_index.json", + "byteSize": 594, + "gitBlobId": "e211c5b00c5b576a6c2ecb7caed95900d3d88700" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 299, + "gitBlobId": "9ab63efc0533c769b781dddbd177fad8c8c18d49" + }, + { + "path": "text_encoder/config.json", + "byteSize": 612, + "gitBlobId": "ba7f38742324e831507964a28e8caec05ad07665" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 492265168, + "sha256": "778d02eb9e707c3fbaae0b67b79ea0d1399b52e624fb634f2f19375ae7c047c3" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 781, + "gitBlobId": "f275773219a8b09538cf72276cf7b3cd8c4b4675" + }, + { + "path": "text_encoder_2/model-00001-of-00004.safetensors", + "byteSize": 4989319680, + "sha256": "7a68b2c8c080696a10109612a649bc69330991ecfea65930ccfdfbdb011f2686" + }, + { + "path": "text_encoder_2/model-00002-of-00004.safetensors", + "byteSize": 4999830656, + "sha256": "b8ed6556d7507e38af5b428c605fb2a6f2bdb7e80bd481308b865f7a40c551ca" + }, + { + "path": "text_encoder_2/model-00003-of-00004.safetensors", + "byteSize": 4865612720, + "sha256": "c831635f83041f83faf0024b39c6ecb21b45d70dd38a63ea5bac6c7c6e5e558c" + }, + { + "path": "text_encoder_2/model-00004-of-00004.safetensors", + "byteSize": 4194506688, + "sha256": "02a5f2d69205be92ad48fe5d712d38c2ff55627969116aeffc58bd75a28da468" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19886, + "gitBlobId": "0900b8ebd70052d4cccc2eacf6e07d75bb1f9cb8" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 588, + "gitBlobId": "cf0682d6de72c1547f41b4f6d7c59f62deffef94" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 705, + "gitBlobId": "180a4e1be2a7d0b38a44108d6f24f585f35147b1" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer_2/tokenizer.json", + "byteSize": 2424235, + "gitBlobId": "21ed409afa3df5a822caa0fd5da30d13941f90b5" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 20817, + "gitBlobId": "b336fa236135e6c87e246485e4d69e415cc57da0" + }, + { + "path": "transformer/config.json", + "byteSize": 453, + "gitBlobId": "d3a5d9b592319fded72547bf0de9b3cea492f639" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9983433520, + "sha256": "820edf2dfc8f62fd300c1d0750f77edf8f796da0873ffc9d7160e5e5e7c94306" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9949328904, + "sha256": "c7b12cbca52531e05b5e201bb4bfcfe6ff52144c6ecbf2e7bcf00db58dd815aa" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3870584832, + "sha256": "8bf064feaf469cc924350aaf9403ff3703ecb926db638ebbdc52b1cb615db83e" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 121262, + "gitBlobId": "e46f02a62954ce137b34e4cbc5b649b70d1fdd55" + }, + { + "path": "vae/config.json", + "byteSize": 817, + "gitBlobId": "17055c09f7556289b3042579ef11d841a9bedc93" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 335306212, + "sha256": "f4487eaa8df19a5254ce83a01d402e93d2b6acba769ed9bfeddc6849cd808745" + } + ] + }, + { + "repo": "Alpha-VLLM/Lumina-Image-2.0", + "revision": "53504abd8178b30685b6c4c7a4cd181ff78b73e9", + "checkedAt": "2026-09-24T01:43:25.751Z", + "source": "https://huggingface.co/api/models/Alpha-VLLM/Lumina-Image-2.0/revision/53504abd8178b30685b6c4c7a4cd181ff78b73e9", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": "model_index.json", + "byteSize": 411, + "gitBlobId": "b58a765d4215a36d540b94fb40d7e84c7cb668dd" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 419, + "gitBlobId": "997da72fb78f4956b81253ac5c39dd38f0ffe530" + }, + { + "path": "text_encoder/config.json", + "byteSize": 852, + "gitBlobId": "db14f6ba4fa8e5a06e117c0f5e807ed82a7ed5a8" + }, + { + "path": "text_encoder/model-00001-of-00003.safetensors", + "byteSize": 4992575568, + "sha256": "d61628bc793240439e608c5ae744f55ec8770f684abb63602648a24cb6da60bc" + }, + { + "path": "text_encoder/model-00002-of-00003.safetensors", + "byteSize": 4983442368, + "sha256": "fbd38d35b33ad210d436cd7e9b13339474e6cb9c533e00d91517e60457cf7dbd" + }, + { + "path": "text_encoder/model-00003-of-00003.safetensors", + "byteSize": 481381280, + "sha256": "e8ea83fa72a2c554b9bd80398d7688a07e48acb354ab64122905b3a6c362b368" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 22496, + "gitBlobId": "5161dbccf08fb25bd4312a106b5479f678105b48" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 636, + "gitBlobId": "8d6368f7e735fbe4781bf6e956b7c6ad0586df80" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 17525357, + "sha256": "3f289bc05132635a8bc7aca7aa21255efd5e18f3710f43e3cdb96bcd41be4922" + }, + { + "path": "tokenizer/tokenizer.model", + "byteSize": 4241003, + "sha256": "61a7b147390c64585d6c3543dd6fc636906c9af3865a5548f27f31aee1d4c8e2" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 46379, + "gitBlobId": "2357fc1a512dc3881eb99b9df3ef8e97ef7ffd51" + }, + { + "path": "transformer/config.json", + "byteSize": 513, + "gitBlobId": "da0d1a693c94fb797ee0ebe8a1d52847a0d70263" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00002.safetensors", + "byteSize": 9957078112, + "sha256": "132b4d213fdd3cfc14333746fc3eb8bbe6358cd73c3bc95ac4ccec230b97dca3" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00002.safetensors", + "byteSize": 482046552, + "sha256": "b9660a895cdedf3a023b131b9c655c32f7d0cd824d6b3f91f0810812bb592947" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 42158, + "gitBlobId": "f827ff81d4a32a88eb468e9a0482be2883c37722" + }, + { + "path": "vae/config.json", + "byteSize": 825, + "gitBlobId": "9eaa731f2941de820ce5c466f3719d89dccf6d0e" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 335306212, + "sha256": "8c717328c8ad41faab2ccfd52ae17332505c6833cf176aad56e7b58f2c4d4c94" + } + ] + }, + { + "repo": "Alpha-VLLM/Lumina-Next-SFT-diffusers", + "revision": "0ee5ec90043acf5cb41fe96274af36eb7fad8d95", + "checkedAt": "2026-09-24T01:43:26.016Z", + "source": "https://huggingface.co/api/models/Alpha-VLLM/Lumina-Next-SFT-diffusers/revision/0ee5ec90043acf5cb41fe96274af36eb7fad8d95", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1562, + "gitBlobId": "c4cb545ce9c9bbbff6607aa2f92b4a57a0851402" + }, + { + "path": "README.md", + "byteSize": 3641, + "gitBlobId": "bea52d69e006cea48ffe3ae0f951408ef7c98f6f" + }, + { + "path": "model_index.json", + "byteSize": 412, + "sha256": "a20a439cd2e262f614927d4dead3e389fef9212cf80b44516f5c124760a70412" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 141, + "sha256": "3944e5af804e1a7eec04e2e01911de26b15ddda871fc80da7e5d00be97632c76" + }, + { + "path": "text_encoder/config.json", + "byteSize": 698, + "sha256": "41691d3ce1da7f192958e7b510f07e77f97d892ab57be235adb303b951ada6c8" + }, + { + "path": "text_encoder/model-00001-of-00002.safetensors", + "byteSize": 4945241304, + "sha256": "01676b4c6e765f737a5e9854a315de3887e939c370cae116d505777729099a68" + }, + { + "path": "text_encoder/model-00002-of-00002.safetensors", + "byteSize": 67121584, + "sha256": "a5df19ba067f127d5c9588e2c50f034cf34fdd1b7fe3e16e4dbd5cb0fd7f3149" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 12505, + "sha256": "d47b0173786ca23b98fac689f80055aa6e33c3cc728714b3c1d13ea3e6dbb8e9" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 636, + "sha256": "baec30ea10906f16adb8c18af7a34023002c1746542612b8b41c9f09e1351351" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 17518965, + "sha256": "cd1df914b205b134e52f15364524a919423b48c9056d725b45c5b89187541de0" + }, + { + "path": "tokenizer/tokenizer.model", + "byteSize": 4241003, + "sha256": "61a7b147390c64585d6c3543dd6fc636906c9af3865a5548f27f31aee1d4c8e2" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 40017, + "sha256": "c10840544aaa731378719a541c9f79744ea6865bb49ad3261554a2013e7b8347" + }, + { + "path": "transformer/config.json", + "byteSize": 408, + "sha256": "045dec3435aaa49e214b23d2f24b24e3c3adbdccb4b999547834b5dff76140d1" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 3509863800, + "sha256": "371153b7c7b7a64899d4016970c7cc472039f9c9b21ebe073adf0b8525cdf1bd" + }, + { + "path": "vae/config.json", + "byteSize": 778, + "sha256": "c326f5b073bb19018434467c7bbd2f15b72386c7e2170777d5e3202b5186afca" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 334643268, + "sha256": "1598f3d24932bcfe6634e8b618ea1e30ab1d57f5aad13a6d2de446d2199f2341" + } + ] + }, + { + "repo": "ATH-MaaS/Ovis-Image-7B", + "revision": "41be1c5821a92c970d63d7eb595a2fd3fe32b22e", + "checkedAt": "2026-09-24T01:43:25.443Z", + "source": "https://huggingface.co/api/models/ATH-MaaS/Ovis-Image-7B/revision/41be1c5821a92c970d63d7eb595a2fd3fe32b22e", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": "LICENSE", + "byteSize": 548, + "gitBlobId": "7fcaa30e3ebf90c97106d294769991062b67f660" + }, + { + "path": "NOTICE", + "byteSize": 975, + "gitBlobId": "aee774d4f5982604fd333b34f7b7e10e630645ee" + }, + { + "path": "model_index.json", + "byteSize": 414, + "gitBlobId": "7ea601d984fd867b4f1fcb4e7090922711cefdb0" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 273, + "gitBlobId": "a64d1e8763bef5828984682680673fb97829734b" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1393, + "gitBlobId": "cc44a0a66f6ea0325b526ba1e015147201f687a6" + }, + { + "path": "text_encoder/model-00001-of-00002.safetensors", + "byteSize": 4969538328, + "sha256": "7d7489c13e12ab8bae21c43c7aab93782fc188d284a6eeb5dc06093eb72a8aff" + }, + { + "path": "text_encoder/model-00002-of-00002.safetensors", + "byteSize": 1912795064, + "sha256": "f252831dfac8bfd8b5d557426c4e3b48863602a8b0996758ed2b9f5e34eabcd4" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 23723, + "gitBlobId": "b12103cc3646d0055f4221584d3db59fabc886dd" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 1582, + "gitBlobId": "d02b44b6f343d54b332d3768b7cdd8c1b1aa0647" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 5404, + "gitBlobId": "ddaf69808214a44fdd26d3785b66c1367c78277a" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 339, + "gitBlobId": "5686dac7013ced5fa4483010945ab3941924dfb7" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00002.safetensors", + "byteSize": 9982976024, + "sha256": "c9f3fdf7fb030497a4e7907cc1bc97362fc30180ae087970d35dddb90767794b" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00002.safetensors", + "byteSize": 4757992496, + "sha256": "b7ceb6befeb153e856f09d85a04427939541e92093a6f848c8b0df9d307a097a" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 61002, + "gitBlobId": "cfef7b9d5f139cb7a91f8ce2571cd4111887a296" + }, + { + "path": "vae/config.json", + "byteSize": 774, + "gitBlobId": "b43183d0f5f0274bccd8054cd0069fc1d5f64586" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "baidu/ERNIE-Image-Turbo", + "revision": "bc68c81e2a1730a394d5fc9fae70713dee940140", + "checkedAt": "2026-09-24T01:43:30.662Z", + "source": "https://huggingface.co/api/models/baidu/ERNIE-Image-Turbo/revision/bc68c81e2a1730a394d5fc9fae70713dee940140", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1859, + "gitBlobId": "2e33eaead6461891b2b914632076f89f3ffe1c77" + }, + { + "path": "LICENSE", + "byteSize": 11366, + "gitBlobId": "c08297a559489b194914cf28d5f52e55bc6f0515" + }, + { + "path": "README.md", + "byteSize": 11154, + "gitBlobId": "6855b627c2c1696ac1b603a90851563ced157f7f" + }, + { + "path": "model_index.json", + "byteSize": 547, + "gitBlobId": "45fd46a02d0f21ecd0d7f048433e783a41cd155a" + }, + { + "path": "pe/chat_template.jinja", + "byteSize": 615, + "gitBlobId": "287d9c58b719d74382ca38862500bf84a73ca8ef" + }, + { + "path": "pe/config.json", + "byteSize": 901, + "gitBlobId": "bc08558592646345934a6ea1f995c5533d8f1325" + }, + { + "path": "pe/generation_config.json", + "byteSize": 153, + "gitBlobId": "7a13a866a501b0fad6ae84eeb82d55306bdb1858" + }, + { + "path": "pe/model.safetensors", + "byteSize": 7663346520, + "sha256": "abd26e2f4b5a0cc3922d3523649706f1d398050a766dc284161724d9ee47f808" + }, + { + "path": "pe/tokenizer.json", + "byteSize": 17077402, + "sha256": "286acad9b0e27fce778ac429763536accf618ccb6ed72963b6f94685e531c5c7" + }, + { + "path": "pe/tokenizer_config.json", + "byteSize": 21124, + "gitBlobId": "83047e1510453251f53c63cf84f12f9abee56a02" + }, + { + "path": "pe_tokenizer/chat_template.jinja", + "byteSize": 615, + "gitBlobId": "287d9c58b719d74382ca38862500bf84a73ca8ef" + }, + { + "path": "pe_tokenizer/tokenizer.json", + "byteSize": 17077402, + "sha256": "286acad9b0e27fce778ac429763536accf618ccb6ed72963b6f94685e531c5c7" + }, + { + "path": "pe_tokenizer/tokenizer_config.json", + "byteSize": 21124, + "gitBlobId": "83047e1510453251f53c63cf84f12f9abee56a02" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 482, + "gitBlobId": "f6a2d0ecbe5e65a7112582ee426b48dba7c6feae" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1678, + "gitBlobId": "ad5fd69765941d1d5b649f7c963bbb11e407d889" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 7698241104, + "sha256": "c2188c5b3d759ac35e3770056153fdcb348276bd1d10f0443a60a2bdcbb6ff7b" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 17078110, + "sha256": "577575622324b2e099e2648be26bdeb5e5815ffe66d7004e9e3ddbf421db6bf1" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 379, + "gitBlobId": "c6a4df56dd09b3744b40da612db91a5a746195d5" + }, + { + "path": "transformer/config.json", + "byteSize": 422, + "gitBlobId": "5991df9b0e807c0c8c1a7a44478378fb45d118ff" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00002.safetensors", + "byteSize": 9992362400, + "sha256": "7704ba8363a3f10e3ee11cb66044f0e87a2a342c1c116aea7cdbf664cd509a8b" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00002.safetensors", + "byteSize": 6074662728, + "sha256": "438e3e4784af84e10d961e516bbceddaabe1ecc4a6b607519e9020eadcc86c07" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 38947, + "gitBlobId": "4acd204e30f1a449174bb225cb6902c4ae2f7706" + }, + { + "path": "vae/config.json", + "byteSize": 836, + "gitBlobId": "8bc1826de586606dbad574d7775678308f1a318b" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 168120878, + "sha256": "ca70d2202afe6415bdbcb8793ba8cd99fd159cfe6192381504d6c4d3036e0f04" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.1-Canny-dev", + "revision": "27c3d8bdc17509b47cf4fd9ba25ab1c7508a69a2", + "checkedAt": "2026-09-24T01:43:30.924Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.1-Canny-dev/revision/27c3d8bdc17509b47cf4fd9ba25ab1c7508a69a2", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "LICENSE.md", + "byteSize": 18621, + "gitBlobId": "763a44c31c02064a8388981040487ce8731ddc86" + }, + { + "path": "README.md", + "byteSize": 4518, + "gitBlobId": "10e10c64c0b4019fa32de3cf632c46ea42347adf" + }, + { + "path": "model_index.json", + "byteSize": 594, + "gitBlobId": "e211c5b00c5b576a6c2ecb7caed95900d3d88700" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 299, + "gitBlobId": "9ab63efc0533c769b781dddbd177fad8c8c18d49" + }, + { + "path": "text_encoder/config.json", + "byteSize": 612, + "gitBlobId": "ba7f38742324e831507964a28e8caec05ad07665" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 492265168, + "sha256": "778d02eb9e707c3fbaae0b67b79ea0d1399b52e624fb634f2f19375ae7c047c3" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 781, + "gitBlobId": "f275773219a8b09538cf72276cf7b3cd8c4b4675" + }, + { + "path": "text_encoder_2/model-00001-of-00004.safetensors", + "byteSize": 4989319680, + "sha256": "7a68b2c8c080696a10109612a649bc69330991ecfea65930ccfdfbdb011f2686" + }, + { + "path": "text_encoder_2/model-00002-of-00004.safetensors", + "byteSize": 4999830656, + "sha256": "b8ed6556d7507e38af5b428c605fb2a6f2bdb7e80bd481308b865f7a40c551ca" + }, + { + "path": "text_encoder_2/model-00003-of-00004.safetensors", + "byteSize": 4865612720, + "sha256": "c831635f83041f83faf0024b39c6ecb21b45d70dd38a63ea5bac6c7c6e5e558c" + }, + { + "path": "text_encoder_2/model-00004-of-00004.safetensors", + "byteSize": 4194506688, + "sha256": "02a5f2d69205be92ad48fe5d712d38c2ff55627969116aeffc58bd75a28da468" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19886, + "gitBlobId": "0900b8ebd70052d4cccc2eacf6e07d75bb1f9cb8" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 588, + "gitBlobId": "cf0682d6de72c1547f41b4f6d7c59f62deffef94" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 705, + "gitBlobId": "180a4e1be2a7d0b38a44108d6f24f585f35147b1" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer_2/tokenizer.json", + "byteSize": 2424235, + "gitBlobId": "21ed409afa3df5a822caa0fd5da30d13941f90b5" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 20817, + "gitBlobId": "b336fa236135e6c87e246485e4d69e415cc57da0" + }, + { + "path": "transformer/config.json", + "byteSize": 453, + "gitBlobId": "d3a5d9b592319fded72547bf0de9b3cea492f639" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9983433520, + "sha256": "820edf2dfc8f62fd300c1d0750f77edf8f796da0873ffc9d7160e5e5e7c94306" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9949328904, + "sha256": "c7b12cbca52531e05b5e201bb4bfcfe6ff52144c6ecbf2e7bcf00db58dd815aa" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3870584832, + "sha256": "8bf064feaf469cc924350aaf9403ff3703ecb926db638ebbdc52b1cb615db83e" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 121262, + "gitBlobId": "e46f02a62954ce137b34e4cbc5b649b70d1fdd55" + }, + { + "path": "vae/config.json", + "byteSize": 817, + "gitBlobId": "17055c09f7556289b3042579ef11d841a9bedc93" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 335306212, + "sha256": "f4487eaa8df19a5254ce83a01d402e93d2b6acba769ed9bfeddc6849cd808745" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.1-Depth-dev", + "revision": "fb5e9b1bae41b8c8adcea4ea2a87b74dd298f07a", + "checkedAt": "2026-09-24T01:43:31.200Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.1-Depth-dev/revision/fb5e9b1bae41b8c8adcea4ea2a87b74dd298f07a", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "LICENSE.md", + "byteSize": 18621, + "gitBlobId": "763a44c31c02064a8388981040487ce8731ddc86" + }, + { + "path": "README.md", + "byteSize": 4742, + "gitBlobId": "07f47a7bd2c607730591713f8c9302613b8bb402" + }, + { + "path": "model_index.json", + "byteSize": 594, + "gitBlobId": "e211c5b00c5b576a6c2ecb7caed95900d3d88700" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 299, + "gitBlobId": "9ab63efc0533c769b781dddbd177fad8c8c18d49" + }, + { + "path": "text_encoder/config.json", + "byteSize": 612, + "gitBlobId": "ba7f38742324e831507964a28e8caec05ad07665" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 492265168, + "sha256": "778d02eb9e707c3fbaae0b67b79ea0d1399b52e624fb634f2f19375ae7c047c3" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 781, + "gitBlobId": "f275773219a8b09538cf72276cf7b3cd8c4b4675" + }, + { + "path": "text_encoder_2/model-00001-of-00004.safetensors", + "byteSize": 4989319680, + "sha256": "7a68b2c8c080696a10109612a649bc69330991ecfea65930ccfdfbdb011f2686" + }, + { + "path": "text_encoder_2/model-00002-of-00004.safetensors", + "byteSize": 4999830656, + "sha256": "b8ed6556d7507e38af5b428c605fb2a6f2bdb7e80bd481308b865f7a40c551ca" + }, + { + "path": "text_encoder_2/model-00003-of-00004.safetensors", + "byteSize": 4865612720, + "sha256": "c831635f83041f83faf0024b39c6ecb21b45d70dd38a63ea5bac6c7c6e5e558c" + }, + { + "path": "text_encoder_2/model-00004-of-00004.safetensors", + "byteSize": 4194506688, + "sha256": "02a5f2d69205be92ad48fe5d712d38c2ff55627969116aeffc58bd75a28da468" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19886, + "gitBlobId": "0900b8ebd70052d4cccc2eacf6e07d75bb1f9cb8" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 588, + "gitBlobId": "cf0682d6de72c1547f41b4f6d7c59f62deffef94" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 705, + "gitBlobId": "180a4e1be2a7d0b38a44108d6f24f585f35147b1" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer_2/tokenizer.json", + "byteSize": 2424235, + "gitBlobId": "21ed409afa3df5a822caa0fd5da30d13941f90b5" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 20817, + "gitBlobId": "b336fa236135e6c87e246485e4d69e415cc57da0" + }, + { + "path": "transformer/config.json", + "byteSize": 453, + "gitBlobId": "e5d6905ce1bea307b4329fdec81a4213a32c24aa" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9983433520, + "sha256": "ca1b8a358874a5023419cbf803fd86c0cf0d7382a4a4b82fb7259497aa3fe645" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9949328904, + "sha256": "3362f89d1f3c2d03b8fa17389d70f2033d64a08726442535c915cf0201c24c99" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3870584832, + "sha256": "61e59875679174c4de46570fae3088a032a6b563914341e2a3e54061fb66fc74" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 121262, + "gitBlobId": "e46f02a62954ce137b34e4cbc5b649b70d1fdd55" + }, + { + "path": "vae/config.json", + "byteSize": 817, + "gitBlobId": "17055c09f7556289b3042579ef11d841a9bedc93" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 335306212, + "sha256": "f4487eaa8df19a5254ce83a01d402e93d2b6acba769ed9bfeddc6849cd808745" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.1-dev", + "revision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21", + "checkedAt": "2026-09-24T01:43:32.569Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.1-dev/revision/3de623fc3c33e44ffbe2bad470d0f45bccf2eb21", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1655, + "gitBlobId": "147b590320fca0e30b07fce9b0aff6a9a5c3d6da" + }, + { + "path": "LICENSE.md", + "byteSize": 18621, + "gitBlobId": "763a44c31c02064a8388981040487ce8731ddc86" + }, + { + "path": "README.md", + "byteSize": 4394, + "gitBlobId": "6089463168e2a87d86f76a6abd7a35f1c791022d" + }, + { + "path": "model_index.json", + "byteSize": 536, + "gitBlobId": "f2dcd899e03be059304cc980a87bb9389008ba2f" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 273, + "gitBlobId": "a64d1e8763bef5828984682680673fb97829734b" + }, + { + "path": "text_encoder/config.json", + "byteSize": 613, + "gitBlobId": "5ebd923ce51522c605f64ab3d26ea024672777b6" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 246144352, + "sha256": "893d67a23f4693ed42cdab4cbad7fe3e727cf59609c40da28a46b5470f9ed082" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 782, + "gitBlobId": "b00a8a6908682a2091f1194b81f220fd2b67c41c" + }, + { + "path": "text_encoder_2/model-00001-of-00002.safetensors", + "byteSize": 4994582224, + "sha256": "ec87bffd1923e8b2774a6d240c922a41f6143081d52cf83b8fe39e9d838c893e" + }, + { + "path": "text_encoder_2/model-00002-of-00002.safetensors", + "byteSize": 4530066360, + "sha256": "a5640855b301fcdbceddfa90ae8066cd9414aff020552a201a255ecf2059da00" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19885, + "gitBlobId": "c8728bc3dca59d2616a2a594cbac3ddb9eb77d5b" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 588, + "gitBlobId": "cf0682d6de72c1547f41b4f6d7c59f62deffef94" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 705, + "gitBlobId": "180a4e1be2a7d0b38a44108d6f24f585f35147b1" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer_2/tokenizer.json", + "byteSize": 2424235, + "gitBlobId": "21ed409afa3df5a822caa0fd5da30d13941f90b5" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 20817, + "gitBlobId": "b336fa236135e6c87e246485e4d69e415cc57da0" + }, + { + "path": "transformer/config.json", + "byteSize": 378, + "gitBlobId": "dbb93438161bb7fbfdcbd8fabf2b67654a38be38" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9983040304, + "sha256": "d86a3038eacaa720682cb9b1da3c49fecf8a3ded605af4def6061eaa18903eb8" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9949328904, + "sha256": "5e830704a83aa938dfaf23da308100a1c44b83fa084283abf1d163ea727e5f7a" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3870584832, + "sha256": "0d9c7c663217d1c3d44a6deed4e1cf1ac09fbc2c4137c47de1e3d74c959833de" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 121262, + "gitBlobId": "a7b03767f2395865d98ad50c6e8234f3726af8c0" + }, + { + "path": "vae/config.json", + "byteSize": 820, + "gitBlobId": "4f7c628fc8e59fc842c9846ed74414249052ba0b" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.1-Fill-dev", + "revision": "358293da0354175698b67ec8299acf928313a78a", + "checkedAt": "2026-09-24T01:43:31.476Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.1-Fill-dev/revision/358293da0354175698b67ec8299acf928313a78a", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1571, + "gitBlobId": "6c07b2b184c926a167c96895381a7c9e8c54c905" + }, + { + "path": "LICENSE.md", + "byteSize": 18621, + "gitBlobId": "763a44c31c02064a8388981040487ce8731ddc86" + }, + { + "path": "README.md", + "byteSize": 4096, + "gitBlobId": "06057201e2db0fa1b096171b0a3921b88cd4a12d" + }, + { + "path": "model_index.json", + "byteSize": 540, + "gitBlobId": "b09a49f0a56a59b9fcaba3087ab3982fcdc79b95" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 299, + "gitBlobId": "9ab63efc0533c769b781dddbd177fad8c8c18d49" + }, + { + "path": "text_encoder/config.json", + "byteSize": 561, + "gitBlobId": "d496a6a2474172a0b3f2b19ea97d90e84287b075" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 246144352, + "sha256": "893d67a23f4693ed42cdab4cbad7fe3e727cf59609c40da28a46b5470f9ed082" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 741, + "gitBlobId": "cf6c4d78b3b4f575a8b296ee5fccf6ab4e14c686" + }, + { + "path": "text_encoder_2/model-00001-of-00002.safetensors", + "byteSize": 4994582224, + "sha256": "ec87bffd1923e8b2774a6d240c922a41f6143081d52cf83b8fe39e9d838c893e" + }, + { + "path": "text_encoder_2/model-00002-of-00002.safetensors", + "byteSize": 4530066360, + "sha256": "a5640855b301fcdbceddfa90ae8066cd9414aff020552a201a255ecf2059da00" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19885, + "gitBlobId": "c8728bc3dca59d2616a2a594cbac3ddb9eb77d5b" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 588, + "gitBlobId": "cf0682d6de72c1547f41b4f6d7c59f62deffef94" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 705, + "gitBlobId": "180a4e1be2a7d0b38a44108d6f24f585f35147b1" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer_2/tokenizer.json", + "byteSize": 2424235, + "gitBlobId": "21ed409afa3df5a822caa0fd5da30d13941f90b5" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 20817, + "gitBlobId": "b336fa236135e6c87e246485e4d69e415cc57da0" + }, + { + "path": "transformer/config.json", + "byteSize": 393, + "gitBlobId": "9a19ee2be2959dc5e225b4d9c61f3e969dbfefb1" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9985006384, + "sha256": "4cef6b166b2163ba9a01ccb68bf53054556852b69b32c0565dfe0741d28982d7" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9949328904, + "sha256": "cd99fdd45ac404a9c98c939bdc986647cd5160ca48b8e679e81d3d59c2b9a1e0" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3870584832, + "sha256": "ed878852603579083b4a4ea80544504faca9f42ebd41aaa103c3023ee2ebfbd5" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 121262, + "gitBlobId": "21f6c277a5b661e77f9b259c05ca7dad2a29f559" + }, + { + "path": "vae/config.json", + "byteSize": 774, + "gitBlobId": "ed8c04742888d41b78298d360299f40710fa20ae" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 335306212, + "sha256": "8c717328c8ad41faab2ccfd52ae17332505c6833cf176aad56e7b58f2c4d4c94" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.1-Kontext-dev", + "revision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d", + "checkedAt": "2026-09-24T01:43:31.743Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.1-Kontext-dev/revision/24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1656, + "gitBlobId": "83a295b587faf735513748818145139fc594e470" + }, + { + "path": "LICENSE.md", + "byteSize": 18621, + "gitBlobId": "763a44c31c02064a8388981040487ce8731ddc86" + }, + { + "path": "README.md", + "byteSize": 9640, + "gitBlobId": "ea5931c277da1d127bd15105b7b95897579bf7e3" + }, + { + "path": "model_index.json", + "byteSize": 688, + "gitBlobId": "c4d1fd0e61682e02e74e379572b2c12d39bbbe60" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 486, + "gitBlobId": "c9ef07f783510dddfe4c63bec65462f063f5bbed" + }, + { + "path": "text_encoder/config.json", + "byteSize": 561, + "gitBlobId": "60694284dee24af4b721ed1e83b8e9c4755428e8" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 246144352, + "sha256": "893d67a23f4693ed42cdab4cbad7fe3e727cf59609c40da28a46b5470f9ed082" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 741, + "gitBlobId": "438bc3b6f06ad249d8f6796a3926d543f9b374b9" + }, + { + "path": "text_encoder_2/model-00001-of-00002.safetensors", + "byteSize": 4994582224, + "sha256": "ec87bffd1923e8b2774a6d240c922a41f6143081d52cf83b8fe39e9d838c893e" + }, + { + "path": "text_encoder_2/model-00002-of-00002.safetensors", + "byteSize": 4530066360, + "sha256": "a5640855b301fcdbceddfa90ae8066cd9414aff020552a201a255ecf2059da00" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19885, + "gitBlobId": "c8728bc3dca59d2616a2a594cbac3ddb9eb77d5b" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 588, + "gitBlobId": "cf0682d6de72c1547f41b4f6d7c59f62deffef94" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 735, + "gitBlobId": "5ec3aeb286ec1bd51d32a092c5246e1bfeb38958" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer_2/tokenizer.json", + "byteSize": 2424235, + "gitBlobId": "21ed409afa3df5a822caa0fd5da30d13941f90b5" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 20847, + "gitBlobId": "ab25e5b781898f1326d306c8313c0f7562280db4" + }, + { + "path": "transformer/config.json", + "byteSize": 452, + "gitBlobId": "1b80da9fc6735e733607df1f3f48dcf18e250ee6" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9983040304, + "sha256": "5414f9ba3d3945512769b1b5ecd41122c8bcfebb7ec906a3ac60daf371d38946" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9949328904, + "sha256": "a6fa67a12833c30040f794365a187cac8eef2e467152b84319034ae2828d9d03" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3870584832, + "sha256": "464f5343b6b06a2337d8c34b72ebec38219ebbfb7aafecbc9b342dc031417e7a" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 121262, + "gitBlobId": "a7b03767f2395865d98ad50c6e8234f3726af8c0" + }, + { + "path": "vae/config.json", + "byteSize": 914, + "gitBlobId": "4ff62a03d28f5db33183264cf1fca1b90c477135" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.1-Krea-dev", + "revision": "8162a9c7b05a641be098422bf2fcf335615c2f28", + "checkedAt": "2026-09-24T01:43:32.019Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.1-Krea-dev/revision/8162a9c7b05a641be098422bf2fcf335615c2f28", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1655, + "gitBlobId": "147b590320fca0e30b07fce9b0aff6a9a5c3d6da" + }, + { + "path": "LICENSE.md", + "byteSize": 18640, + "gitBlobId": "06f0be47235f4ee51bb22f77352912be2d83852f" + }, + { + "path": "README.md", + "byteSize": 8164, + "gitBlobId": "f11b31f7ad7e33f961da1c924f1cadec835edce0" + }, + { + "path": "model_index.json", + "byteSize": 536, + "gitBlobId": "f2dcd899e03be059304cc980a87bb9389008ba2f" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 273, + "gitBlobId": "a64d1e8763bef5828984682680673fb97829734b" + }, + { + "path": "text_encoder/config.json", + "byteSize": 613, + "gitBlobId": "5ebd923ce51522c605f64ab3d26ea024672777b6" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 246144352, + "sha256": "893d67a23f4693ed42cdab4cbad7fe3e727cf59609c40da28a46b5470f9ed082" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 782, + "gitBlobId": "b00a8a6908682a2091f1194b81f220fd2b67c41c" + }, + { + "path": "text_encoder_2/model-00001-of-00002.safetensors", + "byteSize": 4994582224, + "sha256": "ec87bffd1923e8b2774a6d240c922a41f6143081d52cf83b8fe39e9d838c893e" + }, + { + "path": "text_encoder_2/model-00002-of-00002.safetensors", + "byteSize": 4530066360, + "sha256": "a5640855b301fcdbceddfa90ae8066cd9414aff020552a201a255ecf2059da00" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19885, + "gitBlobId": "c8728bc3dca59d2616a2a594cbac3ddb9eb77d5b" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 588, + "gitBlobId": "cf0682d6de72c1547f41b4f6d7c59f62deffef94" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 705, + "gitBlobId": "180a4e1be2a7d0b38a44108d6f24f585f35147b1" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer_2/tokenizer.json", + "byteSize": 2424235, + "gitBlobId": "21ed409afa3df5a822caa0fd5da30d13941f90b5" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 20817, + "gitBlobId": "b336fa236135e6c87e246485e4d69e415cc57da0" + }, + { + "path": "transformer/config.json", + "byteSize": 394, + "gitBlobId": "4696bfcc5a46d2a117da6afc91c9317e74f5fd39" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9983040304, + "sha256": "1b2170ac37156d4cf91909eb6834bb8adac84bc1fce8098a29cfb03738df84ad" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9949328904, + "sha256": "8bba639227043a606468225c37d7b6956919d38b458b1431d40300854747c127" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3870584832, + "sha256": "ff5df9187e0816f397b42113e563773ec0f41b1345c7235c0d4712f389031b3b" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 121262, + "gitBlobId": "a7b03767f2395865d98ad50c6e8234f3726af8c0" + }, + { + "path": "vae/config.json", + "byteSize": 820, + "gitBlobId": "4f7c628fc8e59fc842c9846ed74414249052ba0b" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.1-Redux-dev", + "revision": "c95859fbf7703ca4d6824b4da4407d7cd0434f81", + "checkedAt": "2026-09-24T01:43:32.282Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.1-Redux-dev/revision/c95859fbf7703ca4d6824b4da4407d7cd0434f81", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "LICENSE.md", + "byteSize": 18621, + "gitBlobId": "763a44c31c02064a8388981040487ce8731ddc86" + }, + { + "path": "README.md", + "byteSize": 4331, + "gitBlobId": "ce17316c99b771fdad278b9731a06b28f4752d71" + }, + { + "path": "feature_extractor/preprocessor_config.json", + "byteSize": 394, + "gitBlobId": "0c32f3fbba3be05b4cb9f590c2a9e5e38f58b0ea" + }, + { + "path": "image_embedder/config.json", + "byteSize": 128, + "gitBlobId": "c359a22fd0b0167b1c2b6ae4649ec1c7961d6eb5" + }, + { + "path": "image_embedder/diffusion_pytorch_model.safetensors", + "byteSize": 129008000, + "sha256": "02ace6d3b9dc6fa1ab77e6863151430a3ff128f0d0e378021ab9bcb7f2ed18f0" + }, + { + "path": "image_encoder/config.json", + "byteSize": 419, + "gitBlobId": "8a1494bf351298b03342899604308c737ebb5e99" + }, + { + "path": "image_encoder/model.safetensors", + "byteSize": 856506120, + "sha256": "d769e3a32a6a9bac72d4d93b989e44491f71b50f02bfa14cd9187758d4a68ff1" + }, + { + "path": "model_index.json", + "byteSize": 295, + "gitBlobId": "e12fe6cf8986c4265d5d9b3bac14efcd65848fcd" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.1-schnell", + "revision": "741f7c3ce8b383c54771c7003378a50191e9efe9", + "checkedAt": "2026-09-24T01:43:32.845Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.1-schnell/revision/741f7c3ce8b383c54771c7003378a50191e9efe9", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1668, + "gitBlobId": "6741893559c830018b078fafe2540de58214d287" + }, + { + "path": "README.md", + "byteSize": 3681, + "gitBlobId": "d02a8ab09a5e82933d8d59e499169c8ae26f128e" + }, + { + "path": "model_index.json", + "byteSize": 536, + "gitBlobId": "f2dcd899e03be059304cc980a87bb9389008ba2f" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 274, + "gitBlobId": "0bdb1b0ee10b0b31745d2654369613d432dc297a" + }, + { + "path": "text_encoder/config.json", + "byteSize": 613, + "gitBlobId": "5ebd923ce51522c605f64ab3d26ea024672777b6" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 246144352, + "sha256": "893d67a23f4693ed42cdab4cbad7fe3e727cf59609c40da28a46b5470f9ed082" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 782, + "gitBlobId": "b00a8a6908682a2091f1194b81f220fd2b67c41c" + }, + { + "path": "text_encoder_2/model-00001-of-00002.safetensors", + "byteSize": 4994582224, + "sha256": "ec87bffd1923e8b2774a6d240c922a41f6143081d52cf83b8fe39e9d838c893e" + }, + { + "path": "text_encoder_2/model-00002-of-00002.safetensors", + "byteSize": 4530066360, + "sha256": "a5640855b301fcdbceddfa90ae8066cd9414aff020552a201a255ecf2059da00" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19885, + "gitBlobId": "c8728bc3dca59d2616a2a594cbac3ddb9eb77d5b" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 588, + "gitBlobId": "cf0682d6de72c1547f41b4f6d7c59f62deffef94" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 705, + "gitBlobId": "180a4e1be2a7d0b38a44108d6f24f585f35147b1" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer_2/tokenizer.json", + "byteSize": 2424235, + "gitBlobId": "21ed409afa3df5a822caa0fd5da30d13941f90b5" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 20817, + "gitBlobId": "b336fa236135e6c87e246485e4d69e415cc57da0" + }, + { + "path": "transformer/config.json", + "byteSize": 321, + "gitBlobId": "67e7b3526946734ed5f9014dc8fc2557714cce3e" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9962580296, + "sha256": "9b633dbe87316385c5b1c262bd4b5a01e3d955170661d63dcec8a01e89c0d820" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9949328904, + "sha256": "58b4434078f0c2567ddc54e3b5cbf39626ab55fbd9d5c22956e183668f535dec" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3870584832, + "sha256": "e2cbc25471ed5186e69a9b51098300cb2f612556453e38a372c851a220ed238d" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 120822, + "gitBlobId": "bf1b236a5e90a3e5ffc62c916914d3696ad276e7" + }, + { + "path": "vae/config.json", + "byteSize": 774, + "gitBlobId": "b43183d0f5f0274bccd8054cd0069fc1d5f64586" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.2-dev", + "revision": "26afe3a78bb242c0a8bb181dcc8937bb16e5c66c", + "checkedAt": "2026-09-24T01:43:33.104Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.2-dev/revision/26afe3a78bb242c0a8bb181dcc8937bb16e5c66c", + "gated": "auto", + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1867, + "gitBlobId": "e4fa7b53139a2e99baf375ea9456fbff22fe1a7a" + }, + { + "path": "LICENSE.md", + "byteSize": 18158, + "gitBlobId": "f6eb422412c4d98986fa3671bb46363894f099bb" + }, + { + "path": "README.md", + "byteSize": 9439, + "gitBlobId": "b03747b3ec5bc9d0beb4d63908b86903c1893fb8" + }, + { + "path": "model_index.json", + "byteSize": 431, + "gitBlobId": "0ef3934d54cbd07f6b97d09899bb600d6ff66797" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 486, + "gitBlobId": "17c0b5419f9c1923dddf7b28e2664313fffce40a" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1216, + "gitBlobId": "3417a053519f185fb20eeb2dafe5b8fa65177e0d" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 155, + "gitBlobId": "e7cbec0c3d02641108f32fb5ed557c660b2bc3b5" + }, + { + "path": "text_encoder/model-00001-of-00010.safetensors", + "byteSize": 4883550696, + "sha256": "91831c2ce219df0ce63bc33c6249e5cb01db8d93816bcebf975f1c406286520e" + }, + { + "path": "text_encoder/model-00002-of-00010.safetensors", + "byteSize": 4781593336, + "sha256": "8ffe80706a66b2f5ef1fb058806ccf09f124ec4ad38af7a377e44ab1ee2fd664" + }, + { + "path": "text_encoder/model-00003-of-00010.safetensors", + "byteSize": 4886472224, + "sha256": "99ec66e891f9563f568734eadfc5b7701e04620e8e163d4d5755277a3b50cf2f" + }, + { + "path": "text_encoder/model-00004-of-00010.safetensors", + "byteSize": 4781593376, + "sha256": "e1df1527b12b1eb5cbd9a50914f9e6eb24e885ec830a3c16b5eed6ad0b53a396" + }, + { + "path": "text_encoder/model-00005-of-00010.safetensors", + "byteSize": 4781593368, + "sha256": "3556ac03f47c24eb8ad27c237e25baad639c651d9596fd72cb1523137bf56163" + }, + { + "path": "text_encoder/model-00006-of-00010.safetensors", + "byteSize": 4886472248, + "sha256": "2c41e6f80f2b5ca384ce703eac048a13daf2aff689c3acca66a8943f45338aae" + }, + { + "path": "text_encoder/model-00007-of-00010.safetensors", + "byteSize": 4781593376, + "sha256": "62a725f154f6ba942a36b5cc450db2b2df32f434e3224558c789bc04fa05fd36" + }, + { + "path": "text_encoder/model-00008-of-00010.safetensors", + "byteSize": 4781593368, + "sha256": "3a1a6ac77e6434418bb7273b68a7b3534fed5217c990061c92a8f990dd6ab20e" + }, + { + "path": "text_encoder/model-00009-of-00010.safetensors", + "byteSize": 4886472248, + "sha256": "e1fffc9bb2b77d4d2382c1bd9053e9d017741d67ca00cc6f77034a294f2f5cfd" + }, + { + "path": "text_encoder/model-00010-of-00010.safetensors", + "byteSize": 4571866320, + "sha256": "116ef7ae6fa0fd46b478324e4aa6a49f448afed900ca9f71d4fbd3d02289bbd4" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 57944, + "gitBlobId": "04c982e93122e3a5e9cfda533b8eebda1d0f398d" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 2670, + "gitBlobId": "ec73d954fdf40966f905d25f3d3f935763d4c0fd" + }, + { + "path": "tokenizer/preprocessor_config.json", + "byteSize": 700, + "gitBlobId": "6a1437bed5ae4f04eb0062aef0731dd1620909fe" + }, + { + "path": "tokenizer/processor_config.json", + "byteSize": 189, + "gitBlobId": "0092b0f4fb04cc78378859f013badda71e5262c5" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 21449, + "gitBlobId": "a47054b449916f808ab94e6d2375c00ead78cb8f" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 17078037, + "sha256": "b76085f9923309d873994d444989f7eb6ec074b06f25b58f1e8d7b7741070949" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 198120, + "gitBlobId": "c61ab81def9a66ae3c0789385d9587904ca2d840" + }, + { + "path": "transformer/config.json", + "byteSize": 440, + "gitBlobId": "8b8fc5c765c24fa6601574c9e5a3730cddd26a7d" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00007.safetensors", + "byteSize": 9935797200, + "sha256": "9d9b85f75f72fb17c7d29dacf7c430e924da93122d578a559a36a7635e153714" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00007.safetensors", + "byteSize": 9890181048, + "sha256": "86adf6f41474b00bd57afbb29a09f008be7d6af8ae914956585ba5bc6bf97c28" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00007.safetensors", + "byteSize": 9814681480, + "sha256": "a14e26e8f305dd26d7881f333e6e6ce5b562cbb55282538f46d38e1ff2715179" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00007.safetensors", + "byteSize": 9814681536, + "sha256": "5c4f38976fd8d7e5fb2d4cd20562d74eebba3264566987e3ef938d807c75be90" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00007.safetensors", + "byteSize": 9814681536, + "sha256": "4d7a74d916fc22117cde8bad76aa4b561e6dc92368cddc23bdae06dbc586ad95" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00007.safetensors", + "byteSize": 9814681536, + "sha256": "08f3ad03610651f9d630177ac3a4770d532fa72d788d3b36c39a0301b1595447" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00007.safetensors", + "byteSize": 5361898792, + "sha256": "789b9bacb607e9b97597f77c86056fa6cbb747c2a6016588e6e196814b5f9733" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 35785, + "gitBlobId": "26fe19e847c8947f675b2feea92a021a5d8ed8ac" + }, + { + "path": "vae/config.json", + "byteSize": 770, + "gitBlobId": "183bd5fa89fc62dabcd40abfad5fca243407d6e5" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 336213556, + "sha256": "d64f3a68e1cc4f9f4e29b6e0da38a0204fe9a49f2d4053f0ec1fa1ca02f9c4b5" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.2-klein-4B", + "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294", + "checkedAt": "2026-09-24T01:43:33.368Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.2-klein-4B/revision/e7b7dc27f91deacad38e78976d1f2b499d76a294", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "f1ca65ce6a5b0774360fde1ec05157d9f937aaa2" + }, + { + "path": "LICENSE.md", + "byteSize": 9584, + "gitBlobId": "0afa7632c1a195ea6627a3c53e6ebf18d1be3aa5" + }, + { + "path": "README.md", + "byteSize": 9991, + "gitBlobId": "7b3cca65056e19a6ef27bdd0be671a030f067942" + }, + { + "path": "model_index.json", + "byteSize": 446, + "gitBlobId": "554a39bf1019138dd0b28809b745df8ef33657c8" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 486, + "gitBlobId": "7e53605e2643440b310fa554f087641893883c5f" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1536, + "gitBlobId": "86f951d170cfd1fdc1c8a57a041e05c27b7b789b" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 214, + "gitBlobId": "f5af93d0c7764fa38e3b1cd3aaba1b6307246d3b" + }, + { + "path": "text_encoder/model-00001-of-00002.safetensors", + "byteSize": 4967215360, + "sha256": "8c0506e7f4936fa7e26183a4fd8da4e2bdbc5990ba64ae441f965d51228f36ea" + }, + { + "path": "text_encoder/model-00002-of-00002.safetensors", + "byteSize": 3077766632, + "sha256": "82f2bd839378541b0557bfabaf37c7d3d637071fdcb73302dedd7cf61162ce07" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 32855, + "gitBlobId": "b65d8063815c8679d85bb919337b36fae8de75cf" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 4168, + "gitBlobId": "01be9b307daa2d425f7c168c9fb145a286e0afb4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 5404, + "gitBlobId": "ddaf69808214a44fdd26d3785b66c1367c78277a" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 541, + "gitBlobId": "f21592ae2bc9f43052e78785bde7414f219ae369" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 7751109744, + "sha256": "9f29f9edcfdae452a653ffb51a534ca4decd389952c225724ff3b94042612a6e" + }, + { + "path": "vae/config.json", + "byteSize": 821, + "gitBlobId": "c3f38eb4c188a96a462519159356bd0fcc28cd14" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 168120878, + "sha256": "ca70d2202afe6415bdbcb8793ba8cd99fd159cfe6192381504d6c4d3036e0f04" + } + ] + }, + { + "repo": "black-forest-labs/FLUX.2-klein-base-4B", + "revision": "a3b4f4849157f664bdbc776fd7453c2783562f4d", + "checkedAt": "2026-09-24T01:43:33.903Z", + "source": "https://huggingface.co/api/models/black-forest-labs/FLUX.2-klein-base-4B/revision/a3b4f4849157f664bdbc776fd7453c2783562f4d", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "f1ca65ce6a5b0774360fde1ec05157d9f937aaa2" + }, + { + "path": "LICENSE.md", + "byteSize": 9584, + "gitBlobId": "0afa7632c1a195ea6627a3c53e6ebf18d1be3aa5" + }, + { + "path": "README.md", + "byteSize": 10084, + "gitBlobId": "8baff230bb5629f64f0dccf61c3f17022dd419fc" + }, + { + "path": "model_index.json", + "byteSize": 422, + "gitBlobId": "8bd1147173cb71f16e20819cbb8b7955695f5a16" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 486, + "gitBlobId": "7e53605e2643440b310fa554f087641893883c5f" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1536, + "gitBlobId": "86f951d170cfd1fdc1c8a57a041e05c27b7b789b" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 214, + "gitBlobId": "f5af93d0c7764fa38e3b1cd3aaba1b6307246d3b" + }, + { + "path": "text_encoder/model-00001-of-00002.safetensors", + "byteSize": 4967215360, + "sha256": "8c0506e7f4936fa7e26183a4fd8da4e2bdbc5990ba64ae441f965d51228f36ea" + }, + { + "path": "text_encoder/model-00002-of-00002.safetensors", + "byteSize": 3077766632, + "sha256": "82f2bd839378541b0557bfabaf37c7d3d637071fdcb73302dedd7cf61162ce07" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 32855, + "gitBlobId": "b65d8063815c8679d85bb919337b36fae8de75cf" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 4168, + "gitBlobId": "01be9b307daa2d425f7c168c9fb145a286e0afb4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 5404, + "gitBlobId": "ddaf69808214a44fdd26d3785b66c1367c78277a" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 531, + "gitBlobId": "bc01657cf4dab7af77161271db7bc2f2098fe804" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 7751109744, + "sha256": "e109674697ffa1a3983126e32512f5428a9442bd8df59f9c95566ee90a473bb6" + }, + { + "path": "vae/config.json", + "byteSize": 821, + "gitBlobId": "c3f38eb4c188a96a462519159356bd0fcc28cd14" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 168120878, + "sha256": "ca70d2202afe6415bdbcb8793ba8cd99fd159cfe6192381504d6c4d3036e0f04" + } + ] + }, + { + "repo": "carlofkl/DreamLite-base", + "revision": "751cb8dbb9072a8c8ffd8684e0f254b50f20531b", + "checkedAt": "2026-09-24T01:43:34.168Z", + "source": "https://huggingface.co/api/models/carlofkl/DreamLite-base/revision/751cb8dbb9072a8c8ffd8684e0f254b50f20531b", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1641, + "gitBlobId": "a82744f53a7fb2bd90ab1667ef5dccdfae0094d2" + }, + { + "path": "README.md", + "byteSize": 578, + "gitBlobId": "10f524b0672b6103900ae8b90086c7559285fce1" + }, + { + "path": "model_index.json", + "byteSize": 480, + "gitBlobId": "635798e6cc85105b792e9b2c7bca9303c27ca000" + }, + { + "path": "processor/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "processor/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "processor/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 782, + "gitBlobId": "2fa6553554f69b4e3a6b742a75264ff9dff90ad0" + }, + { + "path": "processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 5445, + "gitBlobId": "fec7f182135c384a08ea2fc9635566ca79c45afc" + }, + { + "path": "processor/video_preprocessor_config.json", + "byteSize": 817, + "gitBlobId": "e32b1d90356f74457edbfca27aa1e121dab7cdc7" + }, + { + "path": "processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 481, + "gitBlobId": "8ffe55493d9aab2b2a200c65879b00e062ff0531" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1546, + "gitBlobId": "454cc4936468d57e3c36067d8afe9f39de2e5f6c" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 213, + "gitBlobId": "3dd2c545b96b4d51586680d7bb27fdca5c90dfe9" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 4255140312, + "sha256": "7de1838c87a5349b016c26a1c3f7d2bc400a3d485f95ef39a7059ffd734977a0" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 5404, + "gitBlobId": "1d4fba2de0e94e8f97e08927cfb7d084160c2880" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "unet/config.json", + "byteSize": 1983, + "gitBlobId": "327570f28f0bdac8d9e3591717bcb243bfafd9fe" + }, + { + "path": "unet/diffusion_pytorch_model.safetensors", + "byteSize": 780074688, + "sha256": "f983bd1710344cb44f7d12d05c0ca961984b48391dcbb095ad710609cbde3246" + }, + { + "path": "vae/config.json", + "byteSize": 634, + "gitBlobId": "e8d284adced0c0d59bad19fadf2fccdb9784878f" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 4903270, + "sha256": "e175c3284b8514a2ecb1a70f004b0b09320decd6842e77dfebbdb5dbbdf0312b" + } + ] + }, + { + "repo": "carlofkl/DreamLite-mobile", + "revision": "6695c3f4be230f0493fa5dbf78be3bc4d3bb2ab4", + "checkedAt": "2026-09-24T01:43:34.475Z", + "source": "https://huggingface.co/api/models/carlofkl/DreamLite-mobile/revision/6695c3f4be230f0493fa5dbf78be3bc4d3bb2ab4", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1641, + "gitBlobId": "a82744f53a7fb2bd90ab1667ef5dccdfae0094d2" + }, + { + "path": "README.md", + "byteSize": 694, + "gitBlobId": "23098a7aab96909b7a85b53d5dcea5275af9c76f" + }, + { + "path": "model_index.json", + "byteSize": 486, + "gitBlobId": "d0e0e8083620d30b92245955574d877aaad1f390" + }, + { + "path": "processor/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "processor/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "processor/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 782, + "gitBlobId": "2fa6553554f69b4e3a6b742a75264ff9dff90ad0" + }, + { + "path": "processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 5445, + "gitBlobId": "fec7f182135c384a08ea2fc9635566ca79c45afc" + }, + { + "path": "processor/video_preprocessor_config.json", + "byteSize": 817, + "gitBlobId": "e32b1d90356f74457edbfca27aa1e121dab7cdc7" + }, + { + "path": "processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 481, + "gitBlobId": "8ffe55493d9aab2b2a200c65879b00e062ff0531" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1546, + "gitBlobId": "454cc4936468d57e3c36067d8afe9f39de2e5f6c" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 213, + "gitBlobId": "3dd2c545b96b4d51586680d7bb27fdca5c90dfe9" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 4255140312, + "sha256": "7de1838c87a5349b016c26a1c3f7d2bc400a3d485f95ef39a7059ffd734977a0" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 5404, + "gitBlobId": "1d4fba2de0e94e8f97e08927cfb7d084160c2880" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "unet/config.json", + "byteSize": 1983, + "gitBlobId": "327570f28f0bdac8d9e3591717bcb243bfafd9fe" + }, + { + "path": "unet/diffusion_pytorch_model.safetensors", + "byteSize": 780074688, + "sha256": "d3b46c229731e4c0c967176fdead58dd24bd3c29132c7f1c2204442cfeb99dc7" + }, + { + "path": "vae/config.json", + "byteSize": 634, + "gitBlobId": "e8d284adced0c0d59bad19fadf2fccdb9784878f" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 4903270, + "sha256": "e175c3284b8514a2ecb1a70f004b0b09320decd6842e77dfebbdb5dbbdf0312b" + } + ] + }, + { + "repo": "circlestone-labs/Anima-Base-v1.0-Diffusers", + "revision": "073c3a9db359c31ad0e8aa268d15775473c2176c", + "checkedAt": "2026-09-24T01:43:34.849Z", + "source": "https://huggingface.co/api/models/circlestone-labs/Anima-Base-v1.0-Diffusers/revision/073c3a9db359c31ad0e8aa268d15775473c2176c", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": "LICENSE.md", + "byteSize": 17688, + "gitBlobId": "a95e0edd5c6408cdd1b0e33431fa549316a0d9c7" + }, + { + "path": "modular_model_index.json", + "byteSize": 2414, + "gitBlobId": "aa22f66f17057f36b8334818bb049d8c33ae1ab1" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 487, + "gitBlobId": "a5ec9f6489fbba607a3867da81e82dec7bc5542d" + }, + { + "path": "t5_tokenizer/tokenizer.json", + "byteSize": 2424069, + "gitBlobId": "7093f0b3e6e8c7cc8a69b6b72d1cf2824803ae88" + }, + { + "path": "t5_tokenizer/tokenizer_config.json", + "byteSize": 2439, + "gitBlobId": "1409b32976dc4a4f29d1e59f5262e7816bdb9030" + }, + { + "path": "text_conditioner/config.json", + "byteSize": 333, + "gitBlobId": "216cee4bca7d12dd48c04750f905c1023fd63422" + }, + { + "path": "text_conditioner/diffusion_pytorch_model.safetensors", + "byteSize": 269339400, + "sha256": "fdcf606524892e35b270b96fc8bbd8d77e615ab882675a049f6e7c2ff6b71da3" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1409, + "gitBlobId": "aac1c77fe596236319b7efdb737c757a4b597bb1" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 1192133232, + "sha256": "75e8db94adb374b61baca3d898fc2c0d5086d20689a96d4e74576546433a0526" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 2427, + "gitBlobId": "28028c056af412405debd878cdda0171e35fa5d1" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422924, + "sha256": "476870a1f2fb6f6a2759a6ede2383bf9d5d738f17844563b65c91965b722ae09" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 421, + "gitBlobId": "69185ab21fab1d98c080feb37c52401938b06ee5" + }, + { + "path": "transformer/config.json", + "byteSize": 728, + "gitBlobId": "5d30c4a788edbc4c25e8d723d61c43766d58c54c" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 3912877104, + "sha256": "81dc5915fc3c3b170684534ca11ed6c535ddea5658793a801fac53b8d5281065" + }, + { + "path": "vae/config.json", + "byteSize": 753, + "gitBlobId": "45edcb2d93c95e4cd918843a5e5f15c1dd6210e9" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "0c8bc8b758c649abef9ea407b95408389a3b2f610d0d10fcb054fe171d0a8344" + } + ] + }, + { + "repo": "deepseek-community/Janus-Pro-1B", + "revision": "1655280bb75959cc1cb85529a2a8b26e7016072e", + "checkedAt": "2026-09-24T01:43:35.244Z", + "source": "https://huggingface.co/api/models/deepseek-community/Janus-Pro-1B/revision/1655280bb75959cc1cb85529a2a8b26e7016072e", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1577, + "gitBlobId": "5a0ba693dd8f366d5e1badf3657d953235798bde" + }, + { + "path": "README.md", + "byteSize": 4971, + "gitBlobId": "0486aedab1f3e771f0de8dd7cc3925257947041a" + }, + { + "path": "chat_template.jinja", + "byteSize": 1027, + "gitBlobId": "e6d74d2fabf22385370e4d0ca3b64dffc22ad90f" + }, + { + "path": "config.json", + "byteSize": 1939, + "gitBlobId": "13427ca7501e8bafadbc8a0464d627e6fa7b8494" + }, + { + "path": "generation_config.json", + "byteSize": 233, + "gitBlobId": "d18b65ce90ab7e77862e1e09149bd6f31a0c1e63" + }, + { + "path": "model.safetensors", + "byteSize": 4153396574, + "sha256": "9d1a416f95fb58d6e02858623c9c676003d66006d51fb5d5cc93348ba78cb942" + }, + { + "path": "preprocessor_config.json", + "byteSize": 465, + "gitBlobId": "c4eeec0bb407f6a2c64fc0046cb51fe8f7048a58" + }, + { + "path": "processor_config.json", + "byteSize": 79, + "gitBlobId": "3f4c1a20855400f93d774ea21f59e32d53ea9c8e" + }, + { + "path": "special_tokens_map.json", + "byteSize": 792, + "gitBlobId": "d8c775ae48c54085e36e40c1a22c72691d659c7f" + }, + { + "path": "tokenizer.json", + "byteSize": 7611780, + "gitBlobId": "9319e013fb14be377c1c969fd9719ab0d9e3b2c7" + }, + { + "path": "tokenizer_config.json", + "byteSize": 105922, + "gitBlobId": "4b168f9c5737e19957656e1ec95c4a8e78dc35fb" + } + ] + }, + { + "repo": "depth-anything/Depth-Anything-V2-Metric-Outdoor-Small-hf", + "revision": "fd2c22027eaf20374204f14099b8341e1925ad39", + "checkedAt": "2026-09-24T01:43:35.518Z", + "source": "https://huggingface.co/api/models/depth-anything/Depth-Anything-V2-Metric-Outdoor-Small-hf/revision/fd2c22027eaf20374204f14099b8341e1925ad39", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": "config.json", + "byteSize": 1063, + "gitBlobId": "d3569a37f098a26fb76fd33ae6ba23e33bea1a13" + }, + { + "path": "model.safetensors", + "byteSize": 99173660, + "sha256": "ad065c77a7421ca55159a1f0db9433397a607690f2d76bb8a6fc54b1be7a3124" + }, + { + "path": "preprocessor_config.json", + "byteSize": 437, + "gitBlobId": "4b48811870a9d567ee23549c08249fe943e57916" + } + ] + }, + { + "repo": "depth-anything/Depth-Anything-V2-Small-hf", + "revision": "5426e4f0f36572d16453bbda7a8389317b1bef99", + "checkedAt": "2026-09-24T01:43:35.863Z", + "source": "https://huggingface.co/api/models/depth-anything/Depth-Anything-V2-Small-hf/revision/5426e4f0f36572d16453bbda7a8389317b1bef99", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": "config.json", + "byteSize": 950, + "gitBlobId": "9207d3a304e86517dab21638c559d8b3195d41b6" + }, + { + "path": "model.safetensors", + "byteSize": 99173660, + "sha256": "3152477ce0d8d6978d76b995120de97cb5b928701fd0f817769f59e249a16b70" + }, + { + "path": "preprocessor_config.json", + "byteSize": 775, + "gitBlobId": "a7822a90a0e91a14d50d1996953bac8f389a4377" + } + ] + }, + { + "repo": "diffusers/sdxl-instructpix2pix-768", + "revision": "06653d47f8d22f2c2205a5884d6a24c5e76d2ca7", + "checkedAt": "2026-09-24T01:43:36.126Z", + "source": "https://huggingface.co/api/models/diffusers/sdxl-instructpix2pix-768/revision/06653d47f8d22f2c2205a5884d6a24c5e76d2ca7", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "README.md", + "byteSize": 3224, + "gitBlobId": "26a106b8d9c68ba57ef7de756e7b4d8f51c2e6b3" + }, + { + "path": "model_index.json", + "byteSize": 660, + "gitBlobId": "08c32476985961e3238328d2b086163c1ba7861d" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 479, + "gitBlobId": "9e13de2afd2250977d01837deed2b1c35b75e552" + }, + { + "path": "text_encoder/config.json", + "byteSize": 623, + "gitBlobId": "c84ead7eb2dccaa2973e8f856fb471d991aad2df" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 246144152, + "sha256": "660c6f5b1abae9dc498ac2d21e1347d2abdb0cf6c0c0c8576cd796491d9a6cdd" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 633, + "gitBlobId": "4bdf27ccc712663f5d781e6cd2131288dbd83728" + }, + { + "path": "text_encoder_2/model.safetensors", + "byteSize": 1389382176, + "sha256": "ec310df2af79c318e24d20511b601a591ca8cd4f1fce1d8dff822a356bcdb1f4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 472, + "gitBlobId": "2c2130b544c0c5a72d5d00da071ba130a9800fb2" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 737, + "gitBlobId": "2e8612a429492973fe60635b3f44a28b065cfac0" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 460, + "gitBlobId": "ae0c5be6f35217e51c4c000fd325d8de0294e99c" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 725, + "gitBlobId": "a8438e020c4497a429240d6b89e0bf9a6e2ffa92" + }, + { + "path": "tokenizer_2/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "unet/config.json", + "byteSize": 1774, + "gitBlobId": "cfd4b261afdaa447005958191f2c28368a3c69f6" + }, + { + "path": "unet/diffusion_pytorch_model.safetensors", + "byteSize": 10270123816, + "sha256": "01d538927edfdf63511a38840b9d0cd8d8dcd4e40001566b83c9104ef1a2f92f" + }, + { + "path": "vae/config.json", + "byteSize": 659, + "gitBlobId": "4a3bb2e796869bdfa617fd28162f4087c4f771e3" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 334643268, + "sha256": "98a14dc6fe8d71c83576f135a87c61a16561c9c080abba418d2cc976ee034f88" + } + ] + }, + { + "repo": "Efficient-Large-Model/Sana_600M_1024px_diffusers", + "revision": "28f3af7689de15f3883d5863059a2fca0aa9b829", + "checkedAt": "2026-09-24T01:43:26.413Z", + "source": "https://huggingface.co/api/models/Efficient-Large-Model/Sana_600M_1024px_diffusers/revision/28f3af7689de15f3883d5863059a2fca0aa9b829", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "f1ca65ce6a5b0774360fde1ec05157d9f937aaa2" + }, + { + "path": "LICENSE", + "byteSize": 11358, + "gitBlobId": "d645695673349e3947e8e5ae42332d0ac3164cd7" + }, + { + "path": "README.md", + "byteSize": 6618, + "gitBlobId": "06ce76f37a8210b7d861943ee8dec8b2c64714bf" + }, + { + "path": "model_index.json", + "byteSize": 401, + "gitBlobId": "c0a7688b3b51e22a1a8947b34381c36608a24a56" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 840, + "gitBlobId": "9c925411315a73f94833297a9a40c2fbb1077354" + }, + { + "path": "text_encoder/config.json", + "byteSize": 875, + "gitBlobId": "3df88b786ae2b1b45e6d075528f74e9f05701e86" + }, + { + "path": "text_encoder/model.fp16-00001-of-00002.safetensors", + "byteSize": 4988024144, + "sha256": "e909230aabafad02d097c7dc02f2ae062b4e6b0593477c1f07679d277e09ce71" + }, + { + "path": "text_encoder/model.fp16-00002-of-00002.safetensors", + "byteSize": 240691624, + "sha256": "48e14f6b7a917e54cabc4ff2047ac2bfe0dae663e771eed3a8acf4ea27dcac18" + }, + { + "path": "text_encoder/model.safetensors.index.fp16.json", + "byteSize": 23935, + "gitBlobId": "a2b0861138abe18adc07163edacbe0e6d62f1144" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 636, + "gitBlobId": "8d6368f7e735fbe4781bf6e956b7c6ad0586df80" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 34362873, + "sha256": "5f7eee611703c5ce5d1eee32d9cdcfe465647b8aff0c1dfb3bed7ad7dbb05060" + }, + { + "path": "tokenizer/tokenizer.model", + "byteSize": 4241003, + "sha256": "61a7b147390c64585d6c3543dd6fc636906c9af3865a5548f27f31aee1d4c8e2" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 46996, + "gitBlobId": "36ba12877e7efaba5252f7b17ab337a59d953563" + }, + { + "path": "transformer/config.json", + "byteSize": 495, + "gitBlobId": "cae015925cba922e1bea2c9e1378a04c9f4c0a3b" + }, + { + "path": "transformer/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 1183558264, + "sha256": "712b69e1e14ac7ece99e35b257927c4e0cf8ba2c24eb49574301ff0b10069298" + }, + { + "path": "vae/config.json", + "byteSize": 1280, + "gitBlobId": "7b2373f087fb618cf228a3ac4367de81e3571b32" + }, + { + "path": "vae/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 1249044836, + "sha256": "15a4b09e56d95b768a0ec9da50b702e21d920333fc9b3480d66bb5c7fad9d87f" + } + ] + }, + { + "repo": "Efficient-Large-Model/Sana_Sprint_0.6B_1024px_diffusers", + "revision": "aa76e7f4f4928f378716b6716a2130fba3caf5b1", + "checkedAt": "2026-09-24T01:43:26.696Z", + "source": "https://huggingface.co/api/models/Efficient-Large-Model/Sana_Sprint_0.6B_1024px_diffusers/revision/aa76e7f4f4928f378716b6716a2130fba3caf5b1", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "f1ca65ce6a5b0774360fde1ec05157d9f937aaa2" + }, + { + "path": "LICENSE", + "byteSize": 11358, + "gitBlobId": "d645695673349e3947e8e5ae42332d0ac3164cd7" + }, + { + "path": "README.md", + "byteSize": 6543, + "gitBlobId": "ca75553ce2d02bed329c94e8eeaf36634e70dba8" + }, + { + "path": "model_index.json", + "byteSize": 392, + "gitBlobId": "795d41a9eaf524bb0d84fbac6a325609d4f737d1" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 160, + "gitBlobId": "6d3081b117d2ea1c4db209edf5914a80c9ed754e" + }, + { + "path": "text_encoder/config.json", + "byteSize": 890, + "gitBlobId": "b9834570dc12b608f336a00e3f81658d17a85b6c" + }, + { + "path": "text_encoder/model-00001-of-00002.safetensors", + "byteSize": 4988024144, + "sha256": "e909230aabafad02d097c7dc02f2ae062b4e6b0593477c1f07679d277e09ce71" + }, + { + "path": "text_encoder/model-00002-of-00002.safetensors", + "byteSize": 240691624, + "sha256": "48e14f6b7a917e54cabc4ff2047ac2bfe0dae663e771eed3a8acf4ea27dcac18" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 22495, + "gitBlobId": "36d550130eb056ee9c28e438c0e3d1b28fddab84" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 636, + "gitBlobId": "8d6368f7e735fbe4781bf6e956b7c6ad0586df80" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 34362873, + "sha256": "5f7eee611703c5ce5d1eee32d9cdcfe465647b8aff0c1dfb3bed7ad7dbb05060" + }, + { + "path": "tokenizer/tokenizer.model", + "byteSize": 4241003, + "sha256": "61a7b147390c64585d6c3543dd6fc636906c9af3865a5548f27f31aee1d4c8e2" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 46996, + "gitBlobId": "36ba12877e7efaba5252f7b17ab337a59d953563" + }, + { + "path": "transformer/config.json", + "byteSize": 648, + "gitBlobId": "e0d55e9165c1992b9c98e16afeb397548e705f57" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 1187078584, + "sha256": "9069dd697395b96a59d1f74837461f7aeb3d95e55b67fa85c9f31c9106b65e87" + }, + { + "path": "vae/config.json", + "byteSize": 1280, + "gitBlobId": "e265030c18bc640fa24267beb2a92c3a187cba5b" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 1249044836, + "sha256": "dfd991d1b54ffabf22745c5885589d8f2a7bc59930d95d92bd741c4fc64454bb" + } + ] + }, + { + "repo": "fal/AuraFlow-v0.3", + "revision": "2cd8588f04c886002be4571697d84654a50e3af3", + "checkedAt": "2026-09-24T01:43:36.426Z", + "source": "https://huggingface.co/api/models/fal/AuraFlow-v0.3/revision/2cd8588f04c886002be4571697d84654a50e3af3", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1641, + "gitBlobId": "3a4590d6b2b677094180ce837252f351646a3df9" + }, + { + "path": "LICENSE", + "byteSize": 11356, + "gitBlobId": "f49a4e16e68b128803cc2dcea614603632b04eac" + }, + { + "path": "README.md", + "byteSize": 2054, + "gitBlobId": "4926c3c6e0541a8c35239e764624c396fd385f5d" + }, + { + "path": "model_index.json", + "byteSize": 458, + "gitBlobId": "dcdb7196dc6dae66f3a45a62f313f10348cda373" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 142, + "gitBlobId": "fb855160b4845688f76cfff1b14d667bffe528c3" + }, + { + "path": "text_encoder/config.json", + "byteSize": 949, + "gitBlobId": "a2971ad9360977a528cc599fc86138eccaa4f679" + }, + { + "path": "text_encoder/model.fp16.safetensors", + "byteSize": 2950448704, + "sha256": "decf9b70814ed5e9965bfca9fbd0483462e2bf743790663025b7742f8c014c72" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 2593, + "gitBlobId": "3f5132007c4fcf42b75b65c8b6aa49c7098bcdf4" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 2677, + "gitBlobId": "ef60a48ee24429d5f6c70e311589c09a1e342652" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 1861548, + "gitBlobId": "00253fd5bd02bd84a4b4bc1c9feb384dddee43a1" + }, + { + "path": "tokenizer/tokenizer.model", + "byteSize": 499723, + "sha256": "9e556afd44213b6bd1be2b850ebbbd98f5481437a8021afaf58ee7fb1818d347" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 20978, + "gitBlobId": "50efc357c41e081367478da156e8d10574bc8279" + }, + { + "path": "transformer/config.json", + "byteSize": 379, + "gitBlobId": "20cb6f34612dc6f10a0acb6e71a8aa86f0ffd849" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00002.fp16.safetensors", + "byteSize": 9942269216, + "sha256": "6a40b011f287452dbca80face78e667055904c5ad97eb2097ade3200259b2203" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00002.fp16.safetensors", + "byteSize": 3774983112, + "sha256": "e46f7a419b9531c293398d8ac012e10fe99f98228bdd71ce0b7dbb51688a36c0" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.fp16.index.json", + "byteSize": 37663, + "gitBlobId": "37de139aed32578839fa39e152954352bc07dcf5" + }, + { + "path": "vae/config.json", + "byteSize": 859, + "gitBlobId": "3e479db185c6197ec35fdf15f3c4031e3eaf5be0" + }, + { + "path": "vae/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 167335342, + "sha256": "bcb60880a46b63dea58e9bc591abe15f8350bde47b405f9c38f4be70c6161e68" + } + ] + }, + { + "repo": "google/ddpm-cifar10-32", + "revision": "267b167dc01f0e4e61923ea244e8b988f84deb80", + "checkedAt": "2026-09-24T01:43:36.883Z", + "source": "https://huggingface.co/api/models/google/ddpm-cifar10-32/revision/267b167dc01f0e4e61923ea244e8b988f84deb80", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1300, + "gitBlobId": "eddeaacf0863c41a7bae323e2f4ecc159cb7153b" + }, + { + "path": "README.md", + "byteSize": 2977, + "gitBlobId": "1c0e213b72888b6bd400ddc04cc8e03a5133f4cc" + }, + { + "path": "config.json", + "byteSize": 699, + "gitBlobId": "4db706a7571d2fbf26bda70416cbbf9ffce86e86" + }, + { + "path": "diffusion_pytorch_model.safetensors", + "byteSize": 143020084, + "sha256": "1558dbe3fb093b0857d473e376b711eeda1b186c557be5e85b3a56300cd49f0f" + }, + { + "path": "model_index.json", + "byteSize": 180, + "gitBlobId": "740e26715a0e98cd69fdd99bb187d56b37da1d48" + }, + { + "path": "scheduler_config.json", + "byteSize": 256, + "gitBlobId": "790fb1d42455c89860b3b6f87ecfe3643a0ae3ee" + } + ] + }, + { + "repo": "HuggingFaceTB/SmolVLM-256M-Instruct", + "revision": "7e3e67edbbed1bf9888184d9df282b700a323964", + "checkedAt": "2026-09-24T01:43:26.973Z", + "source": "https://huggingface.co/api/models/HuggingFaceTB/SmolVLM-256M-Instruct/revision/7e3e67edbbed1bf9888184d9df282b700a323964", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "README.md", + "byteSize": 16400, + "gitBlobId": "66dd9eda6724c2e696a44a05dba796bb707de9ff" + }, + { + "path": "added_tokens.json", + "byteSize": 4739, + "gitBlobId": "71dc811700b6aa9274d3697ee7d4fb2d5375e766" + }, + { + "path": "chat_template.json", + "byteSize": 429, + "gitBlobId": "214a30563adf9598b26cdd3ad3bbafca9d7af45d" + }, + { + "path": "config.json", + "byteSize": 7353, + "gitBlobId": "1c09d62975f946e7e07f62ec4db3f16c9c701144" + }, + { + "path": "generation_config.json", + "byteSize": 136, + "gitBlobId": "14051e31eab5938a09976bf8c3e4769491f53a80" + }, + { + "path": "merges.txt", + "byteSize": 466391, + "gitBlobId": "69503b13f727ba3812b6803e97442a6de05ef5eb" + }, + { + "path": "model.safetensors", + "byteSize": 513028808, + "sha256": "74dea5904032e5ae99a2e0eef5179e6ac0f1dedc3ab0c7c2a5d4d387c843203e" + }, + { + "path": "preprocessor_config.json", + "byteSize": 486, + "gitBlobId": "893236748032d4bf033823d6a4a0d62b78b6bc0e" + }, + { + "path": "processor_config.json", + "byteSize": 68, + "gitBlobId": "b3b1deadd0e6e18c2070fdb0c29074892ea58ffe" + }, + { + "path": "special_tokens_map.json", + "byteSize": 1069, + "gitBlobId": "647565708c361dc42350b5c7b79c46a4ff153b18" + }, + { + "path": "tokenizer.json", + "byteSize": 3548256, + "gitBlobId": "a4005d1cf3170a31600a5c96f95768166cbc2b28" + }, + { + "path": "tokenizer_config.json", + "byteSize": 28249, + "gitBlobId": "c85c07d72e6ffca6b6ff138a8e8f09ae65740bfb" + }, + { + "path": "vocab.json", + "byteSize": 800662, + "gitBlobId": "0ad5ecc2035b7031b88afb544ee95e2d49baa484" + } + ] + }, + { + "repo": "jdopensource/JoyAI-Image-Edit-Diffusers", + "revision": "4b41fb25d961f37668750178ccbb380da326201c", + "checkedAt": "2026-09-24T01:43:37.197Z", + "source": "https://huggingface.co/api/models/jdopensource/JoyAI-Image-Edit-Diffusers/revision/4b41fb25d961f37668750178ccbb380da326201c", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 2086, + "gitBlobId": "9bd417416f98bac503004f650cf7a894c56a6e24" + }, + { + "path": "README.md", + "byteSize": 5839, + "gitBlobId": "9751f6e9eb7518a7e0458377a1e35bc058de375c" + }, + { + "path": "model_index.json", + "byteSize": 509, + "gitBlobId": "e62ebc94f1c82c03af42dadd62df59968f207246" + }, + { + "path": "processor/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "processor/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "processor/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 782, + "gitBlobId": "2fa6553554f69b4e3a6b742a75264ff9dff90ad0" + }, + { + "path": "processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 5465, + "gitBlobId": "79c91a486f74b296b885e215344b99c4b8e8dd46" + }, + { + "path": "processor/video_preprocessor_config.json", + "byteSize": 817, + "gitBlobId": "e32b1d90356f74457edbfca27aa1e121dab7cdc7" + }, + { + "path": "processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 487, + "gitBlobId": "aafe19c29a83a6d11047e27cd214f614fdebea25" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1517, + "gitBlobId": "b932a73c6ac822f4c869278606afdc4b4f4658b5" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 213, + "gitBlobId": "9a7dcbe7624a85aa6b33b11acced8e86653e93cf" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4998056552, + "sha256": "25957be8f716e35cac06c585cae0a9781ab2291c22ed755b2e3dbe15ae2b8802" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4915962464, + "sha256": "bf5e4e71070a7f97de1967958febc07e2dd270fc42ef0c11d8a9898759af81c1" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4915962496, + "sha256": "09d28414b48233f0f5a3ad4ed09a5755fb63b0c7f2bc7c2373986b1789a4e540" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 2704357976, + "sha256": "68d3cbc94351ed9c6ca4027d7cb65b6925cfd092e8109d13a719ad6d0a777553" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 67795, + "gitBlobId": "6d4d74c934b05b8addb0744305eef3674650cd8b" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 5465, + "gitBlobId": "79c91a486f74b296b885e215344b99c4b8e8dd46" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 387, + "gitBlobId": "d2a96938ec9ded4e59a4d1280d57b7a3a05f03f2" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00007.safetensors", + "byteSize": 4970222408, + "sha256": "adcb100cae21cbdc71d75cd26de468531bd1c8e49e718e3d377530a0034ca33a" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00007.safetensors", + "byteSize": 4967585880, + "sha256": "6c76310e69fa291b954280bc728a1a2de95533455ae01f6e6b6aaa58711b10e8" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00007.safetensors", + "byteSize": 4866946400, + "sha256": "845645c96a41269d02e6b02de953d3c58ff2741f33a326823feaccdd65f7937f" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00007.safetensors", + "byteSize": 4967584992, + "sha256": "722c95c3b39233f598dc6ffef589ecb411e8531ebfe4241c0d036f93c74e1bf8" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00007.safetensors", + "byteSize": 4967609680, + "sha256": "0a8e110439a119ad132e1d56ba137b63c48c232ffbd974572c37140f2becbd8c" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00007.safetensors", + "byteSize": 4967585000, + "sha256": "ab5e2cc1b6e15c57ca9fe8126d81141b5a52e9a19a1cbbff8afb7c9e130cee7e" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00007.safetensors", + "byteSize": 2819921264, + "sha256": "b359f79a76e35db3b28be8470a3be1af8be23ec396861e2c641697538ffb8c55" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 91220, + "gitBlobId": "964abe6a135f16722ce33c1e5eeb140f09bce49b" + }, + { + "path": "vae/config.json", + "byteSize": 898, + "gitBlobId": "0e7d47fbfbc5bbad5632af3d22fdac9e688a2967" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "39ffe6326a57bbc7c8a704d862e482588e3affc858bdbfc123a91b5ac3a3a42a" + } + ] + }, + { + "repo": "jdopensource/JoyAI-Image-Edit-Plus-Diffusers", + "revision": "c2686460c7b64d8aa11bc4d0da423fb316b33f9e", + "checkedAt": "2026-09-24T01:43:37.463Z", + "source": "https://huggingface.co/api/models/jdopensource/JoyAI-Image-Edit-Plus-Diffusers/revision/c2686460c7b64d8aa11bc4d0da423fb316b33f9e", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 555, + "gitBlobId": "dca70acfd64c1ab879f50248477e960426e8e6e5" + }, + { + "path": "README.md", + "byteSize": 3640, + "gitBlobId": "72ff3b60267ee456ba5961feb277779c4c64c8f2" + }, + { + "path": "model_index.json", + "byteSize": 513, + "gitBlobId": "c71f5497aea4badea0d1ac558cc40b3f2673f4ac" + }, + { + "path": "processor/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "processor/processor_config.json", + "byteSize": 1191, + "gitBlobId": "33818c7f9e991ad735fd240209f4fa73e6c28c50" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 11422650, + "sha256": "be75606093db2094d7cd20f3c2f385c212750648bd6ea4fb2bf507a6a4c55506" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 734, + "gitBlobId": "42bc491eccb6f2ad617f7fe78ea277b8d37c44fc" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 487, + "gitBlobId": "c8210d8771e8753460ac280cc15ce57155c1fcac" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1554, + "gitBlobId": "dbcdc6d1d652911cdc89bb80247735ca014a57b6" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 241, + "gitBlobId": "dab70c905125779d8b6abe7bb7a978bae6f1c164" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4905357176, + "sha256": "b9ae2762e4d81c9b168d14a37d540bbe3d58e370c8e964488957831b0598b785" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4915962456, + "sha256": "328ef00c46b51b697d7a5c7efb8f46f0439863c96ad7637a44a97a50aa619c51" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4974674312, + "sha256": "4b375095244456e0a6b84980fe1033c190995cc61ff07e7de5b4e6954a20b1ea" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 2738345504, + "sha256": "73f5d73a7859ce0a5832e2a99d3bd3395568ffc0a00052eea1dead71a7df9445" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 67795, + "gitBlobId": "8f515773f956451cd6a8fdab642b469c203c63c0" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422650, + "sha256": "be75606093db2094d7cd20f3c2f385c212750648bd6ea4fb2bf507a6a4c55506" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 693, + "gitBlobId": "2531cbfba403ab235aa03a7b66b1dd122fb805cc" + }, + { + "path": "transformer/config.json", + "byteSize": 391, + "gitBlobId": "bf030920c56c5d6c6d06340912e6695c9acb8c9f" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00007.safetensors", + "byteSize": 4970222408, + "sha256": "1273aa85e705da950fd37ee4e7cb4dde46794d3fecc495d92358b6097ca6be28" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00007.safetensors", + "byteSize": 4967585880, + "sha256": "410566a22c858a900a7ea76c81efefcf6a7aa39f03aee5218515accd61bd8c2c" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00007.safetensors", + "byteSize": 4866946400, + "sha256": "1080ff300c71cbd685f0f9b8b8a5240949c0553d668a9ad068441822651a1833" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00007.safetensors", + "byteSize": 4967584992, + "sha256": "f2a4117e80f7dec48aec065e96a4f571417ebfac2e2a2e9fc374cd8c53ca709f" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00007.safetensors", + "byteSize": 4967609680, + "sha256": "de302cc81dab6dfdf9cb68bb107935275e85d0d61d6f2f1ddd5eff59504626a3" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00007.safetensors", + "byteSize": 4967585000, + "sha256": "c392fcd663e60ff26308f8621e7b4e83fea8fe4a1761a1a14a2ee0a0c44659fe" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00007.safetensors", + "byteSize": 2819921264, + "sha256": "e3f9d06e73fa6236969dc4d22e1f759167a2e142cfc1c616bfa57432c08ed9bc" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 91220, + "gitBlobId": "964abe6a135f16722ce33c1e5eeb140f09bce49b" + }, + { + "path": "vae/config.json", + "byteSize": 898, + "gitBlobId": "7de3aa17845757b2339aa315b4f60f7061209321" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "39ffe6326a57bbc7c8a704d862e482588e3affc858bdbfc123a91b5ac3a3a42a" + } + ] + }, + { + "repo": "kandinsky-community/kandinsky-3", + "revision": "bf79e6c219da8a94abb50235fdc4567eb8fb4632", + "checkedAt": "2026-09-24T01:43:37.871Z", + "source": "https://huggingface.co/api/models/kandinsky-community/kandinsky-3/revision/bf79e6c219da8a94abb50235fdc4567eb8fb4632", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "README.md", + "byteSize": 5608, + "gitBlobId": "46fceb231bee1cb2ad2d25c94a1efe3924128d38" + }, + { + "path": "model_index.json", + "byteSize": 417, + "gitBlobId": "904fac8816fa802ec80f0085cb3a4f98971f29ba" + }, + { + "path": "movq/config.json", + "byteSize": 714, + "gitBlobId": "d715e07f0e60f903001e46943e625a0a52c1ecb2" + }, + { + "path": "movq/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 541388942, + "sha256": "8a4705625c160c49025e1f9b98776aaf5287240ca0d5ed39a1a235598fc39244" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 509, + "gitBlobId": "ac7824e993b2b92d4b79d5234678427e0ada4519" + }, + { + "path": "text_encoder/config.json", + "byteSize": 793, + "gitBlobId": "f64ac28bb924052f22a2b16b8e6fb6f49a723458" + }, + { + "path": "text_encoder/model.fp16-00001-of-00005.safetensors", + "byteSize": 4994491352, + "sha256": "2745f08c74c13536c67282afc1e5ba80daf67e5ded7925845f3cb5e1983a2826" + }, + { + "path": "text_encoder/model.fp16-00002-of-00005.safetensors", + "byteSize": 4932632832, + "sha256": "a1c184e0f3a2e8c80360379ada2d9fce40bee23647a63e3baefa00d837084408" + }, + { + "path": "text_encoder/model.fp16-00003-of-00005.safetensors", + "byteSize": 4831960880, + "sha256": "3a6e4c548e5c2e6aa080a5eefad31e973dbba55b6918f95fb77af97fce341c94" + }, + { + "path": "text_encoder/model.fp16-00004-of-00005.safetensors", + "byteSize": 4999741472, + "sha256": "3e7652beb11f33c5549d391c2affacd6642d9ca21bc0d4319cba60fe1b357ff0" + }, + { + "path": "text_encoder/model.fp16-00005-of-00005.safetensors", + "byteSize": 1979772184, + "sha256": "f41b9aea886f3d583c026011cb7251ff21f615774ad604264aaaa01605e02326" + }, + { + "path": "text_encoder/model.safetensors.index.fp16.json", + "byteSize": 27893, + "gitBlobId": "302004b2ca48e206fcc2789106d076c339beca15" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 3255, + "gitBlobId": "71292767d495f83fe5c67276dde4a8775959f761" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 25436, + "gitBlobId": "9a1567cd5b1039b130111eecdf8eacf5e48b2a33" + }, + { + "path": "unet/config.json", + "byteSize": 322, + "gitBlobId": "fe9a1e5964d268b6d0c6d1c18083bd365fd24757" + }, + { + "path": "unet/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 6110842296, + "sha256": "805179fe9c10cd1e01c59057f0c2bb07ce57834ad343f721e50ca16bef6952f6" + } + ] + }, + { + "repo": "lodestones/Chroma1-HD", + "revision": "0e0c60ece1e82b17cb7f77342d765ba5024c40c0", + "checkedAt": "2026-09-24T01:43:38.167Z", + "source": "https://huggingface.co/api/models/lodestones/Chroma1-HD/revision/0e0c60ece1e82b17cb7f77342d765ba5024c40c0", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1802, + "gitBlobId": "a3e5936c708d3deba2aa6d15bdb81de5a8124b85" + }, + { + "path": "README.md", + "byteSize": 8107, + "gitBlobId": "cac59e211d61272153f65db99f06cdf1bce6fa35" + }, + { + "path": "model_index.json", + "byteSize": 494, + "gitBlobId": "3766e2b9c9921bc3dbee32dd137f29ddb0cbaa85" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 482, + "gitBlobId": "1876155ecfc2ff4f1d03d9d51136dae67e294904" + }, + { + "path": "text_encoder/config.json", + "byteSize": 741, + "gitBlobId": "7b49ed844ac688510aeda1d22e4975f4477c4f36" + }, + { + "path": "text_encoder/model-00001-of-00002.safetensors", + "byteSize": 4994582224, + "sha256": "ec87bffd1923e8b2774a6d240c922a41f6143081d52cf83b8fe39e9d838c893e" + }, + { + "path": "text_encoder/model-00002-of-00002.safetensors", + "byteSize": 4530066360, + "sha256": "a5640855b301fcdbceddfa90ae8066cd9414aff020552a201a255ecf2059da00" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 19921, + "gitBlobId": "2ec5af72751ce5482cdc10d7c2ea353f791c8bb7" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 2593, + "gitBlobId": "3f5132007c4fcf42b75b65c8b6aa49c7098bcdf4" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 20847, + "gitBlobId": "ab25e5b781898f1326d306c8313c0f7562280db4" + }, + { + "path": "transformer/config.json", + "byteSize": 490, + "gitBlobId": "7f1a47d431e695bd9096c3cf41ae86addcb21ae3" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00002.safetensors", + "byteSize": 9946193392, + "sha256": "59f0b26eccaabb8301e4166e653f1bcdebd7c46a4a07b02fe33d7a2c46d78eba" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00002.safetensors", + "byteSize": 7853894360, + "sha256": "65e6541afdabbb96568a3f8975eeef929f0a7e0eef3d2c4741b1bf911ac3a6d5" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 106695, + "gitBlobId": "8a52f5e7b146f0943820c68eebee361dc4d9c22a" + }, + { + "path": "vae/config.json", + "byteSize": 819, + "gitBlobId": "61d8c2c328b253952fc10cf77afd955fdde2675f" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "meituan-longcat/LongCat-Image", + "revision": "d2ea50b79a930074c37b9b97ce45e3b2ea8cf4d8", + "checkedAt": "2026-09-24T01:43:38.426Z", + "source": "https://huggingface.co/api/models/meituan-longcat/LongCat-Image/revision/d2ea50b79a930074c37b9b97ce45e3b2ea8cf4d8", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1659, + "gitBlobId": "8d7e3018641e15e7cf321be8ed4a1be1e7c4e3e6" + }, + { + "path": "README.md", + "byteSize": 5269, + "gitBlobId": "ba3bf1b2ec0d0d1a3ebfb53ee5f359c161185a96" + }, + { + "path": "model_index.json", + "byteSize": 504, + "gitBlobId": "8be7100d48d394f291d10b4d70d2c38406558319" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 273, + "gitBlobId": "a64d1e8763bef5828984682680673fb97829734b" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1374, + "gitBlobId": "58ee12a9fb3d8708a1e41e49cb9aa5ac5017b6a6" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 244, + "gitBlobId": "545039aa39cc7216246844dc015f71ebd8d168db" + }, + { + "path": "text_encoder/model-00001-of-00005.safetensors", + "byteSize": 3900233256, + "sha256": "e97b877e47fde53a6c6e77aafb36e58e91ee9d95c4a3eeac6f1b5c0e6a1c986e" + }, + { + "path": "text_encoder/model-00002-of-00005.safetensors", + "byteSize": 3864726320, + "sha256": "a9a300a43b4724eee2abe7c18ceb26768d0ab011eb0cad19d9bfd2476a24d024" + }, + { + "path": "text_encoder/model-00003-of-00005.safetensors", + "byteSize": 3864726424, + "sha256": "111223d173e00bbee81cba1216fad28668df3476706b7fd26f4d5b50f8b3a507" + }, + { + "path": "text_encoder/model-00004-of-00005.safetensors", + "byteSize": 3864733680, + "sha256": "ef47f634fa57d46ee134edcc09f34085a47da1e16c12a2abe0d67118be6d72ed" + }, + { + "path": "text_encoder/model-00005-of-00005.safetensors", + "byteSize": 1089994880, + "sha256": "0c859795ad3a627a9b95bcb762e059d5b768a4a36fdd4affeff269d93fdecc67" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 57619, + "gitBlobId": "f0c9e9b8e043f5de0ac99fd8b2ea75515d811e7b" + }, + { + "path": "text_processor/chat_template.json", + "byteSize": 1050, + "gitBlobId": "732bd68bc5427d1fb6c06a59b3bf2456b2155d24" + }, + { + "path": "text_processor/config.json", + "byteSize": 1374, + "gitBlobId": "58ee12a9fb3d8708a1e41e49cb9aa5ac5017b6a6" + }, + { + "path": "text_processor/merges.txt", + "byteSize": 1671839, + "gitBlobId": "20024bfe7c83998e9aeaf98a0cd6a2ce6306c2f0" + }, + { + "path": "text_processor/preprocessor_config.json", + "byteSize": 351, + "gitBlobId": "8273ab1b1c5d408d4d175defd1e9391efdab19f2" + }, + { + "path": "text_processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "text_processor/tokenizer.json", + "byteSize": 7031645, + "gitBlobId": "443909a61d429dff23010e5bddd28ff530edda00" + }, + { + "path": "text_processor/tokenizer_config.json", + "byteSize": 7226, + "gitBlobId": "5048e7ae6126fff1921e887db4ec27a98d409f92" + }, + { + "path": "text_processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "tokenizer/chat_template.json", + "byteSize": 1050, + "gitBlobId": "732bd68bc5427d1fb6c06a59b3bf2456b2155d24" + }, + { + "path": "tokenizer/config.json", + "byteSize": 1374, + "gitBlobId": "58ee12a9fb3d8708a1e41e49cb9aa5ac5017b6a6" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671839, + "gitBlobId": "20024bfe7c83998e9aeaf98a0cd6a2ce6306c2f0" + }, + { + "path": "tokenizer/preprocessor_config.json", + "byteSize": 351, + "gitBlobId": "8273ab1b1c5d408d4d175defd1e9391efdab19f2" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 7031645, + "gitBlobId": "443909a61d429dff23010e5bddd28ff530edda00" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 7226, + "gitBlobId": "5048e7ae6126fff1921e887db4ec27a98d409f92" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 356, + "gitBlobId": "83f33ef901b7e5d5a9d91943da714eb546641126" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 12541410184, + "sha256": "df81cbd7da1371c3d0c04af91efbb3c358fd18a4229ea0f3680d62d892b4e4ee" + }, + { + "path": "vae/config.json", + "byteSize": 820, + "gitBlobId": "4f7c628fc8e59fc842c9846ed74414249052ba0b" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "meituan-longcat/LongCat-Image-Edit", + "revision": "7b54ef423aa7854be7861600024be5c56ab7875a", + "checkedAt": "2026-09-24T01:43:38.757Z", + "source": "https://huggingface.co/api/models/meituan-longcat/LongCat-Image-Edit/revision/7b54ef423aa7854be7861600024be5c56ab7875a", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1768, + "gitBlobId": "0ebe7cdaba497321f7f64a18a5febc6b012c2353" + }, + { + "path": "README.md", + "byteSize": 4636, + "gitBlobId": "f3c1fee8df2da9288820644442c89c669c62acaf" + }, + { + "path": "model_index.json", + "byteSize": 509, + "gitBlobId": "c26f4075c3c42819bb7c0029cf468c4ef9193c7b" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 273, + "gitBlobId": "a64d1e8763bef5828984682680673fb97829734b" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1374, + "gitBlobId": "58ee12a9fb3d8708a1e41e49cb9aa5ac5017b6a6" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 244, + "gitBlobId": "545039aa39cc7216246844dc015f71ebd8d168db" + }, + { + "path": "text_encoder/model-00001-of-00005.safetensors", + "byteSize": 3900233256, + "sha256": "e97b877e47fde53a6c6e77aafb36e58e91ee9d95c4a3eeac6f1b5c0e6a1c986e" + }, + { + "path": "text_encoder/model-00002-of-00005.safetensors", + "byteSize": 3864726320, + "sha256": "a9a300a43b4724eee2abe7c18ceb26768d0ab011eb0cad19d9bfd2476a24d024" + }, + { + "path": "text_encoder/model-00003-of-00005.safetensors", + "byteSize": 3864726424, + "sha256": "111223d173e00bbee81cba1216fad28668df3476706b7fd26f4d5b50f8b3a507" + }, + { + "path": "text_encoder/model-00004-of-00005.safetensors", + "byteSize": 3864733680, + "sha256": "ef47f634fa57d46ee134edcc09f34085a47da1e16c12a2abe0d67118be6d72ed" + }, + { + "path": "text_encoder/model-00005-of-00005.safetensors", + "byteSize": 1089994880, + "sha256": "0c859795ad3a627a9b95bcb762e059d5b768a4a36fdd4affeff269d93fdecc67" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 57619, + "gitBlobId": "f0c9e9b8e043f5de0ac99fd8b2ea75515d811e7b" + }, + { + "path": "text_encoder/preprocessor_config.json", + "byteSize": 351, + "gitBlobId": "8273ab1b1c5d408d4d175defd1e9391efdab19f2" + }, + { + "path": "text_processor/chat_template.json", + "byteSize": 1050, + "gitBlobId": "732bd68bc5427d1fb6c06a59b3bf2456b2155d24" + }, + { + "path": "text_processor/config.json", + "byteSize": 1374, + "gitBlobId": "58ee12a9fb3d8708a1e41e49cb9aa5ac5017b6a6" + }, + { + "path": "text_processor/merges.txt", + "byteSize": 1671839, + "gitBlobId": "20024bfe7c83998e9aeaf98a0cd6a2ce6306c2f0" + }, + { + "path": "text_processor/preprocessor_config.json", + "byteSize": 351, + "gitBlobId": "8273ab1b1c5d408d4d175defd1e9391efdab19f2" + }, + { + "path": "text_processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "text_processor/tokenizer.json", + "byteSize": 7031645, + "gitBlobId": "443909a61d429dff23010e5bddd28ff530edda00" + }, + { + "path": "text_processor/tokenizer_config.json", + "byteSize": 7226, + "gitBlobId": "5048e7ae6126fff1921e887db4ec27a98d409f92" + }, + { + "path": "text_processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "tokenizer/chat_template.json", + "byteSize": 1050, + "gitBlobId": "732bd68bc5427d1fb6c06a59b3bf2456b2155d24" + }, + { + "path": "tokenizer/config.json", + "byteSize": 1374, + "gitBlobId": "58ee12a9fb3d8708a1e41e49cb9aa5ac5017b6a6" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671839, + "gitBlobId": "20024bfe7c83998e9aeaf98a0cd6a2ce6306c2f0" + }, + { + "path": "tokenizer/preprocessor_config.json", + "byteSize": 351, + "gitBlobId": "8273ab1b1c5d408d4d175defd1e9391efdab19f2" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 7031645, + "gitBlobId": "443909a61d429dff23010e5bddd28ff530edda00" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 7226, + "gitBlobId": "5048e7ae6126fff1921e887db4ec27a98d409f92" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 356, + "gitBlobId": "83f33ef901b7e5d5a9d91943da714eb546641126" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 12541410152, + "sha256": "7ad9bfe2ca5f32ec85a01c8860d1bc4f472682bc30c9f63c45fd2a61c0804fcd" + }, + { + "path": "vae/config.json", + "byteSize": 820, + "gitBlobId": "4f7c628fc8e59fc842c9846ed74414249052ba0b" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "nateraw/real-esrgan", + "revision": "42efb9c3eeed1f5c0c8a626cf5f7f4481dfbb094", + "checkedAt": "2026-09-24T01:43:39.020Z", + "source": "https://huggingface.co/api/models/nateraw/real-esrgan/revision/42efb9c3eeed1f5c0c8a626cf5f7f4481dfbb094", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": "RealESRGAN_x2plus.pth", + "byteSize": 67061725, + "sha256": "49fafd45f8fd7aa8d31ab2a22d14d91b536c34494a5cfe31eb5d89c2fa266abb" + } + ] + }, + { + "repo": "NucleusAI/Nucleus-Image", + "revision": "5e963db4fd0a65c7e4faf53ca2d4eca567c4dcfa", + "checkedAt": "2026-09-24T01:43:27.232Z", + "source": "https://huggingface.co/api/models/NucleusAI/Nucleus-Image/revision/5e963db4fd0a65c7e4faf53ca2d4eca567c4dcfa", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 2195, + "gitBlobId": "96bd88d2a5f6324ca2d1a63e2cc3b7049c40cc0d" + }, + { + "path": "README.md", + "byteSize": 7832, + "gitBlobId": "cbeda79a2564b24a79a55da114aa2b710fd45820" + }, + { + "path": "model_index.json", + "byteSize": 495, + "gitBlobId": "31e44642e2bfc2b833cde2ce0db6bb3cd5f83069" + }, + { + "path": "processor/chat_template.json", + "byteSize": 5499, + "gitBlobId": "1081bacf1af7c7c6de4a585ce02cd0fd34e382da" + }, + { + "path": "processor/config.json", + "byteSize": 1474, + "gitBlobId": "0e2df614c5fe1ad2b6b540c8625bebf2d7ec43ca" + }, + { + "path": "processor/merges.txt", + "byteSize": 1671839, + "gitBlobId": "20024bfe7c83998e9aeaf98a0cd6a2ce6306c2f0" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 390, + "gitBlobId": "2ea84a437d448ff71b08df68fdd949d5cc4ebb64" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 7032403, + "gitBlobId": "c6cc1014128b19d1fc46b1d30a23e3b1d35db421" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 10868, + "gitBlobId": "d3d3763207692c78780f4bf42d4dadf49a5c8012" + }, + { + "path": "processor/video_preprocessor_config.json", + "byteSize": 385, + "gitBlobId": "3ba673a5ad7d4d13f54155ecd38b2a94a6dac8fe" + }, + { + "path": "processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 482, + "gitBlobId": "0eec8e1d4d48d91f651f6094d4c203872fd3a5aa" + }, + { + "path": "text_encoder/README.md", + "byteSize": 7133, + "gitBlobId": "06fb2c8220b4fb51ccca87dd694ef9d240c36745" + }, + { + "path": "text_encoder/chat_template.json", + "byteSize": 5499, + "gitBlobId": "1081bacf1af7c7c6de4a585ce02cd0fd34e382da" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1474, + "gitBlobId": "0e2df614c5fe1ad2b6b540c8625bebf2d7ec43ca" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 269, + "gitBlobId": "e116347d7ef42621c7a5957f82c015c38d939c1f" + }, + { + "path": "text_encoder/merges.txt", + "byteSize": 1671839, + "gitBlobId": "20024bfe7c83998e9aeaf98a0cd6a2ce6306c2f0" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4902275944, + "sha256": "d5d0aef0eb170fc7453a296c43c0849a56f510555d3588e4fd662bb35490aefa" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4915962496, + "sha256": "8be88fb5501e4d5719a6d4cc212e6a13480330e74f3e8c77daa1a68f199106b5" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4999831048, + "sha256": "83de00eafe6e0d57ccd009dbcf71c9974d74df2f016c27afb7e95aafd16b2192" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 2716270024, + "sha256": "0a88b98e9f96270973f567e6a2c103ede6ccdf915ca3075e21c755604d0377a5" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 67759, + "gitBlobId": "5857ec62530eb589b1876034dd5386f1d98131a6" + }, + { + "path": "text_encoder/preprocessor_config.json", + "byteSize": 390, + "gitBlobId": "2ea84a437d448ff71b08df68fdd949d5cc4ebb64" + }, + { + "path": "text_encoder/tokenizer.json", + "byteSize": 7032403, + "gitBlobId": "c6cc1014128b19d1fc46b1d30a23e3b1d35db421" + }, + { + "path": "text_encoder/tokenizer_config.json", + "byteSize": 10868, + "gitBlobId": "d3d3763207692c78780f4bf42d4dadf49a5c8012" + }, + { + "path": "text_encoder/video_preprocessor_config.json", + "byteSize": 385, + "gitBlobId": "3ba673a5ad7d4d13f54155ecd38b2a94a6dac8fe" + }, + { + "path": "text_encoder/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 887, + "gitBlobId": "4561ad0e2093c5cd086a34e150426b0bdff24282" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00007.safetensors", + "byteSize": 5359982584, + "sha256": "ad0a79d47a0ea15e1f746cf64ed9b85b210b577e6769f1196075d80e6d5b46d7" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00007.safetensors", + "byteSize": 5368036944, + "sha256": "1d1c04d5ae3e81c7c699dcbb9e2d9ccd2a086f50bad889d0617ca2bdf11da3fd" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00007.safetensors", + "byteSize": 5119271264, + "sha256": "ba2dc218dee2032e2e9bbd89548b7962901f28f2f92d63f23ecd6c138e04d612" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00007.safetensors", + "byteSize": 5368036944, + "sha256": "d00f85e512a49b881513f0ca0123d70dc9963c3d5531a6986aa2fe929b62d2bf" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00007.safetensors", + "byteSize": 4977690112, + "sha256": "1ef5a35ddd6f01a8fe26a475dc8e2ffc56585cd8797e8a0525578ddea109b681" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00007.safetensors", + "byteSize": 5368036864, + "sha256": "ceb86f9bb8b934dae6ff9bc562482f6a493c97df50a1b8897a3b9e7d64cedf8d" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00007.safetensors", + "byteSize": 2284376672, + "sha256": "f5a767727147f0686615eb3424b5200d2313548b0c1931ab90b88e90c56de197" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 63931, + "gitBlobId": "0dcad01f034b2ef81dda9e8cf95fd8a83404aba5" + }, + { + "path": "vae/config.json", + "byteSize": 730, + "gitBlobId": "ebc479e2fe5a2265f4bcaf1e1073df64a2d4e33e" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "0c8bc8b758c649abef9ea407b95408389a3b2f610d0d10fcb054fe171d0a8344" + } + ] + }, + { + "repo": "nvidia/Cosmos3-Nano", + "revision": "7a312c868bcce8e40b3eb40861300a9d0ba3fde1", + "checkedAt": "2026-09-24T01:43:39.314Z", + "source": "https://huggingface.co/api/models/nvidia/Cosmos3-Nano/revision/7a312c868bcce8e40b3eb40861300a9d0ba3fde1", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": "README.md", + "byteSize": 44048, + "gitBlobId": "9a22110f5bf992ef40cd413e067cae9ab15cca5b" + }, + { + "path": "SAFETY.md", + "byteSize": 3677, + "gitBlobId": "44e1a5a58e8a5e76b8f0a6a415be45e59ba9f270" + }, + { + "path": "modular_model_index.json", + "byteSize": 1667, + "gitBlobId": "f359c406e914816dbae0e0496b01feeb433ed726" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 884, + "gitBlobId": "feea3f81b41ab07ca305b3e78e54d6929c624b11" + }, + { + "path": "sound_tokenizer/config.json", + "byteSize": 1310, + "gitBlobId": "88842cb6d7115f0c0a90ddc20ed5e933e672ba74" + }, + { + "path": "sound_tokenizer/diffusion_pytorch_model.safetensors", + "byteSize": 1985176840, + "sha256": "a4b6da5975bf89f6853fe589ad4752d281ac79fbdfad52ea90537fa080b4b9c2" + }, + { + "path": "text_tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "text_tokenizer/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "text_tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "text_tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "text_tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "text_tokenizer/tokenizer_config.json", + "byteSize": 5404, + "gitBlobId": "1d4fba2de0e94e8f97e08927cfb7d084160c2880" + }, + { + "path": "text_tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 1383, + "gitBlobId": "c54d151af5170f5a09b02abf03fda3c169f948e8" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00007.safetensors", + "byteSize": 4902257280, + "sha256": "7d0f718198cac14cf323d58568c78aa1290b4bd1dcced6481624b5b9c6d70dc2" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00007.safetensors", + "byteSize": 4999863656, + "sha256": "7e6e7d270c193a58a1aec0cbc89f6f5afcaef6e3b951d86b0a432244c449e34e" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00007.safetensors", + "byteSize": 4932719448, + "sha256": "15d08936149a7e77c6668e5b1cd47dc92a35e33741f0090d619045b66da9ff8d" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00007.safetensors", + "byteSize": 4983084656, + "sha256": "02cebbb557bced701eb825901e1de5f86d2e1feb49cb9a8d1fb7d1b878fdb31b" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00007.safetensors", + "byteSize": 4949498576, + "sha256": "a5e3edd742b8f1fcf27a3eb3878c67dfcb0e6881588fa35deef503d640ea69d1" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00007.safetensors", + "byteSize": 4261644464, + "sha256": "1bb99750f7ab4a47f8b0403a87aa43fefc9648660609ab2ace4cab361b957cd0" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00007.safetensors", + "byteSize": 1318361208, + "sha256": "3f159bc8b82477adde771ae9e4f54f76564e02844a2fc6267903bd745111d7f6" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 78854, + "gitBlobId": "345e95ec0f8233be7b2bfdb9ac8237d98712c6fb" + }, + { + "path": "vae/config.json", + "byteSize": 1750, + "gitBlobId": "3391d3657e5f8fcc79ef83ac720e7f07a572dee4" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 1409400600, + "sha256": "230496cb59ff85bc9c040487737c4062480cb61c71e697b197b4c30142f2a0da" + } + ] + }, + { + "repo": "nvidia/Cosmos3-Super-Text2Image-4Step", + "revision": "aa0d5a57b7b045d68daa60fbacd84ec723c7cb7b", + "checkedAt": "2026-09-24T01:43:39.588Z", + "source": "https://huggingface.co/api/models/nvidia/Cosmos3-Super-Text2Image-4Step/revision/aa0d5a57b7b045d68daa60fbacd84ec723c7cb7b", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": "README.md", + "byteSize": 33448, + "gitBlobId": "17d4bea3edd75fb330fb55f89beaba6733b1f88f" + }, + { + "path": "SAFETY.md", + "byteSize": 3677, + "gitBlobId": "44e1a5a58e8a5e76b8f0a6a415be45e59ba9f270" + }, + { + "path": "modular_model_index.json", + "byteSize": 1766, + "gitBlobId": "dfcae1a1cf17107c40c831964eac05342621de09" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 677, + "gitBlobId": "498a3bb2b4680ae419df1f561127377a3ab64036" + }, + { + "path": "sound_tokenizer/config.json", + "byteSize": 1259, + "gitBlobId": "02d7d97c405c5afb83966bbe618fae53f0f5288d" + }, + { + "path": "sound_tokenizer/diffusion_pytorch_model.safetensors", + "byteSize": 1985176840, + "sha256": "a4b6da5975bf89f6853fe589ad4752d281ac79fbdfad52ea90537fa080b4b9c2" + }, + { + "path": "text_tokenizer/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "text_tokenizer/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "text_tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "text_tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "text_tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "text_tokenizer/tokenizer_config.json", + "byteSize": 5404, + "gitBlobId": "1d4fba2de0e94e8f97e08927cfb7d084160c2880" + }, + { + "path": "text_tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 952, + "gitBlobId": "653b0cab24ee81eeda940d79f86d6cf46d5bcce2" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00027.safetensors", + "byteSize": 4932297056, + "sha256": "5349680a92b755a6b4f813c0186b9289b4093f4735891b66626808ffcb48696c" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00027.safetensors", + "byteSize": 4802610968, + "sha256": "9fbb44119a6be9b98e9de8aaa23cfa3c4ab804abb55ea09f7185873a512bed4f" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00027.safetensors", + "byteSize": 4949368056, + "sha256": "b102b79ce0213884ca4aa390e0634fe5e99e778507ac155ca500da6a5928801b" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00027.safetensors", + "byteSize": 4802610968, + "sha256": "c62e65baa85861caa2b684e1f9c7d95a3007139b22a9a5e444b254ac2f251ac3" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00027.safetensors", + "byteSize": 4949368096, + "sha256": "77d145279dd84ebf82f86b8459cc94d013fbf414e4e57362b18aca2e001b5b3d" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "b0dda9f72e03cd55d73844c0335262961018b0398f3fb088f2c2dca6aaaafa8c" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "8c3c338914f8599819d8115ea74b6ea8017ba1f7f7f944a1dd29f9485408601b" + }, + { + "path": "transformer/diffusion_pytorch_model-00008-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "e9317d6767840bf92efe9e7e14153d2921bb721d09704c81b55614eda6661b11" + }, + { + "path": "transformer/diffusion_pytorch_model-00009-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "32d5739c395bbdac89672507d5314781fc88d7c6cdcc8ccf358bf8610db9136b" + }, + { + "path": "transformer/diffusion_pytorch_model-00010-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "6411374f654df2f111912e0a8649a2408543f3a81efa2a7769489c193b2624aa" + }, + { + "path": "transformer/diffusion_pytorch_model-00011-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "4b7ec78e856107a52df881eb80de575156de9e83c468458d13eedc3f335cb5c2" + }, + { + "path": "transformer/diffusion_pytorch_model-00012-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "4241caccf6cfbbeb380fb314354d65ea4be195e5b8b072bcf2ac7e44abaa6558" + }, + { + "path": "transformer/diffusion_pytorch_model-00013-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "7e802d80b93b856ebc24897dfe311988eeafce729b4660094306e47fa305c50d" + }, + { + "path": "transformer/diffusion_pytorch_model-00014-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "9f0a79b8bb4c0a88aca395115931fcbf3084326f9966fe60c2cdfe2fea6e26e7" + }, + { + "path": "transformer/diffusion_pytorch_model-00015-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "2146a965a535513e21c6c7547783ffbd585385533cb9b1b10d63e3388c4f26ba" + }, + { + "path": "transformer/diffusion_pytorch_model-00016-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "eb6eb52a3a1c15f069a8faa490438c4f657b6321938307063c1a98931a8eafe8" + }, + { + "path": "transformer/diffusion_pytorch_model-00017-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "04db42576c1334fa7b6ac3c225b5eaf09fa71da29ab41dcf3768bbadb04a1f55" + }, + { + "path": "transformer/diffusion_pytorch_model-00018-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "99680080b0cfe5b10a980428fb696a5912ec1f0c99aa0278961aafe827e05f75" + }, + { + "path": "transformer/diffusion_pytorch_model-00019-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "aeeb8d5de0c86ea513daef746e978b77b7720f20795d05eb644952c5a98600a2" + }, + { + "path": "transformer/diffusion_pytorch_model-00020-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "d24c3cd91fc1e87745871ed4c1ac7ad7cc87f393de275174bb4ba0b2a82a03fa" + }, + { + "path": "transformer/diffusion_pytorch_model-00021-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "813f7db79b70279b08bff8a545eebec2fafda75907751c2dd0a560511e557c4d" + }, + { + "path": "transformer/diffusion_pytorch_model-00022-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "468a5f3cef4352cc0be25dc175d6e38b53f3c889fa05e9c8706d39471118be3f" + }, + { + "path": "transformer/diffusion_pytorch_model-00023-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "6b93478f24a8404c1da5bdbb0a2df6db76f7b20a5135c97891361d1a77cb9156" + }, + { + "path": "transformer/diffusion_pytorch_model-00024-of-00027.safetensors", + "byteSize": 4802611024, + "sha256": "372bfe82bfe6d49d6a16dcdce82aa672f4f782226eccc66110b2feeb3fb2922c" + }, + { + "path": "transformer/diffusion_pytorch_model-00025-of-00027.safetensors", + "byteSize": 4949368104, + "sha256": "6f48c2d1d784099abb307cc75e8b7099861d7d7ba9226dd52894fc676d9f17ad" + }, + { + "path": "transformer/diffusion_pytorch_model-00026-of-00027.safetensors", + "byteSize": 4425141960, + "sha256": "3f86b6a99526fc894fd01805ec675c1f55cc81ed1f430e06e772f54cd62cbcfc" + }, + { + "path": "transformer/diffusion_pytorch_model-00027-of-00027.safetensors", + "byteSize": 1616160480, + "sha256": "280c5f1b2532aaaecd4837aedd21b8e1af9e2aa18ab8b33cc8aa501112bc4a08" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 138454, + "gitBlobId": "31fe43886fdd6adf769d29136513b159911ecb34" + }, + { + "path": "vae/config.json", + "byteSize": 1750, + "gitBlobId": "d2345338012a1f3efb4b430f9848240ff2154100" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 1409400600, + "sha256": "230496cb59ff85bc9c040487737c4062480cb61c71e697b197b4c30142f2a0da" + } + ] + }, + { + "repo": "openai/diffusers-cd_imagenet64_l2", + "revision": "5f462e4403fc37b72ec6004e806c71805db22387", + "checkedAt": "2026-09-24T01:43:39.884Z", + "source": "https://huggingface.co/api/models/openai/diffusers-cd_imagenet64_l2/revision/5f462e4403fc37b72ec6004e806c71805db22387", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1477, + "gitBlobId": "c7d9f3332a950355d5a77d85000f05e6f45435ea" + }, + { + "path": "README.md", + "byteSize": 9950, + "gitBlobId": "7dd5356e58bf1a9401192e7f1bd882227db73f2b" + }, + { + "path": "model_index.json", + "byteSize": 254, + "gitBlobId": "4cdb4dd4b21afd1098d13c988683394c1e5ed3d5" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 243, + "gitBlobId": "5eed3e6ce5980537076cbedee453012af289eb54" + }, + { + "path": "unet/config.json", + "byteSize": 1001, + "gitBlobId": "12366baf83c3057ba030b4e55055e2d26241c514" + }, + { + "path": "unet/diffusion_pytorch_model.safetensors", + "byteSize": 1183665484, + "sha256": "9702bf70b1e7cbe67b1da6d829e90c469f78c0592b1760170dbfb9d370f5695c" + } + ] + }, + { + "repo": "Photoroom/prx-512-t2i-sft", + "revision": "2996423bc26e8eaca48774fac1797484214dfea0", + "checkedAt": "2026-09-24T01:43:27.540Z", + "source": "https://huggingface.co/api/models/Photoroom/prx-512-t2i-sft/revision/2996423bc26e8eaca48774fac1797484214dfea0", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "f1ca65ce6a5b0774360fde1ec05157d9f937aaa2" + }, + { + "path": "LICENSE", + "byteSize": 11358, + "gitBlobId": "d645695673349e3947e8e5ae42332d0ac3164cd7" + }, + { + "path": "NOTICE", + "byteSize": 496, + "gitBlobId": "29fe3a903bd80934aad1f38869143b51df5f40fa" + }, + { + "path": "README.md", + "byteSize": 6168, + "gitBlobId": "4da97c9d0f3fd19ca762c8f1a56860a3993ec3e5" + }, + { + "path": "model_index.json", + "byteSize": 464, + "gitBlobId": "45fcd30744d03928bf1a0932ad3dc6f2999c6955" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 101, + "gitBlobId": "cc0eff6aeeddf894383da4cb724c95b14121e240" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1436, + "gitBlobId": "0c563254aaaa935aff04aa99a073465b21256f03" + }, + { + "path": "text_encoder/model-00001-of-00003.safetensors", + "byteSize": 4992603592, + "sha256": "e7a37fef2b11c87a9a2a43c26fcd7199c1d1b203ef11e10863a56611c8fdb57f" + }, + { + "path": "text_encoder/model-00002-of-00003.safetensors", + "byteSize": 4983442512, + "sha256": "49da6c85ed4ff10f7f1afaa5a37a0b292fdb58aa855916ef584d697155a9bf58" + }, + { + "path": "text_encoder/model-00003-of-00003.safetensors", + "byteSize": 481353352, + "sha256": "1a6361ba65805092625aa4e2b3b8d42b810ab836682ddc8277088c9a251b4084" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 22740, + "gitBlobId": "3bb75fd2ea5328d9d43839a0bd4f8940c1174d92" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 636, + "gitBlobId": "8d6368f7e735fbe4781bf6e956b7c6ad0586df80" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 34362429, + "sha256": "7794135caa3ea73918949c902a781cc61dab674a4b59c17d85931c77c1114cbd" + }, + { + "path": "tokenizer/tokenizer.model", + "byteSize": 4241003, + "sha256": "61a7b147390c64585d6c3543dd6fc636906c9af3865a5548f27f31aee1d4c8e2" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 46409, + "gitBlobId": "a40f25378d53abfc8b4fab5226b202f8f1aa41ca" + }, + { + "path": "transformer/config.json", + "byteSize": 252, + "gitBlobId": "e886758a2b50f72dcf15bec2bbc0c78f94f3160a" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 4682786544, + "sha256": "73ce9d3ab7ad9cc798d50963739aaac13c2b89f5212e12acbf2a03977959cdc5" + }, + { + "path": "vae/config.json", + "byteSize": 825, + "gitBlobId": "3d657e7d773eaf188cea3dd9896cc682a3438bed" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 335306212, + "sha256": "8c717328c8ad41faab2ccfd52ae17332505c6833cf176aad56e7b58f2c4d4c94" + } + ] + }, + { + "repo": "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", + "revision": "e102b3591cc82e97071b8b4cb90d834d0c487207", + "checkedAt": "2026-09-24T01:43:27.906Z", + "source": "https://huggingface.co/api/models/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/revision/e102b3591cc82e97071b8b4cb90d834d0c487207", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1574, + "gitBlobId": "093d91771d98ed4a77cb4783ad12839878f74c07" + }, + { + "path": "README.md", + "byteSize": 5509, + "gitBlobId": "02223649612d2d13a967955fd08d7fb39b970f8e" + }, + { + "path": "model_index.json", + "byteSize": 400, + "gitBlobId": "3b2d29b560712a0dda616c841965e91ec62fccea" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 655, + "gitBlobId": "5ea179c71754d1edbb1e2772820a56a077ac0a76" + }, + { + "path": "text_encoder/config.json", + "byteSize": 747, + "gitBlobId": "f8bb31637b0367f1f52a6fcd015a22b45729cb12" + }, + { + "path": "text_encoder/model-00001-of-00002.safetensors", + "byteSize": 9989150328, + "sha256": "eb529f693f4b17773a24e787fcba29486d5e1700dadcc20bb91e4c8b00212d08" + }, + { + "path": "text_encoder/model-00002-of-00002.safetensors", + "byteSize": 9060119392, + "sha256": "58f33f4acba7a5502b6b3e4cb7863f8fd503d5012016428099bb460c98e2fe77" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 19886, + "gitBlobId": "4065a5b676f48d0128f6f2563c2dfc7e2bd4f568" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 2201, + "gitBlobId": "2c19eb6e3b583f52d34b903b5978d3d30b6b7682" + }, + { + "path": "tokenizer/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 2349, + "gitBlobId": "e99a0d1df1d854da2da3e68e80a83ee379dff248" + }, + { + "path": "transformer/config.json", + "byteSize": 785, + "gitBlobId": "da55b34f7684ae762732eb9ca5ecf2fb4d9f7b4a" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 2443492488, + "sha256": "2fa4dee9229c02b03163f57bdb8e80c7a5ee364b7161796abe9c05e8dd13f239" + }, + { + "path": "vae/config.json", + "byteSize": 631, + "gitBlobId": "a0fc2b4620b60f1e599418c4de1c3d5e45db875a" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 334643238, + "sha256": "1b909373b28f2137098b0fd9dbc6f97f8410854f31f84ddc9fa04b077b0ace2c" + } + ] + }, + { + "repo": "prs-eth/marigold-depth-lcm-v1-0", + "revision": "04a73502f7fd8fc5e59947b9df3b2266d71d6849", + "checkedAt": "2026-09-24T01:43:40.157Z", + "source": "https://huggingface.co/api/models/prs-eth/marigold-depth-lcm-v1-0/revision/04a73502f7fd8fc5e59947b9df3b2266d71d6849", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1561, + "gitBlobId": "f6b1f326ca4ab7cf0c8798856f8fe0020ff82d58" + }, + { + "path": "README.md", + "byteSize": 5845, + "gitBlobId": "ed03d8adc028c609851b72d2b753aeb18af74831" + }, + { + "path": "model_index.json", + "byteSize": 533, + "gitBlobId": "1d0f5b0f95dae7a06f4e196d132924382fa8a9cb" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 568, + "gitBlobId": "66e06da2e5c3690bbe60177507ae7a0edbb96380" + }, + { + "path": "text_encoder/config.json", + "byteSize": 633, + "gitBlobId": "9c60528fdcb99a7caf834426a94ea13c56cf422b" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 1361597016, + "sha256": "1e4aa519f64dc6386f88221a66c106a09fa027b47a20cc0e126687695f2a6669" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 460, + "gitBlobId": "ae0c5be6f35217e51c4c000fd325d8de0294e99c" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 824, + "gitBlobId": "f4fe219b936c0e171504b4bba0c33c7bef6ea211" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "unet/config.json", + "byteSize": 1852, + "gitBlobId": "5ef1846fef695062b68b5f6fff6f2a68708ef9a5" + }, + { + "path": "unet/diffusion_pytorch_model.safetensors", + "byteSize": 3463772592, + "sha256": "b29dbc05cd2af1ed20492e0c84b323747231dd7bbe52b5f7309f5ecc487e4153" + }, + { + "path": "vae/config.json", + "byteSize": 611, + "gitBlobId": "f97af6a6a8235236b1346312f328569ce2d70f81" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 334643276, + "sha256": "a1d993488569e928462932c8c38a0760b874d166399b14414135bd9c42df5815" + } + ] + }, + { + "repo": "Qwen/Qwen-Image-2.1", + "revision": "790c92633540aa0cb11d9abf19eb46d861714758", + "checkedAt": "2026-09-24T01:43:28.190Z", + "source": "https://huggingface.co/api/models/Qwen/Qwen-Image-2.1/revision/790c92633540aa0cb11d9abf19eb46d861714758", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1630, + "gitBlobId": "7236eee3e26bfd60df2cd50255284a1c1716274a" + }, + { + "path": "LICENSE", + "byteSize": 7831, + "gitBlobId": "13ae08d5a5828f508cf0e852b1b316c0c0bbb9b3" + }, + { + "path": "README.md", + "byteSize": 5358, + "gitBlobId": "df7f635aeb0358a45427d74610535dd1dc0b992d" + }, + { + "path": "model_index.json", + "byteSize": 447, + "gitBlobId": "85d02d05f150cbe24a8fd3542062b565767bbe7b" + }, + { + "path": "processor/added_tokens.json", + "byteSize": 707, + "gitBlobId": "b54f9135e44c1e81047e8d05cb027af8bc039eed" + }, + { + "path": "processor/chat_template.jinja", + "byteSize": 5292, + "gitBlobId": "124386803f142761528f710e77ae483f5f8c4fc4" + }, + { + "path": "processor/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 782, + "gitBlobId": "2fa6553554f69b4e3a6b742a75264ff9dff90ad0" + }, + { + "path": "processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 5445, + "gitBlobId": "fec7f182135c384a08ea2fc9635566ca79c45afc" + }, + { + "path": "processor/video_preprocessor_config.json", + "byteSize": 817, + "gitBlobId": "e32b1d90356f74457edbfca27aa1e121dab7cdc7" + }, + { + "path": "processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 485, + "gitBlobId": "45cd05b021892b1cafbd16d61ce692637e88bedd" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1517, + "gitBlobId": "581ce4655dde5272e577065dd08b1fda7e97d6e8" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 213, + "gitBlobId": "bdb4e0372b04220c80df250f2b3354d1c81c97cb" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4998056552, + "sha256": "dde00291b5f7fb92013895310a3da0ddba78674df9f10d505d375243dc01fc6f" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4915962464, + "sha256": "9047faccc0a6d98496a52d55f27be1c94a9c259d1e283fbea0128d054a948d42" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4915962496, + "sha256": "8c54187654c0176b73ae73785bf791dc9a14c9df7fb4310083a09d42048cb57e" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 2704357976, + "sha256": "5311532aaaeae3259eb6a7b2c600636be1159adf7ded35f53579f7d0e7d43cdd" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 67795, + "gitBlobId": "6d4d74c934b05b8addb0744305eef3674650cd8b" + }, + { + "path": "transformer/config.json", + "byteSize": 370, + "gitBlobId": "706df60672976086089e99f6595037f917d7602e" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00002.safetensors", + "byteSize": 9968332504, + "sha256": "9e6bc2d641e67bf277895ea8777141044a38f3edb7101bc469b2961dd7c36b4b" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00002.safetensors", + "byteSize": 4261951904, + "sha256": "3aaf234dcbe128530479735854a346b5e3e66283b7c11db56f836bbd1c13ebaa" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 30283, + "gitBlobId": "bd64579cfd093a6a4ea8fb492217790018c53c0d" + }, + { + "path": "vae/config.json", + "byteSize": 2079, + "gitBlobId": "ac6fe795d898a5eff16e4527256c2d039f5b0d4b" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 1350989512, + "sha256": "a07a1b7c4ee2966a1b3bdc37de9b4f983d56937e46619f709a80b6e490675417" + } + ] + }, + { + "repo": "Qwen/Qwen-Image-2512", + "revision": "25468b98e3276ca6700de15c6628e51b7de54a26", + "checkedAt": "2026-09-24T01:43:28.448Z", + "source": "https://huggingface.co/api/models/Qwen/Qwen-Image-2512/revision/25468b98e3276ca6700de15c6628e51b7de54a26", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "README.md", + "byteSize": 19306, + "gitBlobId": "fd3b726ae83e1c439561323a560c18437cafe6ff" + }, + { + "path": "model_index.json", + "byteSize": 443, + "gitBlobId": "2668376cf1a2476d7e8865a05202f1c685bf3ed7" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 485, + "gitBlobId": "f400c338630cdaa261c6575648f374eba547b9c6" + }, + { + "path": "text_encoder/config.json", + "byteSize": 3146, + "gitBlobId": "52b1556c2927e08c13529f2a107b08c87c7a4e65" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 244, + "gitBlobId": "5fdb82f3b3f12d83074ce7b45e330d8ee4efb7d7" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4968243304, + "sha256": "d725335e4ea2399be706469e4b8807716a8fa64bd03468252e9f7acf2415fee4" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4991495816, + "sha256": "b1830db6908dcc76df3a71492acbcf2b8cac130114cf1f3c2d9edae8de8c6de3" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4932751040, + "sha256": "09c1807c6d00d7cab94f7db39d4c02ebb8537225ccde383861ac48db97945aa6" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 1691924384, + "sha256": "5dd068336d14d45ffb43cef374d286cc6ba9d8741b028f90a7d040d847961f4a" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 57655, + "gitBlobId": "92b467639266f4aa72a9b5ab73dbdf5a1f936ee4" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 2427, + "gitBlobId": "28028c056af412405debd878cdda0171e35fa5d1" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 4686, + "gitBlobId": "eaed590d62aaf0ba31e284b66ddcb18222f066c2" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 3383407, + "gitBlobId": "6bce3a0a3866c4791a74d83d78f6824c3af64ec3" + }, + { + "path": "transformer/config.json", + "byteSize": 339, + "gitBlobId": "00304875dc8c28425c2c60170ab7654cb1d1137b" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00009.safetensors", + "byteSize": 4989364312, + "sha256": "1abb592dfb6701994f11dbbe1228d99fef1246e2cdd119a73e030da7c2b4d795" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00009.safetensors", + "byteSize": 4984214160, + "sha256": "f725018bd0b6b54a1529b38f20d38840b69aceefe843b443949e0e035799e88b" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00009.safetensors", + "byteSize": 4946470000, + "sha256": "3de6b71c7d756b4ead46c475f02bc27ff9efe64c43ce2b6790afb9fea81d821b" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00009.safetensors", + "byteSize": 4984213736, + "sha256": "c6877663a33e453e3e7fb73d1c6d006612a3e7b29d6a9f84d697383015e2f4b8" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00009.safetensors", + "byteSize": 4946471896, + "sha256": "31ce58f75ddfaa3851a95397ae5b22e8e435ded5f021dd7d903e138e12586d2e" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00009.safetensors", + "byteSize": 4946451560, + "sha256": "e8c1521290054c832e50086e65e50222e8e9b85cefb595667bc82ee091854772" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00009.safetensors", + "byteSize": 4908690520, + "sha256": "33847ad316bdc789f4b0c522cd0c388a025c6254e6e5b3f9626a6afe175655d3" + }, + { + "path": "transformer/diffusion_pytorch_model-00008-of-00009.safetensors", + "byteSize": 4984232856, + "sha256": "770716dc6e0c7007c2e8fba297f075cae23b45985a4cda3ec4dae5f7b559c03c" + }, + { + "path": "transformer/diffusion_pytorch_model-00009-of-00009.safetensors", + "byteSize": 1170918840, + "sha256": "3f8213dfaaf5aacba65f42c33bc77bb5926506755b1503fa6fa323750088fc0c" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 198887, + "gitBlobId": "056fddcb79864327ada8808ab3b584b6f484355d" + }, + { + "path": "vae/config.json", + "byteSize": 730, + "gitBlobId": "ebc479e2fe5a2265f4bcaf1e1073df64a2d4e33e" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "0c8bc8b758c649abef9ea407b95408389a3b2f610d0d10fcb054fe171d0a8344" + } + ] + }, + { + "repo": "Qwen/Qwen-Image-Edit", + "revision": "ac7f9318f633fc4b5778c59367c8128225f1e3de", + "checkedAt": "2026-09-24T01:43:28.716Z", + "source": "https://huggingface.co/api/models/Qwen/Qwen-Image-Edit/revision/ac7f9318f633fc4b5778c59367c8128225f1e3de", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "b2ef89be59c5792bf037a43d1bb69d922d28e9d8" + }, + { + "path": "README.md", + "byteSize": 11747, + "gitBlobId": "69ae090e3c5e0b66fc2a7c9113d10a92e7bf655c" + }, + { + "path": "model_index.json", + "byteSize": 512, + "gitBlobId": "cd01879ca0486fc4cb10eb9f52d3eee3f693ad1b" + }, + { + "path": "processor/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "processor/chat_template.jinja", + "byteSize": 1017, + "gitBlobId": "6c226632394ae7474b0d4b13e15793eac2e21ee9" + }, + { + "path": "processor/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 788, + "gitBlobId": "14a710470a1f4f12724cd5ab906671939e28d1b7" + }, + { + "path": "processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 11421896, + "sha256": "9c5ae00e602b8860cbd784ba82a8aa14e8feecec692e7076590d014d7b7fdafa" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 4727, + "gitBlobId": "203eb2267a9b927c93a97b67990818164bf3c692" + }, + { + "path": "processor/video_preprocessor_config.json", + "byteSize": 904, + "gitBlobId": "5258b252856504563ff501bbeaf5204ad5d4bc4d" + }, + { + "path": "processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 485, + "gitBlobId": "f4cc4fffa2878626fc807836f4eb5b0c03434e9a" + }, + { + "path": "text_encoder/config.json", + "byteSize": 3217, + "gitBlobId": "ea9e555296603c10d7252174f6b86032de4b73c9" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 244, + "gitBlobId": "42f92ad18683e0865060d227914ecfc77c57a5bf" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4968243304, + "sha256": "d725335e4ea2399be706469e4b8807716a8fa64bd03468252e9f7acf2415fee4" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4991495816, + "sha256": "b1830db6908dcc76df3a71492acbcf2b8cac130114cf1f3c2d9edae8de8c6de3" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4932751040, + "sha256": "09c1807c6d00d7cab94f7db39d4c02ebb8537225ccde383861ac48db97945aa6" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 1691924384, + "sha256": "5dd068336d14d45ffb43cef374d286cc6ba9d8741b028f90a7d040d847961f4a" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 57655, + "gitBlobId": "92b467639266f4aa72a9b5ab73dbdf5a1f936ee4" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 2427, + "gitBlobId": "28028c056af412405debd878cdda0171e35fa5d1" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 4686, + "gitBlobId": "eaed590d62aaf0ba31e284b66ddcb18222f066c2" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 3383407, + "gitBlobId": "6bce3a0a3866c4791a74d83d78f6824c3af64ec3" + }, + { + "path": "transformer/config.json", + "byteSize": 339, + "gitBlobId": "9aa7e5ceb2ca987927ccc5224e687e07063cc09e" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00009.safetensors", + "byteSize": 4989364312, + "sha256": "2003cdd961d3a94897f70a49c4cda975e9f92dd938adfeeba4f335f695570310" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00009.safetensors", + "byteSize": 4984214160, + "sha256": "d20abf545941bbffe26e201276226332e6ad6a2d3d7a7a18fb45ec7006fcdc8a" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00009.safetensors", + "byteSize": 4946470000, + "sha256": "1e4d15c80d388b630a97347694914cbc58ea9dc40e472305add52981ca02e856" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00009.safetensors", + "byteSize": 4984213736, + "sha256": "e5baa4c61fbce2011ea179122a8dc4bfaefb5387feeee5eff765fb237977cbd1" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00009.safetensors", + "byteSize": 4946471896, + "sha256": "c08a2489c7e11f09c9fd4aae929085a31199699f1fa4a2dd95fc65d1ab5c223c" + }, + { + "path": "transformer/diffusion_pytorch_model-00006-of-00009.safetensors", + "byteSize": 4946451560, + "sha256": "901dfafc0de496f358e27b81af4155b889080aebd4417909303c70a260885fff" + }, + { + "path": "transformer/diffusion_pytorch_model-00007-of-00009.safetensors", + "byteSize": 4908690520, + "sha256": "e8d9a14e343d841e425bb4f68c4be44b96d94730d328a175f4549bdde9114c96" + }, + { + "path": "transformer/diffusion_pytorch_model-00008-of-00009.safetensors", + "byteSize": 4984232856, + "sha256": "f57b82e08f23a33af5a2a6dbf586180b4f5c9e69487d49112544ca5e8fe0d94e" + }, + { + "path": "transformer/diffusion_pytorch_model-00009-of-00009.safetensors", + "byteSize": 1170918840, + "sha256": "b068de3d8644da55bc75f156ba50cd374f397f2fc49c8e8e00c0392db7cd3a70" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 198887, + "gitBlobId": "056fddcb79864327ada8808ab3b584b6f484355d" + }, + { + "path": "vae/config.json", + "byteSize": 730, + "gitBlobId": "7107dc5ba335acf886f86541d4ea462a1e097f8a" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "0c8bc8b758c649abef9ea407b95408389a3b2f610d0d10fcb054fe171d0a8344" + } + ] + }, + { + "repo": "Qwen/Qwen-Image-Edit-2511", + "revision": "6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9", + "checkedAt": "2026-09-24T01:43:28.991Z", + "source": "https://huggingface.co/api/models/Qwen/Qwen-Image-Edit-2511/revision/6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "b2ef89be59c5792bf037a43d1bb69d922d28e9d8" + }, + { + "path": "README.md", + "byteSize": 7179, + "gitBlobId": "277c39d3f5bb2c3908b435727e80c98af75f934a" + }, + { + "path": "model_index.json", + "byteSize": 516, + "gitBlobId": "a2e083f28f4f6c958c08f3882c89038b8f6da872" + }, + { + "path": "processor/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "processor/chat_template.jinja", + "byteSize": 1017, + "gitBlobId": "6c226632394ae7474b0d4b13e15793eac2e21ee9" + }, + { + "path": "processor/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 826, + "gitBlobId": "9a24c5165e5de348ba27d0d00dd52b27c6044ab2" + }, + { + "path": "processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 11421896, + "sha256": "9c5ae00e602b8860cbd784ba82a8aa14e8feecec692e7076590d014d7b7fdafa" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 4727, + "gitBlobId": "203eb2267a9b927c93a97b67990818164bf3c692" + }, + { + "path": "processor/video_preprocessor_config.json", + "byteSize": 910, + "gitBlobId": "ef8f979d86a2255c8fc115327eefce152e5a7cf3" + }, + { + "path": "processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 485, + "gitBlobId": "f400c338630cdaa261c6575648f374eba547b9c6" + }, + { + "path": "text_encoder/config.json", + "byteSize": 3146, + "gitBlobId": "52b1556c2927e08c13529f2a107b08c87c7a4e65" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 244, + "gitBlobId": "5fdb82f3b3f12d83074ce7b45e330d8ee4efb7d7" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4968243304, + "sha256": "d725335e4ea2399be706469e4b8807716a8fa64bd03468252e9f7acf2415fee4" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4991495816, + "sha256": "b1830db6908dcc76df3a71492acbcf2b8cac130114cf1f3c2d9edae8de8c6de3" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4932751040, + "sha256": "09c1807c6d00d7cab94f7db39d4c02ebb8537225ccde383861ac48db97945aa6" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 1691924384, + "sha256": "5dd068336d14d45ffb43cef374d286cc6ba9d8741b028f90a7d040d847961f4a" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 57655, + "gitBlobId": "92b467639266f4aa72a9b5ab73dbdf5a1f936ee4" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "tokenizer/chat_template.jinja", + "byteSize": 2427, + "gitBlobId": "28028c056af412405debd878cdda0171e35fa5d1" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 4686, + "gitBlobId": "eaed590d62aaf0ba31e284b66ddcb18222f066c2" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 3383407, + "gitBlobId": "6bce3a0a3866c4791a74d83d78f6824c3af64ec3" + }, + { + "path": "transformer/config.json", + "byteSize": 362, + "gitBlobId": "199398df180d275eee9da066489f9bb6e2b97609" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00005.safetensors", + "byteSize": 9973578592, + "sha256": "2a0c30c9ba44a5f11c21ca139e37951430bbde814ff4e0b5b1a68b80530e7a1a" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00005.safetensors", + "byteSize": 9987326072, + "sha256": "54ec249b07b4376e19cf16b764054f03ca03ae2cfbd9939453e2085f4e9bd259" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00005.safetensors", + "byteSize": 9987307440, + "sha256": "c55157843525653161e8f6af5acc670ba3aceff04284f7cf657199d24d065e16" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00005.safetensors", + "byteSize": 9930685712, + "sha256": "ffcfb5a4895702635890a67bad183591e0ae515d794bdcb26e217b27a7f6d12d" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00005.safetensors", + "byteSize": 982130472, + "sha256": "2b2556b736629e10a5a0dfa14606f2057f4f81c2ba53f94103682c7ac42d4940" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 198887, + "gitBlobId": "2fdb0449e2b2299b77e056c95b1a7caec5a3f438" + }, + { + "path": "vae/config.json", + "byteSize": 730, + "gitBlobId": "ebc479e2fe5a2265f4bcaf1e1073df64a2d4e33e" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253806966, + "sha256": "0c8bc8b758c649abef9ea407b95408389a3b2f610d0d10fcb054fe171d0a8344" + } + ] + }, + { + "repo": "Qwen/Qwen-Image-Layered", + "revision": "8f0ca708dfff6ba1dd5f2d85d78f8c108a040bcf", + "checkedAt": "2026-09-24T01:43:29.254Z", + "source": "https://huggingface.co/api/models/Qwen/Qwen-Image-Layered/revision/8f0ca708dfff6ba1dd5f2d85d78f8c108a040bcf", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "b2ef89be59c5792bf037a43d1bb69d922d28e9d8" + }, + { + "path": "README.md", + "byteSize": 5492, + "gitBlobId": "0dff092a61139502c1a103818cc8ed9274ca5077" + }, + { + "path": "model_index.json", + "byteSize": 515, + "gitBlobId": "45cfa3ce3fa644ed513bab7fde32dc1c4beb5ae0" + }, + { + "path": "processor/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "processor/chat_template.json", + "byteSize": 1049, + "gitBlobId": "13303be6f396b038cf397b34a8096819d403836b" + }, + { + "path": "processor/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 572, + "gitBlobId": "6775a5f42a55a6562cc312e44d38ffa6e6bca1f9" + }, + { + "path": "processor/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 11421896, + "sha256": "9c5ae00e602b8860cbd784ba82a8aa14e8feecec692e7076590d014d7b7fdafa" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 7297, + "gitBlobId": "069efae42af549e7f671f13ee55d94c2beced4c9" + }, + { + "path": "processor/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 482, + "gitBlobId": "a8cc26658324461daaf5c901e8ae19961fe30f92" + }, + { + "path": "text_encoder/config.json", + "byteSize": 1489, + "gitBlobId": "e87ef729d45c383344ce71ddebd49594c13412d3" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 244, + "gitBlobId": "c8359985ed38370e68f9ff1b7986b71b00b1b342" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4968243304, + "sha256": "d725335e4ea2399be706469e4b8807716a8fa64bd03468252e9f7acf2415fee4" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4991495816, + "sha256": "b1830db6908dcc76df3a71492acbcf2b8cac130114cf1f3c2d9edae8de8c6de3" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4932751040, + "sha256": "09c1807c6d00d7cab94f7db39d4c02ebb8537225ccde383861ac48db97945aa6" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 1691924384, + "sha256": "5dd068336d14d45ffb43cef374d286cc6ba9d8741b028f90a7d040d847961f4a" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 57619, + "gitBlobId": "6a84000f74f7809388c808edf0c022f71e09b5ff" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 605, + "gitBlobId": "482ced4679301bf287ebb310bdd1790eb4514232" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 613, + "gitBlobId": "ac23c0aaa2434523c494330aeb79c58395378103" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 7256, + "gitBlobId": "df3a9d96759529ca1006eb6db024bbb099a97578" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 3383407, + "gitBlobId": "6bce3a0a3866c4791a74d83d78f6824c3af64ec3" + }, + { + "path": "transformer/config.json", + "byteSize": 424, + "gitBlobId": "aae60c80c9d9e07c1080866bbd417cc1cbd0a7f9" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00005.safetensors", + "byteSize": 9973590992, + "sha256": "87ae7a48b40c19a5892561a7a85db6b0ecdd7511d7f22c5e3858e9b3d989f0b7" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00005.safetensors", + "byteSize": 9987326072, + "sha256": "e6c0ceb312b751cdbe223df67c1418d9034842498366259839c0be9d3823e785" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00005.safetensors", + "byteSize": 9987307440, + "sha256": "06209de9a20b09e8614cea33f977d5f1b11971d830bffaf62d699a72910d434f" + }, + { + "path": "transformer/diffusion_pytorch_model-00004-of-00005.safetensors", + "byteSize": 9930685712, + "sha256": "24170259dd8c0f6720cae06a545108125f4fa41079f12f49ac3ce05d16806f85" + }, + { + "path": "transformer/diffusion_pytorch_model-00005-of-00005.safetensors", + "byteSize": 982130472, + "sha256": "54095afda4f5b117d66bb6db14123b069678aeff7c665f485524c4fea5e8c1ce" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 198992, + "gitBlobId": "672706ed7c6ef81d1474081f0f57f74ea441f757" + }, + { + "path": "vae/config.json", + "byteSize": 753, + "gitBlobId": "9ca8f79d0fdc3c86f7b4d0d896052b655b4f53b3" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 253817336, + "sha256": "06520463778e64dca1039c7447890065ee220bc408d15412182e5c3e06f304f1" + } + ] + }, + { + "repo": "Shitao/OmniGen-v1-diffusers", + "revision": "016e2f61d12a98303f6bbdf122687694d7984268", + "checkedAt": "2026-09-24T01:43:29.519Z", + "source": "https://huggingface.co/api/models/Shitao/OmniGen-v1-diffusers/revision/016e2f61d12a98303f6bbdf122687694d7984268", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "README.md", + "byteSize": 24, + "gitBlobId": "7be5fc7f47d5db027d120b8024982df93db95b74" + }, + { + "path": "model_index.json", + "byteSize": 348, + "gitBlobId": "41221448a4163fe3b457f4a96dd41996f0d989e8" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 389, + "gitBlobId": "138a49e24b0c08dfbb49990d268389e3e0c883bd" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 670, + "gitBlobId": "0616f20a3af0830d0991f72c15c4f5e6d5dc02fa" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 3628342, + "gitBlobId": "0be3442091b2f555401c41479e4e04e7465a3414" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 10124, + "gitBlobId": "435799db07cda77d43824bf4e8af36e2301ce983" + }, + { + "path": "transformer/config.json", + "byteSize": 3017, + "gitBlobId": "aa075fd56b5b2a7064197bc4fe41565442cd30f1" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 7750667816, + "sha256": "da89cdbea388e9019c74cff11421cbbef62be172176295fb69590d54c1420b7e" + }, + { + "path": "vae/config.json", + "byteSize": 907, + "gitBlobId": "3d24d1d91e85090b5697ef89ff85cc8bb07f8fe1" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 334643268, + "sha256": "1598f3d24932bcfe6634e8b618ea1e30ab1d57f5aad13a6d2de446d2199f2341" + } + ] + }, + { + "repo": "SimianLuo/LCM_Dreamshaper_v7", + "revision": "a85df6a8bd976cdd08b4fd8f3b73f229c9e54df5", + "checkedAt": "2026-09-24T01:43:29.816Z", + "source": "https://huggingface.co/api/models/SimianLuo/LCM_Dreamshaper_v7/revision/a85df6a8bd976cdd08b4fd8f3b73f229c9e54df5", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1566, + "gitBlobId": "fab060b15d0097da52ca66091aaa7d0a8d2d44e0" + }, + { + "path": "README.md", + "byteSize": 3504, + "gitBlobId": "e752797979ab345ee8ee491974628616d74b3e9e" + }, + { + "path": "feature_extractor/preprocessor_config.json", + "byteSize": 518, + "gitBlobId": "0d9d33b883843d1b370da781f3943051067e1b2c" + }, + { + "path": "model_index.json", + "byteSize": 588, + "gitBlobId": "9ba2f4ade074e387800e8bb3bcf99d2f5c6eb18f" + }, + { + "path": "safety_checker/config.json", + "byteSize": 726, + "gitBlobId": "3b01fe56facd4f328bcbfc0be648e993f8e7e522" + }, + { + "path": "safety_checker/model.safetensors", + "byteSize": 1215979664, + "sha256": "fb351a5ded815c3ff744968ad9c6b218d071b9d313d04f35e813b84b4c0ffde8" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 539, + "gitBlobId": "051647709698c2ca7515008834d13ccc7d864d7a" + }, + { + "path": "text_encoder/config.json", + "byteSize": 610, + "gitBlobId": "d6206aa793fe9d4f02ff659a9ae0c53c6fa0148c" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 492265168, + "sha256": "66262104f5099c84ad0e6ec156acae57b2292caebd4d7b8699327fa745145b76" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 133, + "gitBlobId": "d073e26c77c9d2dcb62c109b4488565af35fb5bb" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 765, + "gitBlobId": "ea4f9b67199070656e4324611f5dd9dd936c92e9" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "unet/config.json", + "byteSize": 1734, + "gitBlobId": "f7bddbd01fc84c298b8268e8a417e2597969145f" + }, + { + "path": "unet/diffusion_pytorch_model.safetensors", + "byteSize": 3438495328, + "sha256": "7be468469c513f8614a73423aa009581295fbd8c903703450632b2275c0145a9" + }, + { + "path": "vae/config.json", + "byteSize": 651, + "gitBlobId": "b06f3f4a908938c7733e205c486a15eb0eaf4553" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 334643268, + "sha256": "6bb865b3478d73053007b8a76f114010e26f1a1d2ea02dc0261464caa4289c2d" + } + ] + }, + { + "repo": "stabilityai/sdxl-turbo", + "revision": "71153311d3dbb46851df1931d3ca6e939de83304", + "checkedAt": "2026-09-24T01:43:40.455Z", + "source": "https://huggingface.co/api/models/stabilityai/sdxl-turbo/revision/71153311d3dbb46851df1931d3ca6e939de83304", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "LICENSE.md", + "byteSize": 11852, + "gitBlobId": "1d9ce2ee1067327543544de197291726e4fc57a4" + }, + { + "path": "README.md", + "byteSize": 5883, + "gitBlobId": "fc4ec2bdac39edd491815f89a853a3895d36f1ca" + }, + { + "path": "model_index.json", + "byteSize": 685, + "gitBlobId": "f857cf2f828fff2ee319b1a47e6ce820e8affb9d" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 459, + "gitBlobId": "0359d7abb0b9c7b4a433be2db87cefea03c06ea5" + }, + { + "path": "text_encoder/config.json", + "byteSize": 565, + "gitBlobId": "8e91c97936ad0b2c1356f03de8d47589b5232704" + }, + { + "path": "text_encoder/model.fp16.safetensors", + "byteSize": 246144152, + "sha256": "660c6f5b1abae9dc498ac2d21e1347d2abdb0cf6c0c0c8576cd796491d9a6cdd" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 575, + "gitBlobId": "f9e084535c55110233f44ccc6c7f9d0e1540f8be" + }, + { + "path": "text_encoder_2/model.fp16.safetensors", + "byteSize": 1389382176, + "sha256": "ec310df2af79c318e24d20511b601a591ca8cd4f1fce1d8dff822a356bcdb1f4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 586, + "gitBlobId": "1f467f3b057c46a21f88d9ab2f1070af9916c78c" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 704, + "gitBlobId": "1bf819b6621086dc92428e2c9c8bbab39211fd55" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 460, + "gitBlobId": "ae0c5be6f35217e51c4c000fd325d8de0294e99c" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 855, + "gitBlobId": "bd2abe19377557ff5771584921f9b65fa041fef0" + }, + { + "path": "tokenizer_2/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "unet/config.json", + "byteSize": 1776, + "gitBlobId": "220d7ae3e59ce3484c7eeb47ef2ac9db5097e29a" + }, + { + "path": "unet/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 5135149760, + "sha256": "48fa46161a745f48d4054df3fe13804ee255486bca893403b60373c188fd1bdb" + }, + { + "path": "vae/config.json", + "byteSize": 607, + "gitBlobId": "ae14cf90e29b12134a53383691c98c73dee5d422" + }, + { + "path": "vae/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 167335342, + "sha256": "02ee4bd18e5d16e7fe5fc5b85b4aefa2cba6db28897f674226c9d6ddd2f34f06" + } + ] + }, + { + "repo": "stabilityai/stable-diffusion-xl-base-1.0", + "revision": "462165984030d82259a11f4367a4eed129e94a7b", + "checkedAt": "2026-09-24T01:43:40.713Z", + "source": "https://huggingface.co/api/models/stabilityai/stable-diffusion-xl-base-1.0/revision/462165984030d82259a11f4367a4eed129e94a7b", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1562, + "gitBlobId": "7691f136a2661ab8fa1e9396a024016ae8aa4799" + }, + { + "path": "LICENSE.md", + "byteSize": 14109, + "gitBlobId": "af7e355e31c0d976933aa0d06759611151ad8c39" + }, + { + "path": "README.md", + "byteSize": 8668, + "gitBlobId": "3de812966870aa7122954891ce74faabc77335a9" + }, + { + "path": "model_index.json", + "byteSize": 609, + "gitBlobId": "6cc5138ddb67e9309c9b4e058e33e52087f1d215" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 479, + "gitBlobId": "e5bc8421e047838523be7acfb6720f167f7382f6" + }, + { + "path": "text_encoder/config.json", + "byteSize": 565, + "gitBlobId": "cde352ada4bb95fdad2fc503b8121257cef215a6" + }, + { + "path": "text_encoder/model.fp16.safetensors", + "byteSize": 246144152, + "sha256": "660c6f5b1abae9dc498ac2d21e1347d2abdb0cf6c0c0c8576cd796491d9a6cdd" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 575, + "gitBlobId": "da1848b5ed17b676f021578838f12c4023b86379" + }, + { + "path": "text_encoder_2/model.fp16.safetensors", + "byteSize": 1389382176, + "sha256": "ec310df2af79c318e24d20511b601a591ca8cd4f1fce1d8dff822a356bcdb1f4" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 472, + "gitBlobId": "2c2130b544c0c5a72d5d00da071ba130a9800fb2" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 737, + "gitBlobId": "2e8612a429492973fe60635b3f44a28b065cfac0" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "tokenizer_2/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 460, + "gitBlobId": "ae0c5be6f35217e51c4c000fd325d8de0294e99c" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 725, + "gitBlobId": "a8438e020c4497a429240d6b89e0bf9a6e2ffa92" + }, + { + "path": "tokenizer_2/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "unet/config.json", + "byteSize": 1680, + "gitBlobId": "c8714c90f0e2409156da42781954416cb7df36af" + }, + { + "path": "unet/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 5135149760, + "sha256": "83e012a805b84c7ca28e5646747c90a243c65c8ba4f070e2d7ddc9d74661e139" + }, + { + "path": "vae/config.json", + "byteSize": 642, + "gitBlobId": "a66a171ba7c8efb1a8fc3bdc64e65318eade8e13" + }, + { + "path": "vae/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 167335342, + "sha256": "bcb60880a46b63dea58e9bc591abe15f8350bde47b405f9c38f4be70c6161e68" + } + ] + }, + { + "repo": "stable-diffusion-v1-5/stable-diffusion-v1-5", + "revision": "451f4fe16113bff5a5d2269ed5ad43b0592e9a14", + "checkedAt": "2026-09-24T01:43:40.984Z", + "source": "https://huggingface.co/api/models/stable-diffusion-v1-5/stable-diffusion-v1-5/revision/451f4fe16113bff5a5d2269ed5ad43b0592e9a14", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1548, + "gitBlobId": "55d2855c5be698e0572b9f42af95f06bfd5fb002" + }, + { + "path": "README.md", + "byteSize": 14461, + "gitBlobId": "2a5ba19664aab2052c4ea484e737dff9aca1aa69" + }, + { + "path": "feature_extractor/preprocessor_config.json", + "byteSize": 342, + "gitBlobId": "5294955ff7801083f720b34b55d0f1f51313c5c5" + }, + { + "path": "model_index.json", + "byteSize": 541, + "gitBlobId": "daf7e2e2dfc64fb437a2b44525667111b00cb9fc" + }, + { + "path": "safety_checker/config.json", + "byteSize": 4723, + "gitBlobId": "5dbd88952e7e521aa665e5052e6db7def3641d03" + }, + { + "path": "safety_checker/model.fp16.safetensors", + "byteSize": 608018440, + "sha256": "08902f19b1cfebd7c989f152fc0507bef6898c706a91d666509383122324b511" + }, + { + "path": "safety_checker/model.safetensors", + "byteSize": 1215981830, + "sha256": "9d6a233ff6fd5ccb9f76fd99618d73369c52dd3d8222376384d0e601911089e8" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 308, + "gitBlobId": "82d05b0e688d7ea94675678646c427907419346e" + }, + { + "path": "text_encoder/config.json", + "byteSize": 617, + "gitBlobId": "4d3e873ab5086ad989f407abd50fdce66db8d657" + }, + { + "path": "text_encoder/model.fp16.safetensors", + "byteSize": 246144864, + "sha256": "77795e2023adcf39bc29a884661950380bd093cf0750a966d473d1718dc9ef4e" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 492265874, + "sha256": "d008943c017f0092921106440254dbbe00b6a285f7883ec8ba160c3faad88334" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 524619, + "gitBlobId": "76e821f1b6f0a9709293c3b6b51ed90980b3166b" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 472, + "gitBlobId": "2c2130b544c0c5a72d5d00da071ba130a9800fb2" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 806, + "gitBlobId": "5ba7bf706515bc60487ad0e1816b4929b82542d6" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 1059962, + "gitBlobId": "469be27c5c010538f845f518c4f5e8574c78f7c8" + }, + { + "path": "unet/config.json", + "byteSize": 743, + "gitBlobId": "1a02ee8abc93e840ffbcb2d68b66ccbcb74b3ab3" + }, + { + "path": "unet/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 1719125304, + "sha256": "c83908253f9a64d08c25fc90874c9c8aef9a329ce1ca5fb909d73b0c83d1ea21" + }, + { + "path": "unet/diffusion_pytorch_model.safetensors", + "byteSize": 3438167540, + "sha256": "19da7aaa4b880e59d56843f1fcb4dd9b599c28a1d9d9af7c1143057c8ffae9f1" + }, + { + "path": "vae/config.json", + "byteSize": 547, + "gitBlobId": "55d78924fee13e4220f24320127c5f16284e13b9" + }, + { + "path": "vae/diffusion_pytorch_model.fp16.safetensors", + "byteSize": 167335342, + "sha256": "4fbcf0ebe55a0984f5a5e00d8c4521d52359af7229bb4d81890039d2aa16dd7c" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 334643276, + "sha256": "a2b5134f4dbc140d9c11f11cba3233099e00af40f262f136c691fb7d38d2194c" + } + ] + }, + { + "repo": "Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers-Distilled", + "revision": "ba991d1546d8c50936c4c16398ed0a87b9b99fb1", + "checkedAt": "2026-09-24T01:43:30.090Z", + "source": "https://huggingface.co/api/models/Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers-Distilled/revision/ba991d1546d8c50936c4c16398ed0a87b9b99fb1", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "README.md", + "byteSize": 3812, + "gitBlobId": "f14781e27b25ff98a12db1898d7a9dd98b01d277" + }, + { + "path": "model_index.json", + "byteSize": 700, + "gitBlobId": "8553f834bfa38c7d2c56f3dc65d5e0d943f006c4" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 572, + "gitBlobId": "40e63298d71c4975e0ac423c78a80db851cf975f" + }, + { + "path": "text_encoder/config.json", + "byteSize": 840, + "gitBlobId": "49a263c94c555ebd2d2e3f866d38aed4032aed5c" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 1408188288, + "sha256": "c6c6348af2cb4d5852fe51102ce39605903dbe7925c005cf8995506cc21ea914" + }, + { + "path": "text_encoder_2/config.json", + "byteSize": 776, + "gitBlobId": "5d9f5f2314c1932c9dba328bcddeb8a42925556b" + }, + { + "path": "text_encoder_2/model-00001-of-00002.safetensors", + "byteSize": 4993585936, + "sha256": "2c0c539ab8e8fba3877cc94bc483e427f74c525f817a809b028ebc8d96d75a94" + }, + { + "path": "text_encoder_2/model-00002-of-00002.safetensors", + "byteSize": 1686275552, + "sha256": "3b49976cb1fe40da28a600d783f4686024c97eb01f224c54305cce55ddcd8a5e" + }, + { + "path": "text_encoder_2/model.safetensors.index.json", + "byteSize": 19885, + "gitBlobId": "b2eb1a3fb1d8809a06b9af8aaab746c9a35b468f" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 695, + "gitBlobId": "9bbecc17cabbcbd3112c14d6982b51403b264bfa" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 1241, + "gitBlobId": "c57897ebb275499b3c6d5284f1187860f86741e6" + }, + { + "path": "tokenizer/vocab.txt", + "byteSize": 316627, + "gitBlobId": "6246906805d02aca01714c71e4c8d77b69a7a131" + }, + { + "path": "tokenizer_2/special_tokens_map.json", + "byteSize": 416, + "gitBlobId": "05592dd7745eb8b9b7e19753208af804f6393e45" + }, + { + "path": "tokenizer_2/spiece.model", + "byteSize": 4309802, + "sha256": "ef78f86560d809067d12bac6c09f19a462cb3af3f54d2b8acbba26e1433125d6" + }, + { + "path": "tokenizer_2/tokenizer_config.json", + "byteSize": 861, + "gitBlobId": "3b6e6f54f337932574c05d1377c08aaa9d2062a0" + }, + { + "path": "transformer/config.json", + "byteSize": 540, + "gitBlobId": "36c538ea7c70fa3060bfdedfd8c8efab370b699c" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors", + "byteSize": 5999962656, + "sha256": "af0239f15b91e424160581963873466b0eb673993b952a16e5a54ae18d521a1c" + }, + { + "path": "vae/config.json", + "byteSize": 735, + "gitBlobId": "742efa052c5730728cb140208c25c73268749ad4" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 334643268, + "sha256": "98a14dc6fe8d71c83576f135a87c61a16561c9c080abba418d2cc976ee034f88" + } + ] + }, + { + "repo": "Tongyi-MAI/Z-Image-Turbo", + "revision": "f332072aa78be7aecdf3ee76d5c247082da564a6", + "checkedAt": "2026-09-24T01:43:30.406Z", + "source": "https://huggingface.co/api/models/Tongyi-MAI/Z-Image-Turbo/revision/f332072aa78be7aecdf3ee76d5c247082da564a6", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 2187, + "gitBlobId": "c88e3c116b124ca91c94af1ac21d6d0db7deaa58" + }, + { + "path": "README.md", + "byteSize": 13684, + "gitBlobId": "352aecd07acc46ad96d42ae6a7ef62f8f17172d5" + }, + { + "path": "model_index.json", + "byteSize": 467, + "gitBlobId": "570c63a7c5580f63d0a8ed622324f50015dcf914" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 173, + "gitBlobId": "2349bc43351afa730341d07dd44fce4ace1c4257" + }, + { + "path": "text_encoder/config.json", + "byteSize": 726, + "gitBlobId": "e49eccdc32f36da9c09cfa0e737084f9e0105e5e" + }, + { + "path": "text_encoder/generation_config.json", + "byteSize": 239, + "gitBlobId": "20a8a9156fc8c3f25295ca067f61fdf120d517c5" + }, + { + "path": "text_encoder/model-00001-of-00003.safetensors", + "byteSize": 3957900840, + "sha256": "328a91d3122359d5547f9d79521205bc0a46e1f79a792dfe650e99fc2d651223" + }, + { + "path": "text_encoder/model-00002-of-00003.safetensors", + "byteSize": 3987450520, + "sha256": "6cd087b316306a68c562436b5492edbcf6e16c6dba3a1308279caa5a58e21ca5" + }, + { + "path": "text_encoder/model-00003-of-00003.safetensors", + "byteSize": 99630640, + "sha256": "7ca841ee75b9c61267c0c6148fd8d096d3d21b6d3e161256a9b878154f91fc52" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 32819, + "gitBlobId": "95c0a0059df040d75dc6c396b174382cf61d2f91" + }, + { + "path": "tokenizer/merges.txt", + "byteSize": 1671853, + "gitBlobId": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 11422654, + "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 9732, + "gitBlobId": "417d038a63fa3de29cfde265caedae14d1a58d92" + }, + { + "path": "tokenizer/vocab.json", + "byteSize": 2776833, + "gitBlobId": "4783fe10ac3adce15ac8f358ef5462739852c569" + }, + { + "path": "transformer/config.json", + "byteSize": 473, + "gitBlobId": "c579190a5e03d602a2fd9647221c9d0d9441f150" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 9973693184, + "sha256": "95facd593e2549e8252acb571c653d57f7ddb7f1060d4e81712f152555a88804" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 9973714824, + "sha256": "a4bbe43ee184a1fb5af4b412d27555f532893bdc3165b1149e304ed82b5d7015" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 4672282880, + "sha256": "aba4e37a590e63210878160a718d916d80398f4e1f78ab6c9b2b2a00d92769fa" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 48969, + "gitBlobId": "a3cadc1ed28e334ef58d4e6bbda107e25168ef47" + }, + { + "path": "vae/config.json", + "byteSize": 805, + "gitBlobId": "894fa30ac1950cef422189d2d4cef11043c62875" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 167666902, + "sha256": "f5b59a26851551b67ae1fe58d32e76486e1e812def4696a4bea97f16604d40a3" + } + ] + }, + { + "repo": "zai-org/CogView3-Plus-3B", + "revision": "5d70e40732ac0efac98524c51a7fa9c82707f1e5", + "checkedAt": "2026-09-24T01:43:41.260Z", + "source": "https://huggingface.co/api/models/zai-org/CogView3-Plus-3B/revision/5d70e40732ac0efac98524c51a7fa9c82707f1e5", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1519, + "gitBlobId": "a6344aac8c09253b3b630fb776ae94478aa0275b" + }, + { + "path": "README.md", + "byteSize": 4006, + "gitBlobId": "90b8f0e44308193e923760e039f3f7beb08821cd" + }, + { + "path": "model_index.json", + "byteSize": 408, + "gitBlobId": "8d34ec6111e5251f3b1035734f6192cbeec3b5a6" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 482, + "gitBlobId": "10ec0dcddd4d592b2dc67b534778cc74540bb583" + }, + { + "path": "text_encoder/config.json", + "byteSize": 809, + "gitBlobId": "b6bbd0d035d3d08609d33a1ae5dd9fadd6e3ae92" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4989319680, + "sha256": "8e0e98242fed1de128a73ee6fee174aa30186c66f7d70811c8ebf67d72067f38" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4999830656, + "sha256": "7309a33cd1780b20e3d84b2e14585d7a64ccea45079aecc86c507e381e00ed14" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4865612720, + "sha256": "d278bf7cb4de9957cb67d8a92dc9273f88186f0c90e0f2a3de13882217d9a95c" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 4194506688, + "sha256": "69afce9a62402b82095baa1dfef572be92dfd9a4062693d49a5948e4d27585a5" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 19886, + "gitBlobId": "0900b8ebd70052d4cccc2eacf6e07d75bb1f9cb8" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 2593, + "gitBlobId": "3f5132007c4fcf42b75b65c8b6aa49c7098bcdf4" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 2543, + "gitBlobId": "17ade346a1042cbe0c1436f5bedcbd85c099d582" + }, + { + "path": "tokenizer/spiece.model", + "byteSize": 791656, + "sha256": "d60acb128cf7b7f2536e8f38a5b18a05535c9e14c7a355904270e15b0945ea86" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 20617, + "gitBlobId": "a087222b06b0c1fa806c3dac4686516961fb79ab" + }, + { + "path": "transformer/config.json", + "byteSize": 391, + "gitBlobId": "d9a52e5ae2a6da536cf45375f4a9bfe532252c7f" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00002.safetensors", + "byteSize": 4994474912, + "sha256": "f6227325d23f2e06c40b7f222322c1db36af8d0122d6f6cd99f395d63945ce7e" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00002.safetensors", + "byteSize": 703248424, + "sha256": "5e34e2a9eaf09ab752673a33d451cc0f5e5304cf32be7f98a6553a494fccecab" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 43943, + "gitBlobId": "94cdb8f14b09b806791fd88c33124f047f6ec182" + }, + { + "path": "vae/config.json", + "byteSize": 772, + "gitBlobId": "87a39344191468a2356913e9f78c0cba733876ff" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 812234342, + "sha256": "40aabe789e1591a3a3d8a2f598eda99f60a42275c410e7292a714b5270b97747" + } + ] + }, + { + "repo": "zai-org/CogView4-6B", + "revision": "63a52b7f6dace7033380cd6da14d0915eab3e6b5", + "checkedAt": "2026-09-24T01:43:41.537Z", + "source": "https://huggingface.co/api/models/zai-org/CogView4-6B/revision/63a52b7f6dace7033380cd6da14d0915eab3e6b5", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 2130, + "gitBlobId": "2ed04cb96882a669cff919b6ced57cbd63d7976b" + }, + { + "path": "LICENSE", + "byteSize": 11359, + "gitBlobId": "d5809e9e40aca5070d3ffae323ed1a3b00b9c347" + }, + { + "path": "README.md", + "byteSize": 7320, + "gitBlobId": "e9bfc0694d346e2d6aba2b130ea9c222c134bf9b" + }, + { + "path": "model_index.json", + "byteSize": 415, + "gitBlobId": "80ec80e3b210286d3d0f4b40ba1dc439ebc714a2" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 450, + "gitBlobId": "caa85b7328ec9fe43095768b100f1e488d93341b" + }, + { + "path": "text_encoder/config.json", + "byteSize": 725, + "gitBlobId": "56f2498f71cbf59af9876694259c6a9d33bc0f28" + }, + { + "path": "text_encoder/model-00001-of-00004.safetensors", + "byteSize": 4984132960, + "sha256": "d33a01dba14c62a0d5c471aba9f57902a1d6155080b2cf07e1360b9600fa60a8" + }, + { + "path": "text_encoder/model-00002-of-00004.safetensors", + "byteSize": 4895074376, + "sha256": "f2913605db189102456ad417a4c38903fb038b9e9fb2a96c95e2de56da224667" + }, + { + "path": "text_encoder/model-00003-of-00004.safetensors", + "byteSize": 4895074392, + "sha256": "f81bd5307c4f6175c7b080abad40eb869fafa263ff13c8550d6928330ae749a6" + }, + { + "path": "text_encoder/model-00004-of-00004.safetensors", + "byteSize": 2784155192, + "sha256": "224b322bd87949a50d36adea784bb4d7203b219023b1efec1ee4fd5a7ad05056" + }, + { + "path": "text_encoder/model.safetensors.index.json", + "byteSize": 33884, + "gitBlobId": "6772e9e467b21e665a772076372c06bf7f804968" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 601, + "gitBlobId": "582da4ad333feaaa0c427b4678a3e48447ea932f" + }, + { + "path": "tokenizer/tokenizer.json", + "byteSize": 19967863, + "sha256": "8a7269d6daa6328de533def0082ff9d3a825bb89036cbcc665c324f941f67fbf" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 3301, + "gitBlobId": "c5e187c7d25dcd379c984f9f12bbe6199d2d83dc" + }, + { + "path": "transformer/config.json", + "byteSize": 398, + "gitBlobId": "c2ed3e396ad8377bc7fa669f85e8bf70c70abd3a" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 4891433328, + "sha256": "fe197664340943dd4b1ec30af012a4e136bdfd80c53e6520378928e084de8b0d" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 4984743304, + "sha256": "63bc1152f337fb89ae9cfca5cb0d11451c5b456a345db9b008a14f129916beeb" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 2862106776, + "sha256": "5f196554fd09958d69e1491ed9a7d63a5a29384dbb73848d819285dd5124b478" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 41116, + "gitBlobId": "1e815991b52351c17732ce1ea3ee9ed1c7945c29" + }, + { + "path": "vae/config.json", + "byteSize": 771, + "gitBlobId": "547abd8bf762d39ccfa3cc216e0e5848ce0b8c20" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 812234342, + "sha256": "40aabe789e1591a3a3d8a2f598eda99f60a42275c410e7292a714b5270b97747" + } + ] + }, + { + "repo": "zai-org/GLM-Image", + "revision": "2c433cc0cbc293bde2ac8ca9624f279b5d23fcf4", + "checkedAt": "2026-09-24T01:43:41.812Z", + "source": "https://huggingface.co/api/models/zai-org/GLM-Image/revision/2c433cc0cbc293bde2ac8ca9624f279b5d23fcf4", + "gated": false, + "scope": "Selected-file Hub metadata only; not license approval, cached-content verification or model qualification.", + "files": [ + { + "path": ".gitattributes", + "byteSize": 1580, + "gitBlobId": "b2ef89be59c5792bf037a43d1bb69d922d28e9d8" + }, + { + "path": "README.md", + "byteSize": 31312, + "gitBlobId": "326cb0afc5ad3c0654ff0fedcbfd2d297334d856" + }, + { + "path": "model_index.json", + "byteSize": 619, + "gitBlobId": "0176012b61762dd17a8bad2fddd7882f2fba7aea" + }, + { + "path": "processor/chat_template.jinja", + "byteSize": 435, + "gitBlobId": "637cd237529e862e7db5eaec7e590290178da177" + }, + { + "path": "processor/preprocessor_config.json", + "byteSize": 431, + "gitBlobId": "86aaa268db728093c5cc9ca2dc8b9dff7fa26a1e" + }, + { + "path": "processor/tokenizer.json", + "byteSize": 23763950, + "sha256": "7626ee04802e81cadec0dbb983f2278aa24240b3b5c86a0b7831199ef25c6652" + }, + { + "path": "processor/tokenizer_config.json", + "byteSize": 4926, + "gitBlobId": "87f278e53c3999789e311033784d0fe25597a94c" + }, + { + "path": "scheduler/scheduler_config.json", + "byteSize": 482, + "gitBlobId": "b7051e28f47b16720b38ef9f2cb58cb090b8516a" + }, + { + "path": "text_encoder/config.json", + "byteSize": 781, + "gitBlobId": "3980a3a0e48caf1610b387b0b3a07656dca647fa" + }, + { + "path": "text_encoder/model.safetensors", + "byteSize": 870643240, + "sha256": "aa1ce00ffb0fe8912dfe32c11ba235b87511604298b605ef95314668a02f2bbf" + }, + { + "path": "tokenizer/added_tokens.json", + "byteSize": 3018, + "gitBlobId": "93c190b5690dd55aac16723222a9909e2be0faec" + }, + { + "path": "tokenizer/special_tokens_map.json", + "byteSize": 3090, + "gitBlobId": "04fd58b5fbf6a36fda564a656b14c137ef045689" + }, + { + "path": "tokenizer/tokenizer_config.json", + "byteSize": 25602, + "gitBlobId": "5b1fe24c1b1962f69a7754a26e6770ae7bcb7764" + }, + { + "path": "transformer/config.json", + "byteSize": 349, + "gitBlobId": "daa148441602211a5d68d17fb1d7893f8c0ad36b" + }, + { + "path": "transformer/diffusion_pytorch_model-00001-of-00003.safetensors", + "byteSize": 4966398856, + "sha256": "f58e49fd4f181d80aa4ea22de425e7f49a9e1d0f35415fe53634e722670eec55" + }, + { + "path": "transformer/diffusion_pytorch_model-00002-of-00003.safetensors", + "byteSize": 4984743304, + "sha256": "828d8aa45a4ded6d32e5018d36af9f234929966df3066cbc2b77afe22e4fa59a" + }, + { + "path": "transformer/diffusion_pytorch_model-00003-of-00003.safetensors", + "byteSize": 3902674480, + "sha256": "209b6fa82bd9632e44e9a2daea8727bd3f8206331dd20d784f5cdc39a65a035f" + }, + { + "path": "transformer/diffusion_pytorch_model.safetensors.index.json", + "byteSize": 44596, + "gitBlobId": "55e5ae5a0fcb5a3b3b5eb22457006054c9403cab" + }, + { + "path": "vae/config.json", + "byteSize": 1246, + "gitBlobId": "bec8d23fcc4e4aad0242578373aa869f770c61c6" + }, + { + "path": "vae/diffusion_pytorch_model.safetensors", + "byteSize": 812234342, + "sha256": "a80a129b649cdb6d74d9c2b5bb060f3b882b7826b63a732306a7d3393deedbc0" + }, + { + "path": "vision_language_encoder/config.json", + "byteSize": 1473, + "gitBlobId": "c4b9067ac05de1ab6e3fc2be979b7b6b0deed6ec" + }, + { + "path": "vision_language_encoder/generation_config.json", + "byteSize": 201, + "gitBlobId": "9c53e5f67b68593c8adf4b343f2ec00d1da756b0" + }, + { + "path": "vision_language_encoder/model-00001-of-00004.safetensors", + "byteSize": 5295710600, + "sha256": "8575ed7148893c4fb390ad84c51d9c3800b0f960af6f977c6f6fd6d1708c8c4d" + }, + { + "path": "vision_language_encoder/model-00002-of-00004.safetensors", + "byteSize": 5303216664, + "sha256": "d074e03e7bf01b4a3150adec5afb968303a0310ce701c3eae47f3e78e58b0fc5" + }, + { + "path": "vision_language_encoder/model-00003-of-00004.safetensors", + "byteSize": 5303216648, + "sha256": "c0b8360e705e473233b2f176bfee7ced2f1654e38d24215ae8f5515891312300" + }, + { + "path": "vision_language_encoder/model-00004-of-00004.safetensors", + "byteSize": 4326469720, + "sha256": "24ba0474ffbb11163465a2961c45f2a12d88e91840214f93f355851d7d303d0a" + }, + { + "path": "vision_language_encoder/model.safetensors.index.json", + "byteSize": 90283, + "gitBlobId": "cdddcb9819d9b04ff7bf93ecdd122f87e1bd0d9f" + } + ] + } + ], "schemaVersion": 1, "checkedAt": "2026-07-18T15:24:00+05:30", "selectionPolicy": { @@ -60,6 +7282,7 @@ {"repo": "fuliucansheng/FLUX.1-Canny-dev-diffusers", "revision": "24df2ba1c46a8c735589cc8f81302678797c9821", "license": "other", "purpose": "verified-diffusers-repair-source", "verifiedAt": "2026-08-03T14:34:36+05:30"} ], "models": [ + {"modelType": "QwenImage21Pipeline", "baseRepo": "Qwen/Qwen-Image-2.1", "baseRevision": "790c92633540aa0cb11d9abf19eb46d861714758", "baseLicense": "qwen-research", "artifacts": []}, { "modelType": "AnimaModularPipeline", "baseRepo": "circlestone-labs/Anima-Base-v1.0-Diffusers", @@ -654,6 +7877,20 @@ "baseRevision": "fbdf52fbaaca799592917417eb05f1899f1255ec", "baseLicense": "minimax-music3-community-license", "artifacts": [] + }, + { + "modelType": "DepthAnythingV2Model", + "baseRepo": "depth-anything/Depth-Anything-V2-Small-hf", + "baseRevision": "5426e4f0f36572d16453bbda7a8389317b1bef99", + "baseLicense": "apache-2.0", + "artifacts": [] + }, + { + "modelType": "DepthAnythingV2MetricModel", + "baseRepo": "depth-anything/Depth-Anything-V2-Metric-Outdoor-Small-hf", + "baseRevision": "fd2c22027eaf20374204f14099b8341e1925ad39", + "baseLicense": "apache-2.0", + "artifacts": [] } ] } diff --git a/data/modular-block-contracts.json b/data/modular-block-contracts.json index 97ddcda7..48835b30 100644 --- a/data/modular-block-contracts.json +++ b/data/modular-block-contracts.json @@ -2015,9 +2015,9 @@ "name": "enable_safety_checker" } ], - "contentHash": "sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "contentHash": "sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "description": "Prepares distilled prompt token IDs. Classifier-free guidance is baked into the weights, so `negative_prompt` is not exposed and the unconditional branch is derived from an empty prompt.", - "id": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "id": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "inputs": [ { "default": null, @@ -2060,12 +2060,12 @@ "type": "builtins.float" }, { - "default": true, + "default": null, "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, "name": "use_system_prompt", "required": false, - "type": "builtins.bool" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { "default": true, @@ -2148,13 +2148,13 @@ "defaultConfig": null, "description": "", "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" } ], "configs": [], - "contentHash": "sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "contentHash": "sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "description": "Runs the vision-only distilled Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "id": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "inputs": [ { "default": null, @@ -2180,6 +2180,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -2445,12 +2477,19 @@ "description": "", "name": "transformer", "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" } ], "configs": [], - "contentHash": "sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "contentHash": "sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "description": "Predicts available Cosmos3 modality velocities for one denoising iteration.", - "id": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "id": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "inputs": [ { "default": 6.0, @@ -3404,9 +3443,9 @@ } ], "configs": [], - "contentHash": "sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "contentHash": "sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "description": "Runs the vision-and-action Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "id": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "inputs": [ { "default": null, @@ -3432,6 +3471,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -3652,9 +3723,9 @@ } ], "configs": [], - "contentHash": "sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "contentHash": "sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "description": "Runs the vision-only Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "id": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "inputs": [ { "default": null, @@ -3680,6 +3751,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -4090,9 +4193,9 @@ } ], "configs": [], - "contentHash": "sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "contentHash": "sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "description": "Runs the vision-and-sound Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "id": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "inputs": [ { "default": null, @@ -4118,6 +4221,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -4547,6 +4682,111 @@ "schemaVersion": 1, "variadicInputs": [] }, + { + "className": "ErnieImagePromptEnhancerStep", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "pe", + "type": "transformers.models.ministral3.modeling_ministral3.Ministral3ForCausalLM" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "pe_tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + } + ], + "configs": [], + "contentHash": "sha256:2531b7dd8052234fb8c4962ec02e751915e41d3148fb2388ec7feec4609b3b06", + "description": "Prompt enhancer step that rewrites the input prompt using a causal language model (PE).", + "id": "diffusers.modular-block:ErnieImagePromptEnhancerStep:sha256:2531b7dd8052234fb8c4962ec02e751915e41d3148fb2388ec7feec4609b3b06", + "inputs": [ + { + "default": null, + "description": "The prompt or prompts to guide image generation.", + "kwargsType": null, + "name": "prompt", + "required": true, + "type": "builtins.str" + }, + { + "default": null, + "description": "The height in pixels of the generated image.", + "kwargsType": null, + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "The width in pixels of the generated image.", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional system prompt passed to the prompt enhancer.", + "kwargsType": null, + "name": "pe_system_prompt", + "required": false, + "type": "builtins.str" + }, + { + "default": 0.6, + "description": "Sampling temperature used when generating with the prompt enhancer.", + "kwargsType": null, + "name": "pe_temperature", + "required": false, + "type": "builtins.float" + }, + { + "default": 0.95, + "description": "Nucleus sampling `top_p` used when generating with the prompt enhancer.", + "kwargsType": null, + "name": "pe_top_p", + "required": false, + "type": "builtins.float" + } + ], + "kind": "block", + "outputs": [ + { + "default": null, + "description": "The prompt list after prompt-enhancer rewriting.", + "kwargsType": null, + "name": "prompt", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "The resolved image height in pixels.", + "kwargsType": null, + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "The resolved image width in pixels.", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + } + ], + "requiredInputs": [ + "prompt" + ], + "schemaVersion": 1, + "variadicInputs": [] + }, { "className": "ErnieImageSetTimestepsStep", "components": [ @@ -14475,7 +14715,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -14487,13 +14727,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "contentHash": "sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "description": "Condition denoise step. Iterates `LTX2DenoiseLoopWrapper.__call__`, running per step: - `LTX2ConditionLoopBeforeDenoiser` - `LTX2LoopDenoiser` - `LTX2ConditionLoopAfterDenoiser`", - "id": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "id": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "inputs": [ { "default": null, @@ -15424,7 +15664,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -15436,13 +15676,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "contentHash": "sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "description": "Text-to-video denoise step. Iterates `LTX2DenoiseLoopWrapper.__call__`, running per step: - `LTX2LoopBeforeDenoiser` - `LTX2LoopDenoiser` - `LTX2LoopAfterDenoiser`", - "id": "diffusers.modular-block:LTX2DenoiseStep:sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "id": "diffusers.modular-block:LTX2DenoiseStep:sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "inputs": [ { "default": null, @@ -15830,7 +16070,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -15842,13 +16082,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "contentHash": "sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "description": "Image-to-video denoise step. Iterates `LTX2DenoiseLoopWrapper.__call__`, running per step: - `LTX2Image2VideoLoopBeforeDenoiser` - `LTX2LoopDenoiser` - `LTX2Image2VideoLoopAfterDenoiser`", - "id": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "id": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "inputs": [ { "default": null, @@ -16619,7 +16859,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -16631,13 +16871,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "contentHash": "sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "description": "Joint video+audio denoiser. Runs the transformer once per guidance pass (each a single batch), with each pass's conditioning assembled by the guiders via `prepare_inputs_from_block_state` (driven by `guider_input_fields`) and unioned across the video `guider` and audio `audio_guider`; the per-pass model flags (STG blocks, modality isolation) are set by identifier afterwards. Converts each pass's velocity to x0 and delegates the per-modality CFG + STG + modality-isolation combine to the two guiders.", - "id": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "id": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "inputs": [ { "default": null, @@ -26639,43 +26879,15 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder", - "type": "transformers.modeling_utils.PreTrainedModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", - "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "connectors", - "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "duration_head", - "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" + "name": "text_tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "transformer", - "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" }, { "creationMethod": "from_pretrained", @@ -26688,68 +26900,49 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "audio_vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 3.0, - "modality_scale": 3.0, - "spatio_temporal_guidance_blocks": [ - 28 - ], - "stg_scale": 1.0 + "resample": "bilinear", + "vae_scale_factor": 16 }, "description": "", - "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" - }, + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" + } + ], + "configs": [ { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 7.0, - "modality_scale": 3.0, - "stg_scale": 1.0 - }, + "default": true, "description": "", - "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "name": "default_use_system_prompt" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "default": true, "description": "", - "name": "diffusion_decoder", - "type": "diffusers.models.autoencoders.ltx2_diffusion_decoder.LTX2VideoDiffusionDecoderModel" + "name": "enable_safety_checker" }, { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 32 - }, + "default": true, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "is_distilled" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "default": null, "description": "", - "name": "vocoder", - "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" + "name": "distilled_sigmas" } ], - "configs": [], - "contentHash": "sha256:1af6cf8cf157dc75d2250efa9625fd742cdc481aaf5a73b9091aa645280223e5", + "contentHash": "sha256:0be3e7aec7f37a3c20284ec209459ff639adbadea266665c26e9931049ee3d97", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:1af6cf8cf157dc75d2250efa9625fd742cdc481aaf5a73b9091aa645280223e5", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:0be3e7aec7f37a3c20284ec209459ff639adbadea266665c26e9931049ee3d97", "inputs": [ { "default": null, - "description": "The prompt or prompts to guide image generation.", + "description": "The text prompt that guides Cosmos3 generation.", "kwargsType": null, "name": "prompt", "required": true, @@ -26757,87 +26950,79 @@ }, { "default": null, - "description": "The prompt or prompts not to guide the image generation.", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "builtins.str" - }, - { - "default": 1024, - "description": "Maximum sequence length for prompt encoding.", + "description": "Number of frames to generate.", "kwargsType": null, - "name": "max_sequence_length", - "required": false, + "name": "num_frames", + "required": true, "type": "builtins.int" }, { - "default": 1.0, - "description": "Lower bound on the auto-predicted duration.", + "default": null, + "description": "Height of the generated video or image in pixels.", "kwargsType": null, - "name": "min_seconds", - "required": false, - "type": "builtins.float" + "name": "height", + "required": true, + "type": "builtins.int" }, { - "default": 20.0, - "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", + "default": null, + "description": "Width of the generated video or image in pixels.", "kwargsType": null, - "name": "max_seconds", - "required": false, - "type": "builtins.float" + "name": "width", + "required": true, + "type": "builtins.int" }, { "default": 24.0, - "description": "Frames per second of the generated video.", + "description": "Frame rate of the generated video.", "kwargsType": null, - "name": "frame_rate", + "name": "fps", "required": false, "type": "builtins.float" }, { "default": null, - "description": "`LTX2VideoCondition` (or list of them) placing image/video conditions at latent frame indices of the generated video.", + "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, - "name": "conditions", + "name": "use_system_prompt", "required": false, - "type": "builtins.list" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { - "default": 512, - "description": "The height in pixels of the generated image.", + "default": true, + "description": "Whether to add resolution metadata to the prompt.", "kwargsType": null, - "name": "height", + "name": "add_resolution_template", "required": false, - "type": "builtins.int" + "type": "builtins.bool" }, { - "default": 704, - "description": "The width in pixels of the generated image.", + "default": true, + "description": "Whether to add duration metadata to the prompt.", "kwargsType": null, - "name": "width", + "name": "add_duration_template", "required": false, - "type": "builtins.int" + "type": "builtins.bool" }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "Vision latents encoded from the conditioning image or video.", "kwargsType": null, - "name": "generator", + "name": "x0_tokens_vision", "required": false, - "type": "torch._C.Generator" + "type": "torch.Tensor" }, { - "default": 1, - "description": "The number of images to generate per prompt.", + "default": null, + "description": "Latent-frame indexes fixed by visual conditioning.", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "vision_condition_frames", "required": false, - "type": "builtins.int" + "type": "list[builtins.int]" }, { "default": null, - "description": "Pre-generated noisy latents for image generation.", + "description": "Pre-generated noisy vision latents.", "kwargsType": null, "name": "latents", "required": true, @@ -26845,22 +27030,14 @@ }, { "default": null, - "description": "Initial noise level for the un-conditioned tokens. `None` (default) resolves to `sigmas[0]` when custom `sigmas` are supplied, else 1.0.", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "noise_scale", + "name": "generator", "required": false, - "type": "builtins.float" + "type": "torch._C.Generator" }, { "default": null, - "description": "Custom sigmas for the denoising process.", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "list[builtins.float]" - }, - { - "default": 30, "description": "The number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", @@ -26869,35 +27046,43 @@ }, { "default": null, - "description": "Timesteps for the denoising process.", + "description": "Unused for distilled checkpoints; classifier-free guidance is baked into the weights and the scale is forced to 1.0. Passing a value other than 1.0 raises an error.", "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "torch.Tensor" + "name": "guidance_scale", + "required": false, + "type": "builtins.float" }, { "default": null, - "description": "Optional pre-encoded audio latents; random noise is used when not provided.", + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", "kwargsType": null, - "name": "audio_latents", - "required": true, - "type": "torch.Tensor" + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" }, { - "default": true, - "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", + "default": null, + "description": "Optional leading W8A16 step count.", "kwargsType": null, - "name": "use_cross_timestep", + "name": "mixed_precision_first_steps", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { "default": null, - "description": "Additional kwargs for attention processors.", + "description": "Optional trailing W8A16 step count.", "kwargsType": null, - "name": "attention_kwargs", + "name": "mixed_precision_last_steps", "required": false, - "type": "dict[builtins.str,typing.Any]" + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" }, { "default": "pil", @@ -26912,241 +27097,217 @@ "outputs": [ { "default": null, - "description": "Packed per-layer Gemma hidden states for the prompt.", + "description": "Number of frames to generate.", "kwargsType": null, - "name": "prompt_embeds", + "name": "num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Binary attention mask for `prompt_embeds`.", + "description": "Height of the generated video or image in pixels.", "kwargsType": null, - "name": "prompt_attention_mask", + "name": "height", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Packed per-layer Gemma hidden states for the negative prompt.", + "description": "Width of the generated video or image in pixels.", "kwargsType": null, - "name": "negative_prompt_embeds", + "name": "width", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Binary attention mask for `negative_prompt_embeds`.", + "description": "Token IDs for the conditional prompt.", "kwargsType": null, - "name": "negative_prompt_attention_mask", + "name": "cond_input_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The number of prompts being denoised (before per-prompt expansion).", + "description": "Token IDs for the unconditional prompt (empty prompt; guidance is baked in).", "kwargsType": null, - "name": "batch_size", + "name": "uncond_input_ids", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "The dtype of the prompt embeddings.", - "kwargsType": null, - "name": "dtype", + "description": "Conditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_text_segment", "required": false, - "type": "torch.dtype" + "type": "builtins.dict" }, { "default": null, - "description": "Video-branch text conditioning (cond).", - "kwargsType": null, - "name": "connector_prompt_embeds", + "description": "Unconditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_text_segment", "required": false, - "type": "torch.Tensor" + "type": "builtins.dict" }, { "default": null, - "description": "Audio-branch text conditioning (cond).", + "description": "Noisy vision latents for denoising.", "kwargsType": null, - "name": "connector_audio_prompt_embeds", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Binary text attention mask (cond).", + "description": "Frame rate used to pack vision latents.", "kwargsType": null, - "name": "connector_attention_mask", + "name": "fps_vision", "required": false, - "type": "torch.Tensor" + "type": "builtins.float" }, { "default": null, - "description": "Video-branch text conditioning (uncond).", - "kwargsType": null, - "name": "negative_connector_prompt_embeds", + "description": "Mask marking conditioned vision latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "vision_condition_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio-branch text conditioning (uncond).", + "description": "Indexes of conditioned vision latent frames.", "kwargsType": null, - "name": "negative_connector_audio_prompt_embeds", + "name": "vision_condition_indexes_for_pack", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "Binary text attention mask (uncond).", + "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", "kwargsType": null, - "name": "negative_connector_attention_mask", + "name": "vision_conditioning_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The predicted number of frames to generate.", - "kwargsType": null, - "name": "num_frames", + "description": "Conditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_vision_segment", "required": false, - "type": "builtins.int" + "type": "builtins.dict" }, { "default": null, - "description": "Per-condition normalized VAE latents of shape [1, C, F, H, W].", - "kwargsType": null, - "name": "condition_latents", + "description": "Unconditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_vision_segment", "required": false, - "type": "builtins.list" + "type": "builtins.dict" }, { "default": null, - "description": "Per-condition conditioning strengths.", - "kwargsType": null, - "name": "condition_strengths", + "description": "Conditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "cond_position_ids", "required": false, - "type": "builtins.list" + "type": "torch.Tensor" }, { "default": null, - "description": "Per-condition latent frame index at which the condition is applied.", - "kwargsType": null, - "name": "condition_indices", + "description": "Unconditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_position_ids", "required": false, - "type": "builtins.list" + "type": "torch.Tensor" }, { "default": null, - "description": "Per-condition trimmed pixel frame count, used to clamp the temporal extent of single-frame keyframe coordinates.", - "kwargsType": null, - "name": "condition_pixel_frames", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "Packed noisy video latents, with any keyframe condition tokens appended.", - "kwargsType": null, - "name": "latents", + "description": "Conditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "cond_sequence_length", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Packed per-token conditioning strengths of shape [B, S, 1] in [0, 1]: 1 at fully-conditioned positions, 0 at free positions.", - "kwargsType": null, - "name": "conditioning_mask", + "description": "Unconditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_sequence_length", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Clean condition latents at conditioned positions, zeros elsewhere; same shape as `latents`.", + "description": "Scheduler timesteps for denoising.", "kwargsType": null, - "name": "clean_latents", + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "RoPE coordinates of shape [B, 3, num_keyframe_tokens, 2] for the appended keyframe tokens, zero-width when there are none.", + "description": "Number of scheduler warmup steps.", "kwargsType": null, - "name": "appended_coords", + "name": "num_warmup_steps", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Number of generated-video tokens, i.e. the sequence length before appended tokens.", + "description": "Resolved number of denoising steps (fixed by the distilled schedule).", "kwargsType": null, - "name": "base_token_count", + "name": "num_inference_steps", "required": false, "type": "builtins.int" }, { "default": null, - "description": "The resolved initial noise level, forwarded to the audio latents step.", + "description": "Resolved classifier-free guidance scale (always 1.0 for distilled checkpoints).", "kwargsType": null, - "name": "noise_scale", + "name": "guidance_scale", "required": false, "type": "builtins.float" }, { "default": null, - "description": "", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", + "description": "Vision tokens for the transformer denoiser.", "kwargsType": null, - "name": "num_inference_steps", + "name": "vision_tokens", "required": false, - "type": "builtins.int" + "type": "list[torch.Tensor]" }, { "default": null, - "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", + "description": "Timesteps for the vision tokens.", "kwargsType": null, - "name": "audio_scheduler", + "name": "vision_timesteps", "required": false, - "type": "opaque" + "type": "torch.Tensor" }, { "default": null, - "description": "Packed noisy audio latents.", + "description": "Predicted velocity for vision latents.", "kwargsType": null, - "name": "audio_latents", + "name": "velocity_vision", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Number of audio latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "audio_num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Video RoPE patch coordinates, with the keyframe-condition coordinates appended.", - "kwargsType": "denoiser_input_fields", - "name": "video_coords", + "description": "Predicted velocity for sound latents.", + "kwargsType": null, + "name": "velocity_sound", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio RoPE patch coordinates.", - "kwargsType": "denoiser_input_fields", - "name": "audio_coords", + "description": "Predicted velocity for action latents.", + "kwargsType": null, + "name": "velocity_action", "required": false, "type": "torch.Tensor" }, @@ -27157,44 +27318,27 @@ "name": "videos", "required": false, "type": "list[PIL.Image.Image]" - }, - { - "default": null, - "description": "The generated audio waveform.", - "kwargsType": null, - "name": "audio", - "required": false, - "type": "torch.Tensor" } ], "requiredInputs": [ - "appended_coords", - "audio_latents", - "audio_num_frames", - "audio_scheduler", - "base_token_count", - "batch_size", - "clean_latents", - "condition_indices", - "condition_latents", - "condition_pixel_frames", - "condition_strengths", - "conditioning_mask", - "connector_attention_mask", - "connector_audio_prompt_embeds", - "connector_prompt_embeds", - "dtype", + "cond_input_ids", + "cond_text_segment", + "cond_vision_segment", + "fps_vision", + "height", "latents", - "negative_connector_attention_mask", - "negative_connector_audio_prompt_embeds", - "negative_connector_prompt_embeds", - "negative_prompt_attention_mask", - "negative_prompt_embeds", + "num_frames", "num_inference_steps", + "num_warmup_steps", "prompt", - "prompt_attention_mask", - "prompt_embeds", - "timesteps" + "timesteps", + "uncond_input_ids", + "uncond_text_segment", + "uncond_vision_segment", + "velocity_vision", + "vision_condition_indexes_for_pack", + "vision_condition_mask", + "width" ], "schemaVersion": 1, "variadicInputs": [ @@ -27214,72 +27358,110 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + "name": "text_encoder", + "type": "transformers.modeling_utils.PreTrainedModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "tokenizer", + "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "connectors", + "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "duration_head", + "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "audio_vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" }, { "creationMethod": "from_config", "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 16 + "guidance_rescale": 0.7, + "guidance_scale": 3.0, + "modality_scale": 3.0, + "spatio_temporal_guidance_blocks": [ + 28 + ], + "stg_scale": 1.0 }, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "sound_tokenizer", - "type": "diffusers.models.autoencoders.autoencoder_cosmos3_audio.Cosmos3AVAEAudioTokenizer" - } - ], - "configs": [ - { - "default": true, + "creationMethod": "from_config", + "defaultConfig": { + "guidance_rescale": 0.7, + "guidance_scale": 7.0, + "modality_scale": 3.0, + "stg_scale": 1.0 + }, "description": "", - "name": "default_use_system_prompt" + "name": "audio_guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { - "default": true, + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 32 + }, "description": "", - "name": "enable_safety_checker" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" }, { - "default": false, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "use_native_flow_schedule" + "name": "vocoder", + "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" } ], - "contentHash": "sha256:1c5cb0c3c0e7a7513a6d5a25e1888e5d855bf3a14d86fa1222d8d2f01772bd63", + "configs": [], + "contentHash": "sha256:1aab6c5ff65c77f7f64de2dc9d2671f2f266e5827a111741d2821834e3d6836c", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:1c5cb0c3c0e7a7513a6d5a25e1888e5d855bf3a14d86fa1222d8d2f01772bd63", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:1aab6c5ff65c77f7f64de2dc9d2671f2f266e5827a111741d2821834e3d6836c", "inputs": [ { "default": null, - "description": "The text prompt that guides Cosmos3 generation.", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", "required": true, @@ -27287,79 +27469,87 @@ }, { "default": null, - "description": "The negative text prompt used for classifier-free guidance.", + "description": "The prompt or prompts not to guide the image generation.", "kwargsType": null, "name": "negative_prompt", "required": false, "type": "builtins.str" }, { - "default": null, - "description": "Number of frames to generate.", + "default": 1024, + "description": "Maximum sequence length for prompt encoding.", "kwargsType": null, - "name": "num_frames", - "required": true, + "name": "max_sequence_length", + "required": false, "type": "builtins.int" }, { - "default": null, - "description": "Height of the generated video or image in pixels.", + "default": 1.0, + "description": "Lower bound on the auto-predicted duration.", "kwargsType": null, - "name": "height", - "required": true, - "type": "builtins.int" + "name": "min_seconds", + "required": false, + "type": "builtins.float" }, { - "default": null, - "description": "Width of the generated video or image in pixels.", + "default": 20.0, + "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", "kwargsType": null, - "name": "width", - "required": true, - "type": "builtins.int" + "name": "max_seconds", + "required": false, + "type": "builtins.float" }, { "default": 24.0, - "description": "Frame rate of the generated video.", + "description": "Frames per second of the generated video.", "kwargsType": null, - "name": "fps", + "name": "frame_rate", "required": false, "type": "builtins.float" }, { "default": null, - "description": "Whether to prepend the Cosmos3 system prompt.", + "description": "`LTX2VideoCondition` (or list of them) placing image/video conditions at latent frame indices of the generated video.", "kwargsType": null, - "name": "use_system_prompt", + "name": "conditions", "required": false, - "type": "UnionType[builtins.NoneType,builtins.bool]" + "type": "builtins.list" }, { - "default": true, - "description": "Whether to add resolution metadata to the prompt.", + "default": 512, + "description": "The height in pixels of the generated image.", "kwargsType": null, - "name": "add_resolution_template", + "name": "height", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { - "default": true, - "description": "Whether to add duration metadata to the prompt.", + "default": 704, + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "add_duration_template", + "name": "width", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { "default": null, - "description": "Reference image for image-to-video conditioning.", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "image", + "name": "generator", "required": false, - "type": "opaque" + "type": "torch._C.Generator" + }, + { + "default": 1, + "description": "The number of images to generate per prompt.", + "kwargsType": null, + "name": "num_videos_per_prompt", + "required": false, + "type": "builtins.int" }, { "default": null, - "description": "Pre-generated noisy vision latents.", + "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, "name": "latents", "required": true, @@ -27367,14 +27557,22 @@ }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "Initial noise level for the un-conditioned tokens. `None` (default) resolves to `sigmas[0]` when custom `sigmas` are supplied, else 1.0.", "kwargsType": null, - "name": "generator", + "name": "noise_scale", "required": false, - "type": "torch._C.Generator" + "type": "builtins.float" }, { - "default": 50, + "default": null, + "description": "Custom sigmas for the denoising process.", + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "list[builtins.float]" + }, + { + "default": 30, "description": "The number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", @@ -27383,332 +27581,300 @@ }, { "default": null, - "description": "Pre-generated noisy sound latents.", + "description": "Timesteps for the denoising process.", "kwargsType": null, - "name": "sound_latents", + "name": "timesteps", "required": true, "type": "torch.Tensor" }, { - "default": 6.0, - "description": "Scale for classifier-free guidance.", + "default": null, + "description": "Optional pre-encoded audio latents; random noise is used when not provided.", "kwargsType": null, - "name": "guidance_scale", - "required": false, - "type": "builtins.float" + "name": "audio_latents", + "required": true, + "type": "torch.Tensor" }, { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", + "default": true, + "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", "kwargsType": null, - "name": "output_type", + "name": "use_cross_timestep", "required": false, - "type": "builtins.str" + "type": "builtins.bool" }, { "default": null, - "description": "Denoised action latents.", + "description": "Additional kwargs for attention processors.", "kwargsType": null, - "name": "action_latents", + "name": "attention_kwargs", "required": false, - "type": "torch.Tensor" + "type": "dict[builtins.str,typing.Any]" }, { - "default": null, - "description": "Requested action-generation mode.", + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, - "name": "action_mode", + "name": "output_type", "required": false, "type": "builtins.str" }, + { + "default": 0.0, + "description": "The timestep at which the VAE decodes the final latents.", + "kwargsType": null, + "name": "decode_timestep", + "required": false, + "type": "opaque" + }, { "default": null, - "description": "Unpadded action-vector dimension.", + "description": "Noise interpolation factor applied to the latents at the decode timestep.", "kwargsType": null, - "name": "raw_action_dim_resolved", + "name": "decode_noise_scale", "required": false, - "type": "builtins.int" + "type": "opaque" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "Number of frames to generate.", + "description": "Packed per-layer Gemma hidden states for the prompt.", "kwargsType": null, - "name": "num_frames", + "name": "prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Height of the generated video or image in pixels.", + "description": "Binary attention mask for `prompt_embeds`.", "kwargsType": null, - "name": "height", + "name": "prompt_attention_mask", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Width of the generated video or image in pixels.", + "description": "Packed per-layer Gemma hidden states for the negative prompt.", "kwargsType": null, - "name": "width", + "name": "negative_prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the conditional prompt.", + "description": "Binary attention mask for `negative_prompt_embeds`.", "kwargsType": null, - "name": "cond_input_ids", + "name": "negative_prompt_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the unconditional prompt.", + "description": "The number of prompts being denoised (before per-prompt expansion).", "kwargsType": null, - "name": "uncond_input_ids", + "name": "batch_size", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Vision latents encoded from the conditioning image or video.", + "description": "The dtype of the prompt embeddings.", "kwargsType": null, - "name": "x0_tokens_vision", + "name": "dtype", "required": false, - "type": "torch.Tensor" + "type": "torch.dtype" }, { "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", + "description": "Video-branch text conditioning (cond).", "kwargsType": null, - "name": "vision_condition_frames", + "name": "connector_prompt_embeds", "required": false, - "type": "list[builtins.int]" + "type": "torch.Tensor" }, { "default": null, - "description": "Conditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_text_segment", + "description": "Audio-branch text conditioning (cond).", + "kwargsType": null, + "name": "connector_audio_prompt_embeds", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_text_segment", + "description": "Binary text attention mask (cond).", + "kwargsType": null, + "name": "connector_attention_mask", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Noisy vision latents for denoising.", + "description": "Video-branch text conditioning (uncond).", "kwargsType": null, - "name": "latents", + "name": "negative_connector_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Frame rate used to pack vision latents.", + "description": "Audio-branch text conditioning (uncond).", "kwargsType": null, - "name": "fps_vision", + "name": "negative_connector_audio_prompt_embeds", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "Mask marking conditioned vision latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "vision_condition_mask", + "description": "Binary text attention mask (uncond).", + "kwargsType": null, + "name": "negative_connector_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Indexes of conditioned vision latent frames.", + "description": "The predicted number of frames to generate.", "kwargsType": null, - "name": "vision_condition_indexes_for_pack", + "name": "num_frames", "required": false, - "type": "list[builtins.int]" + "type": "builtins.int" }, { "default": null, - "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", + "description": "Per-condition normalized VAE latents of shape [1, C, F, H, W].", "kwargsType": null, - "name": "vision_conditioning_latents", + "name": "condition_latents", "required": false, - "type": "torch.Tensor" + "type": "builtins.list" }, { "default": null, - "description": "Conditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_vision_segment", + "description": "Per-condition conditioning strengths.", + "kwargsType": null, + "name": "condition_strengths", "required": false, - "type": "builtins.dict" + "type": "builtins.list" }, { "default": null, - "description": "Unconditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_vision_segment", + "description": "Per-condition latent frame index at which the condition is applied.", + "kwargsType": null, + "name": "condition_indices", "required": false, - "type": "builtins.dict" + "type": "builtins.list" }, { "default": null, - "description": "Conditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "cond_position_ids", + "description": "Per-condition trimmed pixel frame count, used to clamp the temporal extent of single-frame keyframe coordinates.", + "kwargsType": null, + "name": "condition_pixel_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.list" }, { "default": null, - "description": "Unconditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_position_ids", + "description": "Packed noisy video latents, with any keyframe condition tokens appended.", + "kwargsType": null, + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sequence_length", + "description": "Packed per-token conditioning strengths of shape [B, S, 1] in [0, 1]: 1 at fully-conditioned positions, 0 at free positions.", + "kwargsType": null, + "name": "conditioning_mask", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sequence_length", + "description": "Clean condition latents at conditioned positions, zeros elsewhere; same shape as `latents`.", + "kwargsType": null, + "name": "clean_latents", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Scheduler timesteps for denoising.", + "description": "RoPE coordinates of shape [B, 3, num_keyframe_tokens, 2] for the appended keyframe tokens, zero-width when there are none.", "kwargsType": null, - "name": "timesteps", + "name": "appended_coords", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Number of scheduler warmup steps.", + "description": "Number of generated-video tokens, i.e. the sequence length before appended tokens.", "kwargsType": null, - "name": "num_warmup_steps", + "name": "base_token_count", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Noisy sound latents for denoising.", + "description": "The resolved initial noise level, forwarded to the audio latents step.", "kwargsType": null, - "name": "sound_latents", + "name": "noise_scale", "required": false, - "type": "torch.Tensor" + "type": "builtins.float" }, { "default": null, - "description": "Frame rate of the sound latent sequence.", + "description": "", "kwargsType": null, - "name": "fps_sound", + "name": "timesteps", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "Mask marking conditioned sound latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "sound_condition_mask", + "description": "", + "kwargsType": null, + "name": "num_inference_steps", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Scheduler used to update sound latents.", + "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", "kwargsType": null, - "name": "sound_scheduler", + "name": "audio_scheduler", "required": false, "type": "opaque" }, { "default": null, - "description": "Conditional sound segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sound_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Unconditional sound segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sound_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Vision tokens for the transformer denoiser.", - "kwargsType": null, - "name": "vision_tokens", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "Timesteps for the vision tokens.", - "kwargsType": null, - "name": "vision_timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Sound tokens for the transformer denoiser.", - "kwargsType": null, - "name": "sound_tokens", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "Timesteps for the sound tokens.", + "description": "Packed noisy audio latents.", "kwargsType": null, - "name": "sound_timesteps", + "name": "audio_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Predicted velocity for vision latents.", - "kwargsType": null, - "name": "velocity_vision", + "description": "Number of audio latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "audio_num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Predicted velocity for sound latents.", - "kwargsType": null, - "name": "velocity_sound", + "description": "Video RoPE patch coordinates, with the keyframe-condition coordinates appended.", + "kwargsType": "denoiser_input_fields", + "name": "video_coords", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Predicted velocity for action latents.", - "kwargsType": null, - "name": "velocity_action", + "description": "Audio RoPE patch coordinates.", + "kwargsType": "denoiser_input_fields", + "name": "audio_coords", "required": false, "type": "torch.Tensor" }, @@ -27722,57 +27888,41 @@ }, { "default": null, - "description": "Generated waveform.", + "description": "The generated audio waveform.", "kwargsType": null, - "name": "sound", + "name": "audio", "required": false, "type": "torch.Tensor" - }, - { - "default": null, - "description": "Sample rate of the generated waveform in Hz.", - "kwargsType": null, - "name": "sampling_rate", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Generated action vectors.", - "kwargsType": null, - "name": "action", - "required": false, - "type": "list[torch.Tensor]" } ], "requiredInputs": [ - "cond_input_ids", - "cond_position_ids", - "cond_sequence_length", - "cond_sound_segment", - "cond_text_segment", - "cond_vision_segment", - "fps_sound", - "fps_vision", - "height", + "appended_coords", + "audio_latents", + "audio_num_frames", + "audio_scheduler", + "base_token_count", + "batch_size", + "clean_latents", + "condition_indices", + "condition_latents", + "condition_pixel_frames", + "condition_strengths", + "conditioning_mask", + "connector_attention_mask", + "connector_audio_prompt_embeds", + "connector_prompt_embeds", + "dtype", "latents", - "num_frames", + "negative_connector_attention_mask", + "negative_connector_audio_prompt_embeds", + "negative_connector_prompt_embeds", + "negative_prompt_attention_mask", + "negative_prompt_embeds", "num_inference_steps", - "num_warmup_steps", "prompt", - "sound_latents", - "sound_scheduler", - "timesteps", - "uncond_input_ids", - "uncond_position_ids", - "uncond_sequence_length", - "uncond_sound_segment", - "uncond_text_segment", - "uncond_vision_segment", - "velocity_sound", - "velocity_vision", - "vision_condition_indexes_for_pack", - "width" + "prompt_attention_mask", + "prompt_embeds", + "timesteps" ], "schemaVersion": 1, "variadicInputs": [ @@ -30458,513 +30608,543 @@ "defaultConfig": null, "description": "", "name": "text_encoder", - "type": "transformers.modeling_utils.PreTrainedModel" + "type": "transformers.models.clip.modeling_clip.CLIPTextModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "tokenizer", - "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" + "name": "text_encoder_2", + "type": "transformers.models.clip.modeling_clip.CLIPTextModelWithProjection" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "connectors", - "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" + "name": "tokenizer", + "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" + "name": "tokenizer_2", + "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 7.5 + }, + "description": "", + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + "name": "image_encoder", + "type": "transformers.models.clip.modeling_clip.CLIPVisionModelWithProjection" }, { "creationMethod": "from_config", "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 32 + "crop_size": 224, + "size": 224 }, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "feature_extractor", + "type": "transformers.models.clip.image_processing_clip.CLIPImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "unet", + "type": "diffusers.models.unets.unet_2d_condition.UNet2DConditionModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "audio_vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 3.0, - "modality_scale": 3.0, - "spatio_temporal_guidance_blocks": [ - 28 - ], - "stg_scale": 1.0 + "vae_scale_factor": 8 }, "description": "", - "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 7.0, - "modality_scale": 3.0, - "stg_scale": 1.0 + "do_binarize": true, + "do_convert_grayscale": true, + "do_normalize": false, + "vae_scale_factor": 8 }, "description": "", - "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "name": "mask_processor", + "type": "diffusers.image_processor.VaeImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "diffusion_decoder", - "type": "diffusers.models.autoencoders.ltx2_diffusion_decoder.LTX2VideoDiffusionDecoderModel" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vocoder", - "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" + "name": "controlnet", + "type": "diffusers.models.controlnets.controlnet_union.ControlNetUnionModel" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "do_convert_rgb": true, + "do_normalize": false + }, + "description": "", + "name": "control_image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" } ], - "configs": [], - "contentHash": "sha256:2ccabff47b77c07cc636d2b0ebe747d476cafa1f7c4def6e879438208a3b25a3", + "configs": [ + { + "default": true, + "description": "", + "name": "force_zeros_for_empty_prompt" + }, + { + "default": false, + "description": "", + "name": "requires_aesthetics_score" + } + ], + "contentHash": "sha256:329ff124d60c5d09ea0ca6c5f2461f08087fd019a995ea0691e17daea463f29f", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:2ccabff47b77c07cc636d2b0ebe747d476cafa1f7c4def6e879438208a3b25a3", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:329ff124d60c5d09ea0ca6c5f2461f08087fd019a995ea0691e17daea463f29f", "inputs": [ { "default": null, - "description": "The prompt or prompts to guide image generation.", + "description": "", "kwargsType": null, "name": "prompt", - "required": true, - "type": "builtins.str" - }, - { - "default": null, - "description": "The prompt or prompts not to guide the image generation.", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "builtins.str" - }, - { - "default": 1024, - "description": "Maximum sequence length for prompt encoding.", - "kwargsType": null, - "name": "max_sequence_length", "required": false, - "type": "builtins.int" + "type": "opaque" }, { "default": null, - "description": "`LTX2VideoCondition` (or list of them) placing image/video conditions at latent frame indices of the generated video.", + "description": "", "kwargsType": null, - "name": "conditions", + "name": "prompt_2", "required": false, - "type": "builtins.list" + "type": "opaque" }, { - "default": 512, - "description": "The height in pixels of the generated image.", + "default": null, + "description": "", "kwargsType": null, - "name": "height", + "name": "negative_prompt", "required": false, - "type": "builtins.int" + "type": "opaque" }, { - "default": 704, - "description": "The width in pixels of the generated image.", + "default": null, + "description": "", "kwargsType": null, - "name": "width", + "name": "negative_prompt_2", "required": false, - "type": "builtins.int" + "type": "opaque" }, { "default": null, - "description": "The number of frames in the generated video. Omit to auto-predict via the `duration_head` (see `LTX2AutoDurationStep`).", + "description": "", "kwargsType": null, - "name": "num_frames", + "name": "cross_attention_kwargs", "required": false, - "type": "builtins.int" + "type": "opaque" }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "", "kwargsType": null, - "name": "generator", + "name": "clip_skip", "required": false, - "type": "torch._C.Generator" + "type": "opaque" }, { "default": null, - "description": "`LTX2ReferenceCondition` (or list of them) whose videos are encoded into extra latent tokens the IC-LoRA adapter attends to.", + "description": "The image(s) to be used as ip adapter", "kwargsType": null, - "name": "reference_conditions", + "name": "ip_adapter_image", "required": true, - "type": "builtins.list" + "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image],list[numpy.ndarray],list[torch.Tensor],numpy.ndarray,torch.Tensor]" }, { - "default": 1, - "description": "Ratio between the target and reference resolutions; 2 means the reference is preprocessed at half the target resolution. Spatial coordinates are scaled by this factor so the reference tokens land in the target coordinate space. Must match the factor the IC-LoRA was trained with.", + "default": null, + "description": "", "kwargsType": null, - "name": "reference_downscale_factor", + "name": "height", "required": false, - "type": "builtins.int" + "type": "opaque" }, { - "default": 1.0, - "description": "Scalar in [0, 1] controlling how strongly the noisy tokens and reference tokens attend to each other. 1.0 (default) leaves attention unmasked.", + "default": null, + "description": "", "kwargsType": null, - "name": "conditioning_attention_strength", + "name": "width", "required": false, - "type": "builtins.float" + "type": "opaque" }, { "default": null, - "description": "Optional pixel-space mask of shape (1, 1, F, H, W) with values in [0, 1] giving spatially varying attention strength. Downsampled to the reference's latent grid and multiplied by `conditioning_attention_strength`.", + "description": "", "kwargsType": null, - "name": "conditioning_attention_mask", - "required": false, - "type": "torch.Tensor" + "name": "image", + "required": true, + "type": "opaque" }, { - "default": 24.0, - "description": "Frames per second of the generated video.", + "default": null, + "description": "", "kwargsType": null, - "name": "frame_rate", - "required": false, - "type": "builtins.float" + "name": "mask_image", + "required": true, + "type": "opaque" }, { - "default": 1, - "description": "The number of images to generate per prompt.", + "default": null, + "description": "", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "padding_mask_crop", "required": false, - "type": "builtins.int" + "type": "opaque" }, { "default": null, - "description": "Pre-generated noisy latents for image generation.", + "description": "The dtype of the model inputs", "kwargsType": null, - "name": "latents", + "name": "dtype", "required": true, - "type": "torch.Tensor" + "type": "torch.dtype" }, { "default": null, - "description": "Initial noise level for the un-conditioned tokens. `None` (default) resolves to `sigmas[0]` when custom `sigmas` are supplied, else 1.0.", + "description": "", "kwargsType": null, - "name": "noise_scale", + "name": "generator", "required": false, - "type": "builtins.float" + "type": "opaque" }, { - "default": null, - "description": "Custom sigmas for the denoising process.", + "default": 1, + "description": "", "kwargsType": null, - "name": "sigmas", + "name": "num_images_per_prompt", "required": false, - "type": "list[builtins.float]" + "type": "opaque" }, { - "default": 30, - "description": "The number of denoising steps.", + "default": 50, + "description": "", "kwargsType": null, "name": "num_inference_steps", "required": true, - "type": "builtins.int" + "type": "opaque" }, { "default": null, - "description": "Timesteps for the denoising process.", + "description": "", "kwargsType": null, "name": "timesteps", "required": true, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "Optional pre-encoded audio latents; random noise is used when not provided.", - "kwargsType": null, - "name": "audio_latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": true, - "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", + "description": "", "kwargsType": null, - "name": "use_cross_timestep", + "name": "sigmas", "required": false, - "type": "builtins.bool" + "type": "opaque" }, { "default": null, - "description": "Additional kwargs for attention processors.", + "description": "", "kwargsType": null, - "name": "attention_kwargs", + "name": "denoising_end", "required": false, - "type": "dict[builtins.str,typing.Any]" + "type": "opaque" }, { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", + "default": 0.3, + "description": "", "kwargsType": null, - "name": "output_type", + "name": "strength", "required": false, - "type": "builtins.str" - } - ], - "kind": "sequential", - "outputs": [ + "type": "opaque" + }, { "default": null, - "description": "Packed per-layer Gemma hidden states for the prompt.", + "description": "", "kwargsType": null, - "name": "prompt_embeds", + "name": "denoising_start", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "Binary attention mask for `prompt_embeds`.", + "description": "", "kwargsType": null, - "name": "prompt_attention_mask", - "required": false, - "type": "torch.Tensor" + "name": "latents", + "required": true, + "type": "opaque" }, { "default": null, - "description": "Packed per-layer Gemma hidden states for the negative prompt.", + "description": "", "kwargsType": null, - "name": "negative_prompt_embeds", + "name": "original_size", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "Binary attention mask for `negative_prompt_embeds`.", + "description": "", "kwargsType": null, - "name": "negative_prompt_attention_mask", + "name": "target_size", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "The number of prompts being denoised (before per-prompt expansion).", + "description": "", "kwargsType": null, - "name": "batch_size", + "name": "negative_original_size", "required": false, - "type": "builtins.int" + "type": "opaque" }, { "default": null, - "description": "The dtype of the prompt embeddings.", + "description": "", "kwargsType": null, - "name": "dtype", + "name": "negative_target_size", "required": false, - "type": "torch.dtype" + "type": "opaque" }, { - "default": null, - "description": "Video-branch text conditioning (cond).", + "default": [ + 0, + 0 + ], + "description": "", "kwargsType": null, - "name": "connector_prompt_embeds", + "name": "crops_coords_top_left", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { - "default": null, - "description": "Audio-branch text conditioning (cond).", + "default": [ + 0, + 0 + ], + "description": "", "kwargsType": null, - "name": "connector_audio_prompt_embeds", + "name": "negative_crops_coords_top_left", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { - "default": null, - "description": "Binary text attention mask (cond).", + "default": 6.0, + "description": "", "kwargsType": null, - "name": "connector_attention_mask", + "name": "aesthetic_score", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { - "default": null, - "description": "Video-branch text conditioning (uncond).", + "default": 2.0, + "description": "", "kwargsType": null, - "name": "negative_connector_prompt_embeds", + "name": "negative_aesthetic_score", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "Audio-branch text conditioning (uncond).", + "description": "", "kwargsType": null, - "name": "negative_connector_audio_prompt_embeds", - "required": false, - "type": "torch.Tensor" + "name": "control_image", + "required": true, + "type": "opaque" }, { "default": null, - "description": "Binary text attention mask (uncond).", + "description": "", "kwargsType": null, - "name": "negative_connector_attention_mask", - "required": false, - "type": "torch.Tensor" + "name": "control_mode", + "required": true, + "type": "opaque" }, { - "default": null, - "description": "Per-condition normalized VAE latents of shape [1, C, F, H, W].", + "default": 0.0, + "description": "", "kwargsType": null, - "name": "condition_latents", + "name": "control_guidance_start", "required": false, - "type": "builtins.list" + "type": "opaque" }, { - "default": null, - "description": "Per-condition conditioning strengths.", + "default": 1.0, + "description": "", "kwargsType": null, - "name": "condition_strengths", + "name": "control_guidance_end", "required": false, - "type": "builtins.list" + "type": "opaque" }, { - "default": null, - "description": "Per-condition latent frame index at which the condition is applied.", + "default": 1.0, + "description": "", "kwargsType": null, - "name": "condition_indices", + "name": "controlnet_conditioning_scale", "required": false, - "type": "builtins.list" + "type": "opaque" }, { - "default": null, - "description": "Per-condition trimmed pixel frame count, used to clamp the temporal extent of single-frame keyframe coordinates.", + "default": false, + "description": "", "kwargsType": null, - "name": "condition_pixel_frames", + "name": "guess_mode", + "required": true, + "type": "opaque" + }, + { + "default": 0.0, + "description": "", + "kwargsType": null, + "name": "eta", "required": false, - "type": "builtins.list" + "type": "opaque" }, { - "default": null, - "description": "Packed reference tokens of shape [1, total_reference_tokens, C].", + "default": "pil", + "description": "", "kwargsType": null, - "name": "reference_latents", + "name": "output_type", + "required": false, + "type": "opaque" + } + ], + "kind": "sequential", + "outputs": [ + { + "default": null, + "description": "text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "RoPE coordinates for the reference tokens, of shape [1, 3, total_reference_tokens, 2].", - "kwargsType": null, - "name": "reference_coords", + "description": "negative text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Per-reference token counts, in `reference_conditions` order.", - "kwargsType": null, - "name": "reference_token_counts", + "description": "pooled text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "pooled_prompt_embeds", "required": false, - "type": "builtins.list" + "type": "torch.Tensor" }, { "default": null, - "description": "Per-reference-token noisy<->reference attention strengths of shape [1, total_reference_tokens], or `None` when attention is left unmasked.", - "kwargsType": null, - "name": "reference_cross_mask", + "description": "negative pooled text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "negative_pooled_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Packed noisy video latents, with keyframe and reference tokens appended.", - "kwargsType": null, - "name": "latents", + "description": "image embeddings for IP-Adapter", + "kwargsType": "denoiser_input_fields", + "name": "ip_adapter_embeds", "required": false, - "type": "torch.Tensor" + "type": "list[torch.Tensor]" }, { "default": null, - "description": "Packed per-token conditioning strengths of shape [B, S, 1] in [0, 1]: 1 at fully-conditioned positions, 0 at free positions.", + "description": "negative image embeddings for IP-Adapter", + "kwargsType": "denoiser_input_fields", + "name": "negative_ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "The latents representation of the input image", "kwargsType": null, - "name": "conditioning_mask", + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Clean condition latents at conditioned positions, zeros elsewhere; same shape as `latents`.", + "description": "The mask to use for the inpainting process", "kwargsType": null, - "name": "clean_latents", + "name": "mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "RoPE coordinates of shape [B, 3, num_keyframe_tokens + num_reference_tokens, 2] for the appended tokens, zero-width when there are none.", + "description": "The masked image latents to use for the inpainting process (only for inpainting-specifid unet)", "kwargsType": null, - "name": "appended_coords", + "name": "masked_image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Number of generated-video tokens, i.e. the sequence length before appended tokens.", + "description": "The crop coordinates to use for the preprocess/postprocess of the image and mask", "kwargsType": null, - "name": "base_token_count", + "name": "crops_coords", "required": false, - "type": "builtins.int" + "type": "UnionType[builtins.NoneType,tuple[builtins.int]]" }, { "default": null, - "description": "Number of reference tokens, which sit at the very end of the sequence.", + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", "kwargsType": null, - "name": "num_ref_tokens", + "name": "batch_size", "required": false, "type": "builtins.int" }, { "default": null, - "description": "The resolved initial noise level, forwarded to the audio latents step.", + "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", "kwargsType": null, - "name": "noise_scale", + "name": "dtype", "required": false, - "type": "builtins.float" + "type": "torch.dtype" }, { "default": null, - "description": "", + "description": "The timesteps to use for inference", "kwargsType": null, "name": "timesteps", "required": false, @@ -30972,7 +31152,7 @@ }, { "default": null, - "description": "", + "description": "The number of denoising steps to perform at inference time", "kwargsType": null, "name": "num_inference_steps", "required": false, @@ -30980,96 +31160,150 @@ }, { "default": null, - "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", + "description": "The timestep that represents the initial noise level for image-to-image generation", "kwargsType": null, - "name": "audio_scheduler", + "name": "latent_timestep", "required": false, - "type": "opaque" + "type": "torch.Tensor" }, { "default": null, - "description": "Packed noisy audio latents.", + "description": "The initial latents to use for the denoising process", "kwargsType": null, - "name": "audio_latents", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Number of audio latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "audio_num_frames", + "description": "The noise added to the image latents, used for inpainting generation", + "kwargsType": null, + "name": "noise", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Video RoPE patch coordinates, with the keyframe-condition coordinates appended.", + "description": "The time ids to condition the denoising process", "kwargsType": "denoiser_input_fields", - "name": "video_coords", + "name": "add_time_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio RoPE patch coordinates.", + "description": "The negative time ids to condition the denoising process", "kwargsType": "denoiser_input_fields", - "name": "audio_coords", + "name": "negative_add_time_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The generated videos.", + "description": "The timestep cond to use for LCM", "kwargsType": null, - "name": "videos", + "name": "timestep_cond", "required": false, - "type": "list[PIL.Image.Image]" + "type": "torch.Tensor" }, { "default": null, - "description": "The generated audio waveform.", + "description": "The processed control images", "kwargsType": null, - "name": "audio", + "name": "controlnet_cond", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "The control mode indices", + "kwargsType": "controlnet_kwargs", + "name": "control_type_idx", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "The control type tensor that specifies which control type is active", + "kwargsType": "controlnet_kwargs", + "name": "control_type", "required": false, "type": "torch.Tensor" + }, + { + "default": null, + "description": "The controlnet guidance start value", + "kwargsType": null, + "name": "control_guidance_start", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "The controlnet guidance end value", + "kwargsType": null, + "name": "control_guidance_end", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "The controlnet conditioning scale values", + "kwargsType": null, + "name": "conditioning_scale", + "required": false, + "type": "list[builtins.float]" + }, + { + "default": null, + "description": "Whether guess mode is used", + "kwargsType": null, + "name": "guess_mode", + "required": false, + "type": "builtins.bool" + }, + { + "default": null, + "description": "The controlnet keep values", + "kwargsType": null, + "name": "controlnet_keep", + "required": false, + "type": "list[builtins.float]" + }, + { + "default": null, + "description": "The generated images, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "kwargsType": null, + "name": "images", + "required": false, + "type": "UnionType[list[PIL.Image.Image],list[numpy.array],list[torch.Tensor]]" } ], "requiredInputs": [ - "appended_coords", - "audio_latents", - "audio_num_frames", - "audio_scheduler", - "base_token_count", "batch_size", - "clean_latents", - "condition_indices", - "condition_latents", - "condition_pixel_frames", - "condition_strengths", - "conditioning_mask", - "connector_attention_mask", - "connector_audio_prompt_embeds", - "connector_prompt_embeds", + "control_image", + "control_mode", + "controlnet_cond", + "controlnet_keep", "dtype", + "guess_mode", + "image", + "image_latents", + "ip_adapter_image", + "latent_timestep", "latents", - "negative_connector_attention_mask", - "negative_connector_audio_prompt_embeds", - "negative_connector_prompt_embeds", - "negative_prompt_attention_mask", - "negative_prompt_embeds", + "mask", + "mask_image", "num_inference_steps", - "prompt", - "prompt_attention_mask", + "pooled_prompt_embeds", "prompt_embeds", - "reference_conditions", "timesteps" ], "schemaVersion": 1, "variadicInputs": [ { "default": null, - "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "description": "All conditional model inputs that need to be prepared with guider. It should contain prompt_embeds/negative_prompt_embeds, add_time_ids/negative_add_time_ids, pooled_prompt_embeds/negative_pooled_prompt_embeds, and ip_adapter_embeds/negative_ip_adapter_embeds (optional).please add `kwargs_type=denoiser_input_fields` to their parameter spec (`OutputParam`) when they are created and added to the pipeline state", "kwargsType": "denoiser_input_fields", "required": false, "type": "opaque" @@ -31084,33 +31318,19 @@ "defaultConfig": null, "description": "", "name": "text_encoder", - "type": "transformers.models.clip.modeling_clip.CLIPTextModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_encoder_2", - "type": "transformers.models.clip.modeling_clip.CLIPTextModelWithProjection" + "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "tokenizer", - "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer_2", - "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_scale": 7.5 + "guidance_scale": 5.0 }, "description": "", "name": "guider", @@ -31120,94 +31340,51 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "image_encoder", - "type": "transformers.models.clip.modeling_clip.CLIPVisionModelWithProjection" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "crop_size": 224, - "size": 224 - }, - "description": "", - "name": "feature_extractor", + "name": "image_processor", "type": "transformers.models.clip.image_processing_clip.CLIPImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "unet", - "type": "diffusers.models.unets.unet_2d_condition.UNet2DConditionModel" + "name": "image_encoder", + "type": "transformers.models.clip.modeling_clip.CLIPVisionModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 8 - }, - "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" }, { "creationMethod": "from_config", "defaultConfig": { - "do_binarize": true, - "do_convert_grayscale": true, - "do_normalize": false, "vae_scale_factor": 8 }, "description": "", - "name": "mask_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "controlnet", - "type": "diffusers.models.controlnets.controlnet_union.ControlNetUnionModel" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "do_convert_rgb": true, - "do_normalize": false - }, - "description": "", - "name": "control_image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" - } - ], - "configs": [ - { - "default": true, - "description": "", - "name": "force_zeros_for_empty_prompt" - }, - { - "default": false, - "description": "", - "name": "requires_aesthetics_score" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" } ], - "contentHash": "sha256:329ff124d60c5d09ea0ca6c5f2461f08087fd019a995ea0691e17daea463f29f", + "configs": [], + "contentHash": "sha256:377fe3d311ab6d585c730af65f5de601c4df2d8e3818301f88f322949f014e3d", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:329ff124d60c5d09ea0ca6c5f2461f08087fd019a995ea0691e17daea463f29f", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:377fe3d311ab6d585c730af65f5de601c4df2d8e3818301f88f322949f014e3d", "inputs": [ { "default": null, @@ -31217,14 +31394,6 @@ "required": false, "type": "opaque" }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "prompt_2", - "required": false, - "type": "opaque" - }, { "default": null, "description": "", @@ -31234,18 +31403,10 @@ "type": "opaque" }, { - "default": null, - "description": "", - "kwargsType": null, - "name": "negative_prompt_2", - "required": false, - "type": "opaque" - }, - { - "default": null, + "default": 512, "description": "", "kwargsType": null, - "name": "cross_attention_kwargs", + "name": "max_sequence_length", "required": false, "type": "opaque" }, @@ -31253,65 +31414,33 @@ "default": null, "description": "", "kwargsType": null, - "name": "clip_skip", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "The image(s) to be used as ip adapter", - "kwargsType": null, - "name": "ip_adapter_image", + "name": "image", "required": true, - "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image],list[numpy.ndarray],list[torch.Tensor],numpy.ndarray,torch.Tensor]" + "type": "PIL.Image.Image" }, { - "default": null, + "default": 480, "description": "", "kwargsType": null, "name": "height", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": null, + "default": 832, "description": "", "kwargsType": null, "name": "width", "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "image", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "mask_image", - "required": true, - "type": "opaque" + "type": "builtins.int" }, { - "default": null, + "default": 81, "description": "", "kwargsType": null, - "name": "padding_mask_crop", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "The dtype of the model inputs", - "kwargsType": null, - "name": "dtype", + "name": "num_frames", "required": true, - "type": "torch.dtype" + "type": "builtins.int" }, { "default": null, @@ -31325,7 +31454,7 @@ "default": 1, "description": "", "kwargsType": null, - "name": "num_images_per_prompt", + "name": "num_videos_per_prompt", "required": false, "type": "opaque" }, @@ -31357,434 +31486,478 @@ "default": null, "description": "", "kwargsType": null, - "name": "denoising_end", - "required": false, - "type": "opaque" - }, - { - "default": 0.3, - "description": "", - "kwargsType": null, - "name": "strength", - "required": false, - "type": "opaque" + "name": "latents", + "required": true, + "type": "UnionType[builtins.NoneType,torch.Tensor]" }, { "default": null, "description": "", "kwargsType": null, - "name": "denoising_start", + "name": "attention_kwargs", "required": false, "type": "opaque" }, { - "default": null, - "description": "", + "default": "np", + "description": "The output type of the decoded videos", "kwargsType": null, - "name": "latents", - "required": true, - "type": "opaque" - }, + "name": "output_type", + "required": false, + "type": "builtins.str" + } + ], + "kind": "sequential", + "outputs": [ { "default": null, - "description": "", - "kwargsType": null, - "name": "original_size", + "description": "text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", "required": false, - "type": "opaque" + "type": "torch.Tensor" }, { "default": null, - "description": "", - "kwargsType": null, - "name": "target_size", + "description": "negative text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", "required": false, - "type": "opaque" + "type": "torch.Tensor" }, { "default": null, "description": "", "kwargsType": null, - "name": "negative_original_size", + "name": "resized_image", "required": false, - "type": "opaque" + "type": "PIL.Image.Image" }, { "default": null, - "description": "", + "description": "The image embeddings", "kwargsType": null, - "name": "negative_target_size", + "name": "image_embeds", "required": false, - "type": "opaque" + "type": "torch.Tensor" }, { - "default": [ - 0, - 0 - ], - "description": "", + "default": null, + "description": "video latent representation with the first frame image condition", "kwargsType": null, - "name": "crops_coords_top_left", + "name": "first_frame_latents", "required": false, - "type": "opaque" + "type": "torch.Tensor" }, { - "default": [ - 0, - 0 - ], + "default": null, "description": "", "kwargsType": null, - "name": "negative_crops_coords_top_left", + "name": "image_condition_latents", "required": false, - "type": "opaque" + "type": "UnionType[builtins.NoneType,torch.Tensor]" }, { - "default": 6.0, - "description": "", + "default": null, + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", "kwargsType": null, - "name": "aesthetic_score", + "name": "batch_size", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": 2.0, - "description": "", + "default": null, + "description": "Data type of model tensor inputs (determined by `transformer.dtype`)", "kwargsType": null, - "name": "negative_aesthetic_score", + "name": "dtype", "required": false, - "type": "opaque" + "type": "torch.dtype" }, { "default": null, - "description": "", + "description": "The initial latents to use for the denoising process", "kwargsType": null, - "name": "control_image", - "required": true, - "type": "opaque" + "name": "latents", + "required": false, + "type": "torch.Tensor" }, { "default": null, - "description": "", - "kwargsType": null, - "name": "control_mode", - "required": true, - "type": "opaque" - }, - { - "default": 0.0, - "description": "", + "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", "kwargsType": null, - "name": "control_guidance_start", + "name": "videos", "required": false, - "type": "opaque" + "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" + } + ], + "requiredInputs": [ + "batch_size", + "dtype", + "image", + "image_condition_latents", + "image_embeds", + "latents", + "negative_prompt_embeds", + "num_frames", + "num_inference_steps", + "prompt_embeds", + "resized_image", + "timesteps" + ], + "schemaVersion": 1, + "variadicInputs": [] + }, + { + "className": "SequentialPipelineBlocks", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder", + "type": "transformers.models.clip.modeling_clip.CLIPTextModel" }, { - "default": 1.0, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "kwargsType": null, - "name": "control_guidance_end", - "required": false, - "type": "opaque" + "name": "tokenizer", + "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" }, { - "default": 1.0, + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder_2", + "type": "transformers.models.t5.modeling_t5.T5EncoderModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "tokenizer_2", + "type": "transformers.models.t5.tokenization_t5.T5Tokenizer" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_latent_channels": 16, + "vae_scale_factor": 16 + }, + "description": "", + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_flux.FluxTransformer2DModel" + } + ], + "configs": [], + "contentHash": "sha256:381f8bcb73996879b57732138b7b108b495b10a6b57ab237afe9e90769d55b68", + "description": "", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:381f8bcb73996879b57732138b7b108b495b10a6b57ab237afe9e90769d55b68", + "inputs": [ + { + "default": null, "description": "", "kwargsType": null, - "name": "controlnet_conditioning_scale", + "name": "prompt", "required": false, "type": "opaque" }, { - "default": false, + "default": null, "description": "", "kwargsType": null, - "name": "guess_mode", - "required": true, + "name": "prompt_2", + "required": false, "type": "opaque" }, { - "default": 0.0, + "default": 512, "description": "", "kwargsType": null, - "name": "eta", + "name": "max_sequence_length", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": "pil", + "default": null, "description": "", "kwargsType": null, - "name": "output_type", + "name": "joint_attention_kwargs", "required": false, "type": "opaque" - } - ], - "kind": "sequential", - "outputs": [ + }, { "default": null, - "description": "text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", + "description": "", + "kwargsType": null, + "name": "resized_image", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "negative text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", + "description": "", + "kwargsType": null, + "name": "image", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "pooled text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "pooled_prompt_embeds", - "required": false, - "type": "torch.Tensor" + "description": "", + "kwargsType": null, + "name": "height", + "required": true, + "type": "opaque" }, { "default": null, - "description": "negative pooled text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_pooled_prompt_embeds", - "required": false, - "type": "torch.Tensor" + "description": "", + "kwargsType": null, + "name": "width", + "required": true, + "type": "opaque" }, { "default": null, - "description": "image embeddings for IP-Adapter", - "kwargsType": "denoiser_input_fields", - "name": "ip_adapter_embeds", + "description": "", + "kwargsType": null, + "name": "generator", "required": false, - "type": "list[torch.Tensor]" + "type": "opaque" }, { - "default": null, - "description": "negative image embeddings for IP-Adapter", - "kwargsType": "denoiser_input_fields", - "name": "negative_ip_adapter_embeds", + "default": 1, + "description": "", + "kwargsType": null, + "name": "num_images_per_prompt", "required": false, - "type": "list[torch.Tensor]" + "type": "opaque" }, { "default": null, - "description": "The latents representation of the input image", + "description": "", "kwargsType": null, - "name": "image_latents", - "required": false, - "type": "torch.Tensor" + "name": "latents", + "required": true, + "type": "UnionType[builtins.NoneType,torch.Tensor]" }, { - "default": null, - "description": "The mask to use for the inpainting process", + "default": 50, + "description": "", "kwargsType": null, - "name": "mask", - "required": false, - "type": "torch.Tensor" + "name": "num_inference_steps", + "required": true, + "type": "opaque" }, { "default": null, - "description": "The masked image latents to use for the inpainting process (only for inpainting-specifid unet)", + "description": "", "kwargsType": null, - "name": "masked_image_latents", - "required": false, - "type": "torch.Tensor" + "name": "timesteps", + "required": true, + "type": "opaque" }, { "default": null, - "description": "The crop coordinates to use for the preprocess/postprocess of the image and mask", + "description": "", "kwargsType": null, - "name": "crops_coords", + "name": "sigmas", "required": false, - "type": "UnionType[builtins.NoneType,tuple[builtins.int]]" + "type": "opaque" }, { - "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", + "default": 0.6, + "description": "", "kwargsType": null, - "name": "batch_size", + "name": "strength", "required": false, - "type": "builtins.int" + "type": "opaque" }, { - "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "default": 3.5, + "description": "", "kwargsType": null, - "name": "dtype", + "name": "guidance_scale", "required": false, - "type": "torch.dtype" + "type": "opaque" }, { - "default": null, - "description": "The timesteps to use for inference", + "default": "pil", + "description": "", "kwargsType": null, - "name": "timesteps", + "name": "output_type", "required": false, - "type": "torch.Tensor" - }, + "type": "opaque" + } + ], + "kind": "sequential", + "outputs": [ { "default": null, - "description": "The number of denoising steps to perform at inference time", - "kwargsType": null, - "name": "num_inference_steps", + "description": "text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "The timestep that represents the initial noise level for image-to-image generation", - "kwargsType": null, - "name": "latent_timestep", + "description": "pooled text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "pooled_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The initial latents to use for the denoising process", + "description": "", "kwargsType": null, - "name": "latents", + "name": "processed_image", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "The noise added to the image latents, used for inpainting generation", + "description": "The latents representing the reference image", "kwargsType": null, - "name": "noise", + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The time ids to condition the denoising process", - "kwargsType": "denoiser_input_fields", - "name": "add_time_ids", + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", + "kwargsType": null, + "name": "batch_size", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "The negative time ids to condition the denoising process", - "kwargsType": "denoiser_input_fields", - "name": "negative_add_time_ids", + "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "kwargsType": null, + "name": "dtype", "required": false, - "type": "torch.Tensor" + "type": "torch.dtype" }, { "default": null, - "description": "The timestep cond to use for LCM", + "description": "The height of the image latents", "kwargsType": null, - "name": "timestep_cond", + "name": "image_height", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "The processed control images", + "description": "The width of the image latents", "kwargsType": null, - "name": "controlnet_cond", + "name": "image_width", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.int" }, { "default": null, - "description": "The control mode indices", - "kwargsType": "controlnet_kwargs", - "name": "control_type_idx", + "description": "The initial latents to use for the denoising process", + "kwargsType": null, + "name": "latents", "required": false, - "type": "list[builtins.int]" + "type": "torch.Tensor" }, { "default": null, - "description": "The control type tensor that specifies which control type is active", - "kwargsType": "controlnet_kwargs", - "name": "control_type", + "description": "The timesteps to use for inference", + "kwargsType": null, + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The controlnet guidance start value", + "description": "The number of denoising steps to perform at inference time", "kwargsType": null, - "name": "control_guidance_start", + "name": "num_inference_steps", "required": false, - "type": "builtins.float" + "type": "builtins.int" }, { "default": null, - "description": "The controlnet guidance end value", + "description": "Optional guidance to be used.", "kwargsType": null, - "name": "control_guidance_end", + "name": "guidance", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "The controlnet conditioning scale values", + "description": "The initial random noised used for inpainting denoising.", "kwargsType": null, - "name": "conditioning_scale", + "name": "initial_noise", "required": false, - "type": "list[builtins.float]" + "type": "torch.Tensor" }, { "default": null, - "description": "Whether guess mode is used", - "kwargsType": null, - "name": "guess_mode", + "description": "The sequence lengths of the prompt embeds, used for RoPE calculation.", + "kwargsType": "denoiser_input_fields", + "name": "txt_ids", "required": false, - "type": "builtins.bool" + "type": "list[builtins.int]" }, { "default": null, - "description": "The controlnet keep values", - "kwargsType": null, - "name": "controlnet_keep", + "description": "The sequence lengths of the image latents, used for RoPE calculation.", + "kwargsType": "denoiser_input_fields", + "name": "img_ids", "required": false, - "type": "list[builtins.float]" + "type": "list[builtins.int]" }, { "default": null, - "description": "The generated images, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", "kwargsType": null, "name": "images", "required": false, - "type": "UnionType[list[PIL.Image.Image],list[numpy.array],list[torch.Tensor]]" + "type": "UnionType[list[PIL.Image.Image],numpy.ndarray,torch.Tensor]" } ], "requiredInputs": [ "batch_size", - "control_image", - "control_mode", - "controlnet_cond", - "controlnet_keep", - "dtype", - "guess_mode", - "image", + "height", "image_latents", - "ip_adapter_image", - "latent_timestep", + "img_ids", "latents", - "mask", - "mask_image", "num_inference_steps", "pooled_prompt_embeds", "prompt_embeds", - "timesteps" + "timesteps", + "txt_ids", + "width" ], "schemaVersion": 1, - "variadicInputs": [ - { - "default": null, - "description": "All conditional model inputs that need to be prepared with guider. It should contain prompt_embeds/negative_prompt_embeds, add_time_ids/negative_add_time_ids, pooled_prompt_embeds/negative_pooled_prompt_embeds, and ip_adapter_embeds/negative_ip_adapter_embeds (optional).please add `kwargs_type=denoiser_input_fields` to their parameter spec (`OutputParam`) when they are created and added to the pipeline state", - "kwargsType": "denoiser_input_fields", - "required": false, - "type": "opaque" - } - ] + "variadicInputs": [] }, { "className": "SequentialPipelineBlocks", @@ -31793,190 +31966,206 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder", - "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", + "name": "text_tokenizer", "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 5.0 - }, - "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "image_processor", - "type": "transformers.models.clip.image_processing_clip.CLIPImageProcessor" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "image_encoder", - "type": "transformers.models.clip.modeling_clip.CLIPVisionModel" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { "creationMethod": "from_config", "defaultConfig": { - "vae_scale_factor": 8 + "resample": "bilinear", + "vae_scale_factor": 16 }, "description": "", "name": "video_processor", "type": "diffusers.video_processor.VideoProcessor" + } + ], + "configs": [ + { + "default": true, + "description": "", + "name": "default_use_system_prompt" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "default": true, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" + "name": "enable_safety_checker" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "default": true, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + "name": "is_distilled" + }, + { + "default": null, + "description": "", + "name": "distilled_sigmas" } ], - "configs": [], - "contentHash": "sha256:377fe3d311ab6d585c730af65f5de601c4df2d8e3818301f88f322949f014e3d", + "contentHash": "sha256:413fb3a62436c7dc39d1a898b08596f025efb7fa87547069fb8b2ee703d2a4ff", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:377fe3d311ab6d585c730af65f5de601c4df2d8e3818301f88f322949f014e3d", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:413fb3a62436c7dc39d1a898b08596f025efb7fa87547069fb8b2ee703d2a4ff", "inputs": [ { "default": null, - "description": "", + "description": "The text prompt that guides Cosmos3 generation.", "kwargsType": null, "name": "prompt", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "opaque" - }, - { - "default": 512, - "description": "", - "kwargsType": null, - "name": "max_sequence_length", - "required": false, - "type": "opaque" + "required": true, + "type": "builtins.str" }, { "default": null, - "description": "", + "description": "Number of frames to generate.", "kwargsType": null, - "name": "image", + "name": "num_frames", "required": true, - "type": "PIL.Image.Image" + "type": "builtins.int" }, { - "default": 480, - "description": "", + "default": null, + "description": "Height of the generated video or image in pixels.", "kwargsType": null, "name": "height", - "required": false, + "required": true, "type": "builtins.int" }, { - "default": 832, - "description": "", + "default": null, + "description": "Width of the generated video or image in pixels.", "kwargsType": null, "name": "width", - "required": false, - "type": "builtins.int" - }, - { - "default": 81, - "description": "", - "kwargsType": null, - "name": "num_frames", "required": true, "type": "builtins.int" }, { - "default": null, - "description": "", + "default": 24.0, + "description": "Frame rate of the generated video.", "kwargsType": null, - "name": "generator", + "name": "fps", "required": false, - "type": "opaque" + "type": "builtins.float" }, { - "default": 1, - "description": "", + "default": null, + "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "use_system_prompt", "required": false, - "type": "opaque" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { - "default": 50, - "description": "", + "default": true, + "description": "Whether to add resolution metadata to the prompt.", "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "opaque" + "name": "add_resolution_template", + "required": false, + "type": "builtins.bool" }, { - "default": null, - "description": "", + "default": true, + "description": "Whether to add duration metadata to the prompt.", "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "opaque" + "name": "add_duration_template", + "required": false, + "type": "builtins.bool" }, { "default": null, - "description": "", + "description": "Reference image for image-to-video conditioning.", "kwargsType": null, - "name": "sigmas", + "name": "image", "required": false, "type": "opaque" }, { "default": null, - "description": "", + "description": "Pre-generated noisy vision latents.", "kwargsType": null, "name": "latents", "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" + "type": "torch.Tensor" }, { "default": null, - "description": "", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "attention_kwargs", + "name": "generator", "required": false, - "type": "opaque" + "type": "torch._C.Generator" }, { - "default": "np", - "description": "The output type of the decoded videos", + "default": null, + "description": "The number of denoising steps.", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "Unused for distilled checkpoints; classifier-free guidance is baked into the weights and the scale is forced to 1.0. Passing a value other than 1.0 raises an error.", + "kwargsType": null, + "name": "guidance_scale", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, + { + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, "name": "output_type", "required": false, @@ -31987,101 +32176,275 @@ "outputs": [ { "default": null, - "description": "text embeddings used to guide the image generation", + "description": "Number of frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Height of the generated video or image in pixels.", + "kwargsType": null, + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Width of the generated video or image in pixels.", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Token IDs for the conditional prompt.", + "kwargsType": null, + "name": "cond_input_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Token IDs for the unconditional prompt (empty prompt; guidance is baked in).", + "kwargsType": null, + "name": "uncond_input_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Vision latents encoded from the conditioning image or video.", + "kwargsType": null, + "name": "x0_tokens_vision", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Latent-frame indexes fixed by visual conditioning.", + "kwargsType": null, + "name": "vision_condition_frames", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "Conditional text segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", + "name": "cond_text_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_text_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Noisy vision latents for denoising.", + "kwargsType": null, + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "negative text embeddings used to guide the image generation", + "description": "Frame rate used to pack vision latents.", + "kwargsType": null, + "name": "fps_vision", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Mask marking conditioned vision latent frames.", "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", + "name": "vision_condition_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "", + "description": "Indexes of conditioned vision latent frames.", "kwargsType": null, - "name": "resized_image", + "name": "vision_condition_indexes_for_pack", "required": false, - "type": "PIL.Image.Image" + "type": "list[builtins.int]" }, { "default": null, - "description": "The image embeddings", + "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", "kwargsType": null, - "name": "image_embeds", + "name": "vision_conditioning_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "video latent representation with the first frame image condition", + "description": "Conditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Conditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "cond_position_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Unconditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_position_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Conditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "cond_sequence_length", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Unconditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_sequence_length", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Scheduler timesteps for denoising.", "kwargsType": null, - "name": "first_frame_latents", + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "", + "description": "Number of scheduler warmup steps.", "kwargsType": null, - "name": "image_condition_latents", + "name": "num_warmup_steps", "required": false, - "type": "UnionType[builtins.NoneType,torch.Tensor]" + "type": "builtins.int" }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", + "description": "Resolved number of denoising steps (fixed by the distilled schedule).", "kwargsType": null, - "name": "batch_size", + "name": "num_inference_steps", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Data type of model tensor inputs (determined by `transformer.dtype`)", + "description": "Resolved classifier-free guidance scale (always 1.0 for distilled checkpoints).", "kwargsType": null, - "name": "dtype", + "name": "guidance_scale", "required": false, - "type": "torch.dtype" + "type": "builtins.float" }, { "default": null, - "description": "The initial latents to use for the denoising process", + "description": "Vision tokens for the transformer denoiser.", "kwargsType": null, - "name": "latents", + "name": "vision_tokens", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Timesteps for the vision tokens.", + "kwargsType": null, + "name": "vision_timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "description": "Predicted velocity for vision latents.", + "kwargsType": null, + "name": "velocity_vision", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for sound latents.", + "kwargsType": null, + "name": "velocity_sound", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for action latents.", + "kwargsType": null, + "name": "velocity_action", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated videos.", "kwargsType": null, "name": "videos", "required": false, - "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" + "type": "list[PIL.Image.Image]" } ], "requiredInputs": [ - "batch_size", - "dtype", - "image", - "image_condition_latents", - "image_embeds", + "cond_input_ids", + "cond_text_segment", + "cond_vision_segment", + "fps_vision", + "height", "latents", - "negative_prompt_embeds", "num_frames", "num_inference_steps", - "prompt_embeds", - "resized_image", - "timesteps" + "num_warmup_steps", + "prompt", + "timesteps", + "uncond_input_ids", + "uncond_text_segment", + "uncond_vision_segment", + "velocity_vision", + "vision_condition_indexes_for_pack", + "vision_condition_mask", + "width" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "SequentialPipelineBlocks", @@ -32091,65 +32454,59 @@ "defaultConfig": null, "description": "", "name": "text_encoder", - "type": "transformers.models.clip.modeling_clip.CLIPTextModel" + "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "tokenizer", - "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_encoder_2", - "type": "transformers.models.t5.modeling_t5.T5EncoderModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer_2", - "type": "transformers.models.t5.tokenization_t5.T5Tokenizer" + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "vae_latent_channels": 16, - "vae_scale_factor": 16 + "guidance_scale": 5.0 }, "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_flux.FluxTransformer2DModel" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 8 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" } ], "configs": [], - "contentHash": "sha256:381f8bcb73996879b57732138b7b108b495b10a6b57ab237afe9e90769d55b68", + "contentHash": "sha256:48ea6514c75263df949ce1398f486626a43c0e1f5c399f6fb4b0812db5164d32", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:381f8bcb73996879b57732138b7b108b495b10a6b57ab237afe9e90769d55b68", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:48ea6514c75263df949ce1398f486626a43c0e1f5c399f6fb4b0812db5164d32", "inputs": [ { "default": null, @@ -32163,7 +32520,7 @@ "default": null, "description": "", "kwargsType": null, - "name": "prompt_2", + "name": "negative_prompt", "required": false, "type": "opaque" }, @@ -32173,37 +32530,21 @@ "kwargsType": null, "name": "max_sequence_length", "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "joint_attention_kwargs", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "resized_image", - "required": false, "type": "opaque" }, { - "default": null, + "default": 1, "description": "", "kwargsType": null, - "name": "image", + "name": "num_videos_per_prompt", "required": false, "type": "opaque" }, { - "default": null, + "default": 50, "description": "", "kwargsType": null, - "name": "height", + "name": "num_inference_steps", "required": true, "type": "opaque" }, @@ -32211,7 +32552,7 @@ "default": null, "description": "", "kwargsType": null, - "name": "width", + "name": "timesteps", "required": true, "type": "opaque" }, @@ -32219,73 +32560,65 @@ "default": null, "description": "", "kwargsType": null, - "name": "generator", + "name": "sigmas", "required": false, "type": "opaque" }, { - "default": 1, + "default": null, "description": "", "kwargsType": null, - "name": "num_images_per_prompt", + "name": "height", "required": false, - "type": "opaque" + "type": "builtins.int" }, { "default": null, "description": "", "kwargsType": null, - "name": "latents", - "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" + "name": "width", + "required": false, + "type": "builtins.int" }, { - "default": 50, + "default": null, "description": "", "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "opaque" + "name": "num_frames", + "required": false, + "type": "builtins.int" }, { "default": null, "description": "", "kwargsType": null, - "name": "timesteps", + "name": "latents", "required": true, - "type": "opaque" + "type": "UnionType[builtins.NoneType,torch.Tensor]" }, { "default": null, "description": "", "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "opaque" - }, - { - "default": 0.6, - "description": "", - "kwargsType": null, - "name": "strength", + "name": "generator", "required": false, "type": "opaque" }, { - "default": 3.5, + "default": null, "description": "", "kwargsType": null, - "name": "guidance_scale", + "name": "attention_kwargs", "required": false, "type": "opaque" }, { - "default": "pil", - "description": "", + "default": "np", + "description": "The output type of the decoded videos", "kwargsType": null, "name": "output_type", "required": false, - "type": "opaque" + "type": "builtins.str" } ], "kind": "sequential", @@ -32300,31 +32633,15 @@ }, { "default": null, - "description": "pooled text embeddings used to guide the image generation", + "description": "negative text embeddings used to guide the image generation", "kwargsType": "denoiser_input_fields", - "name": "pooled_prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "processed_image", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "The latents representing the reference image", - "kwargsType": null, - "name": "image_latents", + "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", "kwargsType": null, "name": "batch_size", "required": false, @@ -32332,28 +32649,12 @@ }, { "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "description": "Data type of model tensor inputs (determined by `transformer.dtype`)", "kwargsType": null, "name": "dtype", "required": false, "type": "torch.dtype" }, - { - "default": null, - "description": "The height of the image latents", - "kwargsType": null, - "name": "image_height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The width of the image latents", - "kwargsType": null, - "name": "image_width", - "required": false, - "type": "builtins.int" - }, { "default": null, "description": "The initial latents to use for the denoising process", @@ -32364,73 +32665,318 @@ }, { "default": null, - "description": "The timesteps to use for inference", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The number of denoising steps to perform at inference time", - "kwargsType": null, - "name": "num_inference_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Optional guidance to be used.", - "kwargsType": null, - "name": "guidance", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The initial random noised used for inpainting denoising.", - "kwargsType": null, - "name": "initial_noise", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The sequence lengths of the prompt embeds, used for RoPE calculation.", - "kwargsType": "denoiser_input_fields", - "name": "txt_ids", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "The sequence lengths of the image latents, used for RoPE calculation.", - "kwargsType": "denoiser_input_fields", - "name": "img_ids", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", + "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", "kwargsType": null, - "name": "images", + "name": "videos", "required": false, - "type": "UnionType[list[PIL.Image.Image],numpy.ndarray,torch.Tensor]" + "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" } ], "requiredInputs": [ "batch_size", - "height", - "image_latents", - "img_ids", + "dtype", "latents", + "negative_prompt_embeds", "num_inference_steps", - "pooled_prompt_embeds", "prompt_embeds", - "timesteps", - "txt_ids", - "width" + "timesteps" + ], + "schemaVersion": 1, + "variadicInputs": [] + }, + { + "className": "SequentialPipelineBlocks", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder", + "type": "transformers.models.qwen3.modeling_qwen3.Qwen3ForCausalLM" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "tokenizer", + "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 4.0 + }, + "description": "", + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_flux2.Flux2Transformer2DModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_flux2.AutoencoderKLFlux2" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_latent_channels": 32, + "vae_scale_factor": 16 + }, + "description": "", + "name": "image_processor", + "type": "diffusers.pipelines.flux2.image_processor.Flux2ImageProcessor" + } + ], + "configs": [ + { + "default": false, + "description": "", + "name": "is_distilled" + } + ], + "contentHash": "sha256:49be77fa455037eeb50c5457c52ebe86f3ff1bce68290708bd9d3be40dcd3612", + "description": "", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:49be77fa455037eeb50c5457c52ebe86f3ff1bce68290708bd9d3be40dcd3612", + "inputs": [ + { + "default": null, + "description": "", + "kwargsType": null, + "name": "prompt", + "required": false, + "type": "opaque" + }, + { + "default": 512, + "description": "", + "kwargsType": null, + "name": "max_sequence_length", + "required": false, + "type": "builtins.int" + }, + { + "default": [ + 9, + 18, + 27 + ], + "description": "", + "kwargsType": null, + "name": "text_encoder_out_layers", + "required": false, + "type": "tuple[builtins.int]" + }, + { + "default": 1, + "description": "", + "kwargsType": null, + "name": "num_images_per_prompt", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "UnionType[builtins.NoneType,torch.Tensor]" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "generator", + "required": false, + "type": "opaque" + }, + { + "default": 50, + "description": "", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "timesteps", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "joint_attention_kwargs", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "Packed image latents for conditioning. Shape: (B, img_seq_len, C)", + "kwargsType": null, + "name": "image_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Position IDs for image latents. Shape: (B, img_seq_len, 4)", + "kwargsType": null, + "name": "image_latent_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": "pil", + "description": "", + "kwargsType": null, + "name": "output_type", + "required": false, + "type": "opaque" + } + ], + "kind": "sequential", + "outputs": [ + { + "default": null, + "description": "Text embeddings from qwen3 used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Negative text embeddings from qwen3 used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", + "kwargsType": null, + "name": "batch_size", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "kwargsType": null, + "name": "dtype", + "required": false, + "type": "torch.dtype" + }, + { + "default": null, + "description": "The initial latents to use for the denoising process", + "kwargsType": null, + "name": "latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Position IDs for the latents (for RoPE)", + "kwargsType": null, + "name": "latent_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The timesteps to use for inference", + "kwargsType": null, + "name": "timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The number of denoising steps to perform at inference time", + "kwargsType": null, + "name": "num_inference_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "4D position IDs (T, H, W, L) for text tokens, used for RoPE calculation.", + "kwargsType": "denoiser_input_fields", + "name": "txt_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "4D position IDs (T, H, W, L) for negative text tokens, used for RoPE calculation.", + "kwargsType": "denoiser_input_fields", + "name": "negative_txt_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", + "kwargsType": null, + "name": "images", + "required": false, + "type": "Union[list[PIL.Image.Image],numpy.ndarray,torch.Tensor]" + } + ], + "requiredInputs": [ + "batch_size", + "latent_ids", + "latents", + "num_inference_steps", + "prompt_embeds", + "timesteps", + "txt_ids" ], "schemaVersion": 1, "variadicInputs": [] @@ -32517,7 +33063,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -32529,7 +33075,7 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_pretrained", @@ -32547,9 +33093,9 @@ } ], "configs": [], - "contentHash": "sha256:3beeae593a6f8c8cbac2fdc75833336af18c0e7dccc7ef92bc4dbe3f320ed0a9", + "contentHash": "sha256:4a1d454b66bec210dc720fb0a73c8888b91862e244fdcdd501c5eaf51e06d3ab", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:3beeae593a6f8c8cbac2fdc75833336af18c0e7dccc7ef92bc4dbe3f320ed0a9", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:4a1d454b66bec210dc720fb0a73c8888b91862e244fdcdd501c5eaf51e06d3ab", "inputs": [ { "default": null, @@ -32970,110 +33516,88 @@ "className": "SequentialPipelineBlocks", "components": [ { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_encoder", - "type": "transformers.modeling_utils.PreTrainedModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", - "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "connectors", - "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "duration_head", - "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 16 + }, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "image_resize_processor", + "type": "diffusers.image_processor.VaeImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + "name": "text_encoder", + "type": "transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLForConditionalGeneration" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "audio_vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" + "name": "processor", + "type": "transformers.models.qwen2_vl.processing_qwen2_vl.Qwen2VLProcessor" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 3.0, - "modality_scale": 3.0, - "spatio_temporal_guidance_blocks": [ - 28 - ], - "stg_scale": 1.0 + "guidance_scale": 4.0 }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 7.0, - "modality_scale": 3.0, - "stg_scale": 1.0 + "vae_scale_factor": 16 }, "description": "", - "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" + "type": "diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage" }, { "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 32 - }, + "defaultConfig": null, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "pachifier", + "type": "diffusers.modular_pipelines.qwenimage.modular_pipeline.QwenImagePachifier" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vocoder", - "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_qwenimage.QwenImageTransformer2DModel" } ], "configs": [], - "contentHash": "sha256:47156a49ab67b15c0926f6a7a92ea629712fa01fa2cff4c66773593db736b727", + "contentHash": "sha256:4a9f5ab28148568f618043010ae3cab3e2bca5c6ef98419e131c1eaba6ee0394", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:47156a49ab67b15c0926f6a7a92ea629712fa01fa2cff4c66773593db736b727", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:4a9f5ab28148568f618043010ae3cab3e2bca5c6ef98419e131c1eaba6ee0394", "inputs": [ + { + "default": null, + "description": "Reference image(s) for denoising. Can be a single image or list of images.", + "kwargsType": null, + "name": "image", + "required": true, + "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" + }, { "default": null, "description": "The prompt or prompts to guide image generation.", @@ -33091,83 +33615,35 @@ "type": "builtins.str" }, { - "default": 1024, - "description": "Maximum sequence length for prompt encoding.", - "kwargsType": null, - "name": "max_sequence_length", - "required": false, - "type": "builtins.int" - }, - { - "default": 1.0, - "description": "Lower bound on the auto-predicted duration.", - "kwargsType": null, - "name": "min_seconds", - "required": false, - "type": "builtins.float" - }, - { - "default": 20.0, - "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", - "kwargsType": null, - "name": "max_seconds", - "required": false, - "type": "builtins.float" - }, - { - "default": 24.0, - "description": "Frames per second of the generated video.", + "default": null, + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "frame_rate", + "name": "generator", "required": false, - "type": "builtins.float" + "type": "torch._C.Generator" }, { "default": 1, "description": "The number of images to generate per prompt.", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "num_images_per_prompt", "required": false, "type": "builtins.int" }, - { - "default": 30, - "description": "The number of denoising steps.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "Timesteps for the denoising process.", - "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "torch.Tensor" - }, { "default": null, - "description": "Custom sigmas for the denoising process.", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "list[builtins.float]" - }, - { - "default": 512, "description": "The height in pixels of the generated image.", "kwargsType": null, "name": "height", - "required": false, + "required": true, "type": "builtins.int" }, { - "default": 704, + "default": null, "description": "The width in pixels of the generated image.", "kwargsType": null, "name": "width", - "required": false, + "required": true, "type": "builtins.int" }, { @@ -33179,36 +33655,20 @@ "type": "torch.Tensor" }, { - "default": null, - "description": "Interpolation factor between random noise and any provided latents. `None` (default) resolves to 0.0, which keeps the provided latents.", - "kwargsType": null, - "name": "noise_scale", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Torch generator for deterministic generation.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" - }, - { - "default": null, - "description": "Optional pre-encoded audio latents; random noise is used when not provided.", + "default": 50, + "description": "The number of denoising steps.", "kwargsType": null, - "name": "audio_latents", + "name": "num_inference_steps", "required": true, - "type": "torch.Tensor" + "type": "builtins.int" }, { - "default": true, - "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", + "default": null, + "description": "Custom sigmas for the denoising process.", "kwargsType": null, - "name": "use_cross_timestep", + "name": "sigmas", "required": false, - "type": "builtins.bool" + "type": "list[builtins.float]" }, { "default": null, @@ -33225,157 +33685,117 @@ "name": "output_type", "required": false, "type": "builtins.str" - }, - { - "default": 0.0, - "description": "The timestep at which the VAE decodes the final latents.", - "kwargsType": null, - "name": "decode_timestep", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "Noise interpolation factor applied to the latents at the decode timestep.", - "kwargsType": null, - "name": "decode_noise_scale", - "required": false, - "type": "opaque" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "Packed per-layer Gemma hidden states for the prompt.", + "description": "The resized images", "kwargsType": null, - "name": "prompt_embeds", + "name": "resized_image", "required": false, - "type": "torch.Tensor" + "type": "list[PIL.Image.Image]" }, { "default": null, - "description": "Binary attention mask for `prompt_embeds`.", - "kwargsType": null, - "name": "prompt_attention_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Packed per-layer Gemma hidden states for the negative prompt.", - "kwargsType": null, - "name": "negative_prompt_embeds", + "description": "The prompt embeddings.", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Binary attention mask for `negative_prompt_embeds`.", - "kwargsType": null, - "name": "negative_prompt_attention_mask", + "description": "The encoder attention mask.", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The number of prompts being denoised (before per-prompt expansion).", - "kwargsType": null, - "name": "batch_size", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The dtype of the prompt embeddings.", - "kwargsType": null, - "name": "dtype", - "required": false, - "type": "torch.dtype" - }, - { - "default": null, - "description": "Video-branch text conditioning (cond).", - "kwargsType": null, - "name": "connector_prompt_embeds", + "description": "The negative prompt embeddings.", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio-branch text conditioning (cond).", - "kwargsType": null, - "name": "connector_audio_prompt_embeds", + "description": "The negative prompt embeddings mask.", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Binary text attention mask (cond).", + "description": "The processed image", "kwargsType": null, - "name": "connector_attention_mask", + "name": "processed_image", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Video-branch text conditioning (uncond).", + "description": "The latent representation of the input image.", "kwargsType": null, - "name": "negative_connector_prompt_embeds", + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio-branch text conditioning (uncond).", + "description": "The batch size of the prompt embeddings", "kwargsType": null, - "name": "negative_connector_audio_prompt_embeds", + "name": "batch_size", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Binary text attention mask (uncond).", + "description": "The data type of the prompt embeddings", "kwargsType": null, - "name": "negative_connector_attention_mask", + "name": "dtype", "required": false, - "type": "torch.Tensor" + "type": "torch.dtype" }, { "default": null, - "description": "The predicted number of frames to generate.", + "description": "The image height calculated from the image latents dimension", "kwargsType": null, - "name": "num_frames", + "name": "image_height", "required": false, "type": "builtins.int" }, { "default": null, - "description": "", + "description": "The image width calculated from the image latents dimension", "kwargsType": null, - "name": "timesteps", + "name": "image_width", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "", + "description": "if not provided, updated to image height", "kwargsType": null, - "name": "num_inference_steps", + "name": "height", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", + "description": "if not provided, updated to image width", "kwargsType": null, - "name": "audio_scheduler", + "name": "width", "required": false, - "type": "opaque" + "type": "builtins.int" }, { "default": null, - "description": "Packed noisy video latents.", + "description": "The initial latents to use for the denoising process", "kwargsType": null, "name": "latents", "required": false, @@ -33383,81 +33803,46 @@ }, { "default": null, - "description": "The resolved interpolation factor, forwarded to the audio latents step.", - "kwargsType": null, - "name": "noise_scale", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Packed noisy audio latents.", + "description": "The timesteps to use for the denoising process", "kwargsType": null, - "name": "audio_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Number of audio latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "audio_num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Video RoPE patch coordinates.", - "kwargsType": "denoiser_input_fields", - "name": "video_coords", + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio RoPE patch coordinates.", + "description": "The shapes of the images latents, used for RoPE calculation", "kwargsType": "denoiser_input_fields", - "name": "audio_coords", + "name": "img_shapes", "required": false, - "type": "torch.Tensor" + "type": "list[list[tuple[builtins.int]]]" }, { "default": null, - "description": "The generated videos.", + "description": "Generated images. (tensor output of the vae decoder.)", "kwargsType": null, - "name": "videos", + "name": "images", "required": false, "type": "list[PIL.Image.Image]" - }, - { - "default": null, - "description": "The generated audio waveform.", - "kwargsType": null, - "name": "audio", - "required": false, - "type": "torch.Tensor" } ], "requiredInputs": [ - "audio_latents", - "audio_num_frames", - "audio_scheduler", - "batch_size", - "connector_attention_mask", - "connector_audio_prompt_embeds", - "connector_prompt_embeds", - "dtype", + "height", + "image", + "image_height", + "image_latents", + "image_width", + "images", + "img_shapes", "latents", - "negative_connector_attention_mask", - "negative_connector_audio_prompt_embeds", - "negative_connector_prompt_embeds", - "negative_prompt_attention_mask", - "negative_prompt_embeds", "num_inference_steps", + "processed_image", "prompt", - "prompt_attention_mask", "prompt_embeds", - "timesteps" + "prompt_embeds_mask", + "resized_image", + "timesteps", + "width" ], "schemaVersion": 1, "variadicInputs": [ @@ -33478,59 +33863,66 @@ "defaultConfig": null, "description": "", "name": "text_encoder", - "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" + "type": "transformers.models.qwen3.modeling_qwen3.Qwen3ForCausalLM" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_scale": 5.0 + "guidance_scale": 4.0 }, "description": "", "name": "guider", "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_latent_channels": 32, + "vae_scale_factor": 16 + }, + "description": "", + "name": "image_processor", + "type": "diffusers.pipelines.flux2.image_processor.Flux2ImageProcessor" + }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_flux2.AutoencoderKLFlux2" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" - }, + "name": "transformer", + "type": "diffusers.models.transformers.transformer_flux2.Flux2Transformer2DModel" + } + ], + "configs": [ { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 8 - }, + "default": false, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "is_distilled" } ], - "configs": [], - "contentHash": "sha256:48ea6514c75263df949ce1398f486626a43c0e1f5c399f6fb4b0812db5164d32", + "contentHash": "sha256:4d2f4482fa6d8d4af31a4118dff2c567ea2ebcd3cd0d4ae847dd87cc5be6d8f9", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:48ea6514c75263df949ce1398f486626a43c0e1f5c399f6fb4b0812db5164d32", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:4d2f4482fa6d8d4af31a4118dff2c567ea2ebcd3cd0d4ae847dd87cc5be6d8f9", "inputs": [ { "default": null, @@ -33541,90 +33933,94 @@ "type": "opaque" }, { - "default": null, + "default": 512, "description": "", "kwargsType": null, - "name": "negative_prompt", + "name": "max_sequence_length", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": 512, + "default": [ + 9, + 18, + 27 + ], "description": "", "kwargsType": null, - "name": "max_sequence_length", + "name": "text_encoder_out_layers", "required": false, - "type": "opaque" + "type": "tuple[builtins.int]" }, { - "default": 1, + "default": null, "description": "", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "image", "required": false, "type": "opaque" }, { - "default": 50, + "default": null, "description": "", "kwargsType": null, - "name": "num_inference_steps", - "required": true, + "name": "height", + "required": false, "type": "opaque" }, { "default": null, "description": "", "kwargsType": null, - "name": "timesteps", - "required": true, + "name": "width", + "required": false, "type": "opaque" }, { "default": null, "description": "", "kwargsType": null, - "name": "sigmas", + "name": "generator", "required": false, "type": "opaque" }, { - "default": null, + "default": 1, "description": "", "kwargsType": null, - "name": "height", + "name": "num_images_per_prompt", "required": false, - "type": "builtins.int" + "type": "opaque" }, { "default": null, "description": "", "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" + "name": "latents", + "required": true, + "type": "UnionType[builtins.NoneType,torch.Tensor]" }, { - "default": null, + "default": 50, "description": "", "kwargsType": null, - "name": "num_frames", - "required": false, - "type": "builtins.int" + "name": "num_inference_steps", + "required": true, + "type": "opaque" }, { "default": null, "description": "", "kwargsType": null, - "name": "latents", + "name": "timesteps", "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" + "type": "opaque" }, { "default": null, "description": "", "kwargsType": null, - "name": "generator", + "name": "sigmas", "required": false, "type": "opaque" }, @@ -33632,24 +34028,24 @@ "default": null, "description": "", "kwargsType": null, - "name": "attention_kwargs", + "name": "joint_attention_kwargs", "required": false, "type": "opaque" }, { - "default": "np", - "description": "The output type of the decoded videos", + "default": "pil", + "description": "", "kwargsType": null, "name": "output_type", "required": false, - "type": "builtins.str" + "type": "opaque" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "text embeddings used to guide the image generation", + "description": "Text embeddings from qwen3 used to guide the image generation", "kwargsType": "denoiser_input_fields", "name": "prompt_embeds", "required": false, @@ -33657,7 +34053,7 @@ }, { "default": null, - "description": "negative text embeddings used to guide the image generation", + "description": "Negative text embeddings from qwen3 used to guide the image generation", "kwargsType": "denoiser_input_fields", "name": "negative_prompt_embeds", "required": false, @@ -33665,7 +34061,23 @@ }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", + "description": "", + "kwargsType": null, + "name": "condition_images", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "List of latent representations for each reference image", + "kwargsType": null, + "name": "image_latents", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", "kwargsType": null, "name": "batch_size", "required": false, @@ -33673,7 +34085,7 @@ }, { "default": null, - "description": "Data type of model tensor inputs (determined by `transformer.dtype`)", + "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", "kwargsType": null, "name": "dtype", "required": false, @@ -33689,21 +34101,69 @@ }, { "default": null, - "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "description": "Position IDs for the latents (for RoPE)", "kwargsType": null, - "name": "videos", + "name": "latent_ids", "required": false, - "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Position IDs for image latents", + "kwargsType": null, + "name": "image_latent_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The timesteps to use for inference", + "kwargsType": null, + "name": "timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The number of denoising steps to perform at inference time", + "kwargsType": null, + "name": "num_inference_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "4D position IDs (T, H, W, L) for text tokens, used for RoPE calculation.", + "kwargsType": "denoiser_input_fields", + "name": "txt_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "4D position IDs (T, H, W, L) for negative text tokens, used for RoPE calculation.", + "kwargsType": "denoiser_input_fields", + "name": "negative_txt_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", + "kwargsType": null, + "name": "images", + "required": false, + "type": "Union[list[PIL.Image.Image],numpy.ndarray,torch.Tensor]" } ], "requiredInputs": [ "batch_size", - "dtype", + "latent_ids", "latents", - "negative_prompt_embeds", "num_inference_steps", "prompt_embeds", - "timesteps" + "timesteps", + "txt_ids" ], "schemaVersion": 1, "variadicInputs": [] @@ -33728,54 +34188,45 @@ { "creationMethod": "from_config", "defaultConfig": { - "guidance_scale": 4.0 + "vae_latent_channels": 32, + "vae_scale_factor": 16 }, "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + "name": "image_processor", + "type": "diffusers.pipelines.flux2.image_processor.Flux2ImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_flux2.AutoencoderKLFlux2" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_flux2.Flux2Transformer2DModel" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_flux2.AutoencoderKLFlux2" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_latent_channels": 32, - "vae_scale_factor": 16 - }, - "description": "", - "name": "image_processor", - "type": "diffusers.pipelines.flux2.image_processor.Flux2ImageProcessor" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_flux2.Flux2Transformer2DModel" } ], "configs": [ { - "default": false, + "default": true, "description": "", "name": "is_distilled" } ], - "contentHash": "sha256:49be77fa455037eeb50c5457c52ebe86f3ff1bce68290708bd9d3be40dcd3612", + "contentHash": "sha256:4ebd3f81d7a385e226b22aad82f5d3e1ac74e55de0af990e7e99a47d6d7d4438", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:49be77fa455037eeb50c5457c52ebe86f3ff1bce68290708bd9d3be40dcd3612", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:4ebd3f81d7a385e226b22aad82f5d3e1ac74e55de0af990e7e99a47d6d7d4438", "inputs": [ { "default": null, @@ -33806,10 +34257,10 @@ "type": "tuple[builtins.int]" }, { - "default": 1, + "default": null, "description": "", "kwargsType": null, - "name": "num_images_per_prompt", + "name": "image", "required": false, "type": "opaque" }, @@ -33819,7 +34270,7 @@ "kwargsType": null, "name": "height", "required": false, - "type": "builtins.int" + "type": "opaque" }, { "default": null, @@ -33827,24 +34278,32 @@ "kwargsType": null, "name": "width", "required": false, - "type": "builtins.int" + "type": "opaque" }, { "default": null, "description": "", "kwargsType": null, - "name": "latents", - "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" + "name": "generator", + "required": false, + "type": "opaque" }, { - "default": null, + "default": 1, "description": "", "kwargsType": null, - "name": "generator", + "name": "num_images_per_prompt", "required": false, "type": "opaque" }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "UnionType[builtins.NoneType,torch.Tensor]" + }, { "default": 50, "description": "", @@ -33877,22 +34336,6 @@ "required": false, "type": "opaque" }, - { - "default": null, - "description": "Packed image latents for conditioning. Shape: (B, img_seq_len, C)", - "kwargsType": null, - "name": "image_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Position IDs for image latents. Shape: (B, img_seq_len, 4)", - "kwargsType": null, - "name": "image_latent_ids", - "required": false, - "type": "torch.Tensor" - }, { "default": "pil", "description": "", @@ -33914,11 +34357,19 @@ }, { "default": null, - "description": "Negative text embeddings from qwen3 used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", + "description": "", + "kwargsType": null, + "name": "condition_images", "required": false, - "type": "torch.Tensor" + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "List of latent representations for each reference image", + "kwargsType": null, + "name": "image_latents", + "required": false, + "type": "list[torch.Tensor]" }, { "default": null, @@ -33936,6 +34387,14 @@ "required": false, "type": "torch.dtype" }, + { + "default": null, + "description": "Position IDs for image latents", + "kwargsType": null, + "name": "image_latent_ids", + "required": false, + "type": "torch.Tensor" + }, { "default": null, "description": "The initial latents to use for the denoising process", @@ -33976,14 +34435,6 @@ "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "4D position IDs (T, H, W, L) for negative text tokens, used for RoPE calculation.", - "kwargsType": "denoiser_input_fields", - "name": "negative_txt_ids", - "required": false, - "type": "torch.Tensor" - }, { "default": null, "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", @@ -34008,28 +34459,19 @@ { "className": "SequentialPipelineBlocks", "components": [ - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 16 - }, - "description": "", - "name": "image_resize_processor", - "type": "diffusers.image_processor.VaeImageProcessor" - }, { "creationMethod": "from_pretrained", "defaultConfig": null, - "description": "", + "description": "The text encoder to use", "name": "text_encoder", "type": "transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLForConditionalGeneration" }, { "creationMethod": "from_pretrained", "defaultConfig": null, - "description": "", - "name": "processor", - "type": "transformers.models.qwen2_vl.processing_qwen2_vl.Qwen2VLProcessor" + "description": "The tokenizer to use", + "name": "tokenizer", + "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" }, { "creationMethod": "from_config", @@ -34040,22 +34482,6 @@ "name": "guider", "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 16 - }, - "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage" - }, { "creationMethod": "from_config", "defaultConfig": null, @@ -34076,23 +34502,31 @@ "description": "", "name": "transformer", "type": "diffusers.models.transformers.transformer_qwenimage.QwenImageTransformer2DModel" - } - ], - "configs": [], - "contentHash": "sha256:4a9f5ab28148568f618043010ae3cab3e2bca5c6ef98419e131c1eaba6ee0394", - "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:4a9f5ab28148568f618043010ae3cab3e2bca5c6ef98419e131c1eaba6ee0394", - "inputs": [ - { - "default": null, - "description": "Reference image(s) for denoising. Can be a single image or list of images.", - "kwargsType": null, - "name": "image", - "required": true, - "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" }, { - "default": null, + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 16 + }, + "description": "", + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" + } + ], + "configs": [], + "contentHash": "sha256:52a16cf5378ec63bbcfb09334264c602829a3a9d00d33532fd424b60d8d65a77", + "description": "", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:52a16cf5378ec63bbcfb09334264c602829a3a9d00d33532fd424b60d8d65a77", + "inputs": [ + { + "default": null, "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", @@ -34108,12 +34542,12 @@ "type": "builtins.str" }, { - "default": null, - "description": "Torch generator for deterministic generation.", + "default": 1024, + "description": "Maximum sequence length for prompt encoding.", "kwargsType": null, - "name": "generator", + "name": "max_sequence_length", "required": false, - "type": "torch._C.Generator" + "type": "builtins.int" }, { "default": 1, @@ -34123,6 +34557,14 @@ "required": false, "type": "builtins.int" }, + { + "default": null, + "description": "Pre-generated noisy latents for image generation.", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "torch.Tensor" + }, { "default": null, "description": "The height in pixels of the generated image.", @@ -34141,11 +34583,11 @@ }, { "default": null, - "description": "Pre-generated noisy latents for image generation.", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" + "name": "generator", + "required": false, + "type": "torch._C.Generator" }, { "default": 50, @@ -34182,14 +34624,6 @@ ], "kind": "sequential", "outputs": [ - { - "default": null, - "description": "The resized images", - "kwargsType": null, - "name": "resized_image", - "required": false, - "type": "list[PIL.Image.Image]" - }, { "default": null, "description": "The prompt embeddings.", @@ -34222,22 +34656,6 @@ "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "The processed image", - "kwargsType": null, - "name": "processed_image", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The latent representation of the input image.", - "kwargsType": null, - "name": "image_latents", - "required": false, - "type": "torch.Tensor" - }, { "default": null, "description": "The batch size of the prompt embeddings", @@ -34256,23 +34674,7 @@ }, { "default": null, - "description": "The image height calculated from the image latents dimension", - "kwargsType": null, - "name": "image_height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The image width calculated from the image latents dimension", - "kwargsType": null, - "name": "image_width", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "if not provided, updated to image height", + "description": "if not set, updated to default value", "kwargsType": null, "name": "height", "required": false, @@ -34280,7 +34682,7 @@ }, { "default": null, - "description": "if not provided, updated to image width", + "description": "if not set, updated to default value", "kwargsType": null, "name": "width", "required": false, @@ -34321,19 +34723,13 @@ ], "requiredInputs": [ "height", - "image", - "image_height", - "image_latents", - "image_width", "images", "img_shapes", "latents", "num_inference_steps", - "processed_image", "prompt", "prompt_embeds", "prompt_embeds_mask", - "resized_image", "timesteps", "width" ], @@ -34355,41 +34751,36 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder", - "type": "transformers.models.qwen3.modeling_qwen3.Qwen3ForCausalLM" + "name": "tokenizer", + "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "tokenizer", - "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" + "name": "language_model", + "type": "transformers.models.qwen3.modeling_qwen3.Qwen3ForCausalLM" }, { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 4.0 - }, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + "name": "rvq_depth_decoder", + "type": "diffusers.models.transformers.minimax_music3_rvq_depth_decoder.MiniMaxMusic3RVQDepthDecoder" }, { - "creationMethod": "from_config", - "defaultConfig": { - "vae_latent_channels": 32, - "vae_scale_factor": 16 - }, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "image_processor", - "type": "diffusers.pipelines.flux2.image_processor.Flux2ImageProcessor" + "name": "condition_encoder", + "type": "diffusers.models.condition_embedders.condition_embedder_minimax_music3.MiniMaxMusic3ConditionEncoder" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_flux2.AutoencoderKLFlux2" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_minimax_music3.MiniMaxMusic3Transformer1DModel" }, { "creationMethod": "from_pretrained", @@ -34399,264 +34790,126 @@ "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 1.7 + }, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_flux2.Flux2Transformer2DModel" - } - ], - "configs": [ + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, { - "default": false, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "is_distilled" + "name": "vocoder", + "type": "diffusers.models.autoencoders.minimax_music3_vocoder.MiniMaxMusic3Vocoder" } ], - "contentHash": "sha256:4d2f4482fa6d8d4af31a4118dff2c567ea2ebcd3cd0d4ae847dd87cc5be6d8f9", + "configs": [], + "contentHash": "sha256:55e8d2ae4992e28fbdd67d0e6404ce0e7af3aa04fecb9fe4f4a75cb213742f5c", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:4d2f4482fa6d8d4af31a4118dff2c567ea2ebcd3cd0d4ae847dd87cc5be6d8f9", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:55e8d2ae4992e28fbdd67d0e6404ce0e7af3aa04fecb9fe4f4a75cb213742f5c", "inputs": [ { "default": null, - "description": "", + "description": "The music description (genre, mood, vocals, instrumentation, arrangement).", "kwargsType": null, "name": "prompt", - "required": false, - "type": "opaque" - }, - { - "default": 512, - "description": "", - "kwargsType": null, - "name": "max_sequence_length", - "required": false, - "type": "builtins.int" - }, - { - "default": [ - 9, - 18, - 27 - ], - "description": "", - "kwargsType": null, - "name": "text_encoder_out_layers", - "required": false, - "type": "tuple[builtins.int]" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "image", - "required": false, - "type": "opaque" + "required": true, + "type": "builtins.str" }, { "default": null, - "description": "", + "description": "The lyrics to sing. Structure tags such as `[verse]` or `[chorus]` must each be on their own line; text on the same line as a leading tag is dropped by the checkpoint's input contract.", "kwargsType": null, - "name": "height", - "required": false, - "type": "opaque" + "name": "lyrics", + "required": true, + "type": "builtins.str" }, { - "default": null, - "description": "", + "default": 60.0, + "description": "Upper bound on the generated audio length in seconds. The language model may stop earlier. Capped at 9000 frames (six minutes).", "kwargsType": null, - "name": "width", + "name": "audio_duration", "required": false, - "type": "opaque" + "type": "builtins.float" }, { "default": null, - "description": "", + "description": "Torch generator for deterministic generation.", "kwargsType": null, "name": "generator", "required": false, - "type": "opaque" - }, - { - "default": 1, - "description": "", - "kwargsType": null, - "name": "num_images_per_prompt", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" + "type": "torch._C.Generator" }, { - "default": 50, - "description": "", + "default": 30, + "description": "Number of flow-matching Euler steps per chunk.", "kwargsType": null, "name": "num_inference_steps", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "joint_attention_kwargs", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": "pil", - "description": "", + "default": "np", + "description": "Output format: 'np' or 'pt'.", "kwargsType": null, "name": "output_type", "required": false, - "type": "opaque" + "type": "builtins.str" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "Text embeddings from qwen3 used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Negative text embeddings from qwen3 used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "condition_images", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "List of latent representations for each reference image", - "kwargsType": null, - "name": "image_latents", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", - "kwargsType": null, - "name": "batch_size", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", - "kwargsType": null, - "name": "dtype", - "required": false, - "type": "torch.dtype" - }, - { - "default": null, - "description": "The initial latents to use for the denoising process", - "kwargsType": null, - "name": "latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Position IDs for the latents (for RoPE)", + "description": "Token ids of shape `[2, sequence_length]` holding the conditional prompt and its classifier-free counterpart (every token except the first and the two trailing structure tokens replaced by the audio-CFG token).", "kwargsType": null, - "name": "latent_ids", + "name": "text_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Position IDs for image latents", + "description": "Concatenated per-frame hidden states of shape `[1, frames, num_codebooks * hidden_size]` that condition the flow-matching stage.", "kwargsType": null, - "name": "image_latent_ids", + "name": "frame_hiddens", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The timesteps to use for inference", + "description": "Frame index at which each 200-frame denoising window starts.", "kwargsType": null, - "name": "timesteps", + "name": "chunk_starts", "required": false, - "type": "torch.Tensor" + "type": "builtins.list" }, { "default": null, - "description": "The number of denoising steps to perform at inference time", + "description": "List of per-window denoised latent tensors (uncropped).", "kwargsType": null, - "name": "num_inference_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "4D position IDs (T, H, W, L) for text tokens, used for RoPE calculation.", - "kwargsType": "denoiser_input_fields", - "name": "txt_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "4D position IDs (T, H, W, L) for negative text tokens, used for RoPE calculation.", - "kwargsType": "denoiser_input_fields", - "name": "negative_txt_ids", + "name": "latent_chunks", "required": false, - "type": "torch.Tensor" + "type": "builtins.list" }, { "default": null, - "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", + "description": "The generated stereo waveform of shape `(batch, channels, samples)` in `[-1, 1]`.", "kwargsType": null, - "name": "images", + "name": "audios", "required": false, - "type": "Union[list[PIL.Image.Image],numpy.ndarray,torch.Tensor]" + "type": "UnionType[numpy.ndarray,torch.Tensor]" } ], "requiredInputs": [ - "batch_size", - "latent_ids", - "latents", - "num_inference_steps", - "prompt_embeds", - "timesteps", - "txt_ids" + "chunk_starts", + "frame_hiddens", + "latent_chunks", + "lyrics", + "prompt", + "text_ids" ], "schemaVersion": 1, "variadicInputs": [] @@ -34669,57 +34922,81 @@ "defaultConfig": null, "description": "", "name": "text_encoder", - "type": "transformers.models.qwen3.modeling_qwen3.Qwen3ForCausalLM" + "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "tokenizer", - "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "vae_latent_channels": 32, - "vae_scale_factor": 16 + "guidance_scale": 5.0 }, "description": "", - "name": "image_processor", - "type": "diffusers.pipelines.flux2.image_processor.Flux2ImageProcessor" + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_flux2.AutoencoderKLFlux2" + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 8 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 3.0 + }, + "description": "", + "name": "guider_2", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_flux2.Flux2Transformer2DModel" + "name": "transformer_2", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" } ], "configs": [ { - "default": true, - "description": "", - "name": "is_distilled" + "default": 0.875, + "description": "The boundary ratio to divide the denoising loop into high noise and low noise stages.", + "name": "boundary_ratio" } ], - "contentHash": "sha256:4ebd3f81d7a385e226b22aad82f5d3e1ac74e55de0af990e7e99a47d6d7d4438", + "contentHash": "sha256:5792eccf29e0767786e025f8dbc6c42a3f1d0c951b1be650328c52bcf878d8ee", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:4ebd3f81d7a385e226b22aad82f5d3e1ac74e55de0af990e7e99a47d6d7d4438", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:5792eccf29e0767786e025f8dbc6c42a3f1d0c951b1be650328c52bcf878d8ee", "inputs": [ { "default": null, @@ -34730,48 +35007,52 @@ "type": "opaque" }, { - "default": 512, + "default": null, "description": "", "kwargsType": null, - "name": "max_sequence_length", + "name": "negative_prompt", "required": false, - "type": "builtins.int" + "type": "opaque" }, { - "default": [ - 9, - 18, - 27 - ], + "default": 512, "description": "", "kwargsType": null, - "name": "text_encoder_out_layers", + "name": "max_sequence_length", "required": false, - "type": "tuple[builtins.int]" + "type": "opaque" }, { "default": null, "description": "", "kwargsType": null, "name": "image", - "required": false, - "type": "opaque" + "required": true, + "type": "PIL.Image.Image" }, { - "default": null, + "default": 480, "description": "", "kwargsType": null, "name": "height", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": null, + "default": 832, "description": "", "kwargsType": null, "name": "width", "required": false, - "type": "opaque" + "type": "builtins.int" + }, + { + "default": 81, + "description": "", + "kwargsType": null, + "name": "num_frames", + "required": true, + "type": "builtins.int" }, { "default": null, @@ -34785,18 +35066,10 @@ "default": 1, "description": "", "kwargsType": null, - "name": "num_images_per_prompt", + "name": "num_videos_per_prompt", "required": false, "type": "opaque" }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" - }, { "default": 50, "description": "", @@ -34825,24 +35098,32 @@ "default": null, "description": "", "kwargsType": null, - "name": "joint_attention_kwargs", + "name": "latents", + "required": true, + "type": "UnionType[builtins.NoneType,torch.Tensor]" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "attention_kwargs", "required": false, "type": "opaque" }, { - "default": "pil", - "description": "", + "default": "np", + "description": "The output type of the decoded videos", "kwargsType": null, "name": "output_type", "required": false, - "type": "opaque" + "type": "builtins.str" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "Text embeddings from qwen3 used to guide the image generation", + "description": "text embeddings used to guide the image generation", "kwargsType": "denoiser_input_fields", "name": "prompt_embeds", "required": false, @@ -34850,308 +35131,39 @@ }, { "default": null, - "description": "", - "kwargsType": null, - "name": "condition_images", + "description": "negative text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", "required": false, - "type": "list[torch.Tensor]" + "type": "torch.Tensor" }, { "default": null, - "description": "List of latent representations for each reference image", + "description": "", "kwargsType": null, - "name": "image_latents", + "name": "resized_image", "required": false, - "type": "list[torch.Tensor]" + "type": "PIL.Image.Image" }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", + "description": "video latent representation with the first frame image condition", "kwargsType": null, - "name": "batch_size", + "name": "first_frame_latents", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "description": "", "kwargsType": null, - "name": "dtype", + "name": "image_condition_latents", "required": false, - "type": "torch.dtype" - }, - { - "default": null, - "description": "Position IDs for image latents", - "kwargsType": null, - "name": "image_latent_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The initial latents to use for the denoising process", - "kwargsType": null, - "name": "latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Position IDs for the latents (for RoPE)", - "kwargsType": null, - "name": "latent_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The timesteps to use for inference", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The number of denoising steps to perform at inference time", - "kwargsType": null, - "name": "num_inference_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "4D position IDs (T, H, W, L) for text tokens, used for RoPE calculation.", - "kwargsType": "denoiser_input_fields", - "name": "txt_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", - "kwargsType": null, - "name": "images", - "required": false, - "type": "Union[list[PIL.Image.Image],numpy.ndarray,torch.Tensor]" - } - ], - "requiredInputs": [ - "batch_size", - "latent_ids", - "latents", - "num_inference_steps", - "prompt_embeds", - "timesteps", - "txt_ids" - ], - "schemaVersion": 1, - "variadicInputs": [] - }, - { - "className": "SequentialPipelineBlocks", - "components": [ - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "The text encoder to use", - "name": "text_encoder", - "type": "transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLForConditionalGeneration" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "The tokenizer to use", - "name": "tokenizer", - "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 4.0 - }, - "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_config", - "defaultConfig": null, - "description": "", - "name": "pachifier", - "type": "diffusers.modular_pipelines.qwenimage.modular_pipeline.QwenImagePachifier" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_qwenimage.QwenImageTransformer2DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 16 - }, - "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" - } - ], - "configs": [], - "contentHash": "sha256:52a16cf5378ec63bbcfb09334264c602829a3a9d00d33532fd424b60d8d65a77", - "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:52a16cf5378ec63bbcfb09334264c602829a3a9d00d33532fd424b60d8d65a77", - "inputs": [ - { - "default": null, - "description": "The prompt or prompts to guide image generation.", - "kwargsType": null, - "name": "prompt", - "required": true, - "type": "builtins.str" - }, - { - "default": null, - "description": "The prompt or prompts not to guide the image generation.", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "builtins.str" - }, - { - "default": 1024, - "description": "Maximum sequence length for prompt encoding.", - "kwargsType": null, - "name": "max_sequence_length", - "required": false, - "type": "builtins.int" - }, - { - "default": 1, - "description": "The number of images to generate per prompt.", - "kwargsType": null, - "name": "num_images_per_prompt", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Pre-generated noisy latents for image generation.", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The height in pixels of the generated image.", - "kwargsType": null, - "name": "height", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "The width in pixels of the generated image.", - "kwargsType": null, - "name": "width", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "Torch generator for deterministic generation.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" - }, - { - "default": 50, - "description": "The number of denoising steps.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "Custom sigmas for the denoising process.", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "list[builtins.float]" - }, - { - "default": null, - "description": "Additional kwargs for attention processors.", - "kwargsType": null, - "name": "attention_kwargs", - "required": false, - "type": "dict[builtins.str,typing.Any]" - }, - { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", - "kwargsType": null, - "name": "output_type", - "required": false, - "type": "builtins.str" - } - ], - "kind": "sequential", - "outputs": [ - { - "default": null, - "description": "The prompt embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The encoder attention mask.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The negative prompt embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The negative prompt embeddings mask.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds_mask", - "required": false, - "type": "torch.Tensor" + "type": "UnionType[builtins.NoneType,torch.Tensor]" }, { "default": null, - "description": "The batch size of the prompt embeddings", + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", "kwargsType": null, "name": "batch_size", "required": false, @@ -35159,28 +35171,12 @@ }, { "default": null, - "description": "The data type of the prompt embeddings", + "description": "Data type of model tensor inputs (determined by `transformer.dtype`)", "kwargsType": null, "name": "dtype", "required": false, "type": "torch.dtype" }, - { - "default": null, - "description": "if not set, updated to default value", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "if not set, updated to default value", - "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" - }, { "default": null, "description": "The initial latents to use for the denoising process", @@ -35191,40 +35187,25 @@ }, { "default": null, - "description": "The timesteps to use for the denoising process", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The shapes of the images latents, used for RoPE calculation", - "kwargsType": "denoiser_input_fields", - "name": "img_shapes", - "required": false, - "type": "list[list[tuple[builtins.int]]]" - }, - { - "default": null, - "description": "Generated images. (tensor output of the vae decoder.)", + "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", "kwargsType": null, - "name": "images", + "name": "videos", "required": false, - "type": "list[PIL.Image.Image]" + "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" } ], "requiredInputs": [ - "height", - "images", - "img_shapes", + "batch_size", + "dtype", + "image", + "image_condition_latents", "latents", + "negative_prompt_embeds", + "num_frames", "num_inference_steps", - "prompt", "prompt_embeds", - "prompt_embeds_mask", - "timesteps", - "width" + "resized_image", + "timesteps" ], "schemaVersion": 1, "variadicInputs": [ @@ -35237,176 +35218,6 @@ } ] }, - { - "className": "SequentialPipelineBlocks", - "components": [ - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", - "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "language_model", - "type": "transformers.models.qwen3.modeling_qwen3.Qwen3ForCausalLM" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "rvq_depth_decoder", - "type": "diffusers.models.transformers.minimax_music3_rvq_depth_decoder.MiniMaxMusic3RVQDepthDecoder" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "condition_encoder", - "type": "diffusers.models.condition_embedders.condition_embedder_minimax_music3.MiniMaxMusic3ConditionEncoder" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_minimax_music3.MiniMaxMusic3Transformer1DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 1.7 - }, - "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vocoder", - "type": "diffusers.models.autoencoders.minimax_music3_vocoder.MiniMaxMusic3Vocoder" - } - ], - "configs": [], - "contentHash": "sha256:55e8d2ae4992e28fbdd67d0e6404ce0e7af3aa04fecb9fe4f4a75cb213742f5c", - "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:55e8d2ae4992e28fbdd67d0e6404ce0e7af3aa04fecb9fe4f4a75cb213742f5c", - "inputs": [ - { - "default": null, - "description": "The music description (genre, mood, vocals, instrumentation, arrangement).", - "kwargsType": null, - "name": "prompt", - "required": true, - "type": "builtins.str" - }, - { - "default": null, - "description": "The lyrics to sing. Structure tags such as `[verse]` or `[chorus]` must each be on their own line; text on the same line as a leading tag is dropped by the checkpoint's input contract.", - "kwargsType": null, - "name": "lyrics", - "required": true, - "type": "builtins.str" - }, - { - "default": 60.0, - "description": "Upper bound on the generated audio length in seconds. The language model may stop earlier. Capped at 9000 frames (six minutes).", - "kwargsType": null, - "name": "audio_duration", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Torch generator for deterministic generation.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" - }, - { - "default": 30, - "description": "Number of flow-matching Euler steps per chunk.", - "kwargsType": null, - "name": "num_inference_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": "np", - "description": "Output format: 'np' or 'pt'.", - "kwargsType": null, - "name": "output_type", - "required": false, - "type": "builtins.str" - } - ], - "kind": "sequential", - "outputs": [ - { - "default": null, - "description": "Token ids of shape `[2, sequence_length]` holding the conditional prompt and its classifier-free counterpart (every token except the first and the two trailing structure tokens replaced by the audio-CFG token).", - "kwargsType": null, - "name": "text_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Concatenated per-frame hidden states of shape `[1, frames, num_codebooks * hidden_size]` that condition the flow-matching stage.", - "kwargsType": null, - "name": "frame_hiddens", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Frame index at which each 200-frame denoising window starts.", - "kwargsType": null, - "name": "chunk_starts", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "List of per-window denoised latent tensors (uncropped).", - "kwargsType": null, - "name": "latent_chunks", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "The generated stereo waveform of shape `(batch, channels, samples)` in `[-1, 1]`.", - "kwargsType": null, - "name": "audios", - "required": false, - "type": "UnionType[numpy.ndarray,torch.Tensor]" - } - ], - "requiredInputs": [ - "chunk_starts", - "frame_hiddens", - "latent_chunks", - "lyrics", - "prompt", - "text_ids" - ], - "schemaVersion": 1, - "variadicInputs": [] - }, { "className": "SequentialPipelineBlocks", "components": [ @@ -35856,45 +35667,45 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder", - "type": "transformers.models.clip.modeling_clip.CLIPTextModel" + "name": "pe", + "type": "transformers.models.ministral3.modeling_ministral3.Ministral3ForCausalLM" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "tokenizer", - "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + "name": "pe_tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder_2", - "type": "transformers.models.t5.modeling_t5.T5EncoderModel" + "name": "text_encoder", + "type": "transformers.models.mistral3.modeling_mistral3.Mistral3Model" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "tokenizer_2", - "type": "transformers.models.t5.tokenization_t5.T5Tokenizer" + "name": "tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "vae_scale_factor": 16 + "guidance_scale": 4.0 }, "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_ernie_image.ErnieImageTransformer2DModel" }, { "creationMethod": "from_pretrained", @@ -35907,205 +35718,175 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_flux.FluxTransformer2DModel" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_flux2.AutoencoderKLFlux2" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "patch_size": 2 + }, + "description": "", + "name": "pachifier", + "type": "diffusers.modular_pipelines.ernie_image.modular_pipeline.ErnieImagePachifier" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 16 + }, + "description": "", + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" } ], "configs": [], - "contentHash": "sha256:5c7959c50346f2237b593b4cb9d726a69efa6f828d77cee7d6c76ec9b7ee07a6", + "contentHash": "sha256:5908f92e30713a3df9726f2d615565d5381c2ff60c3ec9fdacb44377cf3e7f4a", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:5c7959c50346f2237b593b4cb9d726a69efa6f828d77cee7d6c76ec9b7ee07a6", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:5908f92e30713a3df9726f2d615565d5381c2ff60c3ec9fdacb44377cf3e7f4a", "inputs": [ { "default": null, - "description": "", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", - "required": false, - "type": "opaque" + "required": true, + "type": "builtins.str" }, { "default": null, - "description": "", - "kwargsType": null, - "name": "prompt_2", - "required": false, - "type": "opaque" - }, - { - "default": 512, - "description": "", + "description": "The height in pixels of the generated image.", "kwargsType": null, - "name": "max_sequence_length", + "name": "height", "required": false, "type": "builtins.int" }, { "default": null, - "description": "", + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "joint_attention_kwargs", + "name": "width", "required": false, - "type": "opaque" + "type": "builtins.int" }, { "default": null, - "description": "", + "description": "Optional system prompt passed to the prompt enhancer.", "kwargsType": null, - "name": "image", + "name": "pe_system_prompt", "required": false, - "type": "opaque" - }, - { - "default": true, - "description": "", - "kwargsType": null, - "name": "_auto_resize", - "required": false, - "type": "builtins.bool" + "type": "builtins.str" }, { - "default": null, - "description": "", + "default": 0.6, + "description": "Sampling temperature used when generating with the prompt enhancer.", "kwargsType": null, - "name": "generator", + "name": "pe_temperature", "required": false, - "type": "opaque" + "type": "builtins.float" }, { - "default": null, - "description": "", + "default": 0.95, + "description": "Nucleus sampling `top_p` used when generating with the prompt enhancer.", "kwargsType": null, - "name": "height", + "name": "pe_top_p", "required": false, - "type": "opaque" + "type": "builtins.float" }, { "default": null, - "description": "", - "kwargsType": null, - "name": "width", - "required": false, - "type": "opaque" - }, - { - "default": 1048576, - "description": "", + "description": "The prompt or prompts to avoid during image generation.", "kwargsType": null, - "name": "max_area", + "name": "negative_prompt", "required": false, - "type": "builtins.int" + "type": "builtins.str" }, { "default": 1, - "description": "", + "description": "Number of images to generate per prompt.", "kwargsType": null, "name": "num_images_per_prompt", "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" + "type": "builtins.int" }, { "default": 50, - "description": "", + "description": "Number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", "required": true, - "type": "opaque" + "type": "builtins.int" }, { "default": null, - "description": "", + "description": "Pre-generated noisy latents. If provided, skips noise sampling.", "kwargsType": null, - "name": "timesteps", + "name": "latents", "required": true, - "type": "opaque" + "type": "torch.Tensor" }, { "default": null, - "description": "", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "opaque" - }, - { - "default": 3.5, - "description": "", + "description": "Torch generator for deterministic noise sampling.", "kwargsType": null, - "name": "guidance_scale", + "name": "generator", "required": false, - "type": "opaque" + "type": "torch._C.Generator" }, { "default": "pil", - "description": "", + "description": "Output format: 'pil', 'np', or 'pt'.", "kwargsType": null, "name": "output_type", "required": false, - "type": "opaque" + "type": "builtins.str" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "pooled text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "pooled_prompt_embeds", + "description": "The prompt list after prompt-enhancer rewriting.", + "kwargsType": null, + "name": "prompt", "required": false, - "type": "torch.Tensor" + "type": "builtins.list" }, { "default": null, - "description": "", + "description": "The resolved image height in pixels.", "kwargsType": null, - "name": "processed_image", + "name": "height", "required": false, - "type": "opaque" + "type": "builtins.int" }, { "default": null, - "description": "The latents representing the reference image", + "description": "The resolved image width in pixels.", "kwargsType": null, - "name": "image_latents", + "name": "width", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "The height of the initial noisy latents", - "kwargsType": null, - "name": "height", + "description": "List of per-prompt text embeddings of shape (T, H).", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", "required": false, - "type": "builtins.int" + "type": "builtins.list" }, { "default": null, - "description": "The width of the initial noisy latents", - "kwargsType": null, - "name": "width", + "description": "List of per-prompt negative text embeddings for classifier-free guidance.", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", "required": false, - "type": "builtins.int" + "type": "builtins.list" }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", + "description": "The number of prompts in the batch.", "kwargsType": null, "name": "batch_size", "required": false, @@ -36113,39 +35894,39 @@ }, { "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", - "kwargsType": null, - "name": "dtype", + "description": "Padded text hidden states of shape (B, T_max, H) fed into the transformer.", + "kwargsType": "denoiser_input_fields", + "name": "text_bth", "required": false, - "type": "torch.dtype" + "type": "torch.Tensor" }, { "default": null, - "description": "The height of the image latents", - "kwargsType": null, - "name": "image_height", + "description": "Actual per-prompt text lengths used to build the transformer attention mask.", + "kwargsType": "denoiser_input_fields", + "name": "text_lens", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "The width of the image latents", - "kwargsType": null, - "name": "image_width", + "description": "Padded negative text hidden states, when classifier-free guidance is enabled.", + "kwargsType": "denoiser_input_fields", + "name": "negative_text_bth", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "The initial latents to use for the denoising process", - "kwargsType": null, - "name": "latents", + "description": "Actual per-prompt negative text lengths, when classifier-free guidance is enabled.", + "kwargsType": "denoiser_input_fields", + "name": "negative_text_lens", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The timesteps to use for inference", + "description": "The timesteps to use for inference.", "kwargsType": null, "name": "timesteps", "required": false, @@ -36153,7 +35934,7 @@ }, { "default": null, - "description": "The number of denoising steps to perform at inference time", + "description": "The number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", "required": false, @@ -36161,46 +35942,29 @@ }, { "default": null, - "description": "Optional guidance to be used.", + "description": "The initial noise latents to denoise.", "kwargsType": null, - "name": "guidance", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The sequence lengths of the prompt embeds, used for RoPE calculation.", - "kwargsType": "denoiser_input_fields", - "name": "txt_ids", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "The sequence lengths of the image latents, used for RoPE calculation.", - "kwargsType": "denoiser_input_fields", - "name": "img_ids", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", + "description": "The generated images.", "kwargsType": null, "name": "images", "required": false, - "type": "UnionType[list[PIL.Image.Image],numpy.ndarray,torch.Tensor]" + "type": "builtins.list" } ], "requiredInputs": [ - "batch_size", - "img_ids", "latents", "num_inference_steps", - "pooled_prompt_embeds", + "prompt", "prompt_embeds", - "timesteps", - "txt_ids" + "text_bth", + "text_lens", + "timesteps" ], "schemaVersion": 1, "variadicInputs": [] @@ -36213,81 +35977,64 @@ "defaultConfig": null, "description": "", "name": "text_encoder", - "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" + "type": "transformers.models.clip.modeling_clip.CLIPTextModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" }, { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 5.0 - }, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + "name": "text_encoder_2", + "type": "transformers.models.t5.modeling_t5.T5EncoderModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "tokenizer_2", + "type": "transformers.models.t5.tokenization_t5.T5Tokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "vae_scale_factor": 8 + "vae_scale_factor": 16 }, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 3.0 - }, - "description": "", - "name": "guider_2", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer_2", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" - } - ], - "configs": [ - { - "default": 0.875, - "description": "The boundary ratio to divide the denoising loop into high noise and low noise stages.", - "name": "boundary_ratio" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_flux.FluxTransformer2DModel" } ], - "contentHash": "sha256:5d080c121ae7f2dfa77d641fcbaa821e6f95ba7d53809c404d8988818778e529", + "configs": [], + "contentHash": "sha256:5c7959c50346f2237b593b4cb9d726a69efa6f828d77cee7d6c76ec9b7ee07a6", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:5d080c121ae7f2dfa77d641fcbaa821e6f95ba7d53809c404d8988818778e529", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:5c7959c50346f2237b593b4cb9d726a69efa6f828d77cee7d6c76ec9b7ee07a6", "inputs": [ { "default": null, @@ -36301,7 +36048,7 @@ "default": null, "description": "", "kwargsType": null, - "name": "negative_prompt", + "name": "prompt_2", "required": false, "type": "opaque" }, @@ -36311,6 +36058,14 @@ "kwargsType": null, "name": "max_sequence_length", "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "joint_attention_kwargs", + "required": false, "type": "opaque" }, { @@ -36318,49 +36073,65 @@ "description": "", "kwargsType": null, "name": "image", - "required": true, - "type": "PIL.Image.Image" + "required": false, + "type": "opaque" }, { - "default": 480, + "default": true, "description": "", "kwargsType": null, - "name": "height", + "name": "_auto_resize", "required": false, - "type": "builtins.int" + "type": "builtins.bool" }, { - "default": 832, + "default": null, "description": "", "kwargsType": null, - "name": "width", + "name": "generator", "required": false, - "type": "builtins.int" + "type": "opaque" }, { - "default": 81, + "default": null, "description": "", "kwargsType": null, - "name": "num_frames", - "required": true, - "type": "builtins.int" + "name": "height", + "required": false, + "type": "opaque" }, { "default": null, "description": "", "kwargsType": null, - "name": "generator", + "name": "width", "required": false, "type": "opaque" }, + { + "default": 1048576, + "description": "", + "kwargsType": null, + "name": "max_area", + "required": false, + "type": "builtins.int" + }, { "default": 1, "description": "", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "num_images_per_prompt", "required": false, "type": "opaque" }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "UnionType[builtins.NoneType,torch.Tensor]" + }, { "default": 50, "description": "", @@ -36386,28 +36157,20 @@ "type": "opaque" }, { - "default": null, - "description": "", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" - }, - { - "default": null, + "default": 3.5, "description": "", "kwargsType": null, - "name": "attention_kwargs", + "name": "guidance_scale", "required": false, "type": "opaque" }, { - "default": "np", - "description": "The output type of the decoded videos", + "default": "pil", + "description": "", "kwargsType": null, "name": "output_type", "required": false, - "type": "builtins.str" + "type": "opaque" } ], "kind": "sequential", @@ -36422,9 +36185,9 @@ }, { "default": null, - "description": "negative text embeddings used to guide the image generation", + "description": "pooled text embeddings used to guide the image generation", "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", + "name": "pooled_prompt_embeds", "required": false, "type": "torch.Tensor" }, @@ -36432,29 +36195,37 @@ "default": null, "description": "", "kwargsType": null, - "name": "resized_image", + "name": "processed_image", "required": false, - "type": "PIL.Image.Image" + "type": "opaque" }, { "default": null, - "description": "video latent representation with the first frame image condition", + "description": "The latents representing the reference image", "kwargsType": null, - "name": "first_frame_latents", + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "", + "description": "The height of the initial noisy latents", "kwargsType": null, - "name": "image_condition_latents", + "name": "height", "required": false, - "type": "UnionType[builtins.NoneType,torch.Tensor]" + "type": "builtins.int" }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", + "description": "The width of the initial noisy latents", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", "kwargsType": null, "name": "batch_size", "required": false, @@ -36462,12 +36233,28 @@ }, { "default": null, - "description": "Data type of model tensor inputs (determined by `transformer.dtype`)", + "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", "kwargsType": null, "name": "dtype", "required": false, "type": "torch.dtype" }, + { + "default": null, + "description": "The height of the image latents", + "kwargsType": null, + "name": "image_height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "The width of the image latents", + "kwargsType": null, + "name": "image_width", + "required": false, + "type": "builtins.int" + }, { "default": null, "description": "The initial latents to use for the denoising process", @@ -36478,25 +36265,62 @@ }, { "default": null, - "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "description": "The timesteps to use for inference", "kwargsType": null, - "name": "videos", + "name": "timesteps", "required": false, - "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The number of denoising steps to perform at inference time", + "kwargsType": null, + "name": "num_inference_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional guidance to be used.", + "kwargsType": null, + "name": "guidance", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The sequence lengths of the prompt embeds, used for RoPE calculation.", + "kwargsType": "denoiser_input_fields", + "name": "txt_ids", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "The sequence lengths of the image latents, used for RoPE calculation.", + "kwargsType": "denoiser_input_fields", + "name": "img_ids", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "The generated images, can be a list of PIL.Image.Image, torch.Tensor or a numpy array", + "kwargsType": null, + "name": "images", + "required": false, + "type": "UnionType[list[PIL.Image.Image],numpy.ndarray,torch.Tensor]" } ], "requiredInputs": [ "batch_size", - "dtype", - "image", - "image_condition_latents", + "img_ids", "latents", - "negative_prompt_embeds", - "num_frames", "num_inference_steps", + "pooled_prompt_embeds", "prompt_embeds", - "resized_image", - "timesteps" + "timesteps", + "txt_ids" ], "schemaVersion": 1, "variadicInputs": [] @@ -37075,498 +36899,6 @@ } ] }, - { - "className": "SequentialPipelineBlocks", - "components": [ - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 16 - }, - "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" - } - ], - "configs": [ - { - "default": true, - "description": "", - "name": "default_use_system_prompt" - }, - { - "default": true, - "description": "", - "name": "enable_safety_checker" - }, - { - "default": false, - "description": "", - "name": "use_native_flow_schedule" - } - ], - "contentHash": "sha256:6596c52651a5480d32f6e30118ecfb7d4dfa5135d2155b2aab6f3ee0c95c0287", - "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:6596c52651a5480d32f6e30118ecfb7d4dfa5135d2155b2aab6f3ee0c95c0287", - "inputs": [ - { - "default": null, - "description": "The text prompt that guides Cosmos3 generation.", - "kwargsType": null, - "name": "prompt", - "required": true, - "type": "builtins.str" - }, - { - "default": null, - "description": "The negative text prompt used for classifier-free guidance.", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "builtins.str" - }, - { - "default": null, - "description": "Number of frames to generate.", - "kwargsType": null, - "name": "num_frames", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "Height of the generated video or image in pixels.", - "kwargsType": null, - "name": "height", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "Width of the generated video or image in pixels.", - "kwargsType": null, - "name": "width", - "required": true, - "type": "builtins.int" - }, - { - "default": 24.0, - "description": "Frame rate of the generated video.", - "kwargsType": null, - "name": "fps", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Whether to prepend the Cosmos3 system prompt.", - "kwargsType": null, - "name": "use_system_prompt", - "required": false, - "type": "UnionType[builtins.NoneType,builtins.bool]" - }, - { - "default": true, - "description": "Whether to add resolution metadata to the prompt.", - "kwargsType": null, - "name": "add_resolution_template", - "required": false, - "type": "builtins.bool" - }, - { - "default": true, - "description": "Whether to add duration metadata to the prompt.", - "kwargsType": null, - "name": "add_duration_template", - "required": false, - "type": "builtins.bool" - }, - { - "default": null, - "description": "Reference video for video-to-video conditioning.", - "kwargsType": null, - "name": "video", - "required": false, - "type": "opaque" - }, - { - "default": [ - 0, - 1 - ], - "description": "Latent-frame indexes to preserve from the conditioning video.", - "kwargsType": null, - "name": "condition_frame_indexes_vision", - "required": false, - "type": "UnionType[list[builtins.int],tuple[Ellipsis,builtins.int]]" - }, - { - "default": "first", - "description": "Which end of a longer conditioning video to use: `first` or `last`.", - "kwargsType": null, - "name": "condition_video_keep", - "required": false, - "type": "builtins.str" - }, - { - "default": null, - "description": "Pre-generated noisy vision latents.", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Torch generator for deterministic generation.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" - }, - { - "default": 50, - "description": "The number of denoising steps.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": 6.0, - "description": "Scale for classifier-free guidance.", - "kwargsType": null, - "name": "guidance_scale", - "required": false, - "type": "builtins.float" - }, - { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", - "kwargsType": null, - "name": "output_type", - "required": false, - "type": "builtins.str" - }, - { - "default": null, - "description": "Denoised action latents.", - "kwargsType": null, - "name": "action_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Requested action-generation mode.", - "kwargsType": null, - "name": "action_mode", - "required": false, - "type": "builtins.str" - }, - { - "default": null, - "description": "Unpadded action-vector dimension.", - "kwargsType": null, - "name": "raw_action_dim_resolved", - "required": false, - "type": "builtins.int" - } - ], - "kind": "sequential", - "outputs": [ - { - "default": null, - "description": "Number of frames to generate.", - "kwargsType": null, - "name": "num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Height of the generated video or image in pixels.", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Width of the generated video or image in pixels.", - "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Token IDs for the conditional prompt.", - "kwargsType": null, - "name": "cond_input_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Token IDs for the unconditional prompt.", - "kwargsType": null, - "name": "uncond_input_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Vision latents encoded from the conditioning image or video.", - "kwargsType": null, - "name": "x0_tokens_vision", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", - "kwargsType": null, - "name": "vision_condition_frames", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "Conditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_text_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Unconditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_text_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Noisy vision latents for denoising.", - "kwargsType": null, - "name": "latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Frame rate used to pack vision latents.", - "kwargsType": null, - "name": "fps_vision", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Mask marking conditioned vision latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "vision_condition_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Indexes of conditioned vision latent frames.", - "kwargsType": null, - "name": "vision_condition_indexes_for_pack", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", - "kwargsType": null, - "name": "vision_conditioning_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Conditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_vision_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Unconditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_vision_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Conditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "cond_position_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Unconditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_position_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Conditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sequence_length", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Unconditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sequence_length", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Scheduler timesteps for denoising.", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Number of scheduler warmup steps.", - "kwargsType": null, - "name": "num_warmup_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Vision tokens for the transformer denoiser.", - "kwargsType": null, - "name": "vision_tokens", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "Timesteps for the vision tokens.", - "kwargsType": null, - "name": "vision_timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Predicted velocity for vision latents.", - "kwargsType": null, - "name": "velocity_vision", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Predicted velocity for sound latents.", - "kwargsType": null, - "name": "velocity_sound", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Predicted velocity for action latents.", - "kwargsType": null, - "name": "velocity_action", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The generated videos.", - "kwargsType": null, - "name": "videos", - "required": false, - "type": "list[PIL.Image.Image]" - }, - { - "default": null, - "description": "Generated action vectors.", - "kwargsType": null, - "name": "action", - "required": false, - "type": "list[torch.Tensor]" - } - ], - "requiredInputs": [ - "cond_input_ids", - "cond_text_segment", - "cond_vision_segment", - "fps_vision", - "height", - "latents", - "num_frames", - "num_inference_steps", - "num_warmup_steps", - "prompt", - "timesteps", - "uncond_input_ids", - "uncond_text_segment", - "uncond_vision_segment", - "velocity_vision", - "vision_condition_indexes_for_pack", - "width" - ], - "schemaVersion": 1, - "variadicInputs": [ - { - "default": null, - "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", - "kwargsType": "denoiser_input_fields", - "required": false, - "type": "opaque" - } - ] - }, { "className": "SequentialPipelineBlocks", "components": [ @@ -38840,268 +38172,6 @@ "schemaVersion": 1, "variadicInputs": [] }, - { - "className": "SequentialPipelineBlocks", - "components": [ - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_encoder", - "type": "transformers.models.mistral3.modeling_mistral3.Mistral3Model" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 4.0 - }, - "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_ernie_image.ErnieImageTransformer2DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_flux2.AutoencoderKLFlux2" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "patch_size": 2 - }, - "description": "", - "name": "pachifier", - "type": "diffusers.modular_pipelines.ernie_image.modular_pipeline.ErnieImagePachifier" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 16 - }, - "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" - } - ], - "configs": [], - "contentHash": "sha256:766a6741623809e21f6ebb58800138a17581426fea178d8dff721e415079da29", - "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:766a6741623809e21f6ebb58800138a17581426fea178d8dff721e415079da29", - "inputs": [ - { - "default": null, - "description": "The prompt or prompts to guide image generation.", - "kwargsType": null, - "name": "prompt", - "required": false, - "type": "builtins.str" - }, - { - "default": null, - "description": "The prompt or prompts to avoid during image generation.", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "builtins.str" - }, - { - "default": 1, - "description": "Number of images to generate per prompt.", - "kwargsType": null, - "name": "num_images_per_prompt", - "required": false, - "type": "builtins.int" - }, - { - "default": 50, - "description": "Number of denoising steps.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "The height in pixels of the generated image.", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The width in pixels of the generated image.", - "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Pre-generated noisy latents. If provided, skips noise sampling.", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Torch generator for deterministic noise sampling.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" - }, - { - "default": "pil", - "description": "Output format: 'pil', 'np', or 'pt'.", - "kwargsType": null, - "name": "output_type", - "required": false, - "type": "builtins.str" - } - ], - "kind": "sequential", - "outputs": [ - { - "default": null, - "description": "List of per-prompt text embeddings of shape (T, H).", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "List of per-prompt negative text embeddings for classifier-free guidance.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "The number of prompts in the batch.", - "kwargsType": null, - "name": "batch_size", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Padded text hidden states of shape (B, T_max, H) fed into the transformer.", - "kwargsType": "denoiser_input_fields", - "name": "text_bth", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Actual per-prompt text lengths used to build the transformer attention mask.", - "kwargsType": "denoiser_input_fields", - "name": "text_lens", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Padded negative text hidden states, when classifier-free guidance is enabled.", - "kwargsType": "denoiser_input_fields", - "name": "negative_text_bth", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Actual per-prompt negative text lengths, when classifier-free guidance is enabled.", - "kwargsType": "denoiser_input_fields", - "name": "negative_text_lens", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The timesteps to use for inference.", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The number of denoising steps.", - "kwargsType": null, - "name": "num_inference_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The initial noise latents to denoise.", - "kwargsType": null, - "name": "latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The resolved image height in pixels.", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The resolved image width in pixels.", - "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The generated images.", - "kwargsType": null, - "name": "images", - "required": false, - "type": "builtins.list" - } - ], - "requiredInputs": [ - "latents", - "num_inference_steps", - "prompt_embeds", - "text_bth", - "text_lens", - "timesteps" - ], - "schemaVersion": 1, - "variadicInputs": [] - }, { "className": "SequentialPipelineBlocks", "components": [ @@ -40731,15 +39801,15 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" + "name": "duration_head", + "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" }, { "creationMethod": "from_config", @@ -40758,6 +39828,13 @@ "name": "scheduler", "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + }, { "creationMethod": "from_pretrained", "defaultConfig": null, @@ -40778,7 +39855,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -40790,7 +39867,7 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_pretrained", @@ -40801,9 +39878,9 @@ } ], "configs": [], - "contentHash": "sha256:7ede32bbb440833a8e715338d4c4d40ac15e5f9abcb856327342af4170fbafdc", + "contentHash": "sha256:7d27e88a7313f0949bd5805f77f93a7537cdf7c96aa23ddccbb5f6deb9a8674f", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:7ede32bbb440833a8e715338d4c4d40ac15e5f9abcb856327342af4170fbafdc", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:7d27e88a7313f0949bd5805f77f93a7537cdf7c96aa23ddccbb5f6deb9a8674f", "inputs": [ { "default": null, @@ -40830,84 +39907,68 @@ "type": "builtins.int" }, { - "default": null, - "description": "`LTX2VideoCondition` (or list of them) placing image/video conditions at latent frame indices of the generated video.", - "kwargsType": null, - "name": "conditions", - "required": false, - "type": "builtins.list" - }, - { - "default": 512, - "description": "The height in pixels of the generated image.", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": 704, - "description": "The width in pixels of the generated image.", + "default": 1.0, + "description": "Lower bound on the auto-predicted duration.", "kwargsType": null, - "name": "width", + "name": "min_seconds", "required": false, - "type": "builtins.int" + "type": "builtins.float" }, { - "default": null, - "description": "The number of frames in the generated video. Omit to auto-predict via the `duration_head` (see `LTX2AutoDurationStep`).", + "default": 20.0, + "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", "kwargsType": null, - "name": "num_frames", + "name": "max_seconds", "required": false, - "type": "builtins.int" + "type": "builtins.float" }, { - "default": null, - "description": "Torch generator for deterministic generation.", + "default": 24.0, + "description": "Frames per second of the generated video.", "kwargsType": null, - "name": "generator", + "name": "frame_rate", "required": false, - "type": "torch._C.Generator" + "type": "builtins.float" }, { "default": null, - "description": "`LTX2ReferenceCondition` (or list of them) whose videos are encoded into extra latent tokens the IC-LoRA adapter attends to.", + "description": "Reference image(s) for denoising. Can be a single image or list of images.", "kwargsType": null, - "name": "reference_conditions", + "name": "image", "required": true, - "type": "builtins.list" + "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" }, { - "default": 1, - "description": "Ratio between the target and reference resolutions; 2 means the reference is preprocessed at half the target resolution. Spatial coordinates are scaled by this factor so the reference tokens land in the target coordinate space. Must match the factor the IC-LoRA was trained with.", + "default": 512, + "description": "The height in pixels of the generated image.", "kwargsType": null, - "name": "reference_downscale_factor", + "name": "height", "required": false, "type": "builtins.int" }, { - "default": 1.0, - "description": "Scalar in [0, 1] controlling how strongly the noisy tokens and reference tokens attend to each other. 1.0 (default) leaves attention unmasked.", + "default": 704, + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "conditioning_attention_strength", + "name": "width", "required": false, - "type": "builtins.float" + "type": "builtins.int" }, { "default": null, - "description": "Optional pixel-space mask of shape (1, 1, F, H, W) with values in [0, 1] giving spatially varying attention strength. Downsampled to the reference's latent grid and multiplied by `conditioning_attention_strength`.", + "description": "H.264 CRF used to re-compress the conditioning `image` before VAE encode, matching the compression the model was trained against. `None` (default) resolves from the text-encoder generation (33 through LTX-2.3, 18 for LTX-2.5). Pass `0` to skip re-compression. Requires a `PIL.Image.Image` when re-compression runs.", "kwargsType": null, - "name": "conditioning_attention_mask", + "name": "image_crf", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { - "default": 24.0, - "description": "Frames per second of the generated video.", + "default": null, + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "frame_rate", + "name": "generator", "required": false, - "type": "builtins.float" + "type": "torch._C.Generator" }, { "default": 1, @@ -40918,20 +39979,20 @@ "type": "builtins.int" }, { - "default": null, - "description": "Pre-generated noisy latents for image generation.", + "default": 30, + "description": "The number of denoising steps.", "kwargsType": null, - "name": "latents", + "name": "num_inference_steps", "required": true, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Initial noise level for the un-conditioned tokens. `None` (default) resolves to `sigmas[0]` when custom `sigmas` are supplied, else 1.0.", + "description": "Timesteps for the denoising process.", "kwargsType": null, - "name": "noise_scale", - "required": false, - "type": "builtins.float" + "name": "timesteps", + "required": true, + "type": "torch.Tensor" }, { "default": null, @@ -40942,20 +40003,20 @@ "type": "list[builtins.float]" }, { - "default": 30, - "description": "The number of denoising steps.", + "default": null, + "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, - "name": "num_inference_steps", + "name": "latents", "required": true, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Timesteps for the denoising process.", + "description": "Interpolation factor between random noise and any provided latents. `None` (default) resolves to 0.0, which keeps the provided latents.", "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "torch.Tensor" + "name": "noise_scale", + "required": false, + "type": "builtins.float" }, { "default": null, @@ -41106,71 +40167,47 @@ }, { "default": null, - "description": "Per-condition normalized VAE latents of shape [1, C, F, H, W].", - "kwargsType": null, - "name": "condition_latents", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "Per-condition conditioning strengths.", - "kwargsType": null, - "name": "condition_strengths", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "Per-condition latent frame index at which the condition is applied.", - "kwargsType": null, - "name": "condition_indices", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "Per-condition trimmed pixel frame count, used to clamp the temporal extent of single-frame keyframe coordinates.", + "description": "The predicted number of frames to generate.", "kwargsType": null, - "name": "condition_pixel_frames", + "name": "num_frames", "required": false, - "type": "builtins.list" + "type": "builtins.int" }, { "default": null, - "description": "Packed reference tokens of shape [1, total_reference_tokens, C].", + "description": "Normalized image latents (a single latent frame) for image-to-video conditioning.", "kwargsType": null, - "name": "reference_latents", + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "RoPE coordinates for the reference tokens, of shape [1, 3, total_reference_tokens, 2].", + "description": "", "kwargsType": null, - "name": "reference_coords", + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Per-reference token counts, in `reference_conditions` order.", + "description": "", "kwargsType": null, - "name": "reference_token_counts", + "name": "num_inference_steps", "required": false, - "type": "builtins.list" + "type": "builtins.int" }, { "default": null, - "description": "Per-reference-token noisy<->reference attention strengths of shape [1, total_reference_tokens], or `None` when attention is left unmasked.", + "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", "kwargsType": null, - "name": "reference_cross_mask", + "name": "audio_scheduler", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "Packed noisy video latents, with keyframe and reference tokens appended.", + "description": "Packed noisy video latents.", "kwargsType": null, "name": "latents", "required": false, @@ -41178,47 +40215,7 @@ }, { "default": null, - "description": "Packed per-token conditioning strengths of shape [B, S, 1] in [0, 1]: 1 at fully-conditioned positions, 0 at free positions.", - "kwargsType": null, - "name": "conditioning_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Clean condition latents at conditioned positions, zeros elsewhere; same shape as `latents`.", - "kwargsType": null, - "name": "clean_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "RoPE coordinates of shape [B, 3, num_keyframe_tokens + num_reference_tokens, 2] for the appended tokens, zero-width when there are none.", - "kwargsType": null, - "name": "appended_coords", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Number of generated-video tokens, i.e. the sequence length before appended tokens.", - "kwargsType": null, - "name": "base_token_count", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Number of reference tokens, which sit at the very end of the sequence.", - "kwargsType": null, - "name": "num_ref_tokens", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The resolved initial noise level, forwarded to the audio latents step.", + "description": "The resolved interpolation factor, forwarded to the audio latents step.", "kwargsType": null, "name": "noise_scale", "required": false, @@ -41226,28 +40223,12 @@ }, { "default": null, - "description": "", + "description": "Packed per-token mask marking the clean (image-conditioned) first latent frame.", "kwargsType": null, - "name": "timesteps", + "name": "conditioning_mask", "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "num_inference_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", - "kwargsType": null, - "name": "audio_scheduler", - "required": false, - "type": "opaque" - }, { "default": null, "description": "Packed noisy audio latents.", @@ -41266,7 +40247,7 @@ }, { "default": null, - "description": "Video RoPE patch coordinates, with the keyframe-condition coordinates appended.", + "description": "Video RoPE patch coordinates.", "kwargsType": "denoiser_input_fields", "name": "video_coords", "required": false, @@ -41298,22 +40279,17 @@ } ], "requiredInputs": [ - "appended_coords", "audio_latents", "audio_num_frames", "audio_scheduler", - "base_token_count", "batch_size", - "clean_latents", - "condition_indices", - "condition_latents", - "condition_pixel_frames", - "condition_strengths", "conditioning_mask", "connector_attention_mask", "connector_audio_prompt_embeds", "connector_prompt_embeds", "dtype", + "image", + "image_latents", "latents", "negative_connector_attention_mask", "negative_connector_audio_prompt_embeds", @@ -41324,7 +40300,6 @@ "prompt", "prompt_attention_mask", "prompt_embeds", - "reference_conditions", "timesteps" ], "schemaVersion": 1, @@ -41346,106 +40321,69 @@ "defaultConfig": null, "description": "", "name": "text_encoder", - "type": "transformers.modeling_utils.PreTrainedModel" + "type": "transformers.models.t5.modeling_t5.T5EncoderModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "tokenizer", - "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "connectors", - "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" + "type": "transformers.models.t5.tokenization_t5.T5Tokenizer" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 3.0 + }, "description": "", - "name": "duration_head", - "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { "creationMethod": "from_config", "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 32 + "patch_size": 1, + "patch_size_t": 1 }, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "pachifier", + "type": "diffusers.modular_pipelines.ltx.modular_pipeline.LTXVideoPachifier" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "transformer", - "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + "type": "diffusers.models.transformers.transformer_ltx.LTXVideoTransformer3DModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "audio_vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 3.0, - "modality_scale": 3.0, - "spatio_temporal_guidance_blocks": [ - 28 - ], - "stg_scale": 1.0 - }, - "description": "", - "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx.AutoencoderKLLTXVideo" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 7.0, - "modality_scale": 3.0, - "stg_scale": 1.0 + "vae_scale_factor": 32 }, "description": "", - "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vocoder", - "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" } ], "configs": [], - "contentHash": "sha256:80583fd31ce7203d3d29ed785b7a698638b27338e5cd3e61997ca0287cd3e2c0", + "contentHash": "sha256:8422b98b214729b160613739745bdc78cf0bd4231c9c21a8efaf27a0bc3a0977", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:80583fd31ce7203d3d29ed785b7a698638b27338e5cd3e61997ca0287cd3e2c0", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8422b98b214729b160613739745bdc78cf0bd4231c9c21a8efaf27a0bc3a0977", "inputs": [ { "default": null, @@ -41464,7 +40402,7 @@ "type": "builtins.str" }, { - "default": 1024, + "default": 128, "description": "Maximum sequence length for prompt encoding.", "kwargsType": null, "name": "max_sequence_length", @@ -41472,36 +40410,36 @@ "type": "builtins.int" }, { - "default": 1.0, - "description": "Lower bound on the auto-predicted duration.", + "default": 1, + "description": "The number of images to generate per prompt.", "kwargsType": null, - "name": "min_seconds", + "name": "num_videos_per_prompt", "required": false, - "type": "builtins.float" + "type": "builtins.int" }, { - "default": 20.0, - "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", + "default": 50, + "description": "The number of denoising steps.", "kwargsType": null, - "name": "max_seconds", - "required": false, - "type": "builtins.float" + "name": "num_inference_steps", + "required": true, + "type": "builtins.int" }, { - "default": 24.0, - "description": "Frames per second of the generated video.", + "default": null, + "description": "Timesteps for the denoising process.", "kwargsType": null, - "name": "frame_rate", - "required": false, - "type": "builtins.float" + "name": "timesteps", + "required": true, + "type": "torch.Tensor" }, { "default": null, - "description": "Reference image(s) for denoising. Can be a single image or list of images.", + "description": "Custom sigmas for the denoising process.", "kwargsType": null, - "name": "image", - "required": true, - "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" + "name": "sigmas", + "required": false, + "type": "list[builtins.float]" }, { "default": 512, @@ -41520,53 +40458,21 @@ "type": "builtins.int" }, { - "default": null, - "description": "H.264 CRF used to re-compress the conditioning `image` before VAE encode, matching the compression the model was trained against. `None` (default) resolves from the text-encoder generation (33 through LTX-2.3, 18 for LTX-2.5). Pass `0` to skip re-compression. Requires a `PIL.Image.Image` when re-compression runs.", + "default": 161, + "description": "", "kwargsType": null, - "name": "image_crf", + "name": "num_frames", "required": false, "type": "builtins.int" }, { - "default": null, - "description": "Torch generator for deterministic generation.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" - }, - { - "default": 1, - "description": "The number of images to generate per prompt.", + "default": 25, + "description": "", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "frame_rate", "required": false, "type": "builtins.int" }, - { - "default": 30, - "description": "The number of denoising steps.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "Timesteps for the denoising process.", - "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Custom sigmas for the denoising process.", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "list[builtins.float]" - }, { "default": null, "description": "Pre-generated noisy latents for image generation.", @@ -41577,27 +40483,11 @@ }, { "default": null, - "description": "Interpolation factor between random noise and any provided latents. `None` (default) resolves to 0.0, which keeps the provided latents.", - "kwargsType": null, - "name": "noise_scale", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Optional pre-encoded audio latents; random noise is used when not provided.", - "kwargsType": null, - "name": "audio_latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": true, - "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "use_cross_timestep", + "name": "generator", "required": false, - "type": "builtins.bool" + "type": "torch._C.Generator" }, { "default": null, @@ -41608,7 +40498,7 @@ "type": "dict[builtins.str,typing.Any]" }, { - "default": "pil", + "default": "np", "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, "name": "output_type", @@ -41617,7 +40507,7 @@ }, { "default": 0.0, - "description": "The timestep at which the VAE decodes the final latents.", + "description": "", "kwargsType": null, "name": "decode_timestep", "required": false, @@ -41625,7 +40515,7 @@ }, { "default": null, - "description": "Noise interpolation factor applied to the latents at the decode timestep.", + "description": "", "kwargsType": null, "name": "decode_noise_scale", "required": false, @@ -41636,39 +40526,39 @@ "outputs": [ { "default": null, - "description": "Packed per-layer Gemma hidden states for the prompt.", - "kwargsType": null, + "description": "The prompt embeddings.", + "kwargsType": "denoiser_input_fields", "name": "prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Binary attention mask for `prompt_embeds`.", - "kwargsType": null, + "description": "The encoder attention mask.", + "kwargsType": "denoiser_input_fields", "name": "prompt_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Packed per-layer Gemma hidden states for the negative prompt.", - "kwargsType": null, + "description": "The negative prompt embeddings.", + "kwargsType": "denoiser_input_fields", "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Binary attention mask for `negative_prompt_embeds`.", - "kwargsType": null, + "description": "The negative prompt embeddings mask.", + "kwargsType": "denoiser_input_fields", "name": "negative_prompt_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The number of prompts being denoised (before per-prompt expansion).", + "description": "", "kwargsType": null, "name": "batch_size", "required": false, @@ -41676,7 +40566,7 @@ }, { "default": null, - "description": "The dtype of the prompt embeddings.", + "description": "", "kwargsType": null, "name": "dtype", "required": false, @@ -41684,221 +40574,81 @@ }, { "default": null, - "description": "Video-branch text conditioning (cond).", + "description": "", "kwargsType": null, - "name": "connector_prompt_embeds", + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio-branch text conditioning (cond).", + "description": "", "kwargsType": null, - "name": "connector_audio_prompt_embeds", + "name": "num_inference_steps", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Binary text attention mask (cond).", + "description": "", "kwargsType": null, - "name": "connector_attention_mask", + "name": "rope_interpolation_scale", "required": false, - "type": "torch.Tensor" + "type": "builtins.tuple" }, { "default": null, - "description": "Video-branch text conditioning (uncond).", + "description": "", "kwargsType": null, - "name": "negative_connector_prompt_embeds", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio-branch text conditioning (uncond).", + "description": "The generated videos.", "kwargsType": null, - "name": "negative_connector_audio_prompt_embeds", + "name": "videos", "required": false, - "type": "torch.Tensor" - }, + "type": "list[PIL.Image.Image]" + } + ], + "requiredInputs": [ + "batch_size", + "dtype", + "latents", + "negative_prompt_attention_mask", + "negative_prompt_embeds", + "num_inference_steps", + "prompt", + "prompt_attention_mask", + "prompt_embeds", + "timesteps" + ], + "schemaVersion": 1, + "variadicInputs": [] + }, + { + "className": "SequentialPipelineBlocks", + "components": [ { - "default": null, - "description": "Binary text attention mask (uncond).", - "kwargsType": null, - "name": "negative_connector_attention_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The predicted number of frames to generate.", - "kwargsType": null, - "name": "num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Normalized image latents (a single latent frame) for image-to-video conditioning.", - "kwargsType": null, - "name": "image_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "num_inference_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", - "kwargsType": null, - "name": "audio_scheduler", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "Packed noisy video latents.", - "kwargsType": null, - "name": "latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The resolved interpolation factor, forwarded to the audio latents step.", - "kwargsType": null, - "name": "noise_scale", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Packed per-token mask marking the clean (image-conditioned) first latent frame.", - "kwargsType": null, - "name": "conditioning_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Packed noisy audio latents.", - "kwargsType": null, - "name": "audio_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Number of audio latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "audio_num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Video RoPE patch coordinates.", - "kwargsType": "denoiser_input_fields", - "name": "video_coords", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Audio RoPE patch coordinates.", - "kwargsType": "denoiser_input_fields", - "name": "audio_coords", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The generated videos.", - "kwargsType": null, - "name": "videos", - "required": false, - "type": "list[PIL.Image.Image]" - }, - { - "default": null, - "description": "The generated audio waveform.", - "kwargsType": null, - "name": "audio", - "required": false, - "type": "torch.Tensor" - } - ], - "requiredInputs": [ - "audio_latents", - "audio_num_frames", - "audio_scheduler", - "batch_size", - "conditioning_mask", - "connector_attention_mask", - "connector_audio_prompt_embeds", - "connector_prompt_embeds", - "dtype", - "image", - "image_latents", - "latents", - "negative_connector_attention_mask", - "negative_connector_audio_prompt_embeds", - "negative_connector_prompt_embeds", - "negative_prompt_attention_mask", - "negative_prompt_embeds", - "num_inference_steps", - "prompt", - "prompt_attention_mask", - "prompt_embeds", - "timesteps" - ], - "schemaVersion": 1, - "variadicInputs": [ - { - "default": null, - "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", - "kwargsType": "denoiser_input_fields", - "required": false, - "type": "opaque" - } - ] - }, - { - "className": "SequentialPipelineBlocks", - "components": [ - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_encoder", - "type": "transformers.models.t5.modeling_t5.T5EncoderModel" + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder", + "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "tokenizer", - "type": "transformers.models.t5.tokenization_t5.T5Tokenizer" + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_scale": 3.0 + "guidance_scale": 5.0 }, "description": "", "name": "guider", @@ -41908,47 +40658,37 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" }, { "creationMethod": "from_config", "defaultConfig": { - "patch_size": 1, - "patch_size_t": 1 + "vae_scale_factor": 8 }, "description": "", - "name": "pachifier", - "type": "diffusers.modular_pipelines.ltx.modular_pipeline.LTXVideoPachifier" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "transformer", - "type": "diffusers.models.transformers.transformer_ltx.LTXVideoTransformer3DModel" + "type": "diffusers.models.transformers.transformer_helios.HeliosTransformer3DModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx.AutoencoderKLLTXVideo" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 32 - }, - "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_helios.HeliosScheduler" } ], "configs": [], - "contentHash": "sha256:8422b98b214729b160613739745bdc78cf0bd4231c9c21a8efaf27a0bc3a0977", + "contentHash": "sha256:8795e9ed52f3c75d881c030720f2918ce13946514bef0c081851aec2315449f9", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8422b98b214729b160613739745bdc78cf0bd4231c9c21a8efaf27a0bc3a0977", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8795e9ed52f3c75d881c030720f2918ce13946514bef0c081851aec2315449f9", "inputs": [ { "default": null, @@ -41967,7 +40707,7 @@ "type": "builtins.str" }, { - "default": 128, + "default": 512, "description": "Maximum sequence length for prompt encoding.", "kwargsType": null, "name": "max_sequence_length", @@ -41975,84 +40715,144 @@ "type": "builtins.int" }, { - "default": 1, - "description": "The number of images to generate per prompt.", + "default": null, + "description": "Reference image(s) for denoising. Can be a single image or list of images.", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "image", + "required": true, + "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" + }, + { + "default": 384, + "description": "The height in pixels of the generated image.", + "kwargsType": null, + "name": "height", "required": false, "type": "builtins.int" }, { - "default": 50, - "description": "The number of denoising steps.", + "default": 640, + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "num_inference_steps", - "required": true, + "name": "width", + "required": false, "type": "builtins.int" }, { - "default": null, - "description": "Timesteps for the denoising process.", + "default": 9, + "description": "Number of latent frames per temporal chunk.", "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "torch.Tensor" + "name": "num_latent_frames_per_chunk", + "required": false, + "type": "builtins.int" }, { "default": null, - "description": "Custom sigmas for the denoising process.", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "sigmas", + "name": "generator", "required": false, - "type": "list[builtins.float]" + "type": "torch._C.Generator" }, { - "default": 512, - "description": "The height in pixels of the generated image.", + "default": 1, + "description": "Number of videos to generate per prompt.", "kwargsType": null, - "name": "height", + "name": "num_videos_per_prompt", "required": false, "type": "builtins.int" }, { - "default": 704, - "description": "The width in pixels of the generated image.", + "default": 0.111, + "description": "Minimum sigma for image latent noise.", "kwargsType": null, - "name": "width", + "name": "image_noise_sigma_min", "required": false, - "type": "builtins.int" + "type": "builtins.float" }, { - "default": 161, - "description": "", + "default": 0.135, + "description": "Maximum sigma for image latent noise.", "kwargsType": null, - "name": "num_frames", + "name": "image_noise_sigma_max", + "required": false, + "type": "builtins.float" + }, + { + "default": 0.111, + "description": "Minimum sigma for video/fake-image latent noise.", + "kwargsType": null, + "name": "video_noise_sigma_min", + "required": false, + "type": "builtins.float" + }, + { + "default": 0.135, + "description": "Maximum sigma for video/fake-image latent noise.", + "kwargsType": null, + "name": "video_noise_sigma_max", "required": false, + "type": "builtins.float" + }, + { + "default": 132, + "description": "Total number of video frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": true, "type": "builtins.int" }, { - "default": 25, - "description": "", + "default": [ + 16, + 2, + 1 + ], + "description": "Sizes of long/mid/short history buffers for temporal context.", "kwargsType": null, - "name": "frame_rate", + "name": "history_sizes", + "required": true, + "type": "builtins.list" + }, + { + "default": true, + "description": "Whether to keep the first frame as a prefix in history.", + "kwargsType": null, + "name": "keep_first_frame", + "required": false, + "type": "builtins.bool" + }, + { + "default": 50, + "description": "The number of denoising steps.", + "kwargsType": null, + "name": "num_inference_steps", "required": false, "type": "builtins.int" }, + { + "default": null, + "description": "Custom sigmas for the denoising process.", + "kwargsType": null, + "name": "sigmas", + "required": true, + "type": "list[builtins.float]" + }, { "default": null, "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, "name": "latents", - "required": true, + "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "Timesteps for the denoising process.", "kwargsType": null, - "name": "generator", + "name": "timesteps", "required": false, - "type": "torch._C.Generator" + "type": "torch.Tensor" }, { "default": null, @@ -42069,22 +40869,6 @@ "name": "output_type", "required": false, "type": "builtins.str" - }, - { - "default": 0.0, - "description": "", - "kwargsType": null, - "name": "decode_timestep", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "decode_noise_scale", - "required": false, - "type": "opaque" } ], "kind": "sequential", @@ -42099,31 +40883,31 @@ }, { "default": null, - "description": "The encoder attention mask.", + "description": "The negative prompt embeddings.", "kwargsType": "denoiser_input_fields", - "name": "prompt_attention_mask", + "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The negative prompt embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", + "description": "The latent representation of the input image.", + "kwargsType": null, + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The negative prompt embeddings mask.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_attention_mask", + "description": "Fake image latents for history seeding", + "kwargsType": null, + "name": "fake_image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "", + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", "kwargsType": null, "name": "batch_size", "required": false, @@ -42131,7 +40915,7 @@ }, { "default": null, - "description": "", + "description": "Data type of model tensor inputs (determined by `prompt_embeds.dtype`)", "kwargsType": null, "name": "dtype", "required": false, @@ -42141,57 +40925,141 @@ "default": null, "description": "", "kwargsType": null, - "name": "timesteps", + "name": "height", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, "description": "", "kwargsType": null, - "name": "num_inference_steps", + "name": "width", "required": false, "type": "builtins.int" }, { "default": null, - "description": "", + "description": "Number of temporal chunks", "kwargsType": null, - "name": "rope_interpolation_scale", + "name": "num_latent_chunk", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Shape of latent tensor per chunk", + "kwargsType": null, + "name": "latent_shape", "required": false, "type": "builtins.tuple" }, + { + "default": null, + "description": "Adjusted history sizes (sorted, descending)", + "kwargsType": null, + "name": "history_sizes", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "", + "kwargsType": "denoiser_input_fields", + "name": "indices_hidden_states", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "", + "kwargsType": "denoiser_input_fields", + "name": "indices_latents_history_short", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "", + "kwargsType": "denoiser_input_fields", + "name": "indices_latents_history_mid", + "required": false, + "type": "torch.Tensor" + }, { "default": null, "description": "", + "kwargsType": "denoiser_input_fields", + "name": "indices_latents_history_long", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Initialized zero history latents", "kwargsType": null, - "name": "latents", + "name": "history_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The generated videos.", + "description": "Scheduler shift parameter", + "kwargsType": null, + "name": "mu", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Sigma schedule for diffusion", + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "List of per-chunk denoised latent tensors", + "kwargsType": null, + "name": "latent_chunks", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", "kwargsType": null, "name": "videos", "required": false, - "type": "list[PIL.Image.Image]" + "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" } ], "requiredInputs": [ "batch_size", - "dtype", - "latents", - "negative_prompt_attention_mask", - "negative_prompt_embeds", - "num_inference_steps", + "fake_image_latents", + "history_latents", + "history_sizes", + "image", + "image_latents", + "latent_chunks", + "latent_shape", + "mu", + "num_frames", + "num_latent_chunk", "prompt", - "prompt_attention_mask", "prompt_embeds", - "timesteps" + "sigmas" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "SequentialPipelineBlocks", @@ -42200,72 +41068,117 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + "name": "text_encoder", + "type": "transformers.modeling_utils.PreTrainedModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + "name": "tokenizer", + "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + "name": "connectors", + "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "duration_head", + "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" }, { - "creationMethod": "from_config", - "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 16 - }, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "sound_tokenizer", - "type": "diffusers.models.autoencoders.autoencoder_cosmos3_audio.Cosmos3AVAEAudioTokenizer" - } - ], - "configs": [ + "name": "transformer", + "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "default_use_system_prompt" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "enable_safety_checker" + "name": "audio_vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" }, { - "default": false, + "creationMethod": "from_config", + "defaultConfig": { + "guidance_rescale": 0.7, + "guidance_scale": 3.0, + "modality_scale": 3.0, + "spatio_temporal_guidance_blocks": [ + 28 + ], + "stg_scale": 1.0 + }, "description": "", - "name": "use_native_flow_schedule" + "name": "guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_rescale": 0.7, + "guidance_scale": 7.0, + "modality_scale": 3.0, + "stg_scale": 1.0 + }, + "description": "", + "name": "audio_guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "diffusion_decoder", + "type": "diffusers.models.autoencoders.ltx2_diffusion_decoder.LTX2VideoDiffusionDecoderModel" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 32 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vocoder", + "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" } ], - "contentHash": "sha256:8603b54f602e557da6f289f8c4797a26af8f561d7724dd634bd112cc6379ed91", + "configs": [], + "contentHash": "sha256:8827541bf80d06ae897eb1129ce1c49204dbc4826287bec8069d2d6bf8b9c763", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8603b54f602e557da6f289f8c4797a26af8f561d7724dd634bd112cc6379ed91", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8827541bf80d06ae897eb1129ce1c49204dbc4826287bec8069d2d6bf8b9c763", "inputs": [ { "default": null, - "description": "The text prompt that guides Cosmos3 generation.", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", "required": true, @@ -42273,87 +41186,87 @@ }, { "default": null, - "description": "The negative text prompt used for classifier-free guidance.", + "description": "The prompt or prompts not to guide the image generation.", "kwargsType": null, "name": "negative_prompt", "required": false, "type": "builtins.str" }, { - "default": null, - "description": "Number of frames to generate.", + "default": 1024, + "description": "Maximum sequence length for prompt encoding.", "kwargsType": null, - "name": "num_frames", - "required": true, + "name": "max_sequence_length", + "required": false, "type": "builtins.int" }, { - "default": null, - "description": "Height of the generated video or image in pixels.", + "default": 1.0, + "description": "Lower bound on the auto-predicted duration.", "kwargsType": null, - "name": "height", - "required": true, - "type": "builtins.int" + "name": "min_seconds", + "required": false, + "type": "builtins.float" }, { - "default": null, - "description": "Width of the generated video or image in pixels.", + "default": 20.0, + "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", "kwargsType": null, - "name": "width", - "required": true, - "type": "builtins.int" + "name": "max_seconds", + "required": false, + "type": "builtins.float" }, { "default": 24.0, - "description": "Frame rate of the generated video.", + "description": "Frames per second of the generated video.", "kwargsType": null, - "name": "fps", + "name": "frame_rate", "required": false, "type": "builtins.float" }, { "default": null, - "description": "Whether to prepend the Cosmos3 system prompt.", + "description": "`LTX2VideoCondition` (or list of them) placing image/video conditions at latent frame indices of the generated video.", "kwargsType": null, - "name": "use_system_prompt", + "name": "conditions", "required": false, - "type": "UnionType[builtins.NoneType,builtins.bool]" + "type": "builtins.list" }, { - "default": true, - "description": "Whether to add resolution metadata to the prompt.", + "default": 512, + "description": "The height in pixels of the generated image.", "kwargsType": null, - "name": "add_resolution_template", + "name": "height", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { - "default": true, - "description": "Whether to add duration metadata to the prompt.", + "default": 704, + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "add_duration_template", + "name": "width", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { "default": null, - "description": "Vision latents encoded from the conditioning image or video.", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "x0_tokens_vision", + "name": "generator", "required": false, - "type": "torch.Tensor" + "type": "torch._C.Generator" }, { - "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", + "default": 1, + "description": "The number of images to generate per prompt.", "kwargsType": null, - "name": "vision_condition_frames", + "name": "num_videos_per_prompt", "required": false, - "type": "list[builtins.int]" + "type": "builtins.int" }, { "default": null, - "description": "Pre-generated noisy vision latents.", + "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, "name": "latents", "required": true, @@ -42361,14 +41274,22 @@ }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "Initial noise level for the un-conditioned tokens. `None` (default) resolves to `sigmas[0]` when custom `sigmas` are supplied, else 1.0.", "kwargsType": null, - "name": "generator", + "name": "noise_scale", "required": false, - "type": "torch._C.Generator" + "type": "builtins.float" }, { - "default": 50, + "default": null, + "description": "Custom sigmas for the denoising process.", + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "list[builtins.float]" + }, + { + "default": 30, "description": "The number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", @@ -42377,316 +41298,284 @@ }, { "default": null, - "description": "Pre-generated noisy sound latents.", + "description": "Timesteps for the denoising process.", "kwargsType": null, - "name": "sound_latents", + "name": "timesteps", "required": true, "type": "torch.Tensor" }, { - "default": 6.0, - "description": "Scale for classifier-free guidance.", + "default": null, + "description": "Optional pre-encoded audio latents; random noise is used when not provided.", "kwargsType": null, - "name": "guidance_scale", - "required": false, - "type": "builtins.float" + "name": "audio_latents", + "required": true, + "type": "torch.Tensor" }, { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", + "default": true, + "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", "kwargsType": null, - "name": "output_type", + "name": "use_cross_timestep", "required": false, - "type": "builtins.str" + "type": "builtins.bool" }, { "default": null, - "description": "Denoised action latents.", + "description": "Additional kwargs for attention processors.", "kwargsType": null, - "name": "action_latents", + "name": "attention_kwargs", "required": false, - "type": "torch.Tensor" + "type": "dict[builtins.str,typing.Any]" }, { - "default": null, - "description": "Requested action-generation mode.", + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, - "name": "action_mode", + "name": "output_type", "required": false, "type": "builtins.str" - }, - { - "default": null, - "description": "Unpadded action-vector dimension.", - "kwargsType": null, - "name": "raw_action_dim_resolved", - "required": false, - "type": "builtins.int" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "Number of frames to generate.", - "kwargsType": null, - "name": "num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Height of the generated video or image in pixels.", + "description": "Packed per-layer Gemma hidden states for the prompt.", "kwargsType": null, - "name": "height", + "name": "prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Width of the generated video or image in pixels.", + "description": "Binary attention mask for `prompt_embeds`.", "kwargsType": null, - "name": "width", + "name": "prompt_attention_mask", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the conditional prompt.", + "description": "Packed per-layer Gemma hidden states for the negative prompt.", "kwargsType": null, - "name": "cond_input_ids", + "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the unconditional prompt.", + "description": "Binary attention mask for `negative_prompt_embeds`.", "kwargsType": null, - "name": "uncond_input_ids", + "name": "negative_prompt_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_text_segment", + "description": "The number of prompts being denoised (before per-prompt expansion).", + "kwargsType": null, + "name": "batch_size", "required": false, - "type": "builtins.dict" + "type": "builtins.int" }, { "default": null, - "description": "Unconditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_text_segment", + "description": "The dtype of the prompt embeddings.", + "kwargsType": null, + "name": "dtype", "required": false, - "type": "builtins.dict" + "type": "torch.dtype" }, { "default": null, - "description": "Noisy vision latents for denoising.", + "description": "Video-branch text conditioning (cond).", "kwargsType": null, - "name": "latents", + "name": "connector_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Frame rate used to pack vision latents.", + "description": "Audio-branch text conditioning (cond).", "kwargsType": null, - "name": "fps_vision", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Mask marking conditioned vision latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "vision_condition_mask", + "name": "connector_audio_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Indexes of conditioned vision latent frames.", + "description": "Binary text attention mask (cond).", "kwargsType": null, - "name": "vision_condition_indexes_for_pack", + "name": "connector_attention_mask", "required": false, - "type": "list[builtins.int]" + "type": "torch.Tensor" }, { "default": null, - "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", + "description": "Video-branch text conditioning (uncond).", "kwargsType": null, - "name": "vision_conditioning_latents", + "name": "negative_connector_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_vision_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Unconditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_vision_segment", + "description": "Audio-branch text conditioning (uncond).", + "kwargsType": null, + "name": "negative_connector_audio_prompt_embeds", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Conditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "cond_position_ids", + "description": "Binary text attention mask (uncond).", + "kwargsType": null, + "name": "negative_connector_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_position_ids", + "description": "The predicted number of frames to generate.", + "kwargsType": null, + "name": "num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Conditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sequence_length", + "description": "Per-condition normalized VAE latents of shape [1, C, F, H, W].", + "kwargsType": null, + "name": "condition_latents", "required": false, - "type": "builtins.int" + "type": "builtins.list" }, { "default": null, - "description": "Unconditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sequence_length", + "description": "Per-condition conditioning strengths.", + "kwargsType": null, + "name": "condition_strengths", "required": false, - "type": "builtins.int" + "type": "builtins.list" }, { "default": null, - "description": "Scheduler timesteps for denoising.", + "description": "Per-condition latent frame index at which the condition is applied.", "kwargsType": null, - "name": "timesteps", + "name": "condition_indices", "required": false, - "type": "torch.Tensor" + "type": "builtins.list" }, { "default": null, - "description": "Number of scheduler warmup steps.", + "description": "Per-condition trimmed pixel frame count, used to clamp the temporal extent of single-frame keyframe coordinates.", "kwargsType": null, - "name": "num_warmup_steps", + "name": "condition_pixel_frames", "required": false, - "type": "builtins.int" + "type": "builtins.list" }, { "default": null, - "description": "Noisy sound latents for denoising.", + "description": "Packed noisy video latents, with any keyframe condition tokens appended.", "kwargsType": null, - "name": "sound_latents", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Frame rate of the sound latent sequence.", + "description": "Packed per-token conditioning strengths of shape [B, S, 1] in [0, 1]: 1 at fully-conditioned positions, 0 at free positions.", "kwargsType": null, - "name": "fps_sound", + "name": "conditioning_mask", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "Mask marking conditioned sound latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "sound_condition_mask", + "description": "Clean condition latents at conditioned positions, zeros elsewhere; same shape as `latents`.", + "kwargsType": null, + "name": "clean_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Scheduler used to update sound latents.", + "description": "RoPE coordinates of shape [B, 3, num_keyframe_tokens, 2] for the appended keyframe tokens, zero-width when there are none.", "kwargsType": null, - "name": "sound_scheduler", + "name": "appended_coords", "required": false, - "type": "opaque" + "type": "torch.Tensor" }, { "default": null, - "description": "Conditional sound segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sound_segment", + "description": "Number of generated-video tokens, i.e. the sequence length before appended tokens.", + "kwargsType": null, + "name": "base_token_count", "required": false, - "type": "builtins.dict" + "type": "builtins.int" }, { "default": null, - "description": "Unconditional sound segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sound_segment", + "description": "The resolved initial noise level, forwarded to the audio latents step.", + "kwargsType": null, + "name": "noise_scale", "required": false, - "type": "builtins.dict" + "type": "builtins.float" }, { "default": null, - "description": "Vision tokens for the transformer denoiser.", + "description": "", "kwargsType": null, - "name": "vision_tokens", + "name": "timesteps", "required": false, - "type": "list[torch.Tensor]" + "type": "torch.Tensor" }, { "default": null, - "description": "Timesteps for the vision tokens.", + "description": "", "kwargsType": null, - "name": "vision_timesteps", + "name": "num_inference_steps", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Sound tokens for the transformer denoiser.", + "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", "kwargsType": null, - "name": "sound_tokens", + "name": "audio_scheduler", "required": false, - "type": "list[torch.Tensor]" + "type": "opaque" }, { "default": null, - "description": "Timesteps for the sound tokens.", + "description": "Packed noisy audio latents.", "kwargsType": null, - "name": "sound_timesteps", + "name": "audio_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Predicted velocity for vision latents.", - "kwargsType": null, - "name": "velocity_vision", + "description": "Number of audio latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "audio_num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Predicted velocity for sound latents.", - "kwargsType": null, - "name": "velocity_sound", + "description": "Video RoPE patch coordinates, with the keyframe-condition coordinates appended.", + "kwargsType": "denoiser_input_fields", + "name": "video_coords", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Predicted velocity for action latents.", - "kwargsType": null, - "name": "velocity_action", + "description": "Audio RoPE patch coordinates.", + "kwargsType": "denoiser_input_fields", + "name": "audio_coords", "required": false, "type": "torch.Tensor" }, @@ -42700,57 +41589,41 @@ }, { "default": null, - "description": "Generated waveform.", + "description": "The generated audio waveform.", "kwargsType": null, - "name": "sound", + "name": "audio", "required": false, "type": "torch.Tensor" - }, - { - "default": null, - "description": "Sample rate of the generated waveform in Hz.", - "kwargsType": null, - "name": "sampling_rate", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Generated action vectors.", - "kwargsType": null, - "name": "action", - "required": false, - "type": "list[torch.Tensor]" } ], "requiredInputs": [ - "cond_input_ids", - "cond_position_ids", - "cond_sequence_length", - "cond_sound_segment", - "cond_text_segment", - "cond_vision_segment", - "fps_sound", - "fps_vision", - "height", + "appended_coords", + "audio_latents", + "audio_num_frames", + "audio_scheduler", + "base_token_count", + "batch_size", + "clean_latents", + "condition_indices", + "condition_latents", + "condition_pixel_frames", + "condition_strengths", + "conditioning_mask", + "connector_attention_mask", + "connector_audio_prompt_embeds", + "connector_prompt_embeds", + "dtype", "latents", - "num_frames", + "negative_connector_attention_mask", + "negative_connector_audio_prompt_embeds", + "negative_connector_prompt_embeds", + "negative_prompt_attention_mask", + "negative_prompt_embeds", "num_inference_steps", - "num_warmup_steps", "prompt", - "sound_latents", - "sound_scheduler", - "timesteps", - "uncond_input_ids", - "uncond_position_ids", - "uncond_sequence_length", - "uncond_sound_segment", - "uncond_text_segment", - "uncond_vision_segment", - "velocity_sound", - "velocity_vision", - "vision_condition_indexes_for_pack", - "width" + "prompt_attention_mask", + "prompt_embeds", + "timesteps" ], "schemaVersion": 1, "variadicInputs": [ @@ -42793,213 +41666,167 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" }, { "creationMethod": "from_config", "defaultConfig": { - "vae_scale_factor": 8 + "guidance_scale": 3.0 }, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "guider_2", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_helios.HeliosTransformer3DModel" + "name": "transformer_2", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_helios.HeliosScheduler" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 8 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" } ], - "configs": [], - "contentHash": "sha256:8795e9ed52f3c75d881c030720f2918ce13946514bef0c081851aec2315449f9", + "configs": [ + { + "default": 0.875, + "description": "The boundary ratio to divide the denoising loop into high noise and low noise stages.", + "name": "boundary_ratio" + } + ], + "contentHash": "sha256:8d68bdb4a5c22b0f6375a1d9a299e2301d87c3938f23dfe0d42d3474a531fb6e", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8795e9ed52f3c75d881c030720f2918ce13946514bef0c081851aec2315449f9", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8d68bdb4a5c22b0f6375a1d9a299e2301d87c3938f23dfe0d42d3474a531fb6e", "inputs": [ { "default": null, - "description": "The prompt or prompts to guide image generation.", + "description": "", "kwargsType": null, "name": "prompt", - "required": true, - "type": "builtins.str" + "required": false, + "type": "opaque" }, { "default": null, - "description": "The prompt or prompts not to guide the image generation.", + "description": "", "kwargsType": null, "name": "negative_prompt", "required": false, - "type": "builtins.str" + "type": "opaque" }, { "default": 512, - "description": "Maximum sequence length for prompt encoding.", + "description": "", "kwargsType": null, "name": "max_sequence_length", "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Reference image(s) for denoising. Can be a single image or list of images.", - "kwargsType": null, - "name": "image", - "required": true, - "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" - }, - { - "default": 384, - "description": "The height in pixels of the generated image.", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": 640, - "description": "The width in pixels of the generated image.", - "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" - }, - { - "default": 9, - "description": "Number of latent frames per temporal chunk.", - "kwargsType": null, - "name": "num_latent_frames_per_chunk", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Torch generator for deterministic generation.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" + "type": "opaque" }, { "default": 1, - "description": "Number of videos to generate per prompt.", + "description": "", "kwargsType": null, "name": "num_videos_per_prompt", "required": false, - "type": "builtins.int" + "type": "opaque" }, { - "default": 0.111, - "description": "Minimum sigma for image latent noise.", + "default": 50, + "description": "", "kwargsType": null, - "name": "image_noise_sigma_min", - "required": false, - "type": "builtins.float" + "name": "num_inference_steps", + "required": true, + "type": "opaque" }, { - "default": 0.135, - "description": "Maximum sigma for image latent noise.", + "default": null, + "description": "", "kwargsType": null, - "name": "image_noise_sigma_max", - "required": false, - "type": "builtins.float" + "name": "timesteps", + "required": true, + "type": "opaque" }, { - "default": 0.111, - "description": "Minimum sigma for video/fake-image latent noise.", + "default": null, + "description": "", "kwargsType": null, - "name": "video_noise_sigma_min", + "name": "sigmas", "required": false, - "type": "builtins.float" + "type": "opaque" }, { - "default": 0.135, - "description": "Maximum sigma for video/fake-image latent noise.", + "default": null, + "description": "", "kwargsType": null, - "name": "video_noise_sigma_max", + "name": "height", "required": false, - "type": "builtins.float" - }, - { - "default": 132, - "description": "Total number of video frames to generate.", - "kwargsType": null, - "name": "num_frames", - "required": true, "type": "builtins.int" }, { - "default": [ - 16, - 2, - 1 - ], - "description": "Sizes of long/mid/short history buffers for temporal context.", - "kwargsType": null, - "name": "history_sizes", - "required": true, - "type": "builtins.list" - }, - { - "default": true, - "description": "Whether to keep the first frame as a prefix in history.", - "kwargsType": null, - "name": "keep_first_frame", - "required": false, - "type": "builtins.bool" - }, - { - "default": 50, - "description": "The number of denoising steps.", + "default": null, + "description": "", "kwargsType": null, - "name": "num_inference_steps", + "name": "width", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Custom sigmas for the denoising process.", + "description": "", "kwargsType": null, - "name": "sigmas", - "required": true, - "type": "list[builtins.float]" + "name": "num_frames", + "required": false, + "type": "builtins.int" }, { "default": null, - "description": "Pre-generated noisy latents for image generation.", + "description": "", "kwargsType": null, "name": "latents", - "required": false, - "type": "torch.Tensor" + "required": true, + "type": "UnionType[builtins.NoneType,torch.Tensor]" }, { "default": null, - "description": "Timesteps for the denoising process.", + "description": "", "kwargsType": null, - "name": "timesteps", + "name": "generator", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "Additional kwargs for attention processors.", + "description": "", "kwargsType": null, "name": "attention_kwargs", "required": false, - "type": "dict[builtins.str,typing.Any]" + "type": "opaque" }, { "default": "np", - "description": "Output format: 'pil', 'np', 'pt'.", + "description": "The output type of the decoded videos", "kwargsType": null, "name": "output_type", "required": false, @@ -43010,7 +41837,7 @@ "outputs": [ { "default": null, - "description": "The prompt embeddings.", + "description": "text embeddings used to guide the image generation", "kwargsType": "denoiser_input_fields", "name": "prompt_embeds", "required": false, @@ -43018,28 +41845,12 @@ }, { "default": null, - "description": "The negative prompt embeddings.", + "description": "negative text embeddings used to guide the image generation", "kwargsType": "denoiser_input_fields", "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "The latent representation of the input image.", - "kwargsType": null, - "name": "image_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Fake image latents for history seeding", - "kwargsType": null, - "name": "fake_image_latents", - "required": false, - "type": "torch.Tensor" - }, { "default": null, "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", @@ -43050,7 +41861,7 @@ }, { "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds.dtype`)", + "description": "Data type of model tensor inputs (determined by `transformer.dtype`)", "kwargsType": null, "name": "dtype", "required": false, @@ -43058,108 +41869,12 @@ }, { "default": null, - "description": "", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Number of temporal chunks", - "kwargsType": null, - "name": "num_latent_chunk", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Shape of latent tensor per chunk", - "kwargsType": null, - "name": "latent_shape", - "required": false, - "type": "builtins.tuple" - }, - { - "default": null, - "description": "Adjusted history sizes (sorted, descending)", - "kwargsType": null, - "name": "history_sizes", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "", - "kwargsType": "denoiser_input_fields", - "name": "indices_hidden_states", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": "denoiser_input_fields", - "name": "indices_latents_history_short", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": "denoiser_input_fields", - "name": "indices_latents_history_mid", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": "denoiser_input_fields", - "name": "indices_latents_history_long", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Initialized zero history latents", + "description": "The initial latents to use for the denoising process", "kwargsType": null, - "name": "history_latents", + "name": "latents", "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "Scheduler shift parameter", - "kwargsType": null, - "name": "mu", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Sigma schedule for diffusion", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "builtins.list" - }, - { - "default": null, - "description": "List of per-chunk denoised latent tensors", - "kwargsType": null, - "name": "latent_chunks", - "required": false, - "type": "builtins.list" - }, { "default": null, "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", @@ -43171,19 +41886,12 @@ ], "requiredInputs": [ "batch_size", - "fake_image_latents", - "history_latents", - "history_sizes", - "image", - "image_latents", - "latent_chunks", - "latent_shape", - "mu", - "num_frames", - "num_latent_chunk", - "prompt", + "dtype", + "latents", + "negative_prompt_embeds", + "num_inference_steps", "prompt_embeds", - "sigmas" + "timesteps" ], "schemaVersion": 1, "variadicInputs": [ @@ -43204,251 +41912,739 @@ "defaultConfig": null, "description": "", "name": "text_encoder", - "type": "transformers.models.clip.modeling_clip.CLIPTextModel" + "type": "transformers.modeling_utils.PreTrainedModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder_2", - "type": "transformers.models.clip.modeling_clip.CLIPTextModelWithProjection" + "name": "tokenizer", + "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "tokenizer", - "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + "name": "connectors", + "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "tokenizer_2", - "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + "name": "duration_head", + "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" }, { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 7.5 - }, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "audio_vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" }, { "creationMethod": "from_config", "defaultConfig": { - "vae_scale_factor": 8 + "guidance_rescale": 0.7, + "guidance_scale": 3.0, + "modality_scale": 3.0, + "spatio_temporal_guidance_blocks": [ + 28 + ], + "stg_scale": 1.0 }, "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "name": "guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", "defaultConfig": { - "do_binarize": true, - "do_convert_grayscale": true, - "do_normalize": false, - "vae_scale_factor": 8 + "guidance_rescale": 0.7, + "guidance_scale": 7.0, + "modality_scale": 3.0, + "stg_scale": 1.0 }, "description": "", - "name": "mask_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "name": "audio_guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler" + "name": "diffusion_decoder", + "type": "diffusers.models.autoencoders.ltx2_diffusion_decoder.LTX2VideoDiffusionDecoderModel" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "unet", - "type": "diffusers.models.unets.unet_2d_condition.UNet2DConditionModel" - } - ], - "configs": [ - { - "default": true, + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 32 + }, "description": "", - "name": "force_zeros_for_empty_prompt" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" }, { - "default": false, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "requires_aesthetics_score" + "name": "vocoder", + "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" } ], - "contentHash": "sha256:8f9dd654bdf918314cd130422420d7ddac24f52b240ef5746ab573506f3087e8", + "configs": [], + "contentHash": "sha256:8e12049ce331e774d1e4b5ab648a8165d7e98e7c328dd4b3d1d95272a1084b20", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8f9dd654bdf918314cd130422420d7ddac24f52b240ef5746ab573506f3087e8", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8e12049ce331e774d1e4b5ab648a8165d7e98e7c328dd4b3d1d95272a1084b20", "inputs": [ { "default": null, - "description": "", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "prompt_2", - "required": false, - "type": "opaque" + "required": true, + "type": "builtins.str" }, { "default": null, - "description": "", + "description": "The prompt or prompts not to guide the image generation.", "kwargsType": null, "name": "negative_prompt", "required": false, - "type": "opaque" + "type": "builtins.str" }, { - "default": null, - "description": "", + "default": 1024, + "description": "Maximum sequence length for prompt encoding.", "kwargsType": null, - "name": "negative_prompt_2", + "name": "max_sequence_length", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": null, - "description": "", + "default": 1.0, + "description": "Lower bound on the auto-predicted duration.", "kwargsType": null, - "name": "cross_attention_kwargs", + "name": "min_seconds", "required": false, - "type": "opaque" + "type": "builtins.float" }, { - "default": null, - "description": "", + "default": 20.0, + "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", "kwargsType": null, - "name": "clip_skip", + "name": "max_seconds", "required": false, - "type": "opaque" + "type": "builtins.float" }, { - "default": null, - "description": "", + "default": 24.0, + "description": "Frames per second of the generated video.", "kwargsType": null, - "name": "height", + "name": "frame_rate", "required": false, - "type": "opaque" + "type": "builtins.float" }, { - "default": null, - "description": "", + "default": 1, + "description": "The number of images to generate per prompt.", "kwargsType": null, - "name": "width", + "name": "num_videos_per_prompt", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": null, - "description": "", + "default": 30, + "description": "The number of denoising steps.", "kwargsType": null, - "name": "image", + "name": "num_inference_steps", "required": true, - "type": "opaque" + "type": "builtins.int" }, { "default": null, - "description": "", + "description": "Timesteps for the denoising process.", "kwargsType": null, - "name": "mask_image", + "name": "timesteps", "required": true, - "type": "opaque" + "type": "torch.Tensor" }, { "default": null, - "description": "", + "description": "Custom sigmas for the denoising process.", "kwargsType": null, - "name": "padding_mask_crop", + "name": "sigmas", "required": false, - "type": "opaque" + "type": "list[builtins.float]" }, { - "default": null, - "description": "The dtype of the model inputs", + "default": 512, + "description": "The height in pixels of the generated image.", "kwargsType": null, - "name": "dtype", + "name": "height", "required": false, - "type": "torch.dtype" + "type": "builtins.int" }, { - "default": null, - "description": "", + "default": 704, + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "generator", + "name": "width", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": 1, - "description": "", + "default": null, + "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, - "name": "num_images_per_prompt", - "required": false, - "type": "opaque" + "name": "latents", + "required": true, + "type": "torch.Tensor" }, { "default": null, - "description": "Pre-generated image embeddings for IP-Adapter. Can be generated from ip_adapter step.", + "description": "Interpolation factor between random noise and any provided latents. `None` (default) resolves to 0.0, which keeps the provided latents.", "kwargsType": null, - "name": "ip_adapter_embeds", + "name": "noise_scale", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.float" }, { "default": null, - "description": "Pre-generated negative image embeddings for IP-Adapter. Can be generated from ip_adapter step.", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "negative_ip_adapter_embeds", + "name": "generator", "required": false, - "type": "list[torch.Tensor]" + "type": "torch._C.Generator" }, { - "default": 50, - "description": "", + "default": null, + "description": "Optional pre-encoded audio latents; random noise is used when not provided.", "kwargsType": null, - "name": "num_inference_steps", + "name": "audio_latents", "required": true, - "type": "opaque" + "type": "torch.Tensor" }, { - "default": null, - "description": "", + "default": true, + "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "opaque" + "name": "use_cross_timestep", + "required": false, + "type": "builtins.bool" }, { "default": null, - "description": "", + "description": "Additional kwargs for attention processors.", "kwargsType": null, - "name": "sigmas", + "name": "attention_kwargs", "required": false, - "type": "opaque" + "type": "dict[builtins.str,typing.Any]" }, { - "default": null, + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", + "kwargsType": null, + "name": "output_type", + "required": false, + "type": "builtins.str" + } + ], + "kind": "sequential", + "outputs": [ + { + "default": null, + "description": "Packed per-layer Gemma hidden states for the prompt.", + "kwargsType": null, + "name": "prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Binary attention mask for `prompt_embeds`.", + "kwargsType": null, + "name": "prompt_attention_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Packed per-layer Gemma hidden states for the negative prompt.", + "kwargsType": null, + "name": "negative_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Binary attention mask for `negative_prompt_embeds`.", + "kwargsType": null, + "name": "negative_prompt_attention_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The number of prompts being denoised (before per-prompt expansion).", + "kwargsType": null, + "name": "batch_size", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "The dtype of the prompt embeddings.", + "kwargsType": null, + "name": "dtype", + "required": false, + "type": "torch.dtype" + }, + { + "default": null, + "description": "Video-branch text conditioning (cond).", + "kwargsType": null, + "name": "connector_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Audio-branch text conditioning (cond).", + "kwargsType": null, + "name": "connector_audio_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Binary text attention mask (cond).", + "kwargsType": null, + "name": "connector_attention_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Video-branch text conditioning (uncond).", + "kwargsType": null, + "name": "negative_connector_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Audio-branch text conditioning (uncond).", + "kwargsType": null, + "name": "negative_connector_audio_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Binary text attention mask (uncond).", + "kwargsType": null, + "name": "negative_connector_attention_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The predicted number of frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "num_inference_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", + "kwargsType": null, + "name": "audio_scheduler", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "Packed noisy video latents.", + "kwargsType": null, + "name": "latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The resolved interpolation factor, forwarded to the audio latents step.", + "kwargsType": null, + "name": "noise_scale", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Packed noisy audio latents.", + "kwargsType": null, + "name": "audio_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Number of audio latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "audio_num_frames", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Video RoPE patch coordinates.", + "kwargsType": "denoiser_input_fields", + "name": "video_coords", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Audio RoPE patch coordinates.", + "kwargsType": "denoiser_input_fields", + "name": "audio_coords", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated videos.", + "kwargsType": null, + "name": "videos", + "required": false, + "type": "list[PIL.Image.Image]" + }, + { + "default": null, + "description": "The generated audio waveform.", + "kwargsType": null, + "name": "audio", + "required": false, + "type": "torch.Tensor" + } + ], + "requiredInputs": [ + "audio_latents", + "audio_num_frames", + "audio_scheduler", + "batch_size", + "connector_attention_mask", + "connector_audio_prompt_embeds", + "connector_prompt_embeds", + "dtype", + "latents", + "negative_connector_attention_mask", + "negative_connector_audio_prompt_embeds", + "negative_connector_prompt_embeds", + "negative_prompt_attention_mask", + "negative_prompt_embeds", + "num_inference_steps", + "prompt", + "prompt_attention_mask", + "prompt_embeds", + "timesteps" + ], + "schemaVersion": 1, + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] + }, + { + "className": "SequentialPipelineBlocks", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder", + "type": "transformers.models.clip.modeling_clip.CLIPTextModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder_2", + "type": "transformers.models.clip.modeling_clip.CLIPTextModelWithProjection" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "tokenizer", + "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "tokenizer_2", + "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 7.5 + }, + "description": "", + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 8 + }, + "description": "", + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "do_binarize": true, + "do_convert_grayscale": true, + "do_normalize": false, + "vae_scale_factor": 8 + }, + "description": "", + "name": "mask_processor", + "type": "diffusers.image_processor.VaeImageProcessor" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "unet", + "type": "diffusers.models.unets.unet_2d_condition.UNet2DConditionModel" + } + ], + "configs": [ + { + "default": true, + "description": "", + "name": "force_zeros_for_empty_prompt" + }, + { + "default": false, + "description": "", + "name": "requires_aesthetics_score" + } + ], + "contentHash": "sha256:8f9dd654bdf918314cd130422420d7ddac24f52b240ef5746ab573506f3087e8", + "description": "", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8f9dd654bdf918314cd130422420d7ddac24f52b240ef5746ab573506f3087e8", + "inputs": [ + { + "default": null, + "description": "", + "kwargsType": null, + "name": "prompt", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "prompt_2", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "negative_prompt", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "negative_prompt_2", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "cross_attention_kwargs", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "clip_skip", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "height", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "width", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "image", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "mask_image", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "padding_mask_crop", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "The dtype of the model inputs", + "kwargsType": null, + "name": "dtype", + "required": false, + "type": "torch.dtype" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "generator", + "required": false, + "type": "opaque" + }, + { + "default": 1, + "description": "", + "kwargsType": null, + "name": "num_images_per_prompt", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "Pre-generated image embeddings for IP-Adapter. Can be generated from ip_adapter step.", + "kwargsType": null, + "name": "ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Pre-generated negative image embeddings for IP-Adapter. Can be generated from ip_adapter step.", + "kwargsType": null, + "name": "negative_ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": 50, + "description": "", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "timesteps", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "opaque" + }, + { + "default": null, "description": "", "kwargsType": null, "name": "denoising_end", @@ -46205,6 +45401,511 @@ } ] }, + { + "className": "SequentialPipelineBlocks", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "resample": "bilinear", + "vae_scale_factor": 16 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" + } + ], + "configs": [ + { + "default": true, + "description": "", + "name": "default_use_system_prompt" + }, + { + "default": true, + "description": "", + "name": "enable_safety_checker" + }, + { + "default": false, + "description": "", + "name": "use_native_flow_schedule" + } + ], + "contentHash": "sha256:9789a379176c824be5fbd7531a3a381e4a79efb0d0b60faae8a52bf5d7ca9cb2", + "description": "", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9789a379176c824be5fbd7531a3a381e4a79efb0d0b60faae8a52bf5d7ca9cb2", + "inputs": [ + { + "default": null, + "description": "The text prompt that guides Cosmos3 generation.", + "kwargsType": null, + "name": "prompt", + "required": true, + "type": "builtins.str" + }, + { + "default": null, + "description": "The negative text prompt used for classifier-free guidance.", + "kwargsType": null, + "name": "negative_prompt", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Number of frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "Height of the generated video or image in pixels.", + "kwargsType": null, + "name": "height", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "Width of the generated video or image in pixels.", + "kwargsType": null, + "name": "width", + "required": true, + "type": "builtins.int" + }, + { + "default": 24.0, + "description": "Frame rate of the generated video.", + "kwargsType": null, + "name": "fps", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Whether to prepend the Cosmos3 system prompt.", + "kwargsType": null, + "name": "use_system_prompt", + "required": false, + "type": "UnionType[builtins.NoneType,builtins.bool]" + }, + { + "default": true, + "description": "Whether to add resolution metadata to the prompt.", + "kwargsType": null, + "name": "add_resolution_template", + "required": false, + "type": "builtins.bool" + }, + { + "default": true, + "description": "Whether to add duration metadata to the prompt.", + "kwargsType": null, + "name": "add_duration_template", + "required": false, + "type": "builtins.bool" + }, + { + "default": null, + "description": "Reference image for image-to-video conditioning.", + "kwargsType": null, + "name": "image", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "Pre-generated noisy vision latents.", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Torch generator for deterministic generation.", + "kwargsType": null, + "name": "generator", + "required": false, + "type": "torch._C.Generator" + }, + { + "default": 50, + "description": "The number of denoising steps.", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, + { + "default": 6.0, + "description": "Scale for classifier-free guidance.", + "kwargsType": null, + "name": "guidance_scale", + "required": false, + "type": "builtins.float" + }, + { + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", + "kwargsType": null, + "name": "output_type", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Denoised action latents.", + "kwargsType": null, + "name": "action_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Requested action-generation mode.", + "kwargsType": null, + "name": "action_mode", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Unpadded action-vector dimension.", + "kwargsType": null, + "name": "raw_action_dim_resolved", + "required": false, + "type": "builtins.int" + } + ], + "kind": "sequential", + "outputs": [ + { + "default": null, + "description": "Number of frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Height of the generated video or image in pixels.", + "kwargsType": null, + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Width of the generated video or image in pixels.", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Token IDs for the conditional prompt.", + "kwargsType": null, + "name": "cond_input_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Token IDs for the unconditional prompt.", + "kwargsType": null, + "name": "uncond_input_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Vision latents encoded from the conditioning image or video.", + "kwargsType": null, + "name": "x0_tokens_vision", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Latent-frame indexes fixed by visual conditioning.", + "kwargsType": null, + "name": "vision_condition_frames", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "Conditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_text_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_text_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Noisy vision latents for denoising.", + "kwargsType": null, + "name": "latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Frame rate used to pack vision latents.", + "kwargsType": null, + "name": "fps_vision", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Mask marking conditioned vision latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "vision_condition_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Indexes of conditioned vision latent frames.", + "kwargsType": null, + "name": "vision_condition_indexes_for_pack", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", + "kwargsType": null, + "name": "vision_conditioning_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Conditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Conditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "cond_position_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Unconditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_position_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Conditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "cond_sequence_length", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Unconditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_sequence_length", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Scheduler timesteps for denoising.", + "kwargsType": null, + "name": "timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Number of scheduler warmup steps.", + "kwargsType": null, + "name": "num_warmup_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Vision tokens for the transformer denoiser.", + "kwargsType": null, + "name": "vision_tokens", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Timesteps for the vision tokens.", + "kwargsType": null, + "name": "vision_timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for vision latents.", + "kwargsType": null, + "name": "velocity_vision", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for sound latents.", + "kwargsType": null, + "name": "velocity_sound", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for action latents.", + "kwargsType": null, + "name": "velocity_action", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated videos.", + "kwargsType": null, + "name": "videos", + "required": false, + "type": "list[PIL.Image.Image]" + }, + { + "default": null, + "description": "Generated action vectors.", + "kwargsType": null, + "name": "action", + "required": false, + "type": "list[torch.Tensor]" + } + ], + "requiredInputs": [ + "cond_input_ids", + "cond_text_segment", + "cond_vision_segment", + "fps_vision", + "height", + "latents", + "num_frames", + "num_inference_steps", + "num_warmup_steps", + "prompt", + "timesteps", + "uncond_input_ids", + "uncond_text_segment", + "uncond_vision_segment", + "velocity_vision", + "vision_condition_indexes_for_pack", + "width" + ], + "schemaVersion": 1, + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] + }, { "className": "SequentialPipelineBlocks", "components": [ @@ -46730,167 +46431,176 @@ { "className": "SequentialPipelineBlocks", "components": [ - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 16 - }, - "description": "", - "name": "image_resize_processor", - "type": "diffusers.image_processor.VaeImageProcessor" - }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder", - "type": "transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLForConditionalGeneration" + "name": "text_tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "processor", - "type": "transformers.models.qwen2_vl.processing_qwen2_vl.Qwen2VLProcessor" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, - "description": "The tokenizer to use", - "name": "tokenizer", - "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" }, { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 4.0 - }, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" }, { "creationMethod": "from_config", "defaultConfig": { + "resample": "bilinear", "vae_scale_factor": 16 }, "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage" - }, + "name": "sound_tokenizer", + "type": "diffusers.models.autoencoders.autoencoder_cosmos3_audio.Cosmos3AVAEAudioTokenizer" + } + ], + "configs": [ { - "creationMethod": "from_config", - "defaultConfig": null, + "default": true, "description": "", - "name": "pachifier", - "type": "diffusers.modular_pipelines.qwenimage.modular_pipeline.QwenImageLayeredPachifier" + "name": "default_use_system_prompt" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "default": true, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "enable_safety_checker" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "default": false, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_qwenimage.QwenImageTransformer2DModel" + "name": "use_native_flow_schedule" } ], - "configs": [], - "contentHash": "sha256:9ccd37779780a50224a7afdc1b4e309e4d31f901731de4f06dfd6c8f15c14da3", + "contentHash": "sha256:9b89b8d12e1d02cb9c32c2d61d3a7d4ad1f0db0ff4827c8bb4bbdfeaf7544f52", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9ccd37779780a50224a7afdc1b4e309e4d31f901731de4f06dfd6c8f15c14da3", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9b89b8d12e1d02cb9c32c2d61d3a7d4ad1f0db0ff4827c8bb4bbdfeaf7544f52", "inputs": [ { "default": null, - "description": "Reference image(s) for denoising. Can be a single image or list of images.", + "description": "The text prompt that guides Cosmos3 generation.", "kwargsType": null, - "name": "image", + "name": "prompt", "required": true, - "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" + "type": "builtins.str" }, { - "default": 640, - "description": "The target area to resize the image to, can be 1024 or 640", + "default": null, + "description": "The negative text prompt used for classifier-free guidance.", "kwargsType": null, - "name": "resolution", + "name": "negative_prompt", "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Number of frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": true, "type": "builtins.int" }, { "default": null, - "description": "The prompt or prompts to guide image generation.", + "description": "Height of the generated video or image in pixels.", "kwargsType": null, - "name": "prompt", + "name": "height", "required": true, - "type": "builtins.str" + "type": "builtins.int" }, { - "default": false, - "description": "Whether to use English prompt template", + "default": null, + "description": "Width of the generated video or image in pixels.", "kwargsType": null, - "name": "use_en_prompt", + "name": "width", + "required": true, + "type": "builtins.int" + }, + { + "default": 24.0, + "description": "Frame rate of the generated video.", + "kwargsType": null, + "name": "fps", "required": false, - "type": "builtins.bool" + "type": "builtins.float" }, { "default": null, - "description": "The prompt or prompts not to guide the image generation.", + "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, - "name": "negative_prompt", + "name": "use_system_prompt", "required": false, - "type": "builtins.str" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { - "default": 1024, - "description": "Maximum sequence length for prompt encoding.", + "default": true, + "description": "Whether to add resolution metadata to the prompt.", "kwargsType": null, - "name": "max_sequence_length", + "name": "add_resolution_template", "required": false, - "type": "builtins.int" + "type": "builtins.bool" + }, + { + "default": true, + "description": "Whether to add duration metadata to the prompt.", + "kwargsType": null, + "name": "add_duration_template", + "required": false, + "type": "builtins.bool" }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "Vision latents encoded from the conditioning image or video.", "kwargsType": null, - "name": "generator", + "name": "x0_tokens_vision", "required": false, - "type": "torch._C.Generator" + "type": "torch.Tensor" }, { - "default": 1, - "description": "The number of images to generate per prompt.", + "default": null, + "description": "Latent-frame indexes fixed by visual conditioning.", "kwargsType": null, - "name": "num_images_per_prompt", + "name": "vision_condition_frames", "required": false, - "type": "builtins.int" + "type": "list[builtins.int]" }, { "default": null, - "description": "Pre-generated noisy latents for image generation.", + "description": "Pre-generated noisy vision latents.", "kwargsType": null, "name": "latents", "required": true, "type": "torch.Tensor" }, { - "default": 4, - "description": "Number of layers to extract from the image", + "default": null, + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "layers", + "name": "generator", "required": false, - "type": "builtins.int" + "type": "torch._C.Generator" }, { "default": 50, @@ -46902,19 +46612,51 @@ }, { "default": null, - "description": "Custom sigmas for the denoising process.", + "description": "Pre-generated noisy sound latents.", "kwargsType": null, - "name": "sigmas", + "name": "sound_latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", "required": false, - "type": "list[builtins.float]" + "type": "builtins.str" }, { "default": null, - "description": "Additional kwargs for attention processors.", + "description": "Optional leading W8A16 step count.", "kwargsType": null, - "name": "attention_kwargs", + "name": "mixed_precision_first_steps", "required": false, - "type": "dict[builtins.str,typing.Any]" + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, + { + "default": 6.0, + "description": "Scale for classifier-free guidance.", + "kwargsType": null, + "name": "guidance_scale", + "required": false, + "type": "builtins.float" }, { "default": "pil", @@ -46923,192 +46665,358 @@ "name": "output_type", "required": false, "type": "builtins.str" + }, + { + "default": null, + "description": "Denoised action latents.", + "kwargsType": null, + "name": "action_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Requested action-generation mode.", + "kwargsType": null, + "name": "action_mode", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Unpadded action-vector dimension.", + "kwargsType": null, + "name": "raw_action_dim_resolved", + "required": false, + "type": "builtins.int" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "The resized images", + "description": "Number of frames to generate.", "kwargsType": null, - "name": "resized_image", + "name": "num_frames", "required": false, - "type": "list[PIL.Image.Image]" + "type": "builtins.int" }, { "default": null, - "description": "The prompt or prompts to guide image generation. If not provided, updated using image caption", + "description": "Height of the generated video or image in pixels.", "kwargsType": null, - "name": "prompt", + "name": "height", "required": false, - "type": "builtins.str" + "type": "builtins.int" }, { "default": null, - "description": "The prompt embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", + "description": "Width of the generated video or image in pixels.", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Token IDs for the conditional prompt.", + "kwargsType": null, + "name": "cond_input_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The encoder attention mask.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds_mask", + "description": "Token IDs for the unconditional prompt.", + "kwargsType": null, + "name": "uncond_input_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The negative prompt embeddings.", + "description": "Conditional text segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", + "name": "cond_text_segment", "required": false, - "type": "torch.Tensor" + "type": "builtins.dict" }, { "default": null, - "description": "The negative prompt embeddings mask.", + "description": "Unconditional text segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds_mask", + "name": "uncond_text_segment", "required": false, - "type": "torch.Tensor" + "type": "builtins.dict" }, { "default": null, - "description": "The processed image", + "description": "Noisy vision latents for denoising.", "kwargsType": null, - "name": "processed_image", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The latent representation of the input image.", + "description": "Frame rate used to pack vision latents.", "kwargsType": null, - "name": "image_latents", + "name": "fps_vision", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Mask marking conditioned vision latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "vision_condition_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The batch size of the prompt embeddings", + "description": "Indexes of conditioned vision latent frames.", "kwargsType": null, - "name": "batch_size", + "name": "vision_condition_indexes_for_pack", "required": false, - "type": "builtins.int" + "type": "list[builtins.int]" }, { "default": null, - "description": "The data type of the prompt embeddings", + "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", "kwargsType": null, - "name": "dtype", + "name": "vision_conditioning_latents", "required": false, - "type": "torch.dtype" + "type": "torch.Tensor" }, { "default": null, - "description": "The image height calculated from the image latents dimension", - "kwargsType": null, - "name": "image_height", + "description": "Conditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Conditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "cond_position_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Unconditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_position_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Conditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "cond_sequence_length", "required": false, "type": "builtins.int" }, { "default": null, - "description": "The image width calculated from the image latents dimension", - "kwargsType": null, - "name": "image_width", + "description": "Unconditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_sequence_length", "required": false, "type": "builtins.int" }, { "default": null, - "description": "if not provided, updated to image height", + "description": "Scheduler timesteps for denoising.", "kwargsType": null, - "name": "height", + "name": "timesteps", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "if not provided, updated to image width", + "description": "Number of scheduler warmup steps.", "kwargsType": null, - "name": "width", + "name": "num_warmup_steps", "required": false, "type": "builtins.int" }, { "default": null, - "description": "The initial latents to use for the denoising process", + "description": "Noisy sound latents for denoising.", "kwargsType": null, - "name": "latents", + "name": "sound_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The timesteps to use for the denoising process.", + "description": "Frame rate of the sound latent sequence.", "kwargsType": null, - "name": "timesteps", + "name": "fps_sound", "required": false, - "type": "torch.Tensor" + "type": "builtins.float" }, { "default": null, - "description": "The shapes of the image latents, used for RoPE calculation", + "description": "Mask marking conditioned sound latent frames.", "kwargsType": "denoiser_input_fields", - "name": "img_shapes", + "name": "sound_condition_mask", "required": false, - "type": "list[list[tuple[builtins.int]]]" + "type": "torch.Tensor" }, { "default": null, - "description": "The sequence lengths of the prompt embeds, used for RoPE calculation", - "kwargsType": "denoiser_input_fields", - "name": "txt_seq_lens", + "description": "Scheduler used to update sound latents.", + "kwargsType": null, + "name": "sound_scheduler", "required": false, - "type": "list[builtins.int]" + "type": "opaque" }, { "default": null, - "description": "The sequence lengths of the negative prompt embeds, used for RoPE calculation", + "description": "Conditional sound segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "negative_txt_seq_lens", + "name": "cond_sound_segment", "required": false, - "type": "list[builtins.int]" + "type": "builtins.dict" }, { "default": null, - "description": "The additional t cond, used for RoPE calculation", + "description": "Unconditional sound segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "additional_t_cond", + "name": "uncond_sound_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Vision tokens for the transformer denoiser.", + "kwargsType": null, + "name": "vision_tokens", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Timesteps for the vision tokens.", + "kwargsType": null, + "name": "vision_timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Generated images.", + "description": "Sound tokens for the transformer denoiser.", "kwargsType": null, - "name": "images", + "name": "sound_tokens", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Timesteps for the sound tokens.", + "kwargsType": null, + "name": "sound_timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for vision latents.", + "kwargsType": null, + "name": "velocity_vision", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for sound latents.", + "kwargsType": null, + "name": "velocity_sound", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for action latents.", + "kwargsType": null, + "name": "velocity_action", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated videos.", + "kwargsType": null, + "name": "videos", "required": false, "type": "list[PIL.Image.Image]" + }, + { + "default": null, + "description": "Generated waveform.", + "kwargsType": null, + "name": "sound", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Sample rate of the generated waveform in Hz.", + "kwargsType": null, + "name": "sampling_rate", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Generated action vectors.", + "kwargsType": null, + "name": "action", + "required": false, + "type": "list[torch.Tensor]" } ], "requiredInputs": [ + "cond_input_ids", + "cond_position_ids", + "cond_sequence_length", + "cond_sound_segment", + "cond_text_segment", + "cond_vision_segment", + "fps_sound", + "fps_vision", "height", - "image", - "image_latents", - "img_shapes", "latents", + "num_frames", "num_inference_steps", - "processed_image", + "num_warmup_steps", "prompt", - "prompt_embeds", - "prompt_embeds_mask", - "resized_image", + "sound_latents", + "sound_scheduler", "timesteps", + "uncond_input_ids", + "uncond_position_ids", + "uncond_sequence_length", + "uncond_sound_segment", + "uncond_text_segment", + "uncond_vision_segment", + "velocity_sound", + "velocity_vision", + "vision_condition_indexes_for_pack", "width" ], "schemaVersion": 1, @@ -47126,147 +47034,148 @@ "className": "SequentialPipelineBlocks", "components": [ { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 16 + }, "description": "", - "name": "text_encoder", - "type": "transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLTextModel" + "name": "image_resize_processor", + "type": "diffusers.image_processor.VaeImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "tokenizer", - "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" + "name": "text_encoder", + "type": "transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLForConditionalGeneration" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder_2", - "type": "transformers.models.t5.modeling_t5.T5EncoderModel" + "name": "processor", + "type": "transformers.models.qwen2_vl.processing_qwen2_vl.Qwen2VLProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, - "description": "", - "name": "tokenizer_2", - "type": "transformers.models.byt5.tokenization_byt5.ByT5Tokenizer" + "description": "The tokenizer to use", + "name": "tokenizer", + "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_scale": 7.5 + "guidance_scale": 4.0 }, "description": "", "name": "guider", "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 16 + }, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_hunyuan_video15.HunyuanVideo15Transformer3DModel" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage" }, { "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 16 - }, + "defaultConfig": null, "description": "", - "name": "video_processor", - "type": "diffusers.pipelines.hunyuan_video1_5.image_processor.HunyuanVideo15ImageProcessor" + "name": "pachifier", + "type": "diffusers.modular_pipelines.qwenimage.modular_pipeline.QwenImageLayeredPachifier" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_hunyuanvideo15.AutoencoderKLHunyuanVideo15" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_qwenimage.QwenImageTransformer2DModel" } ], "configs": [], - "contentHash": "sha256:9cd1c4aab0caf384724f949c5527d1ece264dbcf5cabef4d2027dcdfc8ab37cc", + "contentHash": "sha256:9ccd37779780a50224a7afdc1b4e309e4d31f901731de4f06dfd6c8f15c14da3", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9cd1c4aab0caf384724f949c5527d1ece264dbcf5cabef4d2027dcdfc8ab37cc", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9ccd37779780a50224a7afdc1b4e309e4d31f901731de4f06dfd6c8f15c14da3", "inputs": [ { "default": null, - "description": "The prompt or prompts to guide image generation.", - "kwargsType": null, - "name": "prompt", - "required": false, - "type": "builtins.str" - }, - { - "default": null, - "description": "The prompt or prompts not to guide the image generation.", + "description": "Reference image(s) for denoising. Can be a single image or list of images.", "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "builtins.str" + "name": "image", + "required": true, + "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" }, { - "default": 1, - "description": "The number of images to generate per prompt.", + "default": 640, + "description": "The target area to resize the image to, can be 1024 or 640", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "resolution", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt. Can be generated in input step.", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, - "name": "batch_size", + "name": "prompt", "required": true, - "type": "builtins.int" + "type": "builtins.str" }, { - "default": 50, - "description": "The number of denoising steps.", + "default": false, + "description": "Whether to use English prompt template", "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" + "name": "use_en_prompt", + "required": false, + "type": "builtins.bool" }, { "default": null, - "description": "Custom sigmas for the denoising process.", + "description": "The prompt or prompts not to guide the image generation.", "kwargsType": null, - "name": "sigmas", + "name": "negative_prompt", "required": false, - "type": "list[builtins.float]" + "type": "builtins.str" }, { - "default": null, - "description": "The height in pixels of the generated image.", + "default": 1024, + "description": "Maximum sequence length for prompt encoding.", "kwargsType": null, - "name": "height", + "name": "max_sequence_length", "required": false, "type": "builtins.int" }, { "default": null, - "description": "The width in pixels of the generated image.", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "width", + "name": "generator", "required": false, - "type": "builtins.int" + "type": "torch._C.Generator" }, { - "default": 121, - "description": "Number of video frames to generate.", + "default": 1, + "description": "The number of images to generate per prompt.", "kwargsType": null, - "name": "num_frames", + "name": "num_images_per_prompt", "required": false, "type": "builtins.int" }, @@ -47278,13 +47187,29 @@ "required": true, "type": "torch.Tensor" }, + { + "default": 4, + "description": "Number of layers to extract from the image", + "kwargsType": null, + "name": "layers", + "required": false, + "type": "builtins.int" + }, + { + "default": 50, + "description": "The number of denoising steps.", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "builtins.int" + }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "Custom sigmas for the denoising process.", "kwargsType": null, - "name": "generator", + "name": "sigmas", "required": false, - "type": "torch._C.Generator" + "type": "list[builtins.float]" }, { "default": null, @@ -47295,7 +47220,7 @@ "type": "dict[builtins.str,typing.Any]" }, { - "default": "np", + "default": "pil", "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, "name": "output_type", @@ -47305,6 +47230,22 @@ ], "kind": "sequential", "outputs": [ + { + "default": null, + "description": "The resized images", + "kwargsType": null, + "name": "resized_image", + "required": false, + "type": "list[PIL.Image.Image]" + }, + { + "default": null, + "description": "The prompt or prompts to guide image generation. If not provided, updated using image caption", + "kwargsType": null, + "name": "prompt", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "The prompt embeddings.", @@ -47339,115 +47280,150 @@ }, { "default": null, - "description": "ByT5 glyph-text embeddings used as a second conditioning stream for the transformer.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds_2", + "description": "The processed image", + "kwargsType": null, + "name": "processed_image", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Attention mask for the ByT5 glyph-text embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds_mask_2", + "description": "The latent representation of the input image.", + "kwargsType": null, + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "ByT5 glyph-text negative embeddings for classifier-free guidance.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds_2", + "description": "The batch size of the prompt embeddings", + "kwargsType": null, + "name": "batch_size", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Attention mask for the ByT5 glyph-text negative embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds_mask_2", + "description": "The data type of the prompt embeddings", + "kwargsType": null, + "name": "dtype", "required": false, - "type": "torch.Tensor" + "type": "torch.dtype" }, { "default": null, - "description": "", + "description": "The image height calculated from the image latents dimension", "kwargsType": null, - "name": "batch_size", + "name": "image_height", "required": false, "type": "builtins.int" }, { "default": null, - "description": "", + "description": "The image width calculated from the image latents dimension", "kwargsType": null, - "name": "timesteps", + "name": "image_width", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "", + "description": "if not provided, updated to image height", "kwargsType": null, - "name": "num_inference_steps", + "name": "height", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Pure noise latents", + "description": "if not provided, updated to image width", "kwargsType": null, - "name": "latents", + "name": "width", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "", + "description": "The initial latents to use for the denoising process", "kwargsType": null, - "name": "cond_latents_concat", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "", + "description": "The timesteps to use for the denoising process.", "kwargsType": null, - "name": "mask_concat", + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "", - "kwargsType": null, - "name": "image_embeds", + "description": "The shapes of the image latents, used for RoPE calculation", + "kwargsType": "denoiser_input_fields", + "name": "img_shapes", + "required": false, + "type": "list[list[tuple[builtins.int]]]" + }, + { + "default": null, + "description": "The sequence lengths of the prompt embeds, used for RoPE calculation", + "kwargsType": "denoiser_input_fields", + "name": "txt_seq_lens", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "The sequence lengths of the negative prompt embeds, used for RoPE calculation", + "kwargsType": "denoiser_input_fields", + "name": "negative_txt_seq_lens", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "The additional t cond, used for RoPE calculation", + "kwargsType": "denoiser_input_fields", + "name": "additional_t_cond", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The generated videos.", + "description": "Generated images.", "kwargsType": null, - "name": "videos", + "name": "images", "required": false, "type": "list[PIL.Image.Image]" } ], "requiredInputs": [ - "batch_size", - "cond_latents_concat", + "height", + "image", + "image_latents", + "img_shapes", "latents", - "mask_concat", "num_inference_steps", + "processed_image", + "prompt", "prompt_embeds", - "prompt_embeds_2", "prompt_embeds_mask", - "prompt_embeds_mask_2", - "timesteps" + "resized_image", + "timesteps", + "width" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "SequentialPipelineBlocks", @@ -47456,153 +47432,150 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + "name": "text_encoder", + "type": "transformers.models.qwen2_5_vl.modeling_qwen2_5_vl.Qwen2_5_VLTextModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + "name": "tokenizer", + "type": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + "name": "text_encoder_2", + "type": "transformers.models.t5.modeling_t5.T5EncoderModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "tokenizer_2", + "type": "transformers.models.byt5.tokenization_byt5.ByT5Tokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 16 + "guidance_scale": 7.5 }, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" - } - ], - "configs": [ + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "default_use_system_prompt" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "enable_safety_checker" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_hunyuan_video15.HunyuanVideo15Transformer3DModel" }, { - "default": false, + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 16 + }, "description": "", - "name": "use_native_flow_schedule" + "name": "video_processor", + "type": "diffusers.pipelines.hunyuan_video1_5.image_processor.HunyuanVideo15ImageProcessor" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_hunyuanvideo15.AutoencoderKLHunyuanVideo15" } ], - "contentHash": "sha256:9cf3268e42b2d81745f83067d5bf4170a6fe44bd7efb3e01a259e20eb1938959", + "configs": [], + "contentHash": "sha256:9cd1c4aab0caf384724f949c5527d1ece264dbcf5cabef4d2027dcdfc8ab37cc", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9cf3268e42b2d81745f83067d5bf4170a6fe44bd7efb3e01a259e20eb1938959", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9cd1c4aab0caf384724f949c5527d1ece264dbcf5cabef4d2027dcdfc8ab37cc", "inputs": [ { "default": null, - "description": "The text prompt that guides Cosmos3 generation.", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", - "required": true, + "required": false, "type": "builtins.str" }, { "default": null, - "description": "The negative text prompt used for classifier-free guidance.", + "description": "The prompt or prompts not to guide the image generation.", "kwargsType": null, "name": "negative_prompt", "required": false, "type": "builtins.str" }, { - "default": null, - "description": "Number of frames to generate.", + "default": 1, + "description": "The number of images to generate per prompt.", "kwargsType": null, - "name": "num_frames", - "required": true, + "name": "num_videos_per_prompt", + "required": false, "type": "builtins.int" }, { "default": null, - "description": "Height of the generated video or image in pixels.", + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt. Can be generated in input step.", "kwargsType": null, - "name": "height", + "name": "batch_size", "required": true, "type": "builtins.int" }, { - "default": null, - "description": "Width of the generated video or image in pixels.", + "default": 50, + "description": "The number of denoising steps.", "kwargsType": null, - "name": "width", + "name": "num_inference_steps", "required": true, "type": "builtins.int" }, - { - "default": 24.0, - "description": "Frame rate of the generated video.", - "kwargsType": null, - "name": "fps", - "required": false, - "type": "builtins.float" - }, { "default": null, - "description": "Whether to prepend the Cosmos3 system prompt.", - "kwargsType": null, - "name": "use_system_prompt", - "required": false, - "type": "UnionType[builtins.NoneType,builtins.bool]" - }, - { - "default": true, - "description": "Whether to add resolution metadata to the prompt.", + "description": "Custom sigmas for the denoising process.", "kwargsType": null, - "name": "add_resolution_template", + "name": "sigmas", "required": false, - "type": "builtins.bool" + "type": "list[builtins.float]" }, { - "default": true, - "description": "Whether to add duration metadata to the prompt.", + "default": null, + "description": "The height in pixels of the generated image.", "kwargsType": null, - "name": "add_duration_template", + "name": "height", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { "default": null, - "description": "Vision latents encoded from the conditioning image or video.", + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "x0_tokens_vision", + "name": "width", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { - "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", + "default": 121, + "description": "Number of video frames to generate.", "kwargsType": null, - "name": "vision_condition_frames", + "name": "num_frames", "required": false, - "type": "list[builtins.int]" + "type": "builtins.int" }, { "default": null, - "description": "Pre-generated noisy vision latents.", + "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, "name": "latents", "required": true, @@ -47617,253 +47590,141 @@ "type": "torch._C.Generator" }, { - "default": 50, - "description": "The number of denoising steps.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": 6.0, - "description": "Scale for classifier-free guidance.", + "default": null, + "description": "Additional kwargs for attention processors.", "kwargsType": null, - "name": "guidance_scale", + "name": "attention_kwargs", "required": false, - "type": "builtins.float" + "type": "dict[builtins.str,typing.Any]" }, { - "default": "pil", + "default": "np", "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, "name": "output_type", "required": false, "type": "builtins.str" - }, - { - "default": null, - "description": "Denoised action latents.", - "kwargsType": null, - "name": "action_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Requested action-generation mode.", - "kwargsType": null, - "name": "action_mode", - "required": false, - "type": "builtins.str" - }, - { - "default": null, - "description": "Unpadded action-vector dimension.", - "kwargsType": null, - "name": "raw_action_dim_resolved", - "required": false, - "type": "builtins.int" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "Number of frames to generate.", - "kwargsType": null, - "name": "num_frames", + "description": "The prompt embeddings.", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Height of the generated video or image in pixels.", - "kwargsType": null, - "name": "height", + "description": "The encoder attention mask.", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds_mask", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Width of the generated video or image in pixels.", - "kwargsType": null, - "name": "width", + "description": "The negative prompt embeddings.", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the conditional prompt.", - "kwargsType": null, - "name": "cond_input_ids", + "description": "The negative prompt embeddings mask.", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the unconditional prompt.", - "kwargsType": null, - "name": "uncond_input_ids", + "description": "ByT5 glyph-text embeddings used as a second conditioning stream for the transformer.", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds_2", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional text segment for the denoiser.", + "description": "Attention mask for the ByT5 glyph-text embeddings.", "kwargsType": "denoiser_input_fields", - "name": "cond_text_segment", + "name": "prompt_embeds_mask_2", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional text segment for the denoiser.", + "description": "ByT5 glyph-text negative embeddings for classifier-free guidance.", "kwargsType": "denoiser_input_fields", - "name": "uncond_text_segment", + "name": "negative_prompt_embeds_2", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Noisy vision latents for denoising.", - "kwargsType": null, - "name": "latents", + "description": "Attention mask for the ByT5 glyph-text negative embeddings.", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds_mask_2", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Frame rate used to pack vision latents.", + "description": "", "kwargsType": null, - "name": "fps_vision", + "name": "batch_size", "required": false, - "type": "builtins.float" + "type": "builtins.int" }, { "default": null, - "description": "Mask marking conditioned vision latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "vision_condition_mask", + "description": "", + "kwargsType": null, + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Indexes of conditioned vision latent frames.", + "description": "", "kwargsType": null, - "name": "vision_condition_indexes_for_pack", + "name": "num_inference_steps", "required": false, - "type": "list[builtins.int]" + "type": "builtins.int" }, { "default": null, - "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", + "description": "Pure noise latents", "kwargsType": null, - "name": "vision_conditioning_latents", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_vision_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Unconditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_vision_segment", + "description": "", + "kwargsType": null, + "name": "cond_latents_concat", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Conditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "cond_position_ids", + "description": "", + "kwargsType": null, + "name": "mask_concat", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_position_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Conditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sequence_length", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Unconditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sequence_length", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Scheduler timesteps for denoising.", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Number of scheduler warmup steps.", - "kwargsType": null, - "name": "num_warmup_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Vision tokens for the transformer denoiser.", - "kwargsType": null, - "name": "vision_tokens", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "Timesteps for the vision tokens.", - "kwargsType": null, - "name": "vision_timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Predicted velocity for vision latents.", - "kwargsType": null, - "name": "velocity_vision", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Predicted velocity for sound latents.", - "kwargsType": null, - "name": "velocity_sound", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Predicted velocity for action latents.", - "kwargsType": null, - "name": "velocity_action", + "description": "", + "kwargsType": null, + "name": "image_embeds", "required": false, "type": "torch.Tensor" }, @@ -47874,45 +47735,22 @@ "name": "videos", "required": false, "type": "list[PIL.Image.Image]" - }, - { - "default": null, - "description": "Generated action vectors.", - "kwargsType": null, - "name": "action", - "required": false, - "type": "list[torch.Tensor]" } ], "requiredInputs": [ - "cond_input_ids", - "cond_text_segment", - "cond_vision_segment", - "fps_vision", - "height", + "batch_size", + "cond_latents_concat", "latents", - "num_frames", + "mask_concat", "num_inference_steps", - "num_warmup_steps", - "prompt", - "timesteps", - "uncond_input_ids", - "uncond_text_segment", - "uncond_vision_segment", - "velocity_vision", - "vision_condition_indexes_for_pack", - "width" + "prompt_embeds", + "prompt_embeds_2", + "prompt_embeds_mask", + "prompt_embeds_mask_2", + "timesteps" ], "schemaVersion": 1, - "variadicInputs": [ - { - "default": null, - "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", - "kwargsType": "denoiser_input_fields", - "required": false, - "type": "opaque" - } - ] + "variadicInputs": [] }, { "className": "SequentialPipelineBlocks", @@ -48324,70 +48162,111 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + "name": "text_encoder", + "type": "transformers.modeling_utils.PreTrainedModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "tokenizer", + "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + "name": "connectors", + "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" }, { "creationMethod": "from_config", "defaultConfig": { "resample": "bilinear", - "vae_scale_factor": 16 + "vae_scale_factor": 32 }, "description": "", "name": "video_processor", "type": "diffusers.video_processor.VideoProcessor" - } - ], - "configs": [ + }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "default_use_system_prompt" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "enable_safety_checker" + "name": "audio_vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" }, { - "default": true, + "creationMethod": "from_config", + "defaultConfig": { + "guidance_rescale": 0.7, + "guidance_scale": 3.0, + "modality_scale": 3.0, + "spatio_temporal_guidance_blocks": [ + 28 + ], + "stg_scale": 1.0 + }, "description": "", - "name": "is_distilled" + "name": "guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { - "default": null, + "creationMethod": "from_config", + "defaultConfig": { + "guidance_rescale": 0.7, + "guidance_scale": 7.0, + "modality_scale": 3.0, + "stg_scale": 1.0 + }, "description": "", - "name": "distilled_sigmas" + "name": "audio_guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "diffusion_decoder", + "type": "diffusers.models.autoencoders.ltx2_diffusion_decoder.LTX2VideoDiffusionDecoderModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vocoder", + "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" } ], - "contentHash": "sha256:9e229f4b014e97f18ca5366654b65d4b35a5468797c43f44e0b057b034ff9088", + "configs": [], + "contentHash": "sha256:9f1a8fa2e9e5ea98cb030cf2860b7419c4a98d811bb48fd48c8d1124b47d411a", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9e229f4b014e97f18ca5366654b65d4b35a5468797c43f44e0b057b034ff9088", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9f1a8fa2e9e5ea98cb030cf2860b7419c4a98d811bb48fd48c8d1124b47d411a", "inputs": [ { "default": null, - "description": "The text prompt that guides Cosmos3 generation.", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", "required": true, @@ -48395,71 +48274,111 @@ }, { "default": null, - "description": "Number of frames to generate.", + "description": "The prompt or prompts not to guide the image generation.", "kwargsType": null, - "name": "num_frames", - "required": true, + "name": "negative_prompt", + "required": false, + "type": "builtins.str" + }, + { + "default": 1024, + "description": "Maximum sequence length for prompt encoding.", + "kwargsType": null, + "name": "max_sequence_length", + "required": false, "type": "builtins.int" }, { "default": null, - "description": "Height of the generated video or image in pixels.", + "description": "`LTX2VideoCondition` (or list of them) placing image/video conditions at latent frame indices of the generated video.", + "kwargsType": null, + "name": "conditions", + "required": false, + "type": "builtins.list" + }, + { + "default": 512, + "description": "The height in pixels of the generated image.", "kwargsType": null, "name": "height", - "required": true, + "required": false, "type": "builtins.int" }, { - "default": null, - "description": "Width of the generated video or image in pixels.", + "default": 704, + "description": "The width in pixels of the generated image.", "kwargsType": null, "name": "width", - "required": true, + "required": false, "type": "builtins.int" }, { - "default": 24.0, - "description": "Frame rate of the generated video.", + "default": null, + "description": "The number of frames in the generated video. Omit to auto-predict via the `duration_head` (see `LTX2AutoDurationStep`).", "kwargsType": null, - "name": "fps", + "name": "num_frames", "required": false, - "type": "builtins.float" + "type": "builtins.int" }, { - "default": true, - "description": "Whether to prepend the Cosmos3 system prompt.", + "default": null, + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "use_system_prompt", + "name": "generator", "required": false, - "type": "builtins.bool" + "type": "torch._C.Generator" }, { - "default": true, - "description": "Whether to add resolution metadata to the prompt.", + "default": null, + "description": "`LTX2ReferenceCondition` (or list of them) whose videos are encoded into extra latent tokens the IC-LoRA adapter attends to.", "kwargsType": null, - "name": "add_resolution_template", + "name": "reference_conditions", + "required": true, + "type": "builtins.list" + }, + { + "default": 1, + "description": "Ratio between the target and reference resolutions; 2 means the reference is preprocessed at half the target resolution. Spatial coordinates are scaled by this factor so the reference tokens land in the target coordinate space. Must match the factor the IC-LoRA was trained with.", + "kwargsType": null, + "name": "reference_downscale_factor", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { - "default": true, - "description": "Whether to add duration metadata to the prompt.", + "default": 1.0, + "description": "Scalar in [0, 1] controlling how strongly the noisy tokens and reference tokens attend to each other. 1.0 (default) leaves attention unmasked.", "kwargsType": null, - "name": "add_duration_template", + "name": "conditioning_attention_strength", "required": false, - "type": "builtins.bool" + "type": "builtins.float" }, { "default": null, - "description": "Reference image for image-to-video conditioning.", + "description": "Optional pixel-space mask of shape (1, 1, F, H, W) with values in [0, 1] giving spatially varying attention strength. Downsampled to the reference's latent grid and multiplied by `conditioning_attention_strength`.", "kwargsType": null, - "name": "image", + "name": "conditioning_attention_mask", "required": false, - "type": "opaque" + "type": "torch.Tensor" + }, + { + "default": 24.0, + "description": "Frames per second of the generated video.", + "kwargsType": null, + "name": "frame_rate", + "required": false, + "type": "builtins.float" + }, + { + "default": 1, + "description": "The number of images to generate per prompt.", + "kwargsType": null, + "name": "num_videos_per_prompt", + "required": false, + "type": "builtins.int" }, { "default": null, - "description": "Pre-generated noisy vision latents.", + "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, "name": "latents", "required": true, @@ -48467,14 +48386,22 @@ }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "Initial noise level for the un-conditioned tokens. `None` (default) resolves to `sigmas[0]` when custom `sigmas` are supplied, else 1.0.", "kwargsType": null, - "name": "generator", + "name": "noise_scale", "required": false, - "type": "torch._C.Generator" + "type": "builtins.float" }, { "default": null, + "description": "Custom sigmas for the denoising process.", + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "list[builtins.float]" + }, + { + "default": 30, "description": "The number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", @@ -48483,11 +48410,35 @@ }, { "default": null, - "description": "Unused for distilled checkpoints; classifier-free guidance is baked into the weights and the scale is forced to 1.0. Passing a value other than 1.0 raises an error.", + "description": "Timesteps for the denoising process.", "kwargsType": null, - "name": "guidance_scale", + "name": "timesteps", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Optional pre-encoded audio latents; random noise is used when not provided.", + "kwargsType": null, + "name": "audio_latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": true, + "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", + "kwargsType": null, + "name": "use_cross_timestep", "required": false, - "type": "builtins.float" + "type": "builtins.bool" + }, + { + "default": null, + "description": "Additional kwargs for attention processors.", + "kwargsType": null, + "name": "attention_kwargs", + "required": false, + "type": "dict[builtins.str,typing.Any]" }, { "default": "pil", @@ -48502,264 +48453,322 @@ "outputs": [ { "default": null, - "description": "Number of frames to generate.", + "description": "Packed per-layer Gemma hidden states for the prompt.", "kwargsType": null, - "name": "num_frames", + "name": "prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Height of the generated video or image in pixels.", + "description": "Binary attention mask for `prompt_embeds`.", "kwargsType": null, - "name": "height", + "name": "prompt_attention_mask", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Width of the generated video or image in pixels.", + "description": "Packed per-layer Gemma hidden states for the negative prompt.", "kwargsType": null, - "name": "width", + "name": "negative_prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the conditional prompt.", + "description": "Binary attention mask for `negative_prompt_embeds`.", "kwargsType": null, - "name": "cond_input_ids", + "name": "negative_prompt_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the unconditional prompt (empty prompt; guidance is baked in).", + "description": "The number of prompts being denoised (before per-prompt expansion).", "kwargsType": null, - "name": "uncond_input_ids", + "name": "batch_size", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Vision latents encoded from the conditioning image or video.", + "description": "The dtype of the prompt embeddings.", "kwargsType": null, - "name": "x0_tokens_vision", + "name": "dtype", "required": false, - "type": "torch.Tensor" + "type": "torch.dtype" }, { "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", + "description": "Video-branch text conditioning (cond).", "kwargsType": null, - "name": "vision_condition_frames", + "name": "connector_prompt_embeds", "required": false, - "type": "list[builtins.int]" + "type": "torch.Tensor" }, { "default": null, - "description": "Conditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_text_segment", + "description": "Audio-branch text conditioning (cond).", + "kwargsType": null, + "name": "connector_audio_prompt_embeds", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_text_segment", + "description": "Binary text attention mask (cond).", + "kwargsType": null, + "name": "connector_attention_mask", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Noisy vision latents for denoising.", + "description": "Video-branch text conditioning (uncond).", "kwargsType": null, - "name": "latents", + "name": "negative_connector_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Frame rate used to pack vision latents.", + "description": "Audio-branch text conditioning (uncond).", "kwargsType": null, - "name": "fps_vision", + "name": "negative_connector_audio_prompt_embeds", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "Mask marking conditioned vision latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "vision_condition_mask", + "description": "Binary text attention mask (uncond).", + "kwargsType": null, + "name": "negative_connector_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Indexes of conditioned vision latent frames.", + "description": "Per-condition normalized VAE latents of shape [1, C, F, H, W].", "kwargsType": null, - "name": "vision_condition_indexes_for_pack", + "name": "condition_latents", "required": false, - "type": "list[builtins.int]" + "type": "builtins.list" }, { "default": null, - "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", + "description": "Per-condition conditioning strengths.", "kwargsType": null, - "name": "vision_conditioning_latents", + "name": "condition_strengths", "required": false, - "type": "torch.Tensor" + "type": "builtins.list" }, { "default": null, - "description": "Conditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_vision_segment", + "description": "Per-condition latent frame index at which the condition is applied.", + "kwargsType": null, + "name": "condition_indices", "required": false, - "type": "builtins.dict" + "type": "builtins.list" }, { "default": null, - "description": "Unconditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_vision_segment", + "description": "Per-condition trimmed pixel frame count, used to clamp the temporal extent of single-frame keyframe coordinates.", + "kwargsType": null, + "name": "condition_pixel_frames", "required": false, - "type": "builtins.dict" + "type": "builtins.list" }, { "default": null, - "description": "Conditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "cond_position_ids", + "description": "Packed reference tokens of shape [1, total_reference_tokens, C].", + "kwargsType": null, + "name": "reference_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_position_ids", + "description": "RoPE coordinates for the reference tokens, of shape [1, 3, total_reference_tokens, 2].", + "kwargsType": null, + "name": "reference_coords", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sequence_length", + "description": "Per-reference token counts, in `reference_conditions` order.", + "kwargsType": null, + "name": "reference_token_counts", "required": false, - "type": "builtins.int" + "type": "builtins.list" }, { "default": null, - "description": "Unconditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sequence_length", + "description": "Per-reference-token noisy<->reference attention strengths of shape [1, total_reference_tokens], or `None` when attention is left unmasked.", + "kwargsType": null, + "name": "reference_cross_mask", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Scheduler timesteps for denoising.", + "description": "Packed noisy video latents, with keyframe and reference tokens appended.", "kwargsType": null, - "name": "timesteps", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Number of scheduler warmup steps.", + "description": "Packed per-token conditioning strengths of shape [B, S, 1] in [0, 1]: 1 at fully-conditioned positions, 0 at free positions.", "kwargsType": null, - "name": "num_warmup_steps", + "name": "conditioning_mask", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Resolved number of denoising steps (fixed by the distilled schedule).", + "description": "Clean condition latents at conditioned positions, zeros elsewhere; same shape as `latents`.", "kwargsType": null, - "name": "num_inference_steps", + "name": "clean_latents", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Resolved classifier-free guidance scale (always 1.0 for distilled checkpoints).", + "description": "RoPE coordinates of shape [B, 3, num_keyframe_tokens + num_reference_tokens, 2] for the appended tokens, zero-width when there are none.", "kwargsType": null, - "name": "guidance_scale", + "name": "appended_coords", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "Vision tokens for the transformer denoiser.", + "description": "Number of generated-video tokens, i.e. the sequence length before appended tokens.", "kwargsType": null, - "name": "vision_tokens", + "name": "base_token_count", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.int" }, { "default": null, - "description": "Timesteps for the vision tokens.", + "description": "Number of reference tokens, which sit at the very end of the sequence.", "kwargsType": null, - "name": "vision_timesteps", + "name": "num_ref_tokens", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Predicted velocity for vision latents.", + "description": "The resolved initial noise level, forwarded to the audio latents step.", "kwargsType": null, - "name": "velocity_vision", + "name": "noise_scale", "required": false, - "type": "torch.Tensor" + "type": "builtins.float" }, { "default": null, - "description": "Predicted velocity for sound latents.", + "description": "", "kwargsType": null, - "name": "velocity_sound", + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Predicted velocity for action latents.", + "description": "", "kwargsType": null, - "name": "velocity_action", + "name": "num_inference_steps", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "The generated videos.", + "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", + "kwargsType": null, + "name": "audio_scheduler", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "Packed noisy audio latents.", + "kwargsType": null, + "name": "audio_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Number of audio latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "audio_num_frames", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Video RoPE patch coordinates, with the keyframe-condition coordinates appended.", + "kwargsType": "denoiser_input_fields", + "name": "video_coords", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Audio RoPE patch coordinates.", + "kwargsType": "denoiser_input_fields", + "name": "audio_coords", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated videos.", "kwargsType": null, "name": "videos", "required": false, "type": "list[PIL.Image.Image]" + }, + { + "default": null, + "description": "The generated audio waveform.", + "kwargsType": null, + "name": "audio", + "required": false, + "type": "torch.Tensor" } ], "requiredInputs": [ - "cond_input_ids", - "cond_text_segment", - "cond_vision_segment", - "fps_vision", - "height", + "appended_coords", + "audio_latents", + "audio_num_frames", + "audio_scheduler", + "base_token_count", + "batch_size", + "clean_latents", + "condition_indices", + "condition_latents", + "condition_pixel_frames", + "condition_strengths", + "conditioning_mask", + "connector_attention_mask", + "connector_audio_prompt_embeds", + "connector_prompt_embeds", + "dtype", "latents", - "num_frames", + "negative_connector_attention_mask", + "negative_connector_audio_prompt_embeds", + "negative_connector_prompt_embeds", + "negative_prompt_attention_mask", + "negative_prompt_embeds", "num_inference_steps", - "num_warmup_steps", "prompt", - "timesteps", - "uncond_input_ids", - "uncond_text_segment", - "uncond_vision_segment", - "velocity_vision", - "vision_condition_indexes_for_pack", - "vision_condition_mask", - "width" + "prompt_attention_mask", + "prompt_embeds", + "reference_conditions", + "timesteps" ], "schemaVersion": 1, "variadicInputs": [ @@ -51124,64 +51133,65 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder", - "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", + "name": "text_tokenizer", "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_scale": 5.0 + "resample": "bilinear", + "vae_scale_factor": 16 }, "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_helios.HeliosTransformer3DModel" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_helios.HeliosScheduler" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + } + ], + "configs": [ + { + "default": true, + "description": "", + "name": "default_use_system_prompt" }, { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 8 - }, + "default": true, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "enable_safety_checker" + }, + { + "default": false, + "description": "", + "name": "use_native_flow_schedule" } ], - "configs": [], - "contentHash": "sha256:b54b7cedac8ea9a1703703173c893e7e89a441fe06b696ed22c8aad3b51d80c8", + "contentHash": "sha256:b183f17a43f6567192e5348ba8ec264f574d52bbb4d787a2ab690f91574bf3c3", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b54b7cedac8ea9a1703703173c893e7e89a441fe06b696ed22c8aad3b51d80c8", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b183f17a43f6567192e5348ba8ec264f574d52bbb4d787a2ab690f91574bf3c3", "inputs": [ { "default": null, - "description": "The prompt or prompts to guide image generation.", + "description": "The text prompt that guides Cosmos3 generation.", "kwargsType": null, "name": "prompt", "required": true, @@ -51189,130 +51199,150 @@ }, { "default": null, - "description": "The prompt or prompts not to guide the image generation.", + "description": "The negative text prompt used for classifier-free guidance.", "kwargsType": null, "name": "negative_prompt", "required": false, "type": "builtins.str" }, { - "default": 512, - "description": "Maximum sequence length for prompt encoding.", + "default": null, + "description": "Action-conditioning metadata and its reference visual input.", "kwargsType": null, - "name": "max_sequence_length", - "required": false, - "type": "builtins.int" + "name": "action", + "required": true, + "type": "diffusers.pipelines.cosmos.pipeline_cosmos3_omni.CosmosActionCondition" }, { - "default": 1, - "description": "Number of videos to generate per prompt.", + "default": null, + "description": "Number of frames to generate.", "kwargsType": null, - "name": "num_videos_per_prompt", - "required": false, + "name": "num_frames", + "required": true, "type": "builtins.int" }, { - "default": 384, - "description": "The height in pixels of the generated image.", + "default": null, + "description": "Height of the generated video or image in pixels.", "kwargsType": null, "name": "height", - "required": false, + "required": true, "type": "builtins.int" }, { - "default": 640, - "description": "The width in pixels of the generated image.", + "default": null, + "description": "Width of the generated video or image in pixels.", "kwargsType": null, "name": "width", - "required": false, + "required": true, "type": "builtins.int" }, { - "default": 132, - "description": "Total number of video frames to generate.", + "default": 24.0, + "description": "Frame rate of the generated video.", "kwargsType": null, - "name": "num_frames", - "required": true, - "type": "builtins.int" + "name": "fps", + "required": false, + "type": "builtins.float" }, { - "default": 9, - "description": "Number of latent frames per temporal chunk.", + "default": null, + "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, - "name": "num_latent_frames_per_chunk", + "name": "use_system_prompt", "required": false, - "type": "builtins.int" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { - "default": [ - 16, - 2, - 1 - ], - "description": "Sizes of long/mid/short history buffers for temporal context.", + "default": true, + "description": "Whether to add resolution metadata to the prompt.", "kwargsType": null, - "name": "history_sizes", - "required": true, - "type": "builtins.list" + "name": "add_resolution_template", + "required": false, + "type": "builtins.bool" }, { "default": true, - "description": "Whether to keep the first frame as a prefix in history.", + "description": "Whether to add duration metadata to the prompt.", "kwargsType": null, - "name": "keep_first_frame", + "name": "add_duration_template", "required": false, "type": "builtins.bool" }, + { + "default": null, + "description": "Pre-generated noisy vision latents.", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Torch generator for deterministic generation.", + "kwargsType": null, + "name": "generator", + "required": false, + "type": "torch._C.Generator" + }, { "default": 50, "description": "The number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", - "required": false, + "required": true, "type": "builtins.int" }, { "default": null, - "description": "Custom sigmas for the denoising process.", + "description": "Pre-generated noisy action latents.", "kwargsType": null, - "name": "sigmas", + "name": "action_latents", "required": true, - "type": "list[builtins.float]" + "type": "torch.Tensor" }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", "kwargsType": null, - "name": "generator", + "name": "mixed_precision_format", "required": false, - "type": "torch._C.Generator" + "type": "builtins.str" }, { "default": null, - "description": "Pre-generated noisy latents for image generation.", + "description": "Optional leading W8A16 step count.", "kwargsType": null, - "name": "latents", + "name": "mixed_precision_first_steps", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Timesteps for the denoising process.", + "description": "Optional trailing W8A16 step count.", "kwargsType": null, - "name": "timesteps", + "name": "mixed_precision_last_steps", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Additional kwargs for attention processors.", + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", "kwargsType": null, - "name": "attention_kwargs", + "name": "mixed_precision_reasoner_policy", "required": false, - "type": "dict[builtins.str,typing.Any]" + "type": "builtins.str" }, { - "default": "np", + "default": 6.0, + "description": "Scale for classifier-free guidance.", + "kwargsType": null, + "name": "guidance_scale", + "required": false, + "type": "builtins.float" + }, + { + "default": "pil", "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, "name": "output_type", @@ -51324,851 +51354,1942 @@ "outputs": [ { "default": null, - "description": "The prompt embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", + "description": "Requested action-generation mode.", + "kwargsType": null, + "name": "action_mode", "required": false, - "type": "torch.Tensor" + "type": "builtins.str" }, { "default": null, - "description": "The negative prompt embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", + "description": "Number of frames to generate.", + "kwargsType": null, + "name": "num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", + "description": "Height of the generated video or image in pixels.", "kwargsType": null, - "name": "batch_size", + "name": "height", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds.dtype`)", + "description": "Width of the generated video or image in pixels.", "kwargsType": null, - "name": "dtype", + "name": "width", "required": false, - "type": "torch.dtype" + "type": "builtins.int" }, { "default": null, - "description": "Number of temporal chunks", + "description": "Token IDs for the conditional prompt.", "kwargsType": null, - "name": "num_latent_chunk", + "name": "cond_input_ids", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Shape of latent tensor per chunk", + "description": "Token IDs for the unconditional prompt.", "kwargsType": null, - "name": "latent_shape", + "name": "uncond_input_ids", "required": false, - "type": "builtins.tuple" + "type": "torch.Tensor" }, { "default": null, - "description": "Adjusted history sizes (sorted, descending)", + "description": "Vision latents encoded from the conditioning image or video.", "kwargsType": null, - "name": "history_sizes", + "name": "x0_tokens_vision", "required": false, - "type": "builtins.list" + "type": "torch.Tensor" }, { "default": null, - "description": "", - "kwargsType": "denoiser_input_fields", - "name": "indices_hidden_states", + "description": "Latent-frame indexes fixed by visual conditioning.", + "kwargsType": null, + "name": "vision_condition_frames", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "", - "kwargsType": "denoiser_input_fields", - "name": "indices_latents_history_short", + "description": "Action-frame indexes fixed by action conditioning.", + "kwargsType": null, + "name": "action_condition_frame_indexes", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "", + "description": "Conditional text segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "indices_latents_history_mid", + "name": "cond_text_segment", "required": false, - "type": "torch.Tensor" + "type": "builtins.dict" }, { "default": null, - "description": "", + "description": "Unconditional text segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "indices_latents_history_long", + "name": "uncond_text_segment", "required": false, - "type": "torch.Tensor" + "type": "builtins.dict" }, { "default": null, - "description": "Initialized zero history latents", + "description": "Noisy vision latents for denoising.", "kwargsType": null, - "name": "history_latents", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Scheduler shift parameter", + "description": "Frame rate used to pack vision latents.", "kwargsType": null, - "name": "mu", + "name": "fps_vision", "required": false, "type": "builtins.float" }, { "default": null, - "description": "Sigma schedule for diffusion", - "kwargsType": null, - "name": "sigmas", + "description": "Mask marking conditioned vision latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "vision_condition_mask", "required": false, - "type": "builtins.list" + "type": "torch.Tensor" }, { "default": null, - "description": "List of per-chunk denoised latent tensors", + "description": "Indexes of conditioned vision latent frames.", "kwargsType": null, - "name": "latent_chunks", + "name": "vision_condition_indexes_for_pack", "required": false, - "type": "builtins.list" + "type": "list[builtins.int]" }, { "default": null, - "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", "kwargsType": null, - "name": "videos", + "name": "vision_conditioning_latents", "required": false, - "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" - } - ], - "requiredInputs": [ - "batch_size", - "history_latents", - "history_sizes", - "latent_chunks", - "latent_shape", - "mu", - "num_frames", - "num_latent_chunk", - "prompt", - "prompt_embeds", - "sigmas" - ], - "schemaVersion": 1, - "variadicInputs": [ + "type": "torch.Tensor" + }, { "default": null, - "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "description": "Conditional vision segment for the denoiser.", "kwargsType": "denoiser_input_fields", + "name": "cond_vision_segment", "required": false, - "type": "opaque" - } - ] - }, - { - "className": "SequentialPipelineBlocks", - "components": [ + "type": "builtins.dict" + }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_encoder", - "type": "transformers.models.clip.modeling_clip.CLIPTextModel" + "default": null, + "description": "Unconditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_vision_segment", + "required": false, + "type": "builtins.dict" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_encoder_2", - "type": "transformers.models.clip.modeling_clip.CLIPTextModelWithProjection" + "default": null, + "description": "Conditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "cond_position_ids", + "required": false, + "type": "torch.Tensor" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", - "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + "default": null, + "description": "Unconditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_position_ids", + "required": false, + "type": "torch.Tensor" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer_2", - "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + "default": null, + "description": "Conditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "cond_sequence_length", + "required": false, + "type": "builtins.int" }, { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 7.5 - }, - "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + "default": null, + "description": "Unconditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_sequence_length", + "required": false, + "type": "builtins.int" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" + "default": null, + "description": "Scheduler timesteps for denoising.", + "kwargsType": null, + "name": "timesteps", + "required": false, + "type": "torch.Tensor" }, { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 8 - }, - "description": "", - "name": "image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "default": null, + "description": "Number of scheduler warmup steps.", + "kwargsType": null, + "name": "num_warmup_steps", + "required": false, + "type": "builtins.int" }, { - "creationMethod": "from_config", - "defaultConfig": { - "do_binarize": true, - "do_convert_grayscale": true, - "do_normalize": false, - "vae_scale_factor": 8 - }, - "description": "", - "name": "mask_processor", - "type": "diffusers.image_processor.VaeImageProcessor" + "default": null, + "description": "Noisy action latents for denoising.", + "kwargsType": null, + "name": "action_latents", + "required": false, + "type": "torch.Tensor" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler" + "default": null, + "description": "Mask marking conditioned action latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "action_condition_mask", + "required": false, + "type": "torch.Tensor" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "unet", - "type": "diffusers.models.unets.unet_2d_condition.UNet2DConditionModel" + "default": null, + "description": "Embodiment domain IDs for action conditioning.", + "kwargsType": "denoiser_input_fields", + "name": "action_domain_ids", + "required": false, + "type": "list[torch.Tensor]" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "controlnet", - "type": "diffusers.models.controlnets.controlnet_union.ControlNetUnionModel" + "default": null, + "description": "Unpadded action-vector dimension.", + "kwargsType": "denoiser_input_fields", + "name": "raw_action_dim_resolved", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Scheduler used to update action latents.", + "kwargsType": null, + "name": "action_scheduler", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "Conditional action segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_action_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional action segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_action_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Vision tokens for the transformer denoiser.", + "kwargsType": null, + "name": "vision_tokens", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Timesteps for the vision tokens.", + "kwargsType": null, + "name": "vision_timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Action tokens for the transformer denoiser.", + "kwargsType": null, + "name": "action_tokens", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Timesteps for the action tokens.", + "kwargsType": null, + "name": "action_timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for vision latents.", + "kwargsType": null, + "name": "velocity_vision", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for sound latents.", + "kwargsType": null, + "name": "velocity_sound", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Predicted velocity for action latents.", + "kwargsType": null, + "name": "velocity_action", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated videos.", + "kwargsType": null, + "name": "videos", + "required": false, + "type": "list[PIL.Image.Image]" + }, + { + "default": null, + "description": "Generated action vectors.", + "kwargsType": null, + "name": "action", + "required": false, + "type": "list[torch.Tensor]" + } + ], + "requiredInputs": [ + "action", + "action_condition_mask", + "action_latents", + "action_scheduler", + "cond_action_segment", + "cond_input_ids", + "cond_position_ids", + "cond_sequence_length", + "cond_text_segment", + "cond_vision_segment", + "fps_vision", + "height", + "latents", + "num_frames", + "num_inference_steps", + "num_warmup_steps", + "prompt", + "timesteps", + "uncond_action_segment", + "uncond_input_ids", + "uncond_position_ids", + "uncond_sequence_length", + "uncond_text_segment", + "uncond_vision_segment", + "velocity_action", + "velocity_vision", + "vision_condition_indexes_for_pack", + "width" + ], + "schemaVersion": 1, + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] + }, + { + "className": "SequentialPipelineBlocks", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder", + "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_config", "defaultConfig": { - "do_convert_rgb": true, - "do_normalize": false + "guidance_scale": 5.0 }, "description": "", - "name": "control_image_processor", - "type": "diffusers.image_processor.VaeImageProcessor" - } - ], - "configs": [ + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "force_zeros_for_empty_prompt" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_helios.HeliosTransformer3DModel" }, { - "default": false, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "requires_aesthetics_score" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_helios.HeliosScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 8 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" } ], - "contentHash": "sha256:b6dd673a7f77c3a8bf85c1b47f612dc8c0433618d5a33ea1b1633d324d16ede4", + "configs": [], + "contentHash": "sha256:b54b7cedac8ea9a1703703173c893e7e89a441fe06b696ed22c8aad3b51d80c8", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b6dd673a7f77c3a8bf85c1b47f612dc8c0433618d5a33ea1b1633d324d16ede4", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b54b7cedac8ea9a1703703173c893e7e89a441fe06b696ed22c8aad3b51d80c8", "inputs": [ { "default": null, - "description": "", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", + "required": true, + "type": "builtins.str" + }, + { + "default": null, + "description": "The prompt or prompts not to guide the image generation.", + "kwargsType": null, + "name": "negative_prompt", "required": false, - "type": "opaque" + "type": "builtins.str" + }, + { + "default": 512, + "description": "Maximum sequence length for prompt encoding.", + "kwargsType": null, + "name": "max_sequence_length", + "required": false, + "type": "builtins.int" + }, + { + "default": 1, + "description": "Number of videos to generate per prompt.", + "kwargsType": null, + "name": "num_videos_per_prompt", + "required": false, + "type": "builtins.int" + }, + { + "default": 384, + "description": "The height in pixels of the generated image.", + "kwargsType": null, + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": 640, + "description": "The width in pixels of the generated image.", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": 132, + "description": "Total number of video frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": true, + "type": "builtins.int" + }, + { + "default": 9, + "description": "Number of latent frames per temporal chunk.", + "kwargsType": null, + "name": "num_latent_frames_per_chunk", + "required": false, + "type": "builtins.int" + }, + { + "default": [ + 16, + 2, + 1 + ], + "description": "Sizes of long/mid/short history buffers for temporal context.", + "kwargsType": null, + "name": "history_sizes", + "required": true, + "type": "builtins.list" + }, + { + "default": true, + "description": "Whether to keep the first frame as a prefix in history.", + "kwargsType": null, + "name": "keep_first_frame", + "required": false, + "type": "builtins.bool" + }, + { + "default": 50, + "description": "The number of denoising steps.", + "kwargsType": null, + "name": "num_inference_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Custom sigmas for the denoising process.", + "kwargsType": null, + "name": "sigmas", + "required": true, + "type": "list[builtins.float]" + }, + { + "default": null, + "description": "Torch generator for deterministic generation.", + "kwargsType": null, + "name": "generator", + "required": false, + "type": "torch._C.Generator" + }, + { + "default": null, + "description": "Pre-generated noisy latents for image generation.", + "kwargsType": null, + "name": "latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Timesteps for the denoising process.", + "kwargsType": null, + "name": "timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Additional kwargs for attention processors.", + "kwargsType": null, + "name": "attention_kwargs", + "required": false, + "type": "dict[builtins.str,typing.Any]" + }, + { + "default": "np", + "description": "Output format: 'pil', 'np', 'pt'.", + "kwargsType": null, + "name": "output_type", + "required": false, + "type": "builtins.str" + } + ], + "kind": "sequential", + "outputs": [ + { + "default": null, + "description": "The prompt embeddings.", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The negative prompt embeddings.", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", + "kwargsType": null, + "name": "batch_size", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Data type of model tensor inputs (determined by `prompt_embeds.dtype`)", + "kwargsType": null, + "name": "dtype", + "required": false, + "type": "torch.dtype" + }, + { + "default": null, + "description": "Number of temporal chunks", + "kwargsType": null, + "name": "num_latent_chunk", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Shape of latent tensor per chunk", + "kwargsType": null, + "name": "latent_shape", + "required": false, + "type": "builtins.tuple" + }, + { + "default": null, + "description": "Adjusted history sizes (sorted, descending)", + "kwargsType": null, + "name": "history_sizes", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "", + "kwargsType": "denoiser_input_fields", + "name": "indices_hidden_states", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "", + "kwargsType": "denoiser_input_fields", + "name": "indices_latents_history_short", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "", + "kwargsType": "denoiser_input_fields", + "name": "indices_latents_history_mid", + "required": false, + "type": "torch.Tensor" }, { "default": null, "description": "", + "kwargsType": "denoiser_input_fields", + "name": "indices_latents_history_long", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Initialized zero history latents", "kwargsType": null, - "name": "prompt_2", + "name": "history_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Scheduler shift parameter", + "kwargsType": null, + "name": "mu", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Sigma schedule for diffusion", + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "List of per-chunk denoised latent tensors", + "kwargsType": null, + "name": "latent_chunks", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "kwargsType": null, + "name": "videos", + "required": false, + "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" + } + ], + "requiredInputs": [ + "batch_size", + "history_latents", + "history_sizes", + "latent_chunks", + "latent_shape", + "mu", + "num_frames", + "num_latent_chunk", + "prompt", + "prompt_embeds", + "sigmas" + ], + "schemaVersion": 1, + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", "required": false, "type": "opaque" + } + ] + }, + { + "className": "SequentialPipelineBlocks", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder", + "type": "transformers.models.clip.modeling_clip.CLIPTextModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_encoder_2", + "type": "transformers.models.clip.modeling_clip.CLIPTextModelWithProjection" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "tokenizer", + "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "tokenizer_2", + "type": "transformers.models.clip.tokenization_clip.CLIPTokenizer" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 7.5 + }, + "description": "", + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl.AutoencoderKL" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 8 + }, + "description": "", + "name": "image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "do_binarize": true, + "do_convert_grayscale": true, + "do_normalize": false, + "vae_scale_factor": 8 + }, + "description": "", + "name": "mask_processor", + "type": "diffusers.image_processor.VaeImageProcessor" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "unet", + "type": "diffusers.models.unets.unet_2d_condition.UNet2DConditionModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "controlnet", + "type": "diffusers.models.controlnets.controlnet_union.ControlNetUnionModel" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "do_convert_rgb": true, + "do_normalize": false + }, + "description": "", + "name": "control_image_processor", + "type": "diffusers.image_processor.VaeImageProcessor" + } + ], + "configs": [ + { + "default": true, + "description": "", + "name": "force_zeros_for_empty_prompt" + }, + { + "default": false, + "description": "", + "name": "requires_aesthetics_score" + } + ], + "contentHash": "sha256:b6dd673a7f77c3a8bf85c1b47f612dc8c0433618d5a33ea1b1633d324d16ede4", + "description": "", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b6dd673a7f77c3a8bf85c1b47f612dc8c0433618d5a33ea1b1633d324d16ede4", + "inputs": [ + { + "default": null, + "description": "", + "kwargsType": null, + "name": "prompt", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "prompt_2", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "negative_prompt", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "negative_prompt_2", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "cross_attention_kwargs", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "clip_skip", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "height", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "width", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "image", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "mask_image", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "padding_mask_crop", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "The dtype of the model inputs", + "kwargsType": null, + "name": "dtype", + "required": true, + "type": "torch.dtype" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "generator", + "required": false, + "type": "opaque" + }, + { + "default": 1, + "description": "", + "kwargsType": null, + "name": "num_images_per_prompt", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "Pre-generated image embeddings for IP-Adapter. Can be generated from ip_adapter step.", + "kwargsType": null, + "name": "ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Pre-generated negative image embeddings for IP-Adapter. Can be generated from ip_adapter step.", + "kwargsType": null, + "name": "negative_ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": 50, + "description": "", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "timesteps", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "denoising_end", + "required": false, + "type": "opaque" + }, + { + "default": 0.3, + "description": "", + "kwargsType": null, + "name": "strength", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "denoising_start", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "original_size", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "target_size", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "negative_original_size", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "negative_target_size", + "required": false, + "type": "opaque" + }, + { + "default": [ + 0, + 0 + ], + "description": "", + "kwargsType": null, + "name": "crops_coords_top_left", + "required": false, + "type": "opaque" + }, + { + "default": [ + 0, + 0 + ], + "description": "", + "kwargsType": null, + "name": "negative_crops_coords_top_left", + "required": false, + "type": "opaque" + }, + { + "default": 6.0, + "description": "", + "kwargsType": null, + "name": "aesthetic_score", + "required": false, + "type": "opaque" + }, + { + "default": 2.0, + "description": "", + "kwargsType": null, + "name": "negative_aesthetic_score", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "control_image", + "required": true, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "control_mode", + "required": true, + "type": "opaque" + }, + { + "default": 0.0, + "description": "", + "kwargsType": null, + "name": "control_guidance_start", + "required": false, + "type": "opaque" + }, + { + "default": 1.0, + "description": "", + "kwargsType": null, + "name": "control_guidance_end", + "required": false, + "type": "opaque" + }, + { + "default": 1.0, + "description": "", + "kwargsType": null, + "name": "controlnet_conditioning_scale", + "required": false, + "type": "opaque" + }, + { + "default": false, + "description": "", + "kwargsType": null, + "name": "guess_mode", + "required": true, + "type": "opaque" + }, + { + "default": 0.0, + "description": "", + "kwargsType": null, + "name": "eta", + "required": false, + "type": "opaque" + }, + { + "default": "pil", + "description": "", + "kwargsType": null, + "name": "output_type", + "required": false, + "type": "opaque" + } + ], + "kind": "sequential", + "outputs": [ + { + "default": null, + "description": "text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "negative text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "pooled text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "pooled_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "negative pooled text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "negative_pooled_prompt_embeds", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The latents representation of the input image", + "kwargsType": null, + "name": "image_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The mask to use for the inpainting process", + "kwargsType": null, + "name": "mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The masked image latents to use for the inpainting process (only for inpainting-specifid unet)", + "kwargsType": null, + "name": "masked_image_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The crop coordinates to use for the preprocess/postprocess of the image and mask", + "kwargsType": null, + "name": "crops_coords", + "required": false, + "type": "UnionType[builtins.NoneType,tuple[builtins.int]]" + }, + { + "default": null, + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", + "kwargsType": null, + "name": "batch_size", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "kwargsType": null, + "name": "dtype", + "required": false, + "type": "torch.dtype" + }, + { + "default": null, + "description": "image embeddings for IP-Adapter", + "kwargsType": "denoiser_input_fields", + "name": "ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "negative image embeddings for IP-Adapter", + "kwargsType": "denoiser_input_fields", + "name": "negative_ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "The timesteps to use for inference", + "kwargsType": null, + "name": "timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The number of denoising steps to perform at inference time", + "kwargsType": null, + "name": "num_inference_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "The timestep that represents the initial noise level for image-to-image generation", + "kwargsType": null, + "name": "latent_timestep", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The initial latents to use for the denoising process", + "kwargsType": null, + "name": "latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The noise added to the image latents, used for inpainting generation", + "kwargsType": null, + "name": "noise", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The time ids to condition the denoising process", + "kwargsType": "denoiser_input_fields", + "name": "add_time_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The negative time ids to condition the denoising process", + "kwargsType": "denoiser_input_fields", + "name": "negative_add_time_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The timestep cond to use for LCM", + "kwargsType": null, + "name": "timestep_cond", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The processed control images", + "kwargsType": null, + "name": "controlnet_cond", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "The control mode indices", + "kwargsType": "controlnet_kwargs", + "name": "control_type_idx", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "The control type tensor that specifies which control type is active", + "kwargsType": "controlnet_kwargs", + "name": "control_type", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The controlnet guidance start value", + "kwargsType": null, + "name": "control_guidance_start", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "The controlnet guidance end value", + "kwargsType": null, + "name": "control_guidance_end", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "The controlnet conditioning scale values", + "kwargsType": null, + "name": "conditioning_scale", + "required": false, + "type": "list[builtins.float]" + }, + { + "default": null, + "description": "Whether guess mode is used", + "kwargsType": null, + "name": "guess_mode", + "required": false, + "type": "builtins.bool" + }, + { + "default": null, + "description": "The controlnet keep values", + "kwargsType": null, + "name": "controlnet_keep", + "required": false, + "type": "list[builtins.float]" + }, + { + "default": null, + "description": "The generated images, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "kwargsType": null, + "name": "images", + "required": false, + "type": "UnionType[list[PIL.Image.Image],list[numpy.array],list[torch.Tensor]]" + } + ], + "requiredInputs": [ + "batch_size", + "control_image", + "control_mode", + "controlnet_cond", + "controlnet_keep", + "dtype", + "guess_mode", + "image", + "image_latents", + "latent_timestep", + "latents", + "mask", + "mask_image", + "num_inference_steps", + "pooled_prompt_embeds", + "prompt_embeds", + "timesteps" + ], + "schemaVersion": 1, + "variadicInputs": [ + { + "default": null, + "description": "All conditional model inputs that need to be prepared with guider. It should contain prompt_embeds/negative_prompt_embeds, add_time_ids/negative_add_time_ids, pooled_prompt_embeds/negative_pooled_prompt_embeds, and ip_adapter_embeds/negative_ip_adapter_embeds (optional).please add `kwargs_type=denoiser_input_fields` to their parameter spec (`OutputParam`) when they are created and added to the pipeline state", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] + }, + { + "className": "SequentialPipelineBlocks", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { - "default": null, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "opaque" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" }, { - "default": null, + "creationMethod": "from_config", + "defaultConfig": { + "resample": "bilinear", + "vae_scale_factor": 16 + }, "description": "", - "kwargsType": null, - "name": "negative_prompt_2", - "required": false, - "type": "opaque" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" }, { - "default": null, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "kwargsType": null, - "name": "cross_attention_kwargs", - "required": false, - "type": "opaque" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" }, { - "default": null, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "kwargsType": null, - "name": "clip_skip", - "required": false, - "type": "opaque" - }, + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + } + ], + "configs": [ { - "default": null, + "default": true, "description": "", - "kwargsType": null, - "name": "height", - "required": false, - "type": "opaque" + "name": "default_use_system_prompt" }, { - "default": null, + "default": true, "description": "", - "kwargsType": null, - "name": "width", - "required": false, - "type": "opaque" + "name": "enable_safety_checker" }, { - "default": null, + "default": false, "description": "", - "kwargsType": null, - "name": "image", - "required": true, - "type": "opaque" - }, + "name": "use_native_flow_schedule" + } + ], + "contentHash": "sha256:b8f456945daa167c65549bb9282a64744360ac1b9fa4b4ea7eb82e8f1f44f17b", + "description": "", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b8f456945daa167c65549bb9282a64744360ac1b9fa4b4ea7eb82e8f1f44f17b", + "inputs": [ { "default": null, - "description": "", + "description": "The text prompt that guides Cosmos3 generation.", "kwargsType": null, - "name": "mask_image", + "name": "prompt", "required": true, - "type": "opaque" + "type": "builtins.str" }, { "default": null, - "description": "", + "description": "The negative text prompt used for classifier-free guidance.", "kwargsType": null, - "name": "padding_mask_crop", + "name": "negative_prompt", "required": false, - "type": "opaque" + "type": "builtins.str" }, { "default": null, - "description": "The dtype of the model inputs", + "description": "Number of frames to generate.", "kwargsType": null, - "name": "dtype", + "name": "num_frames", "required": true, - "type": "torch.dtype" + "type": "builtins.int" }, { "default": null, - "description": "", + "description": "Height of the generated video or image in pixels.", "kwargsType": null, - "name": "generator", - "required": false, - "type": "opaque" + "name": "height", + "required": true, + "type": "builtins.int" }, { - "default": 1, - "description": "", + "default": null, + "description": "Width of the generated video or image in pixels.", "kwargsType": null, - "name": "num_images_per_prompt", - "required": false, - "type": "opaque" + "name": "width", + "required": true, + "type": "builtins.int" }, { - "default": null, - "description": "Pre-generated image embeddings for IP-Adapter. Can be generated from ip_adapter step.", + "default": 24.0, + "description": "Frame rate of the generated video.", "kwargsType": null, - "name": "ip_adapter_embeds", + "name": "fps", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.float" }, { "default": null, - "description": "Pre-generated negative image embeddings for IP-Adapter. Can be generated from ip_adapter step.", + "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, - "name": "negative_ip_adapter_embeds", + "name": "use_system_prompt", "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": 50, - "description": "", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "opaque" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { - "default": null, - "description": "", + "default": true, + "description": "Whether to add resolution metadata to the prompt.", "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "opaque" + "name": "add_resolution_template", + "required": false, + "type": "builtins.bool" }, { - "default": null, - "description": "", + "default": true, + "description": "Whether to add duration metadata to the prompt.", "kwargsType": null, - "name": "sigmas", + "name": "add_duration_template", "required": false, - "type": "opaque" + "type": "builtins.bool" }, { "default": null, - "description": "", + "description": "Reference video for video-to-video conditioning.", "kwargsType": null, - "name": "denoising_end", + "name": "video", "required": false, "type": "opaque" }, { - "default": 0.3, - "description": "", + "default": [ + 0, + 1 + ], + "description": "Latent-frame indexes to preserve from the conditioning video.", "kwargsType": null, - "name": "strength", + "name": "condition_frame_indexes_vision", "required": false, - "type": "opaque" + "type": "UnionType[list[builtins.int],tuple[Ellipsis,builtins.int]]" }, { - "default": null, - "description": "", + "default": "first", + "description": "Which end of a longer conditioning video to use: `first` or `last`.", "kwargsType": null, - "name": "denoising_start", + "name": "condition_video_keep", "required": false, - "type": "opaque" + "type": "builtins.str" }, { "default": null, - "description": "", + "description": "Pre-generated noisy vision latents.", "kwargsType": null, "name": "latents", "required": true, - "type": "opaque" + "type": "torch.Tensor" }, { "default": null, - "description": "", + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "original_size", + "name": "generator", "required": false, - "type": "opaque" + "type": "torch._C.Generator" }, { - "default": null, - "description": "", + "default": 50, + "description": "The number of denoising steps.", "kwargsType": null, - "name": "target_size", - "required": false, - "type": "opaque" + "name": "num_inference_steps", + "required": true, + "type": "builtins.int" }, { "default": null, - "description": "", + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", "kwargsType": null, - "name": "negative_original_size", + "name": "mixed_precision_format", "required": false, - "type": "opaque" + "type": "builtins.str" }, { "default": null, - "description": "", + "description": "Optional leading W8A16 step count.", "kwargsType": null, - "name": "negative_target_size", + "name": "mixed_precision_first_steps", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": [ - 0, - 0 - ], - "description": "", + "default": null, + "description": "Optional trailing W8A16 step count.", "kwargsType": null, - "name": "crops_coords_top_left", + "name": "mixed_precision_last_steps", "required": false, - "type": "opaque" + "type": "builtins.int" }, { - "default": [ - 0, - 0 - ], - "description": "", + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", "kwargsType": null, - "name": "negative_crops_coords_top_left", + "name": "mixed_precision_reasoner_policy", "required": false, - "type": "opaque" + "type": "builtins.str" }, { "default": 6.0, - "description": "", + "description": "Scale for classifier-free guidance.", "kwargsType": null, - "name": "aesthetic_score", + "name": "guidance_scale", "required": false, - "type": "opaque" + "type": "builtins.float" }, { - "default": 2.0, - "description": "", + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, - "name": "negative_aesthetic_score", + "name": "output_type", "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "control_image", - "required": true, - "type": "opaque" + "type": "builtins.str" }, { "default": null, - "description": "", - "kwargsType": null, - "name": "control_mode", - "required": true, - "type": "opaque" - }, - { - "default": 0.0, - "description": "", - "kwargsType": null, - "name": "control_guidance_start", - "required": false, - "type": "opaque" - }, - { - "default": 1.0, - "description": "", - "kwargsType": null, - "name": "control_guidance_end", - "required": false, - "type": "opaque" - }, - { - "default": 1.0, - "description": "", + "description": "Denoised action latents.", "kwargsType": null, - "name": "controlnet_conditioning_scale", + "name": "action_latents", "required": false, - "type": "opaque" - }, - { - "default": false, - "description": "", - "kwargsType": null, - "name": "guess_mode", - "required": true, - "type": "opaque" + "type": "torch.Tensor" }, { - "default": 0.0, - "description": "", + "default": null, + "description": "Requested action-generation mode.", "kwargsType": null, - "name": "eta", + "name": "action_mode", "required": false, - "type": "opaque" + "type": "builtins.str" }, { - "default": "pil", - "description": "", + "default": null, + "description": "Unpadded action-vector dimension.", "kwargsType": null, - "name": "output_type", + "name": "raw_action_dim_resolved", "required": false, - "type": "opaque" + "type": "builtins.int" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", + "description": "Number of frames to generate.", + "kwargsType": null, + "name": "num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "negative text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", + "description": "Height of the generated video or image in pixels.", + "kwargsType": null, + "name": "height", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "pooled text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "pooled_prompt_embeds", + "description": "Width of the generated video or image in pixels.", + "kwargsType": null, + "name": "width", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "negative pooled text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_pooled_prompt_embeds", + "description": "Token IDs for the conditional prompt.", + "kwargsType": null, + "name": "cond_input_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The latents representation of the input image", + "description": "Token IDs for the unconditional prompt.", "kwargsType": null, - "name": "image_latents", + "name": "uncond_input_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The mask to use for the inpainting process", + "description": "Vision latents encoded from the conditioning image or video.", "kwargsType": null, - "name": "mask", + "name": "x0_tokens_vision", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The masked image latents to use for the inpainting process (only for inpainting-specifid unet)", + "description": "Latent-frame indexes fixed by visual conditioning.", "kwargsType": null, - "name": "masked_image_latents", + "name": "vision_condition_frames", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "The crop coordinates to use for the preprocess/postprocess of the image and mask", - "kwargsType": null, - "name": "crops_coords", + "description": "Conditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_text_segment", "required": false, - "type": "UnionType[builtins.NoneType,tuple[builtins.int]]" + "type": "builtins.dict" }, { "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", - "kwargsType": null, - "name": "batch_size", + "description": "Unconditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_text_segment", "required": false, - "type": "builtins.int" + "type": "builtins.dict" }, { "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "description": "Noisy vision latents for denoising.", "kwargsType": null, - "name": "dtype", + "name": "latents", "required": false, - "type": "torch.dtype" + "type": "torch.Tensor" }, { "default": null, - "description": "image embeddings for IP-Adapter", - "kwargsType": "denoiser_input_fields", - "name": "ip_adapter_embeds", + "description": "Frame rate used to pack vision latents.", + "kwargsType": null, + "name": "fps_vision", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.float" }, { "default": null, - "description": "negative image embeddings for IP-Adapter", + "description": "Mask marking conditioned vision latent frames.", "kwargsType": "denoiser_input_fields", - "name": "negative_ip_adapter_embeds", + "name": "vision_condition_mask", "required": false, - "type": "list[torch.Tensor]" + "type": "torch.Tensor" }, { "default": null, - "description": "The timesteps to use for inference", + "description": "Indexes of conditioned vision latent frames.", "kwargsType": null, - "name": "timesteps", + "name": "vision_condition_indexes_for_pack", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "The number of denoising steps to perform at inference time", + "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", "kwargsType": null, - "name": "num_inference_steps", + "name": "vision_conditioning_latents", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "The timestep that represents the initial noise level for image-to-image generation", - "kwargsType": null, - "name": "latent_timestep", + "description": "Conditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_vision_segment", "required": false, - "type": "torch.Tensor" + "type": "builtins.dict" }, { "default": null, - "description": "The initial latents to use for the denoising process", - "kwargsType": null, - "name": "latents", + "description": "Unconditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_vision_segment", "required": false, - "type": "torch.Tensor" + "type": "builtins.dict" }, { "default": null, - "description": "The noise added to the image latents, used for inpainting generation", - "kwargsType": null, - "name": "noise", + "description": "Conditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "cond_position_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The time ids to condition the denoising process", + "description": "Unconditional multimodal RoPE position IDs.", "kwargsType": "denoiser_input_fields", - "name": "add_time_ids", + "name": "uncond_position_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The negative time ids to condition the denoising process", + "description": "Conditional multimodal sequence length.", "kwargsType": "denoiser_input_fields", - "name": "negative_add_time_ids", + "name": "cond_sequence_length", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "The timestep cond to use for LCM", - "kwargsType": null, - "name": "timestep_cond", + "description": "Unconditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_sequence_length", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "The processed control images", + "description": "Scheduler timesteps for denoising.", "kwargsType": null, - "name": "controlnet_cond", + "name": "timesteps", "required": false, - "type": "list[torch.Tensor]" + "type": "torch.Tensor" }, { "default": null, - "description": "The control mode indices", - "kwargsType": "controlnet_kwargs", - "name": "control_type_idx", + "description": "Number of scheduler warmup steps.", + "kwargsType": null, + "name": "num_warmup_steps", "required": false, - "type": "list[builtins.int]" + "type": "builtins.int" }, { "default": null, - "description": "The control type tensor that specifies which control type is active", - "kwargsType": "controlnet_kwargs", - "name": "control_type", + "description": "Vision tokens for the transformer denoiser.", + "kwargsType": null, + "name": "vision_tokens", "required": false, - "type": "torch.Tensor" + "type": "list[torch.Tensor]" }, { "default": null, - "description": "The controlnet guidance start value", + "description": "Timesteps for the vision tokens.", "kwargsType": null, - "name": "control_guidance_start", + "name": "vision_timesteps", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "The controlnet guidance end value", + "description": "Predicted velocity for vision latents.", "kwargsType": null, - "name": "control_guidance_end", + "name": "velocity_vision", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "The controlnet conditioning scale values", + "description": "Predicted velocity for sound latents.", "kwargsType": null, - "name": "conditioning_scale", + "name": "velocity_sound", "required": false, - "type": "list[builtins.float]" + "type": "torch.Tensor" }, { "default": null, - "description": "Whether guess mode is used", + "description": "Predicted velocity for action latents.", "kwargsType": null, - "name": "guess_mode", + "name": "velocity_action", "required": false, - "type": "builtins.bool" + "type": "torch.Tensor" }, { "default": null, - "description": "The controlnet keep values", + "description": "The generated videos.", "kwargsType": null, - "name": "controlnet_keep", + "name": "videos", "required": false, - "type": "list[builtins.float]" + "type": "list[PIL.Image.Image]" }, { "default": null, - "description": "The generated images, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "description": "Generated action vectors.", "kwargsType": null, - "name": "images", + "name": "action", "required": false, - "type": "UnionType[list[PIL.Image.Image],list[numpy.array],list[torch.Tensor]]" + "type": "list[torch.Tensor]" } ], "requiredInputs": [ - "batch_size", - "control_image", - "control_mode", - "controlnet_cond", - "controlnet_keep", - "dtype", - "guess_mode", - "image", - "image_latents", - "latent_timestep", + "cond_input_ids", + "cond_text_segment", + "cond_vision_segment", + "fps_vision", + "height", "latents", - "mask", - "mask_image", + "num_frames", "num_inference_steps", - "pooled_prompt_embeds", - "prompt_embeds", - "timesteps" + "num_warmup_steps", + "prompt", + "timesteps", + "uncond_input_ids", + "uncond_text_segment", + "uncond_vision_segment", + "velocity_vision", + "vision_condition_indexes_for_pack", + "width" ], "schemaVersion": 1, "variadicInputs": [ { "default": null, - "description": "All conditional model inputs that need to be prepared with guider. It should contain prompt_embeds/negative_prompt_embeds, add_time_ids/negative_add_time_ids, pooled_prompt_embeds/negative_pooled_prompt_embeds, and ip_adapter_embeds/negative_ip_adapter_embeds (optional).please add `kwargs_type=denoiser_input_fields` to their parameter spec (`OutputParam`) when they are created and added to the pipeline state", + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", "kwargsType": "denoiser_input_fields", "required": false, "type": "opaque" @@ -53526,903 +54647,429 @@ "type": "list[torch.Tensor]" }, { - "default": 50, - "description": "", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "denoising_end", - "required": false, - "type": "opaque" - }, - { - "default": 0.3, - "description": "", - "kwargsType": null, - "name": "strength", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "denoising_start", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "original_size", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "target_size", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "negative_original_size", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "negative_target_size", - "required": false, - "type": "opaque" - }, - { - "default": [ - 0, - 0 - ], - "description": "", - "kwargsType": null, - "name": "crops_coords_top_left", - "required": false, - "type": "opaque" - }, - { - "default": [ - 0, - 0 - ], - "description": "", - "kwargsType": null, - "name": "negative_crops_coords_top_left", - "required": false, - "type": "opaque" - }, - { - "default": 6.0, - "description": "", - "kwargsType": null, - "name": "aesthetic_score", - "required": false, - "type": "opaque" - }, - { - "default": 2.0, - "description": "", - "kwargsType": null, - "name": "negative_aesthetic_score", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "control_image", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "control_mode", - "required": true, - "type": "opaque" - }, - { - "default": 0.0, - "description": "", - "kwargsType": null, - "name": "control_guidance_start", - "required": false, - "type": "opaque" - }, - { - "default": 1.0, - "description": "", - "kwargsType": null, - "name": "control_guidance_end", - "required": false, - "type": "opaque" - }, - { - "default": 1.0, - "description": "", - "kwargsType": null, - "name": "controlnet_conditioning_scale", - "required": false, - "type": "opaque" - }, - { - "default": false, - "description": "", - "kwargsType": null, - "name": "guess_mode", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "The crop coordinates to use for preprocess/postprocess the image and mask, for inpainting task only. Can be generated in vae_encode step.", - "kwargsType": null, - "name": "crops_coords", - "required": false, - "type": "UnionType[builtins.NoneType,tuple[builtins.int]]" - }, - { - "default": 0.0, - "description": "", - "kwargsType": null, - "name": "eta", - "required": false, - "type": "opaque" - }, - { - "default": "pil", - "description": "", - "kwargsType": null, - "name": "output_type", - "required": false, - "type": "opaque" - } - ], - "kind": "sequential", - "outputs": [ - { - "default": null, - "description": "text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "negative text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "pooled text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "pooled_prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "negative pooled text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_pooled_prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The latents representing the reference image for image-to-image/inpainting generation", - "kwargsType": null, - "name": "image_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", - "kwargsType": null, - "name": "batch_size", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", - "kwargsType": null, - "name": "dtype", - "required": false, - "type": "torch.dtype" - }, - { - "default": null, - "description": "image embeddings for IP-Adapter", - "kwargsType": "denoiser_input_fields", - "name": "ip_adapter_embeds", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "negative image embeddings for IP-Adapter", - "kwargsType": "denoiser_input_fields", - "name": "negative_ip_adapter_embeds", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "The timesteps to use for inference", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The number of denoising steps to perform at inference time", - "kwargsType": null, - "name": "num_inference_steps", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The timestep that represents the initial noise level for image-to-image generation", - "kwargsType": null, - "name": "latent_timestep", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The initial latents to use for the denoising process", - "kwargsType": null, - "name": "latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The time ids to condition the denoising process", - "kwargsType": "denoiser_input_fields", - "name": "add_time_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The negative time ids to condition the denoising process", - "kwargsType": "denoiser_input_fields", - "name": "negative_add_time_ids", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The timestep cond to use for LCM", - "kwargsType": null, - "name": "timestep_cond", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The processed control images", - "kwargsType": null, - "name": "controlnet_cond", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "The control mode indices", - "kwargsType": "controlnet_kwargs", - "name": "control_type_idx", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "The control type tensor that specifies which control type is active", - "kwargsType": "controlnet_kwargs", - "name": "control_type", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The controlnet guidance start value", - "kwargsType": null, - "name": "control_guidance_start", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "The controlnet guidance end value", - "kwargsType": null, - "name": "control_guidance_end", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "The controlnet conditioning scale values", - "kwargsType": null, - "name": "conditioning_scale", - "required": false, - "type": "list[builtins.float]" - }, - { - "default": null, - "description": "Whether guess mode is used", - "kwargsType": null, - "name": "guess_mode", - "required": false, - "type": "builtins.bool" - }, - { - "default": null, - "description": "The controlnet keep values", - "kwargsType": null, - "name": "controlnet_keep", - "required": false, - "type": "list[builtins.float]" - }, - { - "default": null, - "description": "The generated images, can be a PIL.Image.Image, torch.Tensor or a numpy array", - "kwargsType": null, - "name": "images", - "required": false, - "type": "UnionType[list[PIL.Image.Image],list[numpy.array],list[torch.Tensor]]" - } - ], - "requiredInputs": [ - "batch_size", - "control_image", - "control_mode", - "controlnet_cond", - "controlnet_keep", - "dtype", - "guess_mode", - "image", - "image_latents", - "latent_timestep", - "latents", - "num_inference_steps", - "pooled_prompt_embeds", - "prompt_embeds", - "timesteps" - ], - "schemaVersion": 1, - "variadicInputs": [ - { - "default": null, - "description": "All conditional model inputs that need to be prepared with guider. It should contain prompt_embeds/negative_prompt_embeds, add_time_ids/negative_add_time_ids, pooled_prompt_embeds/negative_pooled_prompt_embeds, and ip_adapter_embeds/negative_ip_adapter_embeds (optional).please add `kwargs_type=denoiser_input_fields` to their parameter spec (`OutputParam`) when they are created and added to the pipeline state", - "kwargsType": "denoiser_input_fields", - "required": false, - "type": "opaque" - } - ] - }, - { - "className": "SequentialPipelineBlocks", - "components": [ - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 16 - }, - "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" - } - ], - "configs": [ - { - "default": true, - "description": "", - "name": "default_use_system_prompt" - }, - { - "default": true, + "default": 50, "description": "", - "name": "enable_safety_checker" + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "opaque" }, { - "default": true, + "default": null, "description": "", - "name": "is_distilled" + "kwargsType": null, + "name": "timesteps", + "required": true, + "type": "opaque" }, { "default": null, "description": "", - "name": "distilled_sigmas" - } - ], - "contentHash": "sha256:bd209ba0690d77f2124fa30ab4ff0ab2ffddcc9428063d78c6091fb9e2d8ef5d", - "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:bd209ba0690d77f2124fa30ab4ff0ab2ffddcc9428063d78c6091fb9e2d8ef5d", - "inputs": [ + "kwargsType": null, + "name": "sigmas", + "required": false, + "type": "opaque" + }, { "default": null, - "description": "The text prompt that guides Cosmos3 generation.", + "description": "", "kwargsType": null, - "name": "prompt", - "required": true, - "type": "builtins.str" + "name": "denoising_end", + "required": false, + "type": "opaque" }, { - "default": null, - "description": "Number of frames to generate.", + "default": 0.3, + "description": "", "kwargsType": null, - "name": "num_frames", - "required": true, - "type": "builtins.int" + "name": "strength", + "required": false, + "type": "opaque" }, { "default": null, - "description": "Height of the generated video or image in pixels.", + "description": "", "kwargsType": null, - "name": "height", - "required": true, - "type": "builtins.int" + "name": "denoising_start", + "required": false, + "type": "opaque" }, { "default": null, - "description": "Width of the generated video or image in pixels.", + "description": "", "kwargsType": null, - "name": "width", + "name": "latents", "required": true, - "type": "builtins.int" + "type": "opaque" }, { - "default": 24.0, - "description": "Frame rate of the generated video.", + "default": null, + "description": "", "kwargsType": null, - "name": "fps", + "name": "original_size", "required": false, - "type": "builtins.float" + "type": "opaque" }, { - "default": true, - "description": "Whether to prepend the Cosmos3 system prompt.", + "default": null, + "description": "", "kwargsType": null, - "name": "use_system_prompt", + "name": "target_size", "required": false, - "type": "builtins.bool" + "type": "opaque" }, { - "default": true, - "description": "Whether to add resolution metadata to the prompt.", + "default": null, + "description": "", "kwargsType": null, - "name": "add_resolution_template", + "name": "negative_original_size", "required": false, - "type": "builtins.bool" + "type": "opaque" }, { - "default": true, - "description": "Whether to add duration metadata to the prompt.", + "default": null, + "description": "", "kwargsType": null, - "name": "add_duration_template", + "name": "negative_target_size", "required": false, - "type": "builtins.bool" + "type": "opaque" }, { - "default": null, - "description": "Reference video for video-to-video conditioning.", + "default": [ + 0, + 0 + ], + "description": "", "kwargsType": null, - "name": "video", + "name": "crops_coords_top_left", "required": false, "type": "opaque" }, { "default": [ 0, - 1 + 0 ], - "description": "Latent-frame indexes to preserve from the conditioning video.", + "description": "", "kwargsType": null, - "name": "condition_frame_indexes_vision", + "name": "negative_crops_coords_top_left", "required": false, - "type": "UnionType[list[builtins.int],tuple[Ellipsis,builtins.int]]" + "type": "opaque" }, { - "default": "first", - "description": "Which end of a longer conditioning video to use: `first` or `last`.", + "default": 6.0, + "description": "", "kwargsType": null, - "name": "condition_video_keep", + "name": "aesthetic_score", "required": false, - "type": "builtins.str" + "type": "opaque" }, { - "default": null, - "description": "Pre-generated noisy vision latents.", + "default": 2.0, + "description": "", "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" + "name": "negative_aesthetic_score", + "required": false, + "type": "opaque" }, { "default": null, - "description": "Torch generator for deterministic generation.", + "description": "", "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" + "name": "control_image", + "required": true, + "type": "opaque" }, { "default": null, - "description": "The number of denoising steps.", + "description": "", "kwargsType": null, - "name": "num_inference_steps", + "name": "control_mode", "required": true, - "type": "builtins.int" + "type": "opaque" }, { - "default": null, - "description": "Unused for distilled checkpoints; classifier-free guidance is baked into the weights and the scale is forced to 1.0. Passing a value other than 1.0 raises an error.", + "default": 0.0, + "description": "", "kwargsType": null, - "name": "guidance_scale", + "name": "control_guidance_start", "required": false, - "type": "builtins.float" + "type": "opaque" }, { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", + "default": 1.0, + "description": "", "kwargsType": null, - "name": "output_type", + "name": "control_guidance_end", "required": false, - "type": "builtins.str" - } - ], - "kind": "sequential", - "outputs": [ + "type": "opaque" + }, { - "default": null, - "description": "Number of frames to generate.", + "default": 1.0, + "description": "", "kwargsType": null, - "name": "num_frames", + "name": "controlnet_conditioning_scale", "required": false, - "type": "builtins.int" + "type": "opaque" }, { - "default": null, - "description": "Height of the generated video or image in pixels.", + "default": false, + "description": "", "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" + "name": "guess_mode", + "required": true, + "type": "opaque" }, { "default": null, - "description": "Width of the generated video or image in pixels.", + "description": "The crop coordinates to use for preprocess/postprocess the image and mask, for inpainting task only. Can be generated in vae_encode step.", "kwargsType": null, - "name": "width", + "name": "crops_coords", "required": false, - "type": "builtins.int" + "type": "UnionType[builtins.NoneType,tuple[builtins.int]]" }, { - "default": null, - "description": "Token IDs for the conditional prompt.", + "default": 0.0, + "description": "", "kwargsType": null, - "name": "cond_input_ids", + "name": "eta", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { - "default": null, - "description": "Token IDs for the unconditional prompt (empty prompt; guidance is baked in).", + "default": "pil", + "description": "", "kwargsType": null, - "name": "uncond_input_ids", + "name": "output_type", "required": false, - "type": "torch.Tensor" - }, + "type": "opaque" + } + ], + "kind": "sequential", + "outputs": [ { "default": null, - "description": "Vision latents encoded from the conditioning image or video.", - "kwargsType": null, - "name": "x0_tokens_vision", + "description": "text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", - "kwargsType": null, - "name": "vision_condition_frames", + "description": "negative text embeddings used to guide the image generation", + "kwargsType": "denoiser_input_fields", + "name": "negative_prompt_embeds", "required": false, - "type": "list[builtins.int]" + "type": "torch.Tensor" }, { "default": null, - "description": "Conditional text segment for the denoiser.", + "description": "pooled text embeddings used to guide the image generation", "kwargsType": "denoiser_input_fields", - "name": "cond_text_segment", + "name": "pooled_prompt_embeds", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional text segment for the denoiser.", + "description": "negative pooled text embeddings used to guide the image generation", "kwargsType": "denoiser_input_fields", - "name": "uncond_text_segment", + "name": "negative_pooled_prompt_embeds", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Noisy vision latents for denoising.", + "description": "The latents representing the reference image for image-to-image/inpainting generation", "kwargsType": null, - "name": "latents", + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Frame rate used to pack vision latents.", + "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_images_per_prompt", "kwargsType": null, - "name": "fps_vision", + "name": "batch_size", "required": false, - "type": "builtins.float" + "type": "builtins.int" }, { "default": null, - "description": "Mask marking conditioned vision latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "vision_condition_mask", + "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "kwargsType": null, + "name": "dtype", "required": false, - "type": "torch.Tensor" + "type": "torch.dtype" }, { "default": null, - "description": "Indexes of conditioned vision latent frames.", - "kwargsType": null, - "name": "vision_condition_indexes_for_pack", + "description": "image embeddings for IP-Adapter", + "kwargsType": "denoiser_input_fields", + "name": "ip_adapter_embeds", "required": false, - "type": "list[builtins.int]" + "type": "list[torch.Tensor]" }, { "default": null, - "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", - "kwargsType": null, - "name": "vision_conditioning_latents", + "description": "negative image embeddings for IP-Adapter", + "kwargsType": "denoiser_input_fields", + "name": "negative_ip_adapter_embeds", "required": false, - "type": "torch.Tensor" + "type": "list[torch.Tensor]" }, { "default": null, - "description": "Conditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_vision_segment", + "description": "The timesteps to use for inference", + "kwargsType": null, + "name": "timesteps", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_vision_segment", + "description": "The number of denoising steps to perform at inference time", + "kwargsType": null, + "name": "num_inference_steps", "required": false, - "type": "builtins.dict" + "type": "builtins.int" }, { "default": null, - "description": "Conditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "cond_position_ids", + "description": "The timestep that represents the initial noise level for image-to-image generation", + "kwargsType": null, + "name": "latent_timestep", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_position_ids", + "description": "The initial latents to use for the denoising process", + "kwargsType": null, + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional multimodal sequence length.", + "description": "The time ids to condition the denoising process", "kwargsType": "denoiser_input_fields", - "name": "cond_sequence_length", + "name": "add_time_ids", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal sequence length.", + "description": "The negative time ids to condition the denoising process", "kwargsType": "denoiser_input_fields", - "name": "uncond_sequence_length", + "name": "negative_add_time_ids", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Scheduler timesteps for denoising.", + "description": "The timestep cond to use for LCM", "kwargsType": null, - "name": "timesteps", + "name": "timestep_cond", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Number of scheduler warmup steps.", + "description": "The processed control images", "kwargsType": null, - "name": "num_warmup_steps", + "name": "controlnet_cond", "required": false, - "type": "builtins.int" + "type": "list[torch.Tensor]" }, { "default": null, - "description": "Resolved number of denoising steps (fixed by the distilled schedule).", - "kwargsType": null, - "name": "num_inference_steps", + "description": "The control mode indices", + "kwargsType": "controlnet_kwargs", + "name": "control_type_idx", "required": false, - "type": "builtins.int" + "type": "list[builtins.int]" }, { "default": null, - "description": "Resolved classifier-free guidance scale (always 1.0 for distilled checkpoints).", - "kwargsType": null, - "name": "guidance_scale", + "description": "The control type tensor that specifies which control type is active", + "kwargsType": "controlnet_kwargs", + "name": "control_type", "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "Vision tokens for the transformer denoiser.", + "description": "The controlnet guidance start value", "kwargsType": null, - "name": "vision_tokens", + "name": "control_guidance_start", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.float" }, { "default": null, - "description": "Timesteps for the vision tokens.", + "description": "The controlnet guidance end value", "kwargsType": null, - "name": "vision_timesteps", + "name": "control_guidance_end", "required": false, - "type": "torch.Tensor" + "type": "builtins.float" }, { "default": null, - "description": "Predicted velocity for vision latents.", + "description": "The controlnet conditioning scale values", "kwargsType": null, - "name": "velocity_vision", + "name": "conditioning_scale", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.float]" }, { "default": null, - "description": "Predicted velocity for sound latents.", + "description": "Whether guess mode is used", "kwargsType": null, - "name": "velocity_sound", + "name": "guess_mode", "required": false, - "type": "torch.Tensor" + "type": "builtins.bool" }, { "default": null, - "description": "Predicted velocity for action latents.", + "description": "The controlnet keep values", "kwargsType": null, - "name": "velocity_action", + "name": "controlnet_keep", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.float]" }, { "default": null, - "description": "The generated videos.", + "description": "The generated images, can be a PIL.Image.Image, torch.Tensor or a numpy array", "kwargsType": null, - "name": "videos", + "name": "images", "required": false, - "type": "list[PIL.Image.Image]" + "type": "UnionType[list[PIL.Image.Image],list[numpy.array],list[torch.Tensor]]" } ], "requiredInputs": [ - "cond_input_ids", - "cond_text_segment", - "cond_vision_segment", - "fps_vision", - "height", + "batch_size", + "control_image", + "control_mode", + "controlnet_cond", + "controlnet_keep", + "dtype", + "guess_mode", + "image", + "image_latents", + "latent_timestep", "latents", - "num_frames", "num_inference_steps", - "num_warmup_steps", - "prompt", - "timesteps", - "uncond_input_ids", - "uncond_text_segment", - "uncond_vision_segment", - "velocity_vision", - "vision_condition_indexes_for_pack", - "vision_condition_mask", - "width" + "pooled_prompt_embeds", + "prompt_embeds", + "timesteps" ], "schemaVersion": 1, "variadicInputs": [ { "default": null, - "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "description": "All conditional model inputs that need to be prepared with guider. It should contain prompt_embeds/negative_prompt_embeds, add_time_ids/negative_add_time_ids, pooled_prompt_embeds/negative_pooled_prompt_embeds, and ip_adapter_embeds/negative_ip_adapter_embeds (optional).please add `kwargs_type=denoiser_input_fields` to their parameter spec (`OutputParam`) when they are created and added to the pipeline state", "kwargsType": "denoiser_input_fields", "required": false, "type": "opaque" @@ -55865,65 +56512,110 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + "name": "text_encoder", + "type": "transformers.modeling_utils.PreTrainedModel" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "tokenizer", + "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + "name": "connectors", + "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "duration_head", + "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "audio_vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" }, { "creationMethod": "from_config", "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 16 + "guidance_rescale": 0.7, + "guidance_scale": 3.0, + "modality_scale": 3.0, + "spatio_temporal_guidance_blocks": [ + 28 + ], + "stg_scale": 1.0 }, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" - } - ], - "configs": [ + "name": "guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" + }, { - "default": true, + "creationMethod": "from_config", + "defaultConfig": { + "guidance_rescale": 0.7, + "guidance_scale": 7.0, + "modality_scale": 3.0, + "stg_scale": 1.0 + }, "description": "", - "name": "default_use_system_prompt" + "name": "audio_guider", + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "enable_safety_checker" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2.AutoencoderKLLTX2Video" }, { - "default": false, + "creationMethod": "from_config", + "defaultConfig": { + "vae_scale_factor": 32 + }, "description": "", - "name": "use_native_flow_schedule" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vocoder", + "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" } ], - "contentHash": "sha256:cb28b453878c928160eb99fc57c41553e7f9b382cb712a1e26cd1a00562bb70b", + "configs": [], + "contentHash": "sha256:ce8b330093a49971d1a10f90c3651db1383b1f7495e9f79a6cd6491023bf79b7", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:cb28b453878c928160eb99fc57c41553e7f9b382cb712a1e26cd1a00562bb70b", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:ce8b330093a49971d1a10f90c3651db1383b1f7495e9f79a6cd6491023bf79b7", "inputs": [ { "default": null, - "description": "The text prompt that guides Cosmos3 generation.", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, "name": "prompt", "required": true, @@ -55931,83 +56623,107 @@ }, { "default": null, - "description": "The negative text prompt used for classifier-free guidance.", + "description": "The prompt or prompts not to guide the image generation.", + "kwargsType": null, + "name": "negative_prompt", + "required": false, + "type": "builtins.str" + }, + { + "default": 1024, + "description": "Maximum sequence length for prompt encoding.", + "kwargsType": null, + "name": "max_sequence_length", + "required": false, + "type": "builtins.int" + }, + { + "default": 1.0, + "description": "Lower bound on the auto-predicted duration.", + "kwargsType": null, + "name": "min_seconds", + "required": false, + "type": "builtins.float" + }, + { + "default": 20.0, + "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", + "kwargsType": null, + "name": "max_seconds", + "required": false, + "type": "builtins.float" + }, + { + "default": 24.0, + "description": "Frames per second of the generated video.", "kwargsType": null, - "name": "negative_prompt", + "name": "frame_rate", "required": false, - "type": "builtins.str" + "type": "builtins.float" }, { - "default": null, - "description": "Number of frames to generate.", + "default": 1, + "description": "The number of images to generate per prompt.", "kwargsType": null, - "name": "num_frames", - "required": true, + "name": "num_videos_per_prompt", + "required": false, "type": "builtins.int" }, { - "default": null, - "description": "Height of the generated video or image in pixels.", + "default": 30, + "description": "The number of denoising steps.", "kwargsType": null, - "name": "height", + "name": "num_inference_steps", "required": true, "type": "builtins.int" }, { "default": null, - "description": "Width of the generated video or image in pixels.", + "description": "Timesteps for the denoising process.", "kwargsType": null, - "name": "width", + "name": "timesteps", "required": true, - "type": "builtins.int" - }, - { - "default": 24.0, - "description": "Frame rate of the generated video.", - "kwargsType": null, - "name": "fps", - "required": false, - "type": "builtins.float" + "type": "torch.Tensor" }, { "default": null, - "description": "Whether to prepend the Cosmos3 system prompt.", + "description": "Custom sigmas for the denoising process.", "kwargsType": null, - "name": "use_system_prompt", + "name": "sigmas", "required": false, - "type": "UnionType[builtins.NoneType,builtins.bool]" + "type": "list[builtins.float]" }, { - "default": true, - "description": "Whether to add resolution metadata to the prompt.", + "default": 512, + "description": "The height in pixels of the generated image.", "kwargsType": null, - "name": "add_resolution_template", + "name": "height", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { - "default": true, - "description": "Whether to add duration metadata to the prompt.", + "default": 704, + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "add_duration_template", + "name": "width", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { "default": null, - "description": "Reference image for image-to-video conditioning.", + "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, - "name": "image", - "required": false, - "type": "opaque" + "name": "latents", + "required": true, + "type": "torch.Tensor" }, { "default": null, - "description": "Pre-generated noisy vision latents.", + "description": "Interpolation factor between random noise and any provided latents. `None` (default) resolves to 0.0, which keeps the provided latents.", "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" + "name": "noise_scale", + "required": false, + "type": "builtins.float" }, { "default": null, @@ -56018,269 +56734,229 @@ "type": "torch._C.Generator" }, { - "default": 50, - "description": "The number of denoising steps.", + "default": null, + "description": "Optional pre-encoded audio latents; random noise is used when not provided.", "kwargsType": null, - "name": "num_inference_steps", + "name": "audio_latents", "required": true, - "type": "builtins.int" + "type": "torch.Tensor" }, { - "default": 6.0, - "description": "Scale for classifier-free guidance.", + "default": true, + "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", "kwargsType": null, - "name": "guidance_scale", + "name": "use_cross_timestep", "required": false, - "type": "builtins.float" + "type": "builtins.bool" }, { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", + "default": null, + "description": "Additional kwargs for attention processors.", "kwargsType": null, - "name": "output_type", + "name": "attention_kwargs", "required": false, - "type": "builtins.str" + "type": "dict[builtins.str,typing.Any]" }, { - "default": null, - "description": "Denoised action latents.", + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, - "name": "action_latents", + "name": "output_type", "required": false, - "type": "torch.Tensor" + "type": "builtins.str" }, { - "default": null, - "description": "Requested action-generation mode.", + "default": 0.0, + "description": "The timestep at which the VAE decodes the final latents.", "kwargsType": null, - "name": "action_mode", + "name": "decode_timestep", "required": false, - "type": "builtins.str" + "type": "opaque" }, { "default": null, - "description": "Unpadded action-vector dimension.", + "description": "Noise interpolation factor applied to the latents at the decode timestep.", "kwargsType": null, - "name": "raw_action_dim_resolved", + "name": "decode_noise_scale", "required": false, - "type": "builtins.int" + "type": "opaque" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "Number of frames to generate.", - "kwargsType": null, - "name": "num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Height of the generated video or image in pixels.", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Width of the generated video or image in pixels.", + "description": "Packed per-layer Gemma hidden states for the prompt.", "kwargsType": null, - "name": "width", + "name": "prompt_embeds", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the conditional prompt.", + "description": "Binary attention mask for `prompt_embeds`.", "kwargsType": null, - "name": "cond_input_ids", + "name": "prompt_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Token IDs for the unconditional prompt.", + "description": "Packed per-layer Gemma hidden states for the negative prompt.", "kwargsType": null, - "name": "uncond_input_ids", + "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Vision latents encoded from the conditioning image or video.", + "description": "Binary attention mask for `negative_prompt_embeds`.", "kwargsType": null, - "name": "x0_tokens_vision", + "name": "negative_prompt_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", + "description": "The number of prompts being denoised (before per-prompt expansion).", "kwargsType": null, - "name": "vision_condition_frames", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "Conditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_text_segment", + "name": "batch_size", "required": false, - "type": "builtins.dict" + "type": "builtins.int" }, { "default": null, - "description": "Unconditional text segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_text_segment", + "description": "The dtype of the prompt embeddings.", + "kwargsType": null, + "name": "dtype", "required": false, - "type": "builtins.dict" + "type": "torch.dtype" }, { "default": null, - "description": "Noisy vision latents for denoising.", + "description": "Video-branch text conditioning (cond).", "kwargsType": null, - "name": "latents", + "name": "connector_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Frame rate used to pack vision latents.", + "description": "Audio-branch text conditioning (cond).", "kwargsType": null, - "name": "fps_vision", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Mask marking conditioned vision latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "vision_condition_mask", + "name": "connector_audio_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Indexes of conditioned vision latent frames.", + "description": "Binary text attention mask (cond).", "kwargsType": null, - "name": "vision_condition_indexes_for_pack", + "name": "connector_attention_mask", "required": false, - "type": "list[builtins.int]" + "type": "torch.Tensor" }, { "default": null, - "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", + "description": "Video-branch text conditioning (uncond).", "kwargsType": null, - "name": "vision_conditioning_latents", + "name": "negative_connector_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_vision_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Unconditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_vision_segment", + "description": "Audio-branch text conditioning (uncond).", + "kwargsType": null, + "name": "negative_connector_audio_prompt_embeds", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Conditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "cond_position_ids", + "description": "Binary text attention mask (uncond).", + "kwargsType": null, + "name": "negative_connector_attention_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_position_ids", + "description": "The predicted number of frames to generate.", + "kwargsType": null, + "name": "num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Conditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sequence_length", + "description": "", + "kwargsType": null, + "name": "timesteps", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sequence_length", + "description": "", + "kwargsType": null, + "name": "num_inference_steps", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Scheduler timesteps for denoising.", + "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", "kwargsType": null, - "name": "timesteps", + "name": "audio_scheduler", "required": false, - "type": "torch.Tensor" + "type": "opaque" }, { "default": null, - "description": "Number of scheduler warmup steps.", + "description": "Packed noisy video latents.", "kwargsType": null, - "name": "num_warmup_steps", + "name": "latents", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Vision tokens for the transformer denoiser.", + "description": "The resolved interpolation factor, forwarded to the audio latents step.", "kwargsType": null, - "name": "vision_tokens", + "name": "noise_scale", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.float" }, { "default": null, - "description": "Timesteps for the vision tokens.", + "description": "Packed noisy audio latents.", "kwargsType": null, - "name": "vision_timesteps", + "name": "audio_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Predicted velocity for vision latents.", - "kwargsType": null, - "name": "velocity_vision", + "description": "Number of audio latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "audio_num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Predicted velocity for sound latents.", - "kwargsType": null, - "name": "velocity_sound", + "description": "Video RoPE patch coordinates.", + "kwargsType": "denoiser_input_fields", + "name": "video_coords", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Predicted velocity for action latents.", - "kwargsType": null, - "name": "velocity_action", + "description": "Audio RoPE patch coordinates.", + "kwargsType": "denoiser_input_fields", + "name": "audio_coords", "required": false, "type": "torch.Tensor" }, @@ -56294,31 +56970,33 @@ }, { "default": null, - "description": "Generated action vectors.", + "description": "The generated audio waveform.", "kwargsType": null, - "name": "action", + "name": "audio", "required": false, - "type": "list[torch.Tensor]" + "type": "torch.Tensor" } ], "requiredInputs": [ - "cond_input_ids", - "cond_text_segment", - "cond_vision_segment", - "fps_vision", - "height", + "audio_latents", + "audio_num_frames", + "audio_scheduler", + "batch_size", + "connector_attention_mask", + "connector_audio_prompt_embeds", + "connector_prompt_embeds", + "dtype", "latents", - "num_frames", + "negative_connector_attention_mask", + "negative_connector_audio_prompt_embeds", + "negative_connector_prompt_embeds", + "negative_prompt_attention_mask", + "negative_prompt_embeds", "num_inference_steps", - "num_warmup_steps", "prompt", - "timesteps", - "uncond_input_ids", - "uncond_text_segment", - "uncond_vision_segment", - "velocity_vision", - "vision_condition_indexes_for_pack", - "width" + "prompt_attention_mask", + "prompt_embeds", + "timesteps" ], "schemaVersion": 1, "variadicInputs": [ @@ -58216,6 +58894,13 @@ "name": "text_tokenizer", "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + }, { "creationMethod": "from_config", "defaultConfig": { @@ -58230,22 +58915,22 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + "name": "sound_tokenizer", + "type": "diffusers.models.autoencoders.autoencoder_cosmos3_audio.Cosmos3AVAEAudioTokenizer" } ], "configs": [ @@ -58265,9 +58950,9 @@ "name": "use_native_flow_schedule" } ], - "contentHash": "sha256:d7e680fa704b081d91e6f7d2c0bee5dda178c3296f95ac91d4c5a4490770c0da", + "contentHash": "sha256:d5d2adda5cb4435a515432d61a31b646f9eb0f4e183ebfe1167d4a1f14c4a0da", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:d7e680fa704b081d91e6f7d2c0bee5dda178c3296f95ac91d4c5a4490770c0da", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:d5d2adda5cb4435a515432d61a31b646f9eb0f4e183ebfe1167d4a1f14c4a0da", "inputs": [ { "default": null, @@ -58285,14 +58970,6 @@ "required": false, "type": "builtins.str" }, - { - "default": null, - "description": "Action-conditioning metadata and its reference visual input.", - "kwargsType": null, - "name": "action", - "required": true, - "type": "diffusers.pipelines.cosmos.pipeline_cosmos3_omni.CosmosActionCondition" - }, { "default": null, "description": "Number of frames to generate.", @@ -58349,6 +59026,33 @@ "required": false, "type": "builtins.bool" }, + { + "default": null, + "description": "Reference video for video-to-video conditioning.", + "kwargsType": null, + "name": "video", + "required": false, + "type": "opaque" + }, + { + "default": [ + 0, + 1 + ], + "description": "Latent-frame indexes to preserve from the conditioning video.", + "kwargsType": null, + "name": "condition_frame_indexes_vision", + "required": false, + "type": "UnionType[list[builtins.int],tuple[Ellipsis,builtins.int]]" + }, + { + "default": "first", + "description": "Which end of a longer conditioning video to use: `first` or `last`.", + "kwargsType": null, + "name": "condition_video_keep", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Pre-generated noisy vision latents.", @@ -58375,12 +59079,44 @@ }, { "default": null, - "description": "Pre-generated noisy action latents.", + "description": "Pre-generated noisy sound latents.", "kwargsType": null, - "name": "action_latents", + "name": "sound_latents", "required": true, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -58396,10 +59132,15 @@ "name": "output_type", "required": false, "type": "builtins.str" - } - ], - "kind": "sequential", - "outputs": [ + }, + { + "default": null, + "description": "Denoised action latents.", + "kwargsType": null, + "name": "action_latents", + "required": false, + "type": "torch.Tensor" + }, { "default": null, "description": "Requested action-generation mode.", @@ -58408,6 +59149,17 @@ "required": false, "type": "builtins.str" }, + { + "default": null, + "description": "Unpadded action-vector dimension.", + "kwargsType": null, + "name": "raw_action_dim_resolved", + "required": false, + "type": "builtins.int" + } + ], + "kind": "sequential", + "outputs": [ { "default": null, "description": "Number of frames to generate.", @@ -58464,14 +59216,6 @@ "required": false, "type": "list[builtins.int]" }, - { - "default": null, - "description": "Action-frame indexes fixed by action conditioning.", - "kwargsType": null, - "name": "action_condition_frame_indexes", - "required": false, - "type": "list[builtins.int]" - }, { "default": null, "description": "Conditional text segment for the denoiser.", @@ -58594,57 +59338,49 @@ }, { "default": null, - "description": "Noisy action latents for denoising.", + "description": "Noisy sound latents for denoising.", "kwargsType": null, - "name": "action_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Mask marking conditioned action latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "action_condition_mask", + "name": "sound_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Embodiment domain IDs for action conditioning.", - "kwargsType": "denoiser_input_fields", - "name": "action_domain_ids", + "description": "Frame rate of the sound latent sequence.", + "kwargsType": null, + "name": "fps_sound", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.float" }, { "default": null, - "description": "Unpadded action-vector dimension.", + "description": "Mask marking conditioned sound latent frames.", "kwargsType": "denoiser_input_fields", - "name": "raw_action_dim_resolved", + "name": "sound_condition_mask", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Scheduler used to update action latents.", + "description": "Scheduler used to update sound latents.", "kwargsType": null, - "name": "action_scheduler", + "name": "sound_scheduler", "required": false, "type": "opaque" }, { "default": null, - "description": "Conditional action segment for the denoiser.", + "description": "Conditional sound segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "cond_action_segment", + "name": "cond_sound_segment", "required": false, "type": "builtins.dict" }, { "default": null, - "description": "Unconditional action segment for the denoiser.", + "description": "Unconditional sound segment for the denoiser.", "kwargsType": "denoiser_input_fields", - "name": "uncond_action_segment", + "name": "uncond_sound_segment", "required": false, "type": "builtins.dict" }, @@ -58666,17 +59402,17 @@ }, { "default": null, - "description": "Action tokens for the transformer denoiser.", + "description": "Sound tokens for the transformer denoiser.", "kwargsType": null, - "name": "action_tokens", + "name": "sound_tokens", "required": false, "type": "list[torch.Tensor]" }, { "default": null, - "description": "Timesteps for the action tokens.", + "description": "Timesteps for the sound tokens.", "kwargsType": null, - "name": "action_timesteps", + "name": "sound_timesteps", "required": false, "type": "torch.Tensor" }, @@ -58712,6 +59448,22 @@ "required": false, "type": "list[PIL.Image.Image]" }, + { + "default": null, + "description": "Generated waveform.", + "kwargsType": null, + "name": "sound", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Sample rate of the generated waveform in Hz.", + "kwargsType": null, + "name": "sampling_rate", + "required": false, + "type": "builtins.int" + }, { "default": null, "description": "Generated action vectors.", @@ -58722,16 +59474,13 @@ } ], "requiredInputs": [ - "action", - "action_condition_mask", - "action_latents", - "action_scheduler", - "cond_action_segment", "cond_input_ids", "cond_position_ids", "cond_sequence_length", + "cond_sound_segment", "cond_text_segment", "cond_vision_segment", + "fps_sound", "fps_vision", "height", "latents", @@ -58739,14 +59488,16 @@ "num_inference_steps", "num_warmup_steps", "prompt", + "sound_latents", + "sound_scheduler", "timesteps", - "uncond_action_segment", "uncond_input_ids", "uncond_position_ids", "uncond_sequence_length", + "uncond_sound_segment", "uncond_text_segment", "uncond_vision_segment", - "velocity_action", + "velocity_sound", "velocity_vision", "vision_condition_indexes_for_pack", "width" @@ -60417,492 +61168,102 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage" - }, - { - "creationMethod": "from_config", - "defaultConfig": null, - "description": "", - "name": "pachifier", - "type": "diffusers.modular_pipelines.qwenimage.modular_pipeline.QwenImagePachifier" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_qwenimage.QwenImageTransformer2DModel" - } - ], - "configs": [], - "contentHash": "sha256:dfd20a66df0a0af5a3195ecb278098eaabe5c318575ca83c6bb5621e9d512d9e", - "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:dfd20a66df0a0af5a3195ecb278098eaabe5c318575ca83c6bb5621e9d512d9e", - "inputs": [ - { - "default": null, - "description": "Reference image(s) for denoising. Can be a single image or list of images.", - "kwargsType": null, - "name": "image", - "required": true, - "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" - }, - { - "default": null, - "description": "The prompt or prompts to guide image generation.", - "kwargsType": null, - "name": "prompt", - "required": true, - "type": "builtins.str" - }, - { - "default": null, - "description": "The prompt or prompts not to guide the image generation.", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "builtins.str" - }, - { - "default": null, - "description": "Torch generator for deterministic generation.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" - }, - { - "default": 1, - "description": "The number of images to generate per prompt.", - "kwargsType": null, - "name": "num_images_per_prompt", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The height in pixels of the generated image.", - "kwargsType": null, - "name": "height", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "The width in pixels of the generated image.", - "kwargsType": null, - "name": "width", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "Pre-generated noisy latents for image generation.", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": 50, - "description": "The number of denoising steps.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "Custom sigmas for the denoising process.", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "list[builtins.float]" - }, - { - "default": null, - "description": "Additional kwargs for attention processors.", - "kwargsType": null, - "name": "attention_kwargs", - "required": false, - "type": "dict[builtins.str,typing.Any]" - }, - { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", - "kwargsType": null, - "name": "output_type", - "required": false, - "type": "builtins.str" - } - ], - "kind": "sequential", - "outputs": [ - { - "default": null, - "description": "Images resized to 1024x1024 target area for VAE encoding", - "kwargsType": null, - "name": "resized_image", - "required": false, - "type": "list[PIL.Image.Image]" - }, - { - "default": null, - "description": "Images resized to 384x384 target area for VL text encoding", - "kwargsType": null, - "name": "resized_cond_image", - "required": false, - "type": "list[PIL.Image.Image]" - }, - { - "default": null, - "description": "The prompt embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The encoder attention mask.", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The negative prompt embeddings.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The negative prompt embeddings mask.", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The processed image", - "kwargsType": null, - "name": "processed_image", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The latent representation of the input image.", - "kwargsType": null, - "name": "image_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The batch size of the prompt embeddings", - "kwargsType": null, - "name": "batch_size", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The data type of the prompt embeddings", - "kwargsType": null, - "name": "dtype", - "required": false, - "type": "torch.dtype" - }, - { - "default": null, - "description": "The image heights calculated from the image latents dimension", - "kwargsType": null, - "name": "image_height", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "The image widths calculated from the image latents dimension", - "kwargsType": null, - "name": "image_width", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "if not provided, updated to image height", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "if not provided, updated to image width", - "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The initial latents to use for the denoising process", - "kwargsType": null, - "name": "latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The timesteps to use for the denoising process", - "kwargsType": null, - "name": "timesteps", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The shapes of the image latents, used for RoPE calculation", - "kwargsType": "denoiser_input_fields", - "name": "img_shapes", - "required": false, - "type": "list[list[tuple[builtins.int]]]" - }, - { - "default": null, - "description": "The sequence lengths of the prompt embeds, used for RoPE calculation", - "kwargsType": "denoiser_input_fields", - "name": "txt_seq_lens", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "The sequence lengths of the negative prompt embeds, used for RoPE calculation", - "kwargsType": "denoiser_input_fields", - "name": "negative_txt_seq_lens", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "Generated images. (tensor output of the vae decoder.)", - "kwargsType": null, - "name": "images", - "required": false, - "type": "list[PIL.Image.Image]" - } - ], - "requiredInputs": [ - "height", - "image", - "image_height", - "image_latents", - "image_width", - "images", - "img_shapes", - "latents", - "num_inference_steps", - "processed_image", - "prompt", - "prompt_embeds", - "prompt_embeds_mask", - "resized_cond_image", - "resized_image", - "timesteps", - "width" - ], - "schemaVersion": 1, - "variadicInputs": [ - { - "default": null, - "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", - "kwargsType": "denoiser_input_fields", - "required": false, - "type": "opaque" - } - ] - }, - { - "className": "SequentialPipelineBlocks", - "components": [ - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 16 - }, - "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" - } - ], - "configs": [ - { - "default": true, - "description": "", - "name": "default_use_system_prompt" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_qwenimage.AutoencoderKLQwenImage" }, { - "default": true, + "creationMethod": "from_config", + "defaultConfig": null, "description": "", - "name": "enable_safety_checker" + "name": "pachifier", + "type": "diffusers.modular_pipelines.qwenimage.modular_pipeline.QwenImagePachifier" }, { - "default": true, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "is_distilled" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" }, { - "default": null, + "creationMethod": "from_pretrained", + "defaultConfig": null, "description": "", - "name": "distilled_sigmas" + "name": "transformer", + "type": "diffusers.models.transformers.transformer_qwenimage.QwenImageTransformer2DModel" } ], - "contentHash": "sha256:e1088027382fd15528c635f1992c19eff033f252b633cc5520ec4619574d8c24", + "configs": [], + "contentHash": "sha256:dfd20a66df0a0af5a3195ecb278098eaabe5c318575ca83c6bb5621e9d512d9e", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e1088027382fd15528c635f1992c19eff033f252b633cc5520ec4619574d8c24", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:dfd20a66df0a0af5a3195ecb278098eaabe5c318575ca83c6bb5621e9d512d9e", "inputs": [ { "default": null, - "description": "The text prompt that guides Cosmos3 generation.", - "kwargsType": null, - "name": "prompt", - "required": true, - "type": "builtins.str" - }, - { - "default": null, - "description": "Number of frames to generate.", + "description": "Reference image(s) for denoising. Can be a single image or list of images.", "kwargsType": null, - "name": "num_frames", + "name": "image", "required": true, - "type": "builtins.int" + "type": "UnionType[PIL.Image.Image,list[PIL.Image.Image]]" }, { "default": null, - "description": "Height of the generated video or image in pixels.", + "description": "The prompt or prompts to guide image generation.", "kwargsType": null, - "name": "height", + "name": "prompt", "required": true, - "type": "builtins.int" + "type": "builtins.str" }, { "default": null, - "description": "Width of the generated video or image in pixels.", - "kwargsType": null, - "name": "width", - "required": true, - "type": "builtins.int" - }, - { - "default": 24.0, - "description": "Frame rate of the generated video.", - "kwargsType": null, - "name": "fps", - "required": false, - "type": "builtins.float" - }, - { - "default": true, - "description": "Whether to prepend the Cosmos3 system prompt.", + "description": "The prompt or prompts not to guide the image generation.", "kwargsType": null, - "name": "use_system_prompt", + "name": "negative_prompt", "required": false, - "type": "builtins.bool" + "type": "builtins.str" }, { - "default": true, - "description": "Whether to add resolution metadata to the prompt.", + "default": null, + "description": "Torch generator for deterministic generation.", "kwargsType": null, - "name": "add_resolution_template", + "name": "generator", "required": false, - "type": "builtins.bool" + "type": "torch._C.Generator" }, { - "default": true, - "description": "Whether to add duration metadata to the prompt.", + "default": 1, + "description": "The number of images to generate per prompt.", "kwargsType": null, - "name": "add_duration_template", + "name": "num_images_per_prompt", "required": false, - "type": "builtins.bool" + "type": "builtins.int" }, { "default": null, - "description": "Vision latents encoded from the conditioning image or video.", + "description": "The height in pixels of the generated image.", "kwargsType": null, - "name": "x0_tokens_vision", - "required": false, - "type": "torch.Tensor" + "name": "height", + "required": true, + "type": "builtins.int" }, { "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", + "description": "The width in pixels of the generated image.", "kwargsType": null, - "name": "vision_condition_frames", - "required": false, - "type": "list[builtins.int]" + "name": "width", + "required": true, + "type": "builtins.int" }, { "default": null, - "description": "Pre-generated noisy vision latents.", + "description": "Pre-generated noisy latents for image generation.", "kwargsType": null, "name": "latents", "required": true, "type": "torch.Tensor" }, { - "default": null, - "description": "Torch generator for deterministic generation.", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "torch._C.Generator" - }, - { - "default": null, + "default": 50, "description": "The number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", @@ -60911,11 +61272,19 @@ }, { "default": null, - "description": "Unused for distilled checkpoints; classifier-free guidance is baked into the weights and the scale is forced to 1.0. Passing a value other than 1.0 raises an error.", + "description": "Custom sigmas for the denoising process.", "kwargsType": null, - "name": "guidance_scale", + "name": "sigmas", "required": false, - "type": "builtins.float" + "type": "list[builtins.float]" + }, + { + "default": null, + "description": "Additional kwargs for attention processors.", + "kwargsType": null, + "name": "attention_kwargs", + "required": false, + "type": "dict[builtins.str,typing.Any]" }, { "default": "pil", @@ -60930,247 +61299,182 @@ "outputs": [ { "default": null, - "description": "Number of frames to generate.", - "kwargsType": null, - "name": "num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Height of the generated video or image in pixels.", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Width of the generated video or image in pixels.", + "description": "Images resized to 1024x1024 target area for VAE encoding", "kwargsType": null, - "name": "width", + "name": "resized_image", "required": false, - "type": "builtins.int" + "type": "list[PIL.Image.Image]" }, { "default": null, - "description": "Token IDs for the conditional prompt.", + "description": "Images resized to 384x384 target area for VL text encoding", "kwargsType": null, - "name": "cond_input_ids", + "name": "resized_cond_image", "required": false, - "type": "torch.Tensor" + "type": "list[PIL.Image.Image]" }, { "default": null, - "description": "Token IDs for the unconditional prompt (empty prompt; guidance is baked in).", - "kwargsType": null, - "name": "uncond_input_ids", + "description": "The prompt embeddings.", + "kwargsType": "denoiser_input_fields", + "name": "prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional text segment for the denoiser.", + "description": "The encoder attention mask.", "kwargsType": "denoiser_input_fields", - "name": "cond_text_segment", + "name": "prompt_embeds_mask", "required": false, - "type": "builtins.dict" + "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional text segment for the denoiser.", + "description": "The negative prompt embeddings.", "kwargsType": "denoiser_input_fields", - "name": "uncond_text_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Noisy vision latents for denoising.", - "kwargsType": null, - "name": "latents", + "name": "negative_prompt_embeds", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Frame rate used to pack vision latents.", - "kwargsType": null, - "name": "fps_vision", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Mask marking conditioned vision latent frames.", + "description": "The negative prompt embeddings mask.", "kwargsType": "denoiser_input_fields", - "name": "vision_condition_mask", + "name": "negative_prompt_embeds_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Indexes of conditioned vision latent frames.", - "kwargsType": null, - "name": "vision_condition_indexes_for_pack", - "required": false, - "type": "list[builtins.int]" - }, - { - "default": null, - "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", + "description": "The processed image", "kwargsType": null, - "name": "vision_conditioning_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Conditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_vision_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Unconditional vision segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_vision_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Conditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "cond_position_ids", + "name": "processed_image", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Unconditional multimodal RoPE position IDs.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_position_ids", + "description": "The latent representation of the input image.", + "kwargsType": null, + "name": "image_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Conditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sequence_length", + "description": "The batch size of the prompt embeddings", + "kwargsType": null, + "name": "batch_size", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Unconditional multimodal sequence length.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sequence_length", + "description": "The data type of the prompt embeddings", + "kwargsType": null, + "name": "dtype", "required": false, - "type": "builtins.int" + "type": "torch.dtype" }, { "default": null, - "description": "Scheduler timesteps for denoising.", + "description": "The image heights calculated from the image latents dimension", "kwargsType": null, - "name": "timesteps", + "name": "image_height", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "Number of scheduler warmup steps.", + "description": "The image widths calculated from the image latents dimension", "kwargsType": null, - "name": "num_warmup_steps", + "name": "image_width", "required": false, - "type": "builtins.int" + "type": "list[builtins.int]" }, { "default": null, - "description": "Resolved number of denoising steps (fixed by the distilled schedule).", + "description": "if not provided, updated to image height", "kwargsType": null, - "name": "num_inference_steps", + "name": "height", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Resolved classifier-free guidance scale (always 1.0 for distilled checkpoints).", + "description": "if not provided, updated to image width", "kwargsType": null, - "name": "guidance_scale", + "name": "width", "required": false, - "type": "builtins.float" + "type": "builtins.int" }, { "default": null, - "description": "Vision tokens for the transformer denoiser.", + "description": "The initial latents to use for the denoising process", "kwargsType": null, - "name": "vision_tokens", + "name": "latents", "required": false, - "type": "list[torch.Tensor]" + "type": "torch.Tensor" }, { "default": null, - "description": "Timesteps for the vision tokens.", + "description": "The timesteps to use for the denoising process", "kwargsType": null, - "name": "vision_timesteps", + "name": "timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Predicted velocity for vision latents.", - "kwargsType": null, - "name": "velocity_vision", + "description": "The shapes of the image latents, used for RoPE calculation", + "kwargsType": "denoiser_input_fields", + "name": "img_shapes", "required": false, - "type": "torch.Tensor" + "type": "list[list[tuple[builtins.int]]]" }, { "default": null, - "description": "Predicted velocity for sound latents.", - "kwargsType": null, - "name": "velocity_sound", + "description": "The sequence lengths of the prompt embeds, used for RoPE calculation", + "kwargsType": "denoiser_input_fields", + "name": "txt_seq_lens", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "Predicted velocity for action latents.", - "kwargsType": null, - "name": "velocity_action", + "description": "The sequence lengths of the negative prompt embeds, used for RoPE calculation", + "kwargsType": "denoiser_input_fields", + "name": "negative_txt_seq_lens", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "The generated videos.", + "description": "Generated images. (tensor output of the vae decoder.)", "kwargsType": null, - "name": "videos", + "name": "images", "required": false, "type": "list[PIL.Image.Image]" } ], "requiredInputs": [ - "cond_input_ids", - "cond_text_segment", - "cond_vision_segment", - "fps_vision", "height", + "image", + "image_height", + "image_latents", + "image_width", + "images", + "img_shapes", "latents", - "num_frames", "num_inference_steps", - "num_warmup_steps", + "processed_image", "prompt", + "prompt_embeds", + "prompt_embeds_mask", + "resized_cond_image", + "resized_image", "timesteps", - "uncond_input_ids", - "uncond_text_segment", - "uncond_vision_segment", - "velocity_vision", - "vision_condition_indexes_for_pack", - "vision_condition_mask", "width" ], "schemaVersion": 1, @@ -61194,23 +61498,6 @@ "name": "text_tokenizer", "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "resample": "bilinear", - "vae_scale_factor": 16 - }, - "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" - }, { "creationMethod": "from_pretrained", "defaultConfig": null, @@ -61229,8 +61516,18 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "sound_tokenizer", - "type": "diffusers.models.autoencoders.autoencoder_cosmos3_audio.Cosmos3AVAEAudioTokenizer" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "resample": "bilinear", + "vae_scale_factor": 16 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" } ], "configs": [ @@ -61250,9 +61547,9 @@ "name": "use_native_flow_schedule" } ], - "contentHash": "sha256:e130c0522b6068103dd7487016c7f639a610d71fc6f403b15ce7f01c1069766d", + "contentHash": "sha256:e076a6919c2f2d84e62e409756a023c29ff26820971cbf292e69e948453500b9", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e130c0522b6068103dd7487016c7f639a610d71fc6f403b15ce7f01c1069766d", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e076a6919c2f2d84e62e409756a023c29ff26820971cbf292e69e948453500b9", "inputs": [ { "default": null, @@ -61328,30 +61625,19 @@ }, { "default": null, - "description": "Reference video for video-to-video conditioning.", - "kwargsType": null, - "name": "video", - "required": false, - "type": "opaque" - }, - { - "default": [ - 0, - 1 - ], - "description": "Latent-frame indexes to preserve from the conditioning video.", + "description": "Vision latents encoded from the conditioning image or video.", "kwargsType": null, - "name": "condition_frame_indexes_vision", + "name": "x0_tokens_vision", "required": false, - "type": "UnionType[list[builtins.int],tuple[Ellipsis,builtins.int]]" + "type": "torch.Tensor" }, { - "default": "first", - "description": "Which end of a longer conditioning video to use: `first` or `last`.", + "default": null, + "description": "Latent-frame indexes fixed by visual conditioning.", "kwargsType": null, - "name": "condition_video_keep", + "name": "vision_condition_frames", "required": false, - "type": "builtins.str" + "type": "list[builtins.int]" }, { "default": null, @@ -61379,11 +61665,35 @@ }, { "default": null, - "description": "Pre-generated noisy sound latents.", + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", "kwargsType": null, - "name": "sound_latents", - "required": true, - "type": "torch.Tensor" + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" }, { "default": 6.0, @@ -61468,22 +61778,6 @@ "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "Vision latents encoded from the conditioning image or video.", - "kwargsType": null, - "name": "x0_tokens_vision", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Latent-frame indexes fixed by visual conditioning.", - "kwargsType": null, - "name": "vision_condition_frames", - "required": false, - "type": "list[builtins.int]" - }, { "default": null, "description": "Conditional text segment for the denoiser.", @@ -61604,54 +61898,6 @@ "required": false, "type": "builtins.int" }, - { - "default": null, - "description": "Noisy sound latents for denoising.", - "kwargsType": null, - "name": "sound_latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Frame rate of the sound latent sequence.", - "kwargsType": null, - "name": "fps_sound", - "required": false, - "type": "builtins.float" - }, - { - "default": null, - "description": "Mask marking conditioned sound latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "sound_condition_mask", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Scheduler used to update sound latents.", - "kwargsType": null, - "name": "sound_scheduler", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "Conditional sound segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "cond_sound_segment", - "required": false, - "type": "builtins.dict" - }, - { - "default": null, - "description": "Unconditional sound segment for the denoiser.", - "kwargsType": "denoiser_input_fields", - "name": "uncond_sound_segment", - "required": false, - "type": "builtins.dict" - }, { "default": null, "description": "Vision tokens for the transformer denoiser.", @@ -61668,22 +61914,6 @@ "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "Sound tokens for the transformer denoiser.", - "kwargsType": null, - "name": "sound_tokens", - "required": false, - "type": "list[torch.Tensor]" - }, - { - "default": null, - "description": "Timesteps for the sound tokens.", - "kwargsType": null, - "name": "sound_timesteps", - "required": false, - "type": "torch.Tensor" - }, { "default": null, "description": "Predicted velocity for vision latents.", @@ -61716,22 +61946,6 @@ "required": false, "type": "list[PIL.Image.Image]" }, - { - "default": null, - "description": "Generated waveform.", - "kwargsType": null, - "name": "sound", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Sample rate of the generated waveform in Hz.", - "kwargsType": null, - "name": "sampling_rate", - "required": false, - "type": "builtins.int" - }, { "default": null, "description": "Generated action vectors.", @@ -61743,12 +61957,8 @@ ], "requiredInputs": [ "cond_input_ids", - "cond_position_ids", - "cond_sequence_length", - "cond_sound_segment", "cond_text_segment", "cond_vision_segment", - "fps_sound", "fps_vision", "height", "latents", @@ -61756,16 +61966,10 @@ "num_inference_steps", "num_warmup_steps", "prompt", - "sound_latents", - "sound_scheduler", "timesteps", "uncond_input_ids", - "uncond_position_ids", - "uncond_sequence_length", - "uncond_sound_segment", "uncond_text_segment", "uncond_vision_segment", - "velocity_sound", "velocity_vision", "vision_condition_indexes_for_pack", "width" @@ -63096,31 +63300,513 @@ }, { "default": null, - "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", - "kwargsType": null, - "name": "dtype", + "description": "Data type of model tensor inputs (determined by `prompt_embeds`)", + "kwargsType": null, + "name": "dtype", + "required": false, + "type": "torch.dtype" + }, + { + "default": null, + "description": "image embeddings for IP-Adapter", + "kwargsType": "denoiser_input_fields", + "name": "ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "negative image embeddings for IP-Adapter", + "kwargsType": "denoiser_input_fields", + "name": "negative_ip_adapter_embeds", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "The timesteps to use for inference", + "kwargsType": null, + "name": "timesteps", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The number of denoising steps to perform at inference time", + "kwargsType": null, + "name": "num_inference_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "The timestep that represents the initial noise level for image-to-image generation", + "kwargsType": null, + "name": "latent_timestep", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The initial latents to use for the denoising process", + "kwargsType": null, + "name": "latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The time ids to condition the denoising process", + "kwargsType": "denoiser_input_fields", + "name": "add_time_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The negative time ids to condition the denoising process", + "kwargsType": "denoiser_input_fields", + "name": "negative_add_time_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The timestep cond to use for LCM", + "kwargsType": null, + "name": "timestep_cond", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The generated images, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "kwargsType": null, + "name": "images", + "required": false, + "type": "UnionType[list[PIL.Image.Image],list[numpy.array],list[torch.Tensor]]" + } + ], + "requiredInputs": [ + "batch_size", + "dtype", + "image", + "image_latents", + "latent_timestep", + "latents", + "num_inference_steps", + "pooled_prompt_embeds", + "prompt_embeds", + "timesteps" + ], + "schemaVersion": 1, + "variadicInputs": [ + { + "default": null, + "description": "All conditional model inputs that need to be prepared with guider. It should contain prompt_embeds/negative_prompt_embeds, add_time_ids/negative_add_time_ids, pooled_prompt_embeds/negative_pooled_prompt_embeds, and ip_adapter_embeds/negative_ip_adapter_embeds (optional).please add `kwargs_type=denoiser_input_fields` to their parameter spec (`OutputParam`) when they are created and added to the pipeline state", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] + }, + { + "className": "SequentialPipelineBlocks", + "components": [ + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "text_tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "resample": "bilinear", + "vae_scale_factor": 16 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + } + ], + "configs": [ + { + "default": true, + "description": "", + "name": "default_use_system_prompt" + }, + { + "default": true, + "description": "", + "name": "enable_safety_checker" + }, + { + "default": true, + "description": "", + "name": "is_distilled" + }, + { + "default": null, + "description": "", + "name": "distilled_sigmas" + } + ], + "contentHash": "sha256:f124523fc5af8da9254a7ff6fde52561be7da7fe252564adb6dddfdc05accfce", + "description": "", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:f124523fc5af8da9254a7ff6fde52561be7da7fe252564adb6dddfdc05accfce", + "inputs": [ + { + "default": null, + "description": "The text prompt that guides Cosmos3 generation.", + "kwargsType": null, + "name": "prompt", + "required": true, + "type": "builtins.str" + }, + { + "default": null, + "description": "Number of frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "Height of the generated video or image in pixels.", + "kwargsType": null, + "name": "height", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "Width of the generated video or image in pixels.", + "kwargsType": null, + "name": "width", + "required": true, + "type": "builtins.int" + }, + { + "default": 24.0, + "description": "Frame rate of the generated video.", + "kwargsType": null, + "name": "fps", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Whether to prepend the Cosmos3 system prompt.", + "kwargsType": null, + "name": "use_system_prompt", + "required": false, + "type": "UnionType[builtins.NoneType,builtins.bool]" + }, + { + "default": true, + "description": "Whether to add resolution metadata to the prompt.", + "kwargsType": null, + "name": "add_resolution_template", + "required": false, + "type": "builtins.bool" + }, + { + "default": true, + "description": "Whether to add duration metadata to the prompt.", + "kwargsType": null, + "name": "add_duration_template", + "required": false, + "type": "builtins.bool" + }, + { + "default": null, + "description": "Reference video for video-to-video conditioning.", + "kwargsType": null, + "name": "video", + "required": false, + "type": "opaque" + }, + { + "default": [ + 0, + 1 + ], + "description": "Latent-frame indexes to preserve from the conditioning video.", + "kwargsType": null, + "name": "condition_frame_indexes_vision", + "required": false, + "type": "UnionType[list[builtins.int],tuple[Ellipsis,builtins.int]]" + }, + { + "default": "first", + "description": "Which end of a longer conditioning video to use: `first` or `last`.", + "kwargsType": null, + "name": "condition_video_keep", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Pre-generated noisy vision latents.", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Torch generator for deterministic generation.", + "kwargsType": null, + "name": "generator", + "required": false, + "type": "torch._C.Generator" + }, + { + "default": null, + "description": "The number of denoising steps.", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "Unused for distilled checkpoints; classifier-free guidance is baked into the weights and the scale is forced to 1.0. Passing a value other than 1.0 raises an error.", + "kwargsType": null, + "name": "guidance_scale", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, + { + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", + "kwargsType": null, + "name": "output_type", + "required": false, + "type": "builtins.str" + } + ], + "kind": "sequential", + "outputs": [ + { + "default": null, + "description": "Number of frames to generate.", + "kwargsType": null, + "name": "num_frames", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Height of the generated video or image in pixels.", + "kwargsType": null, + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Width of the generated video or image in pixels.", + "kwargsType": null, + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Token IDs for the conditional prompt.", + "kwargsType": null, + "name": "cond_input_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Token IDs for the unconditional prompt (empty prompt; guidance is baked in).", + "kwargsType": null, + "name": "uncond_input_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Vision latents encoded from the conditioning image or video.", + "kwargsType": null, + "name": "x0_tokens_vision", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Latent-frame indexes fixed by visual conditioning.", + "kwargsType": null, + "name": "vision_condition_frames", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "Conditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_text_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_text_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Noisy vision latents for denoising.", + "kwargsType": null, + "name": "latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Frame rate used to pack vision latents.", + "kwargsType": null, + "name": "fps_vision", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Mask marking conditioned vision latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "vision_condition_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Indexes of conditioned vision latent frames.", + "kwargsType": null, + "name": "vision_condition_indexes_for_pack", + "required": false, + "type": "list[builtins.int]" + }, + { + "default": null, + "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", + "kwargsType": null, + "name": "vision_conditioning_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Conditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Conditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "cond_position_ids", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Unconditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_position_ids", "required": false, - "type": "torch.dtype" + "type": "torch.Tensor" }, { "default": null, - "description": "image embeddings for IP-Adapter", + "description": "Conditional multimodal sequence length.", "kwargsType": "denoiser_input_fields", - "name": "ip_adapter_embeds", + "name": "cond_sequence_length", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.int" }, { "default": null, - "description": "negative image embeddings for IP-Adapter", + "description": "Unconditional multimodal sequence length.", "kwargsType": "denoiser_input_fields", - "name": "negative_ip_adapter_embeds", + "name": "uncond_sequence_length", "required": false, - "type": "list[torch.Tensor]" + "type": "builtins.int" }, { "default": null, - "description": "The timesteps to use for inference", + "description": "Scheduler timesteps for denoising.", "kwargsType": null, "name": "timesteps", "required": false, @@ -63128,7 +63814,15 @@ }, { "default": null, - "description": "The number of denoising steps to perform at inference time", + "description": "Number of scheduler warmup steps.", + "kwargsType": null, + "name": "num_warmup_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Resolved number of denoising steps (fixed by the distilled schedule).", "kwargsType": null, "name": "num_inference_steps", "required": false, @@ -63136,70 +63830,86 @@ }, { "default": null, - "description": "The timestep that represents the initial noise level for image-to-image generation", + "description": "Resolved classifier-free guidance scale (always 1.0 for distilled checkpoints).", "kwargsType": null, - "name": "latent_timestep", + "name": "guidance_scale", "required": false, - "type": "torch.Tensor" + "type": "builtins.float" }, { "default": null, - "description": "The initial latents to use for the denoising process", + "description": "Vision tokens for the transformer denoiser.", "kwargsType": null, - "name": "latents", + "name": "vision_tokens", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Timesteps for the vision tokens.", + "kwargsType": null, + "name": "vision_timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The time ids to condition the denoising process", - "kwargsType": "denoiser_input_fields", - "name": "add_time_ids", + "description": "Predicted velocity for vision latents.", + "kwargsType": null, + "name": "velocity_vision", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The negative time ids to condition the denoising process", - "kwargsType": "denoiser_input_fields", - "name": "negative_add_time_ids", + "description": "Predicted velocity for sound latents.", + "kwargsType": null, + "name": "velocity_sound", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The timestep cond to use for LCM", + "description": "Predicted velocity for action latents.", "kwargsType": null, - "name": "timestep_cond", + "name": "velocity_action", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The generated images, can be a PIL.Image.Image, torch.Tensor or a numpy array", + "description": "The generated videos.", "kwargsType": null, - "name": "images", + "name": "videos", "required": false, - "type": "UnionType[list[PIL.Image.Image],list[numpy.array],list[torch.Tensor]]" + "type": "list[PIL.Image.Image]" } ], "requiredInputs": [ - "batch_size", - "dtype", - "image", - "image_latents", - "latent_timestep", + "cond_input_ids", + "cond_text_segment", + "cond_vision_segment", + "fps_vision", + "height", "latents", + "num_frames", "num_inference_steps", - "pooled_prompt_embeds", - "prompt_embeds", - "timesteps" + "num_warmup_steps", + "prompt", + "timesteps", + "uncond_input_ids", + "uncond_text_segment", + "uncond_vision_segment", + "velocity_vision", + "vision_condition_indexes_for_pack", + "vision_condition_mask", + "width" ], "schemaVersion": 1, "variadicInputs": [ { "default": null, - "description": "All conditional model inputs that need to be prepared with guider. It should contain prompt_embeds/negative_prompt_embeds, add_time_ids/negative_add_time_ids, pooled_prompt_embeds/negative_pooled_prompt_embeds, and ip_adapter_embeds/negative_ip_adapter_embeds (optional).please add `kwargs_type=denoiser_input_fields` to their parameter spec (`OutputParam`) when they are created and added to the pipeline state", + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", "kwargsType": "denoiser_input_fields", "required": false, "type": "opaque" @@ -63230,13 +63940,6 @@ "name": "connectors", "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "duration_head", - "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" - }, { "creationMethod": "from_pretrained", "defaultConfig": null, @@ -63251,6 +63954,16 @@ "name": "transformer", "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" }, + { + "creationMethod": "from_config", + "defaultConfig": { + "resample": "bilinear", + "vae_scale_factor": 32 + }, + "description": "", + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" + }, { "creationMethod": "from_pretrained", "defaultConfig": null, @@ -63278,7 +63991,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -63290,16 +64003,7 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 32 - }, - "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_pretrained", @@ -63310,9 +64014,9 @@ } ], "configs": [], - "contentHash": "sha256:e5f246fb36f9b1e22684c5704aa05831f81fae0e3ff822315c6718aa912abd26", + "contentHash": "sha256:f6facbf653b1ea75a936d62c872901e8a38d8c3faa789ba2d75c3d101c4a8904", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e5f246fb36f9b1e22684c5704aa05831f81fae0e3ff822315c6718aa912abd26", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:f6facbf653b1ea75a936d62c872901e8a38d8c3faa789ba2d75c3d101c4a8904", "inputs": [ { "default": null, @@ -63338,30 +64042,6 @@ "required": false, "type": "builtins.int" }, - { - "default": 1.0, - "description": "Lower bound on the auto-predicted duration.", - "kwargsType": null, - "name": "min_seconds", - "required": false, - "type": "builtins.float" - }, - { - "default": 20.0, - "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", - "kwargsType": null, - "name": "max_seconds", - "required": false, - "type": "builtins.float" - }, - { - "default": 24.0, - "description": "Frames per second of the generated video.", - "kwargsType": null, - "name": "frame_rate", - "required": false, - "type": "builtins.float" - }, { "default": null, "description": "`LTX2VideoCondition` (or list of them) placing image/video conditions at latent frame indices of the generated video.", @@ -63386,6 +64066,14 @@ "required": false, "type": "builtins.int" }, + { + "default": null, + "description": "The number of frames in the generated video. Omit to auto-predict via the `duration_head` (see `LTX2AutoDurationStep`).", + "kwargsType": null, + "name": "num_frames", + "required": false, + "type": "builtins.int" + }, { "default": null, "description": "Torch generator for deterministic generation.", @@ -63394,6 +64082,46 @@ "required": false, "type": "torch._C.Generator" }, + { + "default": null, + "description": "`LTX2ReferenceCondition` (or list of them) whose videos are encoded into extra latent tokens the IC-LoRA adapter attends to.", + "kwargsType": null, + "name": "reference_conditions", + "required": true, + "type": "builtins.list" + }, + { + "default": 1, + "description": "Ratio between the target and reference resolutions; 2 means the reference is preprocessed at half the target resolution. Spatial coordinates are scaled by this factor so the reference tokens land in the target coordinate space. Must match the factor the IC-LoRA was trained with.", + "kwargsType": null, + "name": "reference_downscale_factor", + "required": false, + "type": "builtins.int" + }, + { + "default": 1.0, + "description": "Scalar in [0, 1] controlling how strongly the noisy tokens and reference tokens attend to each other. 1.0 (default) leaves attention unmasked.", + "kwargsType": null, + "name": "conditioning_attention_strength", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Optional pixel-space mask of shape (1, 1, F, H, W) with values in [0, 1] giving spatially varying attention strength. Downsampled to the reference's latent grid and multiplied by `conditioning_attention_strength`.", + "kwargsType": null, + "name": "conditioning_attention_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": 24.0, + "description": "Frames per second of the generated video.", + "kwargsType": null, + "name": "frame_rate", + "required": false, + "type": "builtins.float" + }, { "default": 1, "description": "The number of images to generate per prompt.", @@ -63589,14 +64317,6 @@ "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "The predicted number of frames to generate.", - "kwargsType": null, - "name": "num_frames", - "required": false, - "type": "builtins.int" - }, { "default": null, "description": "Per-condition normalized VAE latents of shape [1, C, F, H, W].", @@ -63631,7 +64351,39 @@ }, { "default": null, - "description": "Packed noisy video latents, with any keyframe condition tokens appended.", + "description": "Packed reference tokens of shape [1, total_reference_tokens, C].", + "kwargsType": null, + "name": "reference_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "RoPE coordinates for the reference tokens, of shape [1, 3, total_reference_tokens, 2].", + "kwargsType": null, + "name": "reference_coords", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Per-reference token counts, in `reference_conditions` order.", + "kwargsType": null, + "name": "reference_token_counts", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "Per-reference-token noisy<->reference attention strengths of shape [1, total_reference_tokens], or `None` when attention is left unmasked.", + "kwargsType": null, + "name": "reference_cross_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Packed noisy video latents, with keyframe and reference tokens appended.", "kwargsType": null, "name": "latents", "required": false, @@ -63655,7 +64407,7 @@ }, { "default": null, - "description": "RoPE coordinates of shape [B, 3, num_keyframe_tokens, 2] for the appended keyframe tokens, zero-width when there are none.", + "description": "RoPE coordinates of shape [B, 3, num_keyframe_tokens + num_reference_tokens, 2] for the appended tokens, zero-width when there are none.", "kwargsType": null, "name": "appended_coords", "required": false, @@ -63669,6 +64421,14 @@ "required": false, "type": "builtins.int" }, + { + "default": null, + "description": "Number of reference tokens, which sit at the very end of the sequence.", + "kwargsType": null, + "name": "num_ref_tokens", + "required": false, + "type": "builtins.int" + }, { "default": null, "description": "The resolved initial noise level, forwarded to the audio latents step.", @@ -63777,6 +64537,7 @@ "prompt", "prompt_attention_mask", "prompt_embeds", + "reference_conditions", "timesteps" ], "schemaVersion": 1, @@ -63785,270 +64546,10 @@ "default": null, "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", "kwargsType": "denoiser_input_fields", - "required": false, - "type": "opaque" - } - ] - }, - { - "className": "SequentialPipelineBlocks", - "components": [ - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "text_encoder", - "type": "transformers.models.umt5.modeling_umt5.UMT5EncoderModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", - "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 5.0 - }, - "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 3.0 - }, - "description": "", - "name": "guider_2", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer_2", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 8 - }, - "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" - } - ], - "configs": [ - { - "default": 0.875, - "description": "The boundary ratio to divide the denoising loop into high noise and low noise stages.", - "name": "boundary_ratio" - } - ], - "contentHash": "sha256:ed1072cef3523a3b6f133d4d620e119f7b20899959d3cb31d62b711c7bb78c53", - "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:ed1072cef3523a3b6f133d4d620e119f7b20899959d3cb31d62b711c7bb78c53", - "inputs": [ - { - "default": null, - "description": "", - "kwargsType": null, - "name": "prompt", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "negative_prompt", - "required": false, - "type": "opaque" - }, - { - "default": 512, - "description": "", - "kwargsType": null, - "name": "max_sequence_length", - "required": false, - "type": "opaque" - }, - { - "default": 1, - "description": "", - "kwargsType": null, - "name": "num_videos_per_prompt", - "required": false, - "type": "opaque" - }, - { - "default": 50, - "description": "", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "sigmas", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "width", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "num_frames", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "UnionType[builtins.NoneType,torch.Tensor]" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "generator", - "required": false, - "type": "opaque" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "attention_kwargs", - "required": false, - "type": "opaque" - }, - { - "default": "np", - "description": "The output type of the decoded videos", - "kwargsType": null, - "name": "output_type", - "required": false, - "type": "builtins.str" - } - ], - "kind": "sequential", - "outputs": [ - { - "default": null, - "description": "text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "negative text embeddings used to guide the image generation", - "kwargsType": "denoiser_input_fields", - "name": "negative_prompt_embeds", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "Number of prompts, the final batch size of model inputs should be batch_size * num_videos_per_prompt", - "kwargsType": null, - "name": "batch_size", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "Data type of model tensor inputs (determined by `transformer.dtype`)", - "kwargsType": null, - "name": "dtype", - "required": false, - "type": "torch.dtype" - }, - { - "default": null, - "description": "The initial latents to use for the denoising process", - "kwargsType": null, - "name": "latents", - "required": false, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The generated videos, can be a PIL.Image.Image, torch.Tensor or a numpy array", - "kwargsType": null, - "name": "videos", - "required": false, - "type": "UnionType[list[list[PIL.Image.Image]],list[numpy.ndarray],list[torch.Tensor]]" - } - ], - "requiredInputs": [ - "batch_size", - "dtype", - "latents", - "negative_prompt_embeds", - "num_inference_steps", - "prompt_embeds", - "timesteps" - ], - "schemaVersion": 1, - "variadicInputs": [] + "required": false, + "type": "opaque" + } + ] }, { "className": "SequentialPipelineBlocks", @@ -64057,110 +64558,72 @@ "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "text_encoder", - "type": "transformers.modeling_utils.PreTrainedModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "tokenizer", - "type": "transformers.tokenization_utils_base.PreTrainedTokenizerBase" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "connectors", - "type": "diffusers.pipelines.ltx2.connectors.LTX2TextConnectors" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "duration_head", - "type": "diffusers.pipelines.ltx2.duration_head.LTX2DurationHead" + "name": "text_tokenizer", + "type": "transformers.models.auto.tokenization_auto.AutoTokenizer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "name": "vae", + "type": "diffusers.models.autoencoders.autoencoder_kl_wan.AutoencoderKLWan" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", "name": "transformer", - "type": "diffusers.models.transformers.transformer_ltx2.LTX2VideoTransformer3DModel" + "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "audio_vae", - "type": "diffusers.models.autoencoders.autoencoder_kl_ltx2_audio.AutoencoderKLLTX2Audio" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 3.0, - "modality_scale": 3.0, - "spatio_temporal_guidance_blocks": [ - 28 - ], - "stg_scale": 1.0 - }, - "description": "", - "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" }, { "creationMethod": "from_config", "defaultConfig": { - "guidance_rescale": 0.7, - "guidance_scale": 7.0, - "modality_scale": 3.0, - "stg_scale": 1.0 + "resample": "bilinear", + "vae_scale_factor": 16 }, "description": "", - "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "name": "video_processor", + "type": "diffusers.video_processor.VideoProcessor" }, { "creationMethod": "from_pretrained", "defaultConfig": null, "description": "", - "name": "diffusion_decoder", - "type": "diffusers.models.autoencoders.ltx2_diffusion_decoder.LTX2VideoDiffusionDecoderModel" + "name": "sound_tokenizer", + "type": "diffusers.models.autoencoders.autoencoder_cosmos3_audio.Cosmos3AVAEAudioTokenizer" + } + ], + "configs": [ + { + "default": true, + "description": "", + "name": "default_use_system_prompt" }, { - "creationMethod": "from_config", - "defaultConfig": { - "vae_scale_factor": 32 - }, + "default": true, "description": "", - "name": "video_processor", - "type": "diffusers.video_processor.VideoProcessor" + "name": "enable_safety_checker" }, { - "creationMethod": "from_pretrained", - "defaultConfig": null, + "default": false, "description": "", - "name": "vocoder", - "type": "diffusers.pipelines.ltx2.vocoder.LTX2Vocoder" + "name": "use_native_flow_schedule" } ], - "configs": [], - "contentHash": "sha256:f5bdfb3cdad9dc2d985676ed4378dcf565be4e6267a4782134e3f12adbfe138d", + "contentHash": "sha256:f973f03d4d8c8a5ca2e43da9286622a532cc6fc3656f2a4b05c72826df1f9a5f", "description": "", - "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:f5bdfb3cdad9dc2d985676ed4378dcf565be4e6267a4782134e3f12adbfe138d", + "id": "diffusers.modular-block:SequentialPipelineBlocks:sha256:f973f03d4d8c8a5ca2e43da9286622a532cc6fc3656f2a4b05c72826df1f9a5f", "inputs": [ { "default": null, - "description": "The prompt or prompts to guide image generation.", + "description": "The text prompt that guides Cosmos3 generation.", "kwargsType": null, "name": "prompt", "required": true, @@ -64168,54 +64631,94 @@ }, { "default": null, - "description": "The prompt or prompts not to guide the image generation.", + "description": "The negative text prompt used for classifier-free guidance.", "kwargsType": null, "name": "negative_prompt", "required": false, "type": "builtins.str" }, { - "default": 1024, - "description": "Maximum sequence length for prompt encoding.", + "default": null, + "description": "Number of frames to generate.", "kwargsType": null, - "name": "max_sequence_length", - "required": false, + "name": "num_frames", + "required": true, "type": "builtins.int" }, { - "default": 1.0, - "description": "Lower bound on the auto-predicted duration.", + "default": null, + "description": "Height of the generated video or image in pixels.", "kwargsType": null, - "name": "min_seconds", + "name": "height", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "Width of the generated video or image in pixels.", + "kwargsType": null, + "name": "width", + "required": true, + "type": "builtins.int" + }, + { + "default": 24.0, + "description": "Frame rate of the generated video.", + "kwargsType": null, + "name": "fps", "required": false, "type": "builtins.float" }, { - "default": 20.0, - "description": "Upper bound on the auto-predicted duration. Must be strictly greater than `min_seconds`.", + "default": null, + "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, - "name": "max_seconds", + "name": "use_system_prompt", "required": false, - "type": "builtins.float" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { - "default": 24.0, - "description": "Frames per second of the generated video.", + "default": true, + "description": "Whether to add resolution metadata to the prompt.", "kwargsType": null, - "name": "frame_rate", + "name": "add_resolution_template", "required": false, - "type": "builtins.float" + "type": "builtins.bool" }, { - "default": 1, - "description": "The number of images to generate per prompt.", + "default": true, + "description": "Whether to add duration metadata to the prompt.", "kwargsType": null, - "name": "num_videos_per_prompt", + "name": "add_duration_template", "required": false, - "type": "builtins.int" + "type": "builtins.bool" }, { - "default": 30, + "default": null, + "description": "Reference image for image-to-video conditioning.", + "kwargsType": null, + "name": "image", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "Pre-generated noisy vision latents.", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Torch generator for deterministic generation.", + "kwargsType": null, + "name": "generator", + "required": false, + "type": "torch._C.Generator" + }, + { + "default": 50, "description": "The number of denoising steps.", "kwargsType": null, "name": "num_inference_steps", @@ -64224,202 +64727,250 @@ }, { "default": null, - "description": "Timesteps for the denoising process.", + "description": "Pre-generated noisy sound latents.", "kwargsType": null, - "name": "timesteps", + "name": "sound_latents", "required": true, "type": "torch.Tensor" }, { "default": null, - "description": "Custom sigmas for the denoising process.", + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", "kwargsType": null, - "name": "sigmas", + "name": "mixed_precision_format", "required": false, - "type": "list[builtins.float]" + "type": "builtins.str" }, { - "default": 512, - "description": "The height in pixels of the generated image.", + "default": null, + "description": "Optional leading W8A16 step count.", "kwargsType": null, - "name": "height", + "name": "mixed_precision_first_steps", "required": false, "type": "builtins.int" }, { - "default": 704, - "description": "The width in pixels of the generated image.", + "default": null, + "description": "Optional trailing W8A16 step count.", "kwargsType": null, - "name": "width", + "name": "mixed_precision_last_steps", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Pre-generated noisy latents for image generation.", + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" }, { - "default": null, - "description": "Interpolation factor between random noise and any provided latents. `None` (default) resolves to 0.0, which keeps the provided latents.", + "default": 6.0, + "description": "Scale for classifier-free guidance.", "kwargsType": null, - "name": "noise_scale", + "name": "guidance_scale", "required": false, "type": "builtins.float" }, { - "default": null, - "description": "Torch generator for deterministic generation.", + "default": "pil", + "description": "Output format: 'pil', 'np', 'pt'.", "kwargsType": null, - "name": "generator", + "name": "output_type", "required": false, - "type": "torch._C.Generator" + "type": "builtins.str" }, { "default": null, - "description": "Optional pre-encoded audio latents; random noise is used when not provided.", - "kwargsType": null, - "name": "audio_latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": true, - "description": "Whether to condition the transformer on a separate per-token cross timestep (LTX-2.3+).", + "description": "Denoised action latents.", "kwargsType": null, - "name": "use_cross_timestep", + "name": "action_latents", "required": false, - "type": "builtins.bool" + "type": "torch.Tensor" }, { "default": null, - "description": "Additional kwargs for attention processors.", + "description": "Requested action-generation mode.", "kwargsType": null, - "name": "attention_kwargs", + "name": "action_mode", "required": false, - "type": "dict[builtins.str,typing.Any]" + "type": "builtins.str" }, { - "default": "pil", - "description": "Output format: 'pil', 'np', 'pt'.", + "default": null, + "description": "Unpadded action-vector dimension.", "kwargsType": null, - "name": "output_type", + "name": "raw_action_dim_resolved", "required": false, - "type": "builtins.str" + "type": "builtins.int" } ], "kind": "sequential", "outputs": [ { "default": null, - "description": "Packed per-layer Gemma hidden states for the prompt.", + "description": "Number of frames to generate.", "kwargsType": null, - "name": "prompt_embeds", + "name": "num_frames", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Binary attention mask for `prompt_embeds`.", + "description": "Height of the generated video or image in pixels.", "kwargsType": null, - "name": "prompt_attention_mask", + "name": "height", "required": false, - "type": "torch.Tensor" + "type": "builtins.int" }, { "default": null, - "description": "Packed per-layer Gemma hidden states for the negative prompt.", + "description": "Width of the generated video or image in pixels.", "kwargsType": null, - "name": "negative_prompt_embeds", + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Token IDs for the conditional prompt.", + "kwargsType": null, + "name": "cond_input_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Binary attention mask for `negative_prompt_embeds`.", + "description": "Token IDs for the unconditional prompt.", "kwargsType": null, - "name": "negative_prompt_attention_mask", + "name": "uncond_input_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The number of prompts being denoised (before per-prompt expansion).", + "description": "Vision latents encoded from the conditioning image or video.", "kwargsType": null, - "name": "batch_size", + "name": "x0_tokens_vision", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "The dtype of the prompt embeddings.", + "description": "Latent-frame indexes fixed by visual conditioning.", "kwargsType": null, - "name": "dtype", + "name": "vision_condition_frames", "required": false, - "type": "torch.dtype" + "type": "list[builtins.int]" }, { "default": null, - "description": "Video-branch text conditioning (cond).", + "description": "Conditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_text_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional text segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_text_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Noisy vision latents for denoising.", "kwargsType": null, - "name": "connector_prompt_embeds", + "name": "latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio-branch text conditioning (cond).", + "description": "Frame rate used to pack vision latents.", "kwargsType": null, - "name": "connector_audio_prompt_embeds", + "name": "fps_vision", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Mask marking conditioned vision latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "vision_condition_mask", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Binary text attention mask (cond).", + "description": "Indexes of conditioned vision latent frames.", "kwargsType": null, - "name": "connector_attention_mask", + "name": "vision_condition_indexes_for_pack", "required": false, - "type": "torch.Tensor" + "type": "list[builtins.int]" }, { "default": null, - "description": "Video-branch text conditioning (uncond).", + "description": "Clean encoded vision latents used to re-anchor image conditioning each step.", "kwargsType": null, - "name": "negative_connector_prompt_embeds", + "name": "vision_conditioning_latents", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio-branch text conditioning (uncond).", - "kwargsType": null, - "name": "negative_connector_audio_prompt_embeds", + "description": "Conditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional vision segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_vision_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Conditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "cond_position_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Binary text attention mask (uncond).", - "kwargsType": null, - "name": "negative_connector_attention_mask", + "description": "Unconditional multimodal RoPE position IDs.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_position_ids", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The predicted number of frames to generate.", - "kwargsType": null, - "name": "num_frames", + "description": "Conditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "cond_sequence_length", "required": false, "type": "builtins.int" }, { "default": null, - "description": "", + "description": "Unconditional multimodal sequence length.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_sequence_length", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Scheduler timesteps for denoising.", "kwargsType": null, "name": "timesteps", "required": false, @@ -64427,65 +64978,113 @@ }, { "default": null, - "description": "", + "description": "Number of scheduler warmup steps.", "kwargsType": null, - "name": "num_inference_steps", + "name": "num_warmup_steps", "required": false, "type": "builtins.int" }, { "default": null, - "description": "Independent deep copy of `scheduler` used to update the audio latents in the loop.", + "description": "Noisy sound latents for denoising.", "kwargsType": null, - "name": "audio_scheduler", + "name": "sound_latents", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Frame rate of the sound latent sequence.", + "kwargsType": null, + "name": "fps_sound", + "required": false, + "type": "builtins.float" + }, + { + "default": null, + "description": "Mask marking conditioned sound latent frames.", + "kwargsType": "denoiser_input_fields", + "name": "sound_condition_mask", + "required": false, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "Scheduler used to update sound latents.", + "kwargsType": null, + "name": "sound_scheduler", "required": false, "type": "opaque" }, { "default": null, - "description": "Packed noisy video latents.", + "description": "Conditional sound segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "cond_sound_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Unconditional sound segment for the denoiser.", + "kwargsType": "denoiser_input_fields", + "name": "uncond_sound_segment", + "required": false, + "type": "builtins.dict" + }, + { + "default": null, + "description": "Vision tokens for the transformer denoiser.", "kwargsType": null, - "name": "latents", + "name": "vision_tokens", + "required": false, + "type": "list[torch.Tensor]" + }, + { + "default": null, + "description": "Timesteps for the vision tokens.", + "kwargsType": null, + "name": "vision_timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "The resolved interpolation factor, forwarded to the audio latents step.", + "description": "Sound tokens for the transformer denoiser.", "kwargsType": null, - "name": "noise_scale", + "name": "sound_tokens", "required": false, - "type": "builtins.float" + "type": "list[torch.Tensor]" }, { "default": null, - "description": "Packed noisy audio latents.", + "description": "Timesteps for the sound tokens.", "kwargsType": null, - "name": "audio_latents", + "name": "sound_timesteps", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Number of audio latent frames.", - "kwargsType": "denoiser_input_fields", - "name": "audio_num_frames", + "description": "Predicted velocity for vision latents.", + "kwargsType": null, + "name": "velocity_vision", "required": false, - "type": "builtins.int" + "type": "torch.Tensor" }, { "default": null, - "description": "Video RoPE patch coordinates.", - "kwargsType": "denoiser_input_fields", - "name": "video_coords", + "description": "Predicted velocity for sound latents.", + "kwargsType": null, + "name": "velocity_sound", "required": false, "type": "torch.Tensor" }, { "default": null, - "description": "Audio RoPE patch coordinates.", - "kwargsType": "denoiser_input_fields", - "name": "audio_coords", + "description": "Predicted velocity for action latents.", + "kwargsType": null, + "name": "velocity_action", "required": false, "type": "torch.Tensor" }, @@ -64499,33 +65098,57 @@ }, { "default": null, - "description": "The generated audio waveform.", + "description": "Generated waveform.", "kwargsType": null, - "name": "audio", + "name": "sound", "required": false, "type": "torch.Tensor" + }, + { + "default": null, + "description": "Sample rate of the generated waveform in Hz.", + "kwargsType": null, + "name": "sampling_rate", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Generated action vectors.", + "kwargsType": null, + "name": "action", + "required": false, + "type": "list[torch.Tensor]" } ], "requiredInputs": [ - "audio_latents", - "audio_num_frames", - "audio_scheduler", - "batch_size", - "connector_attention_mask", - "connector_audio_prompt_embeds", - "connector_prompt_embeds", - "dtype", + "cond_input_ids", + "cond_position_ids", + "cond_sequence_length", + "cond_sound_segment", + "cond_text_segment", + "cond_vision_segment", + "fps_sound", + "fps_vision", + "height", "latents", - "negative_connector_attention_mask", - "negative_connector_audio_prompt_embeds", - "negative_connector_prompt_embeds", - "negative_prompt_attention_mask", - "negative_prompt_embeds", + "num_frames", "num_inference_steps", + "num_warmup_steps", "prompt", - "prompt_attention_mask", - "prompt_embeds", - "timesteps" + "sound_latents", + "sound_scheduler", + "timesteps", + "uncond_input_ids", + "uncond_position_ids", + "uncond_sequence_length", + "uncond_sound_segment", + "uncond_text_segment", + "uncond_vision_segment", + "velocity_sound", + "velocity_vision", + "vision_condition_indexes_for_pack", + "width" ], "schemaVersion": 1, "variadicInputs": [ @@ -68884,13 +69507,279 @@ "description": "", "name": "transformer_2", "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + } + ], + "configs": [ + { + "default": 0.875, + "description": "The boundary ratio to divide the denoising loop into high noise and low noise stages.", + "name": "boundary_ratio" + } + ], + "contentHash": "sha256:217a54b74465756c8b2237e6903c0d035e80f340b4635ecfcbb7686aa65089cb", + "description": "Denoise step that iteratively denoise the latents. Its loop logic is defined in `WanDenoiseLoopWrapper.__call__` method At each iteration, it runs blocks defined in `sub_blocks` sequentially: - `WanLoopBeforeDenoiser` - `Wan22LoopDenoiser` - `WanLoopAfterDenoiser` This block supports text-to-video tasks for Wan2.2.", + "id": "diffusers.modular-block:Wan22DenoiseStep:sha256:217a54b74465756c8b2237e6903c0d035e80f340b4635ecfcbb7686aa65089cb", + "inputs": [ + { + "default": null, + "description": "The timesteps to use for the denoising process. Can be generated in set_timesteps step.", + "kwargsType": null, + "name": "timesteps", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The number of inference steps to use for the denoising process. Can be generated in set_timesteps step.", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "The initial latents to use for the denoising process. Can be generated in prepare_latent step.", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The dtype of the model inputs. Can be generated in input step.", + "kwargsType": null, + "name": "dtype", + "required": true, + "type": "torch.dtype" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "attention_kwargs", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "prompt_embeds", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "negative_prompt_embeds", + "required": true, + "type": "torch.Tensor" + } + ], + "kind": "loop", + "outputs": [], + "requiredInputs": [ + "dtype", + "latents", + "negative_prompt_embeds", + "num_inference_steps", + "prompt_embeds", + "timesteps" + ], + "schemaVersion": 1, + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] + }, + { + "className": "Wan22Image2VideoDenoiseStep", + "components": [ + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 4.0 + }, + "description": "", + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 3.0 + }, + "description": "", + "name": "guider_2", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer_2", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" + } + ], + "configs": [ + { + "default": 0.875, + "description": "The boundary ratio to divide the denoising loop into high noise and low noise stages.", + "name": "boundary_ratio" + } + ], + "contentHash": "sha256:c51d77edbdec4084a4542e734a31d1eedace36aeee6c048de29bfcd5351c942a", + "description": "Denoise step that iteratively denoise the latents. Its loop logic is defined in `WanDenoiseLoopWrapper.__call__` method At each iteration, it runs blocks defined in `sub_blocks` sequentially: - `WanImage2VideoLoopBeforeDenoiser` - `WanLoopDenoiser` - `WanLoopAfterDenoiser` This block supports image-to-video tasks for Wan2.2.", + "id": "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:c51d77edbdec4084a4542e734a31d1eedace36aeee6c048de29bfcd5351c942a", + "inputs": [ + { + "default": null, + "description": "The timesteps to use for the denoising process. Can be generated in set_timesteps step.", + "kwargsType": null, + "name": "timesteps", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The number of inference steps to use for the denoising process. Can be generated in set_timesteps step.", + "kwargsType": null, + "name": "num_inference_steps", + "required": true, + "type": "builtins.int" + }, + { + "default": null, + "description": "The initial latents to use for the denoising process. Can be generated in prepare_latent step.", + "kwargsType": null, + "name": "latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The image condition latents to use for the denoising process. Can be generated in prepare_first_frame_latents/prepare_first_last_frame_latents step.", + "kwargsType": null, + "name": "image_condition_latents", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "The dtype of the model inputs. Can be generated in input step.", + "kwargsType": null, + "name": "dtype", + "required": true, + "type": "torch.dtype" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "attention_kwargs", + "required": false, + "type": "opaque" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "prompt_embeds", + "required": true, + "type": "torch.Tensor" + }, + { + "default": null, + "description": "", + "kwargsType": null, + "name": "negative_prompt_embeds", + "required": true, + "type": "torch.Tensor" + } + ], + "kind": "loop", + "outputs": [], + "requiredInputs": [ + "dtype", + "image_condition_latents", + "latents", + "negative_prompt_embeds", + "num_inference_steps", + "prompt_embeds", + "timesteps" + ], + "schemaVersion": 1, + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] + }, + { + "className": "Wan22LoopDenoiser", + "components": [ + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 4.0 + }, + "description": "", + "name": "guider", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, + { + "creationMethod": "from_config", + "defaultConfig": { + "guidance_scale": 3.0 + }, + "description": "", + "name": "guider_2", + "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "transformer_2", + "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" } ], "configs": [ @@ -68900,42 +69789,10 @@ "name": "boundary_ratio" } ], - "contentHash": "sha256:74bb7f99087db12b178bb99d192ed7209986b8d27817263d0169cb0efb058b9d", - "description": "Denoise step that iteratively denoise the latents. Its loop logic is defined in `WanDenoiseLoopWrapper.__call__` method At each iteration, it runs blocks defined in `sub_blocks` sequentially: - `WanLoopBeforeDenoiser` - `Wan22LoopDenoiser` - `WanLoopAfterDenoiser` This block supports text-to-video tasks for Wan2.2.", - "id": "diffusers.modular-block:Wan22DenoiseStep:sha256:74bb7f99087db12b178bb99d192ed7209986b8d27817263d0169cb0efb058b9d", + "contentHash": "sha256:194524add4567c94a2654a8d4ac2a0e4d67ccede3063e67e296a6a3ef54c90b3", + "description": "Step within the denoising loop that denoise the latents with guidance. This block should be used to compose the `sub_blocks` attribute of a `LoopSequentialPipelineBlocks` object (e.g. `WanDenoiseLoopWrapper`)", + "id": "diffusers.modular-block:Wan22LoopDenoiser:sha256:194524add4567c94a2654a8d4ac2a0e4d67ccede3063e67e296a6a3ef54c90b3", "inputs": [ - { - "default": null, - "description": "The timesteps to use for the denoising process. Can be generated in set_timesteps step.", - "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The number of inference steps to use for the denoising process. Can be generated in set_timesteps step.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "The initial latents to use for the denoising process. Can be generated in prepare_latent step.", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The dtype of the model inputs. Can be generated in input step.", - "kwargsType": null, - "name": "dtype", - "required": true, - "type": "torch.dtype" - }, { "default": null, "description": "", @@ -68944,98 +69801,6 @@ "required": false, "type": "opaque" }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "prompt_embeds", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "negative_prompt_embeds", - "required": true, - "type": "torch.Tensor" - } - ], - "kind": "loop", - "outputs": [], - "requiredInputs": [ - "dtype", - "latents", - "negative_prompt_embeds", - "num_inference_steps", - "prompt_embeds", - "timesteps" - ], - "schemaVersion": 1, - "variadicInputs": [] - }, - { - "className": "Wan22Image2VideoDenoiseStep", - "components": [ - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 4.0 - }, - "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 3.0 - }, - "description": "", - "name": "guider_2", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer_2", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "scheduler", - "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" - } - ], - "configs": [ - { - "default": 0.875, - "description": "The boundary ratio to divide the denoising loop into high noise and low noise stages.", - "name": "boundary_ratio" - } - ], - "contentHash": "sha256:9daec42f0095aefde5606e02f4873451109fede560e75212310e9885dc1b15db", - "description": "Denoise step that iteratively denoise the latents. Its loop logic is defined in `WanDenoiseLoopWrapper.__call__` method At each iteration, it runs blocks defined in `sub_blocks` sequentially: - `WanImage2VideoLoopBeforeDenoiser` - `WanLoopDenoiser` - `WanLoopAfterDenoiser` This block supports image-to-video tasks for Wan2.2.", - "id": "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:9daec42f0095aefde5606e02f4873451109fede560e75212310e9885dc1b15db", - "inputs": [ - { - "default": null, - "description": "The timesteps to use for the denoising process. Can be generated in set_timesteps step.", - "kwargsType": null, - "name": "timesteps", - "required": true, - "type": "torch.Tensor" - }, { "default": null, "description": "The number of inference steps to use for the denoising process. Can be generated in set_timesteps step.", @@ -69044,38 +69809,6 @@ "required": true, "type": "builtins.int" }, - { - "default": null, - "description": "The initial latents to use for the denoising process. Can be generated in prepare_latent step.", - "kwargsType": null, - "name": "latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The image condition latents to use for the denoising process. Can be generated in prepare_first_frame_latents/prepare_first_last_frame_latents step.", - "kwargsType": null, - "name": "image_condition_latents", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "The dtype of the model inputs. Can be generated in input step.", - "kwargsType": null, - "name": "dtype", - "required": true, - "type": "torch.dtype" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "attention_kwargs", - "required": false, - "type": "opaque" - }, { "default": null, "description": "", @@ -69093,109 +69826,23 @@ "type": "torch.Tensor" } ], - "kind": "loop", + "kind": "block", "outputs": [], "requiredInputs": [ - "dtype", - "image_condition_latents", - "latents", "negative_prompt_embeds", "num_inference_steps", - "prompt_embeds", - "timesteps" + "prompt_embeds" ], "schemaVersion": 1, - "variadicInputs": [] - }, - { - "className": "Wan22LoopDenoiser", - "components": [ - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 4.0 - }, - "description": "", - "name": "guider", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_config", - "defaultConfig": { - "guidance_scale": 3.0 - }, - "description": "", - "name": "guider_2", - "type": "diffusers.guiders.classifier_free_guidance.ClassifierFreeGuidance" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" - }, - { - "creationMethod": "from_pretrained", - "defaultConfig": null, - "description": "", - "name": "transformer_2", - "type": "diffusers.models.transformers.transformer_wan.WanTransformer3DModel" - } - ], - "configs": [ - { - "default": 0.875, - "description": "The boundary ratio to divide the denoising loop into high noise and low noise stages.", - "name": "boundary_ratio" - } - ], - "contentHash": "sha256:a902a0d881cb4959492bfac103336d334a533be123a5d83fbc8fae4267d44ba5", - "description": "Step within the denoising loop that denoise the latents with guidance. This block should be used to compose the `sub_blocks` attribute of a `LoopSequentialPipelineBlocks` object (e.g. `WanDenoiseLoopWrapper`)", - "id": "diffusers.modular-block:Wan22LoopDenoiser:sha256:a902a0d881cb4959492bfac103336d334a533be123a5d83fbc8fae4267d44ba5", - "inputs": [ + "variadicInputs": [ { "default": null, - "description": "", - "kwargsType": null, - "name": "attention_kwargs", + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", "required": false, "type": "opaque" - }, - { - "default": null, - "description": "The number of inference steps to use for the denoising process. Can be generated in set_timesteps step.", - "kwargsType": null, - "name": "num_inference_steps", - "required": true, - "type": "builtins.int" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "prompt_embeds", - "required": true, - "type": "torch.Tensor" - }, - { - "default": null, - "description": "", - "kwargsType": null, - "name": "negative_prompt_embeds", - "required": true, - "type": "torch.Tensor" } - ], - "kind": "block", - "outputs": [], - "requiredInputs": [ - "negative_prompt_embeds", - "num_inference_steps", - "prompt_embeds" - ], - "schemaVersion": 1, - "variadicInputs": [] + ] }, { "className": "WanAdditionalInputsStep", @@ -73059,7 +73706,7 @@ "variadicInputs": [] } ], - "diffusersRevision": "2f7e0154a9db246e95c9ede43edba7db5b130805", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", "schemaVersion": 1, "workflows": [ { @@ -73281,11 +73928,11 @@ "workflowKind": "sequential" }, { - "contentHash": "sha256:880e7db8d90c880bc1352746cc04b285c36bc320b10a888e27509f00d93ce18e", + "contentHash": "sha256:5e53ca1573136d377d66419ea1a31f703194eef79bbba07766d6bb8b2d46c57a", "pipelineClass": "Cosmos3DistilledModularPipeline", "placements": [ { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "legacyPath": "text_encoder", "order": 0, "path": [ @@ -73341,7 +73988,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "legacyPath": "denoise.denoise", "order": 7, "path": [ @@ -73358,7 +74005,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -73384,17 +74031,17 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9e229f4b014e97f18ca5366654b65d4b35a5468797c43f44e0b057b034ff9088", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:413fb3a62436c7dc39d1a898b08596f025efb7fa87547069fb8b2ee703d2a4ff", "schemaVersion": 1, "workflowId": "image2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:6b15018f99ff1182eb2c460dabe41fc73513d9f86ab4cf89f5f73623d7d2c5d8", + "contentHash": "sha256:3e7aad7f7a02eb7b2c505224bf32aa211453d88792a24798fda9e84e1470ed35", "pipelineClass": "Cosmos3DistilledModularPipeline", "placements": [ { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "legacyPath": "text_encoder", "order": 0, "path": [ @@ -73442,7 +74089,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "legacyPath": "denoise.denoise", "order": 6, "path": [ @@ -73459,7 +74106,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -73485,17 +74132,17 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e1088027382fd15528c635f1992c19eff033f252b633cc5520ec4619574d8c24", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:0be3e7aec7f37a3c20284ec209459ff639adbadea266665c26e9931049ee3d97", "schemaVersion": 1, "workflowId": "text2image", "workflowKind": "sequential" }, { - "contentHash": "sha256:d6f9b1b575f5f3ead8f0c05a077cc20ecb0a47950be58e53db91f0947c75c875", + "contentHash": "sha256:4b6a3d77790d80ca72cfc3702bc194fa1a4f04d8fa3ba68bc8e2ab80f3afe02a", "pipelineClass": "Cosmos3DistilledModularPipeline", "placements": [ { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "legacyPath": "text_encoder", "order": 0, "path": [ @@ -73543,7 +74190,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "legacyPath": "denoise.denoise", "order": 6, "path": [ @@ -73560,7 +74207,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -73586,17 +74233,17 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e1088027382fd15528c635f1992c19eff033f252b633cc5520ec4619574d8c24", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:0be3e7aec7f37a3c20284ec209459ff639adbadea266665c26e9931049ee3d97", "schemaVersion": 1, "workflowId": "text2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:599984bc92d852b8c8222336cf31e4e813f35d9591d3c904da76197ad4c4ed08", + "contentHash": "sha256:d59ddae8907fe0def12c8c8944f74e0d8eb4d7e918f78212b0bba9d624b24158", "pipelineClass": "Cosmos3DistilledModularPipeline", "placements": [ { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "legacyPath": "text_encoder", "order": 0, "path": [ @@ -73652,7 +74299,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "legacyPath": "denoise.denoise", "order": 7, "path": [ @@ -73669,7 +74316,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -73695,13 +74342,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:bd209ba0690d77f2124fa30ab4ff0ab2ffddcc9428063d78c6091fb9e2d8ef5d", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:f124523fc5af8da9254a7ff6fde52561be7da7fe252564adb6dddfdc05accfce", "schemaVersion": 1, "workflowId": "video2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:f5ea9f9f8bcc9bf7eac061d0db1dbaffe476fc8190e600d99759f133676da1a7", + "contentHash": "sha256:5aa4feccf442d6a6618c1c4a022a88f1b21feb90712f1c792cd108aa6c30dafb", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -73785,7 +74432,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "legacyPath": "denoise.denoise", "order": 10, "path": [ @@ -73811,7 +74458,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 2, "path": [ @@ -73854,13 +74501,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:d7e680fa704b081d91e6f7d2c0bee5dda178c3296f95ac91d4c5a4490770c0da", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b183f17a43f6567192e5348ba8ec264f574d52bbb4d787a2ab690f91574bf3c3", "schemaVersion": 1, "workflowId": "action_forward_dynamics", "workflowKind": "sequential" }, { - "contentHash": "sha256:8edd050a2670bbba83831f1a41e88786d2b98babd16f79ab039c72c28ff9151e", + "contentHash": "sha256:c002121a4c198d29661bf52fba3a5df0067b01a8ed9166507da63feac6bc5c63", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -73944,7 +74591,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "legacyPath": "denoise.denoise", "order": 10, "path": [ @@ -73970,7 +74617,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 2, "path": [ @@ -74013,13 +74660,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:d7e680fa704b081d91e6f7d2c0bee5dda178c3296f95ac91d4c5a4490770c0da", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b183f17a43f6567192e5348ba8ec264f574d52bbb4d787a2ab690f91574bf3c3", "schemaVersion": 1, "workflowId": "action_inverse_dynamics", "workflowKind": "sequential" }, { - "contentHash": "sha256:e88ea84c181db993eb469043532cf34c522eb7f2b81a5c5c07a9c5aaf55156d4", + "contentHash": "sha256:ff8689dd8b140e4de3b08e3284d2a5c2d80ead9ff928c7c62e9548b20fd2a43b", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -74103,7 +74750,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "legacyPath": "denoise.denoise", "order": 10, "path": [ @@ -74129,7 +74776,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 2, "path": [ @@ -74172,13 +74819,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:d7e680fa704b081d91e6f7d2c0bee5dda178c3296f95ac91d4c5a4490770c0da", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b183f17a43f6567192e5348ba8ec264f574d52bbb4d787a2ab690f91574bf3c3", "schemaVersion": 1, "workflowId": "action_policy", "workflowKind": "sequential" }, { - "contentHash": "sha256:743c8dee12718d9390303fd372855b20da3c54da9509968d665316ce2fef6458", + "contentHash": "sha256:b83ba50039672bfe975426405ae4e23ef78018d0483ed44d870e728aa756708c", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -74238,7 +74885,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "legacyPath": "denoise.denoise", "order": 7, "path": [ @@ -74255,7 +74902,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -74289,13 +74936,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:cb28b453878c928160eb99fc57c41553e7f9b382cb712a1e26cd1a00562bb70b", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9789a379176c824be5fbd7531a3a381e4a79efb0d0b60faae8a52bf5d7ca9cb2", "schemaVersion": 1, "workflowId": "image2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:a49b9f643852cf3c2756c1862be67ba61e14ffa0ef0008f4ee951fe808cb46b1", + "contentHash": "sha256:fa3de3920f66ee489859e580b8a5ec05ad4ac94b0eec2ed2f02741d9bd9019c0", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -74379,7 +75026,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "legacyPath": "denoise.denoise", "order": 10, "path": [ @@ -74405,7 +75052,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 2, "path": [ @@ -74456,13 +75103,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:1c5cb0c3c0e7a7513a6d5a25e1888e5d855bf3a14d86fa1222d8d2f01772bd63", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:f973f03d4d8c8a5ca2e43da9286622a532cc6fc3656f2a4b05c72826df1f9a5f", "schemaVersion": 1, "workflowId": "image2video_with_sound", "workflowKind": "sequential" }, { - "contentHash": "sha256:ff9cffeb7242f32673ad0a203b426260f11ab272a3eed0d092cf6a0c81ac89f7", + "contentHash": "sha256:b98e37a4b98e7d9c276a9b8c049acb51e87bca3d1fb862a33bcbbf7e0a66bc3f", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -74514,7 +75161,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "legacyPath": "denoise.denoise", "order": 6, "path": [ @@ -74531,7 +75178,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -74565,13 +75212,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9cf3268e42b2d81745f83067d5bf4170a6fe44bd7efb3e01a259e20eb1938959", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e076a6919c2f2d84e62e409756a023c29ff26820971cbf292e69e948453500b9", "schemaVersion": 1, "workflowId": "text2image", "workflowKind": "sequential" }, { - "contentHash": "sha256:42c84ea15997eec72cf2de41c5d7ad839066147672842e132b483ffecb98dbd1", + "contentHash": "sha256:8d3a5aaeb2358fec25432402b23d34dd58ae5f83b74b7a56fdc7732d88a27e67", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -74623,7 +75270,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "legacyPath": "denoise.denoise", "order": 6, "path": [ @@ -74640,7 +75287,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -74674,13 +75321,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9cf3268e42b2d81745f83067d5bf4170a6fe44bd7efb3e01a259e20eb1938959", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e076a6919c2f2d84e62e409756a023c29ff26820971cbf292e69e948453500b9", "schemaVersion": 1, "workflowId": "text2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:0f4d7610147b5a5c93e4e21b536ec1db169db5709d76762a095ced9709b851fa", + "contentHash": "sha256:3e94ae1744aa029283f0afa2cf9b583fcec709bad7b2d28a9ee79da24d7f029d", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -74756,7 +75403,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "legacyPath": "denoise.denoise", "order": 9, "path": [ @@ -74782,7 +75429,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 2, "path": [ @@ -74833,13 +75480,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8603b54f602e557da6f289f8c4797a26af8f561d7724dd634bd112cc6379ed91", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9b89b8d12e1d02cb9c32c2d61d3a7d4ad1f0db0ff4827c8bb4bbdfeaf7544f52", "schemaVersion": 1, "workflowId": "text2video_with_sound", "workflowKind": "sequential" }, { - "contentHash": "sha256:ead2aad238a7f43faa08786c507536015dcf632e152157a75a7ab636e27e0cb9", + "contentHash": "sha256:15be83c6ef60c703010f325fe78cf17c09d967183bf7fb7b3f02c31b71a81208", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -74899,7 +75546,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "legacyPath": "denoise.denoise", "order": 7, "path": [ @@ -74916,7 +75563,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -74950,13 +75597,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:6596c52651a5480d32f6e30118ecfb7d4dfa5135d2155b2aab6f3ee0c95c0287", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:b8f456945daa167c65549bb9282a64744360ac1b9fa4b4ea7eb82e8f1f44f17b", "schemaVersion": 1, "workflowId": "video2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:cd28ed1be2315a78e348c785052882b7f77e832bf3a401f855506d6999ac7999", + "contentHash": "sha256:a2c7da0798b7c5e3476864d0cb9166b5758ad71614049419114d83cc84dfb154", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -75040,7 +75687,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "legacyPath": "denoise.denoise", "order": 10, "path": [ @@ -75066,7 +75713,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "legacyPath": "denoise.denoise.denoiser", "order": 2, "path": [ @@ -75117,19 +75764,27 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e130c0522b6068103dd7487016c7f639a610d71fc6f403b15ce7f01c1069766d", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:d5d2adda5cb4435a515432d61a31b646f9eb0f4e183ebfe1167d4a1f14c4a0da", "schemaVersion": 1, "workflowId": "video2video_with_sound", "workflowKind": "sequential" }, { - "contentHash": "sha256:445a8eb28b8e9d4b103a7e29e67257998ab3d4dce36518459fb85196cdf85e20", + "contentHash": "sha256:b923df4c1b1cdd39992d5d832a4c77609e26aef986495fdf65a5bd261c2fae5e", "pipelineClass": "ErnieImageModularPipeline", "placements": [ + { + "blockDefinitionId": "diffusers.modular-block:ErnieImagePromptEnhancerStep:sha256:2531b7dd8052234fb8c4962ec02e751915e41d3148fb2388ec7feec4609b3b06", + "legacyPath": "prompt_enhancer", + "order": 0, + "path": [ + "prompt_enhancer" + ] + }, { "blockDefinitionId": "diffusers.modular-block:ErnieImageTextEncoderStep:sha256:8078b53f5972c8b52d36a7cca8a4c41620d9506c459a8387d720771004f50bd8", "legacyPath": "text_encoder", - "order": 0, + "order": 1, "path": [ "text_encoder" ] @@ -75137,7 +75792,7 @@ { "blockDefinitionId": "diffusers.modular-block:ErnieImageTextInputStep:sha256:65b6fe2d66bbd1646ec2f624ac4d7ee87d9ce35adfb2b858557f90f2618959b3", "legacyPath": "denoise.input", - "order": 1, + "order": 2, "path": [ "denoise.input" ] @@ -75145,7 +75800,7 @@ { "blockDefinitionId": "diffusers.modular-block:ErnieImageSetTimestepsStep:sha256:633ae9bf079ef52fc99fec07c390f7412ac0d8c7d20d360545aa654df26b335b", "legacyPath": "denoise.set_timesteps", - "order": 2, + "order": 3, "path": [ "denoise.set_timesteps" ] @@ -75153,7 +75808,7 @@ { "blockDefinitionId": "diffusers.modular-block:ErnieImagePrepareLatentsStep:sha256:189abc38ee3c9db175b8629202787f8ffcc22de7ef2e6db5dcce2ac937f4af1e", "legacyPath": "denoise.prepare_latents", - "order": 3, + "order": 4, "path": [ "denoise.prepare_latents" ] @@ -75161,7 +75816,7 @@ { "blockDefinitionId": "diffusers.modular-block:ErnieImageDenoiseStep:sha256:de46284ad330487cebb5a3ecc64924745b9fbf328711281f2a886271c4a4369c", "legacyPath": "denoise.denoise", - "order": 4, + "order": 5, "path": [ "denoise.denoise" ] @@ -75196,13 +75851,13 @@ { "blockDefinitionId": "diffusers.modular-block:ErnieImageVaeDecoderStep:sha256:595c24f9238cd4d125eed61a386cac9005d445043a4eade77dded81217ce37e4", "legacyPath": "decode", - "order": 5, + "order": 6, "path": [ "decode" ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:766a6741623809e21f6ebb58800138a17581426fea178d8dff721e415079da29", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:5908f92e30713a3df9726f2d615565d5381c2ff60c3ec9fdacb44377cf3e7f4a", "schemaVersion": 1, "workflowId": "text2image", "workflowKind": "sequential" @@ -77754,7 +78409,7 @@ "workflowKind": "sequential" }, { - "contentHash": "sha256:e0cb5bd18f09e68318e3a49263a8c84d247aeb33eaf02776bf357a61d1b3de85", + "contentHash": "sha256:7a1159434f2a220ab37f962cbbf9b2529354ffbec3bb2a5bd21bfc26206898e7", "pipelineClass": "LTX25ModularPipeline", "placements": [ { @@ -77830,7 +78485,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "legacyPath": "denoise.denoise", "order": 9, "path": [ @@ -77847,7 +78502,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -77889,13 +78544,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:1af6cf8cf157dc75d2250efa9625fd742cdc481aaf5a73b9091aa645280223e5", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8827541bf80d06ae897eb1129ce1c49204dbc4826287bec8069d2d6bf8b9c763", "schemaVersion": 1, "workflowId": "condition", "workflowKind": "sequential" }, { - "contentHash": "sha256:43e2980b8582a57286ce97bc48eceb57d8aabd9b783950f9c7b11fce96efdb38", + "contentHash": "sha256:207f06762914474c1bb0857a4cf3d422748cb63f78d4a82862c8cc8981e3abbe", "pipelineClass": "LTX25ModularPipeline", "placements": [ { @@ -77979,7 +78634,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "legacyPath": "denoise.denoise", "order": 10, "path": [ @@ -77996,7 +78651,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -78030,13 +78685,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:3beeae593a6f8c8cbac2fdc75833336af18c0e7dccc7ef92bc4dbe3f320ed0a9", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:4a1d454b66bec210dc720fb0a73c8888b91862e244fdcdd501c5eaf51e06d3ab", "schemaVersion": 1, "workflowId": "image2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:f80319d95d1ddee9ed093a12530e4c54321297d7145604c5fc7f20b8a90bb2d0", + "contentHash": "sha256:010f5e4cd55a38de6837527b9764ac8b805a9b6320ba19f11cc0c34f291ccf4e", "pipelineClass": "LTX25ModularPipeline", "placements": [ { @@ -78112,7 +78767,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "legacyPath": "denoise.denoise", "order": 9, "path": [ @@ -78129,7 +78784,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -78171,13 +78826,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:2ccabff47b77c07cc636d2b0ebe747d476cafa1f7c4def6e879438208a3b25a3", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:9f1a8fa2e9e5ea98cb030cf2860b7419c4a98d811bb48fd48c8d1124b47d411a", "schemaVersion": 1, "workflowId": "in_context", "workflowKind": "sequential" }, { - "contentHash": "sha256:22958568653bb4b58da8737711cbca9a9ec99d5b8026afee04a030de415db5e5", + "contentHash": "sha256:cf5b0a8e4f775d8d804cebad44e868dc5578b97d78d38f835d98d5316881121d", "pipelineClass": "LTX25ModularPipeline", "placements": [ { @@ -78245,7 +78900,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2DenoiseStep:sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "blockDefinitionId": "diffusers.modular-block:LTX2DenoiseStep:sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "legacyPath": "denoise.denoise", "order": 8, "path": [ @@ -78262,7 +78917,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -78296,13 +78951,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:f5bdfb3cdad9dc2d985676ed4378dcf565be4e6267a4782134e3f12adbfe138d", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8e12049ce331e774d1e4b5ab648a8165d7e98e7c328dd4b3d1d95272a1084b20", "schemaVersion": 1, "workflowId": "text2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:9b8ed8e1d7a97928750b494b575abe93d15b4ebacf1b9acc47ffbfd15f5a68fa", + "contentHash": "sha256:9b6d64abe4f2fa369afd2c80f07b9412027de9a2136baaa5e87a14b5a231398a", "pipelineClass": "LTX2ModularPipeline", "placements": [ { @@ -78378,7 +79033,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "legacyPath": "denoise.denoise", "order": 9, "path": [ @@ -78395,7 +79050,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -78437,13 +79092,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:e5f246fb36f9b1e22684c5704aa05831f81fae0e3ff822315c6718aa912abd26", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:1aab6c5ff65c77f7f64de2dc9d2671f2f266e5827a111741d2821834e3d6836c", "schemaVersion": 1, "workflowId": "condition", "workflowKind": "sequential" }, { - "contentHash": "sha256:fd47f3ac31a8d9d1095b775673af5e05ec95dcd812e771bd5f31c41dc72a6d40", + "contentHash": "sha256:e0df538eaf2bd9c0a11a34a8153c27d432ecaf64a652313252736a43a06ea273", "pipelineClass": "LTX2ModularPipeline", "placements": [ { @@ -78527,7 +79182,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "legacyPath": "denoise.denoise", "order": 10, "path": [ @@ -78544,7 +79199,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -78578,13 +79233,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:80583fd31ce7203d3d29ed785b7a698638b27338e5cd3e61997ca0287cd3e2c0", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:7d27e88a7313f0949bd5805f77f93a7537cdf7c96aa23ddccbb5f6deb9a8674f", "schemaVersion": 1, "workflowId": "image2video", "workflowKind": "sequential" }, { - "contentHash": "sha256:bece9cea4e36d844d03663fbcb7ebdc817e5c42d8214451c5c2e379c5ed395f9", + "contentHash": "sha256:37e32c6187957a69f43df60aefb2cf2981de6ceac4d9d1c8bb02b4e3c1574ec1", "pipelineClass": "LTX2ModularPipeline", "placements": [ { @@ -78660,7 +79315,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "legacyPath": "denoise.denoise", "order": 9, "path": [ @@ -78677,7 +79332,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -78719,13 +79374,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:7ede32bbb440833a8e715338d4c4d40ac15e5f9abcb856327342af4170fbafdc", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:f6facbf653b1ea75a936d62c872901e8a38d8c3faa789ba2d75c3d101c4a8904", "schemaVersion": 1, "workflowId": "in_context", "workflowKind": "sequential" }, { - "contentHash": "sha256:d5c22d09180e4ee524d1f6095fda5528be1908b5d787c08fd683ea39ab1e56e0", + "contentHash": "sha256:4552944056e1e679f75e88cf9394525cbb2e5b1430c9309846eb9eb90013fd0d", "pipelineClass": "LTX2ModularPipeline", "placements": [ { @@ -78793,7 +79448,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2DenoiseStep:sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "blockDefinitionId": "diffusers.modular-block:LTX2DenoiseStep:sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "legacyPath": "denoise.denoise", "order": 8, "path": [ @@ -78810,7 +79465,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -78844,7 +79499,7 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:47156a49ab67b15c0926f6a7a92ea629712fa01fa2cff4c66773593db736b727", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:ce8b330093a49971d1a10f90c3651db1383b1f7495e9f79a6cd6491023bf79b7", "schemaVersion": 1, "workflowId": "text2video", "workflowKind": "sequential" @@ -83145,7 +83800,7 @@ "workflowKind": "sequential" }, { - "contentHash": "sha256:07804b5cf373b596c5a4287c4be32927f06ffc2338794f6ad4ad2954814ec8ee", + "contentHash": "sha256:02aa8be2d0ba59347597d80e6ab235e8d9b3005382e2519f1c546d4aad62c31f", "pipelineClass": "Wan22Image2VideoModularPipeline", "placements": [ { @@ -83213,7 +83868,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:9daec42f0095aefde5606e02f4873451109fede560e75212310e9885dc1b15db", + "blockDefinitionId": "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:c51d77edbdec4084a4542e734a31d1eedace36aeee6c048de29bfcd5351c942a", "legacyPath": "denoise.denoise", "order": 8, "path": [ @@ -83230,7 +83885,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22LoopDenoiser:sha256:a902a0d881cb4959492bfac103336d334a533be123a5d83fbc8fae4267d44ba5", + "blockDefinitionId": "diffusers.modular-block:Wan22LoopDenoiser:sha256:194524add4567c94a2654a8d4ac2a0e4d67ccede3063e67e296a6a3ef54c90b3", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -83256,13 +83911,13 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:5d080c121ae7f2dfa77d641fcbaa821e6f95ba7d53809c404d8988818778e529", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:5792eccf29e0767786e025f8dbc6c42a3f1d0c951b1be650328c52bcf878d8ee", "schemaVersion": 1, "workflowId": "default", "workflowKind": "sequential" }, { - "contentHash": "sha256:12a1133c16fe05966c2aadaab0f333169df6fcff2e9ca29d1f5cb5fbbf8eb3c4", + "contentHash": "sha256:d244ab9b8bdf8143db43825532236c5827e53c43f72adfe5386cf9fc0f4f3f9d", "pipelineClass": "Wan22ModularPipeline", "placements": [ { @@ -83298,7 +83953,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22DenoiseStep:sha256:74bb7f99087db12b178bb99d192ed7209986b8d27817263d0169cb0efb058b9d", + "blockDefinitionId": "diffusers.modular-block:Wan22DenoiseStep:sha256:217a54b74465756c8b2237e6903c0d035e80f340b4635ecfcbb7686aa65089cb", "legacyPath": "denoise.denoise", "order": 4, "path": [ @@ -83315,7 +83970,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22LoopDenoiser:sha256:a902a0d881cb4959492bfac103336d334a533be123a5d83fbc8fae4267d44ba5", + "blockDefinitionId": "diffusers.modular-block:Wan22LoopDenoiser:sha256:194524add4567c94a2654a8d4ac2a0e4d67ccede3063e67e296a6a3ef54c90b3", "legacyPath": "denoise.denoise.denoiser", "order": 1, "path": [ @@ -83341,7 +83996,7 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:ed1072cef3523a3b6f133d4d620e119f7b20899959d3cb31d62b711c7bb78c53", + "rootBlockDefinitionId": "diffusers.modular-block:SequentialPipelineBlocks:sha256:8d68bdb4a5c22b0f6375a1d9a299e2301d87c3938f23dfe0d42d3474a531fb6e", "schemaVersion": 1, "workflowId": "default", "workflowKind": "sequential" diff --git a/data/modular-conditional-contracts.json b/data/modular-conditional-contracts.json index d463030c..3648da74 100644 --- a/data/modular-conditional-contracts.json +++ b/data/modular-conditional-contracts.json @@ -2944,9 +2944,9 @@ "name": "use_native_flow_schedule" } ], - "contentHash": "sha256:fc99a68cdb0e31902ef9f01573c3eff46771a725b15169b2a5627620ce751d92", + "contentHash": "sha256:0967677bf4a6c6eebd1eb7df68e26a01031167812d1a3273e9899c11a308715a", "description": "Selects the Cosmos3 core denoising workflow. - transfer runs the autoregressive control-video (ControlNet-style) chunk loop when control_videos are provided. - vision_sound_action runs when action and enable_sound are provided. - vision_action runs when action is provided. - vision_sound runs when enable_sound is true. - vision runs otherwise.", - "id": "diffusers.modular-block:Cosmos3AutoCoreDenoiseStep:sha256:fc99a68cdb0e31902ef9f01573c3eff46771a725b15169b2a5627620ce751d92", + "id": "diffusers.modular-block:Cosmos3AutoCoreDenoiseStep:sha256:0967677bf4a6c6eebd1eb7df68e26a01031167812d1a3273e9899c11a308715a", "inputs": [ { "default": null, @@ -3076,6 +3076,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for text classifier-free guidance.", @@ -4188,9 +4220,9 @@ "name": "distilled_sigmas" } ], - "contentHash": "sha256:fccb35a6312fc84454744d027166d9b06d1f137630ad78f90b7bb5dc11befd6e", + "contentHash": "sha256:d1f005c6bddd5c3057580aad29b6b46659f35e4a1a2d2d2e7bd199ee923f8936", "description": "Modular pipeline blocks for distilled (few-step) Cosmos3 generation modes.", - "id": "diffusers.modular-block:Cosmos3DistilledBlocks:sha256:fccb35a6312fc84454744d027166d9b06d1f137630ad78f90b7bb5dc11befd6e", + "id": "diffusers.modular-block:Cosmos3DistilledBlocks:sha256:d1f005c6bddd5c3057580aad29b6b46659f35e4a1a2d2d2e7bd199ee923f8936", "inputs": [ { "default": null, @@ -4233,12 +4265,12 @@ "type": "builtins.float" }, { - "default": true, + "default": null, "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, "name": "use_system_prompt", "required": false, - "type": "builtins.bool" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { "default": true, @@ -4339,6 +4371,38 @@ "required": false, "type": "builtins.float" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": "pil", "description": "Output format: 'pil', 'np', 'pt'.", @@ -4496,9 +4560,9 @@ "name": "enable_safety_checker" } ], - "contentHash": "sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "contentHash": "sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "description": "Prepares distilled prompt token IDs. Classifier-free guidance is baked into the weights, so `negative_prompt` is not exposed and the unconditional branch is derived from an empty prompt.", - "id": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "id": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "inputs": [ { "default": null, @@ -4541,12 +4605,12 @@ "type": "builtins.float" }, { - "default": true, + "default": null, "description": "Whether to prepend the Cosmos3 system prompt.", "kwargsType": null, "name": "use_system_prompt", "required": false, - "type": "builtins.bool" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { "default": true, @@ -4644,9 +4708,9 @@ "name": "distilled_sigmas" } ], - "contentHash": "sha256:64b6bbf31747e1466d0030ffeb2a2b4f56850970a7b03293d4cb185d8b2cac2b", + "contentHash": "sha256:871f15e52c6647c97b0099f93d318989365176b124d316fab0d9fc37a385f3b7", "description": "Runs the text-and-vision distilled Cosmos3 denoising workflow.", - "id": "diffusers.modular-block:Cosmos3DistilledVisionCoreDenoiseStep:sha256:64b6bbf31747e1466d0030ffeb2a2b4f56850970a7b03293d4cb185d8b2cac2b", + "id": "diffusers.modular-block:Cosmos3DistilledVisionCoreDenoiseStep:sha256:871f15e52c6647c97b0099f93d318989365176b124d316fab0d9fc37a385f3b7", "inputs": [ { "default": null, @@ -4743,6 +4807,38 @@ "name": "guidance_scale", "required": false, "type": "builtins.float" + }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" } ], "kind": "sequential", @@ -4801,13 +4897,13 @@ "defaultConfig": null, "description": "", "name": "scheduler", - "type": "diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler" + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" } ], "configs": [], - "contentHash": "sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "contentHash": "sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "description": "Runs the vision-only distilled Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "id": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "inputs": [ { "default": null, @@ -4833,6 +4929,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -5098,12 +5226,19 @@ "description": "", "name": "transformer", "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" } ], "configs": [], - "contentHash": "sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "contentHash": "sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "description": "Predicts available Cosmos3 modality velocities for one denoising iteration.", - "id": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "id": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "inputs": [ { "default": 6.0, @@ -5219,9 +5354,9 @@ "name": "use_native_flow_schedule" } ], - "contentHash": "sha256:78105cbc44829f30bb20d62798ec76f93c060d07e7cc27804ccfe5b054a2fddb", + "contentHash": "sha256:369ff8a3907febd9973ba49af73dc0ca7ed73b5cbebca5781cb4e4c74dd53fd6", "description": "Modular pipeline blocks for Cosmos3 generation modes.", - "id": "diffusers.modular-block:Cosmos3OmniBlocks:sha256:78105cbc44829f30bb20d62798ec76f93c060d07e7cc27804ccfe5b054a2fddb", + "id": "diffusers.modular-block:Cosmos3OmniBlocks:sha256:369ff8a3907febd9973ba49af73dc0ca7ed73b5cbebca5781cb4e4c74dd53fd6", "inputs": [ { "default": null, @@ -5402,6 +5537,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for text classifier-free guidance.", @@ -6322,9 +6489,9 @@ } ], "configs": [], - "contentHash": "sha256:3501131b68144400ac4b3dbee497e76f6ff09bd8b8156630be60b3f96961f912", + "contentHash": "sha256:78ac114d84c5c92fc845abe0ab6e5d9287d0292c3e8e0ee202a30964cde36307", "description": "Autoregressive transfer chunk loop. Overrides __call__ to iterate chunks (the inner timestep loop is a non-leaf LoopSequentialPipelineBlocks, so this outer loop cannot itself be a LoopSequentialPipelineBlocks). Per-chunk cross-carry (previous_output, output_chunks) lives on PipelineState.", - "id": "diffusers.modular-block:Cosmos3TransferChunkDenoiseStep:sha256:3501131b68144400ac4b3dbee497e76f6ff09bd8b8156630be60b3f96961f912", + "id": "diffusers.modular-block:Cosmos3TransferChunkDenoiseStep:sha256:78ac114d84c5c92fc845abe0ab6e5d9287d0292c3e8e0ee202a30964cde36307", "inputs": [ { "default": 0, @@ -6454,6 +6621,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for text classifier-free guidance.", @@ -6884,9 +7083,9 @@ } ], "configs": [], - "contentHash": "sha256:d55fa64771e15541a9fb16d62911f338f21d9449bc8c64870fcddc6ab9e52564", + "contentHash": "sha256:a930f4dbaa91a9bd4dae5f269ece432169da71a03e6a990b11d09beb0f96c056", "description": "Transfer denoise stage: prepare shared text segments once, then run the autoregressive chunk loop.", - "id": "diffusers.modular-block:Cosmos3TransferCoreDenoiseStep:sha256:d55fa64771e15541a9fb16d62911f338f21d9449bc8c64870fcddc6ab9e52564", + "id": "diffusers.modular-block:Cosmos3TransferCoreDenoiseStep:sha256:a930f4dbaa91a9bd4dae5f269ece432169da71a03e6a990b11d09beb0f96c056", "inputs": [ { "default": null, @@ -7016,6 +7215,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for text classifier-free guidance.", @@ -7370,9 +7601,9 @@ } ], "configs": [], - "contentHash": "sha256:d12de7fe34f32671edbcdf86e4ad4c6ab820b94c3720a46aa9cf059d4c9aea43", + "contentHash": "sha256:0b2dcd8ff550b4907e527b43a5c5c9d2013976de4eac3fe957a0226cccb4ac70", "description": "Runs the per-chunk transfer denoising loop over scheduler timesteps.", - "id": "diffusers.modular-block:Cosmos3TransferDenoiseStep:sha256:d12de7fe34f32671edbcdf86e4ad4c6ab820b94c3720a46aa9cf059d4c9aea43", + "id": "diffusers.modular-block:Cosmos3TransferDenoiseStep:sha256:0b2dcd8ff550b4907e527b43a5c5c9d2013976de4eac3fe957a0226cccb4ac70", "inputs": [ { "default": null, @@ -7398,6 +7629,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Clean control latents for this chunk, one per hint in canonical order.", @@ -7524,12 +7787,19 @@ "description": "", "name": "transformer", "type": "diffusers.models.transformers.transformer_cosmos3.Cosmos3OmniTransformer" + }, + { + "creationMethod": "from_pretrained", + "defaultConfig": null, + "description": "", + "name": "scheduler", + "type": "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler" } ], "configs": [], - "contentHash": "sha256:01bbd8101a3b437f9059bb0afe0895a4cf59c097acfd711530d8a77162f5d1b8", + "contentHash": "sha256:d7c064705cb2ae2a74c48a5c94c59dad948f288780d0b26bed25634d413a5126", "description": "Predicts the transfer velocity with nested control/text CFG over [control..., target]. Each branch is gated by its guidance interval, and the result is masked so conditioned frames get zero velocity.", - "id": "diffusers.modular-block:Cosmos3TransferLoopDenoiser:sha256:01bbd8101a3b437f9059bb0afe0895a4cf59c097acfd711530d8a77162f5d1b8", + "id": "diffusers.modular-block:Cosmos3TransferLoopDenoiser:sha256:d7c064705cb2ae2a74c48a5c94c59dad948f288780d0b26bed25634d413a5126", "inputs": [ { "default": null, @@ -8659,9 +8929,9 @@ "name": "use_native_flow_schedule" } ], - "contentHash": "sha256:4383e1d99f37bcdd022ec7e33cc8057a2633a5c937f691b7059bd28be6646268", + "contentHash": "sha256:936cb25971084367e4a8c91518cd74cf31805c0d50a5ce338e69fcd072919eeb", "description": "Runs the text, vision, and action Cosmos3 denoising workflow.", - "id": "diffusers.modular-block:Cosmos3VisionActionCoreDenoiseStep:sha256:4383e1d99f37bcdd022ec7e33cc8057a2633a5c937f691b7059bd28be6646268", + "id": "diffusers.modular-block:Cosmos3VisionActionCoreDenoiseStep:sha256:936cb25971084367e4a8c91518cd74cf31805c0d50a5ce338e69fcd072919eeb", "inputs": [ { "default": null, @@ -8775,6 +9045,38 @@ "required": true, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -8862,9 +9164,9 @@ } ], "configs": [], - "contentHash": "sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "contentHash": "sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "description": "Runs the vision-and-action Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "id": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "inputs": [ { "default": null, @@ -8890,6 +9192,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -9031,9 +9365,9 @@ "name": "use_native_flow_schedule" } ], - "contentHash": "sha256:24ac92e641d21454b0de4a54f2379bae99cfa8f40aa6511a74eda95936f54cec", + "contentHash": "sha256:434061ea2aaf636e50d15f23b7176d77dc9bdb6dccf3d386373707a1ffbe222c", "description": "Runs the text-and-vision Cosmos3 denoising workflow.", - "id": "diffusers.modular-block:Cosmos3VisionCoreDenoiseStep:sha256:24ac92e641d21454b0de4a54f2379bae99cfa8f40aa6511a74eda95936f54cec", + "id": "diffusers.modular-block:Cosmos3VisionCoreDenoiseStep:sha256:434061ea2aaf636e50d15f23b7176d77dc9bdb6dccf3d386373707a1ffbe222c", "inputs": [ { "default": null, @@ -9123,6 +9457,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -9276,9 +9642,9 @@ } ], "configs": [], - "contentHash": "sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "contentHash": "sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "description": "Runs the vision-only Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "id": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "inputs": [ { "default": null, @@ -9304,6 +9670,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -9720,9 +10118,9 @@ "name": "use_native_flow_schedule" } ], - "contentHash": "sha256:79aa26cb976843a2599ebcfeb7883bb21f3c1b610d50b33f5b38478ab7f69d4d", + "contentHash": "sha256:e41714f334ad11e7157c930dd4eeaf9194e69ae7afaa52f5aaf225c3feb471fa", "description": "Runs the text, vision, sound, and action Cosmos3 denoising workflow.", - "id": "diffusers.modular-block:Cosmos3VisionSoundActionCoreDenoiseStep:sha256:79aa26cb976843a2599ebcfeb7883bb21f3c1b610d50b33f5b38478ab7f69d4d", + "id": "diffusers.modular-block:Cosmos3VisionSoundActionCoreDenoiseStep:sha256:e41714f334ad11e7157c930dd4eeaf9194e69ae7afaa52f5aaf225c3feb471fa", "inputs": [ { "default": null, @@ -9844,6 +10242,38 @@ "required": true, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -9945,9 +10375,9 @@ } ], "configs": [], - "contentHash": "sha256:0ae18dd115e0fe496c430793ac4b99d575e4399f085d7a7ca75f55b64c3792df", + "contentHash": "sha256:44c2bbcd557d3144053a0b0f60c44da42f0c5b828ae2c9d127467ec165a9d65f", "description": "Runs the vision, sound, and action Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3VisionSoundActionDenoiseStep:sha256:0ae18dd115e0fe496c430793ac4b99d575e4399f085d7a7ca75f55b64c3792df", + "id": "diffusers.modular-block:Cosmos3VisionSoundActionDenoiseStep:sha256:44c2bbcd557d3144053a0b0f60c44da42f0c5b828ae2c9d127467ec165a9d65f", "inputs": [ { "default": null, @@ -9973,6 +10403,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -10150,9 +10612,9 @@ "name": "use_native_flow_schedule" } ], - "contentHash": "sha256:b60a4942c2ff28e85194fc3e42bea6e7a63cb303b0ed7827046e91a37a83e39f", + "contentHash": "sha256:6ab06dde2398549cd5156a29c7a04e164e81c6e971739bff11cb31cec6f61151", "description": "Runs the text, vision, and sound Cosmos3 denoising workflow.", - "id": "diffusers.modular-block:Cosmos3VisionSoundCoreDenoiseStep:sha256:b60a4942c2ff28e85194fc3e42bea6e7a63cb303b0ed7827046e91a37a83e39f", + "id": "diffusers.modular-block:Cosmos3VisionSoundCoreDenoiseStep:sha256:6ab06dde2398549cd5156a29c7a04e164e81c6e971739bff11cb31cec6f61151", "inputs": [ { "default": null, @@ -10250,6 +10712,38 @@ "required": true, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -10336,9 +10830,9 @@ } ], "configs": [], - "contentHash": "sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "contentHash": "sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "description": "Runs the vision-and-sound Cosmos3 denoising loop.", - "id": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "id": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "inputs": [ { "default": null, @@ -10364,6 +10858,38 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "kwargsType": null, + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "kwargsType": null, + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "kwargsType": null, + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": null, "description": "Noisy vision latents to denoise.", @@ -32408,7 +32934,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -32420,7 +32946,7 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_pretrained", @@ -32438,9 +32964,9 @@ } ], "configs": [], - "contentHash": "sha256:477021db418762ed44f5598bdc9bb25e8ed21e0d309ac117a899b46e63587694", + "contentHash": "sha256:3f4ba9f7e0d988e2aa06c008b9b3db162ab9ac3803f2341d4d97e52018395538", "description": "Auto blocks for LTX-2.5 supporting text-to-video, image-to-video, condition-to-video and in-context (IC-LoRA) generation (joint video + audio). Identical to `LTX2AutoBlocks` except that the video decoder is `LTX2DiffusionVaeDecoderStep`, since the diffusion decoder is the native default from LTX-2.5 on. To decode with the convolutional VAE instead, swap the decode block: `blocks.sub_blocks[\"decode\"] = LTX2AutoDecoderStep()`.", - "id": "diffusers.modular-block:LTX25AutoBlocks:sha256:477021db418762ed44f5598bdc9bb25e8ed21e0d309ac117a899b46e63587694", + "id": "diffusers.modular-block:LTX25AutoBlocks:sha256:3f4ba9f7e0d988e2aa06c008b9b3db162ab9ac3803f2341d4d97e52018395538", "inputs": [ { "default": null, @@ -33421,7 +33947,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -33433,7 +33959,7 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_pretrained", @@ -33444,9 +33970,9 @@ } ], "configs": [], - "contentHash": "sha256:e5d47b6161c3e8d90740b45b07c56a749c86acc320a1508ea1d31bfa874b5943", + "contentHash": "sha256:a9e4ca4d35ddb1fab0bfa097d09b045e781fbc3c6f077a953450239f523a2151", "description": "Auto blocks for LTX-2 supporting text-to-video, image-to-video, condition-to-video and in-context (IC-LoRA) generation (joint video + audio).", - "id": "diffusers.modular-block:LTX2AutoBlocks:sha256:e5d47b6161c3e8d90740b45b07c56a749c86acc320a1508ea1d31bfa874b5943", + "id": "diffusers.modular-block:LTX2AutoBlocks:sha256:a9e4ca4d35ddb1fab0bfa097d09b045e781fbc3c6f077a953450239f523a2151", "inputs": [ { "default": null, @@ -34055,7 +34581,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -34067,13 +34593,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:0bc669ba1343885122beb0fee8c7d83492b972085c80b591d6f55e04232cbf2f", + "contentHash": "sha256:387852bf402c69f82aad3f2e4e75da41e65c1f791e68b1fea84f88a357ccb904", "description": "Auto denoise block that selects the workflow based on inputs. - `LTX2InContextCoreDenoiseStep` when `reference_conditions` are provided (in-context / IC-LoRA). - `LTX2ConditionCoreDenoiseStep` when `condition_latents` are provided (condition-to-video). - `LTX2Image2VideoCoreDenoiseStep` when `image_latents` is provided. - `LTX2CoreDenoiseStep` otherwise (text-to-video).", - "id": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:0bc669ba1343885122beb0fee8c7d83492b972085c80b591d6f55e04232cbf2f", + "id": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:387852bf402c69f82aad3f2e4e75da41e65c1f791e68b1fea84f88a357ccb904", "inputs": [ { "default": 1, @@ -35089,7 +35615,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -35101,13 +35627,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:3a2942d554bffefe97d23b7a56c375f3b0be7f9107d61d53ecba9d0fd93b809e", + "contentHash": "sha256:6fb81dc68db19bf22dbcc5645106c0af68bf38c4a453f98c39dd756003c98383", "description": "Denoise block (condition-to-video) that expands the text conditioning by `num_videos_per_prompt`, applies the frame conditions to the video latents and runs the joint denoising loop.", - "id": "diffusers.modular-block:LTX2ConditionCoreDenoiseStep:sha256:3a2942d554bffefe97d23b7a56c375f3b0be7f9107d61d53ecba9d0fd93b809e", + "id": "diffusers.modular-block:LTX2ConditionCoreDenoiseStep:sha256:6fb81dc68db19bf22dbcc5645106c0af68bf38c4a453f98c39dd756003c98383", "inputs": [ { "default": 1, @@ -35574,7 +36100,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -35586,13 +36112,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "contentHash": "sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "description": "Condition denoise step. Iterates `LTX2DenoiseLoopWrapper.__call__`, running per step: - `LTX2ConditionLoopBeforeDenoiser` - `LTX2LoopDenoiser` - `LTX2ConditionLoopAfterDenoiser`", - "id": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "id": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "inputs": [ { "default": null, @@ -36635,7 +37161,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -36647,13 +37173,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:ac71e8bacb814171b3eb9ad9d05f1b5d88dccaf502b1f453b7558c8877b5bf18", + "contentHash": "sha256:d7f6a6cedb694be5f5eca1532d80e69b531162a33c1faec7884e18637e76b556", "description": "Denoise block (text-to-video) that expands the text conditioning by `num_videos_per_prompt`, prepares video/audio latents and runs the joint denoising loop.", - "id": "diffusers.modular-block:LTX2CoreDenoiseStep:sha256:ac71e8bacb814171b3eb9ad9d05f1b5d88dccaf502b1f453b7558c8877b5bf18", + "id": "diffusers.modular-block:LTX2CoreDenoiseStep:sha256:d7f6a6cedb694be5f5eca1532d80e69b531162a33c1faec7884e18637e76b556", "inputs": [ { "default": 1, @@ -37072,7 +37598,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -37084,13 +37610,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "contentHash": "sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "description": "Text-to-video denoise step. Iterates `LTX2DenoiseLoopWrapper.__call__`, running per step: - `LTX2LoopBeforeDenoiser` - `LTX2LoopDenoiser` - `LTX2LoopAfterDenoiser`", - "id": "diffusers.modular-block:LTX2DenoiseStep:sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "id": "diffusers.modular-block:LTX2DenoiseStep:sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "inputs": [ { "default": null, @@ -37485,7 +38011,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -37497,13 +38023,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:38af484d045a15c099d7b9a98b934e0bf8dddeb60fb5be4f978b16790f359921", + "contentHash": "sha256:a632c7f4c984dc9ff07eb060258d6d7492ed509c4529396b02cce88c68b4c8cc", "description": "Denoise block (image-to-video) that expands the text conditioning by `num_videos_per_prompt`, adds image conditioning and runs the joint denoising loop.", - "id": "diffusers.modular-block:LTX2Image2VideoCoreDenoiseStep:sha256:38af484d045a15c099d7b9a98b934e0bf8dddeb60fb5be4f978b16790f359921", + "id": "diffusers.modular-block:LTX2Image2VideoCoreDenoiseStep:sha256:a632c7f4c984dc9ff07eb060258d6d7492ed509c4529396b02cce88c68b4c8cc", "inputs": [ { "default": 1, @@ -37768,7 +38294,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -37780,13 +38306,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "contentHash": "sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "description": "Image-to-video denoise step. Iterates `LTX2DenoiseLoopWrapper.__call__`, running per step: - `LTX2Image2VideoLoopBeforeDenoiser` - `LTX2LoopDenoiser` - `LTX2Image2VideoLoopAfterDenoiser`", - "id": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "id": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "inputs": [ { "default": null, @@ -38346,7 +38872,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -38358,13 +38884,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:f24158809c78328798dcb1b0b030a52069b7c80ecf88ae17e07e228c92281d3f", + "contentHash": "sha256:df883f87e496b42bdaae837541893640bb53260bce3584f3bfe42794c6d81203", "description": "Denoise block (in-context) that expands the text conditioning by `num_videos_per_prompt`, folds the frame conditions and the IC-LoRA reference tokens into one latent sequence and runs the joint denoising loop. Reuses the condition denoise step unchanged: reference tokens are pinned by the same x0 blend as frame conditions, matching the reference implementation's uniform treatment of both.", - "id": "diffusers.modular-block:LTX2InContextCoreDenoiseStep:sha256:f24158809c78328798dcb1b0b030a52069b7c80ecf88ae17e07e228c92281d3f", + "id": "diffusers.modular-block:LTX2InContextCoreDenoiseStep:sha256:df883f87e496b42bdaae837541893640bb53260bce3584f3bfe42794c6d81203", "inputs": [ { "default": 1, @@ -39029,7 +39555,7 @@ }, "description": "", "name": "guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -39041,13 +39567,13 @@ }, "description": "", "name": "audio_guider", - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" } ], "configs": [], - "contentHash": "sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "contentHash": "sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "description": "Joint video+audio denoiser. Runs the transformer once per guidance pass (each a single batch), with each pass's conditioning assembled by the guiders via `prepare_inputs_from_block_state` (driven by `guider_input_fields`) and unioned across the video `guider` and audio `audio_guider`; the per-pass model flags (STG blocks, modality isolation) are set by identifier afterwards. Converts each pass's velocity to x0 and delegates the per-modality CFG + STG + modality-isolation combine to the two guiders.", - "id": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "id": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "inputs": [ { "default": null, @@ -66517,9 +67043,9 @@ "name": "boundary_ratio" } ], - "contentHash": "sha256:4b04d4db1a93b220bae125579bb5bc93ad47b82c60c30ca6b525f8da24f9c8be", + "contentHash": "sha256:0a2c1738dac8ca9e40db3d2d09a8e234ff787dcc6ae157a70b8143c48acdb025", "description": "Modular pipeline for text-to-video using Wan2.2.", - "id": "diffusers.modular-block:Wan22Blocks:sha256:4b04d4db1a93b220bae125579bb5bc93ad47b82c60c30ca6b525f8da24f9c8be", + "id": "diffusers.modular-block:Wan22Blocks:sha256:0a2c1738dac8ca9e40db3d2d09a8e234ff787dcc6ae157a70b8143c48acdb025", "inputs": [ { "default": null, @@ -66655,7 +67181,15 @@ "timesteps" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "Wan22CoreDenoiseStep", @@ -66707,9 +67241,9 @@ "name": "boundary_ratio" } ], - "contentHash": "sha256:470671f61d68338083a5a051eede55600400b681b46c7a8c1de6e185cf24d5cd", + "contentHash": "sha256:3bacfdc47bb5b52b1ff1f9444bfd0e8a0ab4b1b06a9f18e465d96dc577af4b7d", "description": "denoise block that takes encoded conditions and runs the denoising process.", - "id": "diffusers.modular-block:Wan22CoreDenoiseStep:sha256:470671f61d68338083a5a051eede55600400b681b46c7a8c1de6e185cf24d5cd", + "id": "diffusers.modular-block:Wan22CoreDenoiseStep:sha256:3bacfdc47bb5b52b1ff1f9444bfd0e8a0ab4b1b06a9f18e465d96dc577af4b7d", "inputs": [ { "default": 1, @@ -66829,7 +67363,15 @@ "timesteps" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "Wan22DenoiseStep", @@ -66881,9 +67423,9 @@ "name": "boundary_ratio" } ], - "contentHash": "sha256:74bb7f99087db12b178bb99d192ed7209986b8d27817263d0169cb0efb058b9d", + "contentHash": "sha256:217a54b74465756c8b2237e6903c0d035e80f340b4635ecfcbb7686aa65089cb", "description": "Denoise step that iteratively denoise the latents. Its loop logic is defined in `WanDenoiseLoopWrapper.__call__` method At each iteration, it runs blocks defined in `sub_blocks` sequentially: - `WanLoopBeforeDenoiser` - `Wan22LoopDenoiser` - `WanLoopAfterDenoiser` This block supports text-to-video tasks for Wan2.2.", - "id": "diffusers.modular-block:Wan22DenoiseStep:sha256:74bb7f99087db12b178bb99d192ed7209986b8d27817263d0169cb0efb058b9d", + "id": "diffusers.modular-block:Wan22DenoiseStep:sha256:217a54b74465756c8b2237e6903c0d035e80f340b4635ecfcbb7686aa65089cb", "inputs": [ { "default": null, @@ -66953,7 +67495,15 @@ "timesteps" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "Wan22Image2VideoBlocks", @@ -67035,9 +67585,9 @@ "name": "boundary_ratio" } ], - "contentHash": "sha256:5d82219c6b1b2062d207b874b81d456152260ff765b22c048a3115f864a180b5", + "contentHash": "sha256:3f74e974c46328f5d2cd26e8a2aea9a337efe0042ca7a14a5fc584b50579326e", "description": "Modular pipeline for image-to-video using Wan2.2.", - "id": "diffusers.modular-block:Wan22Image2VideoBlocks:sha256:5d82219c6b1b2062d207b874b81d456152260ff765b22c048a3115f864a180b5", + "id": "diffusers.modular-block:Wan22Image2VideoBlocks:sha256:3f74e974c46328f5d2cd26e8a2aea9a337efe0042ca7a14a5fc584b50579326e", "inputs": [ { "default": null, @@ -67185,7 +67735,15 @@ "timesteps" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "Wan22Image2VideoCoreDenoiseStep", @@ -67237,9 +67795,9 @@ "name": "boundary_ratio" } ], - "contentHash": "sha256:e9676ec2aef44a0c5cf4ec4e7dfc9a92e828b4eb9d545690a1f1d26566acc44c", + "contentHash": "sha256:08a8f09cd4578f1551be5e392c52132927ed79d46013563a518ec6e5ebaefa9e", "description": "denoise block that takes encoded text and image latent conditions and runs the denoising process.", - "id": "diffusers.modular-block:Wan22Image2VideoCoreDenoiseStep:sha256:e9676ec2aef44a0c5cf4ec4e7dfc9a92e828b4eb9d545690a1f1d26566acc44c", + "id": "diffusers.modular-block:Wan22Image2VideoCoreDenoiseStep:sha256:08a8f09cd4578f1551be5e392c52132927ed79d46013563a518ec6e5ebaefa9e", "inputs": [ { "default": 1, @@ -67368,7 +67926,15 @@ "timesteps" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "Wan22Image2VideoDenoiseStep", @@ -67420,9 +67986,9 @@ "name": "boundary_ratio" } ], - "contentHash": "sha256:9daec42f0095aefde5606e02f4873451109fede560e75212310e9885dc1b15db", + "contentHash": "sha256:c51d77edbdec4084a4542e734a31d1eedace36aeee6c048de29bfcd5351c942a", "description": "Denoise step that iteratively denoise the latents. Its loop logic is defined in `WanDenoiseLoopWrapper.__call__` method At each iteration, it runs blocks defined in `sub_blocks` sequentially: - `WanImage2VideoLoopBeforeDenoiser` - `WanLoopDenoiser` - `WanLoopAfterDenoiser` This block supports image-to-video tasks for Wan2.2.", - "id": "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:9daec42f0095aefde5606e02f4873451109fede560e75212310e9885dc1b15db", + "id": "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:c51d77edbdec4084a4542e734a31d1eedace36aeee6c048de29bfcd5351c942a", "inputs": [ { "default": null, @@ -67501,7 +68067,15 @@ "timesteps" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "Wan22LoopDenoiser", @@ -67546,9 +68120,9 @@ "name": "boundary_ratio" } ], - "contentHash": "sha256:a902a0d881cb4959492bfac103336d334a533be123a5d83fbc8fae4267d44ba5", + "contentHash": "sha256:194524add4567c94a2654a8d4ac2a0e4d67ccede3063e67e296a6a3ef54c90b3", "description": "Step within the denoising loop that denoise the latents with guidance. This block should be used to compose the `sub_blocks` attribute of a `LoopSequentialPipelineBlocks` object (e.g. `WanDenoiseLoopWrapper`)", - "id": "diffusers.modular-block:Wan22LoopDenoiser:sha256:a902a0d881cb4959492bfac103336d334a533be123a5d83fbc8fae4267d44ba5", + "id": "diffusers.modular-block:Wan22LoopDenoiser:sha256:194524add4567c94a2654a8d4ac2a0e4d67ccede3063e67e296a6a3ef54c90b3", "inputs": [ { "default": null, @@ -67591,7 +68165,15 @@ "prompt_embeds" ], "schemaVersion": 1, - "variadicInputs": [] + "variadicInputs": [ + { + "default": null, + "description": "conditional model inputs for the denoiser: e.g. prompt_embeds, negative_prompt_embeds, etc.", + "kwargsType": "denoiser_input_fields", + "required": false, + "type": "opaque" + } + ] }, { "className": "WanAdditionalInputsStep", @@ -74907,7 +75489,7 @@ "variadicInputs": [] } ], - "diffusersRevision": "2f7e0154a9db246e95c9ede43edba7db5b130805", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", "pipelines": [ { "conditionals": [ @@ -75561,11 +76143,11 @@ ] } ], - "contentHash": "sha256:37b979196f68587bb578076bf2bc1d3678fd030c51fc3ab86fa715856e2f463e", + "contentHash": "sha256:32e35b05f93646f709c49e323d7ecbdcc4b29a2cedc9c1b1d13718971d2186e3", "pipelineClass": "Cosmos3DistilledModularPipeline", "placements": [ { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "intermediateOutputs": [ "num_frames", "height", @@ -75618,7 +76200,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionCoreDenoiseStep:sha256:64b6bbf31747e1466d0030ffeb2a2b4f56850970a7b03293d4cb185d8b2cac2b", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionCoreDenoiseStep:sha256:871f15e52c6647c97b0099f93d318989365176b124d316fab0d9fc37a385f3b7", "intermediateOutputs": [ "cond_text_segment", "uncond_text_segment", @@ -75722,7 +76304,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "blockDefinitionId": "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "intermediateOutputs": [ "vision_tokens", "vision_timesteps", @@ -75753,7 +76335,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "intermediateOutputs": [ "velocity_vision", "velocity_sound", @@ -75792,20 +76374,20 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:Cosmos3DistilledBlocks:sha256:fccb35a6312fc84454744d027166d9b06d1f137630ad78f90b7bb5dc11befd6e", + "rootBlockDefinitionId": "diffusers.modular-block:Cosmos3DistilledBlocks:sha256:d1f005c6bddd5c3057580aad29b6b46659f35e4a1a2d2d2e7bd199ee923f8936", "schemaVersion": 1, "workflows": [ { "cases": [ { "activeLeafDefinitionIds": [ - "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "diffusers.modular-block:Cosmos3PrepareTextSegmentsStep:sha256:7c286f278534f6f15df85e22708d4ff5d4070d36aeda49e95a03b4b94c2ae0ea", "diffusers.modular-block:Cosmos3VisionPrepareLatentsStep:sha256:eb3e23123f654c6dd15aca923b1df5fb22d626b8eb77b31c6f0c99c3e6f9036a", "diffusers.modular-block:Cosmos3VisionPackSequenceStep:sha256:e6f15fe12a4e1f9a1205cdb3ce3c1b418977fef1525cdb48f5ca31a43f6fda9c", "diffusers.modular-block:Cosmos3VisionDenoiseInputStep:sha256:76277495e7bc79326a8b56720e089d27410dba20fda2ba772fd410ee1504a946", "diffusers.modular-block:Cosmos3DistilledSetTimestepsStep:sha256:ef99ab52dc5fe92f6a4998bfb9a735c407a1a37e3701f7d1cbbfebcf4010016b", - "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192" ], "activeLeafPaths": [ @@ -75861,13 +76443,13 @@ "cases": [ { "activeLeafDefinitionIds": [ - "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "diffusers.modular-block:Cosmos3PrepareTextSegmentsStep:sha256:7c286f278534f6f15df85e22708d4ff5d4070d36aeda49e95a03b4b94c2ae0ea", "diffusers.modular-block:Cosmos3VisionPrepareLatentsStep:sha256:eb3e23123f654c6dd15aca923b1df5fb22d626b8eb77b31c6f0c99c3e6f9036a", "diffusers.modular-block:Cosmos3VisionPackSequenceStep:sha256:e6f15fe12a4e1f9a1205cdb3ce3c1b418977fef1525cdb48f5ca31a43f6fda9c", "diffusers.modular-block:Cosmos3VisionDenoiseInputStep:sha256:76277495e7bc79326a8b56720e089d27410dba20fda2ba772fd410ee1504a946", "diffusers.modular-block:Cosmos3DistilledSetTimestepsStep:sha256:ef99ab52dc5fe92f6a4998bfb9a735c407a1a37e3701f7d1cbbfebcf4010016b", - "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192" ], "activeLeafPaths": [ @@ -75922,14 +76504,14 @@ "cases": [ { "activeLeafDefinitionIds": [ - "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "diffusers.modular-block:Cosmos3ImageVaeEncoderStep:sha256:5df05e87c8aa9fb2a852d0f46d59319dffc93380b2b0e4c35c3ada1337ecb68d", "diffusers.modular-block:Cosmos3PrepareTextSegmentsStep:sha256:7c286f278534f6f15df85e22708d4ff5d4070d36aeda49e95a03b4b94c2ae0ea", "diffusers.modular-block:Cosmos3VisionPrepareLatentsStep:sha256:eb3e23123f654c6dd15aca923b1df5fb22d626b8eb77b31c6f0c99c3e6f9036a", "diffusers.modular-block:Cosmos3VisionPackSequenceStep:sha256:e6f15fe12a4e1f9a1205cdb3ce3c1b418977fef1525cdb48f5ca31a43f6fda9c", "diffusers.modular-block:Cosmos3VisionDenoiseInputStep:sha256:76277495e7bc79326a8b56720e089d27410dba20fda2ba772fd410ee1504a946", "diffusers.modular-block:Cosmos3DistilledSetTimestepsStep:sha256:ef99ab52dc5fe92f6a4998bfb9a735c407a1a37e3701f7d1cbbfebcf4010016b", - "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192" ], "activeLeafPaths": [ @@ -75989,14 +76571,14 @@ "cases": [ { "activeLeafDefinitionIds": [ - "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:3db355c6b5f45d159832d4a7435d909f63943381ce04d80101e4d564ddac7c43", + "diffusers.modular-block:Cosmos3DistilledTextEncoderStep:sha256:7b912c41ea0391189a60e087600666a446f3031c5afab113a3c7c0568f5f9aab", "diffusers.modular-block:Cosmos3VideoVaeEncoderStep:sha256:0d992736a1e58c90922a180f3037d275af5567d81dfc0dcf06e96e565b1a8162", "diffusers.modular-block:Cosmos3PrepareTextSegmentsStep:sha256:7c286f278534f6f15df85e22708d4ff5d4070d36aeda49e95a03b4b94c2ae0ea", "diffusers.modular-block:Cosmos3VisionPrepareLatentsStep:sha256:eb3e23123f654c6dd15aca923b1df5fb22d626b8eb77b31c6f0c99c3e6f9036a", "diffusers.modular-block:Cosmos3VisionPackSequenceStep:sha256:e6f15fe12a4e1f9a1205cdb3ce3c1b418977fef1525cdb48f5ca31a43f6fda9c", "diffusers.modular-block:Cosmos3VisionDenoiseInputStep:sha256:76277495e7bc79326a8b56720e089d27410dba20fda2ba772fd410ee1504a946", "diffusers.modular-block:Cosmos3DistilledSetTimestepsStep:sha256:ef99ab52dc5fe92f6a4998bfb9a735c407a1a37e3701f7d1cbbfebcf4010016b", - "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:23405c2fef02104c54344b6d7378b4ec621b378f17ccf339f24da6b7a58ff660", + "diffusers.modular-block:Cosmos3DistilledVisionDenoiseStep:sha256:00b1e5253e49822003d7573180339214e6e6928e39ad788d2347583ca85f49bb", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192" ], "activeLeafPaths": [ @@ -76254,7 +76836,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3AutoCoreDenoiseStep:sha256:fc99a68cdb0e31902ef9f01573c3eff46771a725b15169b2a5627620ce751d92", + "blockDefinitionId": "diffusers.modular-block:Cosmos3AutoCoreDenoiseStep:sha256:0967677bf4a6c6eebd1eb7df68e26a01031167812d1a3273e9899c11a308715a", "branchNames": [ "transfer", "vision_sound_action", @@ -76397,7 +76979,7 @@ ] } ], - "contentHash": "sha256:7049dccf36c0ce3ed08a76a814b82718d280d394dadda214768f8c677fd6638b", + "contentHash": "sha256:76585d19a35550bfb1c14f90641c61fe52bb95687476f499a0626cc7325f9fd5", "pipelineClass": "Cosmos3OmniModularPipeline", "placements": [ { @@ -76561,7 +77143,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3AutoCoreDenoiseStep:sha256:fc99a68cdb0e31902ef9f01573c3eff46771a725b15169b2a5627620ce751d92", + "blockDefinitionId": "diffusers.modular-block:Cosmos3AutoCoreDenoiseStep:sha256:0967677bf4a6c6eebd1eb7df68e26a01031167812d1a3273e9899c11a308715a", "intermediateOutputs": [ "cond_text_segment", "uncond_text_segment", @@ -76623,7 +77205,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3TransferCoreDenoiseStep:sha256:d55fa64771e15541a9fb16d62911f338f21d9449bc8c64870fcddc6ab9e52564", + "blockDefinitionId": "diffusers.modular-block:Cosmos3TransferCoreDenoiseStep:sha256:a930f4dbaa91a9bd4dae5f269ece432169da71a03e6a990b11d09beb0f96c056", "intermediateOutputs": [ "cond_text_segment", "uncond_text_segment", @@ -76669,7 +77251,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3TransferChunkDenoiseStep:sha256:3501131b68144400ac4b3dbee497e76f6ff09bd8b8156630be60b3f96961f912", + "blockDefinitionId": "diffusers.modular-block:Cosmos3TransferChunkDenoiseStep:sha256:78ac114d84c5c92fc845abe0ab6e5d9287d0292c3e8e0ee202a30964cde36307", "intermediateOutputs": [ "control_latents", "x0_tokens_vision", @@ -76765,7 +77347,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3TransferDenoiseStep:sha256:d12de7fe34f32671edbcdf86e4ad4c6ab820b94c3720a46aa9cf059d4c9aea43", + "blockDefinitionId": "diffusers.modular-block:Cosmos3TransferDenoiseStep:sha256:0b2dcd8ff550b4907e527b43a5c5c9d2013976de4eac3fe957a0226cccb4ac70", "intermediateOutputs": [ "vision_tokens_full", "vision_tokens_target", @@ -76800,7 +77382,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3TransferLoopDenoiser:sha256:01bbd8101a3b437f9059bb0afe0895a4cf59c097acfd711530d8a77162f5d1b8", + "blockDefinitionId": "diffusers.modular-block:Cosmos3TransferLoopDenoiser:sha256:d7c064705cb2ae2a74c48a5c94c59dad948f288780d0b26bed25634d413a5126", "intermediateOutputs": [ "velocity" ], @@ -76845,7 +77427,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundActionCoreDenoiseStep:sha256:79aa26cb976843a2599ebcfeb7883bb21f3c1b610d50b33f5b38478ab7f69d4d", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundActionCoreDenoiseStep:sha256:e41714f334ad11e7157c930dd4eeaf9194e69ae7afaa52f5aaf225c3feb471fa", "intermediateOutputs": [ "cond_text_segment", "uncond_text_segment", @@ -77061,7 +77643,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundActionDenoiseStep:sha256:0ae18dd115e0fe496c430793ac4b99d575e4399f085d7a7ca75f55b64c3792df", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundActionDenoiseStep:sha256:44c2bbcd557d3144053a0b0f60c44da42f0c5b828ae2c9d127467ec165a9d65f", "intermediateOutputs": [ "vision_tokens", "vision_timesteps", @@ -77130,7 +77712,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "intermediateOutputs": [ "velocity_vision", "velocity_sound", @@ -77188,7 +77770,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionCoreDenoiseStep:sha256:4383e1d99f37bcdd022ec7e33cc8057a2633a5c937f691b7059bd28be6646268", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionCoreDenoiseStep:sha256:936cb25971084367e4a8c91518cd74cf31805c0d50a5ce338e69fcd072919eeb", "intermediateOutputs": [ "cond_text_segment", "uncond_text_segment", @@ -77350,7 +77932,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "intermediateOutputs": [ "vision_tokens", "vision_timesteps", @@ -77401,7 +77983,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "intermediateOutputs": [ "velocity_vision", "velocity_sound", @@ -77445,7 +78027,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundCoreDenoiseStep:sha256:b60a4942c2ff28e85194fc3e42bea6e7a63cb303b0ed7827046e91a37a83e39f", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundCoreDenoiseStep:sha256:6ab06dde2398549cd5156a29c7a04e164e81c6e971739bff11cb31cec6f61151", "intermediateOutputs": [ "cond_text_segment", "uncond_text_segment", @@ -77605,7 +78187,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "intermediateOutputs": [ "vision_tokens", "vision_timesteps", @@ -77656,7 +78238,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "intermediateOutputs": [ "velocity_vision", "velocity_sound", @@ -77700,7 +78282,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionCoreDenoiseStep:sha256:24ac92e641d21454b0de4a54f2379bae99cfa8f40aa6511a74eda95936f54cec", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionCoreDenoiseStep:sha256:434061ea2aaf636e50d15f23b7176d77dc9bdb6dccf3d386373707a1ffbe222c", "intermediateOutputs": [ "cond_text_segment", "uncond_text_segment", @@ -77806,7 +78388,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "blockDefinitionId": "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "intermediateOutputs": [ "vision_tokens", "vision_timesteps", @@ -77839,7 +78421,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:5cf19d60fcee34a0495ce241483eb05a186d5c7bdb3322459bf7fc4eb09d864a", + "blockDefinitionId": "diffusers.modular-block:Cosmos3LoopDenoiser:sha256:e1343dc2e03d8b4676a262da05547990ee3ba38593e88a1af5139d05d5b5be51", "intermediateOutputs": [ "velocity_vision", "velocity_sound", @@ -77963,7 +78545,7 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:Cosmos3OmniBlocks:sha256:78105cbc44829f30bb20d62798ec76f93c060d07e7cc27804ccfe5b054a2fddb", + "rootBlockDefinitionId": "diffusers.modular-block:Cosmos3OmniBlocks:sha256:369ff8a3907febd9973ba49af73dc0ca7ed73b5cbebca5781cb4e4c74dd53fd6", "schemaVersion": 1, "workflows": [ { @@ -77976,7 +78558,7 @@ "diffusers.modular-block:Cosmos3VisionPackSequenceStep:sha256:e6f15fe12a4e1f9a1205cdb3ce3c1b418977fef1525cdb48f5ca31a43f6fda9c", "diffusers.modular-block:Cosmos3VisionDenoiseInputStep:sha256:76277495e7bc79326a8b56720e089d27410dba20fda2ba772fd410ee1504a946", "diffusers.modular-block:Cosmos3SetTimestepsStep:sha256:b5264dfba554ec2a31192ebf04a557bc537d2a2a47be087a20513549bb5daf6a", - "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" ], @@ -78081,7 +78663,7 @@ "diffusers.modular-block:Cosmos3VisionPackSequenceStep:sha256:e6f15fe12a4e1f9a1205cdb3ce3c1b418977fef1525cdb48f5ca31a43f6fda9c", "diffusers.modular-block:Cosmos3VisionDenoiseInputStep:sha256:76277495e7bc79326a8b56720e089d27410dba20fda2ba772fd410ee1504a946", "diffusers.modular-block:Cosmos3SetTimestepsStep:sha256:b5264dfba554ec2a31192ebf04a557bc537d2a2a47be087a20513549bb5daf6a", - "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" ], @@ -78186,7 +78768,7 @@ "diffusers.modular-block:Cosmos3VisionPackSequenceStep:sha256:e6f15fe12a4e1f9a1205cdb3ce3c1b418977fef1525cdb48f5ca31a43f6fda9c", "diffusers.modular-block:Cosmos3VisionDenoiseInputStep:sha256:76277495e7bc79326a8b56720e089d27410dba20fda2ba772fd410ee1504a946", "diffusers.modular-block:Cosmos3SetTimestepsStep:sha256:b5264dfba554ec2a31192ebf04a557bc537d2a2a47be087a20513549bb5daf6a", - "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" ], @@ -78296,7 +78878,7 @@ "diffusers.modular-block:Cosmos3VisionPackSequenceStep:sha256:e6f15fe12a4e1f9a1205cdb3ce3c1b418977fef1525cdb48f5ca31a43f6fda9c", "diffusers.modular-block:Cosmos3VisionDenoiseInputStep:sha256:76277495e7bc79326a8b56720e089d27410dba20fda2ba772fd410ee1504a946", "diffusers.modular-block:Cosmos3SetTimestepsStep:sha256:b5264dfba554ec2a31192ebf04a557bc537d2a2a47be087a20513549bb5daf6a", - "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:e4ab8353177b71b6586fcd270a6280db5794b0979cbd36e8c7eb2ee1ac972f8f", + "diffusers.modular-block:Cosmos3VisionDenoiseStep:sha256:b87bf4cea690fc423db8ae37ea97996b820a4dcb517910dc78881efaacd509da", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" ], @@ -78408,7 +78990,7 @@ "diffusers.modular-block:Cosmos3SoundPrepareLatentsStep:sha256:40b635564c6c443152b0616db8cb13be7d5e3f2ccef5b5c345ff2ff736526962", "diffusers.modular-block:Cosmos3SoundPackSequenceStep:sha256:c53688e7e48a8b5404ed23c3e6dd3575147180df7c075fe6325ad83f4d5ddaec", "diffusers.modular-block:Cosmos3SoundDenoiseInputStep:sha256:a31545af3df6390555e7e757eea180e82ba8dbc444c03424ec335a33e4170439", - "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3SoundDecodeStep:sha256:ff040ab75e63e11be1a622540a6c096cc12f5c52e90a500e9173ed6797a7150a", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" @@ -78539,7 +79121,7 @@ "diffusers.modular-block:Cosmos3SoundPrepareLatentsStep:sha256:40b635564c6c443152b0616db8cb13be7d5e3f2ccef5b5c345ff2ff736526962", "diffusers.modular-block:Cosmos3SoundPackSequenceStep:sha256:c53688e7e48a8b5404ed23c3e6dd3575147180df7c075fe6325ad83f4d5ddaec", "diffusers.modular-block:Cosmos3SoundDenoiseInputStep:sha256:a31545af3df6390555e7e757eea180e82ba8dbc444c03424ec335a33e4170439", - "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3SoundDecodeStep:sha256:ff040ab75e63e11be1a622540a6c096cc12f5c52e90a500e9173ed6797a7150a", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" @@ -78675,7 +79257,7 @@ "diffusers.modular-block:Cosmos3SoundPrepareLatentsStep:sha256:40b635564c6c443152b0616db8cb13be7d5e3f2ccef5b5c345ff2ff736526962", "diffusers.modular-block:Cosmos3SoundPackSequenceStep:sha256:c53688e7e48a8b5404ed23c3e6dd3575147180df7c075fe6325ad83f4d5ddaec", "diffusers.modular-block:Cosmos3SoundDenoiseInputStep:sha256:a31545af3df6390555e7e757eea180e82ba8dbc444c03424ec335a33e4170439", - "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:bcf90b551ba752f30f4766e1c11b5d812361b33f8b347a948e97c66e67bfc6e8", + "diffusers.modular-block:Cosmos3VisionSoundDenoiseStep:sha256:2ab8f6b29642b0a804d7662f8d8c5f5fe5da4a23e757f8cd05a7338ded8637b1", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3SoundDecodeStep:sha256:ff040ab75e63e11be1a622540a6c096cc12f5c52e90a500e9173ed6797a7150a", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" @@ -78811,7 +79393,7 @@ "diffusers.modular-block:Cosmos3ActionPrepareLatentsStep:sha256:7cf3d9fdc1c45e1a748f4b846f7b428a80d68ead99721bf94d03354542b596f8", "diffusers.modular-block:Cosmos3ActionPackSequenceStep:sha256:796f643838f36f7a1cc8f8952c0575f3fd223c65d89a73727b6a84faf5e26771", "diffusers.modular-block:Cosmos3ActionDenoiseInputStep:sha256:c35e82e733ad11566d82c629f8ddd83aa9695dbe49251aaf24b63d97d8eb21a4", - "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" ], @@ -78939,7 +79521,7 @@ "diffusers.modular-block:Cosmos3ActionPrepareLatentsStep:sha256:7cf3d9fdc1c45e1a748f4b846f7b428a80d68ead99721bf94d03354542b596f8", "diffusers.modular-block:Cosmos3ActionPackSequenceStep:sha256:796f643838f36f7a1cc8f8952c0575f3fd223c65d89a73727b6a84faf5e26771", "diffusers.modular-block:Cosmos3ActionDenoiseInputStep:sha256:c35e82e733ad11566d82c629f8ddd83aa9695dbe49251aaf24b63d97d8eb21a4", - "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" ], @@ -79067,7 +79649,7 @@ "diffusers.modular-block:Cosmos3ActionPrepareLatentsStep:sha256:7cf3d9fdc1c45e1a748f4b846f7b428a80d68ead99721bf94d03354542b596f8", "diffusers.modular-block:Cosmos3ActionPackSequenceStep:sha256:796f643838f36f7a1cc8f8952c0575f3fd223c65d89a73727b6a84faf5e26771", "diffusers.modular-block:Cosmos3ActionDenoiseInputStep:sha256:c35e82e733ad11566d82c629f8ddd83aa9695dbe49251aaf24b63d97d8eb21a4", - "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:027c6265eb97489584fa28eda5b0cfe73a3caa006921fb0295b6013cda7926de", + "diffusers.modular-block:Cosmos3VisionActionDenoiseStep:sha256:d80e9a8b7b05eb5a33e735736931902bfbf0623ad327a81bf5236cfde6440fcc", "diffusers.modular-block:Cosmos3VideoDecodeStep:sha256:d3c8644adf9487abfa2fc82a88682e94cabecda8593c4313b064a5213b39a192", "diffusers.modular-block:Cosmos3ActionOutputStep:sha256:bc5da461ed267554b994d2d2e8e3b7a2e866e7d53cbf970460ac705658bce01b" ], @@ -79215,7 +79797,7 @@ ] } ], - "contentHash": "sha256:8a96a7c95ccf09f42aafcffeab8473ad0fd9a203c6d8264feb59fdd4a7a73797", + "contentHash": "sha256:112db8b1cab49af90fe2564dd75cd1711160c6e059d0b20d133b15e1ec5cc70d", "pipelineClass": "ErnieImageModularPipeline", "placements": [ { @@ -79384,6 +79966,7 @@ "cases": [ { "activeLeafDefinitionIds": [ + "diffusers.modular-block:ErnieImagePromptEnhancerStep:sha256:2531b7dd8052234fb8c4962ec02e751915e41d3148fb2388ec7feec4609b3b06", "diffusers.modular-block:ErnieImageTextEncoderStep:sha256:8078b53f5972c8b52d36a7cca8a4c41620d9506c459a8387d720771004f50bd8", "diffusers.modular-block:ErnieImageTextInputStep:sha256:65b6fe2d66bbd1646ec2f624ac4d7ee87d9ce35adfb2b858557f90f2618959b3", "diffusers.modular-block:ErnieImageSetTimestepsStep:sha256:633ae9bf079ef52fc99fec07c390f7412ac0d8c7d20d360545aa654df26b335b", @@ -79392,6 +79975,10 @@ "diffusers.modular-block:ErnieImageVaeDecoderStep:sha256:595c24f9238cd4d125eed61a386cac9005d445043a4eade77dded81217ce37e4" ], "activeLeafPaths": [ + [ + "prompt_enhancer", + "prompt_enhancer" + ], [ "text_encoder" ], @@ -79416,7 +80003,8 @@ ] ], "presentInputs": [ - "prompt" + "prompt", + "use_pe" ], "selections": [ { @@ -79424,7 +80012,7 @@ "path": [ "prompt_enhancer" ], - "selectedBlockName": null + "selectedBlockName": "prompt_enhancer" } ] } @@ -86742,7 +87330,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:0bc669ba1343885122beb0fee8c7d83492b972085c80b591d6f55e04232cbf2f", + "blockDefinitionId": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:387852bf402c69f82aad3f2e4e75da41e65c1f791e68b1fea84f88a357ccb904", "branchNames": [ "in_context", "condition", @@ -86934,7 +87522,7 @@ ] } ], - "contentHash": "sha256:ba4baf7de1b1f2442dc917d37796e8a5db392ed98173f9d47f7f2f523844cdf2", + "contentHash": "sha256:eb52b345488b03e1b95110c7ffab290d2a07f4d61aa9c463e0dbfc0db21a28d5", "pipelineClass": "LTX25ModularPipeline", "placements": [ { @@ -87145,7 +87733,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:0bc669ba1343885122beb0fee8c7d83492b972085c80b591d6f55e04232cbf2f", + "blockDefinitionId": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:387852bf402c69f82aad3f2e4e75da41e65c1f791e68b1fea84f88a357ccb904", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -87176,7 +87764,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2InContextCoreDenoiseStep:sha256:f24158809c78328798dcb1b0b030a52069b7c80ecf88ae17e07e228c92281d3f", + "blockDefinitionId": "diffusers.modular-block:LTX2InContextCoreDenoiseStep:sha256:df883f87e496b42bdaae837541893640bb53260bce3584f3bfe42794c6d81203", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -87315,7 +87903,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "intermediateOutputs": [], "legacyPath": "denoise.in_context.denoise", "order": 6, @@ -87338,7 +87926,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "intermediateOutputs": [], "legacyPath": "denoise.in_context.denoise.denoiser", "order": 1, @@ -87362,7 +87950,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionCoreDenoiseStep:sha256:3a2942d554bffefe97d23b7a56c375f3b0be7f9107d61d53ecba9d0fd93b809e", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionCoreDenoiseStep:sha256:6fb81dc68db19bf22dbcc5645106c0af68bf38c4a453f98c39dd756003c98383", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -87471,7 +88059,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "intermediateOutputs": [], "legacyPath": "denoise.condition.denoise", "order": 5, @@ -87494,7 +88082,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "intermediateOutputs": [], "legacyPath": "denoise.condition.denoise.denoiser", "order": 1, @@ -87518,7 +88106,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoCoreDenoiseStep:sha256:38af484d045a15c099d7b9a98b934e0bf8dddeb60fb5be4f978b16790f359921", + "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoCoreDenoiseStep:sha256:a632c7f4c984dc9ff07eb060258d6d7492ed509c4529396b02cce88c68b4c8cc", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -87634,7 +88222,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "intermediateOutputs": [], "legacyPath": "denoise.image2video.denoise", "order": 6, @@ -87657,7 +88245,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "intermediateOutputs": [], "legacyPath": "denoise.image2video.denoise.denoiser", "order": 1, @@ -87681,7 +88269,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2CoreDenoiseStep:sha256:ac71e8bacb814171b3eb9ad9d05f1b5d88dccaf502b1f453b7558c8877b5bf18", + "blockDefinitionId": "diffusers.modular-block:LTX2CoreDenoiseStep:sha256:d7f6a6cedb694be5f5eca1532d80e69b531162a33c1faec7884e18637e76b556", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -87782,7 +88370,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2DenoiseStep:sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "blockDefinitionId": "diffusers.modular-block:LTX2DenoiseStep:sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "intermediateOutputs": [], "legacyPath": "denoise.text2video.denoise", "order": 5, @@ -87805,7 +88393,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "intermediateOutputs": [], "legacyPath": "denoise.text2video.denoise.denoiser", "order": 1, @@ -87934,7 +88522,7 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:LTX25AutoBlocks:sha256:477021db418762ed44f5598bdc9bb25e8ed21e0d309ac117a899b46e63587694", + "rootBlockDefinitionId": "diffusers.modular-block:LTX25AutoBlocks:sha256:3f4ba9f7e0d988e2aa06c008b9b3db162ab9ac3803f2341d4d97e52018395538", "schemaVersion": 1, "workflows": [ { @@ -87949,7 +88537,7 @@ "diffusers.modular-block:LTX2PrepareLatentsStep:sha256:38851ce4f8fe3ec2b3510c981ee5cf928e746fa983c7fa0f8871f560dfad1f8a", "diffusers.modular-block:LTX2PrepareAudioLatentsStep:sha256:ec54dda599080d54fc884fe1c7ac83a17db6e5151e1aec39ca415cfc374a993f", "diffusers.modular-block:LTX2PrepareCoordsStep:sha256:f0f44a4549e12d132c8ab8e444d28288c2b72332d107211658aea16a5a2b4356", - "diffusers.modular-block:LTX2DenoiseStep:sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "diffusers.modular-block:LTX2DenoiseStep:sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "diffusers.modular-block:LTX2DiffusionVaeDecoderStep:sha256:e9c28196f0f21411af00b38199621c450203cf2a27909bcd417f36784fe92053", "diffusers.modular-block:LTX2AudioDecoderStep:sha256:74ff7c271d200c312dddfd7b7425b65a34e14e1ec6f8d3259cbb4ed406b7de17" ], @@ -88079,7 +88667,7 @@ "diffusers.modular-block:LTX2Image2VideoPrepareLatentsStep:sha256:1d19067d0eda2b4cf3f819859e7f96cccb278150157f700e8d566d793ada767c", "diffusers.modular-block:LTX2PrepareAudioLatentsStep:sha256:ec54dda599080d54fc884fe1c7ac83a17db6e5151e1aec39ca415cfc374a993f", "diffusers.modular-block:LTX2PrepareCoordsStep:sha256:f0f44a4549e12d132c8ab8e444d28288c2b72332d107211658aea16a5a2b4356", - "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "diffusers.modular-block:LTX2DiffusionVaeDecoderStep:sha256:e9c28196f0f21411af00b38199621c450203cf2a27909bcd417f36784fe92053", "diffusers.modular-block:LTX2AudioDecoderStep:sha256:74ff7c271d200c312dddfd7b7425b65a34e14e1ec6f8d3259cbb4ed406b7de17" ], @@ -88218,7 +88806,7 @@ "diffusers.modular-block:LTX2ConditionSetTimestepsStep:sha256:442af509b9639474fcfe9169c6f9e488ffc25cf4b0b67da73537bb2aadbf17e9", "diffusers.modular-block:LTX2ConditionPrepareAudioLatentsStep:sha256:a1cd137fba34089227604d4e7915f5b35664a8c5981e2ecaffab96b60ad06ede", "diffusers.modular-block:LTX2ConditionPrepareCoordsStep:sha256:b47a57267f0c3ff501ea2d4b5c787b97e98916063dd58687050317ad8ede9f6e", - "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "diffusers.modular-block:LTX2TrimConditionTokensStep:sha256:bbbcf6aa79fe6592b2dda8e26f9212527c04292970fcef4271e7926ad5c2f20c", "diffusers.modular-block:LTX2DiffusionVaeDecoderStep:sha256:e9c28196f0f21411af00b38199621c450203cf2a27909bcd417f36784fe92053", "diffusers.modular-block:LTX2AudioDecoderStep:sha256:74ff7c271d200c312dddfd7b7425b65a34e14e1ec6f8d3259cbb4ed406b7de17" @@ -88358,7 +88946,7 @@ "diffusers.modular-block:LTX2ConditionSetTimestepsStep:sha256:442af509b9639474fcfe9169c6f9e488ffc25cf4b0b67da73537bb2aadbf17e9", "diffusers.modular-block:LTX2ConditionPrepareAudioLatentsStep:sha256:a1cd137fba34089227604d4e7915f5b35664a8c5981e2ecaffab96b60ad06ede", "diffusers.modular-block:LTX2ConditionPrepareCoordsStep:sha256:b47a57267f0c3ff501ea2d4b5c787b97e98916063dd58687050317ad8ede9f6e", - "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "diffusers.modular-block:LTX2TrimConditionTokensStep:sha256:bbbcf6aa79fe6592b2dda8e26f9212527c04292970fcef4271e7926ad5c2f20c", "diffusers.modular-block:LTX2DiffusionVaeDecoderStep:sha256:e9c28196f0f21411af00b38199621c450203cf2a27909bcd417f36784fe92053", "diffusers.modular-block:LTX2AudioDecoderStep:sha256:74ff7c271d200c312dddfd7b7425b65a34e14e1ec6f8d3259cbb4ed406b7de17" @@ -88712,7 +89300,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:0bc669ba1343885122beb0fee8c7d83492b972085c80b591d6f55e04232cbf2f", + "blockDefinitionId": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:387852bf402c69f82aad3f2e4e75da41e65c1f791e68b1fea84f88a357ccb904", "branchNames": [ "in_context", "condition", @@ -88904,7 +89492,7 @@ ] } ], - "contentHash": "sha256:843635840f67f4082bbb88816e1c9586fbeb92a383827c1483834ec9d78b139a", + "contentHash": "sha256:e4accf85ea1c05861d9ef9bb161dcffeb86c926993fa69170575b8fe5d475bfa", "pipelineClass": "LTX2ModularPipeline", "placements": [ { @@ -89115,7 +89703,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:0bc669ba1343885122beb0fee8c7d83492b972085c80b591d6f55e04232cbf2f", + "blockDefinitionId": "diffusers.modular-block:LTX2AutoCoreDenoiseStep:sha256:387852bf402c69f82aad3f2e4e75da41e65c1f791e68b1fea84f88a357ccb904", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -89146,7 +89734,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2InContextCoreDenoiseStep:sha256:f24158809c78328798dcb1b0b030a52069b7c80ecf88ae17e07e228c92281d3f", + "blockDefinitionId": "diffusers.modular-block:LTX2InContextCoreDenoiseStep:sha256:df883f87e496b42bdaae837541893640bb53260bce3584f3bfe42794c6d81203", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -89285,7 +89873,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "intermediateOutputs": [], "legacyPath": "denoise.in_context.denoise", "order": 6, @@ -89308,7 +89896,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "intermediateOutputs": [], "legacyPath": "denoise.in_context.denoise.denoiser", "order": 1, @@ -89332,7 +89920,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionCoreDenoiseStep:sha256:3a2942d554bffefe97d23b7a56c375f3b0be7f9107d61d53ecba9d0fd93b809e", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionCoreDenoiseStep:sha256:6fb81dc68db19bf22dbcc5645106c0af68bf38c4a453f98c39dd756003c98383", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -89441,7 +90029,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "blockDefinitionId": "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "intermediateOutputs": [], "legacyPath": "denoise.condition.denoise", "order": 5, @@ -89464,7 +90052,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "intermediateOutputs": [], "legacyPath": "denoise.condition.denoise.denoiser", "order": 1, @@ -89488,7 +90076,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoCoreDenoiseStep:sha256:38af484d045a15c099d7b9a98b934e0bf8dddeb60fb5be4f978b16790f359921", + "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoCoreDenoiseStep:sha256:a632c7f4c984dc9ff07eb060258d6d7492ed509c4529396b02cce88c68b4c8cc", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -89604,7 +90192,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "blockDefinitionId": "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "intermediateOutputs": [], "legacyPath": "denoise.image2video.denoise", "order": 6, @@ -89627,7 +90215,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "intermediateOutputs": [], "legacyPath": "denoise.image2video.denoise.denoiser", "order": 1, @@ -89651,7 +90239,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2CoreDenoiseStep:sha256:ac71e8bacb814171b3eb9ad9d05f1b5d88dccaf502b1f453b7558c8877b5bf18", + "blockDefinitionId": "diffusers.modular-block:LTX2CoreDenoiseStep:sha256:d7f6a6cedb694be5f5eca1532d80e69b531162a33c1faec7884e18637e76b556", "intermediateOutputs": [ "connector_prompt_embeds", "connector_audio_prompt_embeds", @@ -89752,7 +90340,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2DenoiseStep:sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "blockDefinitionId": "diffusers.modular-block:LTX2DenoiseStep:sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "intermediateOutputs": [], "legacyPath": "denoise.text2video.denoise", "order": 5, @@ -89775,7 +90363,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:c1961b0df224676d8158184ebfa98a4347d5c01d87f2c90b576a11b23d98294b", + "blockDefinitionId": "diffusers.modular-block:LTX2LoopDenoiser:sha256:d931b1f5ea38660a04d89e86e4e51ffe956c1a00023045fa427e055fd6d42883", "intermediateOutputs": [], "legacyPath": "denoise.text2video.denoise.denoiser", "order": 1, @@ -89904,7 +90492,7 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:LTX2AutoBlocks:sha256:e5d47b6161c3e8d90740b45b07c56a749c86acc320a1508ea1d31bfa874b5943", + "rootBlockDefinitionId": "diffusers.modular-block:LTX2AutoBlocks:sha256:a9e4ca4d35ddb1fab0bfa097d09b045e781fbc3c6f077a953450239f523a2151", "schemaVersion": 1, "workflows": [ { @@ -89919,7 +90507,7 @@ "diffusers.modular-block:LTX2PrepareLatentsStep:sha256:38851ce4f8fe3ec2b3510c981ee5cf928e746fa983c7fa0f8871f560dfad1f8a", "diffusers.modular-block:LTX2PrepareAudioLatentsStep:sha256:ec54dda599080d54fc884fe1c7ac83a17db6e5151e1aec39ca415cfc374a993f", "diffusers.modular-block:LTX2PrepareCoordsStep:sha256:f0f44a4549e12d132c8ab8e444d28288c2b72332d107211658aea16a5a2b4356", - "diffusers.modular-block:LTX2DenoiseStep:sha256:7ddd8a9879dc1bdbe94e68252941f857f9ae8d0594d3bd2c0247e059cd6be9d4", + "diffusers.modular-block:LTX2DenoiseStep:sha256:4558ab1720e601a8ff548e32c21e61ec509bb13abae661b7c5cd54715a80639a", "diffusers.modular-block:LTX2VaeDecoderStep:sha256:6a09b4e125a2879d6f1b9ad83d669dc7a8f8362943dbf22ea14923ce2141dbef", "diffusers.modular-block:LTX2AudioDecoderStep:sha256:74ff7c271d200c312dddfd7b7425b65a34e14e1ec6f8d3259cbb4ed406b7de17" ], @@ -90049,7 +90637,7 @@ "diffusers.modular-block:LTX2Image2VideoPrepareLatentsStep:sha256:1d19067d0eda2b4cf3f819859e7f96cccb278150157f700e8d566d793ada767c", "diffusers.modular-block:LTX2PrepareAudioLatentsStep:sha256:ec54dda599080d54fc884fe1c7ac83a17db6e5151e1aec39ca415cfc374a993f", "diffusers.modular-block:LTX2PrepareCoordsStep:sha256:f0f44a4549e12d132c8ab8e444d28288c2b72332d107211658aea16a5a2b4356", - "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:5fad62ac7b55e595c34625db68f0a81a5e1418db27070e4e410b28324d0b51ee", + "diffusers.modular-block:LTX2Image2VideoDenoiseStep:sha256:8751d693fa581d9b3c2ef2ba83c2a091083e46a685d5a437318534b2c97d60e1", "diffusers.modular-block:LTX2VaeDecoderStep:sha256:6a09b4e125a2879d6f1b9ad83d669dc7a8f8362943dbf22ea14923ce2141dbef", "diffusers.modular-block:LTX2AudioDecoderStep:sha256:74ff7c271d200c312dddfd7b7425b65a34e14e1ec6f8d3259cbb4ed406b7de17" ], @@ -90188,7 +90776,7 @@ "diffusers.modular-block:LTX2ConditionSetTimestepsStep:sha256:442af509b9639474fcfe9169c6f9e488ffc25cf4b0b67da73537bb2aadbf17e9", "diffusers.modular-block:LTX2ConditionPrepareAudioLatentsStep:sha256:a1cd137fba34089227604d4e7915f5b35664a8c5981e2ecaffab96b60ad06ede", "diffusers.modular-block:LTX2ConditionPrepareCoordsStep:sha256:b47a57267f0c3ff501ea2d4b5c787b97e98916063dd58687050317ad8ede9f6e", - "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "diffusers.modular-block:LTX2TrimConditionTokensStep:sha256:bbbcf6aa79fe6592b2dda8e26f9212527c04292970fcef4271e7926ad5c2f20c", "diffusers.modular-block:LTX2VaeDecoderStep:sha256:6a09b4e125a2879d6f1b9ad83d669dc7a8f8362943dbf22ea14923ce2141dbef", "diffusers.modular-block:LTX2AudioDecoderStep:sha256:74ff7c271d200c312dddfd7b7425b65a34e14e1ec6f8d3259cbb4ed406b7de17" @@ -90328,7 +90916,7 @@ "diffusers.modular-block:LTX2ConditionSetTimestepsStep:sha256:442af509b9639474fcfe9169c6f9e488ffc25cf4b0b67da73537bb2aadbf17e9", "diffusers.modular-block:LTX2ConditionPrepareAudioLatentsStep:sha256:a1cd137fba34089227604d4e7915f5b35664a8c5981e2ecaffab96b60ad06ede", "diffusers.modular-block:LTX2ConditionPrepareCoordsStep:sha256:b47a57267f0c3ff501ea2d4b5c787b97e98916063dd58687050317ad8ede9f6e", - "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:950b2ad05ecd656132f0e50b6431d229440f0693512233806e6937aa3f36ecc7", + "diffusers.modular-block:LTX2ConditionDenoiseStep:sha256:4e2636c352626a765e846ca924e6a311a4f157b253e65f00198812c21de62552", "diffusers.modular-block:LTX2TrimConditionTokensStep:sha256:bbbcf6aa79fe6592b2dda8e26f9212527c04292970fcef4271e7926ad5c2f20c", "diffusers.modular-block:LTX2VaeDecoderStep:sha256:6a09b4e125a2879d6f1b9ad83d669dc7a8f8362943dbf22ea14923ce2141dbef", "diffusers.modular-block:LTX2AudioDecoderStep:sha256:74ff7c271d200c312dddfd7b7425b65a34e14e1ec6f8d3259cbb4ed406b7de17" @@ -100952,7 +101540,7 @@ }, { "conditionals": [], - "contentHash": "sha256:8b9e742bf4e0688ef7f4f2b0aeaa4ac9f03cdd43e62a83275507add2cd9c7b59", + "contentHash": "sha256:7d8f02560be1cf5670e66cd89c60babe23b1b3bf68ce94c21371359c882b29bb", "pipelineClass": "Wan22Image2VideoModularPipeline", "placements": [ { @@ -101017,7 +101605,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22Image2VideoCoreDenoiseStep:sha256:e9676ec2aef44a0c5cf4ec4e7dfc9a92e828b4eb9d545690a1f1d26566acc44c", + "blockDefinitionId": "diffusers.modular-block:Wan22Image2VideoCoreDenoiseStep:sha256:08a8f09cd4578f1551be5e392c52132927ed79d46013563a518ec6e5ebaefa9e", "intermediateOutputs": [ "batch_size", "dtype", @@ -101075,7 +101663,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:9daec42f0095aefde5606e02f4873451109fede560e75212310e9885dc1b15db", + "blockDefinitionId": "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:c51d77edbdec4084a4542e734a31d1eedace36aeee6c048de29bfcd5351c942a", "intermediateOutputs": [], "legacyPath": "denoise.denoise", "order": 4, @@ -101096,7 +101684,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22LoopDenoiser:sha256:a902a0d881cb4959492bfac103336d334a533be123a5d83fbc8fae4267d44ba5", + "blockDefinitionId": "diffusers.modular-block:Wan22LoopDenoiser:sha256:194524add4567c94a2654a8d4ac2a0e4d67ccede3063e67e296a6a3ef54c90b3", "intermediateOutputs": [], "legacyPath": "denoise.denoise.denoiser", "order": 1, @@ -101129,7 +101717,7 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:Wan22Image2VideoBlocks:sha256:5d82219c6b1b2062d207b874b81d456152260ff765b22c048a3115f864a180b5", + "rootBlockDefinitionId": "diffusers.modular-block:Wan22Image2VideoBlocks:sha256:3f74e974c46328f5d2cd26e8a2aea9a337efe0042ca7a14a5fc584b50579326e", "schemaVersion": 1, "workflows": [ { @@ -101144,7 +101732,7 @@ "diffusers.modular-block:WanAdditionalInputsStep:sha256:0e8466078ba357fdb890ebefd7a7af82afbb1a9858558689d324763082ae38ff", "diffusers.modular-block:WanSetTimestepsStep:sha256:1fdb6df6d9234985e800870f50949a9d6887c722403724d91742d2466bccc7bb", "diffusers.modular-block:WanPrepareLatentsStep:sha256:a1ab70a6b56beaf8fbc8ad13f108513c0c1762e6809cdd8d70e8de32335c228c", - "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:9daec42f0095aefde5606e02f4873451109fede560e75212310e9885dc1b15db", + "diffusers.modular-block:Wan22Image2VideoDenoiseStep:sha256:c51d77edbdec4084a4542e734a31d1eedace36aeee6c048de29bfcd5351c942a", "diffusers.modular-block:WanVaeDecoderStep:sha256:25745045203f25f197764c50e17110e177636541705de31ad069576eabdde511" ], "activeLeafPaths": [ @@ -101197,7 +101785,7 @@ }, { "conditionals": [], - "contentHash": "sha256:22802a1b526a03dfb99a1edb98a7ba05629977fc2eb179277285d9419fbcfbc1", + "contentHash": "sha256:20a50179c714006897d2aeaf03d7e9d2eee46c960b1b3bb1331f900dc0a6e615", "pipelineClass": "Wan22ModularPipeline", "placements": [ { @@ -101213,7 +101801,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22CoreDenoiseStep:sha256:470671f61d68338083a5a051eede55600400b681b46c7a8c1de6e185cf24d5cd", + "blockDefinitionId": "diffusers.modular-block:Wan22CoreDenoiseStep:sha256:3bacfdc47bb5b52b1ff1f9444bfd0e8a0ab4b1b06a9f18e465d96dc577af4b7d", "intermediateOutputs": [ "batch_size", "dtype", @@ -101261,7 +101849,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22DenoiseStep:sha256:74bb7f99087db12b178bb99d192ed7209986b8d27817263d0169cb0efb058b9d", + "blockDefinitionId": "diffusers.modular-block:Wan22DenoiseStep:sha256:217a54b74465756c8b2237e6903c0d035e80f340b4635ecfcbb7686aa65089cb", "intermediateOutputs": [], "legacyPath": "denoise.denoise", "order": 3, @@ -101282,7 +101870,7 @@ ] }, { - "blockDefinitionId": "diffusers.modular-block:Wan22LoopDenoiser:sha256:a902a0d881cb4959492bfac103336d334a533be123a5d83fbc8fae4267d44ba5", + "blockDefinitionId": "diffusers.modular-block:Wan22LoopDenoiser:sha256:194524add4567c94a2654a8d4ac2a0e4d67ccede3063e67e296a6a3ef54c90b3", "intermediateOutputs": [], "legacyPath": "denoise.denoise.denoiser", "order": 1, @@ -101315,7 +101903,7 @@ ] } ], - "rootBlockDefinitionId": "diffusers.modular-block:Wan22Blocks:sha256:4b04d4db1a93b220bae125579bb5bc93ad47b82c60c30ca6b525f8da24f9c8be", + "rootBlockDefinitionId": "diffusers.modular-block:Wan22Blocks:sha256:0a2c1738dac8ca9e40db3d2d09a8e234ff787dcc6ae157a70b8143c48acdb025", "schemaVersion": 1, "workflows": [ { @@ -101326,7 +101914,7 @@ "diffusers.modular-block:WanTextInputStep:sha256:d49e890e0f66be55b833da64cbf0cb312089c55f6f01cbcf250fbc52be519bca", "diffusers.modular-block:WanSetTimestepsStep:sha256:1fdb6df6d9234985e800870f50949a9d6887c722403724d91742d2466bccc7bb", "diffusers.modular-block:WanPrepareLatentsStep:sha256:a1ab70a6b56beaf8fbc8ad13f108513c0c1762e6809cdd8d70e8de32335c228c", - "diffusers.modular-block:Wan22DenoiseStep:sha256:74bb7f99087db12b178bb99d192ed7209986b8d27817263d0169cb0efb058b9d", + "diffusers.modular-block:Wan22DenoiseStep:sha256:217a54b74465756c8b2237e6903c0d035e80f340b4635ecfcbb7686aa65089cb", "diffusers.modular-block:WanVaeDecoderStep:sha256:25745045203f25f197764c50e17110e177636541705de31ad069576eabdde511" ], "activeLeafPaths": [ diff --git a/data/modular-workflow-contracts.json b/data/modular-workflow-contracts.json index b19b1ed4..c689dcbf 100644 --- a/data/modular-workflow-contracts.json +++ b/data/modular-workflow-contracts.json @@ -892,11 +892,11 @@ "type": "builtins.float" }, { - "default": true, + "default": null, "description": "Whether to prepend the Cosmos3 system prompt.", "name": "use_system_prompt", "required": false, - "type": "builtins.bool" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { "default": true, @@ -954,6 +954,34 @@ "required": false, "type": "builtins.float" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": "pil", "description": "Output format: 'pil', 'np', 'pt'.", @@ -1180,6 +1208,10 @@ "generator", "num_inference_steps", "guidance_scale", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "output_type" ], @@ -1293,11 +1325,11 @@ "type": "builtins.float" }, { - "default": true, + "default": null, "description": "Whether to prepend the Cosmos3 system prompt.", "name": "use_system_prompt", "required": false, - "type": "builtins.bool" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { "default": true, @@ -1355,6 +1387,34 @@ "required": false, "type": "builtins.float" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": "pil", "description": "Output format: 'pil', 'np', 'pt'.", @@ -1581,6 +1641,10 @@ "generator", "num_inference_steps", "guidance_scale", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "output_type" ], @@ -1694,11 +1758,11 @@ "type": "builtins.float" }, { - "default": true, + "default": null, "description": "Whether to prepend the Cosmos3 system prompt.", "name": "use_system_prompt", "required": false, - "type": "builtins.bool" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { "default": true, @@ -1749,6 +1813,34 @@ "required": false, "type": "builtins.float" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": "pil", "description": "Output format: 'pil', 'np', 'pt'.", @@ -1989,6 +2081,10 @@ "generator", "num_inference_steps", "guidance_scale", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "output_type" ], @@ -2108,11 +2204,11 @@ "type": "builtins.float" }, { - "default": true, + "default": null, "description": "Whether to prepend the Cosmos3 system prompt.", "name": "use_system_prompt", "required": false, - "type": "builtins.bool" + "type": "UnionType[builtins.NoneType,builtins.bool]" }, { "default": true, @@ -2180,6 +2276,34 @@ "required": false, "type": "builtins.float" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": "pil", "description": "Output format: 'pil', 'np', 'pt'.", @@ -2422,6 +2546,10 @@ "generator", "num_inference_steps", "guidance_scale", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "output_type" ], @@ -2693,6 +2821,34 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -2941,6 +3097,10 @@ "latents", "generator", "num_inference_steps", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type", @@ -3126,6 +3286,34 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -3374,6 +3562,10 @@ "latents", "generator", "num_inference_steps", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type", @@ -3552,6 +3744,34 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -3814,6 +4034,10 @@ "latents", "generator", "num_inference_steps", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type", @@ -4015,6 +4239,34 @@ "required": true, "type": "builtins.int" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -4279,6 +4531,10 @@ "latents", "generator", "num_inference_steps", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type", @@ -4477,6 +4733,34 @@ "required": false, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -4796,6 +5080,10 @@ "generator", "num_inference_steps", "sound_latents", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type", @@ -5017,6 +5305,34 @@ "required": false, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -5350,6 +5666,10 @@ "generator", "num_inference_steps", "sound_latents", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type", @@ -5594,6 +5914,34 @@ "required": false, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -5929,6 +6277,10 @@ "generator", "num_inference_steps", "sound_latents", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type", @@ -6156,6 +6508,34 @@ "required": false, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -6475,6 +6855,10 @@ "generator", "num_inference_steps", "action_latents", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type" @@ -6693,6 +7077,34 @@ "required": false, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -7012,6 +7424,10 @@ "generator", "num_inference_steps", "action_latents", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type" @@ -7230,6 +7646,34 @@ "required": false, "type": "torch.Tensor" }, + { + "default": null, + "description": "None follows the ModelOpt FP8 checkpoint schedule; 'none' keeps the native quantized forward; 'fp8' is ModelOpt FP8 only.", + "name": "mixed_precision_format", + "required": false, + "type": "builtins.str" + }, + { + "default": null, + "description": "Optional leading W8A16 step count.", + "name": "mixed_precision_first_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional trailing W8A16 step count.", + "name": "mixed_precision_last_steps", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional reasoner path: 'high_precision' (W8A16) or 'base_precision' (native W8A8).", + "name": "mixed_precision_reasoner_policy", + "required": false, + "type": "builtins.str" + }, { "default": 6.0, "description": "Scale for classifier-free guidance.", @@ -7549,6 +7993,10 @@ "generator", "num_inference_steps", "action_latents", + "mixed_precision_format", + "mixed_precision_first_steps", + "mixed_precision_last_steps", + "mixed_precision_reasoner_policy", "denoiser_input_fields", "guidance_scale", "output_type" @@ -7818,6 +8266,41 @@ "required": true, "type": "builtins.str" }, + { + "default": null, + "description": "The height in pixels of the generated image.", + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "The width in pixels of the generated image.", + "name": "width", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "Optional system prompt passed to the prompt enhancer.", + "name": "pe_system_prompt", + "required": false, + "type": "builtins.str" + }, + { + "default": 0.6, + "description": "Sampling temperature used when generating with the prompt enhancer.", + "name": "pe_temperature", + "required": false, + "type": "builtins.float" + }, + { + "default": 0.95, + "description": "Nucleus sampling `top_p` used when generating with the prompt enhancer.", + "name": "pe_top_p", + "required": false, + "type": "builtins.float" + }, { "default": null, "description": "The prompt or prompts to avoid during image generation.", @@ -7839,20 +8322,6 @@ "required": false, "type": "builtins.int" }, - { - "default": null, - "description": "The height in pixels of the generated image.", - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The width in pixels of the generated image.", - "name": "width", - "required": false, - "type": "builtins.int" - }, { "default": null, "description": "Pre-generated noisy latents. If provided, skips noise sampling.", @@ -7878,6 +8347,27 @@ "kind": "sequential", "label": "Text To Image", "outputs": [ + { + "default": null, + "description": "The prompt list after prompt-enhancer rewriting.", + "name": "prompt", + "required": false, + "type": "builtins.list" + }, + { + "default": null, + "description": "The resolved image height in pixels.", + "name": "height", + "required": false, + "type": "builtins.int" + }, + { + "default": null, + "description": "The resolved image width in pixels.", + "name": "width", + "required": false, + "type": "builtins.int" + }, { "default": null, "description": "List of per-prompt text embeddings of shape (T, H).", @@ -7948,20 +8438,6 @@ "required": false, "type": "torch.Tensor" }, - { - "default": null, - "description": "The resolved image height in pixels.", - "name": "height", - "required": false, - "type": "builtins.int" - }, - { - "default": null, - "description": "The resolved image width in pixels.", - "name": "width", - "required": false, - "type": "builtins.int" - }, { "default": null, "description": "The generated images.", @@ -7975,16 +8451,25 @@ ], "stateKeys": [ "prompt", + "height", + "width", + "pe_system_prompt", + "pe_temperature", + "pe_top_p", "negative_prompt", "num_images_per_prompt", "num_inference_steps", - "height", - "width", "latents", "generator", "output_type" ], "steps": [ + { + "className": "ErnieImagePromptEnhancerStep", + "description": "Prompt enhancer step that rewrites the input prompt using a causal language model (PE).", + "kind": "block", + "path": "prompt_enhancer" + }, { "className": "ErnieImageTextEncoderStep", "description": "Text encoder step that encodes prompts into variable-length hidden states for the ErnieImage transformer.", @@ -17291,7 +17776,7 @@ "device", "offloadMode" ], - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -17304,7 +17789,7 @@ "device", "offloadMode" ], - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_pretrained", @@ -19440,7 +19925,7 @@ "device", "offloadMode" ], - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_config", @@ -19453,7 +19938,7 @@ "device", "offloadMode" ], - "type": "diffusers.modular_pipelines.ltx2.guider.LTX2Guidance" + "type": "diffusers.guiders.ltx2_guidance.LTX2Guidance" }, { "creationMethod": "from_pretrained", @@ -38903,6 +39388,7 @@ "sigmas", "latents", "attention_kwargs", + "denoiser_input_fields", "output_type" ], "steps": [ @@ -39280,6 +39766,7 @@ "latents", "generator", "attention_kwargs", + "denoiser_input_fields", "output_type" ], "steps": [ @@ -42064,6 +42551,6 @@ ] } ], - "diffusersRevision": "2f7e0154a9db246e95c9ede43edba7db5b130805", + "diffusersRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", "schemaVersion": 1 } diff --git a/data/research/comfy-contract-resolution.v1.json b/data/research/comfy-contract-resolution.v1.json index 3aeb4090..24afe071 100644 --- a/data/research/comfy-contract-resolution.v1.json +++ b/data/research/comfy-contract-resolution.v1.json @@ -13,7 +13,7 @@ "publishesTemplates": false, "researchOnly": true }, - "contentHash": "sha256:f384b3a0584f22639360d56e0aedca56bfeb7e3f0361180e08fd1da4a065627b", + "contentHash": "sha256:425d20ad21a3c8b86d66d7e254f65be47a98999758f5ffdf71e0ea79c8725214", "contractKind": "non_executable_comfy_contract_resolution_ledger", "resolutions": [ { @@ -16499,14 +16499,14 @@ "sha256": "sha256:d743007946b424026f86f0890bf7926953ad4808512b91018d100b4ea2940c75" }, "templateAuthoringSpecs": { - "contentHash": "sha256:909079e62e5ec735dce4a803a5357db97e39c2553588d88138363702137ef7ca", + "contentHash": "sha256:f7b2e5cdf11ffe88f38d298357d48641275c69d240ff13cf6b3d39eee3cdc522", "path": "data/template-authoring-specs.v1.json", - "sha256": "sha256:8b5a1bbdf00c9d1dde82ab1a9b8018c03a58ba83c15c8853e88dc860a1e078e4" + "sha256": "sha256:b4fa65929304f22cba6f6d13bb138f7188d49115f81a859b29b9b603614d2f4b" }, "upstreamCoverage": { - "contentHash": "sha256:2c558df6dcb1cebf1649da457b91a74b7cff05ff6f072dc3f1cf534a500b9b34", + "contentHash": "sha256:a7d36b205241f21d4067de7cc3b877c2c29538d3e2a2d965c03d670b9135f9ca", "path": "data/upstream-coverage.v1.json", - "sha256": "sha256:5f226a3c590a87583853d051f9d995983bfed8b55606abf119f630ab26ec3dfc" + "sha256": "sha256:6f5d0b3551c5c4c8e4f109947c8527c23f9c04d7d595ed134e2d8c82a6005cf0" }, "workflowManifest": { "path": "data/workflow-library-manifest.json", diff --git a/data/research/comfy-evidence-resolution.v1.json b/data/research/comfy-evidence-resolution.v1.json index 8f5bc86d..2131e5d8 100644 --- a/data/research/comfy-evidence-resolution.v1.json +++ b/data/research/comfy-evidence-resolution.v1.json @@ -16,7 +16,7 @@ "presentationMediaHints" ] }, - "contentHash": "sha256:118e57eb8f2993a1e0981c2a733ba2bed5826ab49e7109289e8b83a8ff065624", + "contentHash": "sha256:f7fc575517953e6a7c0296851e9dde211de498a92aca36696867a50512f0634c", "contractKind": "non_executable_comfy_evidence_resolution_ledger", "resolutions": [ { @@ -7483,14 +7483,14 @@ "sha256": "sha256:d743007946b424026f86f0890bf7926953ad4808512b91018d100b4ea2940c75" }, "templateAuthoringSpecs": { - "contentHash": "sha256:909079e62e5ec735dce4a803a5357db97e39c2553588d88138363702137ef7ca", + "contentHash": "sha256:f7b2e5cdf11ffe88f38d298357d48641275c69d240ff13cf6b3d39eee3cdc522", "path": "data/template-authoring-specs.v1.json", - "sha256": "sha256:8b5a1bbdf00c9d1dde82ab1a9b8018c03a58ba83c15c8853e88dc860a1e078e4" + "sha256": "sha256:b4fa65929304f22cba6f6d13bb138f7188d49115f81a859b29b9b603614d2f4b" }, "upstreamCoverage": { - "contentHash": "sha256:2c558df6dcb1cebf1649da457b91a74b7cff05ff6f072dc3f1cf534a500b9b34", + "contentHash": "sha256:a7d36b205241f21d4067de7cc3b877c2c29538d3e2a2d965c03d670b9135f9ca", "path": "data/upstream-coverage.v1.json", - "sha256": "sha256:5f226a3c590a87583853d051f9d995983bfed8b55606abf119f630ab26ec3dfc" + "sha256": "sha256:6f5d0b3551c5c4c8e4f109947c8527c23f9c04d7d595ed134e2d8c82a6005cf0" }, "workflowManifest": { "path": "data/workflow-library-manifest.json", diff --git a/data/template-authoring-specs.v1.json b/data/template-authoring-specs.v1.json index b2037660..f48a7d05 100644 --- a/data/template-authoring-specs.v1.json +++ b/data/template-authoring-specs.v1.json @@ -14,14 +14,14 @@ "selectsInputAssets": false, "studioVisible": false }, - "contentHash": "sha256:909079e62e5ec735dce4a803a5357db97e39c2553588d88138363702137ef7ca", + "contentHash": "sha256:f7b2e5cdf11ffe88f38d298357d48641275c69d240ff13cf6b3d39eee3cdc522", "contractKind": "non_public_template_authoring_spec_ledger", "schemaVersion": 1, "sources": { "candidateContracts": { - "contentHash": "sha256:d68f28dbb0d2b1c8a48cf969763ed855812fa56ff5d21920713eabea4d235012", + "contentHash": "sha256:419ecd358329d4ee720c319bb634598870f8cd463fd3f832f0f23ff1b7818679", "path": "data/template-candidate-contracts.v1.json", - "sha256": "sha256:735b1fc8ecbe7c61caba9bd8f3154d6f91544ee1a71ec7f1a983b4bdb3da43c9" + "sha256": "sha256:61546d67c49ea8dfc089522fb222a6a024750fb11aea22df3274cbbf41da2e05" }, "workflowManifest": { "path": "data/workflow-library-manifest.json", diff --git a/data/template-candidate-contracts.v1.json b/data/template-candidate-contracts.v1.json index c0ce4474..189306ed 100644 --- a/data/template-candidate-contracts.v1.json +++ b/data/template-candidate-contracts.v1.json @@ -11,7 +11,7 @@ "qualificationMetadataMeaning": "exact_manifest_binding_not_candidate_evidence", "studioVisible": false }, - "contentHash": "sha256:d68f28dbb0d2b1c8a48cf969763ed855812fa56ff5d21920713eabea4d235012", + "contentHash": "sha256:419ecd358329d4ee720c319bb634598870f8cd463fd3f832f0f23ff1b7818679", "contractKind": "non_public_template_candidate_contract_ledger", "contracts": [ { @@ -6687,9 +6687,9 @@ "sha256": "sha256:f278e24ed314a1ab997b5521cc4c8b9576c67cdbbe76e19ffa3ce58a4593dc0e" }, "upstreamCoverage": { - "contentHash": "sha256:2c558df6dcb1cebf1649da457b91a74b7cff05ff6f072dc3f1cf534a500b9b34", + "contentHash": "sha256:a7d36b205241f21d4067de7cc3b877c2c29538d3e2a2d965c03d670b9135f9ca", "path": "data/upstream-coverage.v1.json", - "sha256": "sha256:5f226a3c590a87583853d051f9d995983bfed8b55606abf119f630ab26ec3dfc" + "sha256": "sha256:6f5d0b3551c5c4c8e4f109947c8527c23f9c04d7d595ed134e2d8c82a6005cf0" }, "workflowManifest": { "path": "data/workflow-library-manifest.json", diff --git a/data/upstream-coverage.v1.json b/data/upstream-coverage.v1.json index cb5a59c4..5af91ae4 100644 --- a/data/upstream-coverage.v1.json +++ b/data/upstream-coverage.v1.json @@ -2339,7 +2339,7 @@ "status": "executable" } ], - "contentHash": "sha256:2c558df6dcb1cebf1649da457b91a74b7cff05ff6f072dc3f1cf534a500b9b34", + "contentHash": "sha256:a7d36b205241f21d4067de7cc3b877c2c29538d3e2a2d965c03d670b9135f9ca", "diffusersPipelines": [ { "artifactReviews": [], @@ -3230,17 +3230,21 @@ }, { "artifactReviews": [], - "equivalentTo": [ - "ErnieImagePipeline" + "equivalentTo": [], + "exactExecutionSpecs": [ + { + "id": "ernie-image-turbo:modular-text-to-image:v1", + "mode": "text_to_image", + "modelType": "ErnieImageModularPipeline" + } ], - "exactExecutionSpecs": [], "modularWorkflowIds": [ "text_to_image" ], "name": "ErnieImageModularPipeline", - "reason": "The public task surface is routed through the listed exact adapter class or classes; this is not a claim of byte-identical outputs or parity with class-specific optional features.", + "reason": "At least one exact backend execution specification selects this upstream class.", "reviewDecision": null, - "status": "equivalent" + "status": "executable" }, { "artifactReviews": [ @@ -3252,11 +3256,6 @@ "id": "ernie-image-turbo:text-to-image:v1", "mode": "text_to_image", "modelType": "ErnieImagePipeline" - }, - { - "id": "ernie-image:equivalent-standard-text-to-image:v1", - "mode": "text_to_image", - "modelType": "ErnieImageModularPipeline" } ], "modularWorkflowIds": [], @@ -4677,6 +4676,26 @@ "reviewDecision": null, "status": "executable" }, + { + "artifactReviews": [], + "equivalentTo": [], + "exactExecutionSpecs": [], + "modularWorkflowIds": [], + "name": "LTX2DFRPipeline", + "reason": "Video diffusion-frame refinement is deferred from the current image/audio release scope.", + "reviewDecision": null, + "status": "intentionally-excluded" + }, + { + "artifactReviews": [], + "equivalentTo": [], + "exactExecutionSpecs": [], + "modularWorkflowIds": [], + "name": "LTX2DFRTemporalRefinePipeline", + "reason": "Video temporal refinement is deferred from the current image/audio release scope.", + "reviewDecision": null, + "status": "intentionally-excluded" + }, { "artifactReviews": [], "equivalentTo": [], @@ -5417,6 +5436,32 @@ "reviewDecision": null, "status": "executable" }, + { + "artifactReviews": [], + "equivalentTo": [], + "exactExecutionSpecs": [ + { + "id": "qwen-image-21:edit-image:v1", + "mode": "edit_image", + "modelType": "QwenImage21Pipeline" + }, + { + "id": "qwen-image-21:multi-image-reference-edit:v1", + "mode": "multi_image_reference_edit", + "modelType": "QwenImage21Pipeline" + }, + { + "id": "qwen-image-21:text-to-image:v1", + "mode": "text_to_image", + "modelType": "QwenImage21Pipeline" + } + ], + "modularWorkflowIds": [], + "name": "QwenImage21Pipeline", + "reason": "At least one exact backend execution specification selects this upstream class.", + "reviewDecision": null, + "status": "executable" + }, { "artifactReviews": [], "equivalentTo": [], @@ -7087,6 +7132,16 @@ "reviewDecision": null, "status": "equivalent" }, + { + "artifactReviews": [], + "equivalentTo": [], + "exactExecutionSpecs": [], + "modularWorkflowIds": [], + "name": "Wan22VaceModularPipeline", + "reason": "This newly exported video composition is deferred from the current image/audio release scope.", + "reviewDecision": null, + "status": "intentionally-excluded" + }, { "artifactReviews": [ "data/wan-animate-2-artifact-review.json" @@ -8604,15 +8659,15 @@ "schemaVersion": 1, "scope": { "diffusers": { - "exportModuleSha256": "a31b3d860c85b22f08976b00c81a00f36cd3986c702715954bb230882803493f", + "exportModuleSha256": "f9536d3cd5f2f5df8992fa4b4b0fb53a407eea15744fcc288760624ce9111251", "inventoryRule": "Every static top-level Diffusers export ending in Pipeline is classified once.", - "revision": "2f7e0154a9db246e95c9ede43edba7db5b130805", - "verifiedSourceRevision": "2f7e0154a9db246e95c9ede43edba7db5b130805", - "version": "0.40.0.dev0" + "revision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "verifiedSourceRevision": "fbf49e7f35857f76bc57b177e26f12b03687c668", + "version": "0.41.0.dev0" }, "qualificationBoundary": "Executable means source/graph admission only; it does not imply live output, Auto, Gallery, hardware, license, or release qualification.", "templateBundle": "web/assets/studio-templates.js", - "templateBundleSha256": "2da5ceb891a230039fb8fa80380f765248ed357f97bd717be632babc1417aca8", + "templateBundleSha256": "e554ddc034cb069918936aae7641bf08345d86c5d3670494dee7e13739c570b8", "transformers": { "productionRuntime": { "byteSize": 11625234, @@ -8634,12 +8689,12 @@ "canonicalWorkflowCount": 200, "canonicalWorkflowsWithPublicTemplates": 52, "canonicalWorkflowsWithoutPublicTemplates": 148, - "diffusersPipelineSymbolCount": 330, + "diffusersPipelineSymbolCount": 334, "pipelineStatusCounts": { "contract-only": 5, - "equivalent": 11, - "executable": 142, - "intentionally-excluded": 56, + "equivalent": 10, + "executable": 144, + "intentionally-excluded": 59, "research-blocked": 116, "unreviewed": 0 }, @@ -8653,12 +8708,12 @@ "research-blocked": 0, "unreviewed": 0 }, - "transformersProductionSupportedSemanticCount": 5, - "transformersSemanticCount": 6, + "transformersProductionSupportedSemanticCount": 6, + "transformersSemanticCount": 7, "transformersSemanticStatusCounts": { "contract-only": 0, "equivalent": 0, - "executable": 4, + "executable": 5, "intentionally-excluded": 0, "research-blocked": 2, "unreviewed": 0 @@ -8737,6 +8792,68 @@ ], "status": "executable" }, + { + "canonicalWorkflowIds": [], + "id": "bounded-depth-estimation", + "label": "Bounded image depth estimation", + "modes": [ + "depth_estimation" + ], + "nodeEvidence": [ + { + "actions": [ + "LoadDepthEstimationModel", + "PredictDepth" + ], + "path": "modules/HuggingFaceTransformers/main.py", + "sha256": "81544771429fac13e5e407699eb0602a91485c0be2de4709a8954218eca09e0c" + } + ], + "nodeKeys": [ + "modules.HuggingFaceTransformers.LoadDepthEstimationModel", + "modules.HuggingFaceTransformers.PredictDepth" + ], + "productionWheelAbsentPaths": [], + "productionWheelEvidence": [ + { + "path": "models/auto/modeling_auto.py", + "sha256": "bb9ae5973f29cdf8cf71e6a14dd0f0bec9efb4fb1d7ac0f58262fd8f28a4b8b6", + "symbols": [ + "AutoModelForDepthEstimation" + ] + }, + { + "path": "models/dpt/image_processing_dpt.py", + "sha256": "646937f80e26520fa9b0b2ba86aeb30d9a7f673b99ed6bcbdf3f082685d1cd1a", + "symbols": [ + "DPTImageProcessor", + "post_process_depth_estimation" + ] + } + ], + "productionWheelSupport": true, + "publicTemplateEligible": false, + "qualification": "source-implemented-mocked-contract-qualified", + "reason": "Generic AutoModel nodes use bounded DPT preprocessing and separate native depth from normalized previews.", + "reviewedMainEvidence": [ + { + "path": "models/auto/modeling_auto.py", + "sha256": "549e9ec1c382d8481028ddef6dd28347080f75a8aad3b1566b3002377b2ed7e0", + "symbols": [ + "AutoModelForDepthEstimation" + ] + }, + { + "path": "models/dpt/image_processing_dpt.py", + "sha256": "646937f80e26520fa9b0b2ba86aeb30d9a7f673b99ed6bcbdf3f082685d1cd1a", + "symbols": [ + "DPTImageProcessor", + "post_process_depth_estimation" + ] + } + ], + "status": "executable" + }, { "canonicalWorkflowIds": [], "id": "bounded-causal-text-generation", @@ -8751,7 +8868,7 @@ "LoadTextGenerationModel" ], "path": "modules/HuggingFaceTransformers/main.py", - "sha256": "1a27da07dd32a3441adafab8d6b983450d545c55d9693a3341b1c37b8958d8a6" + "sha256": "81544771429fac13e5e407699eb0602a91485c0be2de4709a8954218eca09e0c" } ], "nodeKeys": [ @@ -8812,7 +8929,7 @@ "LoadImageTextToTextModel" ], "path": "modules/HuggingFaceTransformers/main.py", - "sha256": "1a27da07dd32a3441adafab8d6b983450d545c55d9693a3341b1c37b8958d8a6" + "sha256": "81544771429fac13e5e407699eb0602a91485c0be2de4709a8954218eca09e0c" } ], "nodeKeys": [ @@ -8887,7 +9004,7 @@ "LoadAnyToAnyModel" ], "path": "modules/HuggingFaceTransformers/main.py", - "sha256": "1a27da07dd32a3441adafab8d6b983450d545c55d9693a3341b1c37b8958d8a6" + "sha256": "81544771429fac13e5e407699eb0602a91485c0be2de4709a8954218eca09e0c" } ], "nodeKeys": [ diff --git a/docs/README.md b/docs/README.md index a9654236..f7443e4e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,20 +13,34 @@ This directory contains the durable technical guides for the MoDiff backend. Sta | Check quantized model, dependency, download, and qualification support | [Quantization support matrix](quantization-support.md) | | Review optional attention, quantization, and compilation capabilities | [Optional runtime optimizations](optional-runtime-optimizations.md) | | Build Modular Diffusers graphs and understand experimental compatibility | [Modular Diffusers guide](../modules/ModularDiffusers/README.md) | -| Use ordinary image actions and their optional typed inputs | [Ordinary Diffusers image nodes](../modules/DiffusersImage/README.md) | -| Build reusable attention-mask and LoRA-scale inputs | [Attention Arguments](../modules/DiffusersImage/README.md#attention-arguments) | -| Choose, edit and reuse FLUX Blocks | [Using FLUX Blocks](../modules/DiffusersImage/README.md#using-flux-blocks) | -| Implement the shared Cluster/User Node composite contract and V2 schemas | [Unified composite-node contract](unified-composite-node-implementation-plan-2026-09-01.md) | -| Track the first-party visual Diffusers/Transformers node system | [Hugging Face visual node system plan](hugging-face-visual-node-system-plan.md) | -| Track Diffusers, Modular Diffusers, speech, testing, and asset work | [Hugging Face integration roadmap](hugging-face-integration-roadmap.md) | +| Use ordinary image actions and their optional typed inputs | [Ordinary Diffusers image nodes](../modules/DiffusersImage/README.md) | +| Run the qualified image demo | [Image demo checkpoint](image-demo.md) | +| Rehearse editable image stages and portable custom art direction | [Image modularity demo](modularity-demo.md) | +| Build a visually meaningful fashion-editing demonstration | [SoHo fashion editorial demo](fashion-editorial-demo.md) | +| Transfer the fashion demo to a Windows NVIDIA machine | [Windows fashion demo setup](windows-fashion-demo.md) | +| Generate/edit with Qwen 2.1 and understand attention-context reuse | [Qwen-Image 2.1](qwen-image-21.md) | +| Estimate depth with generic Transformers nodes | [Transformers depth workflows](../modules/HuggingFaceTransformers/README.md) | +| Build reusable attention-mask and LoRA-scale inputs | [Attention Arguments](../modules/DiffusersImage/README.md#attention-arguments) | +| Choose, edit and reuse FLUX Blocks | [Using FLUX Blocks](../modules/DiffusersImage/README.md#using-flux-blocks) | +| Implement the shared composite contract and V2 schemas | [Composite node contract](composite-node-contract.md) | +| Review workbench acceptance and unresolved qualification boundaries | [Workbench acceptance](workbench-acceptance.md) | | Review the Hugging Face-derived engineering and runtime requirements | [Hugging Face engineering alignment](hugging-face-standards.md) | | Review inherited source baselines and per-file modification notices | [Source provenance map](source-provenance.md) | | Contribute code, nodes, dependencies, or client-facing changes | [Contributing](../CONTRIBUTING.md) | | Understand the local-only trust boundary or report a vulnerability | [Security policy](../SECURITY.md) | | Understand expected conduct in project spaces | [Code of conduct](../CODE_OF_CONDUCT.md) | +Custom Python and Hub block authors: [Developing custom nodes](custom-nodes.md), +including single-file drag/drop, automatic discovery and intentional Add/Load/Reload. + ## Required Engineering Procedure +The [workbench acceptance guide](workbench-acceptance.md) consolidates model +coverage, native-stage exceptions, preservation, custom-node and integrated +qualification requirements. Retired implementation plans and campaign trackers +remain in Git history; their removal does not close outstanding acceptance gates. +Use [workflow authoring](workflow-authoring-ux.md) for current behavior. + Read [Cluster engineering lessons](cluster-engineering-lessons.md) before node/Block, hierarchy, execution, qualification or cross-machine integration work. The [runtime support matrix](runtime-support-matrix.md#model-families-and-support-boundaries) @@ -42,3 +56,8 @@ Public documentation should describe the supported current behavior and make its Do not publish credentials, private media, personal paths, machine inventories, unredacted provenance, or dated internal execution trackers. Document supported product identifiers and contracts exactly as they appear in the current implementation. When behavior changes, update the root README and the narrow guide in the same contribution. Verify repository-relative links and run the validation described in [CONTRIBUTING.md](../CONTRIBUTING.md) before requesting review. + +- [Developer setup with uv and npm](developer-setup.md) +- [Service prototyping with API graphs](service-prototyping.md) + +- [Workflow authoring and model selection](workflow-authoring-ux.md) diff --git a/docs/api-reference.md b/docs/api-reference.md index abe9f9b5..d635e5db 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -29,10 +29,10 @@ resolve to loopback. | Optimizations | `GET /runtime/optimizations`, `/jobs/{job_id}`, `/receipts`; `POST /runtime/optimizations/install`, `/activate`, `/rollback`, `/enable`, `/probe`, `/qualify`, `/jobs/{job_id}/cancel` | Inspect runtime features and legacy package contracts, manage recovery, and record bounded local qualification evidence. Hashless package install and activation are unavailable. | | Optional model runtimes | `GET /runtime/optional-runtimes`, `/jobs/{job_id}`; `POST /runtime/optional-runtimes/install`, `/activate`, `/rollback`, `/jobs/{job_id}/cancel` | Publish the reviewed optional-library contract and its fail-closed staged lifecycle. The current candidate exposes no executable install or activation action. | | Auto resource | `POST /auto_resource/plan`, `POST /auto_resource/plans`, `POST /auto_resource/workflow`, `GET /auto_resource/history`, `DELETE /auto_resource/history` | Plan hardware-aware model recipes and manage local planner history. | -| Models | `GET /huggingface/node-library`, `/huggingface/modular-conditionals`, `/huggingface/registered-block-v2`, `/model_capabilities`, `/model_artifact_catalog`, `/model_fingerprints`, `/local_models`, `/hf_cache`, `/model_cache/diagnostics`, `/hf_hub`, `/hf_download/plan`; `POST /hf_download`, `/hf_token`; `DELETE /hf_cache/{hash}` | Discover reviewed first-party node definitions, exact compiled Block definitions, and unpruned Modular branch contracts; diagnose, space-plan, download, authenticate, fingerprint, and delete model artifacts. | +| Models | `GET /huggingface/node-library`, `/huggingface/modular-conditionals`, `/huggingface/registered-block-v2`, `/huggingface/registered-block-interfaces`, `/model_capabilities`, `/model_artifact_catalog`, `/model_fingerprints`, `/local_models`, `/hf_cache`, `/model_cache/diagnostics`, `/hf_hub`, `/hf_download/plan`; `POST /hf_download`, `/hf_token`; `DELETE /hf_cache/{hash}` | Discover reviewed first-party node definitions, exact compiled Block definitions, and unpruned Modular branch contracts; diagnose, space-plan, download, authenticate, fingerprint, and delete model artifacts. | | Template Gallery setup | `GET /template_gallery/status`, `/template_gallery/plan`; `POST /template_gallery/install` | Inspect, space-plan, and explicitly install or repair the byte-pinned Gallery payload through the local app. | | Media lifecycle | `GET /media_assets`, `DELETE /media_assets` | Inspect temporary media records or remove exact unpinned, task-scoped, or age-scoped files while no generation is active. | -| Custom modules | `GET /custom_modules`; `POST /custom_modules/refresh`, `/install`, `/{name}/update`, `/{name}/disable`, `/{name}/enable` | Clone/copy and import trusted custom Python modules or change their enabled state. | +| Custom modules | `GET /custom_modules`; `POST /custom_modules/refresh`, `/add`, `/install`, `/{name}/inspect`, `/{name}/reload`, `/{name}/update`, `/{name}/disable`, `/{name}/enable` | Discover sources; intentionally add/load/reload or disable content-bound custom code. See [custom nodes](custom-nodes.md). | | Studio outputs | `GET/POST /studio_outputs`, `PATCH/DELETE /studio_outputs/{output_id}` | Persist and manage local Studio output metadata and copied media. | | Studio blocks | `GET/POST /studio/blocks`, `GET/DELETE /studio/blocks/{block_id}` | Persist reusable local graph blocks. | | Composite migration | `GET/POST /studio/composite-migrations/preview`, `GET /studio/composite-migrations`, `/recovery-audit`, `/{migration_id}`; `POST /studio/composite-migrations/apply`, `/{migration_id}/rollback` | Inspect redacted partial recovery evidence or explicitly apply safe V1/exact compiler-supplemented Cluster conversions with exact local backups and fail-closed recovery. | @@ -40,7 +40,7 @@ resolve to loopback. ## Core response contracts -### Node cache deletion +### Node cache recomputation and release `DELETE /cache` accepts `{ "nodes": ["node-id"] }`, a single node-id string, or `{ "nodes": "*" }`. Invalid id shapes return HTTP 400. The response retains @@ -54,6 +54,61 @@ it does not offload discarded weights or rebuild unrelated models' hooks. A run waiting for cache ownership reports `waiting_for_node_cache`; cancelling that wait does not start model work afterward. +Release also removes cached consumers that retain the selected node's pipelines, +state or tensors, including transitive consumers. The response lists these cache +entries as well. Other component owners keep their weights and hooks; published +media and downloaded model files are unaffected. Moving an unchanged loader +between workflow node ids transfers its component collection and exclusive disk +offload ownership without rebuilding hooks or copying weights. + +Use `{ "nodes": ["node-id"], "scope": "outputs" }` to request recomputation on +the next Run without destroying node instances, model owners or offload hooks. +The response is `{ "error": false, "scope": "outputs", "nodes": [...], +"retainedModelNodes": [...] }`: `nodes` lists invalidated result producers and +`retainedModelNodes` lists component owners whose output identities remain valid. +Root Block ids also cover their cached runtime descendants. Current previews +remain available; the normal executor propagates invalidation when the producer +reruns. This action waits for the same execution lease as release. It does not +run a graph, change its parameters, install weights or remove downloaded files. +Unknown scopes return HTTP 400. Omitted scope (or `"all"`) retains the existing +node-destruction behavior. Use `POST /runtime/gpu_cleanup` for idle process-wide +model release. + +Node `executed` WebSocket messages include a bounded reason in `message`, +distinguishing unchanged inputs, invalidation, changed inputs/loaded implementation, and retained objects +with changed usage settings. CPU offload keeps weights available for reuse; it is +not a weight reload or a model release. + +Cache identity is process-local. Validated execution inputs include model and +component revisions, adapters, dtype and resource settings; ordinary canvas +position, selection, collapse and view-mode metadata never enter node arguments. +Input snapshots detect edits to nested data, images, arrays and ordinary +versioned tensors without copying model weights. The existing Modular authority +checks still run before reuse. Replacing a loaded node class or callback invalidates +its output; editing a custom source file requires explicit code review and module +reload to load that code. Merely editing a file does not execute it. + +Opaque objects remain bound to their producer's identity. Custom nodes that mutate +an opaque model must execute and publish the change through the normal graph; +untracked writes through tensor `.data` or external native pointers are not a +supported cache invalidation mechanism. Upstream recomputation invalidates its +consumers even when it returns the same model object. + +Expanded Modular steps fork the preceding Pipeline State and mutable pipeline +components, including schedulers and guiders, while retaining neural weights and +their manager. Generator continuations start at the saved stream position without +advancing the cached snapshot. Branches and retries therefore cannot mutate a +preceding stage's reusable state. Failed/cancelled graph attempts discard runtime +caches before another attempt can use them. Completed model-call traceback frames +are released after capturing diagnostics, so exceptions retained by futures cannot +keep the failed attempt's tensors alive. Suspended queue coroutines remain intact. + +Graph model work and teardown use a dedicated worker thread to bound accelerator +workspaces retained per thread. HTTP/control requests remain independent, and +downloads retain the configured model-I/O serialization policy. Auto retains +owners when their combined envelope fits and uses the existing owner-release +schedule otherwise; actual free memory is checked before the next owner loads. + ### Managed file identifiers `POST /file` stores uploads below the configured data directory and returns a @@ -215,6 +270,23 @@ Transformers, or Torch and does not load or download model weights. Built-in definitions have `surface: diffusers_cluster_nodes`, `ownership: library`, and `mutable: false`; custom blocks remain User Nodes under `/studio/blocks`. +`GET /huggingface/registered-block-interfaces` returns a schema-1 read-only +index of the public sockets of every validated compiled catalog entry. Each +entry contains `catalogDefinitionId`, `catalogDefinitionContentHash`, +`admissionId`, `compiledDefinitionContentHash`, +`compiledDefinitionCanonicalSha256`, and `inputs`/`outputs` arrays of +`{portId, valueType}`. `valueType` is a string or a union of strings. The +response is `{schemaVersion: 1, error: false, entries: [...]}`. Invalid local +catalog data returns HTTP 500 with `error: true` and a message. + +The picker uses this compact index for typed suggestions only when all source, +admission and compiled pins agree with its registered route. It does not infer +ports from an internal graph or execute field actions to discover them. This +endpoint returns no graphs, parameter values, runtime qualification or code +consent. Selecting a suggestion still fetches and validates the full definition +below; final canvas connection validation remains authoritative. Browsing this +index does not import model runtimes, contact the Hub or download weights. + `GET /huggingface/registered-block-v2?definition_id=...&admission_id=...` returns one build-time generated `BlockDefinitionV2`, its initial instance values, recursive internal layout, and canonical SHA-256 for the exact @@ -589,7 +661,7 @@ This check does not claim file-existence, conditional-state or tensor-shape validation; those retain their existing backend and readiness contracts. See the -[unified composite-node contract](unified-composite-node-implementation-plan-2026-09-01.md) +[composite-node contract](composite-node-contract.md) for the normative `BlockDefinitionV2`/`BlockInstanceV2`, hashing, copy-on-write, migration, and schema-maintenance rules. @@ -1136,6 +1208,201 @@ still inspect the same generic node fields from `/nodes`. Templates, resource qualification, live media, and Gallery publication require later graph and remote qualification gates. +### Generic operations and pipeline coverage + +`GET /model_capabilities` publishes `operationContractSchemaVersion: 3`, +`operationContracts`, `pipelineSupportSchemaVersion: 1` and `pipelineSupport`. +These project the existing generic Modular configs, reviewed workflow adapters, +standard image/video/audio/rendered-3D adapters and optional-runtime profiles. +They do not add runtime actions or an alternate graph executor. + +An operation has `pipelineClass`, `task`, `operationId`, `nodeType`, `nodeKey`, +`blockName`, `decomposition`, `support: "declared"`, `workflowId`, `binding` and +`ports`. Identity is `(pipelineClass, operationId, task)`. Task-scoped bindings +are independently authorable; the retained `task: null` declarations describe +individual generic stages without claiming a complete task path. + +| Decomposition | Meaning | +| --- | --- | +| `block` | Named upstream Modular stage; `blockName` is non-null | +| `bundle` | Existing component or typed media helper; no invented upstream stage | +| `loader` | Existing Modular or standard loader for this task | +| `pipeline` | Whole-pipeline call without editable Modular stages | + +Canonical IDs include `diffusion.load_models`, `diffusion.encode_prompt`, +`diffusion.denoise` and `diffusion.decode_latents`. Specialized operations retain +separate identities for prompt rewriting, duration preparation, semantic +conditioning, reference assembly/encoding, media preparation/postprocessing, +video, synchronized video/audio, prediction maps, layer decomposition and +rendered 3D. Rendered 3D produces an orbit video, not a mesh. Reference assembly +uses the existing decoded-media helper, not a new file/URL loading path. + +`binding.pipelineClass` identifies the actual implementation. It may differ +from the selected `pipelineClass` for an existing reviewed standard fallback. +`binding.values` contains only the exact lightweight selectors needed by the +existing action, such as `model_type`, `pipeline_class`, `workflow_id` or +`block_path`. Full field defaults and output signals are resolved on demand. +Fallbacks retain public execution-profile task limits and reviewed local aliases. +A task with Modular stages retains one coherent Modular loader/stage path; +fallback generators are not mixed with that loader. Saved actions, including +historical aliases such as `GenerateLTX2`, remain executable without duplicate +canonical discovery entries. + +Each port retains `name`, original `semanticName`, `direction`, `types`, +`required`, `hidden` and `roles` (`value`, `component`, or `pipeline`). Some +conditioning bundles carry both value and component roles. Output ports are not +required. Hidden fields can carry internal signals or optional controls. +Modular loader ports with no declared component are hidden, while the full +component bundle remains explicit. + +The additional `semantics` object contains: + +- `kind`: value, media, component, conditioning, latents, state, pipeline or opaque. +- `scope`: actual pipeline identity, or pipeline plus workflow for sealed stage + state; plain values and decoded media have no model scope. +- `state`: the completed/required preceding stage for sealed workflow state. +- `owner`: `same_loader` for model-owned objects, otherwise `none`. +- `members`: declared component or stage input/output names and upstream types. + These are descriptions, not inferred tensor dimensions or universal adapters. + +Advisory matching rejects incompatible domains, component requirements and +state stages. Model-owned or opaque values still require runtime validation. +Equal socket types, names or shapes never prove cross-family conditioning or +latent compatibility. An unknown representation stays opaque. Existing +preflight and loader/state ownership checks remain authoritative. + +Each `pipelineSupport` record carries the reviewed upstream coverage decision, +reason, equivalence targets, declared `upstreamTasks` and task-level bindings. +A task has `execution` (`adapter`, `declared`, `unavailable`), `decomposition` +(`stages`, `pipeline`, `none`), operation/profile IDs, `dependencies` +(`ready`, `blocked`, `unknown`) and exact `runtimeRequirements`. + +`adapter` requires existing task operations and a matching loader execution +profile; it does not establish live inference, available weights or adequate +memory. Modular task support comes from reviewed workflow owners, independently +of Studio's narrower curated profile-mode menu. Dependency observation uses the +existing optional-runtime gate, including extra media requirements. A missing +runtime does not erase an adapter. `declared` retains schemas without an +executable profile; contract-only models do not become runnable. An upstream +task name with no exact operation binding remains unavailable even if a related +adapter task has a different name. + +The checked `data/diffusers-operation-inventory.v1.json` accounts for all 330 +pipeline exports at the pinned Diffusers revision, all declared AutoPipeline +mapping entries (including conditional registrations), and all reviewed Modular +workflow/task entries. Other exports retain `pipeline_call` as their call +surface and the existing explicit review decision. This audits named upstream +surfaces; it does not infer every possible mode from optional `__call__` +arguments. Local adapter aliases remain additional support records. +Regenerate/check without model downloads: + +```bash +./scripts/with-runtime-env.sh .venv/bin/python scripts/generate_operation_inventory.py +./scripts/with-runtime-env.sh .venv/bin/python scripts/generate_operation_inventory.py --check +``` + +Discovery reads checked metadata without constructing nodes or pipelines, +resolving executable blocks, downloading models or installing packages. Query +filtering includes linked model classes and direct pipeline-class matches. +The bounded client parsers reject malformed/duplicate records, invalid support +references and unknown versions. Operation versions 1 and 2 remain readable; +version 1 normalizes `task: null` and `hidden: false`. Older backends may omit the +new catalogs. The version 3 response requires the matching updated client. + +`POST /operations/resolve` is a read-only authoring request: + +```json +{"pipelineClass":"AnimaModularPipeline","task":"text_to_image","operationId":"diffusion.denoise"} +``` + +The response is `{ "schemaVersion": 1, "operation": , "node": + }`. Invalid/unknown selections return HTTP 400. +The exact existing action receives its dynamic fields, selectors, canonical +label and selected loader output signals. This constructs no runtime node, +loads no weights, creates no graph or receipt, and grants no execution permission. +The user can insert the result through the normal graph node factory, connect +it and save it using the existing graph representation. + +In Expert's Stages catalog, choose a pipeline and task, then click a canonical +operation to add one ordinary node. Adapter and runtime status remain separate. +Bound implementation entries are consolidated there; Advanced retains raw +nodes and aliases for inspection. Auto's Essentials catalog and existing saved +graphs retain their behavior. Changing picker selections does not change nodes +already on the canvas. In-flight insertion is cancelled when its selection, +active workflow or catalog view changes. Graph-wide changes use the explicit +preview and transaction described below. + +`POST /operations/starter` accepts `pipelineClass`, a non-null `task`, and an +optional `executionProfileId`. No other keys are accepted. The optional identity +selects an exact public profile belonging to that loader and task, including its +reviewed model repository and immutable revision. This distinguishes models that +share a pipeline class (for example FLUX dev and schnell). The endpoint checks +that the resulting ordinary loader values resolve back to that profile; unknown, +unrelated or ambiguous selections fail before any node is constructed. +Omitting the identity preserves the existing pipeline/task defaults. +It returns schema version 1, the selected pipeline/task/workflow ID, `nodes` (each +containing its v3 `operation` and ordinary `node` schema), `edges`, `requiredInputs`, `sharedInputs` +and `upstreamBlocks`. Edges use canonical operation IDs temporarily as `source` +and `target`, and real field names as `sourceHandle`/`targetHandle`. The client +assigns ordinary canvas node/edge IDs at insertion; these authoring references +are never an alternate executable graph format or an execution receipt. + +New ordinary image, audio, video and rendered-3D operations initialize visible +controls from the selected profile's reviewed capability defaults. Video defaults +include frame count and dimensions; rendered-3D defaults include frame size. +Audio defaults include duration, +sample rate, steps, guidance and loader precision where declared; inactive +adapter controls keep their existing defaults. This initialization applies only +when creating nodes. Loading saved graphs or refreshing dynamic field metadata +does not replace edited values, and runtime validation still checks their limits. +Playback FPS remains an independent control on the video export node. + +A v3 operation can declare `decomposition: "integrated"` and +`nodeType: "integrated"` when its existing action both owns and executes its +model. It is the starter's single model owner, with no synthetic loader or +component edges. The image-upscale binding resolves the existing Spandrel action +and its reviewed immutable file selector. Profile selection validates that exact +binding; it does not add undeclared pipeline/revision fields to the executable +node. Required source media stays explicit, and ordinary graph execution still +rehashes the selected artifact through the existing resolver. + +Task-specific Modular operation ports include the reviewed workflow's required +media inputs, even when the reusable node schema makes a socket optional for +other tasks (for example an inpaint mask or a last video frame). The selected +operation and starter therefore agree on required media. This metadata describes +authoring requirements; backend runtime validation remains authoritative. + +Connections reuse the reviewed workflow's exact state and component bindings, +including required component ports beyond the minimum admission edges. Shared +seed groups describe stages continuing one generator through native or sealed +state. The client keeps their ordinary values aligned and resolves random mode +once per group/run. Existing runtime seed/state validation remains authoritative; +these relationships are not receipts or hidden execution parameters. +Required auxiliary models and conditioning remain visible as unbound inputs; +standard pipelines remain a loader and whole call. Effective output dimensions +feed the decoder where the generic adapter declares them. This endpoint does +not construct nodes, install packages, download weights or mutate a workflow. +The Developer **Workflows** chooser uses these drafts for task-first model +selection and adds compatible ordinary output nodes from the live registry. +A preview is not execution evidence; missing inputs and runtime/model setup +remain visible. Creating a draft neither downloads nor runs a model. +Unknown/ambiguous selections return HTTP 400. Runtime, artifact, resource and +actual connected-object validation still happen through the existing executor. + +When distinct public model identities share a standard loader and pipeline +class, a new operation binds the unique execution profile for the selected +identity and task. This preserves the distinction between a direct pipeline +and its reviewed Modular equivalent at optional-runtime dispatch. It does not +rewrite saved loaders or resolve multiple profiles for the same identity by +guessing; runtime profile and artifact validation still apply. + +The client previews model/task changes before applying one history transaction. +Compatible user values and custom nodes survive; unsupported settings are retained +in an advisory annotation outside execution. A changed Python action gets a fresh +runtime ID, avoiding a stale cached field-action instance. Existing Blocks are not +converted; inspection and structural editing remain separate commands. Users +connect the final output to a Preview, Save or Export node to run their draft. + ### Studio execution specifications For migrated exact pairs, `GET /model_capabilities` publishes a @@ -1248,6 +1515,12 @@ mask branches. ### Auto resource compatibility +`form.resourceMode` and runtime-hint `resourceMode` remain execution-policy fields: +`auto` requests automatic planning; `expert` uses explicit resource settings. +The client's global Auto/Expert authoring preference is presentation only and is +not an execution-policy input. Either view can use either saved resource policy. +Older client bundles still couple those controls; the wire values remain compatible. + Both planning endpoints gather runtime/model snapshots, evaluate candidates, and serialize their responses off the HTTP event loop. A Model Manager batch must not block health, library, or cancellation requests while planning is @@ -1382,6 +1655,16 @@ live proof. Qualification proof remains advisory for an otherwise valid executable graph. Independently safe or passed candidates do not depend on that local history check. +Editing an image or video upscaler's Model selector resolves an installed Hub +file to its exact cached commit and records its SHA-256 and byte size in the +selection. Local selections receive the same content identity without a Hub +revision. This metadata-only field action does not download or deserialize a +model, and it cannot authorize custom code. A complete existing pin can still +be authored before its weights are installed. Missing unpinned files require +installation through Model Manager; the action never chooses the newest cached +snapshot as a fallback. Execution independently revalidates the resulting pin, +so changing a cache ref after authoring does not silently change the workflow. + Plan application considers only executable loader IDs referenced by graph `paths`. Direct loaders must already expose the profile's exact `pipeline_class`; modular `ModelsLoader` nodes must already expose the exact @@ -1625,6 +1908,38 @@ workflow, run identity, or canvas epoch no longer owns the visible document. The extra fields are additive so older single-document clients remain wire compatible. +Nonqueued client field-action waits are cancelled when their workflow ownership +expires or browser navigation begins. This releases HTTP connections; it does +not interrupt Python callbacks or grant permission to drop their execution +lease. Queued user actions retain their acknowledgements. When a WebSocket +session disconnects, its pending signal lookups resolve with +`{"__MODIFF_ERROR": "websocket_closed"}` rather than waiting for the lookup timeout. +Other sessions' pending requests retain their ownership. + +Reviewed nonqueued built-in field callbacks use presentation-only contexts. +These include Modular loader filters, component selectors, scheduler/guider/layer +schemas, generic operation schemas and the legacy Dynamic Block contract preview; +ordinary Diffusers image, audio, video and rendered-3D contract updates use the same +boundary. The exact allowlist is `modiff/field_metadata.py`, audited against the +public registry in `tests/test_field_metadata_catalog.py`. + +These callbacks do not construct, mutate or destroy cached executable nodes, so +graph execution does not block their field updates. Each request gets fresh +presentation state and copied ordinary node declarations. Existing immutable +pipeline/component identities and verified custom-contract resolvers remain +unchanged; preview cannot authorize or import repository Python. Dynamic Block +label/schema messages carry the same request-scoped workflow and form identity +as ordinary field messages. + +Metadata callbacks have a separate ordered lease that survives cancellation +until their threads finish. Runtime activation and custom-source mutations remain +unavailable while that lease is held; custom-source mutations also hold it until +imports finish. Authoritative field authorization and optional-runtime checks +still apply. Queued and custom-node callbacks retain the model ownership lease. +The explicit Quantization **Load Model Layers** action also remains serialized: +it constructs empty models under Accelerate and is not passive metadata. +A registry declaration cannot opt an arbitrary callback into the metadata path. + Client callers must also choose the correct local ownership scope. A normal visible form edit is form-scoped and must reject a response after the form epoch advances. A hidden registered-Block compiler action is canvas-scoped: @@ -1650,6 +1965,12 @@ of compact recent terminal receipts. Current and queued graph runs retain the complete workflow snapshot needed for immediate restoration. Completed workflow snapshots and run outputs are loaded on demand through `GET /runs/{task_id}` instead of being repeated in every queue poll. + +Run detail lookup reuses the history cache's task index to decode only matching +output records. The index only narrows candidates: task and client identities +must still agree across output, provenance and media records. Existing history +normalization, atomic writes and detection of external file changes apply; no +outputs or workflow snapshots are pruned to improve lookup speed. Completion, cancellation, and failure are distinct terminal states. Use the WebSocket for live progress and `GET /queue` to restore state after reconnect. Do not infer success only from an HTTP `200` returned by `POST /graph`. @@ -1665,6 +1986,10 @@ transaction lock until completion even if the requesting client disconnects. Executor output preservation uses the same file lock; preview updates at queue admission and completion also wait off-loop. These changes preserve the response shape and output identity checks and do not delete retained media or history. +History writes encode one output record at a time before atomically replacing +the existing file. This avoids token-by-token Python writes for nested workflow +snapshots without allocating a second serialized copy of the entire history. +An encoding failure leaves the previous history document intact. Successful `POST /graph` admission marks only generated preview fields present in that submitted graph as pending and returns `preview_slots` with `preview_state_revision`. The matching `task_queued` WebSocket event carries the same state for other connected clients. A generated `update_value` atomically persists its output and promotes it through `preview_slot`; a newer pending task cannot be displaced by a late output from the task ahead of it. Terminal events carry any failed, cancelled, or completed-without-output slot changes. @@ -1706,6 +2031,19 @@ not an unbounded per-iteration trace. This is not an assertion about internal library defaults, random seeds generated inside a library, output quality, model licensing, or publication qualification. +For concrete Modular graphs, the receipt can also include `graphTasks`, a bounded +list of `{loaderId, pipelineClass, task}` records for captured model owners in the +output's actual ancestry. This is recognition of the owner's reviewed operation +and state-edge contract, separate from captured call arguments. `task: null` +means that graph does not select one unique public model task; it is not guessed +from a Block label or an authoring hint. Complete, unambiguous task evidence takes +precedence over the historical form's task label, without modifying that form. +Edited upstream compositions, identical task signatures and wrapper workflows +may remain unresolved. Neither recognition nor the history label grants execution, +model support or resource qualification. Workflow Auto uses the same recognition +when existing explicit mode/workflow bindings do not select a task, and still +requires its ordinary exact-profile, artifact and memory checks. + The allowlist is maintained in `modiff/execution_input_provenance.py` and the paired client `resolvedExecutionInputs.ts`. Capture never serializes arbitrary model/tensor/media objects or credential fields. Per scalar string/list limits @@ -1732,6 +2070,12 @@ state, and a custom sigma schedule does not establish the saved step count; those receipt values are `null`, without changing the requested form snapshot. Optional absent image/latent outputs do not invalidate a successful node cache. +Ordinary audio actions capture normalized steps and guidance from the active +adapter immediately before dispatch, retaining the original connected control's +provenance. Inactive controls for other audio adapters do not enter the receipt. +`audioDuration` records requested generation duration, and `sampleRate` records +the requested delivery rate after resampling, not the decoder's native rate. + `PATCH /studio_outputs/{output_id}` accepts exactly `{ "favorite": true }` or `{ "favorite": false }`. Unknown fields, non-boolean values, and malformed bodies return `400` without changing history. Execution receipts, task/node/attempt @@ -1803,8 +2147,8 @@ Uploads are written under configured data subdirectories and share the configure backend after active downloads finish so a newly installed `/template-gallery/*` static tree is registered. - `DELETE /hf_cache/{hash}` deletes selected cached model revisions. -- `POST /custom_modules/install` accepts a Git URL or local directory, places it under `custom/`, and refreshes the live registry. Imported custom code has the backend process's permissions. -- Modular Diffusers nodes may expose `trust_remote_code` for stored-graph compatibility, but repository Python and standalone component loading with remote code are rejected before upstream construction. Custom Modular pipeline and Dynamic Block execution is limited to an exact cached 40-character Hub commit whose canonical `modular_model_index.json` resolves to MoDiff-reviewed installed Diffusers pipeline/block exports and pinned official Diffusers or Transformers components. MoDiff revalidates the repository identity immediately before copying the reviewed metadata into a private content-addressed snapshot. Local mutable repositories remain preview-only, and neither a preview nor a persisted checksum grants repository-code authorization. +- `POST /custom_modules/add` is the normal intentional Local/Python-file/Hub/Git import: resolve an immutable remote revision, validate, import and enable in one action with `consent: true`. Discovery never enables code. The lower-level `/install` staging and `/{name}/enable` APIs remain available for tooling; `/{name}/reload` binds the current inspected `codeHash` and explicit consent internally. Source/dependency drift blocks execution, and reload releases only the module and dependent caches. See [custom node development](custom-nodes.md) for requests, bounded file sizes and resource declarations. +- The historical contract-only Modular Diffusers nodes may expose `trust_remote_code` for stored-graph compatibility, but repository Python and standalone component loading with remote code are rejected before upstream construction. Custom Modular pipeline and Dynamic Block execution is limited to an exact cached 40-character Hub commit whose canonical `modular_model_index.json` resolves to MoDiff-reviewed installed Diffusers pipeline/block exports and pinned official Diffusers or Transformers components. MoDiff revalidates the repository identity immediately before copying the reviewed metadata into a private content-addressed snapshot. Local mutable repositories remain preview-only, and neither a preview nor a persisted checksum grants repository-code authorization. HTTP reads and mutations require a literal loopback destination and peer. Browser requests with an `Origin` header must also use a loopback `http` or `https` origin; CLI HTTP clients without an `Origin` header remain supported over loopback. WebSocket upgrades use the same destination and peer boundary, browser clients must send a loopback Origin, and native clients without one are accepted only over a loopback connection. The initial `welcome.recent` list uses the same compact receipts as `GET /queue`; full completed workflow snapshots remain available through `GET /runs/{task_id}`. The separate supervisor control server binds to `127.0.0.1` and likewise rejects non-loopback browser origins. @@ -1820,7 +2164,18 @@ The response includes `canAutoRun`, `issues`, `loaders`, `adapters`, `requiremen Requests carry `runtimeHints.workflowAutoPlan` with `schemaVersion: 1`, `graphHash` and optional `resourceControlGroups` (arrays of `{nodeId, field}` bindings for shared offload controls). The backend verifies the graph hash, prepares supported data-only suppliers through the existing executor when needed, validates mirrored settings, and replans from fresh outputs. It emits existing `auto_resource_plan_applied` events with optional `resourceUpdates` containing `{nodeId, field, value, previousValue}`. The client applies those updates only to the owning workflow with unchanged fields. `runtimePreparation.workflowAuto` in task receipts records resolved fields, applied updates, preparation nodes, schedule and actual releases. -Shared loaders count once. Independent loaders remain separate owners. Single/shared-owner caches remain reusable. Independent owners use a dependency-respecting lifetime plan when it lowers peak memory; the same executor releases completed model caches and checks actual free memory before each subsequent owner. Detached material outputs survive, shared ownership stays live, and opaque model/device outputs block unsafe release. Loops retain all participating owners until the loop finishes. Release notifications use `auto_resource_cleanup`. +Shared loaders count once. Independent loaders remain separate owners. Single/shared-owner caches remain reusable. Independent owners are retained when their combined envelope fits current capacity. When it does not fit, they use a dependency-respecting lifetime plan if it lowers peak memory; the same executor releases completed model caches and checks actual free memory before each subsequent owner. Detached material outputs survive, shared ownership stays live, and opaque model/device outputs block unsafe release. Loops retain all participating owners until the loop finishes. Release notifications use `auto_resource_cleanup`. + +Idle planning refreshes OS host-memory availability and credits only the +worker's measurable PyTorch accelerator reservations, capped by the accessible +capacity. Process RSS is not treated as reclaimable model RAM. Shared/unified +memory remains one physical pool: accelerator requirements also count against +host memory. This conservative estimate can request an explicit cache release +when a warm CPU/offload cache leaves insufficient free RAM; it does not promise +that resident weights make every warm plan admissible. Plans are revalidated at +execution, and incompatible owner/recipe identities or actual pressure trigger +existing cache cleanup. Custom memory policy keeps explicit settings and does +not acquire an Auto capacity guarantee. Unknown model recipes, unreviewed custom/model-dependent resource suppliers, missing artifact evidence, nondefault accelerators and insufficient peak capacity produce explicit blockers. Expert preserves existing validation and explicit settings. Workflow receipts do not grant catalog, publication or model qualification authority. @@ -1838,3 +2193,33 @@ After preparation, the new hash includes precisely the applied patches. Built-in this does not admit arbitrary Tensor actions. Shared outer graph ancestors execute once per attempt, so consumers share a Generator's advancing state. A new attempt creates a fresh Generator. Explicit loop bodies retain their iteration semantics. + +### Cached image pipelines and explicit dimensions + +Ordinary image pipeline loaders resolve a pinned, local-only Hub selection to +its exact managed snapshot directory before calling Diffusers. This uses the +existing cache containment and immutable-revision validation. It avoids treating +unrelated weight folders in a repository as missing pipeline components. Diffusers +still validates the selected pipeline's required files; no download, alternate +revision, remote code, or serialization fallback is enabled by this resolution. +Individual component loaders and explicit local selections keep their existing +paths. + +Hunyuan-DiT's generic image adapters accept explicit width and height from 512 to +2048 in 32-pixel increments, with a combined ceiling of 1,048,576 pixels. The +1024-square default and existing step limits remain unchanged. They disable the +upstream resolution-binning option so a valid requested size is not silently +replaced with the nearest preset. The same declared bounds reach node controls +and backend validation; this is an execution contract, not qualification of every +size or resource policy. + +Pipelines without a step callback emit indeterminate generation progress +(`progress: -1`, without current/total step counts or an ETA). Callback-capable +pipelines retain measured per-step progress and the existing interruption checks. + +Kandinsky 3, ERNIE Image and GLM Image also separate their 1024-square defaults +from valid explicit dimensions. Their adapters retain the same 1,048,576-pixel +ceiling and allow sides from 512 to 2048. Kandinsky retains 64-pixel alignment; +ERNIE and GLM retain 32-pixel alignment. Precision, guidance, token and step +contracts are unchanged. Image-to-image actions that derive dimensions from the +source image continue to do so; this does not add unused width/height controls. diff --git a/docs/assets-model-turnover-and-template-completion-plan-2026-08-21.md b/docs/assets-model-turnover-and-template-completion-plan-2026-08-21.md deleted file mode 100644 index ef9dca0e..00000000 --- a/docs/assets-model-turnover-and-template-completion-plan-2026-08-21.md +++ /dev/null @@ -1,513 +0,0 @@ -# MoDiff asset, model-turnover, and template-completion execution plan - -Status: active execution plan -Created: 2026-08-21 -Owner: MoDiff engineering and qualification campaign -Scope: backend, client, frozen generation checkout, public-template migration, hidden/new templates, review assets, cached-model turnover, and subsequent downloads - -This document is the current operational plan. It supplements -`docs/generic-nodes-upstream-assets-completion-plan.md` and supersedes that document wherever the two conflict about asset quality, retry policy, cached-model eviction, or execution order. The older document remains useful as the source-coverage and generic-node architecture record. - -## 1. Required outcome - -Finish the template and asset program without weakening the generic-node architecture: - -1. A user can click a running/completed run notification and reach the exact owning workflow without an exception. -2. The 77 existing public templates remain loadable after migration, with bounded live canaries only where historical evidence is stale. -3. New template candidates are qualified with researched prompts, parameters, inputs, and real outputs—not merely structurally valid graphs. -4. Models already present in the Hugging Face cache are exhausted first: finish every in-scope workflow for a repository, preserve accepted evidence, then remove the exact revision through the app. -5. Reclaimed disk is used for the next planned model batch; downloads never bypass the app planner, the 64 GiB reserve, licenses, or exact revision checks. -6. Every open-source model, Diffusers pipeline/action, Transformers task, optional runtime, and quantization path exposed by the app has user-facing acquisition and usage documentation. - -No model-specific nodes may be added to make a template pass. Nodes stay generic and model-neutral. Exact model behavior belongs in backend-discovered field definitions, capability/profile records, template data, and immutable execution receipts. - -## 2. Non-negotiable execution gates - -### Gate A — notification navigation before generation - -No new template asset generation may start until all of these pass: - -- run-activity unit and lifecycle regressions; -- mocked notification/shelf navigation in Playwright; -- a live-backend test using a real recent run record and its real workflow snapshot; -- no `QuotaExceededError`, unhandled rejection, wrong-tab navigation, or lost task identity. - -Current state: **implemented and green on 2026-08-21**. - -The reproduced failure was concrete: `/workflows` returned 946 backend-owned documents totaling about 36.2 MB. The client treated every document as an open browser tab and Zustand attempted to copy them all into `localStorage`. Browser quota exhaustion then broke workflow switching and caused the backend-sync effect to retry every two seconds. - -Implemented correction: - -- backend workflows remain a saved-document library; -- startup hydrates only workflow IDs that are already open in that browser; -- unopened websocket workflow updates do not create tabs; -- the local crash checkpoint is bounded to 12 tabs and always retains the active tab; -- a failed local checkpoint cannot throw through normal UI actions; -- run restoration has an error boundary that opens the exact Queue task when a snapshot cannot be restored. - -Evidence: - -- 59 focused run/lifecycle tests passed; -- TypeScript passed; -- live Playwright opened task `QWdsR8VlMCPQ`, restored workflow `QRuV_2qFbhaDOj0iLINGn`, selected Studio, emitted no page error, and kept the local checkpoint below 2 MB; the repeatable focused test passed in 36.6 seconds. - -### Gate B — research before a generation attempt - -Every model-quality attempt needs a small research dossier recorded with the campaign recipe: - -- official model card and official Diffusers/Transformers documentation or source; -- exact base model, adapters, scheduler, revision, dtype, and runtime profile; -- recommended resolution, frame/sample count, steps, guidance, negative prompt, and conditioning rules; -- a prompt/input designed to expose the advertised capability; -- measurable acceptance criteria for the media kind. - -No identical retry is allowed. A second attempt must cite a materially different hypothesis. After two quality failures, stop until there is an engineering change, a different official recipe, or an explicit reviewed exception. - -### Gate C — quality before approval - -Technical success is not Gallery approval. - -- Image: useful card resolution, coherent subject/anatomy, prompt fidelity, no obvious broken objects, no accidental theme repetition. -- Video: real temporal change, legible motion, no near-static slideshow, no severe blur/flicker, correct duration/FPS, and a thumbnail/preview that reads as motion. -- Audio: normally at least 30 seconds for music showcases, clear intended content, no unexplained single tone, discontinuity, clipping, or synthetic placeholder input. -- Transform/utility: show a meaningful before/after pair or comparison; an isolated output that hides the operation is technical proof only. -- 3D: a viewable showcase size and orbit/motion sufficient to inspect the object. - -Automated screens can reject an asset but cannot grant final visual/listening approval. - -### Gate D — deletion and download safety - -No cached repository may be removed until: - -- all canonical workflow dependencies for that exact repository/revision are enumerated; -- all public migration canaries that use it are complete; -- every intended workflow has a durable accepted asset or a recorded non-executable blocker; -- technical metrics, output hashes, runtime fingerprint, and exact model revision are preserved; -- quality and rights/provenance states are explicit; -- no run or download is active; -- the app shows the exact revision and affected workflows before confirmation. - -No new download may start without two fresh, identical app-generated download plans with `fitsWithQueue=true` and at least 64 GiB reserved after queued bytes. - -## 3. Verified baseline - -### 3.1 Canonical workflow and migration structure - -- 199 canonical workflows exist in `data/workflow-library-manifest.json`. -- All 199 graph documents round-trip, hash, parse, and pass the current structural catalog checks. -- The focused generic fake-pipeline/action matrix passed 591 tests with 23 platform/live-only skips. -- Client template tests passed: 129 template, 16 task-template, 5 quality tests; workflow verification reported all 199 supported. -- All 77 public templates resolve to current canonical workflows. -- A legacy persisted template tab is adopted as a managed tab without moving or replacing its saved graph. - -These results prove structural migration and generic action routing. They do not prove that all 77 have fresh live outputs. - -### 3.2 Public-template evidence remaining - -The authoritative reconciliation is -`review-pending/public-templates/reconciliation.v1.json`: - -| Bucket | Count | Required work | -| --- | ---: | --- | -| `current_keep` | 26 | Preserve historical bytes; do not blanket-rerun. Close missing physical metrics separately if needed. | -| `stale_canary_required` | 44 | Run one targeted live canary on the exact current graph/runtime when its model remains cached. | -| `never_had_example` | 7 | Author, generate, review, and publish the first example. | - -The 44 stale records break down into 24 graph/node mismatches, 10 graph/node mismatches plus retired historical node names, 6 template-lock mismatches, 1 lock plus graph mismatch, and 3 lock plus prompt plus graph mismatch. Retired Qwen/LTX names are a migration boundary; they must not be restored as model-specific nodes. - -The seven first-example public templates are: - -1. `ace_step_chinese_new_year_lora` -2. `ace_step_custom_lora` -3. `flux_lora_cinematic_octane_3d` -4. `wan_21_t2v_13b_seed_vault` -5. `wan_22_i2v_seed_vault` -6. `wan_vace_masked_object_replace` -7. `wan_video_long_showcase` - -The 26 `current_keep` IDs are retained in the reconciliation ledger and should be treated as immutable historical examples unless a receipt-specific deficiency requires a metadata-only repair. - -### 3.3 Release-evidence blocker - -`npm run release:contract:verify` currently reports a stale release contract because this host lacks the 70 pinned `*.reviewed-provenance.json` files from the app-owned Gallery dataset. Running `release:contract:generate` in that state would incorrectly erase retained historical evidence. The safe choices are: - -1. install and verify the hash-pinned Gallery dataset, then verify/regenerate; or -2. change check mode so retained, hash-pinned release evidence remains authoritative when the dataset is absent. - -Do not regenerate the release contract until one of those is implemented. - -### 3.4 Current review state - -The human feedback record is `review-pending/user-quality-review-2026-08-21.v1.json`; the quality incident and corrective policy are in `docs/review-campaign-quality-incident-2026-08-21.md`. - -Before the rejected-asset cleanup, the staged review index deduplicated to 38 items: - -- 10 quality-approved but rights/provenance pending; -- 11 rejected; -- 17 awaiting human review. - -The feedback record contains 48 decisions because it also classifies duplicate/alternative campaign files and operation-only proofs. Approved bytes must be preserved. Rejected assets must not be silently replaced or promoted. Near-static videos are rejected as a class, not one by one after wasting another review cycle. - -There is a receipt/index consistency defect to fix before turnover. AuraFlow, ERNIE, LongCat Image, and PRX media files exist in the main `review-pending` tree and the user feedback ledger records quality approval, but the current main review index still lists those workflows under `blockedGeneration` as not generated. The frozen generation checkout has quality-acceptance JSON only for AuraFlow and PRX, and both still contain `userApproval: pending`; ERNIE and LongCat Image currently have media but no matching acceptance JSON beside it. The reconciliation step must create or update exact immutable receipts from the task/run evidence and user decision, then rebuild the index. It must not infer rights approval. - -Cleanup completed on 2026-08-21: 55 rejected/withdrawn files (23,316,849 bytes) were moved out of `review-pending` to the recoverable Trash quarantine at `/home/sayak/.local/share/Trash/files/modiff-review-rejected-2026-08-21`. Explicit approvals and the SanaVideo, SDXL-PAG, Wan VACE, and Wan Video “improve but keep” fallbacks were preserved. There are 25 top-level approved/fallback media files and no rejected media left under `review-pending`. - -The first exact campaign reconciliation is also complete for AuraFlow, ERNIE, LongCat Image, and PRX. Each main approved file was matched byte-for-byte to persisted `data/studio/outputs.json` evidence, including the original task ID, graph snapshot, repository, and immutable 40-character model revision. Per-asset `campaign_generation_review_receipt` files now preserve that evidence and the user quality decision. After the two upscaling candidates described below were added, the review index has 21 valid visible cards, zero broken asset references, 14 quality-approved/right-pending cards, 7 awaiting review, and 23 `removedReviewItems` audit records. The approved campaign receipts correctly remain turnover-ineligible where rights or runtime evidence is pending. None of those gaps was guessed or papered over. - -### 3.5 First new-template quality milestone - -Two app-managed Real-ESRGAN x2 workflows now have exact live ROCm evidence and are classified `execution-qualified-gallery-review-pending`: - -- `SpandrelImageUpscale:image_upscale`: three generic nodes (`Image.Load` → `Spandrel.Upscaler` → `Image.Preview`), 512×256 JPEG input, 1024×512 WebP output, 13.94 seconds, 1.22× edge-detail energy versus bicubic, and 0.021 reconstruction MAE. -- `SpandrelVideoUpscale:video_upscale`: two generic nodes (`Video.UpscaleVideo` → `Video.Export`), 81-frame 16 fps 512×288 input, 81-frame 16 fps 1024×576 output, 5.06 seconds preserved, 62.70 seconds execution, 1.19× detail energy versus bicubic, and 0.018 reconstruction MAE. - -Both candidates include exact model/revision/file hashes, source-fixture provenance, runtime fingerprints, task/run hashes, automated reports, manual Codex shortlist screens, and matched before/after cards. Automated screens are rejection-only. Both remain `pending_human_review`, with user approval, rights approval, Gallery registration, dataset publication, and model-turnover eligibility all false. - -The video run was not accepted merely because it produced MP4 bytes. Its source was replaced before generation: the earlier one-second/eight-frame synthetic canary measured almost no motion and was excluded. The selected five-second greenhouse push-in has approximately 18% materially changing pixels per transition. The first bounded run then exposed one implementation defect—the node returned an undeclared `fps` output key. The output schema was corrected, 21 focused contract tests passed, and one code-fix-driven retry succeeded. No identical recipe retry occurred. - -### 3.6 Cached hidden-template checkpoint — 2026-08-22 - -The next cached, repository-complete cohort is being worked through with the same rejection-first policy: - -- `JoyImageEditPipeline:text_to_image` is now shortlisted for workspace-owner review. The live graph used only generic quantization, recipe, pipeline-loader, image-generate, and preview roles. Selecting JoyAI dynamically bound its exact repository and the official 40-step, 1024×1024, guidance-4, 4096-token recipe. One pre-denoising canary exposed a stale model-scoped 2048-token adapter bound; current Diffusers 0.40 and its official API both declare 4096. The bound was corrected with focused regression coverage, then one media-producing run passed every objective screen and native/card inspection. User approval, rights approval, publication, and deletion eligibility remain pending. -- `JoyImageEditPipeline:edit_image` used the shortlisted 1024px violin-workshop image as a provenance-bound source and the model card's exact camera-control prompt pattern. Its first native attempt produced an all-black image and was rejected before review. Component probes located an edit-only ROCm numerical boundary: the source/VAE path stayed finite, but Qwen3-VL's image-conditioned prompt embeddings became entirely non-finite in BF16. A generic mode-scoped adapter bridge now runs only the JoyAI edit encoder in FP32, checks the multimodal embeddings, and casts them to the BF16 transformer boundary; the text-to-image path remains unchanged. Focused regressions and a real 512px one-step canary passed. The single code-fix-driven full rerun then produced a sharp, coherent camera change of the same scene. Its initial raw-correlation screen exposed a separate quality-gate defect: same-pixel correlation cannot validate a deliberate viewpoint transformation. Following OpenCV's feature-matching/homography guidance, the gate now requires RANSAC-supported ORB evidence, material overlap, and aligned correlation as an alternative retention proof; unrelated/noisy edits still fail, and 12 focused tests pass. The output passed all automated checks with 61 inliers, 75% overlap, and 0.436 aligned correlation, but native inspection rejected it because the closer framing cuts off the violin scroll. Bounded attempts are exhausted; no third JoyAI edit run is allowed without a new decision and materially different reviewed strategy. The repository remains non-evictable while the text-to-image shortlist awaits user/rights decisions. -- `OmniGenPipeline` text-to-image, edit, and multi-reference modes each reached real execution, but the bounded quality attempts failed native prompt/anatomy/reference-fidelity inspection. All rejected media was moved to recoverable Trash and the family is `quality_blocked`; do not retry without a different official recipe or an engineering change. -- `HunyuanDiTPAGPipeline:text_to_image` exposed two concrete contract defects: the adapter targeted PAG block 1 instead of the official block 14, and its long prompt exceeded the CLIP 77-token surface. Both were corrected and tested. The corrected output improved materially but still failed native geometry/text/path fidelity, so it was rejected and the workflow is `quality_blocked` rather than retried again. -- `flux_lora_cinematic_octane_3d`, one of the seven public templates that never had an example, now has two exact live generic-graph attempts and remains `quality_blocked`. The first official 24-step, guidance-3, 768x1024 run was objectively soft and turned ambiguous scar language into broad facial trauma. After official BFL/LoRA research, only the canonical prompt changed: complete framing, sharp eyes, healed-scar/no-trauma wording, and concrete skin/fabric/metal microtexture; both triggers, adapter weights, seed, dimensions, steps, and guidance stayed fixed. All 199 workflow hashes and all 129 template tests passed before the one corrected run. That run completed in one task but still failed the objective detail floor (0.004557 versus 0.008), produced a fresh bloody cheek wound, and omitted the brass pressure collar. Both rejected assets were quarantined; no third attempt is permitted without a newly reviewed concept/seed and a concise prompt validated in a cheap canary first. Generic nodes were not specialized. -- `PixArtSigmaPipeline:text_to_image` is now shortlisted for workspace-owner review after a numeric qualification and one full generation. The earlier 512px/16-step/FP16 campaign output was black. A two-step component probe showed all 65,536 PixArt latent elements become non-finite in BF16 on this ROCm APU, while a coherent FP32 path remained finite (standard deviation 0.311883); a mixed FP32-transformer/BF16-text path was invalid because PixArt derives the latent dtype from the prompt embeddings. The generic PixArt standard and PAG profiles therefore use coherent FP32 on this qualified host, with no model-named execution node. The canonical generic graph dynamically binds native 1024x1024, 20 steps, guidance 4.5, and 300 T5 tokens. The single authorized full run produced a 1024px kinetic-sculpture image that passed every objective check (edge energy 0.042932, luminance standard deviation 0.271577, perceptual-neighbor distance 19) and native/card inspection. Its exact task, run, repository revision, output hash, research dossier, quality report, and shortlist receipt are staged under `review-pending/PixArtSigmaPipeline__text_to_image`. It is not self-approved: user quality review, rights review, publication, and model turnover remain pending. The repository must also be retained until the PAG sibling has a final disposition. -- `PixArtSigmaPAGPipeline:text_to_image` was researched and corrected before its only full attempt. Official Diffusers uses `blocks.14`, PAG scale 4, and guidance 1 for PixArt PAG; MoDiff had implicitly inherited `blocks.1` and used PAG 3/guidance 4.5. The generic adapter, backend capability, client dynamic profile, campaign recipe, canonical graph, hash-pinned ledgers, and regression tests now share the official contract. The exact 1024px FP32 output passed the rejection-only technical gate (edge energy 0.047379; perceptual-neighbor distance 24) but failed native inspection decisively: it rendered one globe-like sphere inside one pseudo-lettered ring, omitting the required central sun, three planets, concentric arms, and gear train. It was rejected before human review, the media/card were quarantined, and no unchanged or seed-only retry is permitted. The workflow is `quality_blocked`; its sibling standard output remains the sole PixArt shortlist awaiting user/rights review, so the shared repository is still not turnover-eligible. -- `LongCatImageEditPipeline:edit_image` is now shortlisted for workspace-owner review on its first model attempt. Its prior campaign record failed at the graph-finalization boundary before loading a model or generating media. Official LongCat research established the exact BF16, model-offload, 50-step, guidance-4.5, seed-43 recipe and identifies precise editing plus non-edited-region consistency as the core capability. The already user-quality-approved LongCat tram-stop image was bound as an exact-hash internal source, with source rights still pending. A localized instruction changed only the red umbrella canopy to mustard-yellow waxed canvas. The 1024px output passed native/card inspection and retained person, pose, clothing, umbrella geometry/shaft/handle, shelter, bench, timetable, rails, buildings, lighting, reflections, perspective, and framing. The initial global-MAE gate falsely classified this precise edit as unchanged, so the rejection-only evaluator was corrected—not the media rerun—to admit a high-delta localized region only when strong composition evidence is also present. Twenty-three quality/review tests pass; the final evidence records 5.1579% localized high-delta pixels, 0.925 raw and 0.969 aligned correlation, 1,805 inliers, 97.6% inlier ratio, and 99.6% overlap. The exact task/run/model/source/output hashes and before/after card are staged under `review-pending/LongCatImageEditPipeline__edit_image`. User quality approval, source/model/output rights approval, publication, and turnover remain pending. Because this exact 29.31 GB edit repository has one current workflow, approval plus rights closure would make it a high-value app-deletion candidate. -- `StableDiffusionXLInstructPix2PixPipeline:edit_image` was researched against the official 768px FP16, 30-step, guidance-3.0, image-guidance-1.5 recipe and the exact cached 12.63 GB revision. Its earlier browser-closed campaign had never reached inference. The first corrected campaign exposed a real generic-binding defect before model load: the execution spec also routed `conditioningScale=1.5` into the unrelated 0–1 `reference_strength` field. The generic spec now removes that binding for InstructPix2Pix, retains only `image_guidance_scale`, has 44 focused backend tests, and regenerated/verified the canonical graph with no hidden reference-strength value. The first and only model attempt then completed in 30.50 seconds, but both gates rejected it. The output was coherent blue botanical line art on white paper rather than a recognizable Prussian-blue cyanotype, several flower forms were redrawn, aligned overlap was zero with only four inliers, and the comparison-card retention check failed. The media/card were moved to recoverable Trash, the rejection receipt remains under `review-pending/rejections`, and the repository stays `quality_blocked`; no unchanged or seed-only retry is allowed. - -- `CogView4Pipeline:text_to_image` is now quality-blocked after a researched corrective attempt. Native review first rejected the frozen 1024px/28-step green-kettle candidate as soft, geometrically weak, and too trivial for a 6B-model showcase; it was never copied into the main review queue. Official CogView4 sources require BF16/FP32, dimensions divisible by 32, at most 2^21 pixels, and demonstrate 50 steps at guidance 3.5. The replacement used a new 1280×768 observatory-library brief and seed. An initial 1280×720 submission exposed a contradiction in the official card, which lists that size in its memory table while also requiring multiples of 32; MoDiff rejected height 720 before denoising with zero accelerator allocation. The corrected single denoising run completed in 327.26 seconds and passed objective size/detail/duplicate gates, but native inspection rejected it: two windows instead of one, no spiral stair, repetitive bars instead of a book wall, and synthetic render quality. The media/card were moved to recoverable Trash. Research and two rejection receipts remain; no third attempt is allowed. The campaign runner now reads reviewed research dossiers and excludes every `quality_blocked*` workflow until its dossier records a materially new resolution, preventing broad campaigns from silently looping rejected families. Seventy-one runner tests pass. - -This checkpoint does not authorize model eviction. The JoyAI repository remains required until its edit side has a final disposition and the workspace owner has made the recorded rights/turnover decision. - -## 4. What “new templates” means - -There are two distinct new-work queues. - -### 4.1 Seven public templates with no example - -These already exist as public template definitions but have never had a Gallery asset. They are the highest-priority “new public example” work, subject to cached dependencies and the research gate. - -### 4.2 148 hidden candidate contracts - -`data/template-candidate-contracts.v1.json` and -`data/template-authoring-specs.v1.json` contain 148 hidden candidates complementing the public set: - -- 100 image; -- 33 video; -- 6 audio; -- 9 JSON/data; -- 55 input-free; -- 93 requiring selected source media, masks, controls, references, or rights-cleared fixtures. - -All 148 have canonical defaults and draft authoring specs. The two upscaling candidates now have execution-qualified, Gallery-review-pending evidence; the remaining candidates stay generation-pending. None becomes public merely because its graph is valid. - -The 55 input-free candidates are: - -```text -AllegroPipeline:text_to_video -AnimateDiffPAGPipeline:text_to_video -AnimateDiffPipeline:text_to_video -AnimateLCMPipeline:text_to_video -AudioLDM2Pipeline:text_to_audio -AuraFlowPipeline:text_to_image -BuiltinDataOperation:data_conversion -BuiltinDataOperation:graph_utility -BuiltinDataOperation:text_select -ChromaPipeline:text_to_image -CogVideoXPipeline:text_to_video -CogView3PlusPipeline:text_to_image -CogView4Pipeline:text_to_image -ConsistencyModelPipeline:unconditional_image -DDIMPipeline:unconditional_image -DDPMPipeline:unconditional_image -DreamLiteMobilePipeline:text_to_image -DreamLitePipeline:text_to_image -ErnieImagePipeline:text_to_image -GlmImagePipeline:text_to_image -HuggingFaceAnyToAnyModel:text_generation -HuggingFaceAnyToAnyModel:text_to_image -HuggingFaceTextGenerationModel:text_generation -HunyuanDiTPAGPipeline:text_to_image -HunyuanDiTPipeline:text_to_image -JoyImageEditPipeline:text_to_image -Kandinsky3Pipeline:text_to_image -LTX2ConditionPipeline:text_to_video -LTX2Pipeline:text_to_video -LatentConsistencyModelPipeline:text_to_image -LattePipeline:text_to_video -LongCatAudioDiTPipeline:text_to_audio -LongCatImagePipeline:text_to_image -Lumina2Pipeline:text_to_image -LuminaPipeline:text_to_image -MochiPipeline:text_to_video -NucleusMoEImagePipeline:text_to_image -OmniGenPipeline:text_to_image -OvisImagePipeline:text_to_image -PRXPipeline:text_to_image -PixArtSigmaPAGPipeline:text_to_image -PixArtSigmaPipeline:text_to_image -SanaPAGPipeline:text_to_image -SanaPipeline:text_to_image -SanaSprintPipeline:text_to_image -SanaVideoPipeline:text_to_video -ShapEPipeline:text_to_3d -StableAudioPipeline:text_to_audio -StableDiffusionPAGPipeline:text_to_image -StableDiffusionPipeline:text_to_image -StableDiffusionXLPAGPipeline:text_to_image -StableDiffusionXLPipeline:text_to_image -StableDiffusionXLTurboPipeline:text_to_image -Wan22Pipeline:text_to_video -WanVACEPipeline:text_to_video -``` - -The exact 92 conditioned candidates and their required inputs are maintained in the candidate contract rather than duplicated here. Major cohorts include 17 built-in media operations; SD/SDXL edit, ControlNet, PAG and adapter variants; FLUX edit/control/inpaint/outpaint variants; Qwen editing/control/layered workflows; LTX/Wan video conditioning; audio continuation/repaint/variation; image depth; speech; and the dedicated video upscaler. - -#### 4.2.1 Janus bounded qualification checkpoint (2026-08-22) - -The cached `deepseek-community/Janus-Pro-1B` family remains generic through -`LoadAnyToAnyModel` and `GenerateAnyToAny`; no Janus-specific Studio node was -introduced. Official model-card and Transformers documentation established the -native 384-by-384, 576-token, BF16 sampled recipe. Text generation completed but -failed the reviewed output contract (repetitive, truncated headings instead of -five requested bullets), so it is quality-blocked without another prompt retry. - -Image qualification exposed three distinct upstream development-pin boundaries: -the Janus static-cache call omitted the newly required `prefill_chunk_size`, the -pipeline postprocessor passed `PIL.Image.Image` as an unsupported tensor type, -and the NumPy postprocessor received a ROCm tensor without a host copy. Each -boundary received one source-driven finite compatibility fix with focused -contract tests. The third fix is implemented and tested, but another live run is -not authorized in this bounded sequence. The workflow is therefore -`technical_blocked_pending_later_canary_authorization`; it must not be retried -unchanged or counted as an asset. No media was emitted by any of the three -technical attempts. - -The next cached Transformers-family canary, SmolVLM 256M image-to-text, used the -workspace-owner-approved AuraFlow rainy-shelter image and the official BF16, -chat-template, deterministic recipe. It completed in 14.6 seconds on ROCm and -proved the generic image-to-text graph executes, but its four-sentence response -violated the requested one-sentence format, contained “an red umbrella,” and did -not explicitly state that it was raining. The output was rejected and removed; -the exact task, input/output hashes, text, and runtime measurements are retained -in the rejection receipt. No unchanged retry is allowed. - -### 4.3 Image and video upscaling - -At least one canonical workflow already exists for each: - -| Capability | Canonical workflow | Graph | Model/input state | Remaining publication work | -| --- | --- | --- | --- | --- | -| Image upscaling | `SpandrelImageUpscale:image_upscale` | `data/graphs/studio/spandrel-image-upscale/image-upscale.json` | Exact x2 model cached; live ROCm run and comparison candidate complete | Workspace-owner quality and rights review; broader platform qualification. | -| Video upscaling | `SpandrelVideoUpscale:video_upscale` | `data/graphs/studio/spandrel-video-upscale/video-upscale.json` | Exact x2 model cached; 81-frame motion-rich live ROCm run and comparison candidate complete | Workspace-owner quality and rights review; broader platform qualification. | - -`BuiltinImageOperation:image_upscale` remains a deterministic Pillow interpolation utility and is not presented as model-backed super-resolution. Both model-backed upscaling templates use the same generic node family and exact app-managed Real-ESRGAN x2 artifact; no model-named execution node was added. Their remaining work is human approval, rights closure, public visibility, and broader hardware qualification—not another generation run with the same recipe. - -## 5. Live cache and disk state - -Snapshot taken 2026-08-21 from the app endpoints: - -- 93 indexed Hugging Face cache entries; -- 87 complete and 6 incomplete; -- approximately 1.713 TB reported cache bytes; -- filesystem free: 106,983,280,640 bytes (about 99.6 GiB); -- reserve: 68,719,476,736 bytes (64 GiB); -- current headroom above reserve: about 38.3 GB; -- download queue: idle, zero queued. - -Earlier audit data is not blindly carried forward. `google/diffusiongemma-26B-A4B-it`, previously a dependency-zero deletion candidate, is no longer present in the live cache. It must not be redownloaded until it has a bounded executable contract. The change happened outside this execution lane and is recorded as external state, not as a completed MoDiff eviction action. - -Six inactive `.incomplete` blobs (mainly Marigold/LTX) total about 1.946 GB. They may be removed only through an app-supported stale-download cleanup, never manual unlinking. - -## 6. Cached-first execution and turnover order - -The objective is not “run every cached model once.” It is “close every in-scope workflow for a repository, preserve evidence, then reclaim the repository.” - -### Cohort 0 — preserve approvals and close receipts without rerunning - -First apply the user’s approvals to exact receipts, research/record model and source-media rights, fill duration/peak-memory/output-hash fields, and publish or archive immutable evidence. Do not spend GPU time replacing approved bytes. - -Best high-value sole-workflow repositories currently include: - -| Repository | Live cache size | Workflow | Current quality state | Public migration dependency | Turnover action | -| --- | ---: | --- | --- | --- | --- | -| `fal/AuraFlow-v0.3` | 65.96 GB | `AuraFlowPipeline:text_to_image` | user approved | none | Close rights/receipt, then eligible. | -| `baidu/ERNIE-Image-Turbo` | 31.63 GB | `ErnieImagePipeline:text_to_image` | user approved | none | Close rights/receipt, then eligible. | -| `meituan-longcat/LongCat-Image` | 29.32 GB | `LongCatImagePipeline:text_to_image` | user approved | none | Close rights/receipt, then eligible. | -| `Photoroom/prx-512-t2i-sft` | 15.51 GB | `PRXPipeline:text_to_image` | user approved | none | Close rights/receipt, then eligible. | -| `zai-org/CogView4-6B` | 31.13 GB | `CogView4Pipeline:text_to_image` | existing campaign proof needs final review/receipt closure | none | Present exact asset, close or rerun once if rejected, then eligible. | - -These candidates can potentially reclaim about 142 GB from the four already user-approved repositories before any additional generation. Exact sizes and revisions must be re-read immediately before deletion because cache state can change. - -### Cohort 1 — finish one remaining workflow, then reclaim - -| Repository | Size | Completed side | Remaining side | -| --- | ---: | --- | --- | -| `Efficient-Large-Model/Sana_600M_1024px_diffusers` | 10.07 GB | Sana PAG approved | Quality-blocked after two researched text-to-image attempts; require a new official-recipe hypothesis before any new generation. | -| `Efficient-Large-Model/Sana_Sprint_0.6B_1024px_diffusers` | 7.70 GB | Text-to-image approved | Edit quality-blocked after the two-attempt boundary; live deletion plan also found six saved-workflow dependencies, so retain. | -| `stabilityai/sdxl-turbo` | 6.94 GB | Technical execution only | Research and rerun once; current eyes/anatomy are rejected. | -| DreamLite Mobile repository | re-read live size | Text-to-image and edit approved | Close exact rights/metrics and confirm no other manifest dependency. | -| LCM DreamShaper repository | re-read live size | Text-to-image and edit approved | Close exact rights/metrics and confirm no public canary dependency. | - -### Cohort 2 — cached new workflows with meaningful reclaim - -After Cohorts 0–1, choose repositories where all remaining workflows can be bounded and where the resulting deletion creates material space. Examples include JoyAI Edit, OmniGen, HunyuanDiT variants, CogView3 Plus rework, Lumina2 rework, Kandinsky3 rework, and Z-Image’s four-workflow closure. Do not mix retry-prone GLM, AnimateDiff, ACE-Step, or static-video families into a clean turnover batch. - -### Cohort 3 — public migration holds - -Retain any repository used by the 44 stale or 7 first-example public templates until its exact canary/first example is accepted. Important holds include: - -- `black-forest-labs/FLUX.1-schnell`: retain for the FLUX Schnell public canary and exact GGUF/base assembly evidence; -- FLUX Dev/Krea/Control/Fill/Kontext families: public stale/first-example dependencies remain; -- Qwen Image families: many public stale canaries remain; -- LTX 13B and Wan 1.3B/2.2 families: stale and first-example video templates remain; -- ACE-Step base and LoRAs: stale audio templates and two first examples remain. - -The older `Lightricks/LTX-Video` cache (about 28.42 GB, revision `8984fa…97bb`) has no current 198-workflow or exact saved-workflow dependency in the previous audit, while current contracts use the 13B distilled repository. It remains a lower-confidence expert alternative. Re-audit source/catalog references before considering deletion. - -## 7. Rejected-asset repair queue - -Repairs are scheduled by root cause, not by filename order. - -### Audio - -- ACE continuation/repaint/variation/text-to-audio: use at least 30-second showcases (normally the reviewed 75-second source for edits), descriptive musical metadata, structured lyrics where appropriate, continuity/listening checks, and official ACE-Step duration/step guidance. -- AudioLDM2: name a recognizable sound scene, use the official 200-step/3.5-guidance baseline, generate bounded candidates, and select only if the intended scene is unambiguous. -- LongCat Audio: decide whether the example is music, speech, or singing; prompts and review labels must make that intent understandable. - -### Video - -- Automatically reject near-static outputs before human review using frame-difference/optical-flow thresholds plus contact-sheet inspection. -- AnimateDiff/PAG should use the official fine-tuned SD1.5 base and scheduler recipe. The current plain SD1.5 base is not the official quality recipe; do not rerun until the correct base is planned/downloaded. -- AnimateLCM likewise needs its official realism base; do not repeat the plain-base attempt. -- CogVideoX should use its official frame/step/guidance range and an explicit moving subject/camera prompt. -- LTX, Wan, and TI2V must visibly change in the card preview. Use longer, action-bearing prompts and preserve frame count/FPS; dedicated Wan video routes are preferred over VACE text-to-video when appropriate. -- ShapE needs a larger, inspectable orbit preview. - -### Image and utility - -- Preserve user-approved AuraFlow, Chroma, DreamLite Mobile, ERNIE, Flux Krea, FLUX GGUF, HunyuanDiT, LCM, LongCat Image, Marigold accepted variant, PRX, Qwen, Sana Sprint, SD PAG, and other explicitly approved bytes. -- Rework DreamLite base, Flux2 Klein, CogView3 Plus, Kandinsky3, Lumina2, SD/SDXL/SDXL Turbo, Sana theme duplication, and Z-Image with model-specific official defaults and new prompts. -- DDIM/DDPM/Consistency outputs remain technical capability proofs. Their native tiny resolutions must not be upscaled and mislabeled as quality showcases; use an explanatory comparison card or keep them out of the public Gallery. -- Built-in transforms must be before/after cards. Do not stage a bare output as if it demonstrated the operation. - -## 8. Download sequence after safe reclaim - -Every item below is conditional on two fresh app plans, available disk, exact license state, and the repository still being absent. - -1. Correct official AnimateDiff/AnimateLCM base models needed to repair rejected videos. -2. ShapE image-to-3D dependency, previously planned at about 1.69 GB, to close the conditioned ShapE workflow. -3. Stable Video Diffusion (about 4.51 GB) and Stable Audio (about 5.35 GB), only after license/token gates are satisfied. -4. Medium new-video batch: Latte (about 23.62 GB), Allegro (about 25.29 GB), and FramePack (about 25.75 GB), one repository at a time. -5. Mochi (about 40.03 GB) and Nucleus (about 51.66 GB) after another turnover checkpoint. -6. Large Wan additions only with dedicated space: FLF about 90.10 GB and T2V A14B about 126.20 GB. - -`city96/FLUX.1-schnell-gguf` Q4_0 is already cached and its visual output was user-approved. Do not plan another GGUF download for the same proof. - -## 9. Engineering work remaining - -### P0 — reliability and truthfulness - -- [x] Isolate browser/page failures between campaign templates. -- [x] Fix graph-finalization handoff races. -- [x] Require exact optional-runtime profile ID plus spec digest; remove the wrong-runtime bypass. -- [x] Preserve output MIME when exporting extensionless WebP results. -- [x] Fix notification/run navigation local-storage overflow and contain restoration errors. -- [x] Add compact workflow-library summaries and lazy exact-document loading so “My workflows” never transfers tens of megabytes just to render names. On the current 1,017-document library, the live metadata response is 223,879 bytes versus 38,359,684 bytes for the legacy full list (171× smaller); no summary contains a snapshot, and opening a row is browser-tested to fetch its one exact document. -- [x] Harden `DELETE /hf_cache/{revisionHash}` with queue/download interlocks and dependency closure checks. The app now requires a fresh immutable deletion-plan hash, blocks active/queued graphs, downloads, Gallery installs, open canonical receipts, and saved-workflow references, and shows the exact repo/revision/size before confirmation. -- [x] Add app-supported incomplete-download cleanup. Model Manager now obtains a hash-bound plan that includes only regular `models--*/blobs/*.incomplete` files older than one hour, blocks active/queued graphs, downloads, and Gallery installation, and revalidates each exact path/size/mtime under the cache mutation lock before unlinking. The first live execution removed six 9–38-day-old partial files and reclaimed 1,945,039,075 bytes; a fresh plan reports zero eligible files, while Marigold and both affected LTX snapshots remain complete and repair-free. No model revision was deleted. - -### P1 — migration and template contracts - -- [x] Prove all 77 public definitions resolve and all 198 canonical graphs are structurally healthy. -- [ ] Run 44 exact stale canaries, cached families first. -- [ ] Create the seven first public examples. -- [ ] Restore/verify the pinned Gallery provenance dataset before release-contract generation. -- [x] Add a live-backend family canary proving exact cached model selections replace backend parameter contracts while node modules remain generic. Hunyuan→PixArt, CogVideoX→Sana Video, and AudioLDM2→LongCat Audio all produced different finalized node contracts through `modules.DiffusersImage`, `modules.DiffusersVideo`, and `modules.DiffusersAudio`; Shap-E, Real-ESRGAN, and SmolLM2 additionally finalized through generic `modules.DiffusersThreeD`, `modules.Spandrel`, and `modules.HuggingFaceTransformers` nodes. The expanded focused Playwright canary passed in 1.3 minutes without executing a model. The Transformers catalog's package-level `present_unqualified` observation is intentional; the execution proof used the qualified, cutover-ready exact active `huggingface-transformers-main-96fe6dce-peft-0.20.0` profile and matching spec digest. -- [ ] Confirm image and video upscaling appear in the intended browser surfaces and publish one accepted template/card for each. - -### P2 — asset quality system - -- [x] Separate technical success, quality approval, rights approval, and publication state. -- [x] Ingest user feedback into a machine-readable quality ledger. -- [x] Refuse synthetic two-second audio placeholders and classify static videos. -- [x] Remove the old rejected/withdrawn campaign bytes from the visible review queue while retaining recoverable Trash copies and index audit records. -- [x] Require the exact media-kind automated screen plus a hash-bound, all-pass Codex screen before an item can enter the visible human-review index. Raw campaign bytes remain explicitly unreviewed and cannot be staged without both gates. -- [ ] Reconcile generated media, frozen-checkout acceptance JSON, user decisions, and the main review index into one authoritative receipt; fail staging when they disagree. Exact Studio-output/user-decision reconciliation is implemented and applied to the first four turnover candidates. Frozen acceptance ingestion now requires an exact workflow + media SHA-256 + task ID match, binds the immutable receipt hash, and closes task/runtime metrics only when those fields are present; focused tests pass. Applying it to AuraFlow, ERNIE, LongCat Image, and PRX proved that their frozen campaign receipts describe different bytes/tasks, so no evidence was inherited. Exact task-metric recovery for the approved bytes and legacy MIME filename normalization remain. -- [ ] Add comparison-card authoring for built-in transforms and upscalers. -- [x] Require every newly staged generation receipt to bind an exact dossier under `review-pending/research`, at least one official-source finding, the dossier SHA-256, and the canonical recipe content hash; staging fails closed when the quality report does not bind that same dossier. -- [ ] Add duplicate-theme and duplicate-source checks across the Gallery. - -### P3 — upstream and runtime compatibility - -- [x] Current generation runtime reports Diffusers `0.40.0.dev0` and Transformers `5.16.0.dev0`; the app is not simply stuck on an older released Diffusers version. -- [ ] Pin the exact upstream commits, regenerate source/capability ledgers, and run the full generic action matrix against those commits. -- [ ] Repair GLM’s Float/BFloat16 component boundary with a component-level dtype canary before another full generation. -- [ ] Keep Stable Audio 3 classes contract-only until an official Diffusers-format artifact exists. -- [ ] Requalify Wan/LTX exact routes under the pinned upstream runtime. - -### P4 — quantization and optional runtimes - -- [x] One exact FLUX GGUF Q4_0 path works and has user-approved visual output. -- [x] Optional-runtime readiness requires the exact active profile/spec pair. -- [x] Prevent TorchAO or Quanto from appearing usable when their app-delivered runtime is unavailable. The live optimization catalog reports both unavailable on the current ROCm runtime, all 17 execution profiles that declare either mode publish neither in `available_expert_quantization_modes`, Studio renders and validates against that filtered list, and 28 focused tests plus 55 subtests pass. -- [ ] Ship platform-specific immutable TorchAO/Quanto runtime profiles, qualifying one backend/platform at a time. -- [ ] Add one bounded bitsandbytes path on NVIDIA before expanding to AWQ/GPTQ. -- [ ] Document supported quantization by exact family, platform, downloadable artifacts, installer path, and fallback—not merely by library name. - -### P5 — generic client and documentation - -- [ ] Keep model and mode definitions backend-discovered; remove remaining duplicated client catalogs where the backend can be authoritative. -- [x] Verify dynamic node parameters for at least one model from each image, video, audio, 3D, Transformers, and upscaler family. The live backend canary now covers all six broad families and asserts that every finalized execution graph retains its generic module boundary. -- [ ] For every exposed open-source model, document license, revision, approximate download size, app download steps, required auxiliary artifacts, supported actions, recommended defaults, hardware/runtime qualification, and known limitations. -- [ ] For every exposed Diffusers/Transformers action, link the official API/model documentation and describe MoDiff input/output mapping. -- [ ] When the app cannot download an artifact automatically, show exact guided steps in-product; do not leave a silent missing dependency. - -## 10. Immediate execution sequence - -1. **Completed:** fix and verify notification-to-workflow navigation against a real recent run. -2. **Partially completed:** AuraFlow, ERNIE, LongCat Image, and PRX now have exact byte-, task-, graph-, repository-, and revision-bound quality receipts. Close the explicitly recorded rights, task-metric/runtime-fingerprint, and legacy MIME filename gaps without rerunning approved media. -3. **Completed:** deletion planning/interlocks are implemented and tested. A live plan for Sana Sprint enumerated two open canonical dependencies and six saved workflows; the exact DELETE was refused with HTTP 409 and the cached revision remained complete. Only revisions with a fresh green plan may now be evicted. -4. In parallel with receipt work, run cached public migration canaries that do not belong to a retry-prone family. -5. Sana 600M and Sana Sprint were researched and bounded. Sana text-to-image failed two quality attempts. Sana Sprint edit attempt 1 preserved too much and failed the brief; the source-researched 0.6-strength correction executed both denoising steps but destroyed source composition. Both Sana Sprint edit outputs were rejected and removed to recoverable Trash. Do not retry either recipe without a new source/brief qualification study or a different exact model. -6. Create the image-upscale and video-upscale comparison assets using the already cached upscaler. -7. Work the seven first public examples, prioritizing cached ACE/FLUX/Wan dependencies while keeping video/audio quality gates strict. -8. Reclaim space at each fully closed repository checkpoint; run two fresh download plans and acquire only the next bounded batch. -9. Repair rejected families only after their research/engineering prerequisite is met. -10. Continue through the hidden candidates in cache-first, repository-complete cohorts. - -## 11. Definition of done - -A template is complete only when all applicable items exist: - -- canonical generic graph and current graph hash; -- exact template/catalog lock; -- successful real-run receipt on a named runtime/hardware profile; -- model and auxiliary artifact revisions; -- prompt/settings/input provenance and recipe-research references; -- output hash, MIME/extension agreement, duration/dimensions/FPS, execution time, and peak memory; -- automated media-kind screen; -- explicit human quality decision; -- explicit rights/provenance decision; -- published Gallery asset or documented reason it remains hidden; -- no unresolved migration or dynamic-field mismatch. - -A model revision is turnover-complete only when every dependent template/workflow is complete or explicitly blocked, durable evidence is outside the cache being removed, the app dependency check is green, and post-delete verification confirms both reclaimed space and retained Gallery assets. - -## 12. Estimated effort - -These are active engineering/GPU-time estimates, not promises about unattended wall time: - -- notification fix and live gate: complete; -- receipt closure plus safe deletion interlock: 4–8 hours; -- first high-value turnover checkpoint: 0.5–1 day, mostly rights/receipt verification; -- cached public stale canaries: 1–3 days depending on video/audio runtime; -- seven first public examples: 2–5 days because ACE/Wan quality review is expensive; -- all 147 hidden candidates: multi-week qualification program, dominated by the 92 conditioned inputs, new downloads, video runtimes, and human review; -- upstream pin, GLM dtype, first app-delivered TorchAO/Quanto profile, and documentation baseline: 2–5 engineering days, with NVIDIA-only BnB qualification requiring the relevant host. - -The queue should be re-estimated after every repository turnover, upstream pin change, or batch of human decisions. The plan must never hide a quality or dependency blocker by counting a technically generated file as a finished asset. diff --git a/docs/cluster-engineering-lessons.md b/docs/cluster-engineering-lessons.md index 00eda9db..15153c8d 100644 --- a/docs/cluster-engineering-lessons.md +++ b/docs/cluster-engineering-lessons.md @@ -516,10 +516,17 @@ the visible leaf or nearest collapsed ancestor for rendering and to the same leaf for execution. Removing the last connection removes the derived socket. Reusable saving retains only contained nodes and deliberately declared ports. -Run Block isolates the selected containment subtree before resolving external -dependencies, including dependencies through existing declared inputs. Use stored -fallbacks or report missing inputs. Whole-graph execution retains crossing edges. -Test all enabled local terminal branches, not only the first preview. +Run Block selects the contained terminal outputs and retains their transitive +upstream dependencies, including ordinary nodes and other Blocks connected through +public or derived crossing inputs. Nested selection also retains required sibling +suppliers in its owner. Exclude downstream outputs and unrelated drafts before +validation; never silently replace a connected input with its stored fallback. +Missing or disabled suppliers still receive normal readiness checks. Reusable +saving remains containment-only: running with an outside dependency does not adopt +it into the saved Block. Test all enabled local terminal branches, mirrored +inputs, connected LoRA/seed/media sources, collapsed/expanded persistence and an +unrelated invalid draft. W5 live multi-reference editing exposed the earlier +isolation behavior dropping valid image and seed connections before submission. Plain movement changes presentation and grows containing frames. Only the explicit modifier drag or toolbar move changes ownership. Moving a node must @@ -610,3 +617,140 @@ runtimes or rewrite graph inputs. values, hierarchy, crossing wires and previews without changing the library. - Prove cache destruction and material-output retention in executor tests, then retain a real multi-owner model run separately from contract-test evidence. + +## Metadata during inference and recovery evidence + +- Review field callbacks against the public registry. Isolate only exact built-in + callbacks that inspect declarations and publish fields; arbitrary custom or + queued callbacks cannot opt themselves out of the execution lease. Explicit + model-layer inspection may construct empty models and must remain serialized. +- A presentation callback must not instantiate an executable node. Constructors, + destructors and shared component collections can affect an active owner even + without calling `execute`. Copy declarations and borrow only reviewed helpers. +- Fresh metadata contexts have no previously published schema. Use an unset + sentinel distinct from both an empty model selection and `None`; otherwise + disconnecting a model can leave stale dynamic fields visible. Test disconnects + as well as supported-model selections and invalid inputs. +- Carry workflow, canvas and form identity on every schema message, including + legacy custom Block labels and styles. Cancellation must drain an executing + metadata thread before releasing its lease or activating another runtime. +- Separate native Stop/queue/reconnection proof, actual model reuse messages and + controlled allocator-fault recovery. Record fault injection explicitly, retain + failed receipts, and require a successful unchanged-settings run afterward. +- Keep successful/running node status distinct from validation warnings. A new + attempt must clear stale node error details while retaining the failed task's + history. Inspect the actual recovery UI as well as terminal backend receipts. +- Keep ordinary image processing in normal discovery alongside text/value + utilities. Reducing implementation clutter must not hide Resize, masks, grids + or saving. Test discovery before selecting a pipeline and native insertion in + both workspaces, then verify the processor is connected and actually consumed. + +Validate upstream defaults that depend on another argument, not only accepted +parameter names. Sana Sprint's intermediate timestep is specialized for two +steps; other supported counts need the upstream evenly spaced schedule. Preserve +the user's requested count, adapt the backend call, and test genuine pinned input +validation and scheduler construction for every declared count and related task. +A fake pipeline that accepts arbitrary keyword arguments cannot expose this bug. + +A successful generic run does not prove that new-workflow defaults belong to the +selected model. Resolve the exact public execution profile before seeding dtype, +size, steps and guidance. Shared upstream classes can have different reviewed +model recipes. Reuse backend-owned capability values for new operations only; +never rewrite saved values or treat authoring defaults as a runtime constraint. +Test both individual node insertion and connected starters, profile aliases, +field bounds and unchanged prior drafts. Inspect the resulting images too. + +Restored previews can use the recorded runtime cache URL or its durable media +URL, with a refreshed cache-busting timestamp. Match the declared output preview +against those exact task references, then verify dimensions and downloaded bytes +against the retained media hash. Do not require one URL representation or accept +an unrelated image merely because it decodes; exclude input images and history +thumbnails from the preview assertion. + +A history worker can still stall HTTP requests if one native JSON operation holds +Python's GIL across the entire retained collection. Profile a real cold browser +while another workflow generates; distinguish schema work, Saved Block validation, +history parsing and response encoding. Move validation off the HTTP loop and +bound native JSON work by individual output records. Preserve older current +previews, complete graph snapshots, Unicode and revision semantics. If caching +parsed history, retain immutable record bytes, give readers independent objects, +and invalidate on file identity/content metadata changes, deletion and corrupt +replacement. Publish cache state only after a successful atomic write. Never +trade away output recovery or silently extend a failed responsiveness budget. + +Generic controls must publish the same bounds enforced by their selected backend +adapter. A correct model default can still leave a slider offering unsupported +values. Use the shared field overlay for both connected starters and dynamic +model changes, test narrow-to-wide and wide-to-narrow transitions, and retain +authored values and historical signal identities. Verify separate unconditional +and perception contracts instead of applying prompt-conditioned limits to them. + +Optional image processors can expose native structured configuration types rather +than plain dictionaries. Check real pinned preprocessing and postprocessing APIs +with no-download tests before model runs; mocked interfaces cannot establish that +contract. Keep native numeric depth separate from a normalized visual preview, +and carry new consumed controls through both backend receipts and the client's +strict parser so persistence does not silently discard their execution evidence. + +Generic workflow discovery must not depend on the closed legacy Studio model +union. Join bounded backend display metadata to exact published pipeline/profile +identities, while retaining legacy form validation and backend execution preflight. +Test an unknown model identity through parsing, search and native creation; a +backend registry test alone cannot prove that the workflow chooser exposes it. + +Test the actual Create Block and Saved Block insertion path before claiming +model switching inside Blocks. A test-built V2 fixture can miss a legacy snapshot +still produced by native saving. Adapt only the selected workflow instance during +an explicit reviewed edit, with conversion and replacement in one Undo transaction. +Opening controls or cancelling a preview must leave the graph and saved library +unchanged. Preserve advisory loader references when adapting historical node IDs. +An exposed model control can override a correctly replaced internal loader; +update editable controls only when all bound targets agree, and reject sealed or +unrelated mirrored bindings instead of silently changing other owners. + +A current Block media reference must supersede stale artifacts retained in its +source node. Media widgets can prefer an artifact URL over the displayed value; +keep source metadata only when its URL and task match the instance preview. +Test the rendered media after reload, including source fields carrying an older +successful run, and retain immutable definitions and unrelated preview owners. + +## Windows qualification: cache, hooks, readiness and test scope + +- Inventory and execution must resolve the same managed Hugging Face cache + roots. Startup may redirect Hub writes, so retain discovery of the original + user cache. Prefer the configured root, preserve exact immutable revisions, + reject cross-root/symlink escapes and never convert inference into a download. +- A pipeline's root forward hook does not cover every native entry point. + Exercise direct embedding/encode/decode calls and direct parameter/buffer + reads with the real pinned hooks, including repeated calls. Legacy + weight-normalization pre-hooks can rebuild weights before a transfer hook. + Keep reviewed exceptions small and adapter-owned; do not change all pipelines' + placement policy or mutate a class-level exclusion list. +- An incoming loader wire does not prove that a required source file exists. + Inspect the selected executable closure and current required-field contracts + without rewriting saved schemas. Preserve optional empty branches. Required + input errors must remain blocking in both Automatic and Custom memory modes. +- An isolated launcher must configure storage before importing the normal + server singleton: node events may publish through that singleton. A second + server can answer HTTP while losing dynamic fields and previews. Handle + unsupported Windows event-loop signal APIs without altering application + shutdown semantics. +- Browser uploads can intentionally cancel when the graph changes. Wait for a + decoded input preview before subsequent edits; then verify the generated + preview against its task receipt and media hash, not an input or thumbnail. + Exercise production bundles separately from development-only test hooks. +- Opt-in generation tests need explicit backend isolation, installed-artifact + checks, an idle queue, bounded execution and task-scoped cleanup. A file-path + environment variable or an empty queue alone does not establish isolation. +- Keep reviewed upstream source checkouts outside ordinary application test + discovery. Distinguish collection/harness failures from product failures; + reproduce suspect pre-existing assertions on the original commit. +- Review authored source separately from generated bundle/hash churn. Rebuild + through the established tooling and verify emitted bytes; do not hand-edit + minified files or mistake regenerated inventory hashes for new qualification. + Stop expanding the campaign when the requested scope has sufficient evidence. + +Keep acceptance criteria in [workbench acceptance](workbench-acceptance.md), +implementation contracts in technical references, and dated plans/run ledgers in +Git history or local evidence. Retiring a tracker must preserve unresolved +qualification boundaries, not silently mark them complete. diff --git a/docs/compact-modular-blocks-and-model-route-switch-plan-2026-09-04.md b/docs/compact-modular-blocks-and-model-route-switch-plan-2026-09-04.md deleted file mode 100644 index 71de8dcf..00000000 --- a/docs/compact-modular-blocks-and-model-route-switch-plan-2026-09-04.md +++ /dev/null @@ -1,175 +0,0 @@ -# Compact Modular Blocks and Safe Model Route Switching - -Date: 2026-09-04 - -## Outcome - -An expanded Diffusers Cluster must remain a faithful visual projection of the reviewed Modular Diffusers workflow while being practical to edit: - -- every upstream block remains an independently movable, reconnectable, replaceable node; -- blocks with no editable controls use a compact body instead of an arbitrary 500 px height; -- blocks with a few controls size to their content, while prompt, loader, and preview nodes retain useful working space; -- the compatible model choice is available as a normal instance control on the expanded loader node and collapsed Cluster; -- selecting a model resolves one reviewed repository + immutable revision pair, not an unsafe repository-string mutation; -- compatible prompts, parameters, public connections, internal-node sizes, and internal-node positions survive a route switch; -- same-family variants with an identical component/workflow contract keep the exact same nodes and edges; -- save, refresh, app restart, collapse, and re-expand preserve the selected variant and retained state. - -## Upstream findings and constraints - -The implementation follows the official Hugging Face contracts rather than treating similarly named repositories as interchangeable: - -- `Qwen/Qwen-Image` and `Qwen/Qwen-Image-2512` are text-to-image repositories using the Qwen Image pipeline. The 2512 model card calls it the December update of the original model. -- `Qwen/Qwen-Image-Edit-2511` is an image-edit model. It belongs in the instruction-edit route set and cannot be substituted into a text-to-image Cluster. -- Modular Diffusers maps a repository to its default block collection through `ModularPipeline.from_pretrained`, supports alternate workflows through `blocks.get_workflow(...)`, and initializes a runnable pipeline from the resulting block graph. A model switch must therefore select a reviewed repository + workflow + immutable revision together. -- Internal block boundaries are semantic. Visual compaction must not merge executable blocks or bypass their state/component ports. - -Primary references: - -- -- -- -- -- - -## Why the selector appeared disabled - -The expanded loader displays the registered definition's `model_type`, `repo_id`, and immutable revision as sealed fields. Sealing is correct for those raw fields: editing only the repository would invalidate the reviewed `BlockDefinitionV2` graph, artifact pin, resource recipe, and execution contract. - -The earlier loader had no ordinary instance parameter representing a reviewed same-family model variant. The user therefore saw a disabled repository identity with no usable replacement. The implementation now adds one `Model` control whose values are restricted to immutable, reviewed repositories for that exact pipeline + workflow. The raw definition repository and revision remain sealed evidence; execution resolves the selected variant to its catalog pin atomically. - -The initial dynamic-field implementation also exposed a second failure mode: a transient backend field publication could contain only the default repository, causing the frontend to render read-only text. The final compiler therefore materializes the exact option list from the registered route's immutable `reviewedArtifacts` contract. Runtime and resource admission independently validate the selected repository and revision; the UI is no longer dependent on ambient node-registry state for this selector. - -## Implementation stages - -### 1. Content-aware node presentation - -Add one deterministic sizing policy used by exact reviewed Modular Diffusers compilation and layout reset: - -- state/loop blocks with no visible controls: compact; -- blocks with one to three scalar controls: small; -- blocks with more scalar controls: medium; -- prompt or other multiline controls: tall enough for useful editing; -- model/component loader: medium/tall because immutable artifact evidence and runtime controls are useful; -- preview/media nodes: media-sized. - -Store these as default `presentation.internalLayout` values. Preserve any user-resized width/height already stored for an instance. Provide a `Compact internal layout` action so existing instances can explicitly adopt the current defaults without changing graph semantics. - -### 2. Same-family model selector - -Add a generic reviewed-variant parameter to the Modular Diffusers loader. The backend publishes options only when multiple immutable repositories implement the same installed pipeline and workflow contract. Registered `BlockDefinitionV2` compilation binds that field as an ordinary instance control, so the existing Block renderer shows the same control in collapsed and expanded modes without a special Cluster-only widget. - -For Qwen text-to-image the first admitted options are: - -- `Qwen/Qwen-Image@75e0b4be04f60ec59a75f475837eced720f823b6`; -- `Qwen/Qwen-Image-2512@25468b98e3276ca6700de15c6628e51b7de54a26`. - -Both publish the same `QwenImagePipeline` component classes. `Qwen/Qwen-Image-Edit-2511` is excluded because it is an image-edit pipeline. Catalog-only, unpinned, or structurally incompatible repositories are unavailable. - -### 3. Compatibility-driven reconciliation - -Replace the current `prompt`/`seed`-only carry rule with declarative reconciliation within a modality route set: - -1. Match controls and boundary inputs by stable logical ID. -2. Require compatible `BlockValueTypeV2` declarations. -3. Carry the current value only when both conditions pass. -4. Never carry sealed artifact identity, pipeline class, workflow identity, or revision fields. -5. Preserve internal layout for target nodes with the same semantic role. -6. Use the target definition's nodes, edges, defaults, and immutable identity for everything unmatched. -7. Validate every existing external edge before committing the switch. - -For the admitted Qwen text-to-image variants, the reviewed workflow schema is identical. The selected model changes as one bound instance value, so the effective graph, prompts, parameters, connections, node positions, and node sizes remain byte-for-byte unchanged. The generic matching rules remain available for a future same-task family whose reviewed variants genuinely require different internal stages, but this phase does not expose cross-family switching. - -Switching never mutates or replaces the registered definition. For compatible Qwen text-to-image checkpoints, the workflow keeps the same `definitionRef`; only the ordinary `modelVariant` instance value changes. No route draft, node replacement, edge replacement, or layout reconciliation is involved. Structural user edits remain workflow-instance data or a User Node definition according to the existing save choice. - -### 4. Tests - -Unit and store tests: - -- zero-control and scalar-control blocks receive compact deterministic defaults; -- manual child resizing is not overwritten by ordinary expand/collapse; -- compatible values and internal layouts survive route changes; -- incompatible values, sealed identity, and target-only defaults do not leak; -- external boundary edges either survive with identical type/port contracts or block the switch before mutation; -- switching back restores the route-specific draft; -- route-switch failure is atomic and undo/redo is one history operation. - -Browser tests: - -- insert Qwen text-to-image Cluster from an empty graph; -- expand it and change the model from the loader; -- verify the node and edge sets do not change and prompt/negative prompt/size/steps/seed are retained; -- resize/move an internal node, switch variants twice, and verify its layout is retained; -- save, refresh, and verify the selected model and values; -- collapse/re-expand and verify the same state; -- run through the frontend and inspect the generated asset. - -Live routes: - -- Qwen text-to-image: `Qwen/Qwen-Image-2512@25468b98e3276ca6700de15c6628e51b7de54a26`. -- Same-family base variant: `Qwen/Qwen-Image@75e0b4be04f60ec59a75f475837eced720f823b6`, after its immutable artifact catalog record and execution/resource validation are in place. - -All model acquisition must use the application's Hugging Face Hub snapshot flow. No `wget`, mutable branch, or unreviewed remote Python path is allowed. - -## Acceptance criteria - -- The first expanded loader has an enabled model selector with accessible busy/error feedback. -- Its immutable repository, revision, pipeline, and workflow fields remain visible and sealed. -- Empty upstream blocks are compact but remain separately selectable, movable, reconnectable, and replaceable. -- A Qwen-Image-2512 → Qwen-Image → Qwen-Image-2512 switch changes only the selected reviewed artifact value; node IDs, edges, prompts, parameters, and internal layout remain unchanged. -- A saved workflow survives browser refresh and backend restart without resetting its selected route or retained values. -- Focused unit/store/browser tests, client check/build, backend contract tests, and one real frontend generation pass. -- Screenshots, graph snapshots, receipts, and generated media are kept outside Git under a new review-only directory containing no historical assets. - -## Implementation checkpoint - -Current implementation scope is deliberately task-safe: - -- `Qwen Image — Text To Image` now owns the `Model` instance control; -- its admitted choices are only `Qwen/Qwen-Image` and `Qwen/Qwen-Image-2512` at their exact reviewed commits; -- Qwen Image Edit 2511 is not an option and remains owned by its separate image-edit Cluster; -- newly inserted Qwen text-to-image Blocks suppress the older cross-family route selector and expose only the ordinary `Model` value; persisted historical cross-family drafts remain readable for recovery; -- reviewed model variants are now admitted by `(pipeline class, workflow ID)`, not by pipeline class alone; repositories for another task cannot appear in or pass the text-to-image selector; -- empty/no-control upstream Modular blocks retain separate execution identities and links, but can be reflowed using `Compact internal layout`; -- model selection propagates to execution, artifact readiness, resource planning, and runtime hints from the instance value. - -Completed automated gates at this checkpoint: - -- exact route/canonical-pin audit; -- 165 backend contract tests, 12 optional hardware/runtime skips; -- 115 focused client compiler, insertion, renderer, persistence, route, resource, runtime-hint, drag/drop, and library tests. - -The post-restart visible-browser lifecycle proves that the selector contains exactly -`Qwen/Qwen-Image` and `Qwen/Qwen-Image-2512`, with no Edit model option. It -selected the base checkpoint from a collapsed Cluster, saved and refreshed the -workflow, verified unchanged graph/prompt/parameter/interface/layout state, and -switched back through the expanded loader. The permanent Playwright lifecycle -test passes in 26.2 seconds. Its current-only screenshots and machine-readable -receipt are in the review directory below. - -The base checkpoint was then installed at its exact pinned revision by the -frontend-started Diffusers/Hugging Face Hub execution path. No `wget` or mutable -branch was used. The real run completed with the unchanged Qwen starter values: -1328 by 1328, 50 steps, guidance 4, seed 42, bfloat16, and model CPU offload. -Its current-only evidence is outside Git at -`/home/sayak/MoDiff/review/qwen-t2i-family-final-2026-09-04`. - -The final production client bundle was also deployed to the backend `web/` -directory and tested directly at `http://127.0.0.1:8088` using only public UI -controls. The integrated smoke inserted the Cluster, observed the exact two -same-task options, chose the base model, edited the prompt, saved, refreshed, -restored both values, expanded all 14 internal nodes, and selected 2512 from the -expanded loader. The receipt and screenshot are in the same clean review -directory. - -Remaining follow-up: forward Hugging Face shard progress and Modular denoising -step progress through the reviewed wrapper. The completed run remained at -coarse fixed queue percentages during both long phases even though heartbeat, -disk, network, and accelerator telemetry confirmed active work. - -## Explicitly deferred - -- Merging upstream Modular Diffusers blocks into fewer executable nodes. That would reduce composition granularity and requires a separate upstream-equivalence review. -- Treating Qwen Image Edit 2511 as a text-to-image model. -- Cross-family Qwen-to-FLUX switching. -- Allowing arbitrary repository text in a registered loader. diff --git a/docs/composite-node-contract.md b/docs/composite-node-contract.md new file mode 100644 index 00000000..2d47b501 --- /dev/null +++ b/docs/composite-node-contract.md @@ -0,0 +1,1168 @@ +# Composite node contract + +This reference preserves the normative Block V2 contract from the retired +implementation plan. It specifies required semantics, not blanket live-model +qualification. Historical milestones, test counts and rollout estimates are in +Git history; current support must be established from exact-route evidence. + +The executable contract lives in `modiff/block_definition_v2.py`, +`modiff/composite_migration.py`, and the client’s `blockSchemaV2.ts`, +`blockRuntimeV2.ts`, `registeredBlockAdapterV2.ts` and persistence modules. +Keep client/backend canonicalization, validators and shared fixtures aligned. +See the [API reference](api-reference.md) for HTTP persistence/migration and +[workbench acceptance](workbench-acceptance.md) for qualification boundaries. + +Normative **MUST**, **MUST NOT**, **SHOULD**, and **MAY** express requirements. + +## Product decision + +MoDiff has one composite-node system. A registered Diffusers Cluster, a +registered Transformers Cluster, an imported Hub block, and a User Node use +the same canvas type, renderer, interface model, expansion mechanics, +persistence format, and connection behavior. + +`Cluster` is a catalog and provenance classification. It is not a second kind +of canvas widget. + +The product differences are limited to: + +- where the reusable definition is registered; +- who owns and may update the reusable definition; +- immutable source and artifact provenance; +- reviewed execution and Auto authority; and +- catalog badges, discovery categories, and permitted save actions. + +Those differences MUST NOT select a different renderer, infer a different +public interface, alter saved parameter values, or replace a canvas node. + +## User-visible invariants + +1. Two insertions of the same definition initially have the same controls, + ports, actions, minimum size, resize behavior, and expansion affordance. +2. Running one instance or viewing its output may update its run status and + preview only. It MUST NOT alter its definition, public interface, or + presentation contract. +3. Expanding a block reveals ordinary connected graph nodes with their normal + controls and sockets. Collapse is presentation-only. +4. Moving an existing internal node changes only that instance's internal + layout. It MUST NOT create a User Node, call `/studio/blocks`, recalculate + controls or ports, or invalidate execution authority. +5. Editing an exposed value changes only that workflow instance. The saved + value wins over definition defaults after Save and browser refresh. +6. Adding, removing, replacing, or reconnecting an internal node is a + copy-on-write structural edit of the same workflow block instance. The + reusable registered source remains immutable. +7. A structural edit may invalidate reviewed execution or Auto authority, but + MUST NOT change the renderer, canvas type, public boundary, name, prompt, + dimensions, or external connections. +8. A reusable User Node is created or updated only after an explicit save + choice. Structural editing alone MUST NOT add a library entry. +9. Inputs and outputs change only through the explicit **Configure interface** + action. Internal movement or topology edits never infer a new boundary. +10. Cluster Nodes and User Nodes cannot be nested. Ordinary nodes can be moved + into or out of an expanded composite with one undoable mutation. + +## Terminology + +| Term | Meaning | +| ------------------- | ------------------------------------------------------------------------------------------------------------------- | +| Block definition | Reusable graph, explicit interface, exposed controls, defaults, preview bindings, and source metadata. | +| Block instance | One canvas insertion with its own values, effective graph snapshot, layout, size, preview, and customization state. | +| Registered Cluster | A block definition owned by the reviewed Diffusers or Transformers catalog. | +| User Node | A reusable block definition owned by the user. | +| Workflow-only block | A customized instance whose reusable definition has not been created or updated. | +| Structural edit | Addition, deletion, replacement, adoption, or reconnection that changes the effective internal graph. | +| Presentation edit | Move, resize, expand, collapse, pan, selection, or viewport change. | +| Public boundary | Ordered, stable input and output ports visible outside the composite. | +| Authority | A separately calculated permission or receipt for reviewed execution, Auto management, or publication. | + +## Canonical persisted contracts + +The following TypeScript-shaped definitions are normative at the semantic +level. Implementation types may split them into files, but field meaning and +invariants MUST remain equivalent. Persisted JSON uses `schemaVersion: 2`. + +```ts +type BlockJsonPrimitive = string | number | boolean | null; +type BlockJsonValue = + BlockJsonPrimitive | BlockJsonValue[] | { [key: string]: BlockJsonValue }; +type BlockJsonObject = { [key: string]: BlockJsonValue }; +``` + +Definition and instance payloads accept JSON values only. Runtime objects, +callbacks, class instances, tensors, and non-finite numbers are rejected. + +### `BlockDefinitionV2` + +```ts +type BlockDefinitionV2 = { + schemaVersion: 2; + definitionId: string; + displayName: string; + description?: string; + contentHash: string; + + source: BlockSourceV2; + graph: BlockGraphV2; + boundary: BlockBoundaryV2; + controls: BlockControlV2[]; + suggestedInputs?: SuggestedInputSetV2[]; + previews: BlockPreviewBindingV2[]; + + // Describes reusable-definition ownership, not canvas rendering. + ownership: { + kind: "registered" | "user"; + definitionMutable: boolean; + }; +}; +``` + +Required invariants: + +- `definitionId` is stable and opaque. Display names are never identity. +- `contentHash` covers exactly the canonical graph (without its derived + `graphHash` field), boundary, ordered controls including declared defaults, + ordered previews, and these execution-relevant source bindings when present: + `provider`, `library`, `libraryRevision`, `pipelineClass`, `blocksClass`, + `workflow`, `manifestDefinitionId`, `manifestContentHash`, `repository`, and + `repositoryRevision`, plus `executionAdmissionId` when declared. +- `contentHash` deliberately excludes `schemaVersion`, `definitionId`, + `displayName`, `description`, `suggestedInputs`, `ownership`, and the source's + `kind`, `catalogCategory`, and `parent` ancestry. Those fields remain strictly + validated and persisted even though they are not execution-content identity. +- A registered definition has `ownership.kind = "registered"` and + `definitionMutable = false`. +- Every `user` or `hub_import` source has `ownership.kind = "user"` and + `definitionMutable = true`. Updating it is still an explicit user operation; + `definitionMutable` does not authorize silent writes. +- The graph contains stable semantic node and edge IDs. IDs do not depend on + canvas position, expansion state, workflow title, or insertion order. +- Defaults exist only in the definition. Current workflow values exist only in + the instance. +- The definition does not contain a volatile resource plan, loaded component, + model object, tensor, task status, or live execution receipt. + +### `BlockSourceV2` + +```ts +type BlockSourceKindV2 = + "diffusers_catalog" | "transformers_catalog" | "hub_import" | "user"; + +type BlockSourceV2 = { + kind: BlockSourceKindV2; + catalogCategory?: "diffusers" | "transformers"; + provider?: string; + library?: "diffusers" | "transformers"; + libraryRevision?: string; + pipelineClass?: string; + blocksClass?: string; + workflow?: string; + manifestDefinitionId?: string; + manifestContentHash?: string; + // Exact reviewed execution admission. Required for registered definitions + // that may receive reviewed-execution or Auto authority. + executionAdmissionId?: string; + repository?: string; + repositoryRevision?: string; + parent?: { + definitionId: string; + contentHash: string; + sourceKind: BlockSourceKindV2; + }; +}; +``` + +Source invariants: + +- Repository-backed execution references use immutable revisions. A moving + branch is not a reusable execution identity. +- `repository` and `repositoryRevision` are an all-or-nothing pair; + `hub_import` requires the pair, and `repositoryRevision` is an exact + lowercase 40-hex commit. +- Registered sources require matching catalog/library kinds plus + `libraryRevision`, `manifestDefinitionId`, `manifestContentHash`, + `pipelineClass`, and `workflow`. Hub and User sources cannot claim a + registered `catalogCategory`. +- `executionAdmissionId`, when present, is an exact immutable catalog identity, + is covered by `contentHash`, and must match the admission used to compile the + registered graph. It is not inferred from `definitionId`, display text, model + class, or workflow mode. Reviewed-execution and Auto receipts for a registered + definition fail closed when it is absent or stale. +- `parent` records ancestry when a reusable User Node is saved from a + registered or imported definition. It does not make the child definition + registered or reviewed. +- Source metadata is never removed merely because an instance is customized. +- Source kind controls catalog placement and provenance display only. It MUST + NOT select a canvas renderer or port-inference algorithm. + +### `BlockGraphV2` + +```ts +type BlockGraphV2 = { + nodes: BlockGraphNodeV2[]; + edges: BlockGraphEdgeV2[]; + executionOrder?: string[]; + graphHash: string; +}; + +type BlockGraphNodeV2 = { + nodeId: string; + nodeType: string; + data: BlockJsonObject; + semanticRole?: string; + upstreamBlockPath?: string; + modularDiffusers?: BlockGraphNodeModularDiffusersV2; + containerInterface?: BlockContainerInterfaceV1; + parentNodeId?: string; +}; + +type BlockGraphEdgeV2 = { + edgeId: string; + sourceNodeId: string; + sourcePortId: string; + targetNodeId: string; + targetPortId: string; +}; + +type BlockContainerInterfaceV1 = { + schemaVersion: 1; + boundary: BlockBoundaryV2; // mode MUST be explicit + controls: Omit[]; + previews?: BlockPreviewBindingV2[]; +}; +``` + +`containerInterface` is an optional, canonical/hash-covered local public +surface on an existing semantic node, not a nested composite instance. Its +ports and controls bind only to that node and its semantic descendants. +Primary/mirror bindings must resolve to existing direction- and +type-compatible fields; duplicate input targets, duplicate control targets, +and a shared input/control ID with different complete target sets are rejected. +Every current edge crossing a declared container boundary must have a matching +local port. Declaration edits that remove or rebind a connected port fail +before any graph mutation. Sealed controls cannot be weakened by this editor. + +Root/local type compatibility has the same narrow file-transport exception: +an explicitly matching media file browser can bind a media port and a string +path control with the same full target set. Video file browsers also admit +declared sequences of PIL/image frames. Preview widgets qualify URL/base64 +transport by their exact media display, rather than pretending URLs are image +tensors. Other string fields and mismatched modalities remain rejected. + +There is still one flat effective execution graph and one owning instance. +Local controls MUST NOT store defaults or values independently. They write +existing effective fields or the root logical override when that field is +already exposed; shared root mirror consumers retain one value identity. +Renaming/reordering a local interface does not rewrite the root interface or +any field values. Newly wired non-baseline crossings retain a durable socket +after disconnection; neither projection nor persistence recreates a wire. +Collapsed mirrored sockets group only identical visible wires and carry exact +semantic-edge receipts for atomic connect/reconnect/delete. Execution always +uses the original flat graph, not those grouped canvas edges. +An undeclared deeper view inherits the nearest ancestor-local controls before +root controls, without adding a declaration during a value edit. A mirrored +public input remains one socket with its complete subtree-local target set. + +Subtree adoption validates the complete incoming graph atomically and remaps +local bindings with semantic IDs. Replacement preserves compatible local IDs +and rebinds their targets; deletion reports declarations held by surviving +containers rather than silently dropping them. Saving a configured subtree as +a User Node promotes that exact local surface to the new root boundary and +controls, baking current field values/defaults into the copy. It does not +modify the source workflow or register a new catalog Cluster. + +Local `previews`, when declared, bind existing compatible preview/output fields +within that same subtree. They share one owner `previewStates` inventory rather +than copying run state per view. Inventory order is root-definition bindings, +then first-seen local bindings in semantic graph-node order, deduplicated by +node/output port. Shared sources MUST agree on media type; a local `primary` +belongs only to that view, not to a newly introduced inventory entry. At most +one primary is allowed per surface. Root and local views filter this inventory +using their own declarations. The paired fixture is +`tests/fixtures/block_container_previews_v1.json`. + +Nesting a whole multi-root Block introduces one generic non-executing group in +the target's flat graph. Its local interface is the source's public boundary, +controls and previews. All semantic IDs, edges, parent relations, exact upstream +placements and declarations are rebased together; effective values are baked +into copied fields. A single container with a different independently declared +surface also receives a wrapper, preserving both interfaces. The inserted outer +container begins collapsed. Moving within a workflow preserves completed media +references but never transfers in-flight execution authority. Saving it as a +new reusable definition preserves current prompts/settings and preview bindings, +not transient output media or publication approval. Generic grouping imposes no +pipeline family; actual upstream control-owner adoption remains pinned-family +checked. Typed execution connections remain authoritative in either case. + +Compatibility: omitted declarations preserve old canonical bytes and hashes. +Legacy interfaces are derived until an explicit interface or non-baseline +wiring edit requires persistence. Readers without this optional-field contract +reject it under strict validation; they must not discard it. This requires +coordinated frontend/backend deployment, not an automatic historical-definition +rewrite. The cross-runtime fixture is +`tests/fixtures/block_container_interface_v1.json`; both strict validators, +hash implementations and API/workflow round trips are release gates. + +`parentNodeId` is optional, canonical/hash-covered semantic ownership for an +ordinary node or a saved User subtree placed inside another semantic container. +It references a group or an exact non-leaf upstream block in the same graph. +Missing parents, self-parenting, cycles, leaf parents, and conflicts with an +already resolved upstream placement parent are rejected. Existing upstream +placements keep their exact provenance; ordinary utility nodes MUST NOT acquire +fabricated `modularDiffusers` identity merely because they are nested. Both +explicit ownership and upstream placement resolve through one parent relation +for projection, descendant scopes, validation and subtree persistence. + +Moving/copying a subtree remaps internal parent IDs and local interface bindings; +saving it independently removes only its external parent reference. Current +field values and root overrides are baked into the reusable copy. Ownership +does not add an executor, second instance, new parameter value authority or +implicit wire. Palette insertion/adoption is atomic: failure removes the draft +insertion, and one Undo restores the prior graph. A whole Block with multiple +independent roots uses a generic wrapper carrying its already explicit public +surface. It never guesses a boundary or silently discards crossing wires. +Ordinary nodes and User containers can change parent within an instance while +preserving IDs, fields, wires and execution order. A retained local declaration +that would cross outside its owner must be rebound explicitly before the move. +Empty generic groups remain expandable insertion targets; inactive empty +upstream branch annotations do not become executable containers. +Whole-subtree move-out creates a workflow-owned User Block, not a library entry. +It preserves local controls and completed preview references; crossing wires +require exact public boundaries and complete fan-out. Existing root-owned +controls and previews are protected until explicitly rebound. The shared fixture is +`tests/fixtures/block_parent_node_v2.json`. Omission preserves pre-extension +hashes; older strict readers reject the new field, requiring paired deployment. + +`modularDiffusers`, when present, is canonical, hash-covered semantic identity +for one expanded registered/imported Modular Diffusers node. It distinguishes +MoDiff infrastructure such as component loading and preview from an exact +upstream block. Every entry pins the pipeline class, blocks class, workflow, +Diffusers revision, and runtime role. An upstream entry additionally pins the +block definition ID, concrete class, kind, contract hash, exact placement path, +parent placement path, and component names. It is provenance and lowering +metadata, never a parallel value or layout store. Registered semantic graphs +must not replace exact upstream block placements with broader convenience +stages; any optimized runtime lowering is explicit, versioned, hash-bound, and +must preserve the meaning of every supported edit. + +Graph positions and reusable layout hints do not belong in +`BlockDefinitionV2`. Initial layout is supplied while constructing an instance +and is persisted only in `BlockInstanceV2.presentation`; instance layout always +wins. + +Recursive Modular UI state is likewise instance presentation, not definition +identity. `collapsedContainerNodeIds` contains semantic graph-node IDs whose +exact `placementPath` is referenced by at least one descendant's +`parentPlacementPath`. The owning node may be a structural `group` or an +executable `custom` block; canvas renderer type is not container authority. +New hierarchical instances seed all such owners as collapsed for progressive +one-level expansion. Client and backend validators must derive and validate +that same set from the hash-covered graph metadata. + +`graphHash` covers node and edge content plus the optional ordered +`executionOrder`, but not the `graphHash` field itself. Canonicalization sorts +the node array by `nodeId` and the edge array by `edgeId`, so their storage order +is not identity; explicit `executionOrder` order remains semantic. Node IDs and +edge IDs are independently unique, edges reference declared nodes, and an +explicit execution order contains unique declared node IDs. Nested composite +nodes are rejected. + +### `BlockBoundaryV2` + +```ts +type BlockBoundaryV2 = { + mode: "explicit" | "derived"; + inputs: BlockPortV2[]; + outputs: BlockPortV2[]; + derivation?: { + algorithmVersion: string; + derivedAtDefinitionHash: string; + }; +}; + +type BlockPortV2 = { + portId: string; + label: string; + valueType: string; + required: boolean; + multiple?: boolean; + binding: { + nodeId: string; + fieldOrPortId: string; + }; + // Inputs only. The one public logical value is projected atomically to the + // primary binding above and every canonically ordered mirror below. + mirrorBindings?: { + nodeId: string; + fieldOrPortId: string; + }[]; +}; +``` + +Boundary rules: + +- Registered Diffusers and Transformers definitions MUST use `explicit`. +- Hub imports that declare a validated interface SHOULD use `explicit`. +- A User Node created from an arbitrary graph selection MAY begin as + `derived`. Derivation runs once while creating the reusable definition and + persists the resulting ordered ports. +- `derived` does not mean continuously recomputed. Graph edits never + automatically add, remove, or reorder ports. +- Re-derivation is an explicit **Configure interface** operation, produces a + preview of affected external edges, and is one undoable mutation. +- `portId` remains stable when its label changes. Deleting a connected port + requires explicit confirmation and a useful connection-loss report. +- Every public `portId` is unique across both `inputs` and `outputs`, not only + within one direction. The shared root connector map and React Flow handle + namespace cannot represent an input and output with the same handle ID + without ambiguity, so both client and backend validators reject that + definition. +- `mirrorBindings` is permitted on public inputs only. It represents genuine + semantic fan-out of one logical input to multiple internal consumers; it is + not a fallback list and the primary binding remains required. Mirrors are + non-empty when present, sorted lexically by `nodeId` then `fieldOrPortId`, + unique, distinct from the primary, bound to declared non-output fields, and + type-compatible with the public port. Public outputs remain single-binding + until an explicit aggregation contract is designed and reviewed. +- The collapsed socket still has exactly one `portId`. An external edge into + that socket, a stored instance value, or an internal adoption gesture fans + out atomically to the primary and every mirror. Expanded root bridge edges + show all of those targets. Execution export expands the one external input + into deterministic internal edges; move-out and collapse coalesce the exact + fan-out back to the one public connection. Partial or divergent fan-out + fails closed instead of silently selecting one consumer. +- Copy-on-write customization of a registered instance starts with the same + exact boundary. It MUST NOT expose every unconsumed internal input or every + internal output. + +### `BlockControlV2` + +```ts +type BlockControlV2 = { + controlId: string; + label: string; + binding: { + nodeId: string; + fieldId: string; + }; + // Additional internal fields that consume the same logical control value. + mirrorBindings?: { + nodeId: string; + fieldId: string; + }[]; + valueType: string; + defaultValue?: BlockJsonValue; + required?: boolean; + sealed?: boolean; + order: number; + group?: string; + help?: string; +}; + +type SuggestedInputSetV2 = { + suggestionId: string; + label: string; + source?: string; + values: Record; // keyed by controlId +}; + +type BlockPreviewBindingV2 = { + nodeId: string; + outputPortId: string; + mediaType: "image" | "video" | "audio" | "text" | "file"; + primary?: boolean; +}; +``` + +Control rules: + +- The collapsed surface and Studio inspector consume the same ordered control + descriptors and instance values. +- A control with `mirrorBindings` is still one logical control and has one + `controlId`, one displayed value, and one entry in `BlockInstanceV2.values`. + Runtime projection writes that value atomically to its primary field and all + mirrors. At compile time, actual/default values across the declared targets + must agree; disagreement is an invalid registered route rather than a reason + to pick one field. +- Control mirrors are non-empty when present, canonically sorted by `nodeId` + then `fieldId`, unique, distinct from the primary, bound to declared + non-output fields, and type-compatible with the control. Registered compiler + routes declare the complete target set explicitly; the adapter never infers + fan-out merely because labels or current values happen to match. +- `defaultValue` is a starter value, not a load-time reset policy. +- A missing instance value may resolve to the definition default only when the + field was never set. A saved explicit empty string, `false`, `0`, or `null` + MUST NOT be treated as missing. +- Suggested creator prompts belong in `suggestedInputs`; applying one is an + explicit user action that writes instance values. `source` records where the + suggestion came from; it does not grant execution authority. +- Control IDs are unique and `order` is contiguous from zero. Suggested values + may reference declared control IDs only, not boundary-only input IDs. +- A control may share an ID with a public input only when both bind the same + internal node and field; validators reject a shared ID with divergent + bindings. +- Sealed controls may be displayed for provenance or runtime setup but cannot + be silently unlocked by customization. Changing a sealed artifact or class + requires an explicit supported workflow/model replacement path. + +### `BlockInstanceV2` + +```ts +type BlockInstanceV2 = { + schemaVersion: 2; + instanceId: string; + definitionRef: { + definitionId: string; + contentHash: string; + }; + + // A self-contained insertion; prevents library drift or deletion from + // resetting an existing workflow. + definitionSnapshot: BlockDefinitionV2; + effectiveGraph: BlockGraphV2; + effectiveInterface: { + boundary: BlockBoundaryV2; + controls: BlockControlV2[]; + baseInterfaceHash: string; + effectiveInterfaceHash: string; + }; + values: Record; + + customization: { + state: "unchanged" | "parameters_changed" | "structure_changed"; + baseGraphHash: string; + effectiveGraphHash: string; + }; + + presentation: { + expanded: boolean; + position: { x: number; y: number }; + // Durable collapsed/user-resized size. This is not the expanded wrapper + // size and MUST NOT be overwritten by expansion or child measurement. + size: { width: number; height: number }; + internalLayout: Record< + string, + { x: number; y: number; width?: number; height?: number } + >; + }; + + previewStates: BlockPreviewStateV2[]; + authorities: BlockAuthorityReceiptV2[]; + routeSelection?: { + schemaVersion: 1; + routeSetId: string; + selectedRouteKey: string; + inactiveDrafts: Record; + }; +}; + +type BlockRouteDraftV1 = { + schemaVersion: 1; + routeKey: string; + definitionRef: BlockInstanceV2["definitionRef"]; + definitionSnapshot: BlockDefinitionV2; + effectiveGraph: BlockGraphV2; + effectiveInterface: BlockInstanceV2["effectiveInterface"]; + values: Record; + customization: BlockInstanceV2["customization"]; + internalLayout: BlockInstanceV2["presentation"]["internalLayout"]; +}; + +type BlockPreviewStateV2 = { + binding: BlockPreviewBindingV2; + mediaReference?: string; + taskId?: string; + status?: "idle" | "queued" | "running" | "complete" | "failed"; +}; +``` + +Instance invariants: + +- Every insertion receives a distinct `instanceId` and independent `values`, + `effectiveGraph`, `presentation`, preview, and authority state. +- `definitionSnapshot` makes workflow reload deterministic. Refresh never + fetches new defaults over saved instance values. +- `effectiveGraph` initially equals the definition graph. A structural edit + replaces it copy-on-write inside that instance; it does not change the + reusable definition or canvas node type. +- `effectiveInterface` initially equals the definition boundary and controls. + Configure Interface replaces it copy-on-write, preserves stable port/control + IDs unless the user explicitly adds or removes an entry, and never mutates + `definitionSnapshot`. Its base hash is always the definition interface hash; + its effective hash covers the ordered boundary and ordered controls. +- Presentation edits update `internalLayout` only and do not change + `effectiveGraphHash`, `contentHash`, or authority. +- Parameter edits update `values`. Only execution-relevant values affect an + execution fingerprint. That fingerprint covers the effective-interface hash + as well as current values, so a boundary/control customization cannot reuse a + receipt issued for a different public contract; neither controls nor the + public boundary are re-derived. +- `values` is keyed by a stable declared logical ID: a `controlId`, a public + input `portId`, or one ID intentionally shared by both projections. Labels + and array positions are never value identity. If two logical IDs bind the + same internal field, their values must agree or execution fails closed. +- Primary and mirror bindings do not add value IDs. Reading, editing, saving, + loading, and execution use the one public/control logical ID and project it + to the complete declared target set. A graph that contains only part of a + declared fan-out, or whose mirrored internal values disagree, is invalid. +- Preview state is instance-local and non-authoritative. Viewing or replacing + it cannot modify the graph or interface. Multiple preview bindings remain + ordered by the definition; at most one is the primary collapsed preview. +- Replacing an internal node that owns a preview or sealed control preserves + that node's stable semantic `nodeId`. The replacement must provide every + referenced field with compatible type and direction; a preview replacement + must additionally preserve its declared media and display role. This swaps + the implementation behind the existing immutable binding instead of + rebinding the preview or sealed control. A replaced preview's stale media, + task, and status are cleared to an idle state. +- Runtime output is accepted into preview state only when its deterministic + projected node ID and output port match one declared preview binding. The + matching artifact URL is preferred over a bounded value fallback. A + successful update replaces stale media/task state for that instance only and + is not an undoable graph edit. +- Authority receipts are keyed by `kind`; an instance may independently carry + reviewed-execution, Auto, and publication receipts. A receipt never grants a + different renderer or public interface. +- External workflow edges target `instanceId + portId`; they do not target a + renderer-specific node identity. +- `routeSelection` is an optional instance-only generic shell. Its selected + route is always the instance's active exact definition; an inactive draft is + a bounded, non-recursive registered route snapshot and never a nested Block. + Drafts exclude instance IDs, canvas position/size, previews, task IDs, and + authority receipts. Switching preserves the one root ID and compatible + external edges, clears volatile output/authority state, and restores each + route's values, effective graph/interface, and internal layout independently. +- Same-pipeline/same-workflow checkpoint selection does not use + `routeSelection`. It is one ordinary bound `modelVariant` control whose exact + repository options are part of the canonical `BlockDefinitionV2` graph and + whose chosen value lives in `BlockInstanceV2.values`. Changing it must leave + the definition reference, effective graph/interface, all other values, and + presentation byte-for-byte unchanged. Execution independently resolves the + selected repository to an immutable reviewed revision. + +### Configure-interface instance contract + +`BlockInstanceV2.effectiveInterface` is the explicit, strictly validated +instance-only interface snapshot. The value namespace, renderer controls, +public connectors, external-edge validation, persistence, and execution +translation all consume this one snapshot. Older V2 workflow instances that +do not contain the field migrate deterministically to the embedded definition +boundary and controls; an unknown field, stale base/effective hash, missing +binding, incompatible field type, duplicate ID/order, or divergent shared +input/control binding fails closed. + +Configure Interface edits the ordered boundary and controls copy-on-write. +Removing a currently connected port is blocked with an edge-impact count until +the user disconnects it. Sealed controls cannot be removed or rebound. The +reusable `BlockDefinitionV2` remains unchanged until the user explicitly +chooses **Save as new User Node** or, for an already user-owned definition, +**Update existing User Node**. + +The editor exposes entry reordering and the complete multi-target contract. +For a public input or editable control, the user can add or remove additional +type-compatible internal consumers while retaining one logical socket/control +and one instance value. The editor excludes the primary and existing mirrors, +stores additions in canonical binding order, never offers mirrors for public +outputs, and disables mirror mutation for sealed controls. + +Re-labeling or reordering an interface entry preserves its declared mirrors. +Rebinding the primary explicitly clears its mirrors until the user reviews and +declares the new complete fan-out. Removing/rebinding an entry, replacing or +deleting an internal node, and sealed-control checks examine the primary and +every mirror together. Interface hashes cover all mirror bindings, so a fan-out +change invalidates stale execution authority even when the public ID and label +do not change. + +This is an instance-only contract change. It follows the synchronized +instance-change rules below and requires workflow migration/lifecycle tests, +but it MUST NOT add a mutable interface field to the registered definition or +silently reuse `NodeData.params` as interface authority. + +### `BlockAuthorityReceiptV2` + +```ts +type BlockAuthorityReceiptV2 = { + kind: "reviewed_execution" | "auto" | "publication"; + definitionId: string; + definitionContentHash: string; + effectiveGraphHash: string; + executionParameterHash: string; + artifactRevisions: Record; + admissionId: string; + issuedAt: string; + expiresAt?: string; +}; +``` + +Authority rules: + +- Provenance says where a block came from. Authority says what its exact + current graph and parameters are permitted to do. They are not equivalent. +- A parameter change invalidates only receipts whose parameter hash no longer + matches. +- A structural edit invalidates reviewed execution, Auto, and publication + receipts unless the effective graph independently matches another reviewed + admission. +- Invalid authority may block Auto or a reviewed route. In non-Auto/Expert + mode, the ordinary graph remains runnable when its concrete dependencies are + available, and real preparation/runtime errors must be shown to the user. +- A finding deliberately demoted to a non-blocking Expert warning remains + visible and actionable in Graph Fix. Model, environment, and media warnings + retain their explicit **Models**, **Setup**, or **Gallery** action, but do not + disable **Run** and are never applied silently. A malformed concrete V2 graph + remains a blocking structural error; warning presentation does not weaken + strict graph validation. +- The issuing backend MUST strictly validate the submitted V2 execution + material and recompute `definitionContentHash`, `effectiveGraphHash`, and + `executionParameterHash`; it cannot treat client-provided hash strings as + evidence and echo them into a receipt. It must also resolve the exact + catalog admission, immutable artifacts, installed revisions, execution + profile, optional runtime, and selected resource candidate. Hash or graph, + interface, value, artifact, recipe, or admission substitution fails closed. +- Registered Auto admission pins both the public V2 definition content hash + and a SHA-256 of the complete canonical definition material. The submitted + planner form includes every resource-impacting field explicitly; a missing + dimension, step/frame count, dtype, device, quantization, or offload field is + an invalid authority request rather than permission to assume a default. +- Multiple independently scoped receipts may coexist in `authorities`. Losing + or replacing one receipt does not silently remove another kind. +- Losing authority never changes the block renderer, boundary, controls, + values, source provenance, or external connections. + +## One renderer and one canvas type + +All composite definitions MUST insert as the existing canonical `block` canvas +type and render through one shared block component. Catalog badges, immutable +source details, qualification status, and save permissions are injected as +capabilities. + +The shared surface includes: + +- header, source badge, name, status, and action placement; +- selection toolbar and keyboard actions; +- width and height resize handles; +- collapsed controls and suggested inputs; +- input and output connector trays; +- generated preview/output presentation; +- expansion into ordinary connected internal nodes; +- internal node movement and resizing; +- error, progress, and retry presentation; and +- accessible labels and focus order. + +The durable root connector trays remain mounted in both collapsed and expanded +states. External workflow edges always terminate on the root's stable public +port IDs; expansion only adds ordinary internal projections and bridge links. +Hiding the root handles while expanded is invalid because it makes existing +external edges point at missing handles and prevents composing the expanded +Block with the surrounding workflow. + +The same connector contract applies recursively. A collapsed internal Block +projects only connections that cross its subtree boundary, using typed visible +handles with runtime-only bindings to the exact hidden leaf sockets. It must +not discard those links, expose links wholly internal to the hidden subtree, +or rewrite semantic endpoints to the container. Resize minimums include the +header and connector tray. Presentation collapse and sizing never filter the +flat graph used by execution. + +The expanded root frame MUST contain the rendered bounding box of every owned +ordinary child. Its canvas width and height are a derived projection of the +durable `internalLayout` plus measured child dimensions and safe header, +connector, and edge padding. `presentation.size` remains the collapsed, +user-resized size; expanding, measuring, or moving one child MUST NOT rewrite +that size, values, effective interface, definition identity, preview state, or +any sibling instance. Collapse MUST restore the exact prior collapsed size. +Any browser lifecycle that claims expansion support MUST assert containment +before and after a child move and again after workflow Save plus browser +refresh; execution/export parity alone is insufficient. + +There MUST NOT be a catalog-specific renderer whose private schema produces a +different visual or graph interface. + +A control and a public input may intentionally share the same logical ID and +internal binding (for example, Qwen `prompt`). The shared renderer MUST derive +two views from the one V2 contract: editable body controls and a connector +tray. It MUST NOT force both through one `NodeData.params` entry, because a +single entry cannot simultaneously behave as a textarea and an input socket. + +The renderer receives capabilities as a computed view, not as another node +schema: + +```ts +type CompositeNodeCapabilitiesV2 = { + editInstanceValues: boolean; + editInstanceStructure: boolean; + configureInterface: boolean; + keepWorkflowOnly: boolean; + saveAsNewUserNode: boolean; + updateReusableDefinition: boolean; +}; +``` + +Capabilities are calculated from definition ownership, instance state, and the +active workspace/user permission boundary. They are not persisted as execution +authority and do not affect canonical graph hashes. A registered definition +normally permits instance edits, workflow-only changes, and **Save as new User +Node**, but never **Update reusable definition**. Registered and user-owned +instances now permit structural editing and Configure Interface when the +workflow/workspace permission boundary allows them; ownership changes only +the reusable-definition save choices. The common connection path derives +public sockets from V2 and commits admitted same-owner internal edge +additions/removals/reconnections copy-on-write. The +safe internal-node deletion path also updates `effectiveGraph` copy-on-write, +clears affected authority, preserves external edges, participates in undo, and +atomically rejects deletion of nodes still referenced by the explicit public +interface, exposed controls, or previews. The pure source-neutral +`addBlockEffectiveGraphNodeV2` reducer can also adopt one persistence-filtered +ordinary semantic node copy-on-write: it validates stable and projection-safe +identity, rejects nested composites, writes explicit execution order and +layout, recomputes graph/customization hashes, and clears authority atomically. +Production gestures route through those reducers: dropping a disconnected +ordinary node adopts it; an edge already connected directly to a declared +root port is translated into an internal edge without inferring a new port; +dropping a compatible ordinary node on an internal projection replaces that +semantic node while preserving compatible edge/public IDs; dragging an +eligible internal node out translates incident links through already-declared +public ports; and React Flow edge reconnection updates either the instance +effective graph or an external workflow edge atomically. Arbitrary external +crossings do not invent an interface: the user disconnects, moves, configures +the explicit interface, and reconnects through the root. Preview-owning and +sealed-control-owning replacements use the stricter identity-preserving path: +the protected semantic ID and binding stay unchanged, all edge/public/control +fields must remain compatible, preview media/display compatibility is checked, +and stale preview run state is cleared. All gestures preserve the wrapper, +participate in undo/redo, invalidate authority on semantic changes, and reject +invalid/nested/incompatible states without a partial mutation. + +### Atomic registered-definition compilation gate + +A registered block may be inserted through V2 only after its exact execution +node schemas and bindings are complete. For example, the real Qwen text-to- +image admission requires dynamic actions for model type and component signals; +an unfinalized static skeleton is not a valid `BlockDefinitionV2` graph. + +The insertion factory MUST resolve the immutable admission/spec/registry, +materialize and finalize an ephemeral persistence-filtered draft, reconcile all +fields and bindings, compile the registered V2 definition/instance, discard the +draft, and then atomically insert one `type: "block"` root. On failure it removes +the draft and shows the exact error. It MUST NOT insert a visible legacy +Cluster first, replace it later, or silently fall back to the legacy renderer. +Legacy `huggingFaceCluster*` field options and projection/receipt markers are +compiler inputs only. The compiler MUST translate their meaning into explicit +V2 controls, boundary, values, provenance, and authority inputs, then omit all +of those legacy-prefixed fields from the source-neutral definition graph. + +Initial routing is gated per exact admission (Qwen text-to-image first). A +pipeline-class-wide switch is not permitted until every route with that class +has completed the same dynamic-finalization proof. + +## Copy-on-write customization flow + +### Parameter or presentation edit + +1. Update the instance value or layout. +2. Preserve the registered definition reference and snapshot. +3. Recalculate only affected execution authority. +4. Save the workflow instance normally. +5. Do not create or update a reusable User Node. + +### Structural edit + +1. Detect an actual graph mutation, not a child drag within the same owner. +2. Clone `effectiveGraph` inside the same instance/history transaction. +3. Apply the mutation and validate typed ports, component/state flow, and + container semantics. +4. Mark `customization.state = "structure_changed"`. +5. Preserve the explicit boundary, controls, values, provenance, layout, + preview, external edges, name, and `instanceId`. +6. Clear incompatible authority receipts and show the resulting Expert/Auto + status without changing presentation. +7. Persist the workflow-only customized instance. + +No destructive Cluster-to-User replacement occurs. + +### Reusable save choices + +After customization the user may choose: + +1. **Keep changes only in this workflow** — persist only the embedded + `BlockInstanceV2`; perform no reusable-definition write. +2. **Save as new User Node** — create a new `BlockDefinitionV2` from the exact + effective graph and existing explicit interface, retain preview bindings + and direct source ancestry, apply current instance values as reusable + defaults for declared controls, assign a new opaque definition ID, and + point only this instance at an embedded snapshot of that definition. +3. **Update existing User Node** — available only when the current reusable + definition has a user-owned source and mutable user ownership; update that + definition explicitly. Other open or saved instances keep their embedded + snapshots until the user explicitly refreshes them. + +A registered definition never offers **Update existing**. + +`BlockPortV2` has no reusable `defaultValue`. Therefore a save-as-new or +update-existing operation MUST fail closed when `instance.values` contains a +public boundary input that is not also a declared control. The UI may instead +keep that value workflow-only, or a later explicit Configure-interface flow +may expose it as a control; the persistence adapter MUST NOT silently drop it. + +The client writes a reusable definition optimistically but rolls the library +state back when the API fails. After a successful write, it verifies the exact +normalized definition response and a semantic signature of the initiating +instance before rebasing. A concurrent semantic edit aborts the rebase. The +target instance retains its ID, values, presentation, and matching preview +state; sibling insertions and snapshots in other open workflows are not +refreshed. A saved V2 User Node can then be clicked or dragged from the Node +Library to create a new independent top-level instance. Focused store/runtime +tests cover rollback, races, sibling/open-workflow isolation, and independent +reinsertion. A mocked browser lifecycle also exercises all three visible save +choices, adoption into the edited User Node, workflow Save/refresh, and the +rebased instance after **Update existing**. The complete release browser suite +remains a separate gate; that does not make this persistence lifecycle pending. + +## Migration and recovery + +Migration is additive, deterministic, idempotent, and non-destructive. + +### Inventory and backup + +The first non-mutating inventory slice is implemented. Run it from the backend +repository with: + +```bash +./scripts/with-runtime-env.sh ./.venv/bin/python \ + scripts/inventory_legacy_composites.py --data-dir /path/to/data +``` + +The CLI reads only `user-workflows/*.json` and `studio/blocks/*.json` below the +selected data directory and writes the report to stdout. `--compact` selects +canonical one-line JSON. `--fail-on-errors` still prints the complete report +and then exits with status 2 when it contains an error-level issue. The scanner +does not follow symlink files or source directories, enforces bounded file and +aggregate input sizes, and reports unreadable, malformed UTF-8/JSON, unsafe, +or oversized sources instead of modifying or skipping them silently. + +The schema-v1 report has this stable top-level shape: + +```json +{ + "schemaVersion": 1, + "kind": "legacy_composite_migration_inventory", + "mode": "read_only_dry_run", + "boundary": { + "readsSavedWorkflowJson": true, + "readsReusableBlockJson": true, + "writesFiles": false, + "convertsRecords": false, + "deletesRecords": false, + "mergesRecords": false, + "authorizesRecovery": false, + "containsPromptAndParameterValues": true + }, + "sourceLayout": { + "workflows": "user-workflows/*.json", + "reusableBlocks": "studio/blocks/*.json" + }, + "summary": {}, + "workflows": [], + "reusableBlocks": [], + "issues": [], + "reportHash": "sha256:..." +} +``` + +Workflow entries classify legacy Cluster roots and their derived children, V1 +`userBlockSnapshot` roots and children, already-V2 roots and projections, and +ambiguous mixed-authority nodes. Each composite inventory includes its root +and source IDs, canvas position/size, persisted presentation, prompts and +parameters, instance overrides/values, public ports and bindings, derived +child IDs, and external edges. Reusable records classify V1 and V2 definitions +and include their source ancestry, graph IDs/counts, declared ports, prompts, +parameters, and strict V2 validation result. Source SHA-256 values and a +deterministic report hash make two scans comparable without adding a mutable +timestamp. + +Definition resolution against the reusable User Node store is informational. +Embedded workflow snapshots remain authoritative for later migration, and +registered catalog definitions are deliberately reported as external because +this scanner does not read the live catalog. Duplicate IDs, missing or +mismatched owners/endpoints/definitions, invalid V2 records, nested composites, +and mixed authorities produce explicit issues for human review. + +The report contains exact persisted prompts and parameter values and therefore +may be sensitive. Redirecting stdout creates an operator-managed plaintext +copy; the tool itself accepts no report destination and writes no application +file. A clean inventory is not a backup, a conversion plan, execution evidence, +or authorization to recover/delete/merge anything. + +The separately implemented backend preview defaults to +`GET /studio/composite-migrations/preview`. It computes a deterministic +`migrationId` and `planHash` over exact source and proposed-target hashes. +Preview remains read-only. `POST /studio/composite-migrations/apply` requires +that exact identity, literal `APPLY_BLOCK_V2_MIGRATION` confirmation, and an +explicit `allowBlockedCandidates` choice when the plan includes records that +cannot be converted safely. It converts V1 reusable definitions and embedded +V1 workflow instances only. Exact original bytes are stored below +`studio/composite-migrations/{migration_id}/backups` before replacement. + +Registered Clusters remain blocked in the default GET. The additive +`POST /studio/composite-migrations/preview` accepts one strict +`registered_cluster_v2_compiler_supplement` and is still read-only. Each +compiler output is tied to an exact workflow source SHA-256 and legacy +composite hash; embeds a backend-validated `BlockInstanceV2`; matches the +backend-pinned admission's public content hash and canonical SHA-256; and +contains exhaustive one-to-one projection-node, public-port, persisted-value, +preview-output, and absorbed-internal-edge receipts. Durable input/control +values preserve the root and child prompts/parameters exactly. Volatile +`display: "output"` parameters are never treated as durable values; a non-null +media reference must instead map exactly to a matching V2 `previewState`. +The conversion also preserves the root ID, position, size, expanded state, +internal layout, external edge IDs, and outside endpoints. Missing, stale, +duplicate, structurally forked, unpinned, ambiguous, or value-changing output +remains blocked without affecting V1 or other exact conversions. Apply must +resend the same supplement so recomputing the plan fails closed before writes +if the compiler output changed. Multiple exact Cluster conversions in one +workflow are accumulated into one proposed target and therefore one atomic +workflow replacement and exact-byte backup. An invalid sibling remains visible +and byte-for-byte unchanged; an exact sibling may be included in that same +atomic target only after the operator explicitly allows blocked candidates to +remain. + +Status/list endpoints report applied, rolled-back, interrupted, and conflicting +targets. `POST /studio/composite-migrations/{migration_id}/rollback` requires +literal `ROLLBACK_BLOCK_V2_MIGRATION`, verifies every backup/current hash, and +refuses independently changed or missing files. Apply and rollback are +idempotent; a rolled-back plan can be explicitly reapplied. There is no force, +delete, rename, merge, or implicit resume operation. + +Setup → Advanced diagnostics now provides the default client preview and +recovery surface. Its strict parser rejects unknown/missing fields, +inconsistent counters, unsafe target paths, stale identities, invalid hashes, +and conflicting journal actions before rendering them. The review dialog lists +every exact target and blocker path. Apply requires the full literal plus a +separate `allowBlockedCandidates` opt-in when needed; rollback refreshes the +exact journal and requires its own full literal. `403`, `409`, and backend +errors stay visible, and both preview and journals refresh after success. +Nothing auto-applies. Mocked component/API tests exercise these controls +without applying against real user data. Backend compiler-supplement preview, +apply, byte-backup, rollback, idempotency, and HTTP tests are complete and use +only temporary fixtures. The client now offers a visible **Generate exact +supplement** action for exact-current registered receipts. It refreshes the +backend-owned source/composite hashes, reads the raw saved workflow, invokes +the same hidden registered V2 compiler/finalizer as insertion, removes all +transients, populates inspectable JSON, and POSTs the exact parsed object for a +final read-only preview. Apply reuses that identical object. Generation exposes +progress/cancel state, aborts on workflow replacement, and warns that dirty +tabs are not part of the saved backend bytes. The paste path remains available +for externally produced exact evidence. A disposable mocked browser lifecycle +proves Generate → Preview → Apply → List/Status → Rollback without writing a +real store. + +The supported generator remains fail-closed for missing execution receipts and +historical manifest hashes without checked-in review authority. A strict +semantic-equivalence receipt can now name one exact destination compiler route; +the client validates the previewed receipt metadata, compiles through a detached +destination-compatible root, and references only its ID/hash. The backend +reloads the checked-in receipt, checks both complete identities plus exact V2 +graph/interface pins, and repeats every instance-preservation check. Matching +only by definition/admission name is forbidden. The semantic-equivalence +ledger currently has zero receipts. A separate exact-body ledger now registers +six archived manifest/admission/Studio tuples covering 42 instances in the +read-only recovery audit and retains one HunyuanVideo 1.5 body as non- +convertible because its historical execution admission is absent. These bodies +are evidence, not compiler mappings. The separately reviewed mapping ledger now +covers 15 instances across three identities; 339 instances across 97 identities +remain blocked. All workflow bytes remain unchanged until exact supplement +preview and explicit Apply. The inventory CLI remains read-only and is not +conversion authority. + +A separate recovery-audit-only lane now preserves the evidence that can be +recovered safely without weakening that boundary. Two byte-pinned, ignored +frontend captures yield 18 strict Studio execution-spec bodies that match 20 +historical manifest identities, 21 manifest/admission/Studio-spec tuples, and +65 instances. The checked-in ledger contains only those public bodies, source +byte receipts, and collision-resistant canonical body hashes; the extractor +never ingests workflow paths, instance IDs, prompts, parameter values, or full +browser state. A schema-v4 recovery audit and +`GET /studio/composite-migrations/recovery-audit` expose the recovered body and +an empty/manual partial-review status alongside the still-missing manifest, +interface, block hierarchy, artifact authority, compiler mapping, and semantic +equivalence. Neither `verified_partial_evidence` nor `rejected_evidence` is +accepted by preview, conversion, compiler supplementation, or execution. + +### Legacy registered Cluster migration + +- Convert the wrapper to the common `block` canvas representation while + preserving its canvas ID. +- Resolve the exact registered definition and embed a V2 snapshot. +- Map legacy controls and explicit Cluster ports by stable binding, not label. +- For a collapsed root whose persisted port/preview metadata references a + deliberately absent execution child, resolve only the deterministic + historical child ID derived from an exact unique V2 `semanticRole`; reject + missing, duplicate, or conflicting roles without inference. +- Preserve instance values, external edges, position, dimensions, preview, and + execution provenance. +- Preserve internal layout when available; otherwise use deterministic default + layout without affecting graph hashes. +- If revision or content hash cannot be resolved, retain a visible + migration-required block. Never substitute current defaults. + +### Legacy Cluster-derived User Node migration + +- Compare its effective graph and values with its recorded registered parent. +- When it is semantically unchanged, offer **Restore registered source** while + preserving its instance values, preview, layout, and external edges. +- When it is structurally changed, migrate it as a workflow-owned or + user-owned V2 block with source ancestry and its existing explicit interface. +- Never recalculate its interface during migration. +- Audit test-generated reusable definitions separately. Deletion requires a + reviewed list and explicit approval. + +### Compatibility window + +- Readers support V1 and V2 during one release window. +- All new writes use V2 after the migration gate is enabled. +- A legacy export remains importable through the V1-to-V2 migrator. +- Removing V1 support requires fixture coverage and a separately documented + release decision. + +## Definition maintenance requirements + +Any change to `BlockDefinitionV2` or one of its nested contracts MUST include, +in the same change: + +1. the client type and strict validator in + `MoDiff-client/src/studio/blockSchemaV2.ts`; +2. the synchronized backend type and strict reusable-definition validator in + `MoDiff/modiff/block_definition_v2.py`; +3. matching client and backend canonical serialization and hashing updates; +4. a schema fixture for each affected source/boundary variant; +5. an explicit migration or a proof that the change is backward-compatible; + when V1 conversion semantics are affected, update both + `migrateUserBlockDefinitionV1` in the client and + `migrate_user_block_definition_v1` in + `MoDiff/modiff/composite_migration.py` and prove the same graph/definition + hashes; +6. registered-source adapter changes in + `MoDiff-client/src/studio/registeredBlockAdapterV2.ts` when the field is + produced from the Hugging Face catalog; +7. updates to this document and linked API documentation; and +8. round-trip, cross-runtime hash, migration, and browser coverage appropriate + to the field. + +Changing only one canonicalizer is a contract defect even when its local tests +pass. Before merging a V2 field or hash change, at least one shared fixture +MUST be hashed by both runtimes and produce the same exact strings. + +An instance-only `BlockInstanceV2` change does not require inventing a backend +definition field. It MUST still update the client type, strict instance +validator, workflow serialization/migration, schema fixtures, and lifecycle +tests together. If backend code starts interpreting that instance field, its +validator and tests join the same synchronized-change rule. + +Field removal or semantic reuse requires a new schema version. Optional fields +may be added within V2 only when old readers fail closed or ignore them without +changing execution, interface, values, or authority. A field name MUST NOT be +reused with a new meaning. + +Definition generators MUST validate before publication and produce stable +canonical hashes. Client code MUST consume declared definitions; it must not +contain model-family branches that reconstruct boundaries or controls. diff --git a/docs/custom-nodes.md b/docs/custom-nodes.md new file mode 100644 index 00000000..74238739 --- /dev/null +++ b/docs/custom-nodes.md @@ -0,0 +1,370 @@ +# Developing custom nodes + +Choose **Nodes → Add custom node**. The full-width yellow button opens three +sources: **Local**, **Hugging Face**, and **Git**. Adding code does not replace +your workflow. Intentional Add/Load imports and enables it in one action. + +- Local: drop a structured `.py` node onto the canvas (or Choose Python file). + One registered node is inserted at the drop position; multiple definitions + offer a node dropdown. Alternatively place a file/package in backend `custom/`; + it appears in the permanent Custom nodes category without executing. Choose + **Manage nodes → Load** to load it. Advanced accepts another backend source path. +- Hugging Face: enter a compatible repository ID or repository URL and click + **Add node**. A model repository alone is not a custom node. +- Git: enter an HTTPS repository URL and click **Add node**. Git must be installed + and private-repository credentials configured on the backend. + +Remote branches/tags resolve to exact commits internally. Revision and package +name overrides live under Advanced. Imports copy bounded source/metadata, not +model weights. Dependencies are checked but never installed automatically. +Errors show **Node import failed** and a reason; failed imports are not enabled. + +Only add code you trust: Python runs with backend permissions, not in a sandbox. +Add/Load/Reload is the explicit authorization—there is no separate approval +checkbox. Opening a workflow, listing sources, and refreshing do not authorize code. +Management offers **Reload**, **Disable**, source location and dependencies. +Code changes invalidate the previous hash until Reload; running or queued work +must finish before code changes. Disable preserves files and may require a restart +to undo arbitrary Python side effects. + +## A small ordinary node + +The repository includes a runnable example at `examples/custom_nodes/PromptTools`. + +For a model-independent image-processing example with multiple outputs, see +[`LightPaletteDirector`](../examples/custom_nodes/LightPaletteDirector/README.md). +It produces an RGB art-directed image, a grayscale region mask and diagnostics +for a downstream image/inpainting workflow. The [modularity demo](modularity-demo.md) +shows how to connect it to native stages and a whole-pipeline generator. +To author the same node yourself, create a folder below the checkout (all paths +in this guide are repository-relative) containing these three files. + +`main.py`: + +```python +from modiff.NodeBase import NodeBase + + +class PromptPrefix(NodeBase): + """Add a reusable prefix to a prompt without loading any models.""" + + label = "Prompt Prefix" + category = "Text" + resizable = True + params = { + "text": { + "label": "Prompt", + "type": "string", + "display": "textarea", + "default": "a quiet observatory", + }, + "prompt_input": { + "label": "Prompt Input", + "type": "string", + "display": "input", + "required": False, + "description": "Optional connected prompt. When connected, this replaces the inline Prompt value.", + }, + "prefix": { + "label": "Prefix", + "type": "string", + "display": "textarea", + "default": "Watercolor:", + }, + "result": {"label": "Prompt", "type": "string", "display": "output"}, + } + + def execute(self, text, prefix, prompt_input=None): + prompt = prompt_input if prompt_input is not None else text + return {"result": f"{prefix} {prompt}".strip()} +``` + +`__init__.py`: + +```python +from .main import PromptPrefix # noqa: F401 +``` + +`modiff_extension.json`: + +```json +{ "runtimeRole": "data" } +``` + +To add and test it without a model: + +1. Open **Nodes → Add custom node → Local → Advanced options**. +2. Enter `examples/custom_nodes/PromptTools` as the backend source; select **Add node**. +3. Expand **Custom nodes**, then add **Prompt Prefix** to the canvas. +4. Add **Text Value** and **Export Data**. Connect Text Value.Output → + Prompt Prefix.Prompt Input → Export Data.Data (using Prompt Prefix.Prompt). +5. Set Export Data to `text`, file `{PATH:data}/exports/PromptPrefix_{HASH:6}.txt`; + enter `a lighthouse at night`, keep `Watercolor:`, then Run. + The result is `Watercolor: a lighthouse at night`. +6. Edit the installed source path shown in Manage nodes, then select **Reload**. + Insert a fresh node if port names/types changed. + +For a single-file node, use the same class in a `.py` file and add +`MODIFF_RUNTIME_ROLE = "data"` at module scope. Optional +`MODIFF_REQUIREMENTS = ["package>=version"]` declares dependencies. +No `__init__.py` is needed for this form. Files must be UTF-8, at most 2 MiB, +and declare NodeBase classes with typed fields and an `execute` method returning +the named output dictionary. Arbitrary Python scripts are rejected. +Packages still use `main.py` and `__init__.py`; extra files belong in a package. + +For typed media examples, use `examples/custom_nodes/LightPaletteDirector/` +(decoded image plus a reusable region mask) and +`examples/custom_nodes/AudioEnvelope.py` (trim, gain and equal-power fades). +The audio example accepts a bounded waveform, keeps its sample rate and channel +layout, and copies samples before processing. Connect Load Audio → Audio Envelope +→ Preview Audio; start with a three-second clip and −6 dB gain. These processors +work after any compatible model's decoded media output, not its latents. + +Set `resizable = True` on the class when the node should show a resize handle. +Use `display: "textarea"` for a multi-line inline editor. A field can render only +one display at a time, so a node that needs both an inline prompt and a connectable +prompt socket must declare two keys, as `text` and `prompt_input` do above. The +execution method decides which wins. Use `display: "input"` (or legacy +`isInput: True`) for an input socket and `display: "output"` for an output socket. +A `type` by itself does not make a configuration field connectable. Modular +sidecars' `input_names` and `model_input_names` declare their sockets directly. + +### Port types and compatibility + +Port types are nominal payload contracts, not Python annotations. Give the two +ends the same narrow type used by the built-in producer or consumer you intend to +connect. The common public types are: + +| Payload | Use this `type` | +| ---------------------------- | ------------------------------------------------------------------ | +| Prompt or other text | `string` | +| Integer, decimal, or switch | `int`, `float`, `bool` | +| One or more images | `image` | +| Video or audio | `video`, `audio` | +| Diffusion latent payloads | `latent` or `latents`—copy the exact peer spelling | +| Prompt/image embeddings | `embeddings` or the exact specialized peer type | +| Generic tensor | `tensor` | +| Modular model component | `diffusers_auto_model` | +| Modular component collection | `diffusers_auto_models` or `diffusers_modular_pipeline_components` | +| Deliberately generic value | `any` | + +`str` and `text` normalize to `string`; `boolean` normalizes to `bool`; `integer` +normalizes to `int`; and `double`/`number` normalize to `float`. Other names are +exact after lower-casing and namespace removal: for example, `latent` and +`latents` are intentionally different. A list of names such as +`["image", "video"]` is a union. `any` and missing types accept a concrete peer, +but custom nodes should avoid them unless their runtime code truly validates all +accepted values. + +The compatibility implementation is maintained in the client at +`src/theme/connectionTypeCompatibility.ts`; connector colors and the reviewed +common vocabulary are in `src/theme/connectionTypes.ts`. The registry is open to +new nominal types, so this table is guidance rather than a closed enum. Inspect +the intended built-in peer in the Nodes library and copy its exact type. Client +tests cover aliases, unions, exact mismatches, direct connect, reconnect, and +connection-search filtering. The checked-in PromptTools integration test keeps +this guide's direction, textarea, resize, and execution example synchronized with +the backend. + +Literal class metadata and module-level literal constants can be previewed +without imports. Dynamic metadata, `MODULE_MAP`, and `MODULE_PARSE` are resolved +only after approval. The executable classes must be available from `main.py`. +Import errors, including missing dependencies, remain visible in Custom nodes. + +Edit the **installed source path** shown in Manage nodes. Staging copies a +folder; it does not create a link back to the original example or checkout. +Use **Reload**; the backend binds authorization to the current code hash. Reload +invalidates this module's cached nodes and their transitive cached consumers, +preserving unrelated owners. A same-size quick edit and edits to relative helper +modules load fresh code. Changed code cannot run using its previous approval. +If you change field names or types, insert a fresh node and reconnect it as needed; +reload refreshes the registry but does not rewrite saved graph parameters. + +Source resolution, inspection and listing remain available while a workflow runs. +Staging, enabling, disabling and reloading require an idle system: running, +queued or active metadata work returns a correction to +finish that work first. If an HTTP client disconnects after an approved import +starts, it cannot stop arbitrary Python safely; the execution lease remains held +until the operation finishes, including its separate metadata lease. Generic +field updates wait until the registry mutation finishes. Refresh sources to see the result. Failed imports +leave the module disabled with diagnostics. Python globals, native libraries, +threads and other import side effects may require a backend restart; disabling +or reloading cannot undo arbitrary code. + +## Modular Diffusers blocks + +Add `examples/custom_nodes/ModularPrompt` to try a model-free upstream +`ModularPipelineBlocks` class. The same layout can be published on the Hub and +staged with its exact commit. Required files are: + +- `modular_config.json` with `auto_map.ModularPipelineBlocks: "block.ClassName"`. +- `block.py` and any package-relative Python helpers. +- `mellon_pipeline_config.json` or `modiff_pipeline_config.json`, containing the + `node_params.custom` typed UI, `input_names`, `model_input_names`, and + `output_names` contract. + +MoDiff validates and translates the existing Diffusers/Mellon metadata. The +custom-source adapter treats an omitted `model_input_names` as an empty list, +as Mellon does, while still rejecting invalid declared values. Normalization +does not rewrite the staged source or the exact bytes covered by approval. +After code approval, a block with pretrained component requirements and no declared +model inputs receives one **Models** socket. Connect **Pipeline Components** from +**Load Models**. Its tooltip lists the required component names. This interface +comes from the approved Python block, so it is not available in the import-free +sidecar preview. Existing sidecar fields and explicit model sockets are preserved; +insert a fresh node after upgrading if a saved instance lacks the new socket. + +The approved entry point is imported in its own package, constructed with the native +Diffusers `from_config`, and executed through native `init_pipeline` and pipeline +calls inside the existing graph executor. It does not use upstream's shared +`diffusers_modules.local` alias, which can collide between local blocks/helpers +and retain stale bytecode. The upstream remote-code disable environment setting +still blocks activation. + +Sidecar input/output names must match the actual imported block. Fields named +`out_` map to upstream output ``. Components must come from connected +MoDiff loaders and the existing ComponentsManager. This adapter does not silently +download or load a model from a block's default repository. Use the generic +Load Models node to select and load model components first. Framework +construction starts fresh pipeline state while connected weights remain shared. +All Modular Load Models routes publish their already-loaded component bundle. +Inactive optional components stay absent. Runtime validation checks actual component +types, including supported upstream Auto factories, rather than pipeline-family +names. A compatible class does not guarantee compatible tensor dimensions or tasks. + +Stage `examples/custom_nodes/ModularImageReconstruction` for an executable example: +connect a decoded image to **Image** and the loader's **Pipeline Components** +to **Models**. Put it inside a Block and expose **Amount** as a control through +Configure Interface (0 to 1). Connect its Image output to Preview Image. +This block reuses an `AutoencoderKL` through its normal forward/offload hooks and +blends the reconstructed image with the source (0 retains the source, 1 uses the +reconstruction). It consumes no random generator and creates no model loader. +It honors the VAE's declared half-precision `force_upcast` setting for its forward +call and restores the original dtype even after an error. +Its component type is compatible with multiple image pipelines; VAEs of different +classes still require a suitable block implementation. Image dimensions must be +appropriate for the connected VAE's spatial scale. + +When all pretrained component types resolve to installed official Diffusers or +Transformers classes, enabling the block also registers **Load Models — [block +name]**. This source-specific supplier uses the same component manager and node +executor as ordinary loaders; it does not add a model-family implementation. +It appears only after approval, alongside the block in Custom nodes. + +Use it when the block needs additional weights, such as an annotator's model and +processor. Each component picker lists only installed Hub repositories whose +indexed component configuration declares the approved block's expected class; +an unrelated cached pipeline is not a compatible component merely because it is +downloaded. Select a Hub repository, exact lowercase 40-character revision, +subfolder and optional weight variant for each component. Download those revisions +in Models before Run: this loader is cache-only and never installs packages or +executes model-repository Python. Select precision, device and offload policy, set +the workflow's Memory policy to **Custom**, and connect its **Pipeline Components** +output to the block's **Models** input. Existing connected model sockets continue +to work. Compatible loaded components are shared; source configuration changes +invalidate reuse without replacing another workflow's component. + +Custom node port labels and field keys are local presentation names, not global +type identities. Connection discovery uses the declared `type`, direction and any +reviewed built-in capability metadata available at the endpoints. A custom node +that declares an overly broad or inaccurate type may still connect and then fail +its own runtime validation; extension authors should use the narrowest stable type +shared by the intended producer and consumer. + +Signal-aware custom ports can also publish `signalCompatibility`. Use +`{"required": true, "values": {"PipelineClass": ["capability"]}}` on a +consumer when its broad transport type is valid only for named signal identities. +When several component roles share that transport type, declare +`connectionRole: "role_name"` on the producer and add `"role": "role_name"` +to the consumer's `signalCompatibility`; the editor then rejects, for example, +a scheduler object wired to a denoiser input even when both came from the same +pipeline class. +For structured pipeline signals whose value contains an `actions` mapping, use +`{"required": true, "action": "$node"}` to require a non-empty entry for the +current node action (and, when the signal has a current `mode`, membership in that +action's mode list). A producer that supplies this contract declares a `signal` +on its connector; a pass-through node relays it with +`onSignal: {"action": "signal", "target": "output_field"}`. These declarations +are used for search, direct connect, reconnect and later signal changes. They do +not replace runtime validation of opaque values, tensor layouts or custom code. + +Source approval is not resource qualification. Additional custom model suppliers +require Custom memory even when the processing block declares connected-component +resource use. Arbitrary Python component classes, local weight directories and +remote model code are not handled by this supplier; an approved block can still +accept compatible components from existing loaders. Model/type compatibility and +the upstream block's tensor/task semantics remain distinct. + +The executable Hub entry point is **Add custom node → Hugging Face**. +Existing contract-only saved Blocks remain separate from executable custom code. + +## Dependencies and memory policy + +`requirements.txt`, project dependencies in `pyproject.toml`, and requirements in +`modular_config.json` are shown with installed versions. Missing, incompatible or +unparseable declarations block enable. Direct URLs and pip command options require +manual review. Nothing installs packages automatically. Review imports too: +undeclared dependencies cannot be inferred completely from Python source. +Use the contributor runtime procedure for dependency changes; do not install into +or modify a sealed optional-runtime overlay. Reinspect after changing packages. + +The code hash binds copied source files and declared installed dependency versions; +it is not a lock of every transitive package, an attestation of Python side +effects, or model qualification. + +An optional `modiff_extension.json` declares one resource role: + +| `runtimeRole` | Behavior | +| ---------------------- | ------------------------------------------------------------------------------------------------------ | +| `data` | Author declares no model loading; the enabled node can run with Automatic memory. | +| `connected_components` | Author declares reuse of connected models; Automatic memory requires a connected reviewed model owner. | +| `manual` (default) | Resource use is unmanaged; select Custom memory in either workspace. | + +Review this declaration with the code. It is an operator-approved extension +contract, not a measured memory guarantee. Automatic memory does not execute custom code +during inspection or grant new execution permissions. Arbitrary custom suppliers +of dimensions/model identities still need manual resource settings; they are not +promoted to the built-in preplanning evaluator. Automatic memory retains model owners in a +graph containing custom code rather than assuming that Python has released every +reference. Insufficient combined memory remains a blocker. + +## HTTP flow + +All code mutations use POST, are bounded, and retain the local single-user server +boundary. Local staging accepts a path on the backend machine. Source preview and +approval state are local administrative data; do not publish machine paths or +approval files in workflow packages. + +The UI uses `POST /custom_modules/add` with +`{"kind":"local","source":"examples/custom_nodes/PromptTools","name":"PromptTools","consent":true}`. +For `hub`/`git`, revision is optional and resolved internally. For a dropped file, +use `{"kind":"file","name":"MyNode","content":"","consent":true}`. +The response includes the enabled module and refreshed catalog. This is a trusted +code mutation, not validation-only. Import failure does not roll back arbitrary +Python side effects. Dependencies are never automatically installed. + +The lower-level inspection/lifecycle endpoints remain available for tooling. +For a Hub source, optionally `POST /custom_modules/resolve` with +`{"source":"https://huggingface.co/owner/repo","revision":"main"}`. The response's +`source` contains the normalized repository ID, `requestedRevision` and immutable +`revision`. Pass that exact identity to installation. This optional read-only +lookup does not stage files, import code or acquire the execution lease. Only Hub +model repositories are supported here, not Dataset/Space, file or subfolder URLs. + +1. `POST /custom_modules/install` with + `{"kind":"local","source":"examples/custom_nodes/PromptTools","name":"PromptTools"}`. + Use `kind: "git"` or `"hub"` and `revision` for remote staging. +2. `POST /custom_modules/PromptTools/inspect` returns its current `module.codeHash`, + files, dependencies and preview without importing Python. +3. `POST /custom_modules/PromptTools/enable` with + `{"codeHash":"","consent":true}`. +4. Use `custom.PromptTools.PromptPrefix` through the normal graph API. After source + edits, inspect again and POST the new hash and consent to `/reload`. +5. `POST /custom_modules/PromptTools/disable` disables future execution and releases + affected caches; files and model downloads are preserved. + +`GET /custom_modules` and `POST /custom_modules/refresh` list sources without +reloading. Moving-branch `/update` returns an actionable rejection: stage a new +exact revision under a new name, review, then explicitly replace graph nodes. +The backend's `custom/.extensions.json` holds local approvals outside each source +package; a source cannot import its own approval by including that filename. diff --git a/docs/demoable-hugging-face-nodes-plan-2026-08-27.md b/docs/demoable-hugging-face-nodes-plan-2026-08-27.md deleted file mode 100644 index 4d28cd2f..00000000 --- a/docs/demoable-hugging-face-nodes-plan-2026-08-27.md +++ /dev/null @@ -1,1506 +0,0 @@ -# Demoable Hugging Face Nodes Plan — 2026-08-27 - -> **Status correction — 2026-09-01:** The separate Cluster renderer and -> automatic Cluster-to-User-Node reconstruction used by this demo are legacy -> behavior. They are no longer accepted as completion because reconstruction -> can change controls, public ports, actions, and connections. Registered -> Clusters and User Nodes must use one composite-node system with copy-on-write -> workflow customization. The authoritative contract, migration plan, current -> checklist, and estimates are in the -> [Unified Composite Node Contract and Implementation Plan](unified-composite-node-implementation-plan-2026-09-01.md). -> Checked legacy rows below retain historical test evidence only. - -## Outcome required - -By the 2026-08-27 demo, MoDiff should visibly prove: - -1. One Diffusers Cluster each for image, video, and standalone audio can be - inserted, edited, saved, refreshed without parameter reset, and executed. -2. A first-party Cluster and a User Node have the same composite canvas surface. - A structural edit updates that workflow instance copy-on-write, preserves - its controls, ports, values, dimensions, preview, and external connections, - and leaves the catalog definition unchanged. -3. A new library node or an existing top-level canvas node can be placed inside - an expanded composite. The wrapper identity and explicit public interface - stay stable, and the topology survives collapse, Save, browser refresh, - re-expansion, and execution. -4. User Nodes and Cluster Nodes are never nested. -5. A changed User Node offers three explicit persistence choices: - **Update existing User Node**, **Save as new User Node**, or - **Keep changes only in this workflow**. -6. A remote-code-disabled Hub import can install an exact commit, preview its - declarative contract, summarize the import, save it to User Nodes, survive - refresh, and execute when the contract is supported. - -## Product decisions - -### Cluster customization is copy-on-write, not conversion - -Parameter edits are always instance-local and do not change first-party -ownership. Viewing or changing a parameter therefore keeps the node a Cluster. -Moving an existing owned child is presentation-only. The first actual topology- -changing action—adopting a node, deleting or reconnecting an internal node, or -requesting direct structure editing—must: - -1. Copy the exact effective graph inside the same workflow instance. -2. Apply the user's structural action as one undoable mutation. -3. Preserve wrapper identity, repository, immutable revision, parameters, - explicit controls and ports, external edges, position, size, and preview. -4. Remove incompatible first-party qualification and Auto authority without - changing rendering or source provenance. -5. Keep the change workflow-only until the user explicitly saves or updates a - reusable User Node definition. - -The registered definition stays immutable. `Cluster` remains its library and -provenance category; it is not a different renderer. No destructive conversion -or automatically persisted fork is part of the accepted flow. - -### No nesting - -- A Cluster or User Node may not become a child of another Cluster/User Node. -- Dropping a Cluster/User Node over an expanded composite leaves it top-level - and displays a clear notice. -- Ordinary nodes may be adopted into one expanded composite at a time. -- Moving a child between composites detaches it from the old instance and - adopts it into the new instance in one undoable action. - -### Contextual names - -A workflow-only customized registered block keeps its existing instance name. -It is not renamed merely because its topology changed. - -Saving a new reusable definition defaults to: - -` — ` - -Names remain editable. Identity is an opaque ID, so two definitions with the -same display name do not overwrite one another. - -### User Node persistence choices - -- **Update existing User Node** saves the current topology and exposed - parameters under the current User Node ID. The current workflow instance is - already updated; future insertions use the updated reusable definition. -- **Save as new User Node** generates a new ID, uses the contextual default - name, saves the exact current topology, and points the current workflow - instance at the new definition. -- **Keep changes only in this workflow** updates only the embedded workflow - snapshot. The reusable library definition remains unchanged. - -No choice should silently rewrite other open workflow instances. Existing -instances retain their embedded snapshot until the user deliberately chooses -to refresh them from the reusable definition. - -## Priority and completion status - -Completed rows report their measured verification result for the original demo -critical path. The active H5 expansion below still has license-bound live -qualification and publication-review work; the bounded H6 speech expansion is -live-qualified. The arbitrary-Python sandbox remains an explicitly separate -security project. - -| Priority | Work | Functional implementation | Verification | Completion target | -| -------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------: | ------------------------------------------------------------------------------------------------------: | ----------------------------------------- | -| P0 | MiniMax Music 3 visible-frontend install and standalone-audio generation | Complete | Real frontend install/edit/save/refresh/parity/generate test passed in 2.0 min with the cached snapshot | Complete | -| P0 | Replace legacy automatic conversion with shared V2 composite instances and copy-on-write customization | In progress | Exact two-instance Qwen regression plus V1-to-V2 migration required | 1.5–2 days for Qwen demo; 4–6 days full | -| P0 | Drag an existing top-level ordinary node into an expanded User Node; move between User Nodes; reject Cluster/User nesting | Complete | Real pointer drag plus mocked persistence/choice coverage | Complete | -| P0 | Update existing / Save as new / Keep only in workflow choices with contextual naming | Complete | Mocked browser Save/refresh/isolation test passed | Complete | -| P1 | Complete declarative Hub import wizard with remote code disabled | Complete | Mocked test and real official-Hub E2E passed | Complete | -| P2 | Execute arbitrary repository `block.py` in a separately authorized sandbox | 2–4 working days | Security boundary, escape, network/filesystem, cancellation, and resource-abuse tests | Not safe to promise for the next-day demo | - -The tomorrow-demo critical path is P0. The declarative Hub importer is included -as P1 because it does not execute repository Python. Arbitrary Python remains -explicitly unsupported and fail-closed until the P2 sandbox is qualified. - -## Implementation sequence - -### Phase 1 — real audio proof - -- [x] Exact MiniMax pipeline/block contract and five-node execution graph. -- [x] Revision-bound license acknowledgement UI and authorization received. -- [x] Install the exact 23-file snapshot through the visible Model Manager. -- [x] Insert the Cluster, edit prompt/lyrics/steps/duration, Save, refresh, and - compare parameters. -- [x] Compare collapsed and expanded API graphs. -- [x] Generate real audio, copy the asset and receipt into the review folder, - and inspect the media metadata. - -### Phase 2 — legacy structural conversion evidence (superseded) - -These checked rows prove what the 2026-08-27 build did. They do not satisfy the -2026-09-01 shared-composite contract and must be replaced by the active V2 plan. - -- [x] Extract Cluster-to-User-Node conversion into one reusable application - action used by the Cluster header, library drop, and canvas drag paths. -- [x] Rename the visible action to **Customize as User Node**. -- [x] Detect a normal library-node drop within an expanded Cluster, convert the - instance, expand the resulting User Node, and apply the drop. -- [x] Detect an existing ordinary canvas node dropped within an expanded User - Node and adopt it with correct relative coordinates. -- [x] If dropped within an expanded Cluster, convert the Cluster first and then - adopt the node. -- [x] Support moving a child from one User Node to another. -- [x] Reject Cluster/User Node nesting consistently for library and canvas drag. -- [x] Make each adoption/conversion one undoable history transaction. - -### Phase 3 — explicit reusable-definition choices - -- [x] Materialize the current User Node definition from either expanded - children or the collapsed embedded snapshot. -- [x] Add **Update existing**, **Save as new**, and **Keep in workflow** actions. -- [x] Generate workflow-context names using the active workflow title. -- [x] Preserve current workflow values, exposed ports, origin provenance, and - immutable artifact revisions for all three choices. -- [x] Ensure Update does not mutate already-open instances silently. -- [x] Ensure Save as new produces a new opaque ID and a separately insertable - library row. - -### Phase 4 — declarative Hub import - -- [x] Turn the current repo/commit form into a bounded wizard state machine. -- [x] Wait for exact-revision installation and show progress/errors. -- [x] Read and validate `mellon_pipeline_config.json` without executing repo - code. -- [x] Preview block hierarchy, components, ports, artifact commit, and rejected - fields. -- [x] Show admission and remote-code summary before confirmation. -- [x] Save the validated definition to User Nodes and preserve it across Save - and browser refresh. -- [x] Run one executable remote-code-disabled real-Hub E2E against - `diffusers/FLUX.2-klein-4B-modular@62ac375aa5308588f111fcd12115f5c54a8b1f4f` - with every component pinned to an exact immutable revision. -- [x] Keep repositories requiring `block.py` blocked with a precise explanation - until the separately authorized sandbox exists. - -## Test order - -Functional tests precede browser E2E so failures remain fast and attributable. - -1. Pure graph tests: adoption coordinates, move between containers, no nesting, - copy-on-write graph edits, stable explicit boundaries, definition - identity/version choices. -2. Store tests: undo/redo, save/update/new behavior, instance isolation, JSON - round trip. -3. Mocked browser tests: library drop, existing-child movement, copy-on-write - structural customization, three persistence choices, - Save/refresh/re-expand. -4. Real frontend tests: Qwen registered-block customization and workflow-owned - block execution, - MiniMax standalone audio, and safe declarative Hub import. -5. Final smoke: image, video, audio demo receipts and assets collected under a - single review index. - -## Historical demo evidence — 2026-08-26 - -- MiniMax Music 3: exact 23-file/28.52 GB revision installed through the visible - Model Manager after the revision-bound license acknowledgement. Multiline - prompt/lyrics, duration, steps, seed, dtype, device, and offload settings were - equal before and after Save/browser refresh. Collapsed and expanded exports - were equal, and task `KqYefJpE0m8c` produced a 44.1 kHz stereo WAV. -- Qwen Image: the visible frontend test edited and persisted Cluster prompt, - width, height, steps, seed, device, dtype, and offload parameters; generated - task `fetVV_JvHMy4`; dragged an existing Data Viewer across the expanded - Cluster boundary with real pointer input; automatically converted only that - instance to `Qwen Image — Text To Image — Workflow 1`; connected the decoder - image branch to the adopted node; preserved the modified topology through - collapse, Save, refresh, and re-expansion; and generated task - `urYf-nTyEoe_` from the customized User Node. Both runs produced byte-equal - 256px technical images, while the second run also executed the retained Data - Viewer branch. -- Safe Hub import: the official FLUX.2 Klein Modular repository and both of its - component repositories were installed at exact commits, inspected without - importing repository Python, previewed, imported with - `trust_remote_code=false`, saved, refreshed byte-for-byte, and executed as - task `f9r--ix0rxHr`. It produced a 1024px image with media hash - `sha256:bytes:2d1f79356886ebfe9d89692939be7fd5093b78c55a0f00387546c87402f002f8`. -- Release verification: the complete backend gate passes with 2,185 passed, - 51 skipped, one warning, and 6,557 subtests; the complete client - check/unit/typecheck/lint/build/bundle gate passes; all 114 mocked Studio - browser tests pass; and the focused optional-runtime cutover browser - regression passes. - -## Remaining post-demo work - -The model-execution and asset portions of the initial demo are implemented and -evidenced. The composite presentation, customization, and persistence portion -was reopened by the 2026-09-01 architecture correction and is complete only -when the [unified V2 plan](unified-composite-node-implementation-plan-2026-09-01.md) -passes. The separate arbitrary-repository-Python -sandbox remains fail-closed until it can enforce filesystem/network -restrictions, time and memory limits, cancellation, explicit operator -authorization, and escape tests. This does not block declarative Mellon import -or first-party reviewed block execution. Showcase-quality prompt -and parameter tuning is also intentionally separate from the technical -qualification assets. - -### Ordered remaining Diffusers priority — 2026-08-27 - -Work must proceed in this order: - -1. Complete shared release engineering before adding more definitions: - extend incompatible resident-model cleanup and resource admission from Auto - runs to Expert/Cluster runs, run the full regression, then generate - definition-specific promotion receipts and enable public flags only after - the corresponding output is approved. -2. Complete the strengthened visible-frontend lifecycle for the 14 already - admitted routes. The nine remaining SDXL combinations, LTX-2 - image-to-video, and both LTX 0.9.8 routes are now green; the active remainder - is Wan 2.2 text-to-video and image-to-video. -3. Only after steps 1 and 2 are green, promote the remaining 45 contract-only - workflows family by family. Each batch must retain immutable artifacts, - official Diffusers/Modular Diffusers behavior, exact conditionals and state - flow, license/access gates, bounded resource planning, and a real visible- - frontend lifecycle receipt before publication. - -Structural catalog coverage is already 94/94. This ordering concerns runtime -admission, live qualification, and publication; it must not relabel a -contract-only definition as executable from schema inspection alone. - -Shared resource handling is now complete. The pre-run cache boundary applies -to both Auto and Expert/Cluster graphs, compares model family, immutable -artifact, loader topology, dtype, quantization and offload recipe, preserves an -identical reusable resident pipeline, and releases incompatible app-owned node, -model, Modular component, disk-offload and allocator state before loading the -next family. A visible-frontend regression warmed both FLUX.2 Klein Base routes -and left 15.1 GiB allocated / 15.7 GiB reserved, then ran LTX-2 in the same -worker without a restart. Task `n3gcydVUYz6O` records the exact -`Flux2KleinBaseModularPipeline` -> `LTX2ModularPipeline` cleanup, 14 released -cached nodes, successful allocator trimming, no cleanup errors, and a completed -H.264/AAC output. Evidence is under -`test-review/frontend-pipeline-e2e-2026-08-26/cross-model-memory/`. - -The same idle-only resource boundary now also protects destructive Model -Manager cache turnover. Before deleting an immutable Hub revision, the backend -revalidates the hash-bound plan, releases app-owned node/model/Modular -Diffusers/offload references, collects and trims the allocator, and only then -removes snapshot bytes. The response contains a typed `runtimeRelease` receipt. -This closes a live Linux failure where a 44.36 GB LTX cache directory was -deleted while roughly 30 GB of its safetensors blobs remained open in the -worker. Focused backend coverage passes with `88 passed, 40 subtests`; client -action tests, lint, and typecheck are green. The first repaired visible -frontend turnover evicted the already-qualified LTX-2 snapshot, reclaimed -92,074,139,514 bytes immediately, preserved workflows/definitions, and left no -deleted Python file descriptors. The frontend qualification harness records -the release receipt on subsequent turnovers. - -## Active release-hardening tranche — 2026-08-27 - -Work is ordered by user-visible correctness first and broad regression cost -last. A checked item means both implementation and its focused regression are -complete; live-backend and full-suite rows remain distinct proof levels. - -### H1 — guided semantic composition - -- [x] Inspect every expanded User Node for missing sockets, invalid directions, - incompatible types, invalid container crossings, and incompatible sealed - Modular workflow-state identities. -- [x] Discover compatible insertion points for an adopted ordinary node and - offer an explicit one-click edge splice instead of guessing silently. -- [x] Make insertion/replacement/deletion one undoable graph mutation and retain - the resulting topology through collapse, Save, refresh, and re-expansion. -- [x] Preserve the exact upstream pipeline/workflow/block identity in User Node - provenance, while clearly distinguishing ordinary MoDiff graph edits from - reviewed upstream `ModularPipelineBlocks` composition. -- [x] For reviewed upstream block-tree edits, reconstruct the selected workflow, - apply only exact pinned block operations, and call upstream - `init_pipeline()` so Diffusers recomputes inputs, outputs, components, and - configs. Unknown classes, paths, revisions, state, or container kinds must - fail closed. - -Focused proof: the graph/store suite covers typed splice discovery, invalid -direction/type/state rejection, one-step undo, provenance sealing, and -replacement/deletion. The live backend additionally rebuilt the pinned Qwen -workflow after a bounded structural operation and returned an -`init_pipeline()` receipt without loading model weights. - -### H2 — legacy live lifecycle and isolation evidence - -- [x] Retain a compatible inserted node in Qwen's actual execution graph, then - reconnect, Save, refresh, Run, replace, delete, and verify persistence. -- [x] Run **Update existing**, **Save as new**, and **Keep only in workflow** - against the live backend; prove a second open instance is unchanged. -- [x] Add a visible two-Cluster/two-workflow isolation proof covering model, - prompt, and parameter edits before and after refresh. -- [x] Prove undo/redo of automatic Cluster-to-User conversion and node adoption. -- [x] Prove backend User Node save failure and interrupted workflow save leave - the original Cluster/User Node recoverable and do not leak changes to - another workflow. - -The legacy Qwen test supplies the retained real model branch and both -generation receipts. The generic live-backend topology test separately proved -replacement, deletion, invalid-socket diagnostics, interrupted workflow save, -and recovery without paying the Qwen model-loading cost for each mutation. - -### H3 — executable declarative Hub import - -- [x] Add an immutable, remote-code-disabled declarative integration fixture - whose sidecar resolves entirely to admitted MoDiff/Diffusers nodes. -- [x] Prove import -> contract preview -> User Node -> Save -> refresh -> Run - against the real backend. Keep repositories requiring `block.py` Python - preview-only until the separate sandbox is authorized and qualified. - -### H4 — release regression and publication boundary - -- [x] Run the complete client check/build/bundle gate, full backend suite, and - complete mocked Studio browser suite after H1-H3. -- [x] Restart the backend and run a clean-browser smoke, including legacy - workflow/User Node migration fixtures. -- [x] Generate definition-specific promotion receipts for the two explicitly - approved technical outputs: Qwen Image 2512 text-to-image and MiniMax - Music 3 text-to-audio. Receipt lookup is admission-specific, so the Qwen - approval cannot promote the five other workflows sharing its execution - profile. Both exact admissions now publish `liveProof: true` while - `executable` and `autoEligible` remain runtime/resource-gated. Unreviewed - routes still require their own output approval and receipt. - -Current regression checkpoint: the post-FLUX.2-Klein-Base backend gate is -green with 2,198 passed, 53 intentional skips, 1 warning, and all 6,579 -subtests passed. The complete client unit/typecheck/build gate is green, and -the post-change mocked Studio browser suite passed all 114 tests in 6.2 -minutes. The verified client build was mirrored into backend `web/` while -preserving backend-owned content; a clean backend restart returned healthy -same-origin API responses, served the byte-identical built shell, established -a visible websocket connection, completed workspace startup, and produced no -browser page errors. Its screenshot is -`test-review/frontend-pipeline-e2e-2026-08-26/production-static-smoke.png`. -No active core graph/lifecycle technical gate remains. Future publication -receipts remain intentionally blocked on explicit approval of each associated -technical output; arbitrary repository Python remains a separate sandboxed -security project rather than a prerequisite for declarative Hub import. - -### H5 — Diffusers definition qualification expansion - -- [x] Audit the current 94-definition Diffusers catalog. Fifty execution - admissions across 49 definitions are graph-qualified and insertable; the - other 45 definitions remain structurally insertable but contract-only. -- [x] Register the three admitted FLUX-family Modular pipeline identities in - the closed client model/profile registry so authoritative backend - execution specifications are retained instead of silently discarded. -- [x] Preserve the exact saved Modular pipeline identity when canonical legacy - workflows are hydrated and adopted; do not fall back to a standard - pipeline merely because both identities share one repository. -- [x] Make Cluster preparation recover from a missing/stale capability snapshot - by refreshing authoritative runtime data and retrying the exact - specification identity and content hash. -- [x] Bind every image-oriented Modular Diffusers `ModelsLoader` revision to - the immutable reviewed artifact commit. This closes the revision gap in - 39 execution specifications; all 50 admitted routes now expose the same - exact artifact revision in their sealed loader binding. -- [x] Admit the pinned FLUX.2 Klein checkpoint's repository-scoped - `Qwen2TokenizerFast` serialization as the exact Transformers 5 - `Qwen2Tokenizer` compatibility alias. Arbitrary tokenizer substitutions - remain rejected. -- [x] Promote the official non-distilled **FLUX.2 Klein Base 4B** workflows - from contract-only discovery to native Modular graph admission without - aliasing them to the four-step distilled class. The text and - image-conditioned definitions retain `Flux2KleinBaseAutoBlocks`, 50-step - defaults, classifier-free guidance 4, and exact artifact revision - `a3b4f4849157f664bdbc776fd7453c2783562f4d`. -- [x] Review and pin the public Apache-2.0 component snapshot: 21 selected - files, 15,964,212,614 weight bytes, no repository Python, and no duplicate - root single-file checkpoint. The checked-in review records every selected - weight's Hub LFS SHA-256 and keeps Auto, Gallery, and live proof closed. -- [x] Finish the visible frontend FLUX.2 Klein Base qualification. The browser - activated the exact optional runtime, installed the revision-bound - 21-file snapshot through Model Manager, inserted both definitions, - changed their parameters, saved and refreshed their workflows, expanded - all 10 text and 13 edit placements, proved collapsed/expanded graph - equality, and generated real WebP outputs. The task ids are - `cqSH2AL_5Z31` and `xvcKj8ikhGyw`; their media hashes are - `sha256:bytes:8898a0947624f2b57b7dc8fc766be65841fda3fc6877fc190c268ff814095729` - and - `sha256:bytes:eaa30ce003fca5e2205ae6e80b5a506539e6b0c8ab41d47a854a6b77b9bf6a4b`. - Evidence is preserved under - `test-review/frontend-pipeline-e2e-2026-08-26/flux2-klein-base-clusters/`. - The outputs remain qualification-only and the public flags are unchanged - pending an improved showcase run and explicit approval. -- [x] Generate separate showcase candidates through the same visible frontend - lifecycle at 768x768 and 24 steps. The text-to-image task - `WHgJeEi0XV7q` produced - `sha256:bytes:34e8b314a0cde65a0e0536d01f77076e9aa2d103cce1850c91eea5c256da3fc4`; - the image-edit task `6oSITqhuz31a` produced - `sha256:bytes:f3bc89e15235b111bddd079496ab4e7097b06b59f1c4e6fcc5815a85f124e3f1`. - Both nondefault prompts and sampling parameters survive Save/browser - refresh exactly. The candidates and receipt are under - `test-review/frontend-pipeline-e2e-2026-08-26/flux2-klein-base-showcase/`. - They remain `showcaseApproved: false`; no promotion receipt or public flag - may be issued until explicit review approval. -- [x] Qualify **Flux2 Klein — Text To Image** and **Flux2 Klein — Edit Image** - through the visible frontend: catalog insertion, parameter changes, - workflow Save, browser refresh, exact parameter comparison, block-tree - expansion, collapsed/expanded API graph comparison, pinned-revision model - load, and real generation all pass in one browser test. -- [ ] Promote either definition's immutable public `executable`, `autoEligible`, - or `liveProof` flag only after its generated output and definition-specific - receipt receive explicit approval. -- [x] Qualify **Flux — Text To Image**, **Flux — Image To Image**, - **Flux Kontext — Text To Image**, and **Flux Kontext — Edit Image** - through the visible frontend. Each route now proves insertion, - nondefault parameter edits, workflow Save, browser refresh with exact - parameter equality, complete reviewed block-tree expansion, - collapsed/expanded graph equivalence, exact-revision loading, and real - generation. -- [x] Preserve the official FLUX split-block geometry contract: image-to-image - preprocessing receives the saved requested size, while the decoder - receives the actual post-denoise width and height. This covers Kontext's - documented normalization to a supported resolution without rewriting the - saved Cluster parameters. -- [x] Admit only the two pinned FLUX.1 repositories' Transformers 5 - `T5TokenizerFast` serialization as the exact `T5Tokenizer` compatibility - alias. Unrelated repositories and tokenizer substitutions remain - rejected. -- [x] Select Z-Image as the next family: its exact reviewed artifact is already - installed, Apache-2.0 licensed, supports both official `text2image` and - `image2image` Modular workflows, and does not require auxiliary model - repositories. -- [x] Preserve the official Z-Image image-encoder geometry contract by exposing - `height` and `width` on the split VAE encoder and binding the saved - Cluster dimensions into that node. -- [x] Qualify **ZImage — Text To Image** and **ZImage — Image To Image** through - the visible frontend with nondefault edits, Save/browser refresh exact - equality, complete block expansion, collapsed/expanded graph equality, - exact-revision execution, and real generated outputs. -- [x] Select the installed Apache-2.0 Qwen Image Edit family ahead of SDXL and - qualify **Qwen Image Edit — Edit Image** through the same complete visible - frontend lifecycle across its exact 17-block `image_conditioned` - hierarchy. -- [x] Qualify **Qwen Image — Image To Image** across its distinct 16-block - `image2image` workflow with saved width, height, strength, prompt, source, - steps, seed, and runtime overrides. -- [x] Qualify **Qwen Image — Inpaint** across its 18-block `inpainting` - workflow, including exact source and mask paths before and after browser - refresh. -- [x] Qualify **Qwen Image Edit — Inpaint** across its exact 20-block - `image_conditioned_inpainting` workflow. The qualification fixed two - execution-contract defects found by the real run: the source image is now - connected to prompt conditioning, and the split graph no longer forces - saved 256px dimensions onto the model's official image-conditioned latent - geometry. -- [x] Qualify both admissions of **Qwen Image Edit Plus — Default** through the - visible frontend: explicitly select `edit_image` or - `multi_image_reference_edit`, preserve the selected admission and exact - one/two-image input list across Save and browser refresh, expand the exact - 17-block hierarchy, prove collapsed/expanded graph equality, and execute - the pinned revision. -- [x] Qualify **Qwen Image Layered — Layer Decomposition** with nondefault - `layers`, `resolution`, and `max_sequence_length`. Preserve those values - across Save/refresh, prove the exact 18-block collapsed/expanded graph, - and carry the upstream nested layer batch through the app as a three-item - `image_collection` without dropping outputs. -- [x] Qualify the complete official Qwen Image ControlNet workflow set: - **Control Image**, **Controlnet Image2Image**, and **Controlnet - Inpainting**. Bind and execute—not only persist—`max_sequence_length`, - `controlnet_conditioning_scale`, `control_guidance_start`, and - `control_guidance_end`; preserve the exact control/source/mask paths and - all nondefault values through Save and browser refresh; and prove the - respective 16-, 20-, and 22-block expanded graphs are API-equivalent to - their collapsed Clusters. -- [x] Preserve a prepared Cluster's run authority when backend dynamic-field - refreshes echo an identical sealed value after expansion. Exact no-op - echoes now bypass history and authority invalidation; any differing - sealed value remains rejected. -- [x] Qualify the next installed, admitted Diffusers family: **Wan — Text To - Video** now covers the Apache-2.0 Wan 2.1 1.3B native Modular pipeline. - The qualification added the missing execution bindings for width, - height, and `max_sequence_length`; retained the exact live backend - execution spec in the client registry; and admitted only the pinned - 1.3B checkpoint's official `T5TokenizerFast` serialization as the exact - Transformers 5 `AutoTokenizer` compatibility alias. -- [x] Prove the Wan video route through the visible frontend: catalog - insertion, nondefault prompt/seed/guidance/size/frame/fps/sequence edits, - workflow Save, browser refresh with exact equality, complete reviewed - block expansion, collapsed/expanded API graph equality, immutable - revision loading, real generation, and MP4 persistence all pass. -- [x] Run the full backend, client, and mocked-browser regression gates after - the Wan qualification changes: backend `2185 passed, 51 skipped, 6557 - subtests`; the complete client `npm run check` gate passed; and all 114 - mocked Studio browser tests passed. -- [x] Qualify **Wan Image2 Video — Flf2V** through the visible frontend. Bind - `max_sequence_length` to the official Modular text-encoding block; retain - distinct first- and last-frame paths, prompt, geometry, generation - controls, and immutable revision across Save/browser refresh; expand all - 17 reviewed placements; prove collapsed/expanded graph equality; execute - the native Modular route; and persist a valid nine-frame MP4. -- [x] Qualify the installed Wan 2.1 single-image-to-video workflow through the - visible frontend. The exact 36-file model closure is recognized by Model - Manager; the workflow retains its prompt, source image, geometry, - sequence length, frame count, FPS, seed, dtype, and offload controls - across Save/browser refresh; all 15 reviewed placements expand; the - collapsed and expanded API graphs are identical; and the native Modular - route persists a valid nine-frame MP4. Continue with another installed - Apache-2.0 Diffusers family. Use SDXL only after its revision-bound - OpenRAIL++ acknowledgement is explicitly available in the visible flow. -- [x] Qualify **Ernie Image — Text To Image** as the next Apache-2.0 family. - Install the exact 24-file, 31.65 GB closure through visible Model - Manager; preserve the exact Modular definition, pinned revision, prompt, - 1024px geometry, step count, seed, guidance, dtype, and offload values - across Save/browser refresh; expand all nine reviewed placements; prove - collapsed/expanded API graph equality; execute the reviewed equivalent - standard `ErnieImagePipeline`; and persist the generated WebP. Public - execution flags remain unchanged pending explicit output approval. -- [x] Preserve the original upstream conditional trees in a separate reviewed - no-weight snapshot before promoting LTX-2 as a native Modular route. The - companion contract covers all 34 pipeline classes and 94 workflows with - 632 static block definitions, 1,051 unpruned placements, and 73 - conditionals. It records trigger order, every branch/default, complete - presence/absence truth tables (including rejected combinations), and the - selected branch plus active-leaf trace for every advertised workflow - predicate. A detached read-only - `GET /huggingface/modular-conditionals` endpoint publishes it without - changing the existing resolved definition identities. Focused contract - and API tests pass. A pinned no-weight upstream test also proves that - LTX-2 with `num_frames` omitted includes `LTX2DurationStep` and a - `duration_head` component, while supplying `num_frames` removes both and - rebuilds the resulting interface through `init_pipeline()`. -- [x] Consume the reviewed conditional companion in the node-library client, - materializer, and expansion UI. The active workflow instance must show - its selected branch while retaining inactive official alternatives. For - LTX-2, prove both `num_frames` supplied (duration prediction skipped) and - `num_frames` omitted (duration branch active) against upstream - `get_execution_blocks(...)`/`init_pipeline()` before claiming native - execution. - Implement this with the upstream presence semantics, not model aliases: - `ConditionalPipelineBlocks` selects only from trigger-input presence, - `AutoPipelineBlocks` selects the first present trigger (or its declared - default), and `SequentialPipelineBlocks.get_execution_blocks()` adds - earlier intermediate-output names to the active input set before later - selectors run. The official API contract is - . - Any edited block tree must be rebuilt with `init_pipeline()`; changing a - previously created pipeline's copied `blocks` property is not a runtime - mutation. The official lifecycle contract is - . - The reviewed companion and client projection therefore carry each - placement's exact upstream `intermediate_outputs`; final public outputs - are not incorrectly treated as later selector inputs. - The visible LTX-2 frontend proof now passes without loading model weights: - a newly inserted Cluster treats Studio-seeded execution values as - omitted, displays the active `duration` selector and branch, then treats - an instance edit to `numFrames=9` as explicitly present and displays the - selector as skipped plus the official duration block as an inactive - alternative. The explicit-presence receipt and value survive workflow - Save/browser refresh; clearing the value restores the active duration - branch. Collapsed and expanded graph exports remain identical. Evidence - is preserved under - `test-review/frontend-pipeline-e2e-2026-08-26/ltx2-conditional-cluster-ui/`. -- [x] Add a revision-bound LTX-2 Community License acknowledgement before any - native LTX-2 qualification. The exact reviewed revision is - `47da56e2ad66ce4125a9922b4a8826bf407f9d0a`; its published terms include an - annual-revenue threshold, redistribution duties, and use restrictions. - The visible install and run paths now share one revision-bound policy - key; focused unit and visible mocked-browser tests prove cancel versus - explicit-confirm behavior. User/company acknowledgement has not been - inferred from another model's terms. On 2026-08-27, the user explicitly - acknowledged this exact revision and confirmed that the planned use is - below the stated annual-revenue threshold or separately authorized. The - fresh native qualification completed through the visible Model Manager - and its revision-bound terms dialog. To make the app's 64 GiB cache-volume - safety reserve fit, the already-qualified - `black-forest-labs/FLUX.1-Canny-dev` local cache was safely evicted - through Model Manager; its saved workflows and canonical definitions - remain available and the weights are recoverable by exact redownload. - The exact 45-file, 92,074,139,514-byte snapshot installed successfully. - The visible Cluster test then passed insert, expand an official internal - block, edit prompt and execution parameters, Save, browser refresh, - visible value comparison, collapsed/expanded graph equality, and native - video-with-audio generation. Task `Vz4mWmZ3gAii` completed in 118.4 seconds - on the clean ROCm worker and produced an H.264/AAC MP4 with SHA-256 - `8d6c532f5f275c58def492305db4084806904ccf66b4532fc7c4e1ddea1ea965`. - The run also fixed two large-Cluster UI races: offscreen node focus now - temporarily suspends React Flow virtualization until `fitView()` finishes, - and Diffusers Cluster insertion awaits the reviewed Modular Conditional - companion so its hierarchy cannot rematerialize during an edit. A first - retry on a long-lived worker confirmed that cached weights from another - large model can cause a global OOM; the exact same reviewed parameters pass - after clearing those cross-model allocations with a clean worker. Evidence - is under `test-review/frontend-pipeline-e2e-2026-08-26/ltx2-native-licensed/`. - Keep both equivalent-standard LTX-2 admissions and any future native - admissions unpublished until the fresh generated output is reviewed. -- [x] Complete the strengthened lifecycle for all nine previously outstanding - SDXL admissions: ControlNet Union image-to-image/inpainting; IP-Adapter - image-to-image/inpainting; IP-Adapter + ControlNet text/image/inpainting; - and IP-Adapter + ControlNet Union text/image. Every route now passes - visible insertion, nondefault parameter edits, workflow Save, browser - refresh with exact equality, official conditional hierarchy expansion, - collapsed/expanded API-graph equality, exact-revision preparation, and - real generation. The browser qualification found and fixed one shared - schema defect: a connected IP-Adapter guider could transiently inject an - undefined local `guidance_scale` during expansion. Connected guiders now - suppress that local callback while retaining the reviewed upstream CFG - component. PNG/WebP assets are preserved under - `test-review/frontend-pipeline-e2e-2026-08-27/sdxl-remaining-clusters/`. - The evidence writer now keeps one JSON receipt per route rather than - overwriting the previous selection; the consolidated receipt rerun is - still required before promotion. All outputs remain qualification-only - pending output approval. -- [x] Complete the strengthened LTX-2 image-to-video lifecycle. Conditioned - generation exposed one exact upstream runtime prerequisite: the official - pipeline applies H.264 CRF recompression and imports PyAV. MoDiff now - declares pinned PyAV 18.1.0 in its managed composite media-codec runtime - and requires it only for LTX-2 image/reference modes. The client now also - expands exact reviewed `satisfiesProfiles` aliases, so the active - Transformers + OpenCV + PyAV composite safely satisfies the pinned - Transformers-main requirement without weakening digest checks. The real - frontend test passed insert, internal prompt edit, nondefault dimensions, - steps, frames and FPS, Save, browser refresh with exact equality, - collapsed/expanded graph parity, and real video-with-audio generation in - 6.6 minutes. Task `tKvuz8NMqo4i` produced a 166,124-byte MP4 with media - hash - `sha256:bytes:909049257dbd79b177f8f760eeb48bf55211fe56f2832c38875609563d13e945`. - Evidence is under - `test-review/frontend-pipeline-e2e-2026-08-27/ltx2-native-licensed/`. -- [x] Complete the strengthened **LTX — Text To Video** lifecycle. Hugging Face - Hub 1.28 correctly rejected the former partial local snapshot because the - official `LTXConditionPipeline` addresses 12 nested - `vae/text_encoder`/`vae/transformer` aliases in addition to the root - components. MoDiff's exact reviewed selection now includes all 34 paths; - Model Manager repaired them without duplicate byte transfer because the - aliases resolve to the same immutable blobs. Task `5cpBN6QNPIan` - completed after Save/refresh and collapsed/expanded parity, producing - media hash - `sha256:bytes:200460890584e7abacc82e5052379503b0be09e937349288d4686e3fe18209bf`. -- [x] Complete the strengthened **LTX — Image To Video** lifecycle. Expanding - finalized dynamic execution fields exposed a real reset race: mounting a - reviewed field replayed its initial action and could change the sealed - mode from `image_to_video` to `text_to_video`. Materialized execution - fields now carry `suppressInitialFieldAction`; signal relay remains live, - but already-finalized on-change/on-signal actions are not replayed on - render. Task `_R2l1dv0tmme` passed exact persistence and graph parity and - produced media hash - `sha256:bytes:2331aacca6e3a589a455b5f82f1f1629f6093073dc6b07145d6db54517fed7ca`. - Both LTX receipts and MP4s are under - `test-review/frontend-pipeline-e2e-2026-08-27/remaining-admitted-video-clusters/`. -- [ ] Complete the two remaining admitted routes: **Wan 2.2 — Text To Video** - and **Wan 2.2 Image2 Video — Image To Video**. The strengthened harness - installs each exact snapshot through Model Manager and records one - route-specific persistence, parity, runtime-cleanup, generation and - media-provenance receipt. The exact 43-path, 126,199,294,813-byte Wan 2.2 - text-to-video transfer is active through the visible frontend at revision - `5be7df9619b54f4e2667b2755bc6a756675b5cd7`. Model Manager first rejected it - with only 157 GiB free because the snapshot plus the 64 GiB safety reserve - did not fit; after the repaired frontend eviction reclaimed the completed - LTX-2 local copy, admission passed with 243 GiB free. - -### H6 — prioritized Transformers speech-to-text lifecycle - -- [x] Keep this phase bounded to the requested speech families. The first - reviewed pair is **Whisper Tiny — Speech to Text** and **Whisper Tiny — - Speech Translation** at the exact Apache-2.0 revision - `169d4a4341b33bc18d8881c4b69c2e104e1cc0af`; broader text, vision, and - multimodal Transformers Cluster expansion remains deferred. -- [x] Implement the four-node reusable speech graph (load audio, load exact - processor/model, transcribe or translate, and preview structured text) - and bind task, language, timestamp mode, chunk length, stride, dtype, - device, and immutable revision into the expanded User/Cluster execution - nodes. This follows the official Transformers Whisper task-token contract: - `transcribe` retains the source language and `translate` produces English. - See . -- [x] Prove both modes generate structured text from the pinned 5.12-second - LibriSpeech fixture through the visible frontend. The technical outputs - already identify the spoken sentence and retain segment timestamps. -- [x] Rerun the strengthened lifecycle proof after the Diffusers tranche: - change nondefault audio/task/language/timestamp/chunk/stride/runtime - values, Save, refresh, compare the exact embedded instance, expand all - four nodes, compare collapsed/expanded exports, and execute both - transcription and translation again. The visible frontend proof passed - against the real backend with transcription task `9J5O3M2Ik1Mj` and - translation task `Qb3IDT81WXgi`; both decoded the pinned 5.12-second - fixture with segment timestamps. Evidence is preserved under - `test-review/frontend-pipeline-e2e-2026-08-26/transformers-asr/`. -- [x] Add one additional reviewed CTC speech family only after the Whisper - lifecycle remains green in the final release regression. Do not broaden - unrelated Transformers families in this phase. **Wav2Vec2 Base 960h — - Speech to Text** is now bound to exact revision - `22aad52d435eb6dbaf354bdad9b0da84ce7d6156`. Its visible frontend proof - repaired and activated the optional Transformers runtime through the app, - cached the exact repository, preserved the instance through Save/browser - refresh, expanded all four nodes, proved collapsed/expanded parity, and - executed task `A23zwjxbPlqo` with word timestamps. The CTC output and - screenshots are preserved in the same Transformers ASR evidence folder. - -The FLUX.2 qualification tasks are `06_fORfy8Bw2` (text-to-image) and -`VTL8AsHT5m8b` (edit image). Both used -`black-forest-labs/FLUX.2-klein-4B@e7b7dc27f91deacad38e78976d1f2b499d76a294`, -256 x 256 output, two inference steps, bfloat16, and model CPU offload. The -first generated WebP has SHA-256 -`407f58958b2f6d23556f08f3088db866e106252c6145b6270643d872cd5fdcac`; -the edit generated WebP has SHA-256 -`d569d38cb4431bc4642c090f95fb86fa4b3557be9a461eeb1ff61f24ad96cff8`. -The combined receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/flux2-klein-clusters/`. -They remain technical qualification outputs with `showcaseApproved: false` and -do not change any publication flag. - -The FLUX.1 qualification tasks are `juZGQKFp89Km` (FLUX text-to-image), -`JsLeZkX47tAH` (FLUX image-to-image), `vQ5bUUPMtCLh` (Kontext text-to-image), -and `xvaI4_KbTbVr` (Kontext edit image). They used the immutable revisions -`black-forest-labs/FLUX.1-dev@3de623fc3c33e44ffbe2bad470d0f45bccf2eb21` -and -`black-forest-labs/FLUX.1-Kontext-dev@24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d`, -two inference steps, bfloat16, and model CPU offload. The generated WebP -SHA-256 values are `878feaedfebe7f83bc53b6d867d03c03efff7d4e40dc21a646ab8086dc35569c`, -`9889d2963805309c0e7db9aa1f2a7eae180ea70fe700a59f46796651f02bbf36`, -`d68c11ac395fcf861c1d0fc4508601bd47d6783bdf117fd8580a6e16c644107a`, -and `0a4fd280fc4e7f772021ad04c62ae54617031ca36ef0d988b3d4c7ce10df3261` -in the same order. The combined receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/flux1-clusters/`. These are -technical qualification outputs with `showcaseApproved: false`; all four -definitions' public promotion flags remain unchanged pending explicit output -approval. - -The Z-Image qualification tasks are `qBbM662AL1dj` (text-to-image) and -`bdkPTwcDGQjR` (image-to-image). Both used -`Tongyi-MAI/Z-Image-Turbo@f332072aa78be7aecdf3ee76d5c247082da564a6`, -256 x 256 output, two inference steps, bfloat16, and model CPU offload. The -generated WebP SHA-256 values are -`12decbe27650d19aca2dfbf819b484f079865dbe71629c43ea0dcd10a1dd0b68` -and `8a15b4d31bc89e5003f5b019f1c091f37487157b733a63e556ad6f928d10498b`. -The combined receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/z-image-clusters/`. These remain -technical qualification outputs with `showcaseApproved: false`; neither -definition's public promotion flags changed. - -The Qwen Image Edit qualification task is `5vMmdX_Dt3E4`. It used -`Qwen/Qwen-Image-Edit@ac7f9318f633fc4b5778c59367c8128225f1e3de`, two -inference steps, bfloat16, and model CPU offload. The generated 1024 x 1024 -WebP has SHA-256 -`ff3eb7aff49f6ec233df9fc79801732eaa18333b0121c31ebc824dbaa8e5008f`. -The receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/qwen-image-edit-cluster/`. -This remains a technical qualification output with `showcaseApproved: false`; -the definition's public promotion flags remain unchanged. - -The Qwen Image image-to-image qualification task is `-0yiE5nwJYFp`. It used -`Qwen/Qwen-Image-2512@25468b98e3276ca6700de15c6628e51b7de54a26`, 256 x -256 output, two inference steps, strength 0.65, bfloat16, and model CPU -offload. The generated WebP has SHA-256 -`8e720fd5d6434e9cf16fc868f8cc1a05fdd76e15298b737ccc57230e961ac399`. -The receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/qwen-image-to-image-cluster/`. -The output is technically valid but not showcase quality; publication remains -unchanged with `showcaseApproved: false`. - -The Qwen Image inpainting qualification task is `kYyG7O1Qfhe3`. It used the -same pinned Qwen Image revision with a reviewed 1024px source/mask pair, saved -256 x 256 qualification dimensions, two inference steps, strength 0.8, -bfloat16, and model CPU offload. The generated WebP has SHA-256 -`41fc729b3c72544b44c997126e0786e3c8d5714506d4dc87600bbb039729b2d4`. -The receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/qwen-image-inpaint-cluster/`. -The mask path executed correctly, but the aggressive qualification settings do -not produce showcase quality; publication remains unchanged. - -The Qwen Image Edit inpainting qualification task is `d8CJn9ZGgH4Z`. It used -`Qwen/Qwen-Image-Edit@ac7f9318f633fc4b5778c59367c8128225f1e3de`, the -reviewed source/mask pair, two inference steps, strength 0.8, bfloat16, and -model CPU offload. The generated 1024 x 1024 WebP has SHA-256 -`899c09030e89e8865d79c0d8a1b8712ba8f17b9a17f15b417605860bb2975538`. -The receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/qwen-image-edit-inpaint-cluster/`. -Visual inspection confirms that generation changed the masked object region -while preserving the surrounding room. This remains technical evidence with -`showcaseApproved: false`; publication flags remain unchanged. - -The Qwen Image Edit Plus qualification tasks are `cDXJmv8V08SD` (single-image -edit) and `J33TZedDTeJy` (multi-reference edit). Both used -`Qwen/Qwen-Image-Edit-2511@6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9`, -two inference steps, bfloat16, and model CPU offload. The generated WebP -SHA-256 values are -`ee56aca1068d563ae023dacb45ff1cbe62878665e53f1c02c7b28aad5afffead` -and `d43ca27b596d2c0171c4e84a633407dd5afca68f84daada7d0fa255825e585fa`. -The receipts and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/qwen-image-edit-plus-cluster/`. -Both are valid technical executions; the two-step multi-reference result is not -semantically strong enough for showcase use. Both admissions remain unpublished -with `showcaseApproved: false`. - -The Qwen Image Layered qualification task is `2LnilNHbwNy_`. It used -`Qwen/Qwen-Image-Layered@8f0ca708dfff6ba1dd5f2d85d78f8c108a040bcf`, -three requested layers at the recommended 640px source-resolution bucket, two -inference steps, bfloat16, and model CPU offload. The three generated RGBA WebPs -have SHA-256 values -`094df5951f7bb0b61211811948da73342d2f800879f6dcc8a862a84316fb47ca`, -`2ff6ca87fe1584e822b6eaea88da0d69969f8ce2fe3f87c0c68cd058ef4d0cf8`, -and `4d36d9cd518a048ca793c6e576efbff318a6921d703d3c29d7f6dff5c16bd20a`. -The receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/qwen-image-layered-cluster/`. -All three files retain alpha, but two-step decomposition quality is intentionally -technical rather than showcase-ready; publication remains unchanged. - -The Qwen Image ControlNet qualification tasks are `dKMxhs33YBWr` -(text-to-image), `J06sUfHnFDc9` (image-to-image), and `PC4kdbNmLUYN` -(inpainting). All three used -`Qwen/Qwen-Image-2512@25468b98e3276ca6700de15c6628e51b7de54a26` with -`InstantX/Qwen-Image-ControlNet-Union@b13036f066d6dee7c20513e263d3d673055e9de8`, -256 x 256 output, two inference steps, a 0.9 conditioning scale, control range -0.1–0.85, maximum sequence length 512, bfloat16, and model CPU offload. The -generated WebP SHA-256 values are -`ec6b1fb45a8db3084318581eb09f66b9496b4dc8261939`, -`9f0482a6f2f3ca5f3727e4d24fe6a6c7df7e615653a4c9651822064a3c50546f`, -and `a87b2a447b9592155f2438bbe820b24187566cad82583be90e0ade3375310ee4` -in the same order. The combined receipt and review assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/qwen-image-controlnet-clusters/`. -The browser test passed in 2.1 minutes and explicitly asserted that every saved -control value reached the corresponding expanded execution node. These are -technical low-step outputs, not showcase assets; all publication flags remain -unchanged with `showcaseApproved: false`. - -The Wan 2.1 Modular text-to-video qualification task is `fWAsCv3UaxfW`. It -used -`Wan-AI/Wan2.1-T2V-1.3B-Diffusers@0fad780a534b6463e45facd96134c9f345acfa5b`, -256 x 256 output, nine frames at 8 fps, two inference steps, maximum sequence -length 256, bfloat16, and model CPU offload. The generated H.264 MP4 contains -all nine requested frames, is 1.13 seconds long, and has SHA-256 -`029df348e783756dc25e077f0ed9e54eb4b13e31c8db583257716ab544373835`. -The receipt, MP4, all extracted frames, and contact sheet are under -`test-review/frontend-pipeline-e2e-2026-08-26/wan-21-t2v-cluster/`. The -browser test passed in 1.2 minutes. The two-step output is useful technical -evidence but is not showcase quality; publication remains unchanged with -`showcaseApproved: false`. - -The Wan 2.1 Modular first/last-frame-to-video qualification task is -`n4ArxD3deeaZ`. It used -`Wan-AI/Wan2.1-FLF2V-14B-720P-diffusers@17c30769b1e0b5dcaa1799b117bf20a9c31f59d7`, -distinct 1024px source endpoints bound through the two separate expanded image -loader nodes, 256 x 256 output, nine frames at 8 fps, two inference steps, -maximum sequence length 256, bfloat16, and model CPU offload. The generated -H.264/yuv420p MP4 contains all nine requested frames, is 1.13 seconds long, and -has SHA-256 -`bafd5c5ebe7b5f09aec4daa8eb38faff4d93dded4d26ce5d9666d4bd76f5a820`. -The browser test passed in 2.3 minutes. The receipt, MP4, all extracted frames, -contact sheet, and browser screenshot are under -`test-review/frontend-pipeline-e2e-2026-08-26/wan-21-flf-cluster/`. The -two-step endpoint interpolation is technically valid but intentionally not a -showcase asset; publication remains unchanged with `showcaseApproved: false`. - -The Wan 2.1 Modular single-image-to-video qualification task is -`PYvxW-qjMTBN`. It used -`Wan-AI/Wan2.1-I2V-14B-480P-Diffusers@b184e23a8a16b20f108f727c902e769e873ffc73`, -the saved source-image path, 256 x 256 output, nine frames at 8 fps, two -inference steps, maximum sequence length 256, bfloat16, and model CPU offload. -The generated H.264/yuv420p MP4 contains all nine requested frames, is 1.13 -seconds long, and has SHA-256 -`b4c5e35101a23216075019292734e6a8b8f3091e68c2e293eed53c68a089d3e1`. -The browser test passed in 2.4 minutes. Its exact receipt, MP4, all extracted -frames, contact sheet, Model Manager readiness screenshot, and generated-result -screenshot are under -`test-review/frontend-pipeline-e2e-2026-08-26/wan-21-i2v-cluster/`. This -two-step output is technical evidence rather than a showcase asset; -publication remains unchanged with `showcaseApproved: false`. - -The ERNIE Image Turbo qualification task is `nttsH7oV8Esf`. It used -`baidu/ERNIE-Image-Turbo@bc68c81e2a1730a394d5fc9fae70713dee940140`, -the repository prompt enhancer, 1024 x 1024 output, one technical inference -step, guidance 1, bfloat16, model CPU offload, and seed 54001. The generated -WebP has SHA-256 -`8d9b3f9344f916d4acfbbfcbaef89ad094399fa3d4cb721d9b9a6281b3043c08`. -The browser test passed in 55.9 minutes including the 31.65 GB visible download; -model execution itself loaded all seven pipeline components and completed its -denoise step in about 39 seconds. The receipt, generated WebP, generated-result -screenshot, download-start screenshot, and Model Manager Ready screenshot are -under `test-review/frontend-pipeline-e2e-2026-08-26/ernie-image-cluster/`. -This remains qualification evidence with `showcaseApproved: false`; no public -promotion flag changed. - -## 2026-08-28 admitted-route and shared-runtime closure - -The priority order for this tranche is now satisfied: shared runtime/resource -handling first, the strengthened admitted Diffusers routes second, and -contract-only family expansion only after those gates. - -### Shared runtime/resource handling - -- [x] Model Manager cache deletion now releases app-owned node/model/component - caches and trims the accelerator allocator before deleting immutable Hub - snapshots. Its response contains a validated `runtimeRelease` receipt. -- [x] The visible Model Manager flow deleted the pinned Wan 2.2 text-to-video - snapshot and reclaimed `126199197011` bytes while preserving saved - workflows and canonical definitions. -- [x] The same flow deleted the pinned Wan 2.2 image-to-video snapshot and - reclaimed `126202473720` bytes. It released six nodes and one model, - trimmed the allocator, reported no cleanup errors, left zero deleted open - file descriptors, and returned the machine to about 243 GiB free. -- [x] Synchronous managed-graph signal requests now dispatch directly on the - websocket event loop instead of waiting behind ordinary progress and - dynamic-definition broadcasts. The future is created and cleaned up on - its owning loop, and the browser-reconciliation wait remains bounded at - 60 seconds. This fixes the repeatable third-workflow SDXL Guider timeout - after Save/refresh transitions. -- [x] Focused signal coverage passes (`22` server-security tests plus `29` - subtests; `18` Modular Guider/Scheduler/Layers tests plus `31` subtests). - -The turnover receipts and screenshots are under -`test-review/frontend-pipeline-e2e-2026-08-27/storage-turnover/`, including the -definition-specific files -`model-manager-eviction-wan22-text-to-video-result.json` and -`model-manager-eviction-wan22-image-to-video-result.json`. - -### Strengthened admitted Diffusers lifecycle - -- [x] All `14/14` routes in the remaining admitted tranche now pass the exact - frontend lifecycle: insert Cluster Node, edit nondefault parameters, - Save, browser refresh, compare persisted parameters, expand, compare the - collapsed/expanded graph, execute, and retain a hashed generated asset. -- [x] All nine SDXL combinations pass together as one serial browser test in - 7.2 minutes after the signal-lifecycle fix. The exact task IDs are - `1oo2yRmuVQRb`, `zu_XOTPQQx5O`, `Ohta6ulgLOeZ`, `7A1obEnWD1-x`, - `ElhHerIrgqtM`, `82tENa1h78ri`, `R_0Q3_RrKIMR`, `qw5dQ0O2oap0`, and - `14C5w2a19hbX`, in the scenario order stored by the combined receipt. -- [x] LTX text-to-video, LTX image-to-video, and LTX-2 image-to-video retain - their strengthened frontend lifecycle evidence from tasks - `5cpBN6QNPIan`, `_R2l1dv0tmme`, and `tKvuz8NMqo4i`. -- [x] Wan 2.2 text-to-video passed task `GgQplt9m5jUl` at pinned revision - `5be7df9619b54f4e2667b2755bc6a756675b5cd7`; its generated MP4 hash is - `sha256:bytes:36cbc4f04c29fa129d8c21fc7c24dadb75ca46cab34958b83c660d75b551f44b`. -- [x] Wan 2.2 image-to-video passed task `NOWutT3Wu4jJ` at pinned revision - `596658fd9ca6b7b71d5057529bbf319ecbc61d74`; before/after parameter - snapshots are identical, graph equivalence is true, and its generated - MP4 hash is - `sha256:bytes:e37f9b549665887cbdb724ae2bef3bfe2467062afc0502d63d7e346f61d3d93b`. - -The SDXL combined receipt and nine generated assets are under -`test-review/frontend-pipeline-e2e-2026-08-27/sdxl-remaining-clusters/`. Wan -receipts and MP4s are under -`test-review/frontend-pipeline-e2e-2026-08-27/remaining-admitted-video-clusters/`. - -### Post-change release gates - -- [x] Full backend after the Helios tranche: `2219 passed`, `53 skipped`, - `6706 subtests passed`. -- [x] Complete client `npm run check`: lock/license/format/lint/typecheck/style, - unit tests, production build, and bundle budget all pass. -- [x] Complete mocked Studio browser suite: `114/114 passed`. -- [x] The live SDXL nine-route serial browser suite passes after a fresh backend - restart, proving the multi-workflow signal fix under the original failure - sequence. - -### Remaining after admitted-route closure - -- [ ] Keep all new technical outputs unpublished until showcase/output approval - and definition-specific promotion receipts exist; `executable`, - `liveProof`, and `autoEligible` public flags remain unchanged. -- [ ] Qualify the remaining `30` contract-only workflows across the `11` - documented image, video, and multimodal families, handling each exact - artifact/license/ROCm gate before promotion. -- [ ] Generate improved showcase-quality assets after implementation breadth is - complete; current low-step outputs are qualification evidence only. - -## 2026-08-28 contract-only expansion — LTX-2 condition tranche - -- [x] Reviewed the current official Diffusers LTX-2 API and pinned source before - implementation. `LTX2AutoBlocks` selects `condition` from `conditions + - prompt`; its exact block order is prompt enhancement, text encoding, - duration, condition encoding, denoising, and condition-aware decoding. -- [x] Promoted `LTX2ModularPipeline:condition` through the exact pinned - `LTX2ConditionPipeline` equivalent route without changing the upstream - block hierarchy or publishing a split-block execution claim. -- [x] Added a backend-owned `conditionImages -> conditions` adapter contract. - The collapsed Cluster renders it as a real multi-image picker, persists - the ordered paths in the workflow instance, and materializes them through - `modules.Image.Load`; the generator converts the images to official - `LTX2VideoCondition` instances and distributes them from the first through - last generated frame. Condition strength remains editable and durable in - the expanded execution graph. -- [x] Added the exact Studio spec, task-template media contract, immutable base - artifact/profile seal, static admission, and client media-control - projection. Public execution, live-proof, Auto, and showcase flags remain - disabled. -- [x] Focused verification passes: `117` backend tests plus `1645` subtests; - Hugging Face Cluster client suite `28/28`; client TypeScript build passes. -- [ ] Run the strengthened visible lifecycle and real generation after the - exact LTX-2 snapshot has been restored through Model Manager. Preserve the - receipt, MP4, extracted frames, contact sheet, and screenshots under the - contract-only review directory. -- [x] Implement `LTX2ModularPipeline:in_context` as a distinct route. Official - Diffusers requires `LTX2ReferenceCondition` values and an IC-LoRA loaded - on `LTX2InContextPipeline`; ordinary condition execution must never be - relabeled as in-context. The reviewed first candidate is - `Lightricks/LTX-2-19b-IC-LoRA-Canny-Control@28be96236294914042a1605f37e3f7812b45f43f` - with weight `ltx-2-19b-ic-lora-canny-control.safetensors` (654,465,256 - bytes). The exact loader, reference-video preprocessing graph, bindings, - task contract, static admission, and synchronized video/audio executor - are implemented. Fixture execution proves both immutable snapshots, - named safe LoRA loading, reference-condition construction, frame-count - propagation, downscale/attention controls, and video/audio output. -- [x] The expanded focused regression passes: `269` backend tests, `14` - expected skips, and `1226` subtests; the Hugging Face Cluster client suite - passes `28/28`, and client TypeScript compilation passes. -- [ ] Run its strengthened visible lifecycle and real generation after the - derivative revision's license is acknowledged and the exact artifacts - are installed. This is a legal/artifact gate, not an implementation - substitute. - -The remaining contract-only count after the LTX-2 tranche was `43` workflows. All four LTX-2 -Modular workflows have reviewed equivalent standard routes; the two newly -added routes remain unpublished until their visible lifecycle evidence and -output review are complete. - -## 2026-08-28 contract-only expansion — FLUX.2 dev tranche - -- [x] Reviewed the current official Diffusers FLUX.2 pipeline API, the pinned - `Flux2AutoBlocks` source, and the immutable Hub repository before - implementation. The two upstream workflows overlap substantially: - `text2image` and `image_conditioned` share the text encoder, VAE path, - denoiser, and decoder; the latter activates image preprocessing/encoding - and image-latent preparation. -- [x] Added exact equivalent routes for - `Flux2ModularPipeline:text2image` and - `Flux2ModularPipeline:image_conditioned` through the official - `Flux2Pipeline`. The image-conditioned Cluster keeps one-or-more ordered - references and maps to `multi_image_reference_edit`; it is not reduced - to a one-image-only route. -- [x] Pinned `black-forest-labs/FLUX.2-dev` at - `26afe3a78bb242c0a8bb181dcc8937bb16e5c66c`, recorded the exact 35-file - component closure (`112,823,045,100` bytes), added the standard generic - loader/generate/edit contracts, resource envelope, task contracts, - equivalent Cluster specs, and static admissions. -- [x] Removed the stale duplicate “contract-only” publication for both the - now-closed LTX-2 and FLUX.2 families. The pinned upstream ledger now - classifies their modular classes as reviewed equivalents and their exact - standard executors as executable graph surfaces; public/live-proof flags - remain disabled. -- [x] Combined focused verification passes: `394` backend tests, `18` expected - skips, and `1655` subtests. This includes generic FLUX.2 text and ordered - multi-reference fixture execution plus the full LTX-2 video/audio slice. -- [x] Fresh-process verification passes after restarting the complete backend: - the live API exposes `240` Studio execution specs and `102` Hugging Face - definitions with `62/62` static admissions accepted. Both FLUX.2 - definitions resolve to their exact equivalent-standard modes, neither - FLUX.2 nor LTX-2 is duplicated as an experimental capability, the client - Hugging Face library suite passes `28/28`, and TypeScript compilation is - clean. -- [ ] Obtain exact FLUX.2 license acceptance for the pinned revision before - downloading the gated 112.8 GB closure. Then run the strengthened visible - lifecycle for both Clusters and preserve generated assets/receipts. - -The remaining contract-only count is now `41` workflows across `15` families. - -## 2026-08-28 contract-only expansion — Anima tranche - -- [x] Reviewed the official Anima Modular Diffusers source, tests, conversion - script, immutable Hub model index, and license before implementation. The - authoritative workflows overlap substantially: `text2image` is - `text_encoder -> denoise -> decode`; `img2img` reuses those stages and - inserts `vae_encoder`, plus the image-aware denoise inputs. -- [x] Added native package-owned whole-workflow execution for both official - routes through `AnimaModularPipeline` and `AnimaAutoBlocks`. The graph - uses sealed `PipelineState` hand-offs between reviewed text encode, image - encode, denoise, and image decode nodes; it does not relabel a standard - full-pipeline call as split Modular execution. -- [x] Pinned - `circlestone-labs/Anima-Base-v1.0-Diffusers@073c3a9db359c31ad0e8aa268d15775473c2176c`, - sealed its 16-file selected closure (`5,642,032,794` bytes), four - safetensors weight digests (`5,628,156,702` bytes), Modular index hash, - seven component identities, and both official workflow/action maps in - `data/anima-artifact-review.json`. -- [x] Added the exact Studio profiles, task contracts, static admissions, - resource limits, loader registration, model artifact catalog entry, and - upstream coverage classification. The live backend now publishes `242` - Studio specs, `102` Cluster definitions, `505` upstream block contracts, - and both Anima routes at the exact revision. -- [x] Added a single revision-bound frontend acknowledgement used by Model - Manager and Studio Run. The reviewed license limits model/derivative use - to non-commercial purposes, permits commercial output use subject to its - restrictions, and prohibits using outputs to train a competing model; - the UI summary preserves that distinction. -- [x] Fixed live client contract gaps exposed by the fresh browser: generic - whole-workflow binding sources, LTX-2 `in_context_to_video` mode and - execution roles, and the `conditionImages` task-media field. -- [x] Focused backend verification passes: `136 passed`, `7` expected skips, - and `1067` subtests. Client TypeScript compilation, the `28/28` Cluster - library suite, the full live catalog insertion test, and the new Anima - insert/expand/edit/Save/refresh/collapsed-expanded materialization-parity - browser test all pass. -- [ ] Download and run the exact model through Model Manager only after the - user acknowledges this exact CircleStone license/revision. Then retain - the generated image, runtime receipt, and screenshots and request output - approval before changing `executable`, `liveProof`, or `autoEligible`. - -Primary implementation references: - -- `https://github.com/huggingface/diffusers/blob/2f7e0154a9db246e95c9ede43edba7db5b130805/src/diffusers/modular_pipelines/anima/modular_blocks_anima.py` -- `https://github.com/huggingface/diffusers/blob/2f7e0154a9db246e95c9ede43edba7db5b130805/tests/modular_pipelines/anima/test_modular_pipeline_anima.py` -- `https://huggingface.co/circlestone-labs/Anima-Base-v1.0-Diffusers/tree/073c3a9db359c31ad0e8aa268d15775473c2176c` -- `https://huggingface.co/circlestone-labs/Anima-Base-v1.0-Diffusers/blob/073c3a9db359c31ad0e8aa268d15775473c2176c/LICENSE.md` - -The remaining contract-only count is now `39` workflows across `14` families. - -## 2026-08-28 contract-only expansion — Helios tranche - -- [x] Reviewed the three official pinned Modular Diffusers implementations, - their shared encoders/denoisers/decoders, immutable Hub repositories, - model indexes, selected component closures, and Apache-2.0 metadata - before implementing their graph routes. -- [x] Added native package-owned execution for all nine official workflows: - text-to-video, image-to-video, and video-to-video for - `HeliosModularPipeline`, `HeliosPyramidModularPipeline`, and - `HeliosPyramidDistilledModularPipeline`. The routes share the reviewed - text encoder, conditional VAE encoder, denoiser, and video decoder nodes; - pyramid stage schedules and distilled guidance behavior remain - pipeline-specific. -- [x] Added exact revision-pinned artifact records for - `BestWishYsh/Helios-Base@5c50b6bc90eae9bd815d2a50b0c9877e3fd2cf88`, - `BestWishYsh/Helios-Mid@477c55427ec0ea774bdebd0fbe736313cfc5a312`, - and - `BestWishYsh/Helios-Distilled@b991c0379a018f4de3227d95468237f56066f5bb`. - Each selected closure has 24 files and 80,481,086,028 bytes of model - weights; exact selected-closure sizes are recorded in - `data/helios-artifact-review.json`. -- [x] Added the nine exact Studio execution specs, task/media contracts, - component-revision propagation, static admissions, resource/offload - profiles, frontend capability profiles, graph roles, and managed-control - policy. All `65/65` reviewed Diffusers candidates now pass static graph - admission. -- [x] Backend node/fixture and ledger verification passes: `110` tests, one - expected skip, and `981` subtests. The fixtures exercise base, - pyramid-stage, distilled, image-conditioned, video-conditioned, and - video-decoding behavior without substituting a generic full-pipeline - executor. -- [x] Complete the fresh-backend visible browser proof for Helios Pyramid - Distilled: insert, expand, edit prompt and generation parameters, Save, - refresh, compare the exact overrides, prove collapsed/expanded canonical - materialization parity, and verify the compiled native action sequence. - The proof passes against a freshly restarted backend and preserves its - screenshots under the dedicated Helios review-assets directory. -- [ ] Run remote heavy-hardware generation and preserve the video, runtime - receipt, frames, contact sheet, and screenshots before enabling public - executable/live-proof/Auto flags. Local real generation is not claimed: - each selected closure is about 80.5 GB and still requires a qualified - memory/offload environment. - -Primary implementation references: - -- `https://github.com/huggingface/diffusers/blob/2f7e0154a9db246e95c9ede43edba7db5b130805/src/diffusers/modular_pipelines/helios/modular_blocks_helios.py` -- `https://github.com/huggingface/diffusers/blob/2f7e0154a9db246e95c9ede43edba7db5b130805/src/diffusers/modular_pipelines/helios/modular_blocks_helios_pyramid.py` -- `https://github.com/huggingface/diffusers/blob/2f7e0154a9db246e95c9ede43edba7db5b130805/src/diffusers/modular_pipelines/helios/modular_blocks_helios_pyramid_distilled.py` -- `https://huggingface.co/BestWishYsh/Helios-Base/tree/5c50b6bc90eae9bd815d2a50b0c9877e3fd2cf88` -- `https://huggingface.co/BestWishYsh/Helios-Mid/tree/477c55427ec0ea774bdebd0fbe736313cfc5a312` -- `https://huggingface.co/BestWishYsh/Helios-Distilled/tree/b991c0379a018f4de3227d95468237f56066f5bb` - -At this historical checkpoint, the remaining contract-only count was `30` -workflows across `11` families. The Wan Animate 2 tranche below reduces the -current count to `28` workflows across `9` families. - -The fresh-browser Helios lifecycle evidence is retained at: - -- `/home/sayak/MoDiff/review-assets/diffusers-contract-expansion/helios-2026-08-28/diffusers-helios-structural-cluster-expanded.png` -- `/home/sayak/MoDiff/review-assets/diffusers-contract-expansion/helios-2026-08-28/diffusers-helios-cluster-save-refresh.png` - -The final clean-process smoke also reran the existing Anima lifecycle alongside -Helios; both tests passed (`2/2`) against the newly restarted backend. - -## Next contract-only tranche — Wan Animate 2 - -- [x] Confirmed against the pinned official Diffusers source that base and - distilled each expose one six-stage Modular workflow: - `text_encoder -> image_encoder -> video_encoder -> vae_encoder -> denoise - -> decode`. Both share the same semantic stages; the distilled denoise - inner loop and scheduler/default-step contract remain distinct. -- [x] Reconfirmed both immutable public repositories and their existing exact - artifact reviews. The base revision is - `7d48412d7b903ff3a89f4f5a960d99e1899605a1`; the distilled revision is - `59e4141466bcb1bf9733eca1bc78be6891c9fbdf`. Each selected weight inventory - is 45,920,934,868 bytes. -- [x] Replace mutable/null nested component descriptors with the exact selected - top-level revision during loading, while rejecting cross-repository - descriptors and remote Python. Dedicated tests prove that both the null - and `refs/pr/*` same-repository descriptors resolve to the exact reviewed - 40-character top-level commit. -- [x] Add the six sealed state-handoff actions, exact image/driving-video - bindings, base/distilled denoise controls, decode/export graph, task - contract, capability/resource profiles, static admissions, and fixture - execution. Base preserves the official 40-step/guidance-3 recipe; - Distilled is presented with the official 10-step/guidance-1 recipe while - still allowing an explicit persisted step override. -- [x] Run the same insert/edit/Save/refresh/materialization-parity browser - lifecycle. Keep real execution, public flags, Gallery, and Auto disabled - until compiled Flex Attention is qualified on suitable remote hardware - and the generated video is reviewed. - -The fresh visible-frontend proof uses the Distilled Cluster and verifies -library insertion, all six official stage paths, an internal prompt edit, -reference-prompt/segment/previous-conditioning-frame edits, -width/height/step/FPS/guidance/seed overrides, collapsed/expanded canonical -graph parity, backend workflow Save, browser refresh, and byte-equivalent -restoration. The semantic edits are asserted on the materialized execution -nodes, so the proof rejects a control that merely persists visually without -reaching the official block call. Evidence is retained at: - -- `/home/sayak/MoDiff/MoDiff/data/qualification/local-review/hugging-face-clusters/wan-animate-2-cluster/save-refresh-expanded.png` -- `/home/sayak/MoDiff/MoDiff/data/qualification/local-review/hugging-face-clusters/wan-animate-2-cluster/frontend-persistence-result.json` - -The upstream coverage ledger now classifies `133` Diffusers pipeline symbols -as executable and `9` as contract-only. At the Wan checkpoint, the remaining -structural expansion was `28` workflows across `9` families. The sealed -HunyuanVideo 1.5, Stable Diffusion 3, and Krea 2 adapter tranches below reduce -that implementation queue to `21` workflows across `4` families: Cosmos 3 -Distilled, Cosmos 3 Omni, MiniMax H3, and LTX-2.5. - -Real Wan Animate 2 Distilled generation is now proven through the visible -frontend. Model Manager installed the exact -`Wan-AI/Wan2.2-Animate-2-14B-Distilled-Diffusers@59e4141466bcb1bf9733eca1bc78be6891c9fbdf` -snapshot from the reviewed 30-file, 45,947,569,675-byte request and reports the -cache complete with no missing or corrupt files. A fresh-browser repair also -proved that a sealed optional-runtime integrity failure is recoverable through -Setup, activates the replacement overlay, and survives the controlled backend -restart. Model Manager now reconciles authoritative background-download state -while open, so a browser that misses the final long-request transition cannot -remain indefinitely at `Installing` after the backend has completed. - -The generation proof inserts the Distilled Cluster from the library, changes -the prompt, reference prompt, media, dimensions, segment length, previous-frame -conditioning, step count, FPS, guidance, and seed, saves the backend workflow, -refreshes the browser, and verifies exact restoration. It then materializes the -six official stages plus media load/export nodes and submits the graph through -the frontend. The exact 14B components load with model CPU offload, the required -`transformer.compile_repeated_blocks(fullgraph=False)` path compiles, and one -nine-frame segment executes on the Radeon 8060S. The qualification MP4 is H.264, -336x192, 16 FPS, 9 frames, and 13,590 bytes with SHA-256 -`d63a732af7b80c50f92b4a63a45417b316f6564279be00df7addd87175d773dc`. -This output is a functional qualification artifact, not a showcase-approved -promotion asset. - -Execution also exposed and closed one upstream contract mismatch without -weakening validation: official Wan Animate 2 blocks declare `AutoTokenizer` -and `SchedulerMixin`, while the pinned official Hub indexes serialize the -concrete `T5TokenizerFast` and `FlowMatchEulerDiscreteScheduler`. MoDiff now -admits only those exact repository/component aliases for the base and -Distilled repositories; focused tests accept both official indexes and reject -an alternate tokenizer class. - -The isolated prerequisite smoke is green on this host: PyTorch -`2.9.1+rocm7.2.0.git7e1940d4` compiled and executed a BF16 Flex Attention call -with a compiled block mask on the Radeon 8060S in 5.899 seconds. Its receipt is -retained at -`data/qualification/local-review/hugging-face-clusters/wan-animate-2-distilled-live/flex-attention-smoke.json`. -This proves the kernel/backend capability, but it is not a substitute for the -full 14B model's memory/offload and generated-video proof. - -Completion gates for this tranche: - -- focused backend after the semantic binding additions: `100 passed`, `882` - subtests passed; -- full backend after the required compile policy: `2,225 passed`, `53 skipped`, - `6,726 subtests passed`; -- complete client `npm run check`: formatting, lint, typecheck, unit tests, - production build, and bundle budget all passed; -- strengthened visible live-backend Wan lifecycle: `1/1 passed` after a fresh - app restart; -- required regional-compile loader policy: `64 passed`, `12` subtests passed - across the focused offload and Wan artifact-review suites; -- exact visible Model Manager installation: `1/1 passed` after the real - download, cache verification, optional-runtime repair/activation, and backend - restart; -- exact visible frontend generation: `1/1 passed` in 2.1 minutes, including - insert/edit/Save/refresh/materialization and the real nine-frame model run; -- pinned Wan component-alias validation: `2 passed`, `9` subtests passed with - the sealed Transformers overlay active; -- complete client `npm run check` after download reconciliation and the live - generation fixes: passed, including formatting, lint, typecheck, unit tests, - production build, and bundle budget (`332,596` entry gzip bytes and `591,802` - total gzip bytes); -- full backend release gate after the component-alias change: `2,225 passed`, - `54 skipped`, `6,726` subtests passed in 123.33 seconds; -- the generated upstream/template/Comfy provenance ledgers pass their offline - deterministic validation after the two-symbol promotion. - -Primary implementation references: - -- `https://github.com/huggingface/diffusers/blob/2f7e0154a9db246e95c9ede43edba7db5b130805/src/diffusers/modular_pipelines/wan_animate_2/modular_blocks_wan_animate_2.py` -- `https://github.com/huggingface/diffusers/blob/2f7e0154a9db246e95c9ede43edba7db5b130805/src/diffusers/modular_pipelines/wan_animate_2/modular_blocks_wan_animate_2_distilled.py` -- `https://github.com/huggingface/diffusers/blob/2f7e0154a9db246e95c9ede43edba7db5b130805/src/diffusers/modular_pipelines/wan_animate_2/denoise.py` -- `https://huggingface.co/docs/diffusers/main/en/api/pipelines/wan_animate_2` -- `https://huggingface.co/Wan-AI/Wan2.2-Animate-2-14B-Distilled-Diffusers/commit/ff28d41a9c0c331137691847cc8700593d785593` - -Evidence is retained at: - -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-distilled-live/frontend-install-result.json` -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-distilled-live/frontend-generation-result.json` -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-distilled-live/output-validation.json` -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-distilled-live/wan-animate-2-distilled-generated.mp4` -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-distilled-live/wan-animate-2-distilled-contact-sheet.png` - -Remaining Wan promotion gate: review/approve a showcase-quality output, then -write the definition-specific promotion receipt before enabling public -`executable`, `liveProof`, Gallery, or Auto flags. The bounded one-step output -is deliberately not promoted. - -## Following Wan — HunyuanVideo 1.5 research gate - -- [x] Revalidated the pinned package structure without promoting it: the - `text2video` workflow is `text_encoder -> denoise -> decode`; the - `image2video` workflow is `text_encoder -> vae_encoder -> image_encoder - -> denoise -> decode`. -- [x] Revalidated the two exact public Diffusers artifacts already recorded in - `data/hunyuanvideo-1.5-artifact-review.json`: T2V revision - `286be7ce72277246578a3e3cc2487e95ddae5bcf` and step-distilled I2V revision - `854c04a4c8a53d990b418c7478f0802c0fc8c726`. -- [x] Added package-owned sealed block adapters for the exact official stage - overlap. Text-to-video materializes text encoding, denoising, and decode; - image-to-video preserves the separate VAE and SigLIP image encoders - before its MeanFlow-aware denoiser and decoder. Focused fixture execution - and library-contract coverage pass (`53 passed`, `7 skipped`, `83` - subtests passed) without loading or exposing model weights. -- [x] After the active Wan Animate 2 base Model Manager installation finishes, - restart the backend and run a fresh-browser Hunyuan structural lifecycle: - insert, expand, edit, Save, refresh, and verify exact restoration. The - browser must continue to show no Prepare/Run/download claim for either - Hunyuan route. -- [ ] Do not expose either route as executable or downloadable until the - publisher's conflicting territory clauses receive an explicit legal - disposition. Implementation may prepare sealed adapters and fixtures, - but it must not silently turn a contract-only entry into a runnable - artifact. - -## Wan Animate 2 base live qualification - -- [x] Generalized the visible-frontend Model Manager and generation tests so - the same lifecycle can select either the reviewed base or Distilled - definition without duplicating a weaker test path. -- [x] Install the exact base snapshot - `Wan-AI/Wan2.2-Animate-2-14B-Diffusers@7d48412d7b903ff3a89f4f5a960d99e1899605a1` - through Model Manager and retain its install receipt/screenshots. All 30 - reviewed files are present at the exact commit; the cache contains - `45,947,570,056` bytes of immutable blobs. -- [x] Run the base Cluster through the visible frontend: insert, edit internal - controls, Save, refresh, verify the restored values and materialized - six-stage graph, then generate and validate the qualification MP4. -- [ ] Keep the generated base asset qualification-only until a separate - showcase-quality output is reviewed and a definition-specific promotion - receipt is approved. - -The live base lifecycle passed `1/1` in 1.8 minutes. It restored the exact -parameter/execution overrides after Save and browser refresh, materialized the -reviewed loader, media inputs, four official Modular stages, and video export, -then generated a real nine-frame H.264 MP4. The pinned base repository exposed -one variant-specific concrete component alias not shared by Distilled: -`DPMSolverMultistepScheduler` for base versus -`FlowMatchEulerDiscreteScheduler` for Distilled. Validation now admits each -concrete scheduler only for its exact repository and continues to reject the -opposite scheduler; the focused loader proof passes with `1 passed`, `6` -subtests passed. - -Base evidence is retained at: - -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-base-live/frontend-install-result.json` -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-base-live/frontend-generation-result.json` -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-base-live/wan-animate-2-base-live-generated.mp4` -- `data/qualification/local-review/hugging-face-clusters/wan-animate-2-base-live/generated-frame-00.png` - through `generated-frame-08.png` - -The generated MP4 is 336x192 at 16 FPS, 0.563 seconds, 13,245 bytes, with -SHA-256 `6538b2ab7a07e44839acc24027e9970c3d35fc075d8aa8edafc524a8c805f953`. -It remains qualification-only because the run deliberately used one denoising -step and was not intended as a showcase asset. - -## Stable Diffusion 3 sealed structural tranche - -- [x] Revalidated the pinned official Modular Diffusers source. Text-to-image - uses `text_encoder -> denoise -> decode`; image-to-image adds the - separately inspectable `vae_encoder` before the shared denoise and decode - stages. The VAE and denoise containers retain their official nested - preprocess, timestep, latent-preparation, and loop blocks in the expanded - Cluster hierarchy. -- [x] Added exact package-owned adapters for the three CLIP/T5 prompt encoders, - image preprocessing/VAE encoding, T2I/I2I denoising, CFG guider update, - strength, seed, and PIL decode. The combined Hunyuan/SD3 focused gate is - green (`61 passed`, `7 skipped`, `83` subtests passed). -- [x] After the active Wan base download completes, run the fresh-browser SD3 - insert/expand/edit/Save/refresh proof alongside Hunyuan. SD3 must remain - catalog-only with no Prepare/Run or Model Manager download claim. -- [ ] Do not download or advertise the SD3 Medium artifact until the Stability - noncommercial gate has explicit task-scoped product/legal acceptance. - Existing artifact tests continue to require zero catalog exposure and - zero model-weight download. - -## Krea 2 sealed structural tranche - -- [x] Revalidated base and Turbo against the pinned official Modular Diffusers - source. Both use `text_encoder -> denoise -> decode` and share the - Qwen3-VL text architecture, packed-latent preparation, position IDs, and - Qwen-Image VAE decode. Their exact controls remain distinct: base exposes - symmetric CFG, negative prompt, 28 steps, and guidance 4.5; Turbo exposes - no negative prompt or guider and defaults to 8 steps. -- [x] Added separate base/Turbo text and denoise actions plus the shared exact - decoder. Focused fixture tests reject cross-variant execution and prove - the distinct calls/defaults. The expanded gate is green (`68 passed`, `7` - skipped, `87` subtests passed). -- [x] Run the clean-browser all-definition insertion smoke after the Wan base - download completes and the backend is restarted. Krea base and Turbo must - remain catalog-only and expose neither Prepare/Run nor Model Manager - download controls. -- [ ] Keep both gated model artifacts unavailable until their custom license, - moving acceptable-use policy, revenue threshold, and deployment content - filters receive explicit task-scoped legal/product approval. - -## Ideogram 4 sealed structural tranche - -- [x] Revalidated the pinned official four-stage Modular route: - `prompt_upsample -> text_encoder -> denoise -> decode`. Expanded Clusters - retain the six internal denoise preparation/loop stages, including both - conditional and unconditional transformers and the after-denoise - unpatchification step. -- [x] Added separate package-owned actions for optional local prompt - upsampling, Qwen3-VL text encoding, asymmetric-CFG denoising, and Flux2 - VAE decode. The visual denoise node accepts an exact JSON guidance - schedule only when it has one finite value per inference step; an empty - value preserves the official upstream schedule. The focused gate is green - (`74 passed`, `7 skipped`, `87` subtests passed). -- [x] Verify insertion/expansion in the clean-browser all-definition smoke - after the Wan base download and backend restart. The route must remain - catalog-only with no Prepare/Run or model download action. -- [ ] Keep the model artifacts and optional prompt-enhancer artifact closed: - the publisher gate/noncommercial terms have not been accepted, exact - authenticated artifact identities remain unresolved, and MoDiff does not - permit fallback to the hosted Ideogram prompt-rewrite API. - -## Structural Cluster graph catalog completion — 2026-08-28 - -- [x] Added exact package-owned graph adapters for all four Cosmos 3 Distilled - workflows: text-to-image, text-to-video, image-to-video, and - video-to-video. The conditioned routes retain the separate VAE encoder; - the common text-encode, denoise, and decode stages remain independently - visible after expansion. -- [x] Added exact package-owned graph adapters for all ten Cosmos 3 Omni - workflows, including sound-generating and action-policy/forward/inverse - dynamics routes. The official after-decode stage is a distinct visual - node. Action conditions are constructed only inside the optional runtime; - importing the catalog never executes repository or model code. -- [x] Added exact package-owned graph adapters for the three MiniMax H3 - workflows. FL2VA and Ref2VA preserve before-encode, text-encode, - VAE-encode, denoise, and joint video/audio decode as separate stages. -- [x] Added exact package-owned graph adapters for all four LTX-2.5 workflows: - text-to-video, image-to-video, condition, and in-context. Duration, - condition encoding, reference encoding, native diffusion decoding, and - audio sample-rate propagation follow the reviewed upstream contracts. - Prompt enhancement is not inserted implicitly because the official - default workflow disables it; it remains an explicit composable block. -- [x] The current first-party library now has sealed graph adapters for all - `102/102` definitions: `94` Diffusers Cluster definitions and `8` - Transformers Cluster definitions. This closes the structural insertion, - expansion, and parameter-persistence implementation queue; it does not - claim that gated or uninstalled artifacts are executable. -- [x] Focused backend coverage after the final structural tranche is green: - `61 passed`, `7 skipped`, `83 subtests passed`. Client TypeScript - validation is also green after adding the before/after encode, duration, - condition-encode, and reference-encode graph roles. -- [x] Restart the app after the Wan Animate 2 base qualification so the visible - frontend loads the complete catalog, then run clean-browser insertion and - save/refresh persistence coverage. Gated definitions must continue to - expose no Prepare, Run, or Model Manager installation claim. -- [x] Complete the post-catalog release regression. The canonical backend suite - passes `2,243` tests with `54` skipped and `6,726` subtests passed. The - complete client `npm run check` gate passes formatting, linting, - TypeScript, style, unit, production-build, and bundle-budget checks. The - complete mocked Studio browser suite passes `114/114` scenarios in 6.1 - minutes, including Cluster/User Node persistence, Model Manager, Auto and - Expert execution forms, and the speech-to-text workflows. -- [x] Prove clean-browser structural lifecycle behavior for all `94` reviewed - Diffusers definitions, plus strengthened save/refresh and complete - upstream AutoBlocks-tree checks for HunyuanVideo 1.5 and Stable Diffusion - 3. These policy-gated definitions remained non-executable throughout the - browser tests. -- [ ] Keep execution promotion separate from structural coverage. Cosmos 3, - MiniMax H3, LTX-2.5, HunyuanVideo 1.5, SD3, Krea 2, and Ideogram 4 remain - closed until their exact artifact, runtime, policy, and legal gates are - individually satisfied and their live outputs are reviewed. diff --git a/docs/developer-setup.md b/docs/developer-setup.md new file mode 100644 index 00000000..32adf6c6 --- /dev/null +++ b/docs/developer-setup.md @@ -0,0 +1,100 @@ +# Developer setup with uv and npm + +Use these commands from the backend checkout on Linux or Windows PowerShell. +They invoke Python directly; no downloaded shell or PowerShell launcher is required. +Install Git and [uv 0.11.26](https://docs.astral.sh/uv/getting-started/installation/) +first. uv can provision Python 3.12. For the client, use Node 24.12.x and npm 11.6.2. + +```text +uv run --no-project --no-sync --python 3.12 -m modiff.dev plan --accelerator cpu --backend-only --json +uv run --no-project --no-sync --python 3.12 -m modiff.dev setup --accelerator cpu --backend-only --non-interactive +uv run --no-project --no-sync --python 3.12 -m modiff.dev check --json --check-port 8088 --fail-on-error +uv run --no-project --no-sync --python 3.12 -m modiff.dev run +``` + +Open http://127.0.0.1:8088. The CPU profile is a small, no-model starting point for +API and UI development. It is not a claim that large diffusion models fit in CPU +memory. Setup downloads Python packages, including Torch; it does not download +inference weights. The backend-only path serves the checked frontend bundle. + +`plan` only inspects the selected installer profile. `setup` delegates to the same +reviewed installer used by `install.sh` and `install.ps1`: exact Diffusers source, +platform Torch sources, package constraints, device smoke, staged promotion and +rollback are shared. Guided installation remains available. + +An existing `.venv` is preserved. Use `check` to inspect it. To deliberately replace +its installed profile, repeat `setup` with `--repair` and the intended accelerator. +Do not repair your working GPU environment to CPU just to test the example; use a +separate checkout. `run` selects the installed interpreter and applies its ROCm +process environment before Torch imports. Ctrl+C stops the supervised runtime. + +## Accelerator and optional runtime choices + +Replace `cpu` with the appropriate installer selector: `auto`, `nvidia`, `amd`, +`amd-instinct`, `intel`, or `mps`. The authoritative support tiers, operating +systems and prerequisites are in the [accelerator guide](accelerator-installation.md). +An experimental profile still requires explicit `--allow-experimental` consent; +using uv does not qualify new hardware or bypass a missing system prerequisite. +`plan` reports the same required driver/system actions as guided setup. + +MoDiff intentionally has no `uv.lock` and is marked `uv`-unmanaged. Ordinary +`uv run` and `uv sync` are not supported inside the accelerator environment. +[`--no-project`](https://docs.astral.sh/uv/reference/cli/#uv-run--no-project) disables +project discovery/resolution; `--no-sync` is redundant alongside it in uv 0.11.26 +and uv prints a harmless warning. These explicit bootstrap commands do not ask +uv to resolve or replace the project's Torch profile. `uv pip` with an explicit +`--python` remains the supported contributor command for test dependencies. + +Selected `requirements/profiles/*.txt`, `pyproject.toml`, and the accelerator +manifest jointly define the reviewed executable contract. They are not a complete +cross-platform transitive lock. The installer writes a profile receipt and checks +it at startup. Service export additionally records the observed package versions. +Optional runtimes retain their separate reviewed install, verification and +activation steps in Model Manager; opening a graph or exporting a service never +installs them. Keep credentials outside graph files and packages. + +## Client development + +From the sibling `MoDiff-client` checkout: + +```text +npm ci +npm run dev +``` + +Use the URL printed by Vite with the backend running on its default loopback +address. `npm ci` consumes the committed lockfile. `npm run check` runs the client +quality gate and builds `dist/`; `npm run check:ui` runs browser regressions. +The backend's development command does not rebuild the client on every launch. +See [CONTRIBUTING](../CONTRIBUTING.md) for installing backend test requirements +and mirroring a validated production client bundle. + +## Bundled app without a separate frontend server + +For normal app use without Vite, clone the backend and client into sibling +`MoDiff/` and `MoDiff-client/` directories as shown in the +[README](../README.md#developer-setup-with-uv-and-npm). +From the backend checkout, omit `--backend-only` so setup also installs the +locked client dependencies and builds the frontend bundle: + +```text +uv run --no-project --no-sync --python 3.12 -m modiff.dev plan --accelerator auto --json +uv run --no-project --no-sync --python 3.12 -m modiff.dev setup --accelerator auto --non-interactive +uv run --no-project --no-sync --python 3.12 -m modiff.dev check --json --check-port 8088 --fail-on-error +uv run --no-project --no-sync --python 3.12 -m modiff.dev run +``` + +`plan` is read-only. Review its selected accelerator and any blockers before +running `setup`. Replace `auto` with an explicit supported profile when needed. +Open ; no separate frontend process is required. +The same existing-environment preservation and repair rules above apply. + +## Verification scope + +The CPU matrix runs these setup/check commands and +`scripts/smoke_service_package.py` on Linux, Windows and macOS. The smoke redirects +all writable paths to temporary storage, binds an ephemeral loopback port, checks +health, and compares a saved model-free graph with Manual and Auto service calls. +A CI definition is not evidence of an executed Windows run. See the workbench +milestone tracker for the platforms actually validated in the current change. +See [service prototyping](service-prototyping.md) for the executable example. diff --git a/docs/diffusers-94-cluster-hardening-and-asset-plan-2026-09-05.md b/docs/diffusers-94-cluster-hardening-and-asset-plan-2026-09-05.md deleted file mode 100644 index 29f7e42f..00000000 --- a/docs/diffusers-94-cluster-hardening-and-asset-plan-2026-09-05.md +++ /dev/null @@ -1,684 +0,0 @@ -# Diffusers 94-Cluster Hardening and Asset Qualification Plan - -Date: 2026-09-05 - -Status: active implementation plan - -## Current guidance - -This dated plan is historical context, not current route or release approval. -Use the [runtime support matrix](runtime-support-matrix.md#model-families-and-support-boundaries) -for family boundaries, the [engineering procedure](cluster-engineering-lessons.md) -for verification, and [runtime troubleshooting](troubleshooting.md#resource-monitoring-during-execution) -for resource monitoring and request responsiveness. - -## Immediate delivery plan — Qwen closure, then cached FLUX - -Updated 2026-09-05 after the overnight regression checkpoint. This sequence -takes priority over further catalog expansion and publication work below. - -| Order | Deliverable and acceptance | Estimate from this checkpoint | -| --- | --- | --- | -| 1 | Diagnose and fix Qwen browser failures: nested-library navigation and dynamic compiler skeleton ownership. Preserve the production ownership guards. Focused browser tests must pass against the current UI and compiler. | 1–2 hours | -| 2 | Close the current Qwen editing lifecycle: parameter locality; nested expansion/resize/links; compatible replacement and reconnection; nested User Node save/reuse; persistence and instance isolation; actual frontend generation from the modified workflow. Reuse existing evidence only where its definition and tested behavior remain applicable. | 1–2 additional hours, including model execution | -| 3 | Finish client/backend checks and the shared browser gate. Regenerate dependent source ledgers in dependency order; investigate failures instead of changing expected results without a cause. Record exact passed, failed, and skipped cases. | Included in the Qwen 2–4-hour target where checks can run alongside independent work | -| 4 | Demonstrate cached FLUX.2 Klein 4B text-to-image through the visible frontend: insert, edit, expand/resize, save/refresh, generate, view and persist media. Use official model recommendations and a complex prompt; preserve the exact workflow and parameters. | 2–4 hours after Qwen closure | -| 5 | Qualify the six cached FLUX routes: FLUX.2 Klein and Klein Base text-to-image/edit, plus Kontext text-to-image/edit. Exercise compatible structural changes and save/reuse behavior as well as generation. | 6–12 hours total for the FLUX tranche | - -Qwen plus one demonstrated FLUX workflow is a 4–8-hour target, with another -working day of contingency if a browser failure exposes a product defect or -model execution exposes an upstream/runtime incompatibility. Report a missed -estimate and its cause immediately; successful isolated runs do not close the -integrated gate. - -The current cache inventory contains the exact artifacts for those six FLUX -routes. FLUX.1-dev and FLUX.2-dev do not have complete required snapshots; -they follow the cached variants. Recheck Model Manager before any installation. -Downloads use the application's Hugging Face Hub pull exclusively. - -New delivery evidence belongs under -`data/review/qwen-flux-delivery-2026-09-05/`, with separate Qwen and FLUX -subdirectories. Keep generated assets, screenshots, parameters, workflow -exports, and technical receipts together. Historical evidence remains in its -original folder and is linked explicitly rather than copied into the new batch. - -Current checkpoint: all 11 Qwen admissions have recorded frontend generations. -The final backend suite now passes: 2,464 tests, 54 intentional skips, and 6,988 -subtests. The complete client `check` also passes after all fixes below, -including the production build and bundle gate. The final `check:ui` passes: -two shared-control tests and 126/126 mocked Studio browser tests. Earlier -115/126 and 125/126 runs were intermediate failures, not completion evidence. -The protected-owner fixture needed all targets in the viewport and a small -pointer move across the drag threshold before its long move. The deployed -clean-browser Qwen smoke also passes. No model family is considered fully -qualified from the all-94 structural audit alone. - -Implemented during this delivery tranche: - -- The mocked catalog compiler now matches the production hidden compilation - transaction, preserving ownership while its backend fields finalize. -- Browser assertions navigate the nested catalog and explicitly expand a - nested loop; expanding the root must not expand all descendants. -- Subtree saves remove excluded public/control mirror targets and renumber - retained controls contiguously without altering their relative order. -- All 94 workflows pass 1,700 independent subtree save/reinsert checks, with - original-instance immutability. Receipt: `all-94-subtree-audit.json` in the - new delivery evidence root. -- Current Qwen text-to-image passes its real FE lifecycle, including two-instance - isolation, parameter locality, save/refresh, nested movement/containment, - collapsed/expanded export equivalence, generation, save as User Node, and a - second generation from that saved User Node. Evidence is under - `qwen/qwen-image-2512-block-v2-live/`. -- Current Qwen compatible prompt-node replacement and delete/reconnect followed - by save/refresh and real generation passes. Evidence is under - `qwen/qwen-v2-structural-execution/`. -- Saving the actual nested Qwen text-encoder frame through its toolbar, adding - that saved definition from User Nodes, independently editing its prompt, and - saving/refreshing both instances passes against the live backend. Cross-boundary - sockets and saved defaults survive. Evidence: `qwen/nested-subtree-reuse/`. - This proves reuse/persistence, not standalone generation from an unconnected - text encoder; that subtree still requires compatible pipeline components. -- The full visible replacement/adoption/reconnection/move-out/interface/undo-redo - browser sequence passes. The corrected test verifies the actual dropped node - center before releasing the mouse instead of assuming the requested pointer - trajectory was applied. -- The clean-browser deployed smoke exposed an additional Arrange graph defect: - the legacy arranger was independently resizing/repositioning V2 projections, - competing with the recursive Block fitter. Global Arrange now moves V2 roots - as whole units and durably saves their positions, leaving internal layout and - semantics unchanged. Unit tests cover undo/rematerialization; the all-94 - hierarchy audit covers projection preservation after Arrange. -- Saving/reinserting a nested User Node exposed click insertion over an expanded - root. User Nodes now use collision-free placement, including a rightmost-edge - fallback when the bounded search is full, and focus the inserted node. - Live nested-subtree reuse now asserts no overlap and passes. -- The post-layout-fix complete client `check` passes. That build is now served - on port 8088, and a clean-browser production smoke passes recursive DOM - containment/non-overlap checks, nested expansion/resizing, and Arrange. - Screenshot: `qwen/deployed-expanded-qwen.png`. The final shared browser - suite also passes; the first FLUX frontend execution has now started. -- The first FLUX.2 Klein V2 demo test is implemented using shared V2 lifecycle - helpers. Its immutable model-card example recommends 1024×1024, four steps, - guidance 1.0, BF16 and model CPU offload: - . - Execution follows closure of the Qwen/shared regression gate. -- The first FLUX attempt exposed another shared control-plane defect before - generation: Model Manager's batch resource planning blocked HTTP health and - library requests. Both single/batch planning handlers evaluated snapshots, - model inventory, and candidates synchronously in the HTTP event loop. - These operations and response serialization now run through `asyncio.to_thread`; - graph admission still revalidates authority, and no model defaults were changed. - A new regression fails both old handlers by demonstrating that a scheduled - heartbeat cannot run during planning. The fixed endpoints pass 147 focused - tests and the complete backend suite (2,461 passed, 54 skips, 6,894 subtests). - The idle server was restarted; live Model Manager now permits library use - and returns a health response in 9 ms during planning. Evidence is under - `flux/flux2-klein-text-to-image/model-manager-control-plane.json`. -- The final backend lint audit found a misplaced `BlockInstanceV2.routeSelection` - type annotation after an unrelated function's return and an unused local in - composition rebuilding. The optional field is now declared on the correct - TypedDict, with a type-introspection regression; the existing composition - validation call is retained without its unused assignment. Focused route and - composition tests pass (13); the final full suite is being rerun. -- FLUX then passed parameter editing, expand/collapse export parity and - save/refresh, but real task `8rcGEaRVf9Cw` failed before loading weights: - the loader's separate mostly-Qwen workflow allowlist rejected Klein's - catalog-declared `text2image` selection. Explicit loader selections now - validate against the same pinned upstream workflow snapshot as the catalog, - and retain the complete selected component bundle. All 94 exact workflow - selections pass the new regression; unknown families/routes still fail, - mandatory Qwen/MiniMax selection and unscoped legacy behavior are preserved. - This changes loader scoping, not execution/publication admission. - Research: Diffusers Modular quickstart `get_workflow()`/`init_pipeline()` and - the installed pinned `ModularPipeline` constructor's `workflow` argument. - Focused loader/identity tests: 77 passed, 12 skipped, 200 subtests. Restart and - real FLUX rerun follow; no image was produced by the failed task. - -Final delivery checkpoint: - -- FLUX.2 Klein text-to-image passed the real frontend lifecycle and completed - task `772RuGGIrJ01`: insert, parameter locality, advanced guidance check, - nested containment, save/refresh, collapsed/expanded export parity, Run, - preview/media retrieval, and unchanged creator defaults. The full test took - approximately 2.7 minutes. There were no browser page errors. -- Review candidate: `flux/flux2-klein-text-to-image/museum-workshop.webp` in the - new evidence root. It is 1024×1024, four steps, guidance 1.0, seed 20260905, - BF16/model CPU offload and no quantization. Exact prompts, model pin, workflow, - receipt and screenshots are beside it. Visual inspection finds coherent - workshop composition/materials; the requested label is not clearly rendered. - No showcase/publication approval has been inferred. -- Full backend: 2,464 passed, 54 skipped, 6,988 subtests. Full client `check`, - shared controls (2), mocked Studio browser suite (126), deployed Qwen smoke, - Qwen real generation/reuse/structural edits, all-94 subtree audit, and the - FLUX live demo pass. The backend has been restarted with the loader and - planning fixes, and the checked client bundle is deployed. -- Remaining shared performance issue: workflow saves during concurrent full - Model Manager planning took approximately 9–25 seconds; one diagnostic queue - request also exceeded three seconds. Offloading planning removed its direct - event-loop block, but does not prove all control-plane latency is resolved. - Profile workflow validation/serialization and planner contention next, before - broadening the FLUX campaign. Do not hide this by reducing model defaults. - Budget 1–3 focused hours for this performance follow-up, then continue the - remaining five cached FLUX generation routes and FLUX structural-edit proofs - within the 6–12-hour FLUX tranche estimate above. - -The new Qwen outputs are 256-square, two-step **technical checks**, not proposed -showcase assets. Existing high-quality assets remain in their earlier campaign -folder; they are not copied into this new review batch. - -### Continuation — save responsiveness, then remaining cached FLUX - -- Reproduced blocking storage/serialization in all five workflow route paths - with HTTP-heartbeat regressions. Workflow operations now run off-loop under - the existing shared persistence lock; summary listings retain bounded metadata - instead of rereading all graph snapshots on every request. -- The clean-browser baseline save took 368 ms; the earlier 9–25-second delay is - intermittent, not a claim that every save took that long. Profiling its actual - 13-form Model Manager request found 5.96 seconds in inventory scanning and - 4.36 seconds in 13 repeated optional-runtime catalog inspections (10.87 seconds - total with profiler overhead). -- Inventory was decoding large tokenizer/weight-map payloads to find loader - class names. These payloads are now excluded; normal/custom configuration JSON - remains eligible. The measured scan dropped from 5.879 seconds to 0.082 seconds, - with an identical class-index digest across all 97 cached repositories. -- Batch planning now shares one lazy runtime inspection per request, retaining - fresh checks on the next request and at graph admission. Official Python - `asyncio.to_thread`, Transformers configuration, Diffusers configuration, and - installed pinned loader config constants were reviewed before these changes. -- Next acceptance: full backend regression, cold-restart frontend save/refresh - under planning, then real frontend runs for cached FLUX Klein edit, Klein Base - text/edit, and Kontext text/edit. Record asset quality separately from lifecycle - correctness; keep creator defaults unchanged and use explicit instance values. -- Save follow-up completed: full backend regression **2,470 passed, 54 skipped, - 6,993 subtests**; workflow heartbeat, cache invalidation/bounding and cancelled - save-notification regressions pass. The backend was restarted. The final live - browser save under concurrent Model Manager planning took **191 ms**; both - batch plans finished in **1.33–1.36 seconds**. Receipts are in - `performance/after/`. The profiled 13-form request now takes 1.04 seconds - instead of 10.87 seconds on this machine; these are observed measurements, - not a universal latency guarantee. -- FLUX.2 Klein edit passed the complete real frontend lifecycle as task - `frSbt8RXlxQ2`: source image entered through its file field, complex edit - instruction, instance-local dimensions/steps/seed/guidance, nested containment, - collapsed/expanded parity, Save, refresh, Run and persisted media. The 1024-square - result changed the blue velvet to burgundy while preserving the main workshop - composition. Four steps, guidance 1.0, BF16/model CPU offload, no quantization; - creator defaults unchanged. Evidence: `flux/flux2-klein-edit-image/`. - The earlier attempts failed in the test harness before submitting a run: - its Advanced disclosure had not been opened, and the float control normalizes - `1` to `1.0`. The shared input label correctly inherits its shell's ID; no - input-component change was necessary or retained. -- FLUX.2 Klein Base text-to-image passed the same frontend lifecycle as task - `Rk1Z_RY6nYhO`, with 208 seconds of measured backend execution (4.5 minutes - for the browser test). Exact creator recommendation: 1024-square, 50 steps, - guidance 4.0, BF16/model CPU offload, no quantization. Evidence: - `flux/flux2-klein-base-text-to-image/`. The scene is coherent, but the requested - label still contains a lettering error; retain this limitation for review. -- Kontext's exact cached revision uses the FLUX.1 [dev] Non-Commercial License - v1.1.1. The license was retrieved through `hf_hub_download`, not a direct-file - downloader, and read before scheduling that family. Requested explicit - confirmation of permitted non-production evaluation or separate commercial - authorization for `24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d` before execution. - This does not block the Apache-licensed Klein/Base routes. -- Klein Base edit passed the frontend lifecycle as task `qzJ0-Y3jJ4Fc`: - 447 seconds backend execution, 8.5 minutes complete browser lifecycle, - 1024-square/50 steps/guidance 4.0/BF16/model CPU offload/no quantization. - Blue-to-burgundy velvet editing preserves the main composition, with minor - fine-detail changes. Four of six cached FLUX routes now have current generation - evidence; this is not completion of FLUX structural-edit/User Node qualification. -- Deployed frontend port 8088 also passed the save-under-planning test: - Save 307 ms, planning 1.419 seconds, edited prompt retained after refresh. -- Post-change Studio regression reached 124/126 passing. Both failures traced - to required Hub dataset fixtures returning HTTP 429, not model execution. - Added an opt-in, SHA-256-verified local runtime-input fixture route, populated - using `hf_hub_download`; production download/admission behavior is unchanged. - The Wan fixture test now passes. Full regression rerun follows with frozen - frontend sources and verified fixture cache; do not count it as green yet. -- That full rerun is now green: **2 shared-control + 126 Studio browser tests**, - zero failures (9.0 minutes Studio), plus `npm run check` including unit/build/ - bundle and exact registered-catalog checks. Shared controls and interface - mutations are mocked client proofs, not substitutes for live generation. -- Generalized the existing Qwen structural-execution browser test for cached - FLUX Klein. Replacement is performed by a real pointer drag; link deletion - now waits for Arrange geometry and selects a pointer-reachable segment instead - of assuming its midpoint is exposed. The complete generation rerun is active. -- FLUX Klein structural rerun passed (1.7 minutes) as task `QCdN2VXiBCr2`: - compatible internal prompt replacement, outgoing-edge deletion, pointer - reconnection, Save/refresh, identical instance values, containment and real - 1024-square generation with a complex observatory prompt. The remaining FLUX - reusable User Node lifecycle is not covered by this workflow-only test. - The image and readable parameters are in `flux/flux-klein-v2-structural-execution/`. - No product UI change was needed for the test harness's edge-geometry or - JSON-escaped quote assertions. The sign lettering is imperfect; no automatic - showcase approval is inferred. - -- [x] Qwen catalog insertion/ownership and progressive-expansion browser failures resolved. -- [x] Complete the remaining adoption/reconnection focused browser test. -- [x] Current Qwen editing/reuse/generation proofs complete (separate attributed lifecycle tests). -- [x] New Arrange/insertion fixes deployed and clean-browser smoke passed. -- [x] Required client/backend/browser checks complete with results recorded. -- [x] First FLUX.2 Klein frontend demonstration and asset ready. -- [x] Workflow-save/batch-planning latency follow-up (9–25 seconds observed). -- [ ] All six cached FLUX routes qualified through the frontend. - -## Goal - -Finish the Qwen Image vertical slice with no known visual, editing, -persistence, or execution defects, then apply the same source-neutral contract -and automated conformance gates to all 94 pinned Modular Diffusers workflows. - -The result must provide both experiences without maintaining two graph types: - -- a registered Cluster is the immutable, collapsed, one-node way to run a - reviewed workflow; -- expanding that instance reveals the exact active Modular Diffusers hierarchy - as ordinary Blocks with ordinary controls, ports, selection, movement, - resizing, connection, and subtree-save behavior; -- changing the instance is copy-on-write. The registered catalog definition is - never changed, while the workflow retains the exact modified instance; -- a changed root or any changed subtree can be saved as a User Node with its - current prompts, parameters, model/component bindings, topology, interfaces, - layout, and immutable provenance; -- browser refresh and backend restart restore the same instance, not current - catalog defaults; -- execution continues through MoDiff's one backend graph executor and the - pinned Diffusers `init_pipeline()` composition path. - -This plan covers the 94 public workflows discovered from the pinned Modular -Diffusers snapshot. The library endpoint currently also contains one standard -Diffusers composite (`WanTI2VPipeline`), so the UI reports 95 Diffusers Cluster -entries. That extra route receives the shared canvas/catalog checks, but it is -not counted as one of the 94 Modular workflows. - -## Upstream contract used by this plan - -The implementation follows the current official Hugging Face Diffusers -Modular documentation and the exact pinned source revision, not inferred -behavior from display names: - -- `ModularPipelineBlocks` are reusable units that can be composed and can - contain sub-blocks; -- `SequentialPipelineBlocks` pass intermediate state from one block to the - next; -- `LoopSequentialPipelineBlocks` own loop-member blocks and loop state; -- `AutoPipelineBlocks` select a branch from supplied trigger inputs; -- `get_workflow()` selects the exact active workflow; -- any valid block/subtree may become a pipeline through `init_pipeline()`; -- adding, removing, and swapping blocks changes the effective pipeline inputs, - outputs, and components and must be rebuilt and validated, not visually - simulated by the client. - -The upstream API is experimental. All imported structure, inputs, outputs, -components, class identities, and revisions therefore remain snapshot-pinned -and hash-covered. - -## Qwen issue ledger and catalog-wide invariants - -Every issue below occurred, or was exposed by manual Qwen testing, during the -current implementation. Each row defines the wider regression that must run -against all 94 definitions so a family-specific patch cannot hide the same -class of defect elsewhere. - -| Area | Qwen issue observed | Required invariant for all 94 | -| --- | --- | --- | -| Definition identity | Generated `BlockDefinitionV2` identity became stale after catalog/compiler changes. | A reproducible build regenerates every definition; client, backend, served catalog, embedded instance, and content hash agree byte-for-byte before insertion. | -| Old workflow loading | Stale execution `bindings` prevented Studio loading. | Invalid execution metadata is reported and recoverable without preventing an empty graph from opening; no unvalidated binding reaches execution. | -| Insertion | Click/drop appeared to do nothing while definition materialization was slow. | Every click/drop shows immediate per-entry progress, inserts once, and reports a bounded actionable failure. | -| Empty graph drop | A Cluster could fail with malformed presentation state or a stale receipt. | All structurally admitted Clusters and Blocks insert into an empty graph with valid V2 presentation and no pre-existing task node. | -| Renderer parity | Registered Cluster, changed instance, and User Node used different renderers, actions, controls, ports, and field shapes. | One Block V2 frame and projection renders every root/container/subtree; registration changes provenance and permissions only. | -| Expansion containment | Children appeared outside the root; collapsed user dimensions constrained expanded content. | Expanded bounds are derived bottom-up from visible descendants and ignore compact collapsed dimensions. Every visible descendant lies within its immediate visible parent. | -| Nested containment | Content overflowed intermediate containers. | The same recursive bound rule applies at every depth, including after child resize, move, collapse, re-expand, save, and refresh. | -| Initial disclosure | Expanding the root also expanded every nested container. | A fresh root expansion reveals immediate children; deeper containers begin collapsed and expand independently. | -| Layout | Nodes overlapped, scattered, or inherited stale saved coordinates. | Deterministic sibling layout resolves collisions per parent while preserving intentional user positions; legacy positions normalize once without changing execution. | -| Resizing | Root/internal Block resizing clipped children, hid content, or lost links. | Compact size persists separately from derived expanded bounds. Resize handles, connector trays, and links remain usable at the minimum size. | -| Ports | Some root and internal Blocks had no visible inputs/outputs. | Every visible subtree derives typed boundary ports from public bindings and semantic edges crossing its boundary. Leaf ports are unchanged. | -| Collapsed links | Links disappeared when an internal container was shrunk/collapsed. | Crossing edges project to the nearest visible boundary port and always resolve back to the exact semantic leaf endpoint. | -| Inert alternatives | Qwen VAE/control-VAE auto alternatives appeared as disconnected active nodes in text-to-image. | Unselected upstream auto/conditional alternatives remain in the immutable source snapshot but never render or execute as active workflow nodes. Customized/user-owned or newly connected nodes are never hidden by this rule. | -| Nested required inputs | Qwen Edit supplied the source image to representative text/VAE owners but not to an earlier active nested `resize` leaf, so the exact graph failed only at runtime. | Every external media/data edge fans out to every selected active leaf declaring that socket; every required runtime input socket has an incoming edge. Inactive alternatives receive neither edges nor execution. | -| Stale inactive branches after refresh | Older finalized graphs could preserve unmatched exact execution roles as infrastructure, leaving useful-but-inactive VAE/ControlNet branches visible and disconnected in text-to-image. | Re-finalization preserves only actual loader/preview/export infrastructure. Skipped Conditional/Auto placements remain available in the left library for compatible routes but do not survive as active graph nodes. | -| Loader visibility | It was unclear which model was used; the model selector was disabled. | Every executable Cluster exposes its reviewed loader/components and exact repository/revision at the appropriate expanded level. Compatible same-family choices are explicit and preserve shared fields. | -| Creator defaults | Prompts were blank or technical test overrides looked like defaults. | Creator/model-card starter values are visibly labeled and immutable catalog defaults remain unchanged. Qualification overrides live only in the test instance/receipt. | -| Parameter-only edit | A small parameter edit changed presentation/interface and made the node look unrelated. | Editing one value changes only that instance value and copy-on-write ownership state; graph, interface, layout, other values, and source definition remain byte-identical. | -| Structural edit | Move/add/replace/delete/reconnect behavior was incomplete or silently changed semantics. | Valid structural edits update the effective graph and exact composition recipe; invalid edits remain visible, identify the implicated node/edge, and offer a precise Fix action. | -| Conversion semantics | A changed registered instance looked like a different canvas type. | Copy-on-write does not change the renderer or effective interface. Only the provenance/ownership badge and allowed save choices change. | -| Save choices | Update existing, save as new, and workflow-only behavior was unclear. | Registered instance: save new or keep workflow-only. User-owned instance: update existing, save new, or keep workflow-only. Other open instances remain unchanged. | -| Persistence | Reloading previously reset model/prompts/parameters. | Save, refresh, clean browser, and backend restart preserve definition snapshot, values, model selection, ports, topology, expansion, sizes, and positions. | -| Multi-instance isolation | Automatic model/route state could couple separate nodes. | Two instances and two workflows have independent values, effective graphs, routes, and presentation. | -| Run authority | Expert/non-Auto runs were blocked by warnings such as projected memory risk. | Expert mode blocks only true missing/invalid graph, runtime, artifact, or required-input errors. Risks remain prominent warnings with explicit run-anyway/recovery actions. | -| Qualification language | `Prepare qualification run` appeared in the ordinary node and obscured normal Run. | Qualification is a developer/evidence action, never the primary user workflow. Normal runnable instances use the application Run/Queue controls. | -| Worker failure | AMD SVM/OOM killed a worker, leaving stale progress and a misleading backend-disconnected state. | Worker death atomically fails the run with preserved stderr classification, model/resource context, and actionable retry/unload/lower-memory guidance. Backend health and worker health are distinct. | -| Long loop progress | Exact nested denoise execution remained at the outer graph percentage while upstream `progress_bar()` advanced only in worker stderr, making a healthy Qwen run look stuck. | Every exact reviewed loop owner bridges the upstream Modular Diffusers progress-bar boundary into queue/activity telemetry with step count, elapsed time, ETA, heartbeat, and clean step-boundary cancellation; the pinned block tree is not modified. | -| Fixed-sequence workflow identity | Qwen Edit Plus/Layered use a reviewed internal `default` identity, but their upstream `SequentialPipelineBlocks` do not publish `_workflow_map`; forwarding `workflow="default"` made Diffusers reject model loading. | Keep the reviewed identity in the graph/runtime contract, but forward `workflow` to Diffusers only for a real named upstream workflow. Fixed block sequences initialize directly, as required by the upstream API. | -| Fixed-sequence nested runtime paths | The selected catalog indexed `text_encoder.resize` as one dotted compatibility segment while the fixed upstream block tree exposes the real path `text_encoder` → `resize`; execution therefore loaded every model component and then rejected the first nested leaf. | Named workflows execute and validate their `get_workflow()` path. Internal `default` definitions execute and validate the full immutable unpruned path. Every fixed-definition executable parameter path must equal its hierarchical placement path; dotted display/index keys never cross the backend boundary. | -| Nested decoded media | Qwen Image Layered correctly returned decoded PIL images grouped as `batch → layers`, but the shared Preview node recognized only a flat PIL list, misclassified the nested collection as latents, requested an unrelated VAE, and let the graph finish without visible media. | Preserve the upstream nested value inside the Modular graph, then normalize decoded nested PIL batches only at the generic Preview/output boundary. A run is not complete evidence until its typed media reaches Studio and is persisted. | -| Shared logical input fan-out | Qwen Image Layered exposed `layers` on its loop owner and on earlier/later nested preparation leaves. Editing the root control changed only the loop owner, so preparation retained the upstream default of four layers even when the workflow requested two. | One admitted top-level ModularPipeline argument compiles to one public V2 control whose binding and canonical mirrors cover every selected active leaf consuming that argument. Existing Studio/export consumers and newly discovered hierarchical consumers are merged under one authority; inactive, output-only, shadowed-optional, and differently owned fields are excluded. | -| Default mutation | Low-resource debugging could silently alter default parameters. | Resource presets and test overrides are explicit reversible instance changes. No debug path mutates registered defaults. | -| Discovery | 94 Clusters and more than 1,000 contextual block rows produced an unusable flat list. | The catalog uses nested facets and one visible entry per semantic identity. Contexts are metadata/choices, not duplicate rows. | -| Status labels | Catalog-only, structurally available, installed, runnable, and promoted states were conflated. | Separate badges describe structure, artifact installation, runtime/resource qualification, and publication; none imply another. | - -## Definition of done and evidence levels - -No Cluster is marked complete from a screenshot or a compiler test alone. -Evidence is cumulative: - -1. **Schema** — immutable source, definitions, hashes, ports, hierarchy, and - dependencies validate without model import or download. -2. **Canvas contract** — insertion, progressive expansion, containment, - resize, ports, links, controls, parameter edit, structural edit, undo, save, - refresh, restart, and instance isolation pass. -3. **Execution contract** — a visible-frontend run reaches the pinned official - Diffusers path, emits progress, handles cancellation/failure, and produces - typed media. -4. **Output qualification** — the current definition/revision/resource recipe - produces a reviewable asset with its full receipt. -5. **Publication** — a human approves that exact asset/receipt before public, - `liveProof`, showcase, or Auto-eligible flags are enabled. - -The all-94 guarantee means no known issue and passing automated invariants for -all 94 definitions. It does not pretend that static tests can prove hardware -execution for model snapshots that are not installed. - -## Work order - -### Gate 0 — Inventory and reproducibility - -1. Freeze the pinned Diffusers revision, the 94-workflow manifest, unpruned - hierarchy snapshot, generated V2 catalog, and backend compiler revision. -2. Add one manifest that records every workflow's family, modality, task, - hierarchy depth, active definition identity, artifact repositories, - installation state, execution/resource status, and evidence paths. -3. Fail both client and backend gates on stale generated identities, duplicate - workflow identities, duplicate semantic block identities, invalid edges, - missing endpoints, or mutable artifact revisions. -4. Keep audit scratch data in temporary ignored directories and delete it after - each gate. - -### Gate 1 — Qwen Image release gate - -No work is promoted to another model family until this gate passes. - -1. Run the full 10-route Qwen structural/browser matrix from a clean empty - workflow using the production-served frontend. -2. For Qwen text-to-image, perform and persist: - - creator-quality complex prompt and negative prompt; - - width, height, steps, guidance, seed, offload, and compatible model choice; - - root and nested expand/collapse and resize; - - parameter-only edit with zero collateral graph/interface/layout changes; - - add, replace, remove, move-out, and reconnect of compatible Blocks; - - invalid connection followed by the highlighted Fix recovery; - - root, intermediate container, loop, and leaf save-as-User-Node; - - workflow-only, save-new, and update-existing persistence decisions; - - save, refresh, backend restart, and multi-instance isolation. -3. Run the modified workflow from the visible frontend in collapsed and - expanded states. Compare execution graph fingerprints and deterministic - receipt fields; preserve both generated assets. -4. Repeat route-specific required-input and persistence checks for Qwen - image-to-image, inpaint, three ControlNet routes, Edit, Edit inpaint, Edit - Plus, and Layered. -5. Do not qualify an inactive auto branch as a missing/disconnected node. Do - fail any active or user-connected orphan. - -### Gate 2 — Generic all-94 conformance harness - -Implement a table-driven suite over the pinned manifest, not 94 model-specific -frontend branches. For every Modular workflow it must verify: - -- current generated identity and exact revision; -- deterministic `BlockDefinitionV2` compilation; -- unique node/edge/parameter/port identities; -- valid edge endpoints and type-compatible crossing bindings; -- selected upstream auto/conditional workflow only; -- exact recursive parent chain and maximum declared depth; -- progressive initial collapse state; -- collapsed and fully expanded execution graph equality; -- boundary ports and links at every visible container depth; -- bottom-up containment after minimum/maximum resize and child move; -- parameter-only copy-on-write locality; -- structural mutation validation and recoverable invalid state; -- save/normalize/export/import/refresh byte equivalence; -- two-instance isolation; -- no hidden Hub request, runtime install, or remote-code enablement during - browsing, insertion, expansion, or planning. - -Run browser coverage exhaustively for insertion/collapse/persistence where it -is bounded, and use risk-selected live browser cases for each modality, block -kind, maximum hierarchy depth, optional branch shape, loop shape, and component -loading pattern. A real model run remains per-route evidence and is never -faked by this structural suite. - -### Gate 3 — Nested catalog and duplicate removal - -Replace the four flat Hugging Face lists with a searchable tree: - -- **Diffusers Cluster Nodes** - - modality: Image, Video, Audio, Multimodal; - - task: Text to Image, Image to Image, Inpaint, Text to Video, and so on; - - family: Qwen Image, FLUX, Stable Diffusion XL, Wan, and so on; - - workflow/model variant. -- **Modular Diffusers Block Nodes** - - family; - - role/kind: Load Components, Encode/Condition, Prepare Inputs/Latents, - Denoise, Decode/Postprocess, Preview, Auto/Conditional, Loop, Other; - - one exact semantic definition row. -- **Diffusers Component Nodes** - - component role/type; - - family compatibility and reuse contexts. -- **User Nodes** - - workflow/source family; - - user versions. - -Duplicate policy: - -- a Cluster identity is `(provider, pipeline class, workflow id, immutable - revision)`; -- a Modular Block identity is its exact pinned block-definition identity/hash; -- one block used in multiple placements is shown once, with all compatible - family/workflow/path contexts in its details and insertion context chooser; -- aliases and friendly labels improve search but do not create another row; -- context-specific variants remain separate only when their contract, child - tree, components, or exact composition recipe differs; -- search matches every hidden breadcrumb/context and expands only matching - branches. - -All rows keep immediate insertion feedback and are draggable both onto a -top-level graph and into a compatible expanded Block. Incompatible drops are -kept visible with a node-targeted validation/Fix explanation. - -### Gate 4 — Generic same-family model and route behavior - -Generic means contract-compatible, not arbitrary cross-task switching. - -1. Declare model choices in backend execution/admission data with exact - repository, revision, component schema, parameter aliases, and supported - workflows. -2. Offer choices in a shared loader control only when the selected model is - compatible with the Cluster's task and effective public interface. -3. On model/route change, diff the reviewed definitions by stable semantic - source identity: - - retain shared nodes, current compatible values, user positions, public - ports, and connections; - - add/remove only route-specific subtrees; - - preserve disconnected user additions and explain any newly incompatible - connection; - - never reset an unchanged prompt/parameter merely because a loader changed. -4. Rebuild valid changes through the exact pinned `init_pipeline()` recipe. -5. Store route drafts independently so switching away and back restores each - route's prior instance state. - -### Gate 5 — Cached-model asset campaign - -Asset generation starts only after the corresponding definition passes Gates -1 and 2. It is performed through the visible frontend so the normal user flow, -queue, progress, media preview, save/refresh, and error handling are exercised. - -Rules: - -- model installation uses the application's Hugging Face Hub snapshot pull; - never `wget`, mutable URLs, or an unrecorded manual weight copy; -- use exact immutable revisions and `trust_remote_code=false` unless a separate - reviewed authorization explicitly permits a bounded remote-code route; -- start with already complete cached repositories to avoid unnecessary - downloads and disk churn; -- use official model-card/creator recommendations as the starting values; - quality overrides are stored in the workflow and receipt, not registered - defaults; -- preserve one dated, isolated review directory containing only this campaign; - include asset, thumbnail/contact sheet where relevant, frontend screenshot, - workflow export, definition hash, model/artifact revisions, prompt, negative - prompt, all effective parameters, resource recipe, timing, and run logs; -- do not mix historical assets into the current review folder; -- after human approval, retain the approved evidence and preview cache deletion - per model family before rotating to uncached models. - -Current cache candidates to reconcile against exact route revisions first -include Qwen Image 2512/Edit/Edit 2511/Layered and Qwen ControlNet, FLUX/FLUX -Kontext/FLUX.2 Klein variants, Stable Diffusion XL and its reviewed adapters, -Wan 2.1/2.2 and Wan Animate variants, Z-Image, ERNIE Image, and MiniMax Music 3. -Cache presence alone does not prove that every required component or exact -revision is complete, licensed, resource-qualified, or compatible with the -current pinned Diffusers runtime. - -Prioritized output order after Qwen: - -1. one high-quality image path per installed family; -2. image edit/inpaint/control paths using curated source media; -3. audio with creator-recommended duration/steps; -4. video with motion-specific prompts and native recommended dimensions/frame - counts, rejecting static, pixelated, or temporally glitchy results; -5. additional routes that reuse the resident family before cache turnover. - -### Gate 6 — Regression, evidence, and promotion - -1. Run the complete client unit/type/lint/build/bundle gate. -2. Run the complete backend suite and cross-runtime definition/hash fixtures. -3. Run the complete mocked Studio browser suite. -4. Restart the backend and run a clean-browser production-bundle smoke. -5. Reconcile Git so generated media, caches, temporary workspaces, tokens, - logs, browser reports, and local configuration remain ignored and absent. -6. Stage definition-specific promotion receipts only for human-approved output; - approval of technical behavior does not imply showcase or Auto approval. - -## Immediate implementation slices - -The first implementation pass is intentionally bounded and testable: - -1. add the all-94 conformance audit for identity, hierarchy, selected branches, - boundary connectivity, collapsed/expanded execution equality, and - parameter-only locality; -2. change the Modular Block catalog from contextual-placement rows to unique - semantic definitions with retained searchable contexts; -3. add nested catalog breadcrumbs/groups and duplicate-count regression tests; -4. run the complete Qwen 10-route production-browser lifecycle; -5. run a real modified Qwen text-to-image frontend generation into a new dated - review folder; -6. run full gates before moving to the first cached non-Qwen family. - -## Blockers and required acknowledgements - -- **Disk/resource:** current free storage must be rechecked before each large - pull and video/audio run. Cache rotation must not delete a model until all - reusable routes for that family have approved evidence or an explicit - decision says otherwise. -- **Gated/license-controlled artifacts:** any repository requiring license - acceptance or usage/revenue/territory acknowledgement needs the exact model - revision and explicit operator acknowledgement recorded before installation - or execution. Existing acknowledgements do not transfer to a new revision. -- **Remote repository Python:** arbitrary `block.py` execution remains outside - this phase. Declarative imports remain remote-code-disabled; a sandbox and - separate explicit authorization are required for reviewed remote Python. -- **Hardware:** AMD/ROCm compatibility and memory qualification are per recipe. - A structurally valid Cluster may remain execution-blocked until its exact - recipe passes. Expert mode still reports all issues and only hard-blocks - requirements that make execution impossible or unsafe to submit. -- **Output approval:** public promotion requires a human decision on the exact - current asset and receipt. Old or technically valid but poor-quality assets - are not reused as showcase approval. - -## Time estimate - -Assuming no new upstream incompatibility: - -- Qwen final structural/edit/persistence gate plus one real modified - text-to-image proof: 0.5–1.5 focused days, with model runtime included; -- generic all-94 conformance harness and defect fixes it exposes: 1.5–3 days; -- nested catalog, semantic deduplication, and browser coverage: 1.5–3 days; -- cached-model execution campaign: approximately 3–7 days of elapsed machine - time, heavily dependent on video/audio runtimes and reruns for quality; -- full regression/evidence cleanup: 0.5–1 day; -- uncached/gated model rotation: additional time per download, license gate, - and hardware qualification, estimated only after the cache-to-route manifest - is complete. - -The functional all-94 structural/catalog target is therefore approximately -4–7 focused engineering days. Real high-quality execution evidence for every -route cannot be honestly bounded to that window because several snapshots are -not installed or legally/resource qualified; cached routes will be completed -first and reported separately. - -## Progress checklist - -- [x] Official Modular Diffusers behavior and pinned source boundary reviewed. -- [x] Historical Qwen defect classes converted into catalog-wide invariants. -- [x] Cache-to-route/evidence manifest generated. Current machine-readable - inventory: - `data/review/diffusers-94-hardening-2026-09-05/diffusers-94-qualification-manifest.json`. -- [x] Qwen 10-route clean live-backend browser contract rerun. Current receipt: - `data/review/diffusers-94-hardening-2026-09-05/qwen-v2-family-lifecycle/frontend-result.json`. -- [x] Real modified Qwen text-to-image proof generated. The technical - structural-edit receipt is under - `data/review/diffusers-94-hardening-2026-09-05/qwen-v2-structural-execution/`; - the current quality asset and receipt are isolated under - `data/review/diffusers-94-assets-2026-09-05/qwen-image-2512-block-v2-live/`. -- [x] All-94 generic structural conformance audit green: 94 workflows, 1,794 - semantic nodes, maximum compiled placement depth 5, no duplicate - workflow identities, boundary interfaces present, recursive containment, - progressive nested disclosure, collapsed/expanded execution equality, - parameter-only locality, zero disconnected active execution leaves, and - zero unbound required runtime inputs. - Containers are projections rather than executable leaves; inactive - conditional alternatives are excluded from the selected graph. Evidence: - `data/review/diffusers-94-hardening-2026-09-05/all-94-structural-audit.json`. -- [x] Nested Hugging Face catalog and semantic Modular Block deduplication - implemented: 1,051 placement contexts now render as 598 exact semantic - Block rows while preserving all contexts for compatible insertion. -- [x] Deployed Qwen production nested-Block smoke passes from an explicitly - empty graph: skipped text-to-image VAE/ControlNet alternatives are absent, - every visible nested container exposes connectors, resize preserves its - incident links, progressive expansion retains recursive containment, and - sibling projections do not overlap. Screenshot: - `data/review/diffusers-94-hardening-2026-09-05/qwen-production-nested-smoke.png`. -- [x] Sequential duplicate-media-writer regression fixed generically. Public - direct-media outputs now follow the final selected writer/postprocessor - unless an exact reviewed placement mapping deliberately pins another - writer. The affected 9 Qwen and 2 Anima definitions were regenerated and - repinned; the compiler suite also proves that any duplicate writers have - one terminal writer and that the public output binds to it. -- [x] Remaining Qwen admission execution wave complete. Qwen image-to-image, - inpaint, and all three ControlNet routes now pass insert, edited values, - expand/collapse parity, save, refresh, real visible-frontend execution, - and persisted media output. The Edit-family wave exposed and now has a - generic fix for nested required-input projection: external values fan out - to every selected active leaf declaring that socket, and the 90-route - compiler gate rejects any required runtime input without an incoming - edge. Qwen Edit now passes that real frontend rerun as task - `iaMyam0yQPwU`, and Edit Inpaint passes as task `pD_0nDpId1ho`. Edit Plus - also exposed two fixed-sequence API mismatches which are now covered by - backend and all-route compiler tests: `workflow="default"` is no longer - forwarded to a class without `_workflow_map`, and nested executable paths - use the full immutable tree rather than dotted catalog shorthand. The - corrected Edit Plus single- and multi-reference frontend reruns pass. - Layered now persists its decoded nested PIL collection correctly; its - first visible proof then exposed a generic shared-control fan-out defect. - The compiler now merges existing reviewed consumers with every selected - hierarchical leaf consuming the same ModularPipeline argument, and all - 90 registered BlockDefinitionV2 routes compile with zero ambiguity. The - corrected Layered visible-frontend rerun passed as task - `qxjE5KXAasA9`: the saved `layers = 2` value reached the loop owner and - all three selected nested consumers, exactly two images were persisted, - save/refresh remained byte-identical, and collapsed/expanded execution - stayed equivalent. Together with the earlier text-to-image proof, all 11 - Qwen admissions now have current real-frontend execution evidence. -- [ ] Cached image-family asset batch ready for review. -- [ ] Cached audio/video batch ready for review. -- [ ] Full release regression green. -- [ ] Approved definition-specific promotion receipts staged. diff --git a/docs/diffusers-execution-qualification-rotation-plan-2026-08-28.md b/docs/diffusers-execution-qualification-rotation-plan-2026-08-28.md deleted file mode 100644 index 40486939..00000000 --- a/docs/diffusers-execution-qualification-rotation-plan-2026-08-28.md +++ /dev/null @@ -1,675 +0,0 @@ -# Diffusers execution qualification and model-rotation plan - -Date: 2026-08-28 - -Status: active - -## Objective - -Qualify every reviewed Diffusers Cluster through the real frontend while using -local storage efficiently: - -1. Use every exact, currently installed model before deleting it. -2. Produce reviewable, good-quality assets rather than low-step smoke outputs. -3. Prove the complete Cluster lifecycle for each route: - `insert -> expand -> edit -> save -> refresh -> verify -> generate`. -4. Retain the workflow, immutable artifact revision, parameters, generated - asset, hashes, browser evidence, and execution receipt. -5. Delete a model only after all of its routes pass and the retained outputs - are approved or explicitly waived by the reviewer. -6. Install newly authorized families one at a time, qualify them, then rotate - them out using the same process. -7. Keep legal, access, safety, hardware, and publication gates separate from - structural Cluster support. - -## Starting state - -- Disk available at campaign start: approximately 156 GiB. -- Hugging Face cache at campaign start: approximately 1.56 TiB. -- Reviewed Diffusers Cluster definitions: 95 (94 official Modular Diffusers - workflows plus one reviewed standard-Diffusers composite). -- Definitions with an exact pinned main artifact currently cached: 46. -- Unique cached pinned main artifacts used by those definitions: 17. -- Approximate storage occupied by those main artifacts: 736 GiB, excluding - shared auxiliary ControlNet, IP-Adapter, LoRA, encoder, and upscaler assets. - -The campaign must compute actual reclaimable bytes through the app before each -deletion. Displayed family totals are planning estimates, not deletion claims. - -## Non-negotiable safety and evidence rules - -- Do not use raw filesystem deletion for model rotation. Use Model Manager's - exact, dependency-aware deletion flow and retain its receipt. -- Do not delete a model while any route that depends on it is unqualified or - has an unresolved review decision. -- Do not treat a directory-name match as installation proof. Repository, - 40-character commit, reviewed file selection, and component identities must - match the sealed admission. -- Do not accept a moving branch such as `main` in a promotion receipt. -- Do not silently accept a model license, privacy gate, AUP, commercial term, - territory condition, or hosted-service condition for the user or company. -- A generic instruction to continue is not a revision-bound legal acceptance. -- Do not expose Prepare, Run, Model Manager download, `executable`, - `liveProof`, or `autoEligible` claims until the exact route has passed its - corresponding gate. -- Qualification assets remain private review evidence until manually approved. -- Rejected outputs are rerun using corrected prompts/parameters; technical - qualification is not used to mislabel a poor output as showcase-ready. - -## Review evidence layout - -Campaign root: - -`data/qualification/local-review/hugging-face-clusters/showcase-rotation-2026-08-28/` - -Each definition receives a directory containing: - -- generated image, video, or audio asset; -- source media, when applicable; -- saved workflow and post-refresh graph export; -- exact model and auxiliary artifact revisions; -- effective generation and execution parameters; -- frontend lifecycle receipt; -- generated-asset metadata and SHA-256; -- screenshots proving insertion, expanded controls, restored values, and run; -- contact sheet for video or waveform/spectrogram for audio; -- review decision with approve, reject, or waive status. - -The campaign root also contains a machine-readable queue and a local review -page linking every pending asset. - -## Phase 0 — freeze inventory and prepare the campaign - -Target: 2–4 focused hours. - -- [x] Export the exact installed repository/revision/file inventory. -- [x] Match every installed artifact to its sealed Cluster admission. -- [ ] Resolve shared auxiliary dependencies and actual reclaimability. -- [x] Create the dated campaign directory and review queue. -- [x] Define quality profiles from each pinned official model card and package - contract. Profiles must include supported resolution/aspect ratio, - sampling schedule, steps or sigmas, guidance, frame count/rate, duration, - negative prompting, offload policy, and reproducible seed behavior. -- [ ] Create safe source fixtures for image, mask, control, driving-video, - first/last-frame, reference, and audio-conditioned routes. -- [x] Confirm sufficient output storage without downloading another model. -- [x] Prove that a failed generation preserves the saved workflow and model. - -Campaign implementation artifacts: - -- `installed-artifact-inventory.json` freezes 17 exact installed artifacts and - their 45 current definition consumers. -- `quality-profiles.json` records the official first-batch quality settings. -- `review-queue.json` is the private review queue. It keeps generated model - outputs and optional delivery derivatives as distinct review items. -- `scripts/build_showcase_review.py` validates generated media and builds the - campaign-local `index.html`, `review-index.json`, contact sheets, waveforms, - media hashes, and technical checks without publishing anything. - -## Phase 1 — first reviewable image/video/audio set - -Target: first coherent manual-review batch within 6–12 execution hours. - -### Image - -- Qwen Image text-to-image. -- Use the official quality resolution and sampling profile rather than the - prior minimum-cost qualification settings. -- Retain at least two seeded candidates and select without overwriting either. - -### Video - -- Wan Animate 2 or Wan 2.1, selected according to the strongest compatible - prepared source fixture. -- Use a motion-bearing prompt/fixture, practical showcase resolution, enough - frames to make temporal motion visible, and the official sampling schedule. -- Validate non-static motion numerically in addition to visual review. - -### Audio - -- MiniMax Music 3 at revision - `fbdf52fbaaca799592917417eb05f1899f1255ec`. -- The user's exact-revision Community License/revenue acknowledgement is - already recorded in the task history. -- Generate a meaningful-duration musical sample with lyrics/style controls, - then validate duration, sample rate, channels, loudness, clipping, and - non-silence. - -Every route in this phase must pass the complete frontend save/refresh/run -lifecycle before its output is placed in the review queue. - -### Phase 1 execution log - -- Qwen Image text-to-image completed task `IcFmWpqEPzmE` at the pinned - revision. The frontend restored the edited prompt, negative prompt, - 1328x1328 geometry, 50 steps, guidance 4, and seed 280828 after refresh; the - collapsed and expanded exports matched. The 1328x1328 WEBP passed decoding, - dimension, non-uniformity, and SHA-256 validation. Human review approved the - image. -- Wan 2.1 text-to-video completed task `GEjlARR3FEej` after its full frontend - lifecycle proof. Save and browser refresh restored the edited prompt, - negative prompt, 832x480 geometry, 81 frames, 50 steps, guidance 5, 15 fps, - and seed 280828; collapsed and expanded exports matched. The 5.4-second, - 81-frame H.264 MP4 passed decoding and non-static-motion validation and is - pending human review. -- MiniMax Music 3 completed task `3DoJ-w8jP3cZ` at the acknowledged pinned - revision. The visible frontend restored the edited style prompt, lyrics, - 30-second duration, 30 denoising steps, bfloat16/model-CPU offload, and seed 7 - after refresh; collapsed and expanded exports matched. The resulting - 30.02-second stereo 44.1 kHz WAV passed decoding, non-silence, clipping, - duration, and SHA-256 checks. Human review approved the audio. -- Human review approved the Qwen image and MiniMax audio. The first Wan video - was rejected because its distant subject made motion read as static and its - quality-5 H.264 export was visibly pixelated. The rejected evidence is - preserved under `wan-21-t2v-showcase/rejected-distant-dancer-seed-280828/`. - The replacement keeps the model card's stable native 832x480 resolution, - raises ordinary video export to the app's delivery-quality default of 8, - uses guidance 6, and makes a large close subject traverse the frame and - collide with multiple objects. -- The replacement Wan Cluster completed task `yXVYtz06P_nt` through the full - frontend lifecycle. Its 81-frame, 5.4-second H.264 MP4 is 3,669,246 bytes, - has a sampled motion mean of 58.0198, and preserves the new prompt, geometry, - frame rate, guidance, steps, and seed across save and browser refresh. The - native replacement is pending human review. -- An optional delivery derivative completed task `aRXn4cNS0Eql` through a - separately saved and refreshed frontend `SpandrelVideoUpscale` workflow. - Visible Expert Model Manager admitted - `nateraw/real-esrgan@42efb9c3eeed1f5c0c8a626cf5f7f4481dfbb094`, - the runtime released five resident Wan nodes before the model-family switch, - and the app produced an 81-frame 1664x960 quality-8 MP4 in 91.58 seconds. - The 15,739,015-byte derivative and native MP4 are separate review items. -- Human review rejected both the replacement 1.3B native clip and its 2x - derivative because the apparent motion consisted of abrupt frame-to-frame - jumps. Upscaling did not repair temporal coherence. Both review decisions - are closed and preserved; neither output is eligible for promotion. -- The stronger cached Wan 2.1 I2V 14B Cluster completed task `Nop-smF5yYmu` - through insert, edit, save, browser refresh, 15-block expansion parity, - collapse parity, preparation, generation, decode, export, and frontend - download. The official 832x480, guidance-5, 16-fps, 512-token controls and - motion-focused prompt were retained. A measured 81-frame/50-step benchmark - projected about 9.5 hours on this ROCm APU, so the review run used a valid - 49-frame (`4k+1`), 30-step bounded profile. The resulting 3.06-second H.264 - MP4 decoded to exactly 49 frames. All adjacent-frame continuity checks pass: - no abrupt outliers, 0.999722 fifth-percentile adjacent structural - correlation, and bounded transition dispersion. Automated screening left it - pending because it cannot approve perceived smoothness or showcase motion. - Human review then rejected it as effectively static: the camera, - rain, and foliage motion requested by the prompt were not meaningfully - visible. -- The review builder now reports motion presence separately from continuity. - The rejected observatory clip remains free of abrupt transition outliers, - but its 0.4393/255 median and 0.5495/255 p95 adjacent luminance change fail - the meaningful-motion screen, so `technicalChecks.nonStatic` is false. This - prevents accumulated distant-frame drift from masking a nearly frozen clip. -- Model Manager safely rotated out the closed Wan 2.1 I2V 14B and Wan 2.1 T2V - 1.3B snapshots after confirmation, preserving their workflows and evidence. - The visible browser flow reclaimed 119,033,230,186 bytes and retained the - runtime-release receipts under `storage-turnover/`. -- The replacement route is the exact Apache-2.0 Wan 2.2 I2V A14B artifact at - `596658fd9ca6b7b71d5057529bbf319ecbc61d74`. Its source fixture is the guitar - image used by the official Diffusers model card. The prompt follows the - upstream I2V guidance: under 100 words, focused on visible hand/head motion, - with a fixed camera so actual subject motion is auditable. The first - 17-frame canary was rejected before denoising by the production quality - contract, which requires at least 81 frames for Wan I2V. The corrected - qualification-only canary retains 81 frames while bounding sampling to 12 - steps; it runs before the full reviewed 832x480, 81-frame, 40-step, - dual-guidance-3.5 recipe. -- The corrected canary passed Cluster insertion, parameter editing, Save and - browser refresh, collapsed/expanded parity, and exact artifact preparation. - During the first valid 81-frame execution, the pinned A14B pipeline reached - about 112.8 GB anonymous RSS on this 121 GiB host. ROCm then requested more - shared memory and the Linux OOM killer terminated the worker and headless - browser before denoising produced an output. No canary asset or promotion - receipt was created. The next run must use a proven bounded-memory execution - recipe; repeating `model_cpu` offload unchanged is prohibited. -- Per-offload A14B resource admission is now enforced before model loading. - CPU-resident BF16 routes require 160 GiB system RAM; the disk-group route - requires 96 GiB system RAM, 24 GiB accelerator-accessible memory, and - 140 GiB free disk. Auto selects the first feasible reviewed mode instead of - retrying an infeasible saved mode. Focused backend coverage (142 tests) and - the complete client template suite (139 tests) pass. A new visible-frontend - canary is currently running with `group_disk`: its 38 GiB offload store - reduced worker RSS to about 5 GiB and reached the real 12-step denoising - loop without repeating the OOM. -- The corrected Wan 2.2 A14B `group_disk` canary completed task - `IZuRsOqU9AIJ` through visible insertion, edit, Save, refresh, expansion, - collapse, preparation, generation, export, and frontend download. The app - then released one resident model, six cached node objects, and all 34 - temporary offload files without an error. Its 832x480, 81-frame, 16-fps - H.264 output is a valid non-static lifecycle/resource canary, but the - 12-step frames are overexposed and visually abstract; it is not eligible for - showcase promotion. The exact A14B snapshot remains installed pending the - review/turnover decision. -- The standard Diffusers Wan 2.2 TI2V 5B Cluster now reports its legacy cached - snapshot against the exact catalog commit instead of briefly presenting an - erroneous Install action. Its reviewed human label is retained consistently - in the node library and inserted graph. The live browser proof has saved and - refreshed `Wan 2.2 TI2V 5B Boxing Showcase`, preserved the expanded internal - scheduler edit from flow shift 4.5 to the pinned value 5, explicitly proved - expanded-state persistence, and submitted task `k6Q-Oe_fDM8n` with the full - official 1280x704, 121-frame, 50-step, guidance-5, 24-fps recipe. The task - completed through the visible frontend and exported a 9,673,535-byte MP4 - with media hash - `sha256:bytes:069e3c204d8ffb6a38b50ad1dda587c076e245c783e078413fd6e048511a7255`. - The receipt records collapsed/expanded graph equivalence and the pinned - official Diffusers recipe. The asset is staged for human review; - publication remains unchanged until approval. - -Seven harness/runtime defects found by this real campaign were fixed: - -1. Qwen's `text_encoder` is an upstream auto-selector; the editable official - branch is `text_encoder/text_encoder`. The frontend E2E now targets the - exact active branch instead of waiting on the sealed selector. -2. Model discovery treated the query string `refresh=false` as true and three - Model Manager requests could rescan the large cache independently on the - aiohttp event loop. Query parsing is now explicit and concurrent refreshes - share one background actualization. The focused backend regression is - green (24 tests and 29 subtests). -3. Custom-named Playwright output was not ignored and Vite watched long-run - trace resources. That created tens of thousands of Git-visible temporary - files and exhausted the host file-watcher limit. Custom result/report - directories are now ignored, Vite excludes them from HMR watching, and - long-running qualification can disable trace/video capture while retaining - the compact review media and receipts. -4. Ordinary `modules.Video.Export` used a quality-5 default while the app's - delivery exporters and video upscaler used quality 8. The ordinary exporter - now inherits quality 8, with backend and live-browser regression coverage. - The E2E assertion resolves immutable live defaults separately from explicit - workflow overrides, matching the app's persistence contract. -5. Wan's active I2V Auto-block branches reuse `WanImageResizeStep` in both the - image-encoder and VAE-encoder paths. Conditional expansion previously could - not map those branch-container paths back to their two exact reviewed - `get_workflow()` placements. The client now removes only immutable declared - branch segments and requires a unique reviewed legacy-path match. The - repeated-block regression and all 29 Cluster/node-library tests pass. -6. Long-running live-model tests had a one-hour active-execution budget even - when their surrounding Playwright test explicitly allowed six hours. Normal - live tests remain capped at one hour; only - `MODIFF_LONG_RUNNING_QUALIFICATION=1` receives a ten-hour active-model - budget within an eleven-hour model-I/O deadline. Ordinary live tests remain - capped at one active hour. -7. Model Manager treated a missing Auto recipe as stronger than the exact - artifact's installed status. That left reviewed Expert-only Cluster models - labelled `Installing` after a successful download. Installation readiness - now accepts either a runnable exact artifact or a ready Auto plan, with a - regression proving the Expert-only case. -8. The Model Manager cache-turnover browser test still expected the retired - confirmation copy. It now verifies the current safe-deletion dialog and - exact-revision action. The run also fixed its visible pluralization from - `dependencyies` to `dependencies`. - -## Phase 2 — exhaust currently installed exact artifacts - -Target: 3–5 focused working days, excluding reviewer response time. - -### Current locally unblocked queue (2026-08-29) - -The live library/cache join currently finds 46 graph-qualified Diffusers -admissions whose exact main artifact and every sealed model dependency are -already cached. Two Wan Animate 2 admissions remain hardware-blocked by their -mandatory compiled Flex Attention/remote-heavy qualification. The other 44 do -not need another model download or license acknowledgement before lifecycle -testing. - -- The Wan 2.2 A14B I2V lifecycle/resource canary is complete and awaiting its - review/turnover decision; its output is explicitly not a showcase candidate. -- The Wan 2.2 TI2V 5B standard-Diffusers route completed task - `k6Q-Oe_fDM8n` after passing insert, edit, Save, refresh, expanded-state - persistence, collapse, exact runtime preparation, generation, and export - through the visible frontend. Its full-recipe MP4 is pending human review. -- 10 Qwen routes remain after the approved Qwen text-to-image route: five - Qwen Image condition/edit routes, two Qwen Image Edit routes, two Edit Plus - modes, and Layered decomposition. - The next-wave preflight confirms that all four source/mask/control fixtures - exist and that Qwen Image 2512, Image Edit, Edit Plus, Layered, and the exact - InstantX ControlNet Union dependency are complete at their sealed commits. - Every reviewed file selection matches its current app-owned cache plan; no - Qwen download or legal acknowledgement is required for this batch. -- 8 FLUX routes are cached: FLUX.1 text/image, Kontext text/edit, and FLUX.2 - Klein/Base text/edit. -- 18 SDXL routes are cached with their sealed auxiliaries. -- 2 Z-Image routes, 1 ERNIE route, and 1 Wan FLF route remain in the local - qualification/review queue. -- MiniMax Music 3 and Qwen text-to-image already passed the strengthened - lifecycle and received asset approval. Their exact route-specific promotion - receipts are now checked in and only those two admissions publish - `liveProof: true`; executable and Auto eligibility remain dynamic - runtime/resource decisions. -- The closed MiniMax family has a fresh dependency-protecting Model Manager - deletion preview. Its approved WAV is retained independently, the exact - 28,517,608,999-byte revision has no open canonical or saved-workflow - dependencies, and no deletion has been performed. -- Full regression, temporary offload cleanup, evidence reconciliation, and - dependency-aware Model Manager turnover remain unblocked shared work. -- The installed Wan 2.2 TI2V 5B standard Diffusers route is represented by a - first-party standard-pipeline Cluster without reusing the incompatible - dual-expert Wan22 Modular block hierarchy. The current official - `WanPipeline` call exposes text-to-video only, so the Cluster does not - advertise native-code image conditioning. - -### Cached-route campaign execution log (2026-08-29) - -The current review bundle is -`data/qualification/local-review/hugging-face-clusters/cached-route-campaign-2026-08-29/`. -It contains only generated model media in its review index; browser screenshots -remain supporting evidence and cannot be selected as the route asset. - -- All 10 selected Qwen routes passed the visible Cluster lifecycle. Layered - decomposition retains each generated layer as a separate review asset. -- All 8 cached FLUX routes passed: FLUX.1 text/image, Kontext text/edit, - FLUX.2 Klein text/edit, and FLUX.2 Klein Base text/edit. -- All 18 SDXL routes now have current - insert/edit/save/refresh/expand/parity/generate receipts. The second batch - added base text/image/inpaint, three ControlNet routes, ControlNet Union - text-to-image, IP-Adapter text-to-image, and IP-Adapter + ControlNet Union - inpainting instead of relying on loose historical outputs. -- Both Z-Image routes and the ERNIE Image text-to-image route passed through - the visible frontend at their exact pinned revisions. -- Wan 2.1 FLF2V passed task `Y0rFaetIr2bk` through two endpoint-image edits, - Save, refresh, 17-block expansion parity, and real MP4 generation. Its - nine-frame/eight-fps output is qualification evidence, not a showcase claim. -- The completed private review page validates 42 of 42 generated assets across - all 40 planned cached routes. These map to 39 definitions because Qwen Image - Edit Plus has two admitted modes in one definition; the three Layered media - items are retained separately. Every decision remains pending in this - campaign; publication and model deletion remain unchanged. -- Repeated SDXL IP-Adapter workflow instances exposed duplicate - `CLIPVisionModelWithProjection` loads with the same immutable Diffusers load - ID. The loader now reuses one exact shared image encoder across Cluster - instances and fails closed on ambiguous or dtype-incompatible resident - state. Focused backend coverage proves reuse by separate node instances and - the two failure paths. - -The Wan 5B standard-pipeline Cluster implementation must therefore: - -1. [x] add a reviewed `diffusers.composite` definition kind derived from the - existing sealed `wan-22-ti2v-5b:text-to-video:v1` Studio execution spec; -2. [x] retain the exact loader, generate, export/preview roles and edges as the - expandable internal graph, labelled as Diffusers components rather than - pretending they are upstream Modular Diffusers sub-blocks; -3. [x] extend backend and frontend library validation with separate immutable - composite block identifiers, hashes, and task membership; -4. [x] reuse the existing Cluster instance isolation, parameter override, - save/refresh, fork-to-User-Node, materialization, and runtime-authority - machinery; -5. [x] qualify the exact cached - `Wan-AI/Wan2.2-TI2V-5B-Diffusers@b8fff7315c768468a5333511427288870b2e9635` - artifact through the visible frontend; and -6. [x] keep native Wan-code image conditioning out of this Diffusers Cluster until - the official Diffusers call itself exposes and MoDiff reviews that input. - -The standard-composite implementation is covered by exact backend library, -runtime-authority, API, model-capability, and client parser contracts. Its live -frontend lifecycle and exact cached execution are complete. The remaining -route-specific work is human review, a definition-specific promotion receipt -if approved, and dependency-aware cache turnover after its resident family is -closed. - -Run families in model-resident batches. Reviewer approval can overlap with the -next family, but deletion cannot. - -| Order | Resident batch | Routes | Approximate main-model storage | Rotation rule | -| ---: | --- | ---: | ---: | --- | -| 1 | Qwen Image, Edit, Edit Plus, Layered | 10 | 215 GiB | Delete only after all ten route decisions are closed. | -| 2 | Wan 2.1 and Wan Animate 2 | 5 | 280 GiB | Retain all source fixtures and approved MP4s first. | -| 3 | FLUX.1, Kontext, FLUX.2 Klein/Base | 8 | 145 GiB | Preserve model-specific outputs and revisions separately. | -| 4 | Z-Image and ERNIE Image | 3 | 60 GiB | Do not merge their promotion evidence. | -| 5 | MiniMax Music 3 | 1 | 27 GiB | Preserve WAV/FLAC master plus preview. | -| 6 | SDXL and admitted auxiliaries | 18 | 10 GiB plus auxiliaries | Delete only auxiliaries with zero remaining consumers. | - -For every definition: - -- [ ] Insert the exact first-party Cluster. -- [ ] Expand the official block hierarchy. -- [ ] Modify at least one meaningful internal parameter. -- [ ] Save and refresh the browser. -- [ ] Verify all model, prompt, input, and execution values exactly. -- [ ] Generate through the visible frontend. -- [ ] Verify collapsed/expanded execution parity. -- [ ] Validate the media contract and output fingerprint. -- [ ] Add the result to the manual-review queue. -- [ ] Record approve, reject, or waive. -- [ ] Generate a definition-specific promotion receipt after approval. -- [ ] Run the exact Model Manager deletion preview after the family closes. -- [ ] Obtain deletion confirmation and retain the turnover receipt. - -## Phase 3 — rotate through locally feasible new families - -Target: 5–8 focused working days after required acknowledgements and access. - -Install and qualify one family at a time after approved cached families free a -safe storage margin. - -### Stable Diffusion 3 - -- Routes: text-to-image and image-to-image. -- Exact repository: - `stabilityai/stable-diffusion-3-medium-diffusers@ea42f8cef0f178587cf766dc8129abd379c90671`. -- Repository size: about 31 GB; reviewed selected weights: about 15.5 GB. -- Required user/legal gate: affirmative Stability AI license acceptance and a - statement that this qualification is non-commercial research and not a - hosted/API/commercial use, or evidence of a separate applicable license. -- Engineering gates: authenticated component review, backend-owned bounds, - optional-runtime admission, real AMD/ROCm execution, output review. -- Estimate after authorization: 1 focused working day. - -### Krea 2 Raw and Turbo - -- Exact repositories: - - `krea/Krea-2-Raw@6b0ece7fffb640c5e3bcbe0a7f10f66b8e60a603` - - `krea/Krea-2-Turbo@98e0fe118d17c9e3547fbb2e25acdbae2cadf7c7` -- Each planned full app snapshot: about 62 GB; selective qualification target: - about 48 GB. -- Required user/legal gates: Community License and incorporated AUP acceptance, - confirmation of eligibility under the revenue term or separate enterprise - authorization, and authorization to implement the required notices and - content filtering. -- Engineering gates: immutable AUP receipt, downstream terms/notice UX, - content-filter integration, backend bounds, optional runtime, AMD run. -- Estimate after authorization: 1–2 focused working days. - -### HunyuanVideo 1.5 - -- Routes: text-to-video and image-to-video. -- Governing repository/license revision: - `tencent/HunyuanVideo-1.5@9b49404b3f5df2a8f0b31df27a0c7ab872e7b038`. -- Diffusers artifacts: - - T2V `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v@286be7ce72277246578a3e3cc2487e95ddae5bcf` - - I2V `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_i2v_step_distilled@854c04a4c8a53d990b418c7478f0802c0fc8c726` -- Selective disk estimates: about 68 GB T2V and 52 GB I2V. -- Required user/legal gates: license/AUP acceptance, confirmation that all - execution and output use remains outside the excluded territories, MAU - threshold eligibility or separate license, distribution notice, and - generated-content disclosure. -- Engineering gates: exact territory disposition, backend resource bounds, - AMD/ROCm execution, output review. -- Estimate after authorization: 1–2 focused working days. - -### LTX-2.5 - -- Routes: text-to-video, image-to-video, condition, and in-context. -- Exact repository: - `Lightricks/LTX-2.5-Diffusers@a6de4b5354f078db24d9cf4778c14846788aea3d`. -- The existing local entry is a zero-byte gated placeholder. -- Target distilled snapshot: about 72 GB excluding `transformer_full`; the full - repository is about 110 GB. -- Earlier acceptance for LTX-2 revision - `47da56e2ad66ce4125a9922b4a8826bf407f9d0a` does not automatically accept the - separate LTX-2.5 Hugging Face access/privacy gate. -- Required user/legal gates: exact LTX-2.5 gate/privacy consent, LTX-2.x license - acknowledgement for the pinned revision, revenue eligibility or separate - authorization, and authenticated Hugging Face access. -- Engineering gates: authenticated index/partition review, exact distilled - sigma schedule, AMD compatibility of the diffusion decoder and fetched - NATTEN/kernel implementation, resource bounds, four live outputs. -- A convolutional decoder substitution is a modified User Node unless it is - separately reviewed and admitted as a first-party Cluster workflow. -- Estimate after authorization: 2–3 focused working days; CUDA-only decoder - behavior may move exact qualification into Phase 4. - -## Phase 4 — remote-hardware or infrastructure-bound families - -Target: 4–8 focused working days after suitable compute, access, region, and -spend authorization exist. Calendar completion is blocked until they exist. - -### Cosmos 3 - -- Routes: 4 Distilled and 10 Omni workflows. -- No current Hugging Face click-through acceptance identified; model cards - declare OpenMDW 1.1, global deployment, and commercial/non-commercial use. -- Engineering gates: immutable component descriptors, standard-index class - resolution, qualified mandatory guardrail, AMD/runtime validation. -- Cosmos Nano planning envelope: about 64 GiB disk, 96 GiB RAM, and 24 GiB - accelerator memory with offload. Attempt local qualification only after the - component and guardrail gates close. -- Cosmos Super/Distilled 64B routes require remote multi-GPU hardware; the - reviewed estimate is about 192 GiB system RAM and 128 GiB aggregate - accelerator memory. -- Required external authorization: provider, allowed region, credentials, - spending ceiling, retention policy, and permission to upload safe fixtures. - -### MiniMax H3 - -- Routes: T2VA, FL2VA, and Ref2VA. -- Exact repository: - `MiniMaxAI/MiniMax-H3@42ed227ee7df40d41602854ae760620d6eb651fe`. -- Selective estimate: about 160 GiB; full repository is substantially larger. -- Planning envelope: at least 256 GiB RAM and about 192 GiB aggregate - accelerator memory across four accelerators. -- Required user/legal gates: Community License/AUP acceptance, confirmation - that execution, hosted access, and output use remain outside the United - States, EU, UK, and Republic of Korea, revenue eligibility below the stated - threshold or separate authorization, and distribution/AI-output notices. -- Required external authorization: permitted-region four-GPU environment, - credentials, budget, storage, and retention policy. - -### Ideogram 4 - -- Route: text-to-image. -- Preferred package-owned candidate: - `ideogram-ai/ideogram-4-nf4-diffusers@1874bc70267ba2c823a7239e1d70dd308c8d64dc`. -- Visible repository size: about 16 GB. -- Required user/legal gates: exact non-commercial model agreement acceptance, - confirmation that this use is non-commercial or separately licensed, - incorporated AUP, downstream notices, human oversight, disclosures, and - output safety filtering. -- Hardware gate: the official Diffusers route uses CUDA/NF4. The current - AMD/ROCm machine cannot qualify that exact route. The publisher's FP8 variant - is not supported by the reviewed Diffusers recipe. -- Required external authorization: CUDA environment, credentials, permitted - use, budget, and retention policy. - -## User and external blockers - -At campaign start: - -- The app reports no configured Hugging Face token. Gated downloads require the - user to accept the repository gate on their Hugging Face account and configure - that account's token through the frontend; credentials must not be sent in - chat or written into receipts. -- Revision-bound acknowledgements remain required for LTX-2.5, SD3, both Krea - variants, HunyuanVideo 1.5, MiniMax H3, and Ideogram 4. -- Remote-only work requires explicit provider, region, spend, fixture-upload, - and retention authorization. Local full access does not grant cloud spend or - third-party data-transfer authority. -- Public promotion requires the reviewer's definition-specific output approval. -- Physical macOS qualification remains separate from the AMD/ROCm Linux gate. - -## Timeline - -Assuming acknowledgements and remote compute arrive without delay: - -- First image/video/audio review batch: within one working day. -- All 45 currently cached routes: 3–5 focused working days. -- New locally feasible families: an additional 5–8 focused working days. -- Remote/infrastructure families: an additional 4–8 focused working days. -- Full remaining Diffusers execution qualification: 12–21 focused working - days, realistically 3–4 calendar weeks including downloads and review cycles. - -A rejected image/audio asset typically adds 1–3 execution hours. A rejected -quality video may add 4–12 hours. Upstream AMD incompatibilities or unavailable -remote multi-GPU capacity can extend the corresponding family independently; -other admitted families should continue rather than waiting idle. - -## Completion definition - -The campaign is complete only when: - -- every reviewed Diffusers definition has either an approved exact execution - receipt or a clearly documented unresolved external blocker; -- all executable routes preserve edited values across save and browser refresh; -- expanded and collapsed executions are equivalent; -- approved assets and revisions are retained independently of model cache - turnover; -- deletion receipts exist for rotated models; -- public execution flags match the approved evidence exactly; -- no policy-gated definition is advertised as executable by inference. - -## Receipt-backed promotion and cached-route campaign — 2026-08-29 - -- [x] Added the checked-in, data-only - `data/huggingface-cluster-promotion-receipts.v1.json` ledger and a strict - backend validator. A receipt is accepted only when its definition, - workflow, admission, immutable model revision, generated-media hash and - size, runtime fingerprint, task identity, lifecycle proof, review - decision, and publication claims all match exactly. -- [x] Bound the approved Qwen Image 2512 text-to-image and MiniMax Music 3 - receipts to their exact admissions. Only those routes now report - `liveProof: true`; `executable`, `autoEligible`, and `galleryEligible` - remain false until the separate runtime/resource and publication gates - close. Other routes sharing the same model profile do not inherit the - proof. -- [x] Preserved the complete Wan 2.2 TI2V 5B terminal execution receipt at - `data/qualification/local-review/hugging-face-clusters/showcase-rotation-2026-08-28/wan-22-ti2v-5b-cluster/backend-execution-receipt.json`. - The receipt records the exact task, artifact revision, runtime/resource - fingerprints, phase timings, peak measurements, generated MP4 hash, and - dedicated-worker exit without claiming an unavailable in-process cleanup - receipt. Promotion remains pending reviewer approval of that output. -- [x] Re-ran the complete backend gate after the receipt binding, retained - Modular cleanup correction, and shared IP-Adapter encoder fix: `2,265` - passed, `54` skipped, and `6,730` subtests passed in 127.14 seconds. - Focused IP-Adapter coverage passes `10` tests and `4` subtests. -- [x] Re-ran the complete client release gate. Formatting, linting, TypeScript, - unit tests, production build, and bundle budgets pass; the complete - mocked Studio browser suite passes `114/114` scenarios in 6.1 minutes. -- [x] Generated a dependency-aware MiniMax Music 3 deletion preview without - deleting the model. The preview found no canonical or saved-workflow - dependents, retains the approved WAV/hash, and remains subject to explicit - turnover confirmation. -- [x] Removed eleven unpinned app-temporary media assets through the app's media - cleanup path after verifying that none belonged to an active or queued - task. Review assets and model snapshots were not touched. -- [x] Completed the visible-frontend Qwen batch covering ten exact cached - routes: image edit, image-to-image, inpaint, edit inpaint, Edit Plus - single and multi-reference modes, layered decomposition, and ControlNet - text-to-image, image-to-image, and inpainting. Every route performed - insert, edit, Save, browser refresh, expanded/collapsed graph parity, real - generation, and media capture. The strengthened run completed five test - cases (eight routes, with the first two already proven immediately before - the rerun) in 11.8 minutes with five passing and two intentionally skipped. -- [x] Corrected conditional-workflow parameter placement for reviewed Modular - Diffusers branches whose exact `get_workflow()` block has a distinct - upstream id but the same class, kind, input/output, component, and config - shape as its Auto-pipeline branch. The Qwen inpaint lifecycle now expands - with its required `image_latents` parameter and executes successfully; - unsafe class or shape mismatches still fail closed. -- [x] Corrected cross-model retained-component cleanup ordering. The app now - releases the Modular `ComponentsManager` collection once before clearing - loader nodes, avoiding repeated group-offload reconfiguration and garbage - collection for every destructor. The exact Qwen Image to Qwen Image Edit, - Edit Plus, Layered, and ControlNet transitions completed without the prior - RAM/swap thrash. -- [x] Built the private cached-route review index with twelve actual backend - image assets for the ten routes (including three layered outputs), each - pinned to its task, definition, model revision, and exact media filename. - Companion browser screenshots are excluded. Review decisions remain - pending and no new public promotion is inferred. -- [x] Completed the post-change mocked Studio browser gate, reconciled the - cached-route receipts into a 42/42 generated-asset review index, audited - the two source repositories, removed disposable Playwright output, and - created the dependency-aware Qwen deletion preview. -- [ ] Record reviewer decisions for the 40 cached routes, create promotion - receipts only for approved definitions, then run fresh per-family Model - Manager deletion previews. Do not delete any snapshot until that family - is closed and turnover is explicitly confirmed. diff --git a/docs/exact-modular-diffusers-block-expansion-plan-2026-09-04.md b/docs/exact-modular-diffusers-block-expansion-plan-2026-09-04.md deleted file mode 100644 index e5b3925f..00000000 --- a/docs/exact-modular-diffusers-block-expansion-plan-2026-09-04.md +++ /dev/null @@ -1,267 +0,0 @@ -# Exact Modular Diffusers expansion plan - -Status: Qwen text-to-image implemented and qualified; generic rollout pending -Owner: MoDiff -First qualification target: `QwenImageModularPipeline` / `text2image` -Pinned model: `Qwen/Qwen-Image-2512@25468b98e3276ca6700de15c6628e51b7de54a26` - -## Problem - -The current registered Qwen Cluster is a valid `BlockDefinitionV2`, but its -effective graph is compiled from a five-node MoDiff execution skeleton: -`ModelsLoader`, `EncodePrompt`, `Denoise`, `DecodeLatents`, and `Preview`. -That graph is executable, but it is not the exact Modular Diffusers workflow -the Cluster claims to expose. The reviewed catalog already records the exact -upstream block classes, placement paths, fields, components, and ordering, but -the V2 compiler currently discards that information. - -The first node labelled `models` is the model loader. Its current label hides -its purpose and its collapsed controls make the pinned repository difficult to -discover. The expanded graph also hides the actual component inventory and the -twelve Qwen text-to-image block placements. - -## Required user-visible result - -Collapsed mode remains one ordinary Cluster Node with creator starter values, -public inputs, public outputs, preview, resize, and the same action toolbar as a -User Node. - -Expanded mode is one resizable container holding ordinary connected nodes: - -1. `Load Qwen Image Components` (MoDiff infrastructure) -2. `QwenImageTextEncoderStep` (`text_encoder`) -3. `QwenImageTextInputsStep` (`denoise.input`) -4. `QwenImagePrepareLatentsStep` (`denoise.prepare_latents`) -5. `QwenImageSetTimestepsStep` (`denoise.set_timesteps`) -6. `QwenImageRoPEInputsStep` (`denoise.prepare_rope_inputs`) -7. `QwenImageDenoiseStep` (`denoise.denoise`, loop owner) -8. `QwenImageLoopBeforeDenoiser` -9. `QwenImageLoopDenoiser` -10. `QwenImageLoopAfterDenoiser` -11. `QwenImageAfterDenoiseStep` (`denoise.after_denoise`) -12. `QwenImageDecoderStep` (`decode.decode`) -13. `QwenImageProcessImagesOutputStep` (`decode.postprocess`) -14. `Preview Image` (MoDiff infrastructure) - -The loader must visibly show the immutable repository/revision, pipeline and -blocks class, component list, dtype, device, offload, and quantization policy. -Component loading is an explicit MoDiff infrastructure node because upstream -Modular Diffusers separates `ModularPipeline.from_pretrained()` / -`load_components()` from the compute block tree. - -## Contract invariants - -- `Cluster` and `User Node` are catalog/ownership classifications, not - different canvas types or renderers. Both use `BlockDefinitionV2`, - `BlockInstanceV2`, the `block` canvas type, and `BlockNode`. -- `BlockDefinitionV2.graph` is the editable semantic graph shown to the user. - It may not silently substitute broader convenience stages for reviewed - upstream blocks. -- A registered Diffusers graph node identifies whether it is MoDiff - infrastructure or an exact upstream block, and exact blocks carry the pinned - class, definition id, placement path, block kind, and contract hash. -- Runtime lowering is explicit, versioned, and hash-bound. It may combine - leaf steps for efficient execution only if every editable field and - structural operation has a deterministic upstream meaning. -- Decorative or non-executing "fake block" nodes are prohibited. Until a - structural operation can be rebuilt through upstream `init_pipeline()`, the - editor must reject it with a precise Fix diagnostic instead of accepting an - ineffective edit. -- A parameter-only edit changes only its bound value. It must not change node - identity, sockets, layout, unrelated defaults, or model selection. -- A structural edit changes only that workflow instance to user ownership. - The registered definition remains immutable; the modified instance and any - saved User Node preserve all values and layout. -- Save, browser refresh, and backend restart preserve repository, revision, - prompts, parameters, public interface, internal topology, and layout. -- Collapsed and expanded views execute the same effective graph. -- Cluster Nodes and User Nodes cannot be nested. -- Runtime/resource work must not rewrite creator defaults merely to make a - model fit. Auto may propose an explicit preset; non-Auto reports actual - errors and lets the operator decide. - -## `BlockDefinitionV2` maintenance - -`BlockGraphNodeV2` gains optional `modularDiffusers` metadata: - -```text -kind: infrastructure | upstream_block -pipelineClass / blocksClass / workflowId -blockDefinitionId / blockClass / blockKind / blockContractHash -placementPath (array, preserving dotted top-level keys as one segment) -parentPlacementPath (for loop/container ownership) -componentNames -runtimeRole -``` - -The Python and TypeScript validators, canonical JSON/hash fixtures, API docs, -migration inventory, and normative unified-composite contract must change in -lockstep. Unknown metadata fields continue to fail closed. - -This metadata is identity/provenance, not a second value store. Parameters -remain in `node.data.params`; instance values remain in -`BlockInstanceV2.values`; layout remains in -`BlockInstanceV2.presentation.internalLayout`. - -A reviewed checkpoint choice within the same pipeline/workflow contract is a -normal bound `BlockControlV2`, not a mutation of `BlockDefinitionV2.source`. -The registered definition keeps its immutable baseline repository and content -hash; the workflow-owned `BlockInstanceV2.values` stores the selected variant. -Its backing graph field contains the deterministic option list copied from the -registered route's exact `reviewedArtifacts`; it must not depend on ambient or -transient node-registry options. Adding, removing, or reordering a reviewed -choice therefore changes the canonical `BlockDefinitionV2` and requires a new -content hash, canonical SHA-256 pin, and route audit. -Before execution the backend must resolve that value through the exact -pipeline-family allowlist and immutable artifact catalog. If changing a model -requires different blocks, ports, or edges, it is a different registered -definition/route and cannot use this same-family control. - -## Runtime design - -### Semantic graph - -The registered compiler consumes the exact `blockPlacements` and -`blockDefinitions` from the reviewed Hugging Face node library. It builds -ordinary semantic nodes and explicit typed state/component edges. Loop -membership is metadata and visible links, not nested Cluster/User Nodes. - -### Lowering - -The executor lowers a hash-bound semantic graph to runnable MoDiff nodes: - -- loader infrastructure loads/reuses components from the exact Hub revision; -- upstream blocks execute through reviewed package-owned adapters; -- loop children remain owned by the reviewed loop and are rebuilt through - `init_pipeline()` when their order or membership changes; -- preview infrastructure consumes the exact declared workflow output. - -The first Qwen delivery may only advertise the unmodified route and supported -parameter mappings as executable. Unsupported add/replace/delete/reconnect -operations must produce a visible compatibility diagnostic until the -composition recipe validates and rebuilds successfully. - -## Implementation phases - -### Phase 1 — contract and source pin - -- Add and validate `modularDiffusers` node metadata in Python and TypeScript. -- Update canonical cross-runtime fixtures and contract documentation. -- Add an exact Qwen topology compiler fixture containing the 12 reviewed - placements, component inventory, and immutable revisions. - -### Phase 2 — Qwen semantic compiler - -- Replace the five-card semantic definition with loader + 12 upstream blocks - + preview. -- Use human-readable labels while retaining exact class/path in the node help. -- Lay out the loop children as ordinary nodes inside the Cluster frame and draw - entry, sequence, feedback, and exit links. -- Project creator inputs to the exact owning block fields. - -### Phase 3 — executable lowering - -- Add reviewed Qwen step/loop adapters or an equivalent versioned lowering - that preserves exact block semantics. -- Load components exclusively through `huggingface_hub`/Diffusers Hub APIs. -- Bind the exact model/revision and component types at runtime. -- Rebuild changed supported topology through upstream `init_pipeline()`. - -### Phase 4 — editing and persistence - -- Parameter edits update only the addressed binding. -- First structural edit changes instance ownership to a User Node without - replacing the canvas root or public interface. -- Validate node removal/replacement/reconnection; highlight the first invalid - block/edge and offer a precise Fix action. -- Preserve independent instances and all three save choices: update existing - User Node, save as a new User Node, or keep workflow-only. - -### Phase 5 — qualification - -- Unit: strict schema, canonical hash, exact topology, component inventory, - typed edges, loop ownership, and lowering validation. -- Browser: insert/click/drag feedback, collapse/expand, resize root and child, - edit prompt/steps, save, refresh, restart, and compare exact persisted data. -- Real frontend/backend: collapsed run, expanded run, output fingerprint - parity, parameter-only run, and one supported structural composition run. -- Regression: complete client unit/typecheck/build, backend suite, mocked - Studio browser suite, and backend-served clean-browser smoke. - -### Phase 6 — generic rollout - -After Qwen text-to-image passes, apply the same compiler to the remaining Qwen -workflows, then other reviewed Diffusers families. A definition is draggable -only when its exact semantic graph and runtime lowering are admitted. Catalog -classification never changes its renderer. - -## Acceptance criteria for Qwen text-to-image - -- Expanded view visibly names the loader and shows - `Qwen/Qwen-Image-2512` plus the exact revision. -- All 12 pinned upstream block placements appear as ordinary, resizable nodes - inside the Cluster, with correct links and loop ownership. -- The public prompt/negative prompt/size/steps inputs and images output remain - stable across collapse/expand. -- Editing one parameter changes no other value or interface field. -- Save + refresh + backend restart preserve values, topology, public sockets, - child sizes, and child positions. -- Collapsed and expanded real runs produce equivalent execution receipts and - output for the same seed. -- An unsupported structural edit cannot silently run the old graph. -- The production bundle served at `127.0.0.1:8088` passes the browser test; - testing only the development source is insufficient. - -## Known blockers and risks - -- Modular Diffusers leaf blocks communicate through mutable `PipelineState`, - including variadic kwargs bundles; those are not ordinary one-value edges. -- `QwenImageDenoiseStep` is a loop container. Its three children execute per - timestep and cannot be flattened into one pass each. -- Component replacement must satisfy the exact upstream type hints and pinned - artifact policy. -- Existing five-node Qwen instances need a hash-bound migration or an explicit - recovery copy; silently reinterpreting them is prohibited. -- Real generation remains bounded by local accelerator memory and model - availability, but those conditions do not block schema/compiler/UI work. - -## Progress log - -- 2026-09-04: confirmed the reviewed catalog contains all 12 exact Qwen - placements and component contracts; confirmed the V2 compiler currently - derives its graph from the five-role Studio execution skeleton. -- 2026-09-04: added matching Python and TypeScript - `BlockGraphNodeV2.modularDiffusers` contracts and strict validators. The - canonical definition hash now covers infrastructure/upstream identity, - exact placement paths, block contract hashes, component names, and runtime - roles. -- 2026-09-04: replaced the Qwen text-to-image five-stage semantic projection - with one explicit component loader, all twelve reviewed upstream Modular - Diffusers placements, and one preview node. The projection contains 14 - ordinary, resizable child nodes and 13 explicit state/component/loop links. -- 2026-09-04: added the reviewed runtime step adapter. It validates the pinned - pipeline/workflow/block identities and hashes, retains upstream - `PipelineState`, preserves `kwargs_type`, delegates loop iteration to the - exact upstream loop owner, and normalizes JSON boundary values before - calling Diffusers. -- 2026-09-04: real visible-frontend lifecycle passed against the pinned - `Qwen/Qwen-Image-2512` revision: insert two instances, preserve publisher - defaults, edit one instance, expand, move a child, collapse, save, refresh, - re-expand, execute, save as a User Node, refresh, and execute again. Backend - task ids: `4dkWzAlS3H59` and `93LO09k_0jQJ`. -- 2026-09-04: the original collapsed-resize regression is covered directly: - resize the collapsed Block, expand, verify all 14 children are inside the - recalculated frame, resize the loader child, move a child, collapse, refresh, - expand, and recheck containment plus both child size and position. The - focused mocked browser test, client typecheck/lint/build, and nine reviewed - runtime tests pass. -- 2026-09-04: mirrored the production client bundle to the backend and ran a - clean browser smoke against `127.0.0.1:8088`. It observed 14 child nodes, 13 - links, and zero children outside the Block frame. Current registered pin: - `block-definition-v2-ec976096` / - `sha256:fd2c47dddc69396efb255765303747bcea88b47385a3d0135ef7078e550ff74f`. -- 2026-09-04: completed the cross-route regression boundary. All 34 registered - Block V2 adapter/insertion/live-catalog audit tests pass with the exact Qwen - compiler enabled, alongside client typechecking and focused lint. The nine - backend reviewed-step/promotion tests also pass against the currently served - 222-node backend registry. diff --git a/docs/fashion-editorial-demo.md b/docs/fashion-editorial-demo.md new file mode 100644 index 00000000..acab84f3 --- /dev/null +++ b/docs/fashion-editorial-demo.md @@ -0,0 +1,221 @@ +# SoHo fashion editorial demo + +Execution success, +different image hashes and a large graph are **not** visual acceptance criteria. +The intent is an attractive, detailed editorial photograph and an unmistakable, +explainable change in every chapter. + +## Presentation structure + +| Chapter | Creative purpose | What the graph must demonstrate | +| --- | --- | --- | +| 01 | Adult woman in an ivory suit on a detailed SoHo street | Native text encoding, denoising and decoding | +| 02 | Opposed warm/cool lighting treatment | Editable custom image processing, before/after | +| 03 | Replace the suit with an emerald evening dress | Region masks, downstream generation, protected compositing | +| 04 | Replace the burgundy bag with metallic gold | Localized image-conditioned editing | +| 05 | Elaborate flower boutique | Larger architectural-region replacement | +| 06 | Vintage convertible instead of the taxi | Object replacement with the subject protected | +| 07 | Autumn foliage and fallen leaves | Coordinated environment changes | +| 08 | Rain and wet-street reflections | Material and weather transformation | +| 09 | Blue hour, illuminated windows | Time-of-day editing | +| 10 | Neon-lit night editorial | Strong atmospheric transformation | +| 11 | Monochrome editorial | Deterministic saturation control | +| 12 | Whole-pipeline alternative | Custom processing retained at the image boundary | +| 13 | Native return and final selected look | Preserved settings, connections and refresh persistence | + +Before presenting a locally curated set, verify each chapter's own retained +image after reopening and refresh. Check executable values, edges and memory +policy as well as the photograph. A different file hash is not visual acceptance. +For transfer and hardware boundaries, see [Windows demo setup](windows-fashion-demo.md). + +## Custom code with a visible purpose + +Stage [`EditorialRegions`](../examples/custom_nodes/EditorialRegions/README.md) +through **Nodes → Custom nodes**, inspect its source and explicitly enable it. +It uses the normal node executor and existing Pillow/NumPy dependencies. + +**Editorial Regions** produces an RGB image and a grayscale mask. Its polygon +list supports `add`, `subtract` and `intersect`, normalized coordinates and +feathered edges. Exposure, split color temperature and saturation are separate +controls. A subtract region can protect a face or hand. This is manual image-space +art direction, not automatic object segmentation or physical relighting. + +**Protected Editorial Composite** blends the generated image over the original +using that same mask. Black-mask pixels remain exactly the original RGB pixels. +White-mask pixels use the generated edit. Inspect transitions at feathered edges. +This guarantees pixel preservation outside the mask, not identity preservation +inside it. Reference-conditioned editing is a separate model capability. + +## Build the first three chapters + +1. In Developer, use Custom memory policy and add a native Z-Image text-to-image + connected starter. Set 1024×1024, seed `603219`, nine steps and guidance one. + Describe a full-length adult brunette woman in an ivory tailored suit, a + burgundy handbag, SoHo cast-iron storefronts, fire escapes, flowers, pedestrians + and a yellow taxi. Connect Decode Latents to Preview Image and Run. +2. Inspect the photograph before continuing. Require a complete figure, readable + outfit, plausible anatomy and detailed surroundings. Save the baseline. +3. **Save As a new chapter before editing a saved canvas.** Add Editorial Regions + and branch the decoded image into it, retaining the original preview. Set Warm + cool split to `0.8` and Exposure stops to `-0.3`; preview Directed image. +4. Save another chapter before adding a second Editorial Regions instance. Leave + its grading neutral. Cover the whole wardrobe silhouette while keeping the + face outside the edit region. Do not cut old sleeves/hands into the new garment + blindly: that caused ghost-arm artifacts in a rejected trial. Preview the mask + rather than assuming its coordinates fit every generated image. The selected + chapter03 polygon is `[[0.38,0.282],[0.66,0.282],[0.73,0.6],[0.73,0.98], + [0.32,0.98],[0.32,0.55]]` with operation `add`; chapter06 demonstrates subtraction. +5. For the rehearsed wardrobe edit, add a **FLUX.2 Klein native edit image** + starter. Connect Directed image to its image-conditioning input. Use four + steps and guidance one. Instruct it to replace only the ivory suit with an + emerald silk evening gown, retaining the exact woman, pose, burgundy bag, + shoes, background and lighting. Ordinary SDXL inpainting was tried but rejected + visually for pose/background changes; stronger denoising alone was not enough. + Klein's selected edit recipe does **not** take this custom mask: the mask is + consumed by the protected compositor in the next step. +6. Add Protected Editorial Composite: original directed image → Protected + original, diffusion output → Generated edit, region mask → Edit mask. + Connect Final image to a new preview. Compare original, mask and composite. +7. Save, refresh and confirm prompts, region JSON, parameters and connections. + Run from the restored graph. Do not regenerate earlier successful images just + to repeat a screenshot or collect a missing receipt. + +## Model switching and honest comparisons + +Use models for their supported tasks. Text-to-image generation, ordinary img2img, +instruction editing and inpainting are not interchangeable guarantees. A new +creative brief must be distinguished from a model-only A/B comparison. + +Before changing models, save a chapter and inspect the change review. Verify the +authored prompt and custom-node values survive **before** intentionally editing +them for the next artistic change. Do not imply that a changed prompt's effect +was caused solely by the model switch. Native stages and whole-pipeline routes +share a decoded-image boundary, not universally compatible latents/components. + +Some model changes add a required port. In the native Klein → Qwen Image Edit +2511 change, Qwen's prompt encoder also needs the source image. Keep the existing +image-encoder branch and explicitly connect Directed image to the new Encode +Prompt image input. Run correctly remains blocked until that input is supplied. +Do not describe this new connection as something the previous recipe already had. + +For a long editorial sequence, an explicitly loaded previous approved image can +be a checkpoint boundary. Label it as such: it prevents repeated generation and +keeps the current editing graph readable. It is not a hidden live connection to +the earlier graph. Retain the source image and its producing workflow together. + +## Present the saved sequence + +After importing a curated package, use **Workflows → My workflows**, search +**Fashion Demo**, and open the numbered chapters. Keep working attempts separate +from the selected presentation records. +Use **Save As** to make a presentation copy before experimenting. Model weights, +enabled custom code and retained local media are prerequisites, not embedded in +a workflow JSON export. + +Stay in **Developer** mode with **Custom** memory policy for this graph-level +demonstration. Click an output preview to inspect it full-size; fit-to-canvas is +for explaining connections, not judging the photograph at thumbnail scale. + +1. **01 — Establish the scene.** Show the full-length ivory suit, burgundy bag, + yellow taxi, storefronts and street detail. Explain the native text encoding, + denoising and decoding stages. This is the image all subsequent edits build on. +2. **02 — Add user code.** Show the Editorial Regions source and its actual image + connection. Compare the original and warm-left/cool-right previews. Explain + that this node handles polygon algebra and linear-light color processing, + not merely a prompt string. Its controls work on decoded images independently + of the generator family. +3. **03 — Add a generative second stage.** Show the neutral region node, mask + preview, Klein reference edit and protected compositor. The ivory suit becomes + an emerald silk gown. The complexity now has a concrete purpose: generated + wardrobe replacement while retaining pixels outside the edit region. +4. **04 — Checkpoint and edit a small object.** Explain the Load Image node: it + contains the preceding reviewed result, so the whole earlier graph need not + rerun. Show the burgundy bag changing to metallic gold. The custom mask controls + where the generated result is composited, not where Klein internally denoises. +5. **05 — Edit architecture.** The left storefront becomes a large flower arch + and warm boutique window. Compare the subject and right street, which are + outside this region. Use the mask preview to explain the scope. +6. **06 — Replace the vehicle.** The taxi becomes a cherry-red vintage convertible. + Show the broad right-side polygon and the subtract polygon protecting the + woman. A tight rectangle clipped the car in rejected trials: a meaningful mask + must allow the new object's entire geometry, not just the old object's box. +7. **07 — Change the season.** Add golden branches and fallen maple leaves. + The environment mask excludes the central subject. Point out that one custom + node can compose several regions without proliferating one-off model nodes. +8. **08 — Change materials and weather.** Wet cobblestones, reflections, rain and + mist require a global edit. The recognizable scene is retained, but global + generative edits can change pose or framing slightly; do not claim exact pixels. +9. **09 — Switch native model families.** Compare the preceding Klein graph with + native FLUX Kontext. In a live switch, inspect retained prompt/custom values + first; then change the brief to cobalt twilight and amber shop windows. The + image change is an artistic instruction, not a controlled model-only A/B test. +10. **10 — Return to Klein for neon night.** Compare blue-hour lighting with vivid + magenta/cyan practical lights and reflections. The same image-boundary custom + nodes remain useful after another native model change. +11. **11 — Make a deterministic editorial look.** This deliberately small graph + needs no diffusion model: Load Image → Editorial Regions → Preview. Saturation + zero and exposure `+0.2` create monochrome. Complexity is not the objective; + each node should earn its place by changing or controlling the result. +12. **12 — Explain whole-pipeline compatibility.** Change the editing operation to + Qwen Image 2.1. Its internal pipeline is not split into interchangeable native + modular stages. The source image, custom region node, compositor and previews + still connect at image boundaries. The brief restores color and changes the + gown to sapphire blue. Opening a workflow is never consent to install a runtime. +13. **13 — Return to a native graph.** Switch back to Klein and change night into + peach/rose-gold sunrise. Save, refresh, reopen an earlier chapter and compare + its own retained output. Show prompts, custom parameters and connections, not + just the last image in the browser cache. + +During a presentation, use retained outputs to keep the narrative moving and run +one selected edit live. Loading another model takes time and can need runtime +activation/restart; do not promise instantaneous switching. Each chapter's full +prompt and exact mask are in its saved nodes. Mask coordinates fit this particular +photograph: changing the seed or composition requires reviewing them again. + +## Local rehearsal tooling + +### Selected settings + +The selected images are 1024×1024 with fixed seed `603219`, batch one, Custom +memory policy, BF16 and model-CPU offload. Model defaults are not interchangeable. + +| Stage | Route | Steps / guidance | +| --- | --- | --- | +| Initial generation | Z-Image Turbo native modular | 9 / 1 | +| Wardrobe and most semantic edits | FLUX.2 Klein 4B native modular edit | 4 / 1 | +| Blue hour | FLUX.1 Kontext dev native modular edit | 20 / 2.5 | +| Monochrome | Custom CPU image processing only | No denoising | +| Whole-pipeline color edit | Qwen Image 2.1 whole-pipeline edit | 20 / 1 | + +Measure cold model loading separately from cached execution and browser authoring. +Activating a different optional runtime may require a backend restart; preflight +the chosen chapters before presenting. Linux shared-memory results are not +Windows GPU-memory qualification. + +### Tools + +The client repository provides these opt-in tools: + +- `scripts/run-fashion-demo.mjs ` with `MODIFF_FASHION_DEMO=1` submits + chapters serially through Playwright UI gestures. It records failures, skips a + busy backend, retains completed attempts and bounds each process. It never + retries an unchanged failure automatically. Dependent chapters require a visual + review bound to the exact preceding PNG hash; independent cases can continue. +- `scripts/prepare-fashion-demo.mjs ` + prepares reviewed local exports and an image gallery without model inference. + Only preview output fields are bound to exact-run durable media; executable + inputs and graph edges remain unchanged. +- `fashion-demo-checkpoints.spec.ts` imports the reviewed exports on the deployed + frontend and checks reopening after refresh, input/edge retention and each + displayed image's durable URL and hash. This is a targeted demo check, not a full + UI suite or qualification of every model in the registry. +- `scripts/export-fashion-demo.mjs ` creates + a separate local transfer folder containing workflows and hash-named images. + It rejects traversal, escaping symlinks, transient cache URLs and machine-local + paths. It neither publishes media nor transfers model weights or code approvals. + +The local ledger records accepted outputs, rejected attempts and actual timings. +Do not confuse completed inference with a visually acceptable result. Qwen Image +Edit 2511 was tried at 1024px but cancelled for impractical demo latency; it is not +a completed visual qualification of that model. It is distinct from the Qwen +Image 2.1 whole-pipeline chapter. diff --git a/docs/generic-nodes-upstream-assets-completion-plan.md b/docs/generic-nodes-upstream-assets-completion-plan.md deleted file mode 100644 index e9e66332..00000000 --- a/docs/generic-nodes-upstream-assets-completion-plan.md +++ /dev/null @@ -1,517 +0,0 @@ -# MoDiff Generic Nodes, Upstream Coverage, and Asset Completion Plan - -Status date: 2026-08-20 - -This document is the handoff source of truth for continuing the MoDiff campaign in a new chat. It separates source/graph readiness from real execution, asset quality, rights, hardware qualification, and release eligibility. - -## Primary goals - -1. Keep the Studio client model-neutral. It must render backend-published contracts; it must not decide behavior from Diffusers or Transformers model names. -2. Review every relevant surface in the latest pinned Hugging Face Diffusers and Transformers commits. Every upstream class or finite task semantic must have exactly one evidence-backed disposition: executable, equivalent, contract-only, research-blocked, or intentionally excluded. -3. Provide generic nodes, canonical workflows, and authoring/template contracts for every safely supportable task. -4. Generate review assets as early as possible without allowing generation work to block source work. -5. Treat every failed template run as a defect to diagnose, fix, test, and rerun. Never turn failures into skips or silently omit them. -6. Make workflows portable through explicit platform/model fallback policies for quantization, offload, attention, dtype, and device placement. Do not claim universal FlashAttention or universal quantization. -7. Preserve previously approved visual examples. Re-run only examples whose current graph, prompt, artifact, runtime, or numerical contract cannot be reconciled to the approved evidence. - -## Non-negotiable operating rules - -- Read both repository `AGENTS.md` files before editing. -- Preserve the inherited dirty worktrees. Do not reset, clean, overwrite, or discard unrelated work. -- Split inherited draft work into focused, reviewable commits before building on it. -- Model downloads and runtime installs must go through the app only. -- Before every app download, obtain two fresh identical plans and require `fitsWithQueue=true` while retaining the 64 GiB reserve. -- Do not delete existing models; they are required for preview-regression testing. -- Generate from a clean, frozen execution checkout. Continue source work in a separate checkout/worktree so generation receipts remain bound to an immutable commit. -- Record the exact source commit, graph hash, template contract hash, artifact revisions, runtime profile/digest, device, dtype, quantization, offload policy, attention backend, seed, and output hash for every run. -- A generated file is not approved merely because the graph completed. -- Images and videos receive Codex quality screening before user review. -- Audio receives automated integrity checks, then goes directly to the user for listening review; Codex must not claim subjective audio approval. -- A failed run remains in the queue until fixed and successfully rerun, or until an explicit evidence-backed external blocker is recorded. - -## Current baseline - -The last coherent committed coverage ledger is pinned to Diffusers `90b4e34e79a86ec5e7f2437634fe95ecd2108796` and Transformers main `96fe6dce36cc929a5ffd3e34296554c4cb6b669e`. - -- Diffusers exports classified: 327 - - executable: 116 - - equivalent: 15 - - contract-only: 20 - - research-blocked: 120 - - intentionally excluded: 56 - - unreviewed: 0 -- Finite Transformers semantics: 6 - - executable: 4 - - research-blocked: 2 - - production-supported: 5 -- Canonical workflows: 198 -- Unique canonical model/mode pairs: 186 -- Public templates: 77, covering 51 workflows -- Hidden candidate contracts: 147, covering the workflows without public templates -- Hidden candidates requiring input examples: 92 -- Hidden candidates not requiring input examples: 55 -- Historically reviewed Gallery examples: 70 -- Current exact-complete release receipts: 11 -- Resource recipes physically qualified for release: 0/51 - -Important boundary: `executable` currently proves source/graph admission only. It does not automatically prove a real output, correct quality, hardware portability, license approval, Auto eligibility, Gallery eligibility, or release readiness. - -## Current inherited draft state - -The backend and client both contain substantial uncommitted work from a later model. Preserve it, but do not treat it as complete. - -Known draft areas: - -- Shap-E image-to-3D adapter/artifact/spec work exists, but its graph, coverage, candidate contract, client bridge, runtime receipt, and asset are not complete. -- Template authoring drafts exist for 147 hidden candidates. -- Original input-fixture planning exists for 92 input-conditioned candidates. -- A 30-item generic task-gap inventory exists; 28 entries require new generic task boundaries. -- Human-review staging/approval tooling exists but does not consume the new generation-campaign report. -- AudioLDM compatibility code monkeypatches Transformers generation behavior and requires narrow version/model review before acceptance. -- GLM/DreamLite dtype changes conflict with locked tests and require an explicit numerical-contract decision. -- The client draft contains an unsafe E2E optional-runtime bypass that can accept the wrong active runtime profile. Remove it and test exact-profile matching. -- Generated `web/assets` are modified while the client source tree does not pass the full check. Do not commit generated bundles independently. - -Current dirty-tree gates observed on 2026-08-20: - -- Backend exact-pin suite: 21 failed, 1,994 passed, 4 skipped. -- Client typecheck: passed. -- Client full check: failed formatting on five draft files. - -No generation campaign should be called release evidence until these inconsistencies are repaired and the execution checkout is clean. - -## Latest upstream delta to admit - -Freeze and verify the current official heads again immediately before implementation; upstream can move. - -The 2026-08-20 audit found: - -- Diffusers main `4e0466f3e5260f0d78b5e2b68ffbf27d819cc6db`, 17 commits beyond the current MoDiff pin. -- Transformers main `94f09cfec149050b5355bab7f207ac69e21f1a02`, 55 commits beyond the reviewed MoDiff main. -- Comfy workflow templates main `0f3d903f4e22bc6d4cf7fc9c5733555f701e4dce`, beyond the current research pin. - -Diffusers now exposes 330 static top-level `*Pipeline` classes. The three additions are: - -- `StableAudio3Pipeline` -- `StableAudio3AudioToAudioPipeline` -- `StableAudio3InpaintPipeline` - -Stable Audio 3 immediate disposition: - -- Add all three to the inventory and create explicit contract-only/research records. -- Define generic `text_to_audio`, `audio_to_audio`, and `audio_inpaint` task contracts without adding model-name behavior to the client. -- Do not claim app-downloadable execution yet. Upstream documentation currently uses a locally converted checkpoint and says Diffusers-format checkpoints are not published. -- Record the gated-source and conversion-artifact blocker. -- Enforce float32 on CPU/MPS unless later exact evidence supports another dtype. - -Transformers delta immediate work: - -- Relock the exact current main archive and deterministic app-owned wheel. -- Review Step 3.7 through the existing generic image/video-to-text semantic rather than inventing a model-specific frontend task. -- Review NVFP4, FlashAttention hub-kernel changes, and audio/video processor fixes for runtime/profile impact. -- Re-run install, activate, workload, rollback, and clean-base qualification on Linux x86-64 before changing the production cutover. - -Comfy delta immediate work: - -- Regenerate the pinned inventory and authoring-research ledger. -- Classify the new local Wan Animate distilled template. -- Keep hosted/API-only templates ineligible for local execution. -- Do not copy Comfy graphs, custom packages, model files, or media. - -## Contract readiness tiers - -### Tier A — Structurally ready now - -The 198 checked-in canonical workflows have source and graph contracts. They can enter preflight immediately, but each still needs runtime/model/input/resource checks before execution. - -The 77 public templates are the safest first regression population because they have established intent and 70 have previously reviewed visual examples. - -### Tier B — Fastest new asset candidates - -The 55 hidden candidates with no required input examples should be considered first, ordered by: - -1. exact graph and authoring hashes current; -2. model snapshot already complete in app cache; -3. required runtime already active and exact; -4. no unresolved model/output rights gate; -5. smallest expected memory/runtime cost; -6. image before short video before long video, while audio is sent directly after integrity checks. - -These require authoring finalization, a clean execution receipt, generation, and review—not new generic task boundaries. - -### Tier C — Fast after fixture approval - -The 92 input-conditioned hidden candidates already have procedural fixture planning. Finalize a small rights-cleared fixture kit and bind each input by hash. The current radio, tram-stop, control-line, and mask fixtures are suitable for technical routing tests, but publication-quality inputs must be explicitly selected and reviewed. - -### Tier D — Previously executed on this machine - -The last campaign recorded 59 completed workflows: - -- 38 image outputs; -- 15 video outputs; -- 6 audio outputs. - -This is useful execution evidence, but not approval evidence. After the image-quality cleanup performed on 2026-08-20, 21 image candidates remain, 17 image outputs require fixes and reruns, all 15 videos require Codex temporal/visual review, and all 6 audio files require direct user listening review after integrity checks. - -### Tier E — Fast failure recovery - -The 107 failed campaign runs are not 107 independent model gaps. The recorded failures include: - -- 55 browser/page termination failures; -- 28 graph lifecycle/finalization failures; -- 3 optional-runtime failures; -- 2 missing OpenCV failures; -- 1 incomplete-model failure; -- remaining failures requiring individual classification. - -Browser and graph lifecycle failures are the highest-leverage repair because one correct fix can unblock dozens of assets. Fix the campaign runner and graph lifecycle before downloading additional heavy models. - -### Tier F — Not yet generation-ready - -- Shap-E image-to-3D draft until its source/spec/graph/client/ledger chain is complete and the app can safely install the selected snapshot. -- Stable Audio 3 until an official or reviewed deterministic Diffusers-format artifact path exists. -- Research-blocked/contract-only upstream classes lacking safe immutable artifacts, bounded generic actions, acceptable licenses, or verified component assembly. -- Any workflow whose exact optional runtime/profile is not active. -- Any input-conditioned workflow without a selected rights-cleared input. - -## Parallel execution strategy - -### Lane 1 — Asset generation and review - -Run from a clean, frozen execution checkout while Lane 2 continues in another checkout. - -1. Build a machine-readable readiness ledger for all 198 workflows. -2. Select only workflows whose graph, artifacts, inputs, runtime, disk plan, and device recipe pass preflight. -3. Prioritize cached image workflows, then short cached videos. Keep one heavy model generation active at a time on the accelerator. -4. Allow app-only model downloads to run while CPU-only source work proceeds, but never exceed disk reserve and never delete old models. -5. Validate every output before it is considered generated. -6. Route images/videos through Codex screening. -7. Route technically valid audio directly to the user. -8. Store rejected outputs outside the active review shortlist and return their workflow IDs to the rerun queue. - -### Lane 2 — Source, runtime, and generic-client work - -1. Stabilize inherited changes and restore all gates. -2. Refresh exact upstream pins and ledgers. -3. Move frontend model decisions into backend-published schemas. -4. Resolve upstream source/admission gaps. -5. Regenerate canonical graphs and candidate contracts only after the source catalog is stable. -6. Publish a new execution checkout to Lane 1 only after all source and graph gates pass. - -The lanes communicate through immutable revisions and hashes. Lane 1 must never silently consume a dirty or moving Lane 2 checkout. - -## Failure policy: fix, prove, rerun - -Every run follows this state machine: - -`queued -> preflight -> running -> output_integrity -> Codex/user_review -> accepted` - -A failure transitions to: - -`failed -> classified -> reproduction -> fix -> regression_test -> same-workflow_rerun` - -It must not transition from `failed` to `skipped` merely to let the campaign finish. - -For every failure: - -1. Preserve the exact error, graph, template contract, runtime/profile digest, app state, and relevant logs. -2. Classify it as graph/client lifecycle, backend action, artifact, runtime, memory/resource, dependency, output serialization, or quality failure. -3. Reproduce the smallest failing path. -4. Fix the underlying generic layer when possible; do not add a model-name client special case. -5. Add a focused regression test that failed before the fix. -6. Run the affected broader gates. -7. Rerun the exact workflow with the same locked inputs and seed. -8. Keep it in the retry ledger until a valid output exists or an external blocker is formally recorded. - -Quality failure is also a real failure. Adjust prompt/default/resource settings only through reviewed contract changes; bind the new contract hash before rerunning. - -## Output integrity and review gates - -### All media - -- File extension and MIME/file magic must agree. -- Decode must succeed using a second independent decoder where practical. -- Output must match the declared media kind. -- Record dimensions, frame rate, duration, channels/sample rate, and byte size. -- Reject empty, truncated, all-black/all-white, near-flat, NaN/Inf, or implausibly tiny outputs. -- Bind SHA-256 and generation receipt before review. - -The current campaign has a concrete packaging bug: all 38 image outputs used `.png` filenames but contained WebP bytes. Fix the exporter before rerunning or publishing. - -### Image review by Codex - -Check: - -- prompt/task adherence; -- source identity and composition preservation for edits; -- mask locality and seam quality for inpaint/outpaint; -- control adherence; -- anatomy, geometry, text, duplication, and artifact defects; -- exposure, sharpness, coherence, and useful resolution; -- comparison with any previously approved example. - -Only shortlisted images go to the user. Codex screening is not final user approval or rights approval. - -### Video review by Codex - -Decode the complete video and inspect contact frames plus temporal playback. Check: - -- first/middle/last frames and scene intent; -- identity and geometry continuity; -- flicker, jitter, melting, duplication, and abrupt cuts; -- camera/motion compliance; -- duration, FPS, frame count, resolution, and container integrity; -- source/control timing for conditioned video; -- audio/video sync where applicable. - -Rejected videos return to the rerun queue before user review. - -### Audio review by the user - -Codex performs only technical checks: - -- decoding; -- declared duration and sample rate; -- channel count; -- clipping/near-silence/DC-offset checks; -- abrupt truncation detection; -- expected file/container type. - -Technically valid audio is then sent directly to the user for subjective review. - -## Older public templates: confidence and verification without full inference - -Current confidence is mixed: - -- Visual intent confidence is high for the 70 previously reviewed Gallery examples. -- Current-release evidence confidence is high for only 11 templates with exact current receipts. -- Forty-four historical examples are stale relative to current graph/runtime evidence. -- Seven have no historical execution evidence; one of these is a user-supplied-artifact exemption. - -Do not infer that old templates are correct merely because their labels and screenshots remain present. Also do not blanket-rerun all 77. - -Use the following verification ladder, cheapest first: - -### Level 1 — Exact contract reconciliation - -Compare the old approved receipt with the current template and graph: - -- canonical workflow ID; -- semantic graph hash, ignoring node positions and other visual-only metadata; -- model repo/revision and selected-file hashes; -- prompt, negative prompt, seed, scheduler, steps, guidance, dimensions, and mode-specific fields; -- adapter/component revisions; -- generic node/action IDs and field bindings; -- output contract; -- runtime profile and dependency versions. - -If everything execution-relevant is identical, preserve the old visual approval. No inference is required. - -### Level 2 — Static graph and schema replay - -Load every canonical graph through the current backend/client parsers without model weights and prove: - -- all nodes/actions exist; -- handles, types, required inputs, and edges resolve; -- no isolated/unreachable managed nodes exist; -- current backend field schemas accept every stored value; -- generic node roles map to the same backend adapter/action; -- artifact and optional-runtime requirements resolve exactly; -- graph serialization round-trips without semantic changes. - -This is the best inexpensive proof that migration to generic nodes did not structurally break older templates. - -### Level 3 — Fake-pipeline behavioral contracts - -Use signature-compatible fake pipelines and synthetic tensors/media to execute each generic action path. Assert: - -- exact parameter forwarding and omission of unsupported fields; -- source/control/mask routing; -- bounds and cancellation; -- callback and progress behavior; -- output shape/type/serialization; -- inpaint/outpaint compositing locality; -- runtime/profile mismatch rejection. - -This checks backend semantics without loading model weights or running denoising. - -### Level 4 — Component/load canaries - -For templates affected by dependency or loader changes, instantiate the exact local pipeline and components with `local_files_only` and safe serialization, but do not run the full workflow. Verify scheduler, tokenizer/processor, component dtype/device, adapters, and optimization application. - -### Level 5 — Tiny targeted numerical canaries - -Run one very small, bounded inference only for families whose numerics may have changed—for example GLM/T5 dtype, scheduler behavior, quantization, attention backend, or adapter fuse/hotswap. Use 64–256 px or a very short clip/audio segment as appropriate. Compare deterministic hashes where possible; otherwise compare latent statistics, prompt embeddings, perceptual hashes, SSIM/LPIPS/CLIP similarity, and safety/integrity invariants. - -### Level 6 — Full output rerun - -Required only when: - -- Levels 1–5 cannot reconcile the current behavior; -- an execution-relevant graph/model/runtime field changed; -- a targeted canary fails; -- the existing asset is missing or corrupt; -- current release policy requires a new physical resource receipt. - -This ladder confirms generic-node migrations cheaply while limiting expensive full generations to affected families. - -## Prioritized work plan - -### P0 — Make generation reliable immediately - -Acceptance target: a frozen clean checkout can run a bounded campaign without browser death, graph lifecycle races, wrong-runtime bypasses, MIME mismatches, or silent skips. - -1. Snapshot and split inherited backend/client draft changes. -2. Remove the E2E optional-runtime bypass and require the exact profile ID and digest. -3. Fix browser/page lifecycle and graph-finalization races with focused regressions. -4. Fix WebP-as-PNG output naming/serialization. -5. Install/route the OpenCV dependency through the correct app/runtime profile rather than direct ad hoc installation. -6. Reconcile the one incomplete model through two app plans and space policy; do not delete older models. -7. Build a retry ledger for all 107 failures. -8. Rerun the 17 removed image workflows after their technical or quality issue is corrected. -9. Review all 15 existing videos before presenting them to the user. -10. Perform integrity checks on the six audio files and present them directly to the user. - -Estimated active agent time: 6–12 hours, excluding model inference/download duration. - -### P1 — Lock the old-template compatibility proof - -Acceptance target: all 77 public templates have a Level 1–3 compatibility result, and only materially affected families are selected for canaries/full reruns. - -1. Generate an exact old-vs-current reconciliation ledger. -2. Run static graph/schema replay for all 198 workflows. -3. Run fake-pipeline behavioral tests for every generic action topology. -4. Classify each public template as exact-compatible, canary-required, or full-rerun-required. -5. Preserve the 70 old quality approvals unless a real execution-relevant change is proven. - -Estimated active agent time: 4–8 hours. - -### P2 — Refresh latest upstream pins and contracts - -Acceptance target: current heads are frozen, every latest upstream class/semantic is classified, and generated ledgers are exact. - -1. Recheck official heads. -2. Relock Diffusers and Transformers artifacts/source. -3. Add the three Stable Audio 3 records and generic task contracts. -4. Reconcile Step 3.7 and runtime/quantization deltas. -5. Refresh Comfy catalog/research ledgers. -6. Regenerate coverage and provenance tests. - -Estimated active agent time: 4–8 hours. Stable Audio 3 live execution remains artifact/access dependent. - -### P3 — Complete model-neutral client architecture - -Acceptance target: adding a backend model/spec requires no model-name conditional in the client. - -1. Extend backend capability schemas with complete UI, graph, artifact, and optimization metadata. -2. Add strict schema validation and generated/shared types. -3. Replace handwritten static client profiles with authoritative backend records. -4. Replace model-name template/runtime branches with contract fields. -5. Replace model-specific UI controls with schema-rendered generic fields. -6. Retain a narrowly scoped migration map for old saved workflows only. -7. Add a source test that rejects new model-name behavior branches outside approved migration/catalog files. - -Estimated active agent time: 12–24 hours. - -### P4 — Close fast source/admission gaps - -Acceptance target: every safely reusable existing artifact/class is either admitted or explicitly blocked with evidence. - -1. Finish or explicitly defer Shap-E image-to-3D. -2. Repair/reject the AudioLDM compatibility monkeypatch. -3. Resolve GLM/DreamLite dtype changes through exact tests/canaries. -4. Implement the highest-value generic task gaps that reuse existing artifacts. -5. Reclassify every affected Diffusers/Transformers entry and regenerate graphs/contracts. - -Estimated active agent time: 1–3 continuous agent-days depending on artifact/security findings. - -### P5 — Produce the asset review queues - -Acceptance target: every generation-ready contract has a valid asset or a precise unresolved external blocker. - -1. Generate the 55 input-free hidden candidates first. -2. Finalize rights-cleared inputs for the 92 conditioned candidates. -3. Continue cached-model batches while P2–P4 source work proceeds. -4. Codex-screen every image/video. -5. Send technically valid audio directly to the user. -6. Create small, navigable review batches grouped by media and model family. -7. Apply user approval only through explicit approval tooling. - -Estimated agent orchestration/review time: 6–12 hours plus model inference time. Heavy generation duration depends on the accelerator, model cache, and workflow length. - -### P6 — Platform and optimization qualification - -Acceptance target: each published workflow has an explicit supported platform/device/resource recipe and tested fallback ladder. - -Required platform rows: - -- Linux x86-64 CPU -- Linux x86-64 NVIDIA CUDA -- Linux x86-64 AMD ROCm -- Windows x86-64 CPU/CUDA -- macOS ARM64 CPU/MPS -- Linux ARM64 and Windows ARM64 only where dependencies genuinely support them - -Per-family policy must choose among: - -- CUDA FlashAttention 2/3 or xFormers when supported; -- native PyTorch SDPA/efficient attention fallback; -- MPS/CPU math fallback; -- model-supported bitsandbytes, TorchAO, Quanto, GGUF, or native quantization only where qualified; -- resident, model CPU offload, sequential offload, or group offload only where compatible; -- explicit prohibition of unsafe quantization/offload combinations. - -Fill all 51 resource recipes with real measurements: load/run time, peak host memory, peak device memory, output integrity, runtime profile, restart/rollback behavior, and exact receipt hashes. - -Estimated agent work: 1–2 days of orchestration and evidence processing. Wall-clock completion depends on access to the physical target machines and model inference duration. - -## Current 21-image Codex shortlist - -These images passed the first Codex visual/task screen. They are not yet user-approved or rights-approved. - -1. [AuraFlow — text to image](/home/sayak/MoDiff/MoDiff/review-pending/AuraFlowPipeline__text_to_image/campaign-auraflowpipeline__text_to_image-gpu-v1.png) -2. [Chroma — text to image](/home/sayak/MoDiff/MoDiff/review-pending/ChromaPipeline__text_to_image/campaign-chromapipeline__text_to_image-gpu-v1.png) -3. [CogView3 Plus — text to image](/home/sayak/MoDiff/MoDiff/review-pending/CogView3PlusPipeline__text_to_image/campaign-cogview3pluspipeline__text_to_image-gpu-v1.png) -4. [DreamLite — text to image](/home/sayak/MoDiff/MoDiff/review-pending/DreamLitePipeline__text_to_image/campaign-dreamlitepipeline__text_to_image-gpu-v1.png) -5. [Ernie Image — text to image](/home/sayak/MoDiff/MoDiff/review-pending/ErnieImagePipeline__text_to_image/campaign-ernieimagepipeline__text_to_image-gpu-v1.png) -6. [FLUX.2 Klein — text to image](/home/sayak/MoDiff/MoDiff/review-pending/Flux2KleinPipeline__text_to_image/campaign-flux2kleinpipeline__text_to_image-gpu-v1.png) -7. [FLUX Krea — text to image](/home/sayak/MoDiff/MoDiff/review-pending/FluxKreaPipeline__text_to_image/campaign-fluxkreapipeline__text_to_image-gpu-v1.png) -8. [HunyuanDiT — text to image](/home/sayak/MoDiff/MoDiff/review-pending/HunyuanDiTPipeline__text_to_image/campaign-hunyuanditpipeline__text_to_image-gpu-v1.png) -9. [Kandinsky 3 — text to image](/home/sayak/MoDiff/MoDiff/review-pending/Kandinsky3Pipeline__text_to_image/campaign-kandinsky3pipeline__text_to_image-gpu-v1.png) -10. [LongCat Image — text to image](/home/sayak/MoDiff/MoDiff/review-pending/LongCatImagePipeline__text_to_image/campaign-longcatimagepipeline__text_to_image-gpu-v1.png) -11. [Lumina 2 — text to image](/home/sayak/MoDiff/MoDiff/review-pending/Lumina2Pipeline__text_to_image/campaign-lumina2pipeline__text_to_image-gpu-v1.png) -12. [Marigold — depth estimation](/home/sayak/MoDiff/MoDiff/review-pending/MarigoldDepthPipeline__depth_estimation/campaign-marigolddepthpipeline__depth_estimation-gpu-v1.png) -13. [PRX — text to image](/home/sayak/MoDiff/MoDiff/review-pending/PRXPipeline__text_to_image/campaign-prxpipeline__text_to_image-gpu-v1.png) -14. [Qwen Image Modular — text to image](/home/sayak/MoDiff/MoDiff/review-pending/QwenImageModularPipeline__text_to_image/campaign-qwenimagemodularpipeline__text_to_image-gpu-v1.png) -15. [Sana — text to image](/home/sayak/MoDiff/MoDiff/review-pending/SanaPipeline__text_to_image/campaign-sanapipeline__text_to_image-gpu-v1.png) -16. [Sana Sprint — text to image](/home/sayak/MoDiff/MoDiff/review-pending/SanaSprintPipeline__text_to_image/campaign-sanasprintpipeline__text_to_image-gpu-v1.png) -17. [Stable Diffusion PAG — text to image](/home/sayak/MoDiff/MoDiff/review-pending/StableDiffusionPAGPipeline__text_to_image/campaign-stablediffusionpagpipeline__text_to_image-gpu-v1.png) -18. [Stable Diffusion XL PAG — text to image](/home/sayak/MoDiff/MoDiff/review-pending/StableDiffusionXLPAGPipeline__text_to_image/campaign-stablediffusionxlpagpipeline__text_to_image-gpu-v1.png) -19. [Stable Diffusion XL — text to image](/home/sayak/MoDiff/MoDiff/review-pending/StableDiffusionXLPipeline__text_to_image/campaign-stablediffusionxlpipeline__text_to_image-gpu-v1.png) -20. [Stable Diffusion XL Turbo — text to image](/home/sayak/MoDiff/MoDiff/review-pending/StableDiffusionXLTurboPipeline__text_to_image/campaign-stablediffusionxlturbopipeline__text_to_image-gpu-v1.png) -21. [Z-Image Modular — text to image](/home/sayak/MoDiff/MoDiff/review-pending/ZImageModularPipeline__text_to_image/campaign-zimagemodularpipeline__text_to_image-gpu-v1.png) - -All 21 files currently contain WebP bytes despite their `.png` suffix. Correct the format/extension during review-package preparation; do not silently alter the generation receipt without recording the new byte hash. - -## Removed image outputs - -On 2026-08-20, the 12 rejected and 5 borderline image outputs were removed from their original campaign paths using `gio trash`. They are recoverable from the desktop trash until it is emptied. Their workflow IDs remain in the rerun queue and must not be marked skipped or complete solely because the old file is absent. - -## Definition of done - -The campaign is complete only when: - -- the client contains no model-specific execution/UI decisions outside reviewed catalog/migration boundaries; -- current exact Diffusers/Transformers/Comfy revisions are pinned and fully classified; -- every safely supported task has generic nodes, exact backend adapters, canonical workflows, and template/authoring contracts; -- every failed generation has been fixed and rerun or has a precise external blocker; -- all intended images/videos have passed Codex screening before user review; -- all intended audio has passed integrity checks and been sent to the user; -- user approvals and rights approvals are explicitly recorded; -- every published workflow has a physically measured supported resource recipe and fallback policy for its claimed platforms; -- all backend/client/source/graph/release gates pass from clean checkouts; -- all local commits intended for delivery are pushed to the correct remote branches. - -## Initial instruction for the next chat - -Continue this plan persistently. Begin with P0 and maintain the two parallel lanes. Preserve the inherited dirty worktrees and the 21-image shortlist. Do not skip failed workflows. Do not ask for image/video review until Codex has screened them; send technically valid audio directly for user listening review. Use app-only downloads with two fresh matching space plans, preserve all existing models, and stop only for a real external blocker or required user intervention. diff --git a/docs/hugging-face-integration-roadmap.md b/docs/hugging-face-integration-roadmap.md deleted file mode 100644 index 31bbac07..00000000 --- a/docs/hugging-face-integration-roadmap.md +++ /dev/null @@ -1,7640 +0,0 @@ -# Hugging Face Integration Roadmap - -This document is the implementation and completion tracker for closing MoDiff's -official Diffusers, Modular Diffusers, and approved Hugging Face speech-runtime -gaps. It is a durable product roadmap rather than a claim that every upstream -pipeline is already runnable. - -The reviewed Diffusers installation is pinned to commit -[`2f7e0154a9db246e95c9ede43edba7db5b130805`](https://github.com/huggingface/diffusers/commit/2f7e0154a9db246e95c9ede43edba7db5b130805), -the verified `main` head on 2026-08-20. The reviewed comparison from the prior -pin contains 23 commits. The comparison inventory and executable dependency use -that immutable snapshot; re-run the inventory before changing the pin or -marking a gap complete. - -### Pin delta admitted 2026-08-21 - -The exact 23-commit delta from -`90b4e34e79a86ec5e7f2437634fe95ecd2108796` adds the three Stable Audio 3 -exports: `StableAudio3Pipeline`, `StableAudio3AudioToAudioPipeline`, and -`StableAudio3InpaintPipeline`. Each has an explicit task-generic contract for -`text_to_audio`, `audio_to_audio`, or `audio_inpaint`, but remains -`research-blocked`. Upstream documents that the gated Stability AI checkpoints -are not published in Diffusers format and must be converted locally. MoDiff -therefore does not publish a repository, app download, workflow, model node, or -execution claim for this family. CPU and MPS remain float32-only pending exact -contrary evidence. - -The source archive was reviewed at SHA-256 -`5b62d1dc5c6902ee6279e0debc6a240f0443f779d9bd95d128d844c07efa7ebf` -(11,338,603 bytes). Diffusers' declared dependency requirements are unchanged. -The delta also contains bounded fixes for DiffusionGemma adaptive stopping, -Krea2 repeated-image preparation, Cosmos3 preprocessing, MiniMax-H3 LoRA, -tensor parallelism, and Diffusers BnB/TorchAO quantizer handling; none widens a -MoDiff model or quantization support claim without its existing exact profile -and artifact gates. - -### Pin delta admitted 2026-08-14 - -The 14-commit delta adds one exported pipeline family: -`MiniMaxMusic3ModularPipeline`, with one prompt-and-lyrics text-to-audio -workflow. It is registered as Expert-visible and contract-only; no repository, -runnable mode, template, Gallery entry, download, or execution claim is inferred. -The complete no-weight Modular snapshot now contains 34 classes and 94 upstream -workflows. - -Other product-relevant changes add MiniMax-H3 LoRA loading, repair Wan -video-to-video placement and Wan VACE RoPE dtype handling, align PEFT cleanup, -and replace the old `aiter` attention backend upstream with `aiter_fa2_hub`. -MoDiff removed the obsolete option and rejects both spellings: the replacement's -mutable Hub-kernel revision does not meet the immutable artifact policy. No new -standard `DiffusionPipeline` family was added by this delta. - -### Previous pin delta admitted 2026-08-13 - -The admitted pin is 73 commits after the prior -`13a7bee4878d62fccc8d25f97e480e68de96fa03` snapshot. Its product-relevant -surface consists of Krea2 Modular support, MiniMax H3, LTX-2.5 plus its final -`LTX25AutoBlocks` rename, Wan-Animate-2, SDNQ support, JAX/Flax removal, and -core changes to component management, group/automatic offload, split-device -deduction, dtype naming, LoRA scaling/bookkeeping, GGUF dequantization, and -custom-block required-input propagation. The remaining commits are tests, -documentation, training/examples, CLI work, or model-specific fixes outside -the currently admitted execution surface. - -The update is isolated in backend commit `5ee9e1d`; no client change was -required. Regenerating the existing 20-class no-weight Modular contract -inventory produced no structural drift beyond the pin identity. MoDiff carries -the upstream required-custom-input fix into its adapted schema helper and adds -an exact regression test. The full backend suite passed at the proposed pin -(`1312 passed, 3 skipped, 2957 subtests`) in an isolated reviewed -Transformers/PEFT test environment. The repaired clean CPU base contains 61 -application packages, keeps Transformers and PEFT absent, reports the exact -new VCS identity, passes package validation, and is preflight-ready with an -exact requirements receipt. No weights or media were downloaded. - -LTX-2.5 reuses the standard `LTX2Pipeline` family rather than adding a -model-named standard pipeline, but it adds execution behavior that must be -reviewed explicitly: - -- the immutable `Lightricks/LTX-2.5-Diffusers` artifact and its distinct - distilled `transformer/`, full/SFT `transformer_full/`, latent upsampler, and - stage-2 distilled-LoRA receipts; -- the reference distilled sigma schedules and both supported two-stage - generation variants, without substituting a generic step-count schedule; -- `LTX2DurationHead`, the optional Gemma-4 prompt-enhancement component, and - their bounded/explicit controls (no discovery-time model download); -- `LTX2VideoDiffusionDecoderModel` and - `LTX2VideoDiffusionDecodePipeline`, including the production-resolution - NATTEN dependency and video/audio latent handoff contract; and -- the new `LTX2ModularPipeline` and `LTX25ModularPipeline` exports, with - `LTX2AutoBlocks`/`LTX25AutoBlocks` covering text-to-video, - image-to-video, condition-to-video, and IC-LoRA/in-context selection. - -MiniMax H3 was already present in the prior inventory from upstream commit -[`f53d552`](https://github.com/huggingface/diffusers/commit/f53d552) and remains -post-pin. Its Phase 6 item now records the three separate joint video-and-audio -workflows: text-only `t2va`, first/last-keyframe `fl2va`, and omni-reference -`ref2va`. `t2va`/`fl2va` use the repository's `transformer/` partition; -`ref2va` uses `transformer_ref/`. These are future generic video+audio task -contracts, not permission to add a MiniMax-named node or enable an unqualified -artifact. - -### Transformers main audit 2026-08-15 - -The reviewed Transformers `main` head is -[`96fe6dce36cc929a5ffd3e34296554c4cb6b669e`](https://github.com/huggingface/transformers/commit/96fe6dce36cc929a5ffd3e34296554c4cb6b669e) -(`5.16.0.dev0`). It is two test-only commits after the previously reviewed -`a597f974857b3d92939971296bc0deb93d33d780`: only the Gemma and AXK1 CUDA A10G -expected-value tests changed. `LICENSE`, `README.md`, package metadata, -dependencies, and the entire `src/transformers` tree have identical Git object -IDs. The selected-source seal and deterministic wheel bytes are therefore also -unchanged. - -The app now has a separate immutable-main optional-runtime profile. It verifies -the official commit archive by exact URL, size, and SHA-256; performs bounded, -link/reparse-safe selected-tree extraction; and assembles a normalized wheel -without executing upstream `setup.py`, a build backend, or downloaded source. -The final wheel, metadata, `RECORD`, and complete file seal are independently -validated before the existing isolated overlay promotion path may use them. -Linux x86-64 passed a clean-checkout acquire, install, fresh-process activation, -finite CLIP+PEFT workload, rollback, and clean-base-child campaign. Execution on -that target was historically qualified at the byte-identical `a597f974` -profile. The current app subsequently acquired, validated, activated, and -fresh-process loaded the exact -`huggingface-transformers-main-96fe6dce-peft-0.20.0` profile at spec digest -`sha256:7566ef4c2cd9b8ed1e39850bbf370c66fa98a2909960503bfdfe12fc469088cf`. -This exact app cutover does not rewrite the earlier qualification evidence as a -96fe run. Windows x86-64 keeps -the previously qualified published Transformers `5.14.1` profile; Linux ARM, -Windows ARM, and both macOS architectures remain fail-closed candidates. The -manually triggered macOS workflow now names and verifies the exact main profile, -but physical macOS qualification is still pending. - -The main-tree delta adds AXK1/2, Cohere Compass, Cosmos3 Edge, Granite SWA/MoE -SWA, and MuseGlimmer model packages but no new Transformers pipeline task -registry. MoDiff now exposes bounded generic speech, causal-text, and -image/video-to-text loaders/actions, plus a finite model-specific AnyToAny -adapter for Janus text and image output. Immutable SmolLM2 and SmolVLM model -contracts and Studio workflow specifications exercise the generic text and -image-to-text paths, and both selected app-only downloads are complete. The app -also downloaded and verified the exact 11-file / 4,161,125,359-byte -`deepseek-community/Janus-Pro-1B@1655280bb75959cc1cb85529a2a8b26e7016072e` -selection without deleting an older model. Its 4,153,396,574-byte safetensors -blob matches SHA-256 -`9d1a416f95fb58d6e02858623c9c676003d66006d51fb5d5cc93348ba78cb942`; -the app reports the cache complete and repair-free. Janus weights remain under -the DeepSeek Model License Agreement v1.0, and product/user compliance review -is still required. Native -Emu3 image generation, Cosmos3 reasoner orchestration, and Qwen2.5-Omni output -remain research-blocked pending their own safe artifact, resource, and bounded -output contracts. Static symbol compatibility at `main` is not a live model or -cross-platform qualification claim. - -## How to update this tracker - -- Use `[x]` only after every required backend, client, test, live-proof, and - asset item for that checkbox is complete. -- Record the backend and client commit or pull-request references in the - completion ledger. Do not create empty commits in either repository; a - backend-only segment instead records the compatible client gate that passed. -- Keep a template hidden or explicitly `qualification_pending` until its live - output and public assets meet the publication contract. -- Update this file in the same change that completes or reschedules a segment. -- Distinguish contract, mocked, tiny-model, live-output, and Gallery-asset proof. - -## Completed groundwork - -- [x] Inventory the current backend profiles, Auto requirements, nodes, graphs, - and Gallery manifest. -- [x] Compare the pinned Diffusers revision with the reviewed upstream snapshot. -- [x] Identify the initial 18 missing official Modular pipeline classes and 69 - missing standard pipeline families listed in the appendices. -- [x] Re-audit upstream through 2026-08-12 and append the two newly exported - LTX2/LTX2.5 Modular classes, bringing that checkpoint's Modular gap inventory - to 20 without changing the 69-family standard-pipeline inventory. -- [x] Run the pre-change backend baseline: 627 tests and 274 subtests passed; - Ruff, dependency validation, and backend preflight passed. -- [x] Confirm the following owner decisions: - - Official libraries maintained by Hugging Face may be added as reviewed - model runtimes; Transformers speech-to-text is the first planned use. - - Transformers and other workflow-specific Hugging Face model runtimes are - optional and are not installed by the base application installer. - - No hosted inference provider or browser-side model runtime is approved. - - MoDiff will not support Mellon's configuration filename or schema. - - `modiff_pipeline_config.json` is the only MoDiff dynamic-node sidecar. - - Release media should be generated on a separate qualification machine. - - No command or model run on the current development machine may exceed 40 - minutes. - -## Non-negotiable architecture decisions - -### Generic nodes, explicit adapters - -Nodes represent tasks and media contracts, not model names. Existing generic -image, audio, and video nodes should be extended before adding another -`NodeBase` class. New generic contracts are allowed for genuinely different -semantics such as unconditional image generation, perception maps, 3D -artifacts, diffusion text, and speech recognition. - -Model-specific behavior belongs in a declarative execution specification that -records: - -- exact `(modelType, mode)` identity; -- loader and generator module/action; -- upstream pipeline class or approved Hugging Face runtime class; -- required, optional, and aliased inputs; -- normalized output contract; -- immutable model, adapter, and auxiliary revisions; -- dtype, quantization, placement, and offload constraints; -- template coverage and qualification state. - -Do not pass arbitrary form fields to a model and hope its call signature accepts -them. Generic means a stable user contract backed by validated adapters. - -The frontend follows the same rule. It should render a generic task contract -such as `control_image` from backend-declared fields and constraints, without a -Qwen-only or Flux-only graph-building branch. The backend execution -specification selects standard versus Modular loading, maps parameter aliases, -and rejects unsupported combinations. Curated templates remain model/task -specific only as data: they carry reviewed artifacts, defaults, prompts, and -evidence while reusing the same generic node and form implementations. - -### Approved Hugging Face execution boundary - -MoDiff may execute models through official libraries maintained by Hugging -Face, including Diffusers, Transformers, and future reviewed Hugging Face -libraries. This broadens the model-runtime boundary, not the graph or trust -boundary: - -- MoDiff's existing graph executor remains the only graph executor; -- every new library and task receives an explicit generic node/adapter contract; -- executable dependencies and model/auxiliary artifacts are reviewed and - immutably pinned where the source supports revisions; -- safetensors is preferred and unsafe deserialization is an explicit reviewed - exception; -- `trust_remote_code` is never enabled implicitly; -- no hosted Inference Provider or browser-side model runtime is added; -- arbitrary Hub Python is not made trusted merely because it is stored on the - Hugging Face Hub; -- workflow-specific Hugging Face libraries are installed through an explicit, - reviewed first-use action instead of the base application installation; -- file, network, input-size, output-size, resource, and cleanup limits remain in - force. - -Library ownership alone does not prove that every task or model is supported. -Each exact task still progresses through contract, fixture, live, and Auto -qualification states. Transformers automatic speech recognition and speech -translation are the first planned non-Diffusers tasks. They use a generic model -loader and transcription node, not Whisper-specific nodes, with bounded audio -and text contracts. - -### Lazy optional Hugging Face runtimes - -The base application environment must not directly depend on Transformers or -another library needed only by particular templates. An execution specification -declares its required runtime packages. When a user first tries to run a -template or workflow whose package is absent, MoDiff blocks execution and -offers an explicit install action. Merely opening a template, discovering -nodes, or requesting an Auto plan must not download or install packages. - -After confirmation, the backend stages the reviewed package set, validates it -in a fresh process, activates it atomically, restarts when required, and retains -the prior environment for rollback. The client shows download, validation, -activation, restart, failure, and rollback states. A failed or declined install -leaves the workflow unchanged and Expert-visible with a concrete missing-runtime -reason. - -Transformers and PEFT are currently direct project dependencies, and PEFT has -an unconditional Transformers dependency. They therefore move out of the base -environment together in one compatibility segment. Do not remove either until -registry discovery, preflight, existing Diffusers workflows, optional -installation, restart, and rollback all pass from a clean base installation. - -### Dynamic execution and safe import inspection are separate boundaries - -The executable Dynamic Modular path does not fall back from -`modiff_pipeline_config.json` to `mellon_pipeline_config.json`. Dynamic Modular -configuration uses canonical upstream metadata such as -`modular_model_index.json` and `modular_config.json`; executable MoDiff UI fields -and defaults use `modiff_pipeline_config.json` only. The User Node Hub importer -may separately read and translate an exact cached -`mellon_pipeline_config.json` for bounded visual preview. That inspector never -imports repository Python, and a Mellon-only import remains disabled until a -separately authorized sandbox exists. - -The currently curated `diffusers/FLUX.2-klein-4B-modular` example is not a -valid MoDiff dynamic-block example because its reviewed revision does not -publish `modiff_pipeline_config.json`, and an auxiliary model reference is not -immutably pinned. Remove it from the curated selector and bundled graph until a -reviewed repository satisfies the MoDiff contract. A user-selected repository -without the MoDiff sidecar receives an actionable unsupported-config error. - -### Auto is fail-closed - -Auto may run only an exact registered and qualified `(modelType, mode)` recipe. -An installed artifact, a pipeline class name, a mocked test, or a successful -lighter task is insufficient proof. Unknown or unqualified combinations remain -visible only in Expert mode with a specific reason. - -### Local 40-minute ceiling - -Every local command, download/load/inference job, and test batch must have a -wall-clock timeout of at most 40 minutes. Model smokes should be designed for a -30-minute expected maximum. Request graceful cancellation by 35 minutes and -retain 5 minutes for cleanup and diagnostics. At the hard timeout: - -1. cancel the run; -2. release managed model and accelerator resources; -3. record the candidate as not locally qualified, not as failed upstream - support; -4. move the workload to the remote qualification queue; -5. do not retry the unchanged recipe on this machine. - -Permitted local live candidates are initially limited to small DDPM/DDIM, -Consistency Models, short low-resolution SD/LCM/PAG, small Marigold, and -Whisper Tiny/Base with a short audio fixture. Release assets are still produced -on the remote machine by default. Video, long audio, AudioLDM2 quality/TTS, -large image models, and long-form workflows are remote-only. - -## Commit and asset handoff contract - -Every segment ends at a clean source-control boundary. - -1. **Backend source commit:** nodes/adapters, execution specifications, API - changes, graph contracts, focused tests, and documentation. -2. **Client source commit:** typed capability handling, Studio profile/form, - graph bridge, template metadata, readiness UX, and unit/browser tests. -3. **Integration gate:** complete backend and client checks against the paired - commits. No generated media is required at this point; public Gallery - activation remains blocked. -4. **Remote qualification:** the other machine checks out the exact two commits, - installs the reviewed profiles, runs the model, and records the model and - dependency revisions, graph hash, settings, output checks, runtime, and peak - memory. -5. **Asset publication:** reviewed media is uploaded to the public Hugging Face - Dataset. Generated images, audio, and video are not committed to Git. -6. **Activation commits:** the client commits the immutable Dataset revision, - SHA-256 manifest, rights/provenance record, review, and Gallery status. Its - generated `dist/` is mirrored into backend `web/` using the documented - process; minified files are never edited manually. - -An integration may merge before remote assets exist only when its UI says -`qualification_pending`, Auto is disabled, and no public Gallery entry implies -live proof. Asset activation is a separate committable segment. - -Broad repeatable entries such as a model-family template batch must use one -family/mode per paired commit. Add suffixed ledger rows such as `P2.2a` and -`P2.2b`; do not combine unrelated families merely because they share a phase. - -## Known integration defects that set the initial order - -### Flux Modular falsely claims ControlNet - -Before the P0.1 working-tree fix, the backend capability table said -`FluxModularPipeline` supported -`control_image`, while its node specification explicitly sets `controlnet` to -`None`. The pinned upstream `FluxAutoBlocks` exposes `text2image` and -`image2image`, not a ControlNet workflow. - -A user could encounter the contradiction in two ways: - -1. A consumer of `/model_capabilities` sees `control_image` as runnable for - `FluxModularPipeline`, even though the graph runtime cannot construct it. -2. In a generic Modular graph, connect a Modular `ControlNet` node and switch - the signalled model type to `FluxModularPipeline`. The node-definition update - receives the `None` specification and removes its control image, model, and - scale fields; the Flux denoise definition also lacks `controlnet_bundle`. The - graph becomes unwireable and can retain stale edges. A stale imported or - hand-edited graph that retains those fields reaches execution and tries to - iterate `node_config["params"]`, causing a `NoneType` failure. A full graph - may load large Flux components before reaching that failure. - -There is no checked-in `FluxModularPipeline:control_image` Studio template, so -the normal Gallery path never executes this combination. The current bundled -client also discards `experimentalCapabilities`, so it does not create a normal -Flux Modular Control button from this bad entry. The Flux Canny and Flux Depth -control templates use standard Diffusers pipelines and are not affected. -Exactly one checked-in graph contains the Modular ControlNet node: -`qwen-image-modular-pipeline/control-image.json`. It signals Qwen, whose -configuration is present, and fails only if a user manually changes that graph -to Flux. - -Git history shows that the initial Modular import commit `346c203` inherited -both the Flux option in the generic ControlNet signal map and the explicit -`controlnet: None` marker from the recorded Mellon baseline. The best-supported -interpretation is that the signal map named known models so the generic node -could reconfigure or hide itself; it was not itself intended to declare -support. Its missing symmetric execution guard was still a defect. Commit -`76bbafe` later added the public experimental capability claim. Existing tests -check registry exports and working generic contracts, but do not enforce that -every advertised mode has a non-null node specification and an upstream -workflow. The history does not record why the later claim was added; confusing -standard Flux Control pipelines with Modular Flux support is plausible but is -only an inference. - -P0.1 removes the false capability and makes both dynamic-node update and stale -graph execution return the same actionable unsupported-workflow error. The -generic signal map remains only a reconfiguration mechanism; it is not treated -as proof of a runnable workflow. - -### The curated DynamicBlock example is an incomplete Mellon-to-MoDiff migration - -The execution path still does not request or parse -`mellon_pipeline_config.json`. The remote-code-disabled User Node import -inspector now parses that sidecar only as declarative preview metadata. -Commit `346c203` originally used Diffusers' inherited `MellonPipelineConfig` -helper with `YiYiXu/FLUX.2-klein-4B-modular`. Commit `57b9bb` introduced -`MoDiffPipelineConfig`, renamed the sidecar to `modiff_pipeline_config.json`, -and documented that legacy filenames are unsupported, but it did not replace -the example. Commit `76bbafe` switched the curated option to the pinned -`diffusers/FLUX.2-klein-4B-modular` repository, which still has Mellon UI -metadata rather than a MoDiff sidecar. - -The original revision-forwarding unit test mocked the configuration loader, so it -could not discover that the real pinned repository lacked the requested file. -Executable compatibility remains fixed by removing/replacing the curated -example and adding repository-layout contract coverage, not by treating Mellon -metadata as execution authority. Attribution in the -source-provenance map remains unchanged because provenance is not a runtime -compatibility promise. - -## Model-dependent node and workflow audit - -The 2026-08-07 follow-up audit checked every registered Modular pipeline against -the pinned Diffusers classes and block definitions without loading model -weights. The generic node class is shown at the top of each column; `yes` means -that the registered MoDiff specification has a non-null action contract, not -that a model has completed live qualification. - -| Registered Modular model | Encode Prompt | Image Embeddings | Image Encode | Denoise | Decode Latents | ControlNet | -| --- | --- | --- | --- | --- | --- | --- | -| `StableDiffusionXLModularPipeline` | yes | no | yes | yes | yes | yes | -| `QwenImageModularPipeline` | yes | no | yes | yes | yes | yes | -| `QwenImageEditModularPipeline` | yes | no | yes | yes | yes | no | -| `QwenImageEditPlusModularPipeline` | yes | no | yes | yes | yes | no | -| `QwenImageLayeredModularPipeline` | yes | no | yes | yes | yes | no | -| `FluxModularPipeline` | yes | no | yes | yes | yes | no | -| `FluxKontextModularPipeline` | yes | no | yes | yes | yes | no | -| `Flux2KleinModularPipeline` | yes | no | yes | yes | yes | no | -| `ZImageModularPipeline` | yes | no | yes | yes | yes | no | -| `WanModularPipeline` | yes | no | no | yes | yes | no | -| `WanImage2VideoModularPipeline` | yes | yes | yes | yes | yes | no | - -`DummyCustomPipeline` is deliberately absent from the matrix because its -actions must be discovered from the reviewed `modiff_pipeline_config.json`; it -must never inherit a built-in model's actions. An absent action is an explicit -unsupported model/action pair. The frontend must hide or disable it and the -backend must reject stale or hand-edited graphs before loading weights. - -The custom path currently uses one mutable `DummyCustomPipeline` class and one -registry slot. Loading a standard model resets that slot, while loading a -second custom repository overwrites the first. Runtime payloads restore the -first repository's ID, revision, and trust flag but not its matching sidecar, -so a later node can resolve the wrong or empty action contract. Custom support -therefore needs an immutable per-`(source, repository, revision, explicit trust -choice, sidecar hash)` identity and isolated binding before it can share the -built-in completion claim. - -The audit found the same failure family as the former Flux ControlNet defect in -the other five dynamic nodes. Their definition-update handlers can silently -clear fields for a non-null-to-null model switch, while stale execution can -dereference the missing specification. They also remove connector fields from -the returned parameter mapping in place. Built-in mappings happen to be -reconstructed, but a dynamic custom mapping can be permanently changed by the -first update. Finally, runtime component payloads are not always reconciled -against the node's selected pipeline class, so components from two model types -can reach the wrong action specification. - -The surrounding generic nodes also contain undeclared compatibility rules: - -- `Denoise` has a class-name list that controls whether width and height are - shown for image-conditioned models. -- `ModelsLoader` assumes a text encoder exists and special-cases the Wan I2V - image encoder instead of deriving required and optional components. -- `AutoModelLoader` describes a component and repository but not the owning - Modular pipeline contract. A graph built entirely from standalone component - loaders therefore cannot recover its exact action schema after transient UI - signals are lost. The execution specification must supply that identity; it - must not be guessed from a repository name. -- `Layers` publishes a hard-coded family map and omits several registered - models; missing entries must be researched rather than copied from a nearby - architecture. -- `Guider` is task-generic but does not declare model compatibility. The pinned - Diffusers revision has two guiders that MoDiff does not expose, and latest - `main` adds a third. Flux Modular specifications currently show a guider - input even though the upstream Flux Modular pipelines have no guider - component. -- `Scheduler` offers every registered scheduler to every model without an - exact compatibility contract. - -The standard task nodes have related adapter-specific gaps even though they do -not create model-specific node classes: - -- Image loading validates pipeline class and mode, but individual Generate, - Edit, Inpaint, and Control actions do not revalidate the connected adapter's - allowed mode. Changing only the pipeline-class field can also retain the Flux - Schnell repository default for Z-Image, Flux2 Klein, Flux Fill, Flux Control, - Flux Kontext, or Flux Redux. Auto and templates normally overwrite the - repository, which hid this manual-workflow defect. -- Video generation does revalidate modes, but its loader's final branch assumes - FramePack and an untagged pipeline falls back to Wan VACE. Both must fail - closed when exact recovery is impossible. -- Audio loading knows ACE-Step versus Stable Audio, but generation does not - retain and revalidate the selected mode/task contract. Stable Audio can show - ACE-specific task choices, while its own `text2audio` task is absent from the - visible choices. - -The raw Expert canvas is already structurally generic: it renders `/nodes` -metadata and executes backend `onChange` and `onSignal` actions. Studio graph -authoring is not. Its model union, profiles, role-to-node mapping, topology, -edge recipes, initialization order, form bindings, pipeline classes, and many -readiness/resource messages are selected by Flux, Qwen, Wan, Z-Image, audio, -or video branches. `/model_capabilities` schema v2 can say whether a pair is -runnable, but it cannot yet describe the nodes, handles, bindings, or ordered -dynamic actions needed to build that pair's graph. - -The target contract is a backend-owned `studioExecutionSpec`, keyed by exact -`(modelType, mode, executionProfileId)`. It references existing `/nodes` keys -and declares stable roles, nodes, edges, accepted handle aliases, form fields -and bindings, initialization actions, normalized inputs/outputs, a schema -version, and a content hash. It is configuration for the existing MoDiff graph -executor, not a second graph representation or executor. The client validates -every referenced node, parameter, and handle against `/nodes`, then materializes -the same visible editable graph. Templates carry an execution-spec reference -and reviewed overrides instead of hidden model-specific code. - -### Historical upstream workflow coverage from the 2026-08-07 node audit - -| Family | Pinned upstream workflow surface | Current MoDiff gap or mismatch | -| --- | --- | --- | -| SDXL Modular | text-to-image, img2img, and inpaint, each with ControlNet, ControlNet Union, IP-Adapter, and combined variants (18 workflows) | All 18 pinned workflows now have exact generic action/state truth for generator continuation, typed mask/masked latents, crop overlay, exact VAE/ControlNet provenance, and process-local adapter mutation/embedding provenance. The four high-level base modes, including inpaint, are published only as `contract_only`; the combined variants remain manual generic-graph compositions rather than new model-named modes. Multi-ControlNet and multiple-IP-Adapter variants are outside the pinned 18-workflow contract. | -| Qwen Image Modular | text-to-image, img2img, inpaint, plus ControlNet versions of all three | Direct text-to-image and Modular control text-to-image are exposed. The generic main-VAE image/mask/overlay route, ControlNet generator/provenance chain, and exact internal combined img2img/inpaint state-flow contracts are implemented. Combined modes remain unadvertised and still require profile/template/live qualification. | -| Qwen Edit Modular | image-conditioned and image-conditioned inpainting | Edit is exposed. The generic VAE/denoise/decode generator, mask, and overlay route is implemented contract-only; Modular inpaint exposure and qualification remain pending. The separately registered standard inpaint/outpaint path is unaffected. | -| Qwen Edit Plus | upstream block sequence, without an upstream workflow map | Core actions and the generator-only VAE/denoise/decode route exist. Inpaint state is rejected; multi-image input cardinality and field normalization still need contract tests before broader exposure. | -| Qwen Layered | upstream block sequence, without an upstream workflow map | Core actions now expose the pinned shared 640/1024 source resolution plus text-only English-prompt and bounded maximum-sequence controls. Broader exposure remains tied to the reviewed fixed-block contract and later qualification. | -| Flux Modular | text-to-image and img2img | Current modes match; Modular ControlNet remains unsupported. | -| Flux Kontext Modular | text-to-image and image-conditioned | Registered internally but no public Modular execution profile/specification. | -| Flux2 Klein Modular | text-to-image and image-conditioned | Registered internally; current experimental metadata points at a standard pipeline and advertises edit semantics without publishing a Modular execution path. | -| Z-Image Modular | text-to-image and img2img | Text-to-image only is declared; existing generic latent/strength fields make img2img a small contract/profile gap, still requiring qualification. | -| Wan I2V Modular | image-to-video and first/last-frame video | The existing image-to-video mode has its exact split-action typed edges and opaque route. The distinct official FLF checkpoint is now pinned immutably and admitted as a reviewed repository variant of the same generic Models Loader; action admission requires the I2V/FLF input shape to match that exact loader publication. Public FLF profile/template promotion and live execution remain pending. | -| Wan T2V Modular | canonical text-to-video block sequence | The declared text-to-video action matches the available block sequence. | - -This table is an admission inventory, not permission to advertise every -upstream workflow. Support is added only when all required node actions, -parameter adapters, execution specification, tests, and proof level agree. - -### Historical action gaps recorded by the 2026-08-07 audit - -This table preserves the pre-implementation action/adapter audit. It is not the -current closure ledger; later completion entries and the generated coverage -ledger supersede rows that have since been admitted. The architectural rule -still applies: extend generic image, video, and audio task nodes unless a -model-specific semantic truly requires its own bounded adapter. - -| Family | Existing generic coverage | Confirmed pinned classes/actions not yet covered or exposed | -| --- | --- | --- | -| SDXL | Modular text/image/control/inpaint high-level modes and standard text/img2img/inpaint adapters published contract-only; exact ordinary/bounded single-ControlNet-Union and reviewed single-IP-Adapter generic state flows | instruct-pix2pix; live Modular qualification, multi-ControlNet, multiple IP-Adapters, and templates for combined actions | -| Qwen Image | standard text-to-image and Edit inpaint/outpaint; contract-only standard img2img, inpaint, Edit, and Edit Plus; Modular control text-to-image and edit paths | standard ControlNet, ControlNet inpaint, and Layered adapters; Modular img2img/inpaint combinations | -| Z-Image | standard and Modular text-to-image plus contract-only standard img2img/inpaint | standard ControlNet, ControlNet inpaint, and Omni adapters; Modular img2img exposure | -| Flux | text/image edit, fill, base control, ControlNet, Kontext, Redux, contract-only img2img/inpaint/Kontext-inpaint, and exact true-CFG forwarding | control-img2img, control-inpaint, ControlNet-img2img, and ControlNet-inpaint | -| Flux2 | Klein text/image edit and multi-reference plus contract-only Klein inpaint | KV and full Flux2 after artifact/runtime review | -| Wan | five profiled standard video adapters, Modular T2V/I2V, and contract-only Wan 2.2 T2V/Animate adapters | first/last-frame profile; the three Expert-only VACE video/reference/color modes; a truthful VACE first-frame adapter that synthesizes the required video-and-mask state; live qualification for Animate | -| LTX/LTX2 | profiled LTX condition modes plus contract-only long-prompt I2V and LTX2 condition adapters | latent upsample; LTX2 in-context, HDR, and latent-upsample actions; post-pin LTX-2.5 distilled/full and two-stage recipes, duration head, Gemma-4 prompt enhancer, diffusion decoder, and LTX2/LTX2.5 Modular pipelines; live execution qualification for long/LTX2 | -| Hunyuan Video | FramePack adapter published contract-only | base text-to-video, image-to-video, and SkyReels image-to-video; live FramePack qualification | -| Audio | ACE-Step plus Stable Audio published contract-only | live Stable Audio qualification; unsupported ACE `extract`, `lego`, and `complete` choices remain hidden until their missing inputs exist | - -All entries begin `contract_only`. Real output, resource envelopes, Auto -qualification, and Gallery publication remain separate remote-machine work. - -## Test and evidence ladder - -Each phase below names its applicable levels. - -1. **Static/contract:** registry closure, class and signature availability, - immutable revisions, safetensors/trust policy, schema and manifest checks. -2. **Mocked/tiny:** fake pipeline calls, input aliasing, output normalization, - Modular state/component flow, and tiny upstream fixtures without large - downloads. -3. **Integrated backend/client:** HTTP contracts, Studio graph application, - Auto/Expert gating, stale-request handling, and mocked browser flows. -4. **Local short live:** only an allowlisted workload that is expected to finish - inside 30 minutes and is forcibly bounded at 40 minutes. -5. **Remote qualification:** exact normal recipe on the target qualification - hardware, producing attributable output and a resource receipt. -6. **Gallery publication:** rights review, inventory, hash verification, - anonymous remote verification, activation, and release acceptance. - -Required backend gate: - -```powershell -uvx --from ruff==0.12.7 ruff check . --select E9,F -uv pip check --python .venv/Scripts/python.exe -.venv/Scripts/python.exe -m modiff.preflight --json --check-port 8088 --fail-on-error -.venv/Scripts/python.exe -m pytest -q -``` - -Required client gates for public-contract or UI changes: - -```powershell -npm run check -npm run check:ui -``` - -Template and Gallery changes also require: - -```powershell -npm run gallery:verify -npm run gallery:coverage -npm run workflows:verify -npm run test:asset-storage -``` - -Asset activation additionally follows the complete release gate in the client -`docs/template-gallery-assets.md` guide, including `npm run check:acceptance`, -`npm run release:assets:gate`, `npm run release:template:qualify`, and -`npm run release:resource:qualify` on the external qualification host. - -## Phase 0 — Auto correctness and registry closure - -Priority: immediate. Hardware: CPU only. Assets: none. - -### Committable segments - -- [x] **P0.1 Exact pair validation and Flux truth fix** - - Backend: require an exact `(modelType, mode)` entry; remove - `control_image` from `FluxModularPipeline`; make a `None` node specification - an explicit unsupported result in both node update and execution; never - treat membership in a generic signal map as a capability declaration. - - Client: do not offer a backend mode absent from the exact capability; show - the backend reason and preserve Expert access only for structurally valid - graphs. - - Tests: unknown pair, wrong-mode pair, Flux control capability absence, - dynamic node update, stale imported graph, and mocked Studio mode gating. - - Status 2026-08-07: implementation and source gates are complete in paired - backend `91c9a36` and client `28b12b7`. Auto now requires - the pair in both its requirements and execution-profile registries, rejects - stale plan/form identity mismatches at execution, and cannot be promoted by - installed artifacts, history, or client-supplied proof. The client treats - schema-v2 `runnableModes` (including `[]`) as authoritative and submits the - exact model and mode in runtime hints. - - Evidence: backend Ruff, dependency validation, preflight, and the complete - suite passed (`639 passed, 318 subtests passed`). Client `npm run check` - passed, and the focused mocked-browser regression passed (`1 passed`). The - complete Windows browser run executed all 75 cases: the 74 functional cases - passed, while one unrelated layout-snapshot case reported only the two - absent Win32 JSON baselines; the generated baselines were removed. No model, - media, or Gallery assets were downloaded or generated. -- [x] **P0.2 Explicit resource-plan targeting** - - Backend: put loader module/action and execution path in every specification; - remove class-name substring routing; fail if a plan changes zero matching - loaders. - - Client: verify the returned target matches the visible graph before applying - a plan; surface a mismatch instead of enabling Run. - - Tests: Qwen Modular, Qwen Edit Plus, Qwen Layered, Wan Modular, mixed-loader - graphs, stale candidate IDs, and zero-update plans. - - Status 2026-08-10: paired backend `8fb2cb9` and client `c3e8a17` make the - reviewed execution profile the exact Auto authority for loader module, - loader action, execution path, pipeline class, model type, mode, and pinned - artifact. Plans, selected candidates, candidate lists, retries, history, and - runtime hints retain and cross-check that identity. The backend targets only - exact executable-path loaders, rejects missing, ambiguous, stale, - cross-profile, disconnected, and zero-target plans, and emits bounded - non-echoing failures. The client validates bounded schema-v2 responses, - requires an exact selected/list receipt, checks an enabled managed loader - before readiness, mutation, and submission, and exposes a mismatch instead - of enabling Run. Z-Image Auto uses its reviewed direct-image adapter while - its separate Expert Modular capability remains unchanged. - - Evidence 2026-08-10: the complete backend gate passed 1113 tests with 4 - skips and 1688 subtests; the focused independent P0.2 replay passed 172 - tests and 330 subtests. Ruff E9/F, dependency validation, preflight, and - diff checks passed. The client `npm run check` and all 86 mocked Studio - browser cases passed; focused contracts passed 76/76 and an independent - critical-browser replay passed 7/7. The production bundle is 523003 bytes - gzip, 133 bytes inside the stricter 523136-byte safety target. The mirrored - client matched all 26 generated files byte-for-byte, and a fresh backend - served `/`, `/assets/index.js`, `/health`, and `/runtime/status`. These are - CPU/static/unit/contract/mocked-browser and local HTTP results: no model or - Gallery asset was downloaded, and no model or media output was generated. -- [x] **P0.3 Canonical execution-spec registry and generic node closure** - - Deliver this as the following independently committable, CPU-only paired - segments. None downloads a model or generates an asset. - - [x] **P0.3a.1 Registered Modular dynamic action safety** - - Backend: add one action-contract resolver used by Encode Prompt, Image - Embeddings, Image Encode, Denoise, Decode Latents, and ControlNet; derive - their fields from a defensive copy of the selected model specification; - reject absent actions consistently during definition update and stale - execution; reconcile every connected component's `model_type` against - the selected pipeline rather than letting cached node state win. - - Client: keep the generic `/nodes` renderer and dynamic field-action path; - add a mocked browser matrix proving that switching the connected model - changes fields without a model-name branch, removes unsupported actions, - and reports stale imported edges cleanly. - - Tests: every registered pipeline by every dynamic action; supported and - unsupported updates; selected-versus-connected identity mismatch; unknown - class; repeated custom-sidecar updates without registry mutation; existing - Auto exact-pair behavior unchanged. - - Status 2026-08-07: implementation and source gates are complete for the - eleven registered built-in classes in paired backend `91c9a36` and client - `28b12b7`. The backend now uses - one immutable contract resolver and one actionable model-type resolver - across all six nodes. Runtime recovery rejects mixed selected/connected - identities, while SDXL's valid bundle-only ControlNet contract remains - supported. ControlNet now forwards any signalled model identity instead - of maintaining a class allowlist. The client test uses a synthetic generic - task/model contract and proves backend definitions remove and restore - executable fields with no product model-name branch. - - Evidence: the focused backend run passed (`56 passed, 92 subtests - passed`); the complete backend suite passed (`649 passed, 402 subtests - passed`), along with Ruff, dependency validation, and preflight. Client - `npm run check` passed, the two focused dynamic-definition browser tests - passed, and the complete mocked Studio run passed all 75 functional cases; - its only failure was the pre-existing absent Win32 JSON baselines for one - layout snapshot. `check:ui` likewise reached the pre-existing absent - Win32 PNG baselines. All six generated baseline candidates were removed. - No model, media, or Gallery asset was downloaded or generated. - - [x] **P0.3a.2 Custom Modular contract identity isolation** - - Backend: replace the global mutable Dummy configuration with an immutable - contract identity and bounded cache keyed by source (`hub` or `local`), - repository, revision, explicit remote-code trust choice, the verified - `modiff_pipeline_config.json` hash, and a bounded executable-metadata - manifest hash. Resolve the exact sidecar bytes without network access or - upstream construction. Runtime recovery must restore the matching - declarative contract without one standard or custom loader resetting - another graph. Hub recovery requires an exact cached commit; local - recovery rejects containment and executable symlink escapes. Keep - per-identity custom bindings out of the public built-in enumeration and - use locked, defensive snapshots. - - Client: carry the same backend-issued opaque custom contract identity in - two generic carriers without interpreting repository names or identity - fields: a hidden loader value that is persisted, exported, and included in - run hashes, plus transient output signals that drive connected dynamic - fields. Signal values remain non-durable. Rebase stored nodes onto current - backend action metadata and hidden contract fields so legacy graphs can - acquire the identity without a model-specific migration. - - Security boundary: custom pipeline and Dynamic Block execution is - `contract_only` in this segment, even with `trust_remote_code=false`. - The pinned upstream constructor imports the installed library named by a - repository-controlled component `type_hint` before MoDiff can approve - it. `trust_remote_code=true` and non-boolean lookalikes fail before cache - reuse or upstream calls. Sidecar callbacks are declarative-only, imported - client actions remain inert until rebased from `/nodes`, and - `/fields/action` validates the live module, node, field, and callback. - Built-in Modular execution is separately bound to the registered default - repository, cataloged commit, pipeline class, and installed component - type hints. The generic standalone component loader resolves a reviewed - Diffusers model class from bounded `config.json` rather than letting - repository `model_index.json` select an installed package. - - Tests: custom A, standard B, then A again; interleaved custom A/B nodes; - restart/recovery from self-describing inputs; sidecar hash/revision - mismatch; source/trust/execution-ID tampering; strict boolean validation; - missing, malformed, duplicate-key, or oversized local sidecar; Hub/local - shadowing and local symlink escape; no network during recovery; concurrent - registry access and failed-load cache isolation; repository-class - masquerading; cache-mutated component hints; standalone component - category/class mismatch; arbitrary installed-package dispatch; and - authoritative field-action dispatch. Client tests cover opaque - propagation, disconnect clearing, hidden-value graph round-trip and - run-hash participation, legacy/empty-registry inert hydration and live - rebasing, source/revision commit actions, and switching back to a normal - transient signal. Assets and model weights: none. - - Status 2026-08-09: implementation and all source, contract, browser, bundle, - and fresh-backend HTTP gates are complete in paired backend `91c9a36` and - client `28b12b7`. The identity is a content checksum used for recovery and run - hashing, not authorization. Its executable-metadata manifest detects - bounded Python and loader-config drift but deliberately does not claim - atomic custom-code execution or model-weight proof. Executable custom - pipelines and Dynamic Blocks moved to the reviewed P1.1 admission contract - rather than weakening this boundary. - - Evidence: the combined focused backend matrix passed (`166 passed, 180 - subtests passed`); the complete backend suite passed (`709 passed, 482 - subtests passed`), with only the reviewed upstream `torch_dtype` - deprecation warning. Ruff `E9,F`, dependency validation (`78 packages - compatible`), compile checks, diff checks, and preflight (`ready: true`) - passed. Client `npm run check` passed, including formatting, lint, type - checking, all unit tests, the production build, and the bundle budget - (`523172 / 523264` gzip bytes). The three focused mocked-browser dynamic - contract regressions passed. The exact built `index.js` was mirrored into - the backend without touching backend-owned Gallery media; a fresh backend - served `/` and `/assets/index.js` with HTTP 200 and the expected 1,303,875 - byte asset. No model, media, Gallery asset, Transformers installation, - network model download, or GPU execution occurred. - - [x] **P0.3b Standard adapter fail-closed closure** - - Backend: choose the official default repository after pipeline-class - changes; revalidate image actions against adapter modes; make video loader - dispatch and untagged-pipeline recovery explicit; preserve and validate - audio mode/task/input identity; add permanent direct-profile-to-adapter - closure tests. - - Client: filter generic action and task choices from backend declarations, - with no Flux, Wan, ACE-Step, or Stable Audio selection branch; show an - unsupported-contract error for stale graphs. Remove ModelSelect's - client-owned family inference and repository-name matching so the live - backend `fieldOptions.filter` contract is authoritative for both Hub and - local choices, including future model families. - - Tests: fake pipelines only, including manual class-only changes, wrong - image action, unknown video adapter, Stable Audio task filtering, ACE - mode/task disagreement, every direct profile/class/mode tuple, and a - synthetic future-family ModelSelect option admitted solely by backend - metadata. - - [x] **P0.3b.1 Registry and direct-profile closure:** declare ordered modes, - reviewed primary/compatible repositories, and exact load/execute handlers; - permanently test every public direct profile maps to an adapter and every - advertised mode is implemented. Expert-only adapters need not be promoted - into Studio. CPU/static tests only; assets: none. - - [x] **P0.3b.2 Generic image action identity:** resolve backend-managed - defaults after a class change while preserving explicit Hub/local choices; - tag class, mode, and repository even on cache hits; validate Generate, - Edit, Inpaint/Outpaint, and Control actions before upstream execution; and - publish generic field filters/contracts from the backend. Remove - `FluxControlNetPipeline` from selectable claims until the generic loader - can supply its required ControlNet component. Fake pipelines only; assets: - none. - - [x] **P0.3b.3 Generic video dispatch and recovery:** replace FramePack and - Wan-VACE catch-all fallbacks with exact handler lookup; recover an untagged - object only when runtime class and reviewed repository identify exactly one - adapter; reject shared Wan classes without enough identity; and publish - allowed modes from backend metadata. Fake pipelines only; assets: none. - - [x] **P0.3b.4 Generic audio mode/task/input contract:** bind each ACE-Step - and Stable Audio mode to its valid task, required/forbidden audio inputs, - duration/interval rules, and backend-driven task visibility. Keep - `extract`, `lego`, and `complete` unavailable until their missing dedicated - inputs and modes are designed. Fake pipelines only; assets: none. - - [x] **P0.3b.5 Generic client filtering:** remove repository-name/family - inference from ModelSelect. Apply backend class/id filters and dynamic - field mutations only; prove an opaque future family works without a - production model-name branch and a stale tuple remains blocked. Mocked - browser only; assets: none. - - [x] **P0.3b.6 Generic auxiliary LoRA identity:** replace mutable and - path-only `custom_lora` payloads with one versioned, model-neutral - descriptor. Hub weights require an exact commit, SHA-256, managed-cache - containment, and a literal lowercase `.safetensors` filename; local - weights require an existing resolved Safetensors file and a content - digest. Revalidate the descriptor immediately before every Modular, - stack/mix, and hotswap load so a hand-authored graph cannot bypass the - producer node. Keep scheduler overrides inside a reviewed Diffusers - scheduler contract. Fake pipelines and temporary tiny Safetensors only; - assets: none. - - [x] **P0.3b.7 Paired checkpoint:** run focused and complete backend/client - gates, mirror the reviewed client bundle, perform a fresh HTTP smoke, and - record paired references. Live generation and Gallery assets are not - required for this contract-only phase. - - Status 2026-08-09: P0.3b.1 through P0.3b.6 are implemented and independently - re-reviewed. Image, video, and audio loaders now bind exact class, mode, - source, repository, revision, runtime identity, and backend-issued dynamic - contracts. Unknown or ambiguous adapters fail closed. ModelSelect consumes - only the backend class/id filter grammar, including opaque future-family - tests, and contains no new family-name selection branch. Cache-ignored mode - retags reuse resident pipelines while invalidating descendants so every - action reruns its authoritative preflight. The generic auxiliary LoRA - descriptor revalidates an exact commit, managed alias or contained local - path, SHA-256, and a nonempty Safetensors header before every lifecycle - mutation. Its optional scheduler override is restricted to a bounded, - reviewed FlowMatch contract and is preconstructed for the same pipeline. - - Evidence 2026-08-09: the independent combined P0.3b matrix passed (`436 - passed, 880 subtests, 2 platform skips`), and the final complete backend - suite passed (`873 passed, 2 skipped, 1 warning, 1100 subtests`). Ruff - `E9,F`, dependency validation (`78 packages compatible`), preflight - (`ready: true`), and both diff checks passed. Client `npm run check` passed - in 44.8 seconds, including format, lint, typecheck, contract suites, - production build, and the bundle budget (`522401 / 523264` gzip bytes). - Six focused backend-driven mocked-browser contracts passed, and a fresh - real-browser smoke connected to the backend and materialized the generic - Layered workflow at the backend-bound 640 by 640 resolution. - - Checkpoint closure 2026-08-10: the implementation is recorded in backend - `91c9a36` and client `28b12b7`; the exact-target follow-up checkpoints are - backend `8fb2cb9` and client `c3e8a17`. Four Win32 shared-control snapshots - were generated by the existing Playwright contract, reviewed for font, - clipping, overlap, disabled/error-state, and portalled-listbox correctness, - and committed as client `d226c4b`. The unmodified `npm run check:ui` then - passed all 2 shared-control and 86 mocked Studio cases. The current client - `npm run check` passed with a 523003-byte gzip bundle, and the current - complete backend gate passed 1113 tests with 4 skips and 1688 subtests. - The reviewed build matched all 26 mirrored files byte-for-byte; all 317 - backend-owned local Gallery files remained present; and a fresh backend - returned HTTP 200 for the app, bundle, health, and runtime status before - port 8088 was released. No model, media, Gallery asset, Transformers - installation, network model download, or GPU execution occurred. The - separate P0.5 clean-base migration remains required before the base - installer can claim that Transformers and PEFT are absent by default. - - [x] **P0.3c Upstream workflow and component truth closure** - - Backend: remove false runnable claims first; validate every Modular - action against an upstream workflow or reviewed fixed block sequence; - remove Flux-family Guider ports, add Wan Guider ports, add the two guiders - present at the pin, and record latest-only guiders behind the pin update. - Add missing fields only where their complete state flow is understood. - Correct the standard Flux adapter's modern `true_cfg_scale` and negative- - prompt forwarding so the generic guidance field does not silently target - the wrong upstream parameter. - - Client: consume the corrected action set and never infer support from a - family name, a generic port, an installed artifact, or a nearby workflow. - - Tests: all eleven registered Modular classes, all non-null node actions, - block input/output/component closure, guider compatibility, and explicit - empty runnable sets. New modes remain `contract_only`, Expert-visible, - and excluded from Auto. - - [x] **P0.3c.1 Remove false claims and freeze the upstream matrix:** derive - closure tests from the pinned workflow maps or reviewed fixed block - sequences; withdraw currently advertised modes that cannot be assembled, - including Modular SDXL inpaint, before adding any replacement. Record - unsupported reasons in capabilities instead of exposing a broken action. - Static/no-weight tests only; assets: none. - - Status 2026-08-09: a data-only truth matrix now freezes the exact - workflow maps or reviewed fixed top-level block sequences for all eleven - registered Modular classes. Every advertised Modular mode resolves to a - current generic action sequence, covers its upstream-required user - inputs, and has explicit producer-to-consumer state edges. At that - checkpoint, SDXL Modular inpaint was removed from runnable modes and its - then-missing mask, masked-latent, crop, and overlay state was published - as an additive schema-v2 `unsupportedModes` reason. The later P0.3c.3 - internal base-inpaint closure below supersedes that state-gap detail - without promoting the mode. The existing Flux ControlNet reason is also - normalized. Current clients safely ignore that new optional detail while - continuing to treat the corrected `runnableModes` list as authoritative. - Flux2 Klein retains its legacy Modular `modelType` identifier for - compatibility, but now identifies only the standard - `Flux2KleinPipeline`, generic image backend, and - `flux2-klein:direct` execution profile; a missing referenced profile - empties its public runnable set. - - Evidence 2026-08-09: the focused matrix passed (`8 passed, 24 - subtests`); the adjacent capability, upstream Modular, and direct-profile - batch passed (`46 passed, 158 subtests`). Repository-wide Ruff E9/F and - `git diff --check` passed. The checks instantiated only no-weight block - definitions and generated no model downloads, media, or assets. - - [x] **P0.3c.2 Guider and component-port truth:** remove Guider ports from - Flux, Flux Kontext, and Flux2 Klein; add the upstream Wan T2V/I2V Guider - ports; expose the two guiders present at the pin; keep the latest-only - guider behind a Diffusers-pin update. Validate every port against the - instantiated no-weight block contract. Assets: none. - - Status 2026-08-09: the eleven registered no-weight denoise block - contracts now enforce exact generic component-port closure. Flux, Flux - Kontext, and Flux2 Klein expose no Guider port; Wan T2V and I2V do. - `AdaptiveProjectedMixGuidance` and `PerturbedAttentionGuidance` use the - pinned constructor contracts, while latest-only - `MagnitudeAwareGuidance` remains unregistered. Perturbed Attention maps - the generic Layers payload to `perturbed_guidance_config` and rejects - missing, malformed, or incompatible layer data before construction. - Additional model-to-layer-stack discovery remains tracked under P0.3e; - no unreviewed Wan stack name was guessed here. - - Evidence 2026-08-09: focused upstream contracts passed with `34 passed, - 116 subtests`; the adjacent Modular/schema/identity batch passed with - `113 passed, 1 warning, 185 subtests`; repository-wide Ruff E9/F and - `git diff --check` passed. These were CPU/no-weight tests and generated - no media, downloads, or assets. - - Superseded by P1.3 on 2026-08-12: the class was verified at the existing - pin under the official `diffusers.guiders` namespace even though the - top-level lazy export omits it, and it is now registered without a pin - change. - - [x] **P0.3c.3 Complete generic state flows:** add mask/processed-mask/ - overlay state for SDXL and Qwen inpaint, a generic IP-Adapter encoding - action for SDXL, and first/last-frame state for Wan I2V. Preserve the - exact Qwen Layered controls recorded below. Add generic - control-edit/control-inpaint fields only as - complete backend adapters, never as family-named frontend nodes. Tiny - fixture tests may run locally; assets: none. - - Preparatory truth cleanup 2026-08-09: removed the stale - `QwenImageEditPlusModularPipeline:inpaint` claim from backend legacy - mode metadata and the client's offline profile and Auto fallbacks. The - blocked mask-contract explanation remains available, schema-v2 - `runnableModes` remains authoritative, and the standard - `QwenImageEditInpaintPipeline` inpaint/outpaint profile is unchanged. - An imported stale Edit Plus inpaint form now receives the exact backend - unsupported-mode blocker and cannot become Auto-ready. Focused backend - tests passed (`4 passed, 39 subtests`), and the Auto execution guard - passed (`1 passed, 3 subtests`); client template/contract tests passed - (`57 passed`), with client format, lint, and typecheck plus - backend Ruff E9/F and both diff checks passing. Static tests only; no - downloads, model execution, generated media, or assets. This does not - complete the P0.3c.3 state-flow work. - - Preparatory Layered-control closure 2026-08-09: the backend's existing - generic Encode Prompt and Encode Image actions now publish the targeted - pinned Layered controls: a shared `resolution` constrained to 640 or - 1024 with default 640, text-only `use_en_prompt` defaulting false, and a - positive `max_sequence_length` capped at 1024 with default 1024. The - client removed its Layered class-name branch and consumes a small - backend-issued `studioBinding` grammar through the existing managed - graph. Opaque field names synchronize only when their complete binding - signatures match. Unknown, extra, malformed, direct-dimension identity, - and out-of-envelope dimension metadata fail closed; valid numeric node - edits canonicalize the source and every peer to the target type. This - slice does not change Auto modes or the existing Layered template; the - new control evidence is contract-only. - - Evidence 2026-08-09: backend upstream/schema/fake-pipeline tests passed - (`49 passed, 134 subtests`), and the adjacent Modular suite passed (`74 - passed, 158 subtests`); client generic graph contracts passed (`38 - passed`), the focused mocked-browser dynamic-refresh test passed (`1 - passed`), and client format, lint, and typecheck plus backend Ruff E9/F - and both scoped diff checks passed. These checks loaded no weights and - generated no downloads, media, templates, or assets. - - Preparatory seed-state closure 2026-08-09: every one of the ten - registered VAE encoder actions whose pinned upstream block exposes - `generator` now declares the same bounded generic seed field. Encode - Image validates the scalar before pipeline initialization, constructs a - Torch generator on the managed pipeline execution device, and forwards - no raw seed. Caller-supplied generator objects and backend/upstream - generator-contract disagreement fail before model identity resolution or - block initialization. Wan I2V's distinct image encoder truthfully remains - unchanged because it has no upstream generator input. Focused - schema/upstream/fake-pipeline tests passed (`54 passed, 160 subtests`), - the adjacent Modular/graph/custom-identity batch passed (`137 passed, 1 - warning, 309 subtests`), and the independent root matrix passed (`121 - passed, 1 warning, 229 subtests`). Ruff E9/F and diff checks passed. No - dependency, client, graph, template, asset, model, GPU, inference, or - Transformers-install action was involved. - - Preparatory opaque-route closure 2026-08-09: built-in Qwen Image and - Qwen Image Edit VAE encoders now carry post-sampling generator state, - processed mask, overlay state, and exact latent pairing through generic - Encode Image -> Denoise -> Decode Latents route handles. Qwen Image Edit - Plus uses the same route only for generator continuity and rejects - inpaint state. Each process-local route is bound to one successful - Models Loader execution, exact component role and model-id inventory, - stage, seed, and the exact paired Torch tensor through a weak identity - reference. Edit Plus multi-image state instead seals a bounded ordered - list of exact tensor identities. Serialized, nested, stale, - cross-loader, role-swapped, same-loader cross-paired, reordered, and - dead-reference values fail before pipeline initialization. Route-aware - node-cache comparison preserves only exact tensor identities, so an - equal-valued clone cannot bypass that validation. Denoise retries clone - the stored post-VAE generator state, and every supported Qwen Decode - requires the matching Denoise route. - - The client selects this topology only from generic live handles: every - Modular graph with a complete route contract connects Denoise to Decode; - image flows additionally connect Encode Image to Denoise; and native - inpaint replaces the Apply Mask fallback only after the mask plus both - route chains are complete and have exact input/output displays plus one - identical unpadded opaque type. Partial, malformed, mismatched, or - out-of-order node definitions keep finalization and Run pending. The - schema-v2 finalization proof binds the sorted exact endpoint set and is - revalidated against live managed edges, so preserved-ID endpoint edits - and forged checksums cannot restore readiness. No model or repository - name controls this branch. - - Preparatory standalone-component provenance 2026-08-09: generic - AutoModelLoader outputs now carry an immutable, process-local binding - that seals the component role, ComponentsManager identity, canonical - source/repository/revision/subfolder/class tuple, and config SHA-256. - Per-loader current-publication identity revokes resident A after A->B; - resident reuse and node-cache hits require the exact live binding, while - config TOCTOU, relabeling, metadata tampering, and stale publications - fail before class resolution or component initialization. This is the - dormant provenance foundation for the next Qwen ControlNet route slice; - it does not add ControlNet fields, runtime behavior, modes, templates, - or client branches. Cache HTTP responses now also reject connector and - other opaque static types with a controlled `400`; only declared media - and text families are served. - - Evidence 2026-08-09: the final dedicated route/provenance suite passed - (`49 passed, 61 subtests`), cache HTTP security passed (`20 passed, 29 - subtests`), and the complete backend gate passed (`931 passed, 2 - skipped, 1 warning, 1211 subtests`). Repository-wide Ruff E9/F, - dependency validation, preflight, and diff checks passed. Client graph - contracts passed (`39 passed`), proof/template contracts passed (`58 - passed`), the focused out-of-order mocked-browser transition passed (`1 - passed`), and full `npm run check` passed with the unchanged bundle - budget (`523126 / 523264` gzip bytes). The reviewed bundle was mirrored - byte-for-byte and fresh HTTP smoke checks returned `200` for `/`, - `/assets/index.js`, and `/health`. These were CPU/static, no-weight, and - mocked-browser checks; they made no model downloads, live inference, - generated media, template, capability, optional-runtime, or Gallery- - asset changes. - - Preparatory Qwen ControlNet route closure 2026-08-09: the generic - ControlNet action now exposes a bounded seed plus opaque route input and - output only when the pinned upstream ControlNet VAE block exposes its - generator. Text control starts from that seed; an image route continues - the exact post-main-VAE generator snapshot. The output seals the exact - current standalone ControlNet publication and an ordered weak identity - for the resulting control latent tensor or bounded tensor list. Denoise - requires the matching ControlNet bundle, component publication, seed, - and inherited image state, continues the generator once, and emits the - already-reviewed Decode route. No large image latent is copied into the - opaque carrier or routed through ControlNet. - - Cache candidates re-resolve and validate the complete effective - image/control inputs, reserved fields, component roles, and route state; - same-object nested mutation cannot reuse a cached result. ControlNet - revalidates both the VAE and standalone publications after block - initialization, after component updates, immediately before the - upstream call, and again after it before publishing any bundle or route. - Init-time, call-time, stale-publication, cross-loader, cloned-latent, - reordered-list, nested-reserved-input, and retry paths all fail closed - at their reviewed boundary. The SDXL bundle-only branch remains - unchanged and has an exact legacy-output regression. - - The client adopts this route using only generic live handles, display - directions, and one exact opaque type. Legacy ControlNet graphs retain - their bundle-only topology. A complete contract connects ControlNet to - Denoise to Decode, and adds Encode Image to ControlNet only when that - prefix is published; ordinary image/control latent and bundle edges stay - typed and direct. Partial, mismatched, or out-of-order definitions keep - the last stable topology and Run proof pending. Seed synchronization is - field-driven across Encode Image, ControlNet, and Denoise; no model or - repository name selects the client branch. - - The existing Qwen ControlNet component is now pinned consistently in - the executable catalog, backend capability requirements, saved graph, - workflow manifest, and client requirement to official repository - `InstantX/Qwen-Image-ControlNet-Union` commit - `b13036f066d6dee7c20513e263d3d673055e9de8`. Metadata-only review confirmed - the Apache-2.0 repository, configuration, and Safetensors files; no - weight was downloaded. This slice promotes no new mode and generates no - template or Gallery asset. - - Evidence 2026-08-09: the final focused backend route/upstream/truth/schema - matrix passed (`120 passed, 247 subtests`), the adjacent matrix passed - (`134 passed, 1 warning, 81 subtests`), the exact adversarial boundary - matrix passed, and the complete backend gate passed (`954 passed, 2 - skipped, 1 warning, 1242 subtests`). Repository-wide Ruff E9/F, - dependency validation, preflight, and diff checks passed. Client graph - contracts passed (`39 passed`), the focused late-definition browser test - passed (`1 passed`), and full `npm run check` passed with the unchanged - budget (`523065 / 523264` gzip bytes). The validated client bundle was - mirrored byte-for-byte; fresh HTTP smoke checks returned `200` for `/`, - `/assets/index.js`, and `/health`. These were CPU/static, no-weight, and - mocked-browser checks with no model download, inference, generated - media, optional-runtime installation, or new asset. - - Preparatory combined-Qwen state-flow closure 2026-08-10: a nonadvertised - pinned truth table now records the exact upstream block order, required - inputs, generic action order, and ordered typed/opaque state edges for - Qwen img2img, inpaint, ControlNet img2img, and ControlNet inpaint. A - no-weight fake-action matrix executes all four paths through the real - Encode Image, optional ControlNet, Denoise, and Decode adapters while - proving generator continuation, mask/overlay preservation, and exact - source/control separation. Public capabilities, profiles, modes, - templates, Auto candidates, and assets are unchanged. - - The generic client may preserve that dormant combined topology only for - canonical `edit_image` or `inpaint` bindings that already own a complete - distinct control-image loader, ControlNet model loader, and ControlNet - action. Legacy text-control retains its single-loader fallback. Missing, - partial, detached, duplicated, wrong-mode, or stale-form role groups - remain pending and mint no proof. Binding mode/model/fingerprint now - gate value application, edge creation, restore, waiting, and final proof - publication; every participating optional node and exact endpoint is - sealed by the binding-scoped schema-v2 proof. No model or repository - name selects this topology. - - Evidence 2026-08-10: focused backend truth/route tests passed (`80 - passed, 128 subtests`), and the complete backend gate passed (`957 - passed, 2 skipped, 1 warning, 1254 subtests`). Client graph contracts - passed (`39 passed`), independent adversarial restore/finalizer review - found no remaining blocker, and full `npm run check` passed with the - unchanged bundle budget (`523078 / 523264` gzip bytes). Repository-wide - Ruff E9/F, dependency validation, preflight, and both diff checks passed. - The validated bundle was mirrored byte-for-byte (SHA-256 - `5beab30710b35c50d105f091cb3d578079b0138fc20fbacc20cce5d818208baf`); - fresh HTTP smoke checks returned `200` for `/`, `/assets/index.js`, and - `/health`, and port 8088 was free after the worker stopped. These were - CPU/static and fake-action checks with no model download, inference, - generated media, optional-runtime installation, template, or asset - change. - - Preparatory SDXL base-inpaint state-flow closure 2026-08-10: a - nonadvertised pinned truth entry now records the exact upstream block - sequence, required inputs, generic action order, and typed/opaque state - edges for base inpaint. The no-weight generic action route preserves - post-VAE generator state, typed mask and masked-image latents, crop - overlay state, exact tensor identity and mutation version, and the - exact resident VAE identity plus derived geometry across cache, - initialization, component update, call, and output boundaries. Bounded - tensor and PIL crop validation fails before expensive execution. - - The generic client latches this dormant typed topology only after a live - Denoise VAE handle or persisted managed VAE edge is observed, then - requires the exact matching VAE, mask, masked-latent, and route handles - before finalization. Missing or malformed later definitions remain - pending instead of downgrading to Apply Mask or a route-only topology; - the latch is binding-fingerprint-scoped, so a new route-only Qwen - binding does not inherit it. The complete native topology has an exact - 16-edge schema-v2 proof, and no model or repository name selects it. - - Public capabilities, profiles, modes, Auto candidates, templates, and - Gallery assets are unchanged. SDXL ControlNet inpaint, ControlNet - Union, IP-Adapter, and combined variants remain explicitly outside this - base contract, and no live model qualification was performed. - - Evidence 2026-08-10: the independent focused backend matrix passed - (`152 passed, 298 subtests`), its exact adversarial slice passed (`14 - passed, 39 subtests`), and the complete backend gate passed (`969 - passed, 2 skipped, 1 warning, 1296 subtests`). Client graph contracts - passed (`39 passed`), independent review and full `npm run check` - passed with the unchanged budget (`523030 / 523264` gzip bytes). - Repository-wide Ruff E9/F, dependency validation, preflight, and both - diff checks passed. The validated client bundle was mirrored - byte-for-byte (1,298,272 bytes; SHA-256 - `e2811ebfc6c9d5cd78f294216daaa69667843af7ae57a1ade0144e1d3eda1ba8`); - fresh HTTP smoke checks returned `200` for `/`, `/assets/index.js`, and - `/health`, and port 8088 was free after the process tree stopped. These - were CPU/static and fake-action checks with no model download, - inference, generated media, optional-runtime installation, template, - or Gallery-asset change. - - Preparatory Wan split-route closure 2026-08-10: the existing - backend-declared Modular `image_to_video` action now preserves exact - image embeddings, condition latents, two-pass area-budget geometry, - post-VAE generator state, source-media snapshots, resident - component/processor identity, and bounded effective configuration - through Image Embeddings, Encode Image, Denoise, Decode, cache, retry, - and TOCTOU boundaries. Raw - mode-specific frame latents remain internal to the VAE action. - First/last-frame truth and typed topology are structural only. The - official FLF repository has a distinct CLIP preprocessing and - transformer positional-embedding contract and is not an executable - catalog artifact, so `last_image` is rejected before contract lookup, - component resolution, block initialization, or cache reuse. No mode, - profile, template, asset, or dependency pin was added. - - The generic client recognizes this dormant Modular video topology only - from complete live roles, handles, display directions, and one exact - opaque route type. An already-present I2V group has an exact 17-edge - schema-v2 proof; the preparatory FLF group adds a distinct last-image - loader and two last-image edges for an exact 19-edge proof. Partial, - removed, remapped, duplicated, type-mismatched, or retained hidden role - groups remain pending and cannot downgrade to the standard video - facade. No model/repository name selects the branch, and no selectable - Studio profile or template was added. - - Evidence 2026-08-10: the dedicated CPU/no-weight Wan boundary suite - passed (`30 passed, 81 subtests`); the route/schema/truth/upstream matrix - passed (`165 passed, 382 subtests`); and the adjacent action recovery, - output, offload, and custom-identity matrix passed (`124 passed, 1 - warning, 106 subtests`). Two independent frozen audits reproduced the - exact cataloged I2V processor/component contracts and adversarial - device, tensor, generator, crop, cache, and TOCTOU boundaries without a - model download. The complete backend gate passed (`999 passed, 2 - skipped, 1 warning, 1377 subtests`); repository-wide Ruff E9/F, - dependency validation, preflight, and diff checks passed. - - Client graph contracts passed (`40 passed`) and full `npm run check` - passed with the unchanged budget (`523110 / 523264` gzip bytes). The - reviewed build was mirrored exactly as `index.js` (1,006,250 bytes, - SHA-256 `801f0f5d06c2d073b77deee696ce048039ab5f68fa6b30da7c09480d9d7459f3`) - plus its static `studio-templates.js` chunk (296,450 bytes, SHA-256 - `e3c722e395f5f93751d14b6e936d5d07d85e5a65e3b73c428d8f26f74aaaddea`). - Fresh HTTP smoke checks returned `200` for `/`, `/assets/index.js`, - `/assets/studio-templates.js`, and `/health`, and port 8088 was free - after the process tree stopped. These were CPU/static, no-weight, and - fake-action checks with no live inference or model, media, template, or - Gallery-asset download/generation. - - Preparatory SDXL ordinary-ControlNet composition closure 2026-08-12: - pinned truth now records the exact upstream - `controlnet_image2image` and `controlnet_inpainting` block order, - required inputs, generic action sequence, and typed/opaque graph edges. - The existing VAE route may reach Denoise alongside the generic - ControlNet bundle only when the standalone loader publication is the - exact ordinary `ControlNetModel` class. Denoise resolves its exact - managed component before pipeline initialization, proves that the same - object was installed, and rechecks publication plus resident identity - at cache, pre-call, and post-call boundaries. Missing bundles, - prepared Qwen control latents, stale publications, Union components, - and init/call-time resident swaps fail closed before a runnable output - route is published. The upstream component remains installed on the - Modular pipeline; only its ordinary control image and scale inputs are - forwarded to the call. - - This slice is internal and no-weight. It does not advertise a new - mode, choose or download an SDXL ControlNet artifact, add an execution - profile, Auto candidate, template, or Gallery asset, or qualify model - output. ControlNet Union and IP-Adapter remain rejected as distinct - component/weight contracts. Focused truth, route, upstream, and Wan - regression tests passed (`173 passed, 456 subtests`). The complete - backend gate passed (`1213 passed, 4 skipped, 1 existing warning, 2048 - subtests`); Ruff E9/F, `py_compile`, dependency validation (78 - compatible packages), preflight, and diff checks passed. The unchanged - compatible client passed complete `npm run check`, including its graph - contracts and a 523108/523264-byte gzip bundle (28 bytes below the - stricter 523136-byte target). - - Public mode/profile promotion, templates/assets, and live qualification - for the completed Qwen and SDXL base/ordinary-ControlNet flows remain - outstanding; SDXL ControlNet Union and IP-Adapter combinations remain - unfinished, so P0.3c.3 stays incomplete. - - Preparatory SDXL ControlNet Union composition closure 2026-08-12: the - same generic ControlNet action now declares an ordinary/Union selector. - The existing client field-action grammar hides the Union control-type - index for ordinary execution and reveals it only for Union. The index is - a bounded generic integer rather than an artifact-specific semantic - name. The action requires the selected variant to match the exact - process-local `ControlNetModel` or `ControlNetUnionModel` publication; - Denoise additionally validates the index against the exact resident - Union model's bounded `num_control_type`, installs that same object, and - repeats the publication/resident checks at cache, initialization, - pre-call, and post-call boundaries. Pinned truth records the complete - single-control Union image-to-image and inpaint block/action/edge flows. - - This Union slice remains internal, CPU/no-weight, and single-control. It - adds no public mode, execution profile, selected repository, Auto - candidate, template, Gallery asset, model download, or output - qualification. Multi-ControlNet, IP-Adapter, public promotion, and live - qualification remain open, so P0.3c.3 stays incomplete. - Focused truth/route/upstream/Wan regressions passed (`173 passed, 459 - subtests`); the complete backend gate passed (`1213 passed, 4 skipped, - 1 existing warning, 2051 subtests`). Ruff E9/F, `py_compile`, dependency - validation (78 compatible packages), preflight, and diff checks passed. - The compatible client passed complete `npm run check`, including 37 - run/field-action tests and the unchanged 523108/523264-byte gzip bundle. - - Preparatory SDXL single-IP-Adapter state-flow closure 2026-08-12: a new - generic **IP-Adapter Embeddings** action derives its fields from the - selected registered pipeline contract and matches the pinned upstream - `StableDiffusionXLIPAdapterStep`. All nine upstream single-adapter - text/image/inpaint plus ordinary/Union ControlNet compositions now have - exact nonadvertised block, action, and typed-state edge truth. The only - admitted descriptor is the Apache-2.0 `h94/IP-Adapter` repository at - exact commit `018e402774aeeddd60609b4ecdb7e298259dc729`, standard SDXL - weight `sdxl_models/ip-adapter_sdxl.safetensors`, reviewed size - 702,585,376 bytes, and SHA-256 - `ba1002529e783604c5f326d49f0122025392d1d20ac8d573b3eeb3e6dea4ebb6`. - Execution resolves and hashes that file from the local Hub cache only; - it never installs or downloads. The pinned image encoder is likewise - local-only under `sdxl_models/image_encoder` and must retain its exact - `CLIPVisionModelWithProjection` contract: image size 224, patch size 14, - hidden size 1664, 48 layers, 16 attention heads, projection size 1280, - and the reviewed CLIP processor settings. - - The adapter action mutates only the exact resident SDXL UNet, supports - one standard projection and one bounded scale, and issues a - nonserializable process-local receipt sealing loader/UNet/Guider, - adapter parameter versions, encoder/processor, source pixels, and both - embedding tensor identities. Denoise validates the receipt before - initialization, after component installation, before the upstream call, - after the call, and on cache reuse. A missing bundle, copied state, - wrong loader/component/Guider, changed image/tensor/parameter/scale, or - resident swap fails closed. Models Loader removes only a current owned - adapter before minting a replacement loader receipt and rejects - unreceipted mutation state. - - This is an internal/manual contract-only slice. It adds no public mode, - execution profile, Auto candidate, template, Gallery asset, installer, - dependency cutover, or live output claim. The optional Transformers - runtime must already have been explicitly installed and verified; - registry discovery and graph execution do not install it. Multiple - adapters, Multi-ControlNet, public promotion, templates/assets, and live - qualification remain open, so P0.3c.3 stays incomplete. - - Evidence 2026-08-12: the focused IP-Adapter, route-state, workflow-truth, - upstream-contract, artifact-catalog, and local-resolution matrix passed - `162 tests` with `411 subtests`; the complete backend gate passed - `1228 tests` with `4 skips`, `2086 subtests`, and only the existing - upstream Diffusers `torch_dtype` deprecation warning. Ruff E9/F, - `py_compile`, dependency validation (78 compatible packages), preflight, - port, and diff checks passed. The unchanged compatible client passed - complete `npm run check`, including the `523108/523264`-byte gzip - bundle. Its exact mocked-browser generic `node_definition` contract - passed `1/1` and proved that backend-selected node/field metadata updates - without replacing current field values. These are CPU/static/unit/ - contract/mocked-browser results: no Hub download, adapter installation, - model execution, generated media, or live output qualification occurred. - - P0.3c contract-only closure 2026-08-12: the completed base inpaint flow - is now an exact high-level `inpaint` mode on the existing generic SDXL - Modular capability. That whole capability is explicitly - `contract_only`, pinned to - `stabilityai/stable-diffusion-xl-base-1.0@462165984030d82259a11f4367a4eed129e94a7b`, - and publishes exact reference-image plus mask input requirements. It has - no execution profile and sets Auto, template, and Gallery eligibility - false. The combined ordinary/Union ControlNet and single-IP-Adapter - routes remain constructible manual generic-node compositions; no new - model-named mode or client branch was added. Multi-ControlNet and - multiple-adapter execution are outside the pinned 18-workflow upstream - matrix and require their own future artifact/state-flow review rather - than keeping this scoped truth phase open. - - Evidence 2026-08-12: the final P0.3c truth/capability/route/optional- - runtime matrix passed `227 tests` with `1 skip` and `729 subtests`; the - complete backend gate passed `1228 tests` with `4 skips`, `2088 - subtests`, and only the existing upstream Diffusers `torch_dtype` - deprecation warning. Ruff E9/F, dependency validation (78 compatible - packages), preflight/port, and diff checks passed. The unchanged client - passed complete `npm run check` and its `523108/523264`-byte gzip - budget. A fresh HTTP smoke reported ready and returned schema-v2 SDXL - Modular contract-only truth with exactly four high-level modes, the - pinned revision, zero execution profiles, and all three eligibility - flags false; the owned server tree was stopped and port 8088 was free. - This is CPU/static/unit/contract/HTTP evidence only, not model or media - execution or live qualification. - - Wan FLF artifact admission 2026-08-12: the Apache-2.0 official - `Wan-AI/Wan2.1-FLF2V-14B-720P-diffusers` checkpoint is pinned at exact - commit `17c30769b1e0b5dcaa1799b117bf20a9c31f59d7`. Its 50-file snapshot - contains 21 Safetensors files and no Python source. The existing generic - `WanImage2VideoModularPipeline` Models Loader accepts it only as an exact - reviewed repository variant and still requires the catalog revision. - The loader index boundary recognizes only its exact concrete - `CLIPProcessor`/`CLIPVisionModelWithProjection` declarations; the - Image Embeddings action loads the pinned block's exact - `CLIPImageProcessor` view locally. Image Embeddings and Encode Image bind - `image2video` to the original I2V artifact and `flf2v` to the FLF - artifact before block initialization and cache reuse. Swapping only - `last_image`, repository, revision, or component type fails closed. The - generic Model Manager install route now resolves an omitted revision to - the catalog commit for every reviewed repository, so the existing - model-select Install control cannot fetch mutable `main` for this or - another curated artifact; uncataloged user-selected repositories retain - their prior behavior. The affected loader/route/catalog/download matrix - passed 296 tests with 747 subtests. The complete backend gate passed - 1,211 tests with 4 skips, the existing Diffusers deprecation warning, - and 2,041 subtests; Ruff E9/F, `uv pip check` (78 packages), preflight, - `py_compile`, JSON parsing, and diff checks passed. A no-weight exact - config probe read the pinned Hub `model_index.json` and image-processor - config, admitted their exact reviewed types, normalized only the - validated FLF image-processor load contract to `CLIPImageProcessor`, - and observed its 224-pixel shortest-edge/center-crop configuration. The - temporary config-only snapshot was removed. This is metadata, - no-weight, unit, contract, and fake-action evidence only; no FLF model - weights, inference, generated video, public template, or Gallery asset - were downloaded or exercised. The unchanged compatible client passed - complete `npm run check` with a 523108/523264-byte gzip bundle, 28 bytes - below the stricter 523136-byte target; its mocked Model Manager install - flow passed 1/1 in 5.2 seconds. - Public FLF mode/profile/template promotion, assets, and live execution - remain outside this slice. - - [x] **P0.3c.4 Standard signature and adapter truth:** correct Flux - `true_cfg_scale`/negative-prompt forwarding and register missing standard - image classes only when each maps to an existing generic action with an - exact fake-pipeline signature test. Keep KV/full Flux2 and other - artifact-sensitive variants deferred until their runtime artifacts are - reviewed. Assets: none. - - Status 2026-08-10: modern Flux text, img2img, inpaint, and Kontext - adapters now bind the generic guidance value to `true_cfg_scale` and - forward the negative prompt only through an upstream signature that - declares it. Eleven pinned standard classes now reuse the existing - generic Generate/Edit/Inpaint actions: SDXL base/img2img/inpaint; Qwen - Image img2img/inpaint and Edit/Edit Plus; Z-Image img2img/inpaint; Flux - Kontext inpaint; and Flux2 Klein inpaint. Every class uses an already - reviewed immutable base artifact and remains contract-only with no Auto - profile or public template. Full Flux2, Flux2 KV, ControlNet/composite - variants, Qwen Layered, Z-Image Omni, and SDXL instruct-pix2pix remain - unregistered until their extra inputs or artifacts are reviewed. - - Evidence 2026-08-10: the focused image registry passed 73 tests with 2 - skips and 177 subtests. The adjacent profile, capability, artifact, - offload, Qwen-inpaint, and optional-runtime matrix passed 99 tests and - 176 subtests. The complete backend gate passed 1116 tests with 4 skips, - 1740 subtests, and only the existing upstream Diffusers deprecation - warning. Ruff E9/F, `py_compile`, dependency validation (78 compatible - packages), preflight, and diff checks passed. Tests inspected the pinned - upstream signatures and executed every new adapter through fake - pipelines; no model, artifact, media, network, or GPU execution occurred. - - [x] **P0.3c.5 Contract-only exposure:** publish newly complete modes and - already implemented but unprofiled video/audio adapters as - `contract_only`, with backend-driven parameters and no Auto eligibility. - Add templates only after graph-contract validation; generation and public - Gallery media run later on the qualification machine. - - Status 2026-08-11: `/model_capabilities` now publishes every registered - but unprofiled generic Diffusers adapter as an exact experimental - `contract_only` record: 13 standard image adapters, five video adapters, - and Stable Audio. Each record names one generic loader, exact pipeline - class/mode set, reviewed repository and immutable catalog revision, - backend parameter aliases, and mode input contract. Contract-only - records explicitly disable Auto, template, and Gallery eligibility; - they have no execution profile or optional-runtime execution - requirement and remain outside the primary supported capability list. - - Evidence 2026-08-11: the adapter/profile/upstream-truth matrix passed - 267 tests with 2 skips and 777 subtests. The complete backend gate - passed 1117 tests with 4 skips and 1759 subtests, with only the existing - upstream Diffusers deprecation warning. Ruff E9/F, dependency - validation (78 compatible packages), preflight, `py_compile`, and diff - checks passed. Registry closure tests prove the published set is - exactly the image/video/audio adapter set minus profiled classes and - that every artifact is immutably cataloged. No model, artifact, media, - network, GPU execution, template, or Gallery asset was used or changed. - - [x] **P0.3c.6 Paired checkpoint:** run focused and complete backend/client - gates, mirror the reviewed client bundle, perform a fresh HTTP smoke, and - update the support matrix. No large-model or media qualification is part - of this phase. - - Evidence 2026-08-11: paired backend `896661a8dc13` and client - `d226c4bbc2e0` passed the checkpoint. The backend adapter/profile truth - matrix passed 267 tests with 2 skips and 777 subtests; the full backend - gate passed 1117 tests with 4 skips and 1759 subtests plus the existing - upstream deprecation warning. Ruff E9/F, dependency validation, - preflight, and diff checks passed. The unchanged client passed full - `npm run check`, 2/2 shared-control browser tests, and all 86 mocked - Studio browser tests. Its production bundle remained 523003/523264 - gzip bytes, 133 bytes inside the stricter 523136-byte safety target. - - The 26-file client build was verified byte-for-byte against `web/` with - no extra generated deployment files; all 317 preserved local Gallery - files remained untouched. Exact deployed hashes were - `index.js`=`4eb4230f9e3779c89a49a7155ffc56984e47d0c492d604780cafad3ac3e5e133`, - `studio-templates.js`=`e8552942199e04fe3980da5b91550f7e9964af7894bc43e781d2bceaa62554d4`, - and `graph-vendor.js`=`76e8c330da63ee5806ef230e2567ed978fca7005efd1cf679b8c5d99e8bfd337`. - A fresh supervised HTTP smoke returned `200` for `/` and - `/assets/index.js`, `/health` reported ready, and schema-v2 - `/model_capabilities` returned 20 supported, 25 experimental, and 19 - contract-only records with zero Auto-eligible or profiled contract-only - entries. The owned process tree was stopped and port 8088 was free. - No model download, inference, GPU workload, generated media, template, - or Gallery publication occurred. - - [x] **P0.3d Backend-owned Studio execution specifications** - - Backend: make one exact-pair registry the source for execution profiles, - capabilities, Auto requirements, loader choices, workflow validation, - generic graph roles/edges, form bindings, ordered dynamic actions, schema - version, and content hash. Validate every reference against `/nodes`. - - Client: parse and fail-close the new schema. First materialize Flux - Schnell and Flux Dev text-to-image from the same generic recipe so the two - graphs differ only through backend data. Keep legacy schema-v2 handling - during migration, but never fall back from a malformed new specification. - - Tests: unknown nodes/parameters/handles, dangling edges, invalid bindings, - stable hashing, model switch with identical topology, runtime hints and - proof receipts bound to specification identity, and Auto candidate - overrides restricted to declared bindings. - - Evidence 2026-08-11: backend commit `fd258d8` owns the versioned Flux - Schnell and Flux Dev text-to-image specifications, validates them against - the live node registry, publishes their exact content hashes, and rejects - mismatched execution receipts before graph execution. Client commit - `642ea9c` strictly parses those specifications, materializes both models - through one generic graph recipe, preserves node IDs/topology across the - model switch, and seals the selected specification into finalization and - runtime receipts. The exact backend tree passed 1,121 tests with 4 skips - and 1,762 subtests; the focused specification matrix passed 104 tests with - 466 subtests. Ruff E9/F, `uv pip check` (78 compatible packages), preflight, - and diff checks passed. The client specification contracts passed 136/136, - the complete `npm run check` passed, and the complete mocked Studio browser - suite passed 87/87. The production bundle was 522606/523264 gzip bytes, - 530 bytes below the stricter 523136-byte safety target. The mirrored build - matched all 26 generated files byte-for-byte while preserving 317 backend- - owned Gallery files. A fresh HTTP smoke served the exact entry bundle, - reported 131 nodes, and published both specification IDs with hashes - `studio-spec-v1-9cd1abb5` and `studio-spec-v1-d5ee399d`; its owned process - tree was stopped and port 8088 was free afterward. This is static, unit, - contract, mocked-browser, and local HTTP evidence only: no model download, - model execution, generated media, or live workload qualification occurred. - - [x] **P0.3e Remove remaining frontend and node model switches** - - Migrate one exact pair per backend/client commit from `modelProfiles` and - `graphBridge` into validated specifications. Then move loader component - requirements, Denoise field visibility, Layers allowlists, Guider and - Scheduler compatibility, readiness, and resource metadata into reviewed - declarative overlays. - - Composite creative workflows remain versioned template/recipe data, but - reuse the same generic nodes and bindings. Imported/manual Expert graphs - remain editable and do not silently become managed Studio graphs. - - Tests: graph equivalence for every migrated pair, model switching, - dynamic parameter refresh, input/output normalization, readiness, Auto - fail-closed behavior, and removal of the corresponding class-name branch. - - [x] `FluxKreaPipeline:text_to_image`: backend commit `96f70cb` moves its - execution profile, capability, Auto resource contract, generic roles, - topology, field bindings, and receipt hash into the specification - registry. Client commit `80ac243` removes Krea from the Flux-family graph - switch and resolves the direct loader and `FluxPipeline` class from the - exact backend specification/profile. Schnell, Dev, and Krea retain the - same managed node IDs and edge shape while their artifacts and receipt - identities remain distinct. The focused backend matrix passed 66 tests - with 321 subtests; the exact full backend tree passed 1,121 tests with 4 - skips and 1,762 subtests. The client execution-spec matrix passed 136/136, - the complete `npm run check` passed, and the complete mocked Studio browser - suite passed 87/87. The production bundle was 522633/523264 gzip bytes, - 503 bytes below the stricter 523136-byte safety target. The mirror matched - all 26 generated files byte-for-byte and preserved 317 backend-owned - Gallery files. Ruff E9/F, package compatibility, preflight, formatting, - lint, type, and diff checks passed. This is static, unit, contract, mocked- - browser, build, and local preflight evidence only; no Krea download, model - execution, generated media, or live workload qualification occurred. - - [x] `FluxDepthPipeline:control_image`: backend commit `a299d1d` moves its - execution profile, capability, Auto resource contract, generic image - loader, control-image loader, control generator, preview route, field - bindings, and receipt hash `studio-spec-v1-2d8b881e` into the specification - registry. Client commit `2de0c68` removes Depth from the Flux-family graph - switch and resolves the direct facade plus `FluxControlPipeline` identity - from the exact backend specification/profile. The focused backend matrix - passed 67 tests with 321 subtests; the exact mirrored backend tree passed - 1,122 tests with 4 skips and 1,762 subtests. The client execution-spec - matrix passed 136/136, the complete `npm run check` passed, and the full - mocked Studio browser suite passed 87/87. The production bundle was - 522635/523264 gzip bytes, 501 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 backend-owned Gallery files, and the local HTTP smoke - returned 200 for the index and all seven referenced assets. Ruff E9/F, - package compatibility, preflight, formatting, lint, type, and diff checks - passed. This is static, unit, contract, mocked-browser, build, and local - HTTP evidence only; no Depth download, model execution, generated media, - or live workload qualification occurred. - - [x] `FluxCannyPipeline:control_image`: backend commit `14fef9f` moves its - execution profile, capability, Auto resource contract, reviewed compatible - repair source, shared generic control-image recipe, field bindings, and - receipt hash `studio-spec-v1-82045f56` into the specification registry. - Client commit `784e3c7` removes Canny from the Flux-family graph and - `FluxControlPipeline` class switches; Depth and Canny now reuse the exact - same managed role IDs and edge shape while retaining distinct artifacts, - profiles, and receipts. The focused backend matrix passed 95 tests with - 443 subtests; the exact mirrored backend tree passed 1,122 tests with 4 - skips and 1,762 subtests. The client execution-spec matrix passed 136/136, - the complete `npm run check` passed, and the final full mocked Studio - browser suite passed 87/87 after its legacy empty-capability fixture was - corrected to serve the new exact schema-v2 Canny contract. The production - bundle was 522626/523264 gzip bytes, 510 bytes below the stricter - 523136-byte safety target. The mirror matched all 26 generated files - byte-for-byte while preserving 317 backend-owned Gallery files, and the - local HTTP smoke returned 200 for the index and all seven referenced - assets. Ruff E9/F, package compatibility, preflight, formatting, lint, - type, and diff checks passed. This is static, unit, contract, mocked- - browser, build, and local HTTP evidence only; no Canny download, repair, - model execution, generated media, or live workload qualification occurred. - - [x] `FluxReduxPipeline:edit_image`: backend commit `6be23e7` moves its - execution profile, capability, Auto resource contract, generic image - loader, reference-image loader, edit generator, preview route, field - bindings, and receipt hash `studio-spec-v1-18e2c4ac` into the - specification registry. Client commit `b709126` removes Redux from the - Flux-family graph and pipeline-class switches and materializes the exact - edit recipe from the backend contract. The focused backend matrix passed - 67 tests with 321 subtests; the exact mirrored backend tree passed 1,122 - tests with 4 skips and 1,762 subtests. The focused client graph suite - passed 43/43, the complete `npm run check` passed, the exact Redux browser - transition passed 1/1, and the final full mocked Studio browser suite - passed 87/87. The production bundle was 522627/523264 gzip bytes, 509 - bytes below the stricter 523136-byte safety target. The mirror matched all - 26 generated files byte-for-byte while preserving 317 backend-owned - Gallery files, and the fresh local HTTP smoke returned 200 for the index - and all seven referenced assets. Ruff E9/F, package compatibility, - preflight, formatting, lint, type, and diff checks passed. This is static, - unit, contract, mocked-browser, build, and local HTTP evidence only; no - Redux download, model execution, generated media, or live workload - qualification occurred. - - [x] `WanTI2VPipeline:text_to_video`: backend commit `276dd1f` moves its - execution profile, capability, Auto resource contract, generic video - quantization, execution-recipe, pipeline, generation, and export roles, - field bindings, and receipt hash `studio-spec-v1-a83efd57` into the - specification registry. Client commit `049addb` removes the TI2V model - from the legacy video pipeline, artifact, native-flash, and scheduler - switches and materializes the five-node recipe from the backend contract. - Follow-up backend `6983ce6` and client `60f4036` restore the prior video - node coordinates and clear native-flash component selection on CPU; those - corrections are covered by the subsequent I2V full-gate evidence below. - The original focused backend matrix passed 67 tests with 325 subtests; - the exact pre-mirror backend tree passed 1,122 tests with 4 skips and - 1,766 subtests. The focused client specification test and exact mocked- - browser transition each passed 1/1, the complete `npm run check` passed, - and the final full mocked Studio browser suite passed 87/87. The - production bundle was 522709/523264 gzip bytes, 427 bytes below the - stricter 523136-byte safety target. The mirror matched all 26 generated - files byte-for-byte while preserving 317 backend-owned Gallery files, - and a fresh local HTTP smoke returned 200 for the index and all eight - requested generated asset references. Ruff E9/F, package compatibility, - preflight, formatting, lint, type, and diff checks passed. This is static, - unit, contract, mocked-browser, build, and local HTTP evidence only; no - Wan model download, model execution, generated media, or live workload - qualification occurred. - - [x] `WanImageToVideoPipeline:image_to_video`: backend commit `6983ce6` - moves its execution profile, capability, Auto resource contract, generic - video runtime/loader/generator/export roles, generic opening-image loader, - exact image edge, dual-transformer bindings, and receipt hash - `studio-spec-v1-fed2321d` into the specification registry. Client commit - `60f4036` removes the I2V class, artifact, VAE-tiling, quantization, and - native-flash branches from `graphBridge` while preserving the separate - restored Modular-video route. The focused backend matrix passed 68 tests - with 329 subtests; the exact pre-mirror backend tree passed 1,123 tests - with 4 skips and 1,770 subtests. The focused client graph contract passed - 1/1, the I2V and complete eight-spec mocked-browser transitions passed - 2/2, the complete `npm run check` passed, and the final full mocked Studio - browser suite passed 87/87. The production bundle was 522682/523264 gzip - bytes, 454 bytes below the stricter 523136-byte safety target. The mirror - matched all 26 generated files byte-for-byte while preserving 317 backend- - owned Gallery files. A fresh worker returned 200 for the index and all - eight requested assets and published both Wan hashes across eight exact - specifications before its process stopped and port 8088 became free. - Ruff E9/F, `py_compile`, package compatibility, preflight, formatting, - lint, type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local HTTP evidence only; no I2V model download, - model execution, generated media, or live workload qualification occurred. - - [x] `WanVideoPipeline:text_to_video`: backend commit `92cd1f5` - moves the exact `wan-text-to-video:direct` profile, mode-specific Auto - requirements, five-node generic video recipe, declarative form bindings, - and receipt hash `studio-spec-v1-10c9a3f2` into the specification registry. - Client commit `0e359ce` adds the exact bounded - `studioExecutionSpecModes` ownership contract, materializes the migrated - mode from the backend recipe, and removes the now-unreachable legacy - `WanPipeline` construction/native-flash switches. The marker/spec mode sets - must match exactly, so a missing, duplicate, unknown, or mismatched claimed - mode fails closed; the unclaimed `video_to_video` and `video_color_edit` - siblings retain their prior `WanVideoToVideoPipeline` graph path. The - focused backend matrix passed 69 tests with 329 subtests; the exact mirrored - backend tree passed 1,124 tests with 4 skips, the existing Diffusers - deprecation warning, and 1,770 subtests. The focused client parser/graph - matrix passed 70/70, the exact mocked-browser recipe and sibling-mode - transition passed 1/1, the complete `npm run check` passed, and the final - full mocked Studio browser suite passed 87/87. The production bundle was - 522738/523264 gzip bytes, 398 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 backend-owned Gallery files. A fresh worker returned 200 - for the index and all 23 generated assets, published nine exact specs and - the Wan marker/hash, and port 8088 was free after the worker stopped. Ruff - 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - formatting, lint, type, and diff checks passed. This is static, unit, - contract, mocked-browser, build, and local HTTP evidence only; no Wan model - download, model execution, generated media, or new live workload - qualification occurred. - - [x] `WanVideoPipeline:video_to_video`: backend commit `93b1e17` - moves the existing `wan-video-to-video:direct` profile, seven-role generic - video-input/normalization recipe, exact source-video and dimension/frame - bindings, and receipt hash `studio-spec-v1-473c930e` into the specification - registry. Client commit `651eeb3` extends the bounded generic role/source - vocabulary and materializes the exact V2V topology from that receipt. The - sibling `video_color_edit` mode remains deliberately unclaimed on its - legacy graph path. The focused backend matrix passed 131 tests with 348 - subtests; the exact mirrored backend tree passed 1,127 tests with 4 skips, - the existing Diffusers deprecation warning, and 1,770 subtests. The - focused client graph contract and exact mocked-browser transition each - passed 1/1, the complete `npm run check` passed, and the final full mocked - Studio browser suite passed 87/87 in 219 seconds. The production bundle - was 522745/523264 gzip bytes, 391 bytes below the stricter 523136-byte - safety target. The mirror matched all 26 generated files byte-for-byte - while preserving 317 backend-owned Gallery files. A fresh supervised - server returned 200 for `/`, the favicon, and all 23 generated assets, - published seventeen exact specs with Wan ownership limited to - `text_to_video` and `video_to_video`, and exposed the exact V2V hash while - omitting a color-edit receipt. Its verified five-process supervisor and - worker tree stopped and port 8088 was free. Ruff 0.12.7 E9/F, - `py_compile`, `uv pip check` (78 packages), preflight, formatting, lint, - type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local HTTP evidence only; no Wan model - download, model execution, generated media, or live workload - qualification occurred. - - [x] `WanVideoPipeline:video_color_edit`: backend commit `09b1d4b` - seals the final Wan 2.1 sibling with the same reviewed seven-role V2V - recipe and a distinct receipt hash `studio-spec-v1-0be460bc`. Client - commit `2525937` materializes that exact receipt and removes the remaining - `WanVideoPipeline` class, repository, and scheduler branches from - `graphBridge`; all three Wan 2.1 modes are now specification-owned. The - focused backend matrix passed 131 tests with 348 subtests; the exact - mirrored backend tree passed 1,127 tests with 4 skips, the existing - Diffusers deprecation warning, and 1,770 subtests. The focused client graph - contract and exact mocked-browser transition each passed 1/1, the complete - `npm run check` passed, and the complete mocked Studio browser suite passed - 87/87 in 219.2 seconds. The production bundle was 522662/523264 gzip bytes, - 474 bytes below the stricter 523136-byte safety target. The mirror matched - all 26 generated files byte-for-byte while preserving 317 backend-owned - Gallery files. A fresh supervised server returned 200 for `/`, the favicon, - and all 23 generated assets and published eighteen exact specs with all - three Wan modes; the V2V and color-edit hashes were distinct while sharing - the reviewed profile. Its verified five-process supervisor and worker tree - stopped and port 8088 was free. Ruff 0.12.7 E9/F, `py_compile`, - `uv pip check` (78 packages), preflight, formatting, lint, type, and diff - checks passed. This is static, unit, contract, mocked-browser, build, and - local HTTP evidence only; no Wan model download, model execution, - generated media, or live workload qualification occurred. - - [x] `LTXVideoPipeline:text_to_video`: backend commit `7736dd3` moves - the existing four-mode `ltx-video:direct` profile and the text-to-video - generic video recipe into the versioned specification registry with - receipt `studio-spec-v1-8f100d39`. Client commit `9f2122f` materializes - the exact `LTXConditionPipeline` loader, portable native-math attention, - and LTX-specific generation bindings without adding a model-named graph - branch. Exact ownership remains limited to `text_to_video`; the sibling - image-, video-, and reference-to-video modes retain their prior paths. - The focused backend matrix passed 132 tests with 348 subtests; the exact - mirrored backend tree passed 1,128 tests with 4 skips, the existing - Diffusers deprecation warning, and 1,770 subtests. The focused client - graph contract and exact mocked-browser transition each passed 1/1, the - complete `npm run check` passed, and the final full mocked Studio browser - suite passed 87/87 in 220.8 seconds. The production bundle was - 522678/523264 gzip bytes, 458 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 backend-owned Gallery files. A fresh supervised server - returned 200 for `/`, the favicon, and all 23 generated assets, published - nineteen exact specs with only the LTX text mode claimed, and exposed the - exact LTX receipt and class. Its verified five-process tree stopped and - port 8088 was free. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 - packages), preflight, formatting, lint, type, and diff checks passed. - This is static, unit, contract, mocked-browser, build, and local HTTP - evidence only; no LTX model download, model execution, generated media, - or live workload qualification occurred. - - [x] `LTXVideoPipeline:video_to_video`: backend commit `e83c760` - adds the reviewed load/normalize video route and binds source-trajectory - `strength` to `conditioningScale` while preserving form `strength` as the - independent `denoise_strength`. Receipt `studio-spec-v1-ad97d224` keeps - the shared `LTXConditionPipeline`, portable attention, and no-scheduler- - shift contract. Client commit `8bd95e6` proves the exact topology and both - values through generic specification materialization. Exact LTX ownership - now covers text-, image-, and video-to-video; reference-to-video remains - unclaimed. The focused backend matrix passed 132 tests with 348 subtests; - the exact mirrored backend tree passed 1,128 tests with 4 skips, the - existing Diffusers deprecation warning, and 1,770 subtests. The focused - client graph contract and exact mocked-browser transition each passed - 1/1, the complete `npm run check` passed, and the full mocked Studio - browser suite passed 87/87 in 221.1 seconds. The unchanged production - bundle was 522678/523264 gzip bytes, 458 bytes below the stricter - 523136-byte safety target. The mirror matched all 26 generated files - byte-for-byte while preserving 317 backend-owned Gallery files. A fresh - supervised server returned 200 for `/`, the favicon, and all 23 generated - assets, published twenty-one exact specs and all three expected LTX - hashes, and exposed both distinct strength bindings. Its verified five- - process tree stopped and port 8088 was free. Ruff 0.12.7 E9/F, - `py_compile`, `uv pip check` (78 packages), preflight, formatting, lint, - type, and diff checks passed. This is static, unit, contract, mocked- - browser, build, and local HTTP evidence only; no LTX model download, - model execution, generated media, or live workload qualification - occurred. - - [x] `LTXVideoPipeline:image_to_video`: backend commit `7b8d5c1` - adds the exact image sibling over the reviewed `ltx-video:direct` profile - with the generic source-image role, `image -> reference_images` edge, - reference list/alpha bindings, and receipt `studio-spec-v1-71f17ad0`. - Client commit `709ddd3` proves that the generic specification path - preserves portable native-math attention and LTX generation behavior - without inheriting Wan dual-transformer, forced-tiling, native-flash, or - scheduler bindings. Exact LTX ownership is now text- and image-to-video; - video- and reference-to-video remain deliberately unclaimed. The focused - backend matrix passed 132 tests with 348 subtests; the exact mirrored - backend tree passed 1,128 tests with 4 skips, the existing Diffusers - deprecation warning, and 1,770 subtests. The focused client graph contract - and exact mocked-browser transition each passed 1/1, the complete - `npm run check` passed, and the full mocked Studio browser suite passed - 87/87 in 222.4 seconds. The unchanged production bundle was - 522678/523264 gzip bytes, 458 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 backend-owned Gallery files. A fresh supervised server - returned 200 for `/`, the favicon, and all 23 generated assets, published - twenty exact specs with only the two migrated LTX modes claimed, and - exposed both expected hashes. Its verified five-process tree stopped and - port 8088 was free. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 - packages), preflight, formatting, lint, type, and diff checks passed. - This is static, unit, contract, mocked-browser, build, and local HTTP - evidence only; no LTX model download, model execution, generated media, - or live workload qualification occurred. - - [x] `LTXVideoPipeline:reference_to_video`: backend commit `0bdc364` - seals the fourth LTX mode with the reviewed multi-image reference route - and distinct receipt `studio-spec-v1-0c5abd50`, reusing the exact generic - image role, edge, and bindings rather than adding a mode-specific node. - Client commit `cddd140` proves multiple reference paths, alpha handling, - portable attention, and receipt selection through the shared - materializer. All four LTX modes are now specification-owned. The focused - backend matrix passed 132 tests with 348 subtests; the exact mirrored - backend tree passed 1,128 tests with 4 skips, the existing Diffusers - deprecation warning, and 1,770 subtests. The focused client graph contract - and exact mocked-browser transition each passed 1/1, the complete - `npm run check` passed, and the full mocked Studio browser suite passed - 87/87 in 221.3 seconds. The unchanged production bundle was - 522678/523264 gzip bytes, 458 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 backend-owned Gallery files. A fresh supervised server - returned 200 for `/`, the favicon, and all 23 generated assets, published - twenty-two exact specs, and exposed all four LTX modes, hashes, and the - single reviewed pipeline class. Its verified five-process tree stopped - and port 8088 was free. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` - (78 packages), preflight, formatting, lint, type, and diff checks passed. - This is static, unit, contract, mocked-browser, build, and local HTTP - evidence only; no LTX model download, model execution, generated media, - or live workload qualification occurred. - - [x] `AceStepAudioPipeline:text_to_audio`: backend commit `adcaf48` - moves the existing `ace-step-audio:direct` profile and reviewed ACE-Step - repositories into the specification registry, then seals the generic - quantization, recipe, audio loader, generator, and exporter route with - receipt `studio-spec-v1-4bc8ed64`. Client commit `5f91ed9` materializes - the exact `AceStepPipeline` text-to-music recipe, form values, 48 kHz - generation/export contract, and template base-model override through the - shared specification path without adding a model-name graph branch. - Exact ownership at this checkpoint was limited to `text_to_audio`; - variation joined it in the immediately following migration, while - continuation and repaint remained on their existing unclaimed paths. The - focused backend matrix passed 245 tests with 588 subtests; the exact - mirrored backend tree passed 1,129 tests with 4 skips, the existing - Diffusers deprecation warning, and 1,770 subtests. The focused client - graph contract and exact mocked-browser transition each passed 1/1, the - complete `npm run check` passed, and the complete mocked Studio browser - suite passed 87/87 in 221 seconds. The production bundle was - 522816/523264 gzip bytes, 320 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 installer-owned Gallery files. A fresh supervised server - returned 200 with byte-exact content for `/`, the favicon, and all 23 - generated assets, published twenty-three exact specs with only ACE-Step - text-to-audio claimed, and exposed the reviewed receipt and pipeline - class. Its verified six-process tree stopped and port 8088 was free. Ruff - 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - formatting, lint, type, and diff checks passed. This is static, unit, - contract, mocked-browser, build, and local HTTP evidence only; no ACE-Step - model download, model execution, generated audio, or live workload - qualification occurred. - - [x] `AceStepAudioPipeline:audio_variation`: backend commit `5f6ffdc` - adds the source-audio loader, exact `audio -> source_audio` edge, `cover` - task binding, and distinct receipt `studio-spec-v1-eb222623` over the - same reviewed `ace-step-audio:direct` profile. Client commit `2a776c0` - extends the bounded generic role/source vocabulary and proves source-file - binding, exact topology, task selection, and receipt materialization - without adding an ACE-Step mode branch. Exact ACE-Step ownership at this - checkpoint covered text generation and variation; continuation joined it - in the immediately following migration, while repaint remained - deliberately unclaimed. The focused backend matrix passed 245 tests with - 588 subtests; the exact mirrored backend tree passed 1,129 tests with 4 - skips, the existing Diffusers deprecation warning, and 1,770 subtests. - The focused client graph contract and exact mocked-browser transition - each passed 1/1, the complete `npm run check` passed, and the complete - mocked Studio browser suite passed 87/87 in 221.5 seconds. The production - bundle was 522832/523264 gzip bytes, 304 bytes below the stricter - 523136-byte safety target. The mirror matched all 26 generated files - byte-for-byte while preserving 317 installer-owned Gallery files. A - fresh supervised server returned 200 with byte-exact content for `/`, the - favicon, and all 23 generated assets, published twenty-four exact specs, - and exposed exactly the text and variation ACE-Step modes plus the new - receipt. Its owned process tree stopped and port 8088 was free. Ruff - 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - formatting, lint, type, and diff checks passed. This is static, unit, - contract, mocked-browser, build, and local HTTP evidence only; no ACE-Step - model download, model execution, generated audio, or live workload - qualification occurred. - - [x] `AceStepAudioPipeline:audio_continuation`: backend commit `10b9b1c` - adds the exact source-audio route, `continuation` task and tail binding, - generic loudness-match and audio-join roles, reviewed loudness/fade - constants, and receipt `studio-spec-v1-541adefc` over the same - `ace-step-audio:direct` profile. Client commit `7e69367` extends only the - bounded generic role/source vocabulary and proves the exact - `Load -> Generate -> MatchLoudness -> Join -> Export` topology, values, - readiness, and receipt without adding an ACE-Step mode branch. Exact - ACE-Step ownership at this checkpoint covered text generation, variation, - and continuation; repaint joined them in the immediately following - migration. The focused - backend matrix passed 245 tests with 588 subtests; the exact mirrored - backend tree passed 1,129 tests with 4 skips, the existing Diffusers - deprecation warning, and 1,770 subtests. The complete client graph suite - passed 43/43, the exact mocked-browser transition passed 1/1, the complete - `npm run check` passed, and the complete mocked Studio browser suite - passed 87/87 in 3.7 minutes. The production bundle was - 522943/523264 gzip bytes, 193 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 installer-owned Gallery files. A fresh supervised server - served byte-exact content for `/`, the favicon, and all 23 generated - assets, published twenty-five exact specs with exactly the three claimed - ACE-Step modes, and exposed the continuation receipt. Its verified six- - process tree stopped and port 8088 was free. Ruff 0.12.7 E9/F, - `py_compile`, `uv pip check` (78 packages), preflight, formatting, lint, - type, and diff checks passed. This is static, unit, contract, mocked- - browser, build, and local HTTP evidence only; no ACE-Step model download, - model execution, generated audio, or live workload qualification - occurred. - - [x] `AceStepAudioPipeline:audio_repaint`: backend commit `60b87a1` - seals the final ACE-Step sibling with the generic source-audio route, - exact `repaint` task and repaint-range bindings, and distinct receipt - `studio-spec-v1-8f5c37c7` over the same reviewed - `ace-step-audio:direct` profile. Client commit `62dfe17` adds only the - bounded `repaint` binding source and proves the source-file, task, range, - topology, readiness, and receipt through the shared specification - materializer. All four ACE-Step modes are now specification-owned. The - focused backend matrix passed 245 tests with 588 subtests; the exact - mirrored backend tree passed 1,129 tests with 4 skips, the existing - Diffusers deprecation warning, and 1,770 subtests. The complete client - graph suite passed 43/43, the exact mocked-browser transition passed 1/1, - the complete `npm run check` passed, and the complete mocked Studio - browser suite passed 87/87 in 3.7 minutes. The production bundle was - 522950/523264 gzip bytes, 186 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 installer-owned Gallery files. A fresh supervised server - served byte-exact content for `/`, the favicon, and all 23 generated - assets, published twenty-six exact specs with all four ACE-Step modes, - and exposed the repaint receipt. Its verified six-process tree stopped - and port 8088 was free. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` - (78 packages), preflight, formatting, lint, type, and diff checks passed. - This is static, unit, contract, mocked-browser, build, and local HTTP - evidence only; no ACE-Step model download, model execution, generated - audio, or live workload qualification occurred. - - [x] `QwenImageEditModularPipeline:inpaint`: backend commit `de2160f` - seals the unambiguous `qwen-edit:direct-inpaint` profile, reviewed - `QwenImageEditInpaintPipeline`, source-image and mask loaders, generic - inpaint/preview route, and exact form bindings with receipt - `studio-spec-v1-ac52abb3`. Client commit `f28ff89` materializes that - contract through the shared specification path and removes three inert - model-named direct-Qwen diagnostic predicates. Exact ownership remains - limited to inpaint; the distinct outpaint and Modular edit recipes remain - deliberately unclaimed. The focused backend matrix passed 246 tests with - 588 subtests; the exact mirrored backend tree passed 1,130 tests with 4 - skips, the existing Diffusers deprecation warning, and 1,770 subtests. - The complete client graph suite passed 43/43, the exact mocked-browser - transition passed 1/1, the complete `npm run check` passed, and the - complete mocked Studio browser suite passed 87/87 in 3.7 minutes. The - production bundle remained 522950/523264 gzip bytes, 186 bytes below the - stricter 523136-byte safety target. The mirror matched all 26 generated - files byte-for-byte while preserving 317 installer-owned Gallery files. - A fresh supervised server served byte-exact content for `/`, the favicon, - and all 23 generated assets, published twenty-seven exact specs, exposed - only the Qwen Image Edit inpaint marker/receipt, and kept outpaint - unclaimed. Its verified six-process tree stopped and port 8088 was free. - Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - formatting, lint, type, and diff checks passed. This is static, unit, - contract, mocked-browser, build, and local HTTP evidence only; no Qwen - model download, model execution, generated image, or live workload - qualification occurred. - - [x] `WanVACEPipeline:text_to_video`: backend commit `69a8561`, corrected - by `2616014` and mirrored by `69247d0`, - seals the reviewed `wan-vace:direct` profile, immutable - `Wan-AI/Wan2.1-VACE-1.3B-diffusers` artifact, shared generic video - recipe, exact text-mode bindings, and receipt - `studio-spec-v1-4a34e319`. Client commit `8f05541`, corrected by - `4c9d40c`, proves that the - existing specification materializer builds the exact four-edge route, - pipeline identity, artifact, mode, readiness, and receipt without a new - model-named production branch. The correction binds and persists the - catalog's reviewed `ec4d2cb062b548996b179d493fdd05340de702a1` - revision instead of relying only on the loader's execution-time catalog - resolution. At this checkpoint ownership remained limited to - text-to-video; VACE inpaint, outpaint, and control modes were - deliberately unclaimed until their distinct conditioned-input graphs - were migrated. The focused backend matrix passed 188 tests with 360 - subtests; the exact mirrored backend tree passed 1,131 tests with 4 - skips, the existing Diffusers deprecation warning, and 1,770 subtests. - The complete client graph suite passed 43/43, the exact mocked-browser - transition passed 1/1, the complete `npm run check` passed, and the final - exact-tree mocked Studio browser suite passed 87/87 in 3.7 minutes. The - production bundle was 522970/523264 gzip bytes, 166 bytes below the - stricter 523136-byte safety target. The mirror matched all 26 generated - files byte-for-byte while preserving 317 installer-owned Gallery files. - A fresh supervised server served byte-exact content for `/`, the - favicon, and all 23 generated assets, published twenty-eight exact - specs, exposed only the Wan VACE text-to-video marker/receipt, and kept - its three conditioned siblings unclaimed. Its verified six-process tree - stopped and port 8088 was free. Ruff 0.12.7 E9/F, `py_compile`, `uv pip - check` (78 packages), preflight, formatting, lint, type, and diff checks - passed. This is static, unit, contract, mocked-browser, build, and local - HTTP evidence only; no Wan VACE model download, model execution, - generated video, or live workload qualification occurred. - - [x] `WanVACEPipeline:video_inpaint`: backend and bundled-client commit - `0e9c3f2` extends the reviewed `wan-vace:direct` specification with the - exact source-video normalization and aligned-mask route, immutable VACE - revision binding, source and mask file bindings, and reviewed threshold - 127 / 96-pixel inpaint mask-growth policy. Client commit `3f79ca2` - accepts only those new generic roles and binding sources, materializes - the exact nine-role/nine-edge recipe, and verifies receipt - `studio-spec-v1-d0b56303`, both media inputs, mask policy, mode, and Run - readiness without a model-named production branch. At this checkpoint - ownership was limited to `video_inpaint`; VACE outpaint and - control-to-video remained unclaimed until their distinct conditioned- - input contracts were migrated. The - focused backend matrix passed 189 tests with 360 subtests; the complete - backend gate passed 1,132 tests with 4 skips, the existing Diffusers - deprecation warning, and 1,770 subtests. The client graph suite passed - 43/43, the exact mocked-browser transition passed 1/1, the complete - `npm run check` passed, and the frozen complete mocked Studio suite - passed 87/87 in 3.6 minutes. The production bundle was - 523045/523264 gzip bytes, 91 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 installer-owned Gallery files. A fresh backend served - byte-exact content for all 25 public generated files, published 29 exact - specs, and exposed exactly the VACE text-to-video and video-inpaint - markers with the inpaint revision source and mask route intact. Its - verified three-process tree stopped and port 8088 was free. Ruff 0.12.7 - E9/F, `py_compile`, `uv pip check` (78 packages), preflight, formatting, - lint, type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local HTTP evidence only; no Wan VACE model - download, model execution, generated video, or live workload - qualification occurred. - - [x] `WanVACEPipeline:video_outpaint`: backend and bundled-client commit - `b004af1` adds the distinct outpaint receipt over the reviewed VACE - source-normalization and aligned-boundary-mask route. It retains the - immutable VACE artifact revision and threshold 127 while binding the - legacy outpaint policy to zero mask growth. Client commit `fce224e` - materializes that same nine-role/nine-edge graph, seals receipt - `studio-spec-v1-1fd16911`, and verifies both media inputs, the distinct - zero-growth policy, mode, and Run readiness without a model-named - production branch. Ownership is limited to `video_outpaint`; VACE - control-to-video remains unclaimed until its distinct control-input - contract is migrated. The focused backend matrix passed 190 tests with - 360 subtests; the complete backend gate passed 1,133 tests with 4 skips, - the existing Diffusers deprecation warning, and 1,770 subtests. The - client graph suite passed 43/43, the exact mocked-browser transition - passed 1/1, the complete `npm run check` passed, and the frozen complete - mocked Studio suite passed 87/87 in 3.6 minutes. The production bundle - was 523055/523264 gzip bytes, 81 bytes below the stricter 523136-byte - safety target. The mirror matched all 26 generated files byte-for-byte - while preserving 317 installer-owned Gallery files. A fresh backend - served byte-exact content for all 25 public generated files, published - 30 exact specs, and exposed exactly the VACE text-to-video, video- - inpaint, and video-outpaint markers with the distinct outpaint growth - source intact. Its verified five-process tree stopped and port 8088 was - free. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), - preflight, formatting, lint, type, and diff checks passed. This is - static, unit, contract, mocked-browser, build, and local HTTP evidence - only; no Wan VACE model download, model execution, generated video, or - live workload qualification occurred. - - [x] `WanVACEPipeline:control_to_video`: backend and bundled-client commit - `ed07f34` completes the exact specification coverage for all four - advertised VACE modes. It binds the reviewed direct loader and immutable - VACE revision to a separate control-video loader, width/height/frame-count - normalization, generator `video` input, and the existing generic export - route; it does not admit the source-video or mask branches. Client commit - `72ed446` accepts only the added generic control-video role and form source, - materializes the exact seven-role/six-edge graph, seals receipt - `studio-spec-v1-d05d263d`, and verifies the control file, normalized frame - count, mode, absence of source/mask roles, and Run readiness without a - model-named production branch. The focused backend matrix passed 191 tests - with 360 subtests; the complete backend gate passed 1,134 tests with 4 - skips, the existing Diffusers deprecation warning, and 1,770 subtests in - 45.08 seconds. The client graph suite passed 43/43, the exact mocked-browser - transition passed 1/1, the complete `npm run check` passed, and the frozen - complete mocked Studio suite passed 87/87 in 220.1 seconds. The production - bundle was 523065/523264 gzip bytes, 71 bytes below the stricter - 523136-byte safety target. The mirror matched all 26 generated files - byte-for-byte while preserving 317 installer-owned Gallery files. A fresh - supervised server served byte-exact content for all 25 public generated - files, published 31 exact specs, and exposed exactly all four advertised - VACE markers with the control receipt, topology, and bindings intact. Its - verified five-process tree stopped and port 8088 was free. Ruff 0.12.7 - E9/F, `py_compile`, `uv pip check` (78 packages), preflight, formatting, - lint, type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local HTTP evidence only; no Wan VACE model - download, model execution, generated video, or live workload qualification - occurred. - - [x] `QwenImageEditModularPipeline:outpaint`: backend and bundled-client - commit `823357d` extends the reviewed `qwen-edit:direct-inpaint` profile - with the distinct generated-canvas route, removes the separate mask-file - loader from this mode, and seals every boundary-placement binding with - receipt `studio-spec-v1-4ffd900b`. Client commit `de2eba1` accepts only - the existing generic outpaint-canvas role and seven form sources, - materializes the exact seven-role/seven-edge source-to-canvas-to-inpaint - graph, and verifies the source file, canvas dimensions and offsets, mask - route, absence of `loadMask`, receipt, and Run readiness without a new - model-named production branch. Qwen Image Edit inpaint and outpaint are - now specification-owned; its distinct Modular edit recipe remains - deliberately unclaimed. The focused backend matrix passed 192 tests with - 360 subtests; the complete backend gate passed 1,135 tests with 4 skips, - the existing Diffusers deprecation warning, and 1,770 subtests in 41.89 - seconds. The client graph suite passed 43/43, the exact mocked-browser - transition passed 1/1, the complete `npm run check` passed, and the - frozen complete mocked Studio suite passed 87/87 in 219.6 seconds. The - production bundle was 523109/523264 gzip bytes, 27 bytes below the - stricter 523136-byte safety target. The mirror matched all 26 generated - files byte-for-byte while preserving 317 installer-owned Gallery files. - A fresh backend served byte-exact content for all 25 public generated - files, published 32 exact specs, and exposed both Qwen Image Edit modes - with the outpaint receipt, topology, and bindings intact. Its verified - five-process tree stopped and port 8088 was free. Ruff 0.12.7 E9/F, - `py_compile`, `uv pip check` (78 packages), preflight, formatting, lint, - type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local HTTP evidence only; no Qwen model - download, model execution, generated image, or live workload - qualification occurred. - - [x] `ZImageModularPipeline:text_to_image`: backend commit `a4efd6c` - moves the existing direct `z-image:auto` loader profile and generic - five-node image recipe into exact specification ownership with receipt - `studio-spec-v1-0d3c1205`. Client commit `77ceab9` proves the existing - generic materializer consumes that contract without a production source - change. The complete evidence and remaining six-pair boundary are recorded - in the P0.4 receipt item below. - - [x] `QwenImageModularPipeline:text_to_image`: backend commit `6e40bab` - moves the existing direct `qwen-image:t2i-direct` profile and generic - five-node image recipe into exact specification ownership with receipt - `studio-spec-v1-f53ab380`. Client commit `531d4b9` proves the current - official and reviewed prequantized artifact candidates bind and consume - the same generic graph contract without a production source change. The - complete evidence and remaining five-pair boundary are recorded in the - P0.4 receipt item below. - - [x] `FluxKontextPipeline:edit_image`: backend commit `5f4d437` - moves the existing `flux-kontext:direct` profile, exact edit-only Auto - requirements, six-node generic edit recipe, declarative form bindings, - and receipt hash `studio-spec-v1-393009a9` into the specification - registry. Client commit `8ae0dd9` proves the generic specification path - without adding a new model-named production branch. Exact mode ownership - was limited to `edit_image`; `multi_image_reference_edit` remained on its - existing generic legacy graph until the immediately following paired - migration. The focused backend - matrix passed 98 tests with 451 subtests; the exact mirrored backend tree - passed 1,125 tests with 4 skips, the existing Diffusers deprecation - warning, and 1,770 subtests. The focused client graph contract and exact - mocked-browser recipe/sibling transition each passed 1/1, the complete - `npm run check` passed, and the final full mocked Studio browser suite - passed 87/87. The unchanged production bundle was 522738/523264 gzip - bytes, 398 bytes below the stricter 523136-byte safety target. The mirror - matched all 26 generated files byte-for-byte while preserving 317 - backend-owned Gallery files. A fresh worker returned 200 for `/`, the - favicon, and all 23 generated assets, published ten exact specs plus the - Kontext `edit_image` marker/hash, and port 8088 was free after its verified - worker stopped. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 - packages), preflight, formatting, lint, type, and diff checks passed. This - is static, unit, contract, mocked-browser, build, and local HTTP evidence - only; no Kontext download, model execution, generated media, or live - workload qualification occurred. - - [x] `FluxKontextPipeline:multi_image_reference_edit`: backend commit - `119c720` adds the second exact receipt - `studio-spec-v1-aa060039`, reuses the same reviewed - `flux-kontext:direct` loader profile without making loader-only optional- - runtime resolution ambiguous, and keeps Auto requirements explicitly - edit-only. Client commit `d956a42` removes Kontext from the remaining - Flux-family and pipeline-class switches; both modes now materialize the - same generic six-node edit topology entirely from their distinct backend - specifications. The focused backend matrix passed 98 tests with 451 - subtests; the exact mirrored backend tree passed 1,125 tests with 4 skips, - the existing Diffusers deprecation warning, and 1,770 subtests. The - focused two-mode graph contract and exact mocked-browser transition each - passed 1/1, the complete `npm run check` passed, and the final full mocked - Studio browser suite passed 87/87. The production bundle was - 522724/523264 gzip bytes, 412 bytes below the stricter 523136-byte safety - target. The mirror matched all 26 generated files byte-for-byte while - preserving 317 backend-owned Gallery files. A fresh worker returned 200 - for `/`, the favicon, and all 23 generated assets, published eleven exact - specs plus both Kontext mode markers/hashes, and port 8088 was free after - its verified worker stopped. Ruff 0.12.7 E9/F, `py_compile`, `uv pip - check` (78 packages), preflight, formatting, lint, type, and diff checks - passed. This is static, unit, contract, mocked-browser, build, and local - HTTP evidence only; no Kontext download, model execution, generated - media, or live workload qualification occurred. - - [x] `FluxFillPipeline:inpaint`: backend commit `544c54f` moves the - shared `flux-fill:direct` execution profile and existing inpaint/outpaint - Auto resource policy into the versioned specification registry, while - claiming only the inpaint mode with exact source-image, mask, pipeline, - and preview edges plus declarative form bindings. Client commit `38f8d81` - extends the bounded generic parser/materializer for those roles and proves - receipt `studio-spec-v1-ba8c8dd1`; the sibling `outpaint` mode remains - explicitly unclaimed on its prior generic graph path. The focused backend - matrix passed 71 tests with 329 subtests; the exact mirrored backend tree - passed 1,126 tests with 4 skips, the existing Diffusers deprecation - warning, and 1,770 subtests. The focused client graph contract and exact - mocked-browser inpaint/outpaint transition each passed 1/1, the complete - `npm run check` passed, and the final full mocked Studio browser suite - passed 87/87. The production bundle was 522756/523264 gzip bytes, 380 - bytes below the stricter 523136-byte safety target. The mirror matched all - 26 generated files byte-for-byte while preserving 317 backend-owned - Gallery files. A fresh worker returned 200 for `/`, the favicon, and all - 23 generated assets, published twelve exact specs plus the Fill inpaint - marker/hash, and port 8088 was free after its verified worker stopped. - Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - formatting, lint, type, and diff checks passed. This is static, unit, - contract, mocked-browser, build, and local HTTP evidence only; no Fill - model download, model execution, generated media, or live workload - qualification occurred. - - [x] `FluxFillPipeline:outpaint`: backend commit `634c485` adds the - distinct outpaint receipt `studio-spec-v1-5c0d7413` over the same exact - reviewed `flux-fill:direct` profile, source-image/mask topology, and - declarative bindings. Client commit `4c0bd05` proves graph equivalence - across both Fill modes and removes Fill from the legacy Flux-family and - pipeline-class switches; neither production graph construction nor - pipeline selection now branches on `FluxFillPipeline`. The focused - backend matrix passed 71 tests with 329 subtests; the exact mirrored - backend tree passed 1,126 tests with 4 skips, the existing Diffusers - deprecation warning, and 1,770 subtests. The focused client graph contract - and exact mocked-browser two-mode transition each passed 1/1, the complete - `npm run check` passed, and the final full mocked Studio browser suite - passed 87/87. The production bundle was 522741/523264 gzip bytes, 395 - bytes below the stricter 523136-byte safety target. The mirror matched all - 26 generated files byte-for-byte while preserving 317 backend-owned - Gallery files. A fresh supervised server returned 200 for `/`, the - favicon, and all 23 generated assets, published thirteen exact specs plus - both Fill markers/hashes, and port 8088 was free after its verified - supervisor and worker stopped. Ruff 0.12.7 E9/F, `py_compile`, `uv pip - check` (78 packages), preflight, formatting, lint, type, and diff checks - passed. This is static, unit, contract, mocked-browser, build, and local - HTTP evidence only; no Fill model download, model execution, generated - media, or live workload qualification occurred. - - [x] `Flux2KleinPipeline:text_to_image`: backend commit `441cd00` - moves the shared three-mode `flux2-klein:direct` profile, capability, and - Auto policy into the versioned specification registry while claiming - only the text-to-image graph through receipt - `studio-spec-v1-e11dfdc6`. Client commit `931621d` proves the exact - generic Diffusers image recipe and stable role topology, while explicitly - keeping `edit_image` and `multi_image_reference_edit` on their prior - unclaimed legacy path. The focused backend matrix passed 129 tests with - 329 subtests; the exact mirrored backend tree passed 1,127 tests with 4 - skips, the existing Diffusers deprecation warning, and 1,770 subtests. - The focused client graph contract and exact mocked-browser transition - each passed 1/1, the complete `npm run check` passed, and the final full - mocked Studio browser suite passed 87/87 in 218.2 seconds. The production - bundle remained 522741/523264 gzip bytes, 395 bytes below the stricter - 523136-byte safety target. The mirror matched all 26 generated files - byte-for-byte while preserving 317 backend-owned Gallery files. A fresh - supervised server returned 200 for `/`, the favicon, and all 23 generated - assets, and published fourteen exact specs plus the Klein marker/hash. - Its verified supervisor and worker tree stopped and port 8088 was free. - Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - formatting, lint, type, and diff checks passed. This is static, unit, - contract, mocked-browser, build, and local HTTP evidence only; no Klein - model download, model execution, generated media, or live workload - qualification occurred. - - [x] `Flux2KleinPipeline:edit_image`: backend commit `ab3bd34` - adds the distinct edit receipt `studio-spec-v1-ab4da919` over the same - reviewed `flux2-klein:direct` profile with an exact source-image, - Diffusers Edit, and preview route. Client commit `84e1d8f` proves the - exact edit graph and transition while keeping only - `multi_image_reference_edit` unclaimed. The focused backend matrix passed - 131 tests with 348 subtests; the exact mirrored backend tree passed 1,127 - tests with 4 skips, the existing Diffusers deprecation warning, and 1,770 - subtests. The focused client graph contract and exact mocked-browser - transition each passed 1/1, the complete `npm run check` passed, and the - final full mocked Studio browser suite passed 87/87 in 220.1 seconds. The - production bundle remained 522741/523264 gzip bytes, 395 bytes below the - stricter 523136-byte safety target. The mirror matched all 26 generated - files byte-for-byte while preserving 317 backend-owned Gallery files. A - fresh supervised server returned 200 for `/`, the favicon, and all 23 - generated assets, and published fifteen exact specs plus both Klein - markers/hashes. Its verified supervisor and worker tree stopped and port - 8088 was free. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 - packages), preflight, formatting, lint, type, and diff checks passed. This - is static, unit, contract, mocked-browser, build, and local HTTP evidence - only; no Klein model download, model execution, generated media, or live - workload qualification occurred. - - [x] `Flux2KleinPipeline:multi_image_reference_edit`: backend commit - `4527764` adds the final distinct Klein receipt - `studio-spec-v1-756c2d69` over the reviewed shared - `flux2-klein:direct` profile and the exact source-image, Diffusers Edit, - and preview route. Client commit `7180694` proves that exact receipt and - graph transition, and removes `Flux2KleinPipeline` from the legacy Flux - family and pipeline-class switches; all three Klein modes are now - specification-owned. The focused backend matrix passed 131 tests with - 348 subtests; the exact mirrored backend tree passed 1,127 tests with 4 - skips, the existing Diffusers deprecation warning, and 1,770 subtests. - The focused client graph contract and exact mocked-browser transition - each passed 1/1, the complete `npm run check` passed, and the final full - mocked Studio browser suite passed 87/87 in 219.1 seconds. The production - bundle was 522725/523264 gzip bytes, 411 bytes below the stricter - 523136-byte safety target. The mirror matched all 26 generated files - byte-for-byte while preserving 317 backend-owned Gallery files. A fresh - supervised server returned 200 for `/`, the favicon, and all 23 generated - assets, and published sixteen exact specs with all three Klein modes and - the exact multi-reference hash. Its verified five-process supervisor and - worker tree stopped and port 8088 was free. Ruff 0.12.7 E9/F, - `py_compile`, `uv pip check` (78 packages), preflight, formatting, lint, - type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local HTTP evidence only; no Klein model - download, model execution, generated media, or live workload - qualification occurred. - - [x] Exact-pair specification migration: all 39 currently declared - execution-profile pairs now have a backend-owned Studio specification and - a tested generic client receipt. This closes the legacy exact-pair gap but - does not qualify live model execution or the remaining declarative - overlays. - - [x] Loader-component requirement overlay. Backend commit `732e15c` adds a - bounded `loader_component_outputs` contract to reviewed Modular pipeline - metadata. Wan I2V now declares `image_encoder`; `ModelsLoader` consumes - the generic list for both strict required-component loading and published - component receipts, with no Wan class-name branch. Custom sidecars may - carry only a bounded declarative list and remain `contract_only`; the - loader accepts only node-supported outputs from the built-in registry. - The focused Modular/loading matrix passed 253 tests and 440 subtests. The - complete backend gate passed 1,155 tests with 4 skips and 1,795 subtests, - with only the existing Diffusers `torch_dtype` deprecation warning. Ruff - 0.12.7 E9/F, `compileall`, `uv pip check` (78 packages), preflight, and - diff checks passed. This is static, unit, contract, and local preflight - evidence only; no model download, component load, inference, or generated - media occurred. - - [x] Layer-block allowlist overlay. Backend commit `02afc25` moves the six - existing SDXL, Qwen Image/Edit/Edit Plus, Flux, and Flux Kontext - model-to-transformer-stack selections onto bounded reviewed pipeline - metadata. The generic `Layers` node publishes the generated map without - pipeline-class keys and now requires the connected model signal at both - dynamic-field creation and execution. Missing/unknown identities, - duplicate or unlisted paths, and extra injected block configurations fail - before an upstream guidance object can consume them. The focused schema, - registry, and route matrix passed 184 tests and 360 subtests. The complete - backend gate passed 1,156 tests with 4 skips and 1,802 subtests, with only - the existing Diffusers `torch_dtype` deprecation warning. Ruff 0.12.7 - E9/F, `compileall`, `uv pip check` (78 packages), preflight, and diff - checks passed. This is static, unit, contract, and local preflight evidence - only; no model download, inference, guidance execution, or generated media - occurred. - - [x] Denoise image-latent dimension overlay. Backend commit `7228c1f` - replaces the four-class compatibility branch with bounded reviewed - `denoise_image_latent_dimensions` metadata. Qwen Image Edit/Edit Plus, - Flux Kontext, and Flux2 Klein retain legacy hidden `height` and `width` - values when image latents are present; every other registered pipeline - drops them. Unknown, malformed, duplicate, and unlisted metadata fails - closed, while custom sidecars remain `contract_only` and cannot authorize - this built-in execution exception. The focused schema/security matrix - passed 99 tests and 240 subtests; the wider no-weight Modular route matrix - passed 221 tests and 491 subtests. The complete backend gate passed 1,158 - tests with 4 skips and 1,821 subtests, with only the existing Diffusers - `torch_dtype` deprecation warning. Ruff 0.12.7 E9/F, `compileall`, `uv pip - check` (78 packages), preflight, and diff checks passed. This is static, - unit, contract, and local preflight evidence only; no model download, - inference, or generated media occurred. - - [x] Guider compatibility overlay. Backend commit `51206e6` declares the - exact bounded Diffusers guider choices on reviewed Modular pipeline - metadata. SDXL and Qwen Image/Edit/Edit Plus expose the complete reviewed - set because they also declare layer-stack contracts; Qwen Layered, - Z-Image, and Wan expose only non-layer guiders; Flux variants expose no - guider component. The generic Guider selector consumes the generated map - and revalidates the connected pipeline identity at field refresh and - execution, so missing, unknown, malformed, or incompatible selections - fail before constructing an upstream guider. Client commit `d1b2f88` - preserves scalar values for dynamic single-select options while retaining - array values for multi-select Layers. The focused backend schema/security - matrix passed 101 tests and 258 subtests; the wider no-weight Modular route - matrix passed 223 tests and 509 subtests. The complete backend gate passed - 1,160 tests with 4 skips and 1,839 subtests, with only the existing - Diffusers `torch_dtype` deprecation warning. The complete client - `npm run check` passed, the exact model-signal/Guider/Layers mocked-browser - contract passed 1/1, and the production bundle was 523123/523264 gzip - bytes, 13 bytes below the stricter 523136-byte safety target. Ruff 0.12.7 - E9/F, `compileall`, `uv pip check` (78 packages), preflight, formatting, - lint, type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local preflight evidence only; no model - download, guider execution, inference, or generated media occurred. - - [x] Scheduler compatibility overlay. Backend commit `6779a19` publishes - bounded scheduler choices from the pinned official Diffusers component - contracts. SDXL and Wan expose the 14 replacements shared by their Euler - or UniPC scheduler compatibility sets; Qwen, Flux, and Z-Image - flow-matching pipelines expose no unsupported legacy replacement. The - generic Scheduler node requires the connected reviewed pipeline identity - during field refresh and execution, verifies the live component class, - and requires the replacement constructor to return the exact selected - official scheduler class. Unknown, malformed, duplicate, oversized, LCM, - TCD, and incompatible selections fail closed. Client commit `140cab2` - extends the generic signal-relay browser contract across Guider, Layers, - and Scheduler without a model-named client branch. The focused backend - schema/security matrix passed 103 tests and 278 subtests; the wider - no-weight Modular route matrix passed 225 tests and 529 subtests. The - complete backend gate passed 1,162 tests with 4 skips and 1,859 subtests, - with only the existing Diffusers `torch_dtype` deprecation warning. The - complete client `npm run check` passed, the exact mocked-browser contract - passed 1/1 twice, and the production bundle was 523123/523264 gzip bytes, - 13 bytes below the stricter 523136-byte safety target. Ruff 0.12.7 E9/F, - `compileall`, `uv pip check` (78 packages), preflight, formatting, lint, - type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local preflight evidence only; no model - download, scheduler execution, inference, or generated media occurred. - - [x] Execution-spec readiness overlay. Client commit `7a02806` centralizes - exact specification lookup and derives managed loader capability checks - from the live binding or the backend-advertised loader module/action. It - replaces the Qwen-specific missing-node branch with a generic check over - every role in the exact specification. A deliberately opaque future - loader contract proves offload readiness without a model-name route, and - a schema-v2 Qwen outpaint browser fixture proves a missing exact role is - reported from the authoritative contract. The focused client contract - passed 67/67, the exact specification browser matrix passed 1/1, the - complete client gate passed, and the final mocked Studio suite passed - 88/88. The production bundle passed at 523080/523264 gzip bytes, including - 56 bytes of headroom against the stricter 523136-byte safety target. This - is static, unit, contract, mocked-browser, and build evidence only; no - model download, inference, or generated media occurred. - - [x] Diffusers audio field-contract overlay. Backend commit `2a98856` - makes every reviewed audio pipeline/mode contract publish its canonical - generic `Generate` field overlay, including visibility, required inputs, - task choices, and duration bounds. The field action reconstructs the - exact contract and rejects a stored or client-edited overlay before any - mutation. Client commit `fba496c` removes the duplicate pipeline-class - visibility switches from managed graph synchronization and soundtrack - construction; generic signal/action handling now applies the backend- - authored fields when the selected pipeline or mode changes. The focused - audio suite passed 59 tests with 257 subtests, the adjacent contract matrix - passed 115 tests with 377 subtests, and the complete backend gate passed - 1,162 tests with 4 skips and 1,888 subtests with only the existing - Diffusers deprecation warning. The focused client graph/specification - matrix passed 110/110, `npm run check` passed, the field-update, - exact-audio-recipe, and soundtrack-proof browser paths passed 3/3, and the - complete mocked Studio suite passed 89/89 in 4.5 minutes. The production - bundle was 522793/523264 gzip bytes, 343 bytes below the stricter - 523136-byte safety target. Ruff E9/F, `py_compile`, `uv pip check` (78 - packages), preflight, formatting, lint, type, and diff checks passed. - This is static, unit, contract, mocked-browser, build, and local preflight - evidence only; no model download, audio execution, generated media, or - live workload qualification occurred. - - [x] Auto execution-path authority overlay. Client commit `16b7f12` - removes the pre-plan Qwen and broad family execution-path guesses from - the local resource fallback. Auto now stays path-neutral until an exact - selected schema-v2 backend candidate supplies the reviewed loader path; - Expert likewise describes the editable full graph without claiming a - model-specific execution path. All current model/mode fallbacks are - covered by a zero-invented-path contract, while the mocked exact Auto run - proves `direct-diffusers-image` still reaches the submitted receipt from - the bound backend candidate. The focused resource/request matrix passed - 79/79, `npm run check` passed, the exact Auto submission browser path - passed 1/1, and the final complete mocked Studio suite passed 89/89 in - 4.5 minutes. The production bundle was 522524/523264 gzip bytes, 612 - bytes below the stricter 523136-byte safety target. Formatting, lint, - type, build, bundle, and diff checks passed. This is static, unit, - contract, mocked-browser, and build evidence only; no model download, - inference, or generated media occurred. - - [x] Auto retry-mode authority overlay. Backend commit `a1173db` ignores a - duplicate client `resourceRetryModes` list in Auto and derives fallback - offload modes from the selected exact execution profile in canonical - memory-pressure order; Expert retains its bounded explicit hint. Client - commit `52e98d2` stops submitting `supportedOffloadModes` and - `resourceRetryModes` in Auto while preserving the selected candidate and - candidate-bound retry receipts. The focused backend resource/profile - matrix passed 182 tests with 349 subtests, and the complete backend gate - passed 1,163 tests with 4 skips and 1,888 subtests with only the existing - Diffusers deprecation warning. The focused client request/resource matrix - passed 79/79, `npm run check` passed, the exact Auto submission browser - path passed 1/1, and the complete mocked Studio suite passed 89/89 in 273 - seconds. The production bundle was 522530/523264 gzip bytes, 606 bytes - below the stricter 523136-byte safety target. Ruff 0.12.7 E9/F, - `py_compile`, `uv pip check` (78 packages), preflight, formatting, lint, - type, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local preflight evidence only; no model - download, inference, generated media, or live workload qualification - occurred. - - [x] Runtime diagnostic-classifier cleanup. Backend commit `85e80f3` - removes client `modelFamily` and `lowVramMode` from the admitted runtime - hint contract and its CUDA diagnostic projection; exact model type, - execution profile, selected recipe, and graph receipt remain the reviewed - execution identities. Client commit `e464cb7` deletes the Qwen-named - low-memory classifier and stops submitting both duplicate labels. The - focused backend runtime/resource matrix passed 177 tests with 307 - subtests, and the complete backend gate passed 1,164 tests with 4 skips - and 1,888 subtests with only the existing Diffusers deprecation warning. - The focused client template/provenance/run matrix passed 123/123, - `npm run check` passed, the exact Auto submission browser path passed 1/1, - and the complete mocked Studio suite passed 89/89 in 280 seconds. The - production bundle was 522389/523264 gzip bytes, 747 bytes below the - stricter 523136-byte safety target. Ruff 0.12.7 E9/F, `py_compile`, `uv - pip check` (78 packages), preflight, formatting, lint, type, and diff - checks passed. This is static, unit, contract, mocked-browser, build, and - local preflight evidence only; no model download, inference, generated - media, or live workload qualification occurred. - - [x] Diffusers video field-contract overlay. Backend commit `b32241b` - declares a complete reviewed field contract for every registered generic - video adapter/mode pair. The existing exact pipeline signal now drives - mode choices, input visibility and requiredness, adapter-specific - controls, and the shared strength control's declarative Studio binding; - the action reconstructs the canonical signal and rejects a stale or - edited contract before any field mutation. Client commit `947f7d9` - expands the bounded identity-binding grammar only to the reviewed - `strength` and `conditioningScale` form fields and removes the remaining - LTX model-name branch from managed control synchronization. The focused - video suite passed 79 tests with 161 subtests, the adjacent backend matrix - passed 115 tests with 208 subtests, and the complete backend gate passed - 1,165 tests with 4 skips and 1,902 subtests with only the existing - Diffusers deprecation warning. The focused client graph/action matrix - passed 103/103, `npm run check` passed, the selected-pipeline field-update - and tamper browser contract passed 1/1, and the complete mocked Studio - suite passed 90/90 in 283 seconds. The production bundle was - 522359/523264 gzip bytes, 777 bytes below the stricter 523136-byte safety - target. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), - preflight, formatting, lint, type, build, bundle, and diff checks passed. - This is static, unit, contract, mocked-browser, build, and local preflight - evidence only; no model download, video execution, generated media, or - live workload qualification occurred. - - [x] Expert CUDA resource-policy overlay. Backend commit `b1f514f` - adds a bounded schema-v1 policy to each exact Qwen execution profile for - blocked CUDA dtypes, the recommended replacement dtype, projected - offloaded/resident VRAM, and per-quantization resident overrides. Other - profiles omit the policy rather than publishing a nullable or inferred - contract. Client commit `0259624` strictly parses the bounded policy and - consumes it only from the unique execution profile named by the selected - exact specification; the Qwen-family and 10/24/80 GiB readiness branches - are removed. A deliberately different 12 GiB unit receipt and a - float16-blocking mocked-browser receipt prove that the backend profile, - not a retained client constant, controls the result. The focused backend - matrix passed 131 tests with 367 subtests, and the complete backend gate - passed 1,167 tests with 4 skips and 1,932 subtests with only the existing - Diffusers deprecation warning. The focused client graph/request/readiness - matrix passed 137/137, `npm run check` passed, the exact policy browser - contract passed 1/1, and the complete mocked Studio suite passed 91/91. - The production bundle was 522840/523264 gzip bytes, 296 bytes below the - stricter 523136-byte safety target. Ruff 0.12.7 E9/F, `py_compile`, `uv - pip check` (78 packages), preflight, formatting, lint, type, build, - bundle, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local preflight evidence only; no model - download, inference, generated media, or live workload qualification - occurred. - - [x] Expert quantization resource-policy overlay. Backend commit - `d3125dd` adds one bounded schema-v1 policy to the six exact Qwen - execution profiles, declaring the Expert quantization/offload modes, - generic Modular quantization node, reviewed component/subfolder, BnB - quant type, compute dtype, and double-quant setting. Client commit - `5e8a1e7` strictly parses the policy and resolves it only through the - unique execution profile named by the selected exact specification. - Direct specifications derive required loader fields from their exact - bindings; Modular specifications create, populate, connect, and seal the - declared generic quantization node without a model-family or pipeline-name - fallback. Missing fields, node definitions, or incompatible registry - options fail closed. The focused backend profile suite passed 9 tests - with 81 subtests, the adjacent backend matrix passed 98 tests with 114 - subtests, and the complete backend gate passed 1,168 tests with 4 skips - and 1,941 subtests with only the existing Diffusers deprecation warning. - The focused client graph/request/readiness matrix passed 137/137, - `npm run check` passed, the exact policy/topology and prior-regression - browser matrix passed 4/4 plus the preserved expanded-node contract 1/1, - and the complete mocked Studio suite passed 91/91 in 288.5 seconds. The - production bundle was 523099/523264 gzip bytes, 37 bytes below the - stricter 523136-byte safety target. Ruff 0.12.7 E9/F, `py_compile`, `uv - pip check` (78 packages), preflight, formatting, lint, type, build, - bundle, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local preflight evidence only; no model - download, inference, generated media, or live workload qualification - occurred. - - [x] Expert MPS resource-policy overlay. Backend commit `f2ec7ac` adds a - bounded schema-v1 advisory to each exact reviewed Qwen, video, and - Z-Image execution profile that has an Apple MPS qualification status and - fallback action. Other profiles omit the policy. Client commit `06ca70f` - strictly parses the policy and resolves it only through the execution - profile selected by the exact specification; the previous Qwen-family, - Z-Image-family, and video-output readiness branches are removed. The - advisory remains Expert-only and non-blocking. The focused backend - profile/runtime matrix passed 100 tests with 140 subtests, and the - complete backend gate passed 1,170 tests with 4 skips and 1,967 subtests - with only the existing Diffusers deprecation warning. The focused client - graph/request/readiness matrix passed 137/137, `npm run check` passed, - the exact MPS browser contract passed 1/1, and the complete mocked Studio - suite passed 92/92 in 310.7 seconds. The production bundle was - 523116/523264 gzip bytes, 20 bytes below the stricter 523136-byte safety - target. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), - preflight, formatting, lint, type, build, bundle, and diff checks passed. - This is static, unit, contract, mocked-browser, build, and local preflight - evidence only; no model download, inference, generated media, Apple - Silicon workload, or live qualification occurred. - - [x] Generic image and Modular field-contract overlay. Backend commit - `98f3841` gives every reviewed generic Diffusers image pipeline/mode an - exact field-visibility overlay and makes the connected Generate, Edit, - Inpaint/Outpaint, and Control Generate node validate and apply the whole - selected loader contract. The same audit proves Encode Prompt, Denoise, - Image Encode, Decode Latents, and Image Embeddings rebuild their generic - fields from the selected Modular pipeline's registry metadata without a - model-name switch. Client commit `c88e685` adds a mocked-browser contract - that switches one live generic Edit node across Flux Redux - multi-reference, SDXL img2img, and Qwen Image Edit Plus multi-reference - selections and observes the fields changing in place. The focused image - matrix passed 76 tests with 2 skips and 201 subtests, the adjacent - image/Modular/profile matrix passed 134 tests with 2 skips and 504 - subtests, and the complete backend gate passed 1,174 tests with 4 skips - and 1,996 subtests with only the existing Diffusers deprecation warning. - `npm run check` passed, the exact image switching browser contract passed - 1/1, and the complete mocked Studio suite passed 93/93 in 308.7 seconds. - The production bundle remained 523116/523264 gzip bytes, 20 bytes below - the stricter 523136-byte safety target. Ruff 0.12.7 formatting and E9/F, - `py_compile`, `uv pip check` (78 packages), preflight, client lint/type/ - build/bundle, and diff checks passed. This is static, unit, contract, - mocked-browser, build, and local preflight evidence only; no model - download, inference, generated media, or live qualification occurred. - - [x] Exact image-path and Expert quantization-choice cleanup. Client - commit `a63d882` removes the remaining image-facade registry, Flux-family, - Qwen/Z-Image mode, and pipeline-class fallbacks from managed graph - construction; only the exact selected execution profile or an already - bound managed role may select the direct image facade and loader class. - Backend commit `fd514f7` publishes a bounded, unique list of reviewed - Expert quantization choices on each exact Qwen and Flux execution profile, - while profiles without reviewed choices omit the field. Client commit - `5abfab9` strictly parses that list and renders the Expert selector only - from the unique profile selected by the exact specification. The same - client checkpoint preserves the bound execution-spec receipt during tab - hydration, so restored controlled graphs validate the same specification - instead of losing their authority. The focused backend matrix passed 15 - tests with 151 subtests, and the complete backend gate passed 1,176 tests - with 4 skips and 2,021 subtests with only the existing Diffusers - deprecation warning. The focused client contract passed 27/27, the - complete `npm run check` passed, and the final mocked Studio suite passed - 94/94 in 290.8 seconds. The production bundle was 522877/523264 gzip - bytes, 259 bytes below the stricter 523136-byte safety target. Ruff E9/F, - `uv pip check` (78 packages), preflight, formatting, lint, type, build, - bundle, and diff checks passed. This is static, unit, contract, mocked- - browser, build, and local preflight evidence only; no model download, - inference, generated media, or live qualification occurred. - - [x] Exact installed-model loader insertion. Client commit `9cec2db` - removes the remaining Qwen/family loader-choice branches from the model - library insertion path. A known catalog model now derives its loader - module, action, and `model_type` or `pipeline_class` identity from the - authoritative backend execution profiles; an authoritative catalog that - has no matching execution profile fails closed instead of guessing a - facade. Unknown/manual artifacts retain the explicitly editable generic - image/audio/video and Modular fallback. The focused browser regression - proved Qwen Image inserts `DiffusersImage.LoadPipeline` with - `QwenImagePipeline`, and that removing its authoritative execution - profile leaves the canvas unchanged with a bounded error. The complete - `npm run check` passed, the complete mocked Studio suite passed 95/95, - and the production bundle was 522977/523264 gzip bytes, 159 bytes below - the stricter 523136-byte safety target. Type, build, bundle, and diff - checks passed. This is static, unit, contract, and mocked-browser evidence - only; no model download, inference, generated media, or live qualification - occurred. - - [x] Exact Expert quantization retention on model changes. Client commit - `d3ad700` removes the local `Qwen Image` family exception from form - mutation. The model selector now retains an Expert quantization choice - only when the exact target model/mode execution profile declares that - choice; missing, invalid, or non-declaring profiles reset to `none`. - The focused browser regression proved `bnb_4bit` survives Qwen-to-FLUX - switching and is removed when switching to Z-Image. The complete - `npm run check` passed, the complete mocked Studio suite passed 96/96 in - 290.8 seconds, and the production bundle was 522954/523264 gzip bytes, - 182 bytes below the stricter 523136-byte safety target. Formatting, lint, - type, unit/contract, build, bundle, browser, and diff checks passed. No - model download, inference, generated media, or live qualification - occurred. - - [x] Declarative low-memory preset selection. Client commit `229b5d1` - removes the Qwen- and Wan-family branches from both Studio low-memory - entry points. The selected model profile now owns the form patch for - dimensions, frame count, steps, dtype, quantization reset, and offload; - the existing distinct Wan 2.2 and LTX values are therefore no longer - overwritten by the legacy Wan VACE preset. The focused contract covers - Qwen Image, Wan VACE, Wan 2.2 I2V, and LTX. The complete `npm run check` - passed, the complete mocked Studio suite passed 96/96 in 295 seconds, - and the production bundle was 522708/523264 gzip bytes, 428 bytes below - the stricter 523136-byte safety target. Formatting, lint, type, unit/ - contract, build, bundle, browser, and diff checks passed. This is a - declarative client-profile cleanup; moving all low-memory dimensions and - frame limits into backend execution specifications remains part of the - parent metadata-ownership audit. No model download, inference, generated - media, or live qualification occurred. - - [x] Generic Modular readiness identity. Client commit `a32b37a` - removes the last `Qwen Image` family check from managed Run readiness. - An existing/restored graph is classified from its generic managed - ModelsLoader, prompt, and denoise roles; before a graph exists, readiness - uses the exact backend execution profile's `modular-diffusers` path. - The focused 43/43 graph-visual matrix includes the legacy restore path, - the complete `npm run check` passed, and the complete mocked Studio suite - passed 96/96 in 291.9 seconds. The production bundle was - 522707/523264 gzip bytes, 429 bytes below the stricter 523136-byte safety - target. Formatting, lint, type, unit/contract, build, bundle, browser, - and diff checks passed. No model download, inference, generated media, - or live qualification occurred. - - [x] Exact restored-quantization admission. Client commit `0c3a4c5` - removes the Qwen/FLUX family filter from persisted Studio form coercion. - Persistence now retains only the existing bounded quantization enum and - Expert readiness permits a non-`none` choice only when the exact backend - execution profile declares it; stale or unsupported restored choices - block with a bounded corrective issue instead of being guessed or - silently reinterpreted by the client. The focused 68/68 contract matrix, - complete `npm run check`, and complete 96/96 mocked Studio suite passed; - the browser run completed in 293.7 seconds. The production bundle was - 522741/523264 gzip bytes, 395 bytes below the stricter 523136-byte safety - target. Formatting, lint, type, unit/contract, build, bundle, browser, - and diff checks passed. No model download, inference, generated media, - or live qualification occurred. - - [x] Final residual client audit and graph-construction cleanup. Client - commit `4afc515` removes the remaining model- and pipeline-name branches - from fallback role selection and form-to-graph value synchronization. - Exact execution specifications remain authoritative for current managed - profiles; pre-specification registered facades retain their generic - dynamic-definition fallback and consume the selected Auto candidate or - backend execution profile without inventing a client pipeline class. - Remaining model comparisons are identity lookup/filtering, explicit - versioned template recipe data, or imported/manual Expert graph inference, - not managed frontend execution routing. Source contracts passed 43/43, - the three affected registered-facade/dynamic-definition/MPS browser cases - passed together, the complete `npm run check` passed, and the final mocked - Studio suite passed 96/96 in 294.7 seconds. The production bundle was - 522653/523264 gzip bytes, 483 bytes below the stricter 523136-byte safety - target. One preceding full browser run hit the known media-format popover - close race at 95/96; that unrelated test passed alone in 3.3 seconds and - the unchanged complete rerun passed 96/96. No model download, inference, - generated media, or live qualification occurred. -- [x] **P0.4 Proof receipts and current mismatch cleanup** - - Backend: bind history to profile/schema version, graph and loader topology, - auxiliary repositories, adapters, LoRAs, ControlNets, runtime profile, and - artifact revisions. - - Client: invalidate stale proof after any bound field changes and label proof - levels accurately. - - Tests: Z-Image loader identity, Qwen mode mappings, Wan profile closure, - Flux Kontext multi-reference, receipt invalidation, and all checked-in graphs. - - [x] Auto schema/profile history receipt binding. Backend commit `bf0af6b` - corrects the schema-v2 planner so every declared candidate publishes its - exact `executionProfileId` and owning `autoResourceSchemaVersion`; runtime - admission now requires both values to match the current backend contract. - History schema v3 at those commits binds local success/failure evidence to those identities, - so an older history schema, replaced execution profile, or changed planner - schema cannot promote a current candidate to `live_proven`. Client commit - `4cad1b2` preserves the typed receipt and adds the positive response-boundary - contract. This also closes the cross-repository P0.2 regression in which - the frozen client correctly rejected real backend candidates because their - required profile ID was absent while complete mocked fixtures supplied it. - The focused backend matrix passed 169 tests with 298 subtests; the complete - backend gate passed 1,138 tests with 4 skips, the existing Diffusers - deprecation warning, and 1,772 subtests. The focused client request suite - passed 12/12, the complete `npm run check` passed, and the complete mocked - Studio browser suite passed 87/87 in 221.6 seconds. The production bundle - remained 523109/523264 gzip bytes, 27 bytes below the stricter 523136-byte - target. Ruff E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - type, formatting, lint, and diff checks passed. All 26 generated client - files remained byte-identical and 317 Gallery files were preserved. A fresh - backend returned ready health and HTTP 200 for `/`; a real Flux plan - returned two schema-v2 candidates, both bound to `flux-schnell:direct` and - candidate schema 2. The served entry SHA-256 was - `6c1c05f0b199746275d7ef008078a1f0ebea2bfce149b531e7a533f1637e271d`. - Its exact five-process tree was stopped and ports 8088/8089 were free. This - is static/unit/contract/mocked-browser/local-HTTP evidence only; no model - download, inference, generated media, or live workload qualification ran. - - [x] Auto optional-runtime receipt binding. Backend commit `f0ccd13` - advances local history to schema v4 and binds success/failure evidence to - the exact optional-runtime profile list plus the immutable requirement - schema, delivery mode, `requiredNow` flag, and execution-profile IDs. - Runtime admission independently recomputes that contract from the current - backend execution profile and rejects a selected/list receipt that is - self-consistent but stale. Transient package state and explanatory reason - remain outside the history identity; graph-derived optional-runtime - admission remains authoritative immediately before execution. The focused - backend compatibility matrix passed 231 tests with 490 subtests, and the - complete backend gate passed 1,138 tests with 4 skips, the existing - Diffusers deprecation warning, and 1,774 subtests. The client receipt/run - contracts passed 78/78, complete `npm run check` passed, and the exact - optional-runtime, atomic Auto submission, and wrong-loader mocked-browser - cases passed 3/3. The unchanged production client remained - 523109/523264 gzip bytes and all 26 generated files matched the backend - mirror byte-for-byte. Ruff E9/F, `py_compile`, `uv pip check` (78 - packages), preflight, and diff checks passed. This is static, unit, - contract, and mocked-browser evidence only; it does not qualify an overlay, - install packages, download a model, or execute a workload. - - [x] Specification-owned Auto graph receipt binding. Backend commit - `3a0b355` and client commit `0131ea7` advance local history to schema v5 - and bind each of the 32 currently specification-owned model/mode pairs to - the exact Studio execution-spec schema version, ID, content hash, and - execution-profile ID. The client requires that candidate contract to match - the active managed binding before apply or Run. Runtime admission - independently recomputes the current backend contract, requires the graph's - role-to-node receipt, and revalidates the exact reviewed nodes, typed edges, - and form bindings before execution. Missing, extra, malformed, or stale - receipts fail closed, and older history cannot promote a changed graph - contract to `live_proven`. The seven exact profile pairs that do not yet - have a backend Studio execution specification remain explicitly outside - this claim rather than receiving an inferred topology receipt. The focused - backend matrix passed 231 tests with 493 subtests; the complete backend gate - passed 1,138 tests with 4 skips, the existing Diffusers deprecation warning, - and 1,777 subtests. Focused client contracts passed 78/78, the complete - `npm run check` passed, and the complete mocked Studio browser suite passed - 87/87 in 229.1 seconds. The production bundle was 523120/523264 gzip bytes, - 16 bytes below the stricter 523136-byte target; all 26 generated files - matched the backend mirror byte-for-byte and all 317 Gallery files were - preserved. Ruff E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - type, formatting, lint, and diff checks passed. A fresh local HTTP plan - returned `flux-schnell:text-to-image:v1`, content hash - `studio-spec-v1-9cd1abb5`, and profile `flux-schnell:direct`; the served - 1,034,853-byte entry matched SHA-256 - `c2baccd69a6c5302c4063d3b124e107d28f20c8a9a46a9355574d1b539a527e2`. - Its exact process tree was stopped and ports 8088/8089 were free. This is - static, unit, contract, mocked-browser, build, and local-HTTP evidence only; - no model download, inference, generated media, or live workload - qualification occurred. - - [x] Auto auxiliary-artifact receipt binding. Backend/bundle commit - `e2a1bf2` and client commit `453da03` bind the two currently declared - auxiliary or internally loaded model dependencies to exact bounded - `id`/`kind`/repository/immutable-revision receipts: Qwen ControlNet Union - for `QwenImageModularPipeline:control_image`, and FLUX.1-dev for - `FluxReduxPipeline:edit_image`. Schema-v2 candidates, selected/list and - retry identity, exact-pair public requirements, runtime hints, admission, - and local history schema v6 all carry or independently recompute the same - receipt; missing, extra, stale, malformed, or top-level/candidate-mismatched - dependencies fail closed. Capability metadata now publishes the same - immutable requirements. The focused backend matrix passed 139 tests with - 317 subtests; the complete backend gate passed 1,141 tests with 4 skips, - the existing Diffusers deprecation warning, and 1,781 subtests. Focused - client contracts passed 79/79, the complete `npm run check` passed, and the - complete mocked Studio browser suite passed 87/87 in 225.1 seconds. The - production bundle was 523132/523264 gzip bytes, four bytes below the - stricter 523136-byte target; all 26 generated files matched the backend - mirror byte-for-byte and all 317 Gallery files were preserved. Ruff 0.12.7 - E9/F, `py_compile`, `uv pip check` (78 packages), preflight, formatting, - lint, type, and diff checks passed. A fresh local server returned ready - health and HTTP 200 for `/`; its real Qwen Control Auto plan and public - capability response both returned the exact ControlNet Union commit, while - the capability response also returned the exact FLUX.1-dev Redux commit. - The verified process tree stopped and port 8088 was free. This is static, - unit, contract, mocked-browser, build, and local-HTTP evidence only; no - dependency download, model inference, generated media, or live workload - qualification occurred. Executable controlled-LoRA receipts are closed by - the following bounded slice; future auxiliary dependencies remain pending. - - [x] Executable controlled-LoRA Auto receipt binding. Backend commit - `5cb785d` advances local history to schema v7 and derives an ordered receipt - from the submitted executable graph immediately before Auto admission. The - worker recognizes the reviewed Modular, direct-image, and direct-audio LoRA - node contracts, resolves their Hub or local Safetensors through the existing - exact descriptor boundary, rehashes the bytes, excludes disconnected/no-op - adapter nodes, and binds module/action, safe artifact identity, adapter - name, scale, scheduler, replacement policy, and descriptor digest. Submitted - `controlledArtifacts` claims are discarded; the server copies only its own - derived receipt into the selected/list candidate identity, resident-cache - signature, and success/failure history. A base-only `live_proven` result is - downgraded for a nonempty adapter set unless exact current schema-v7 history - exists, while passed or independently safe candidates retain their proof. - Local roots never enter the public receipt or bounded error envelope, and - every loader still revalidates its exact bytes immediately before mutation. - Focused backend tests passed 143 tests with 327 subtests; the exact final - backend tree passed 1,147 tests with 4 skips, the existing Diffusers - deprecation warning, and 1,781 subtests. Ruff 0.12.7 E9/F, `py_compile`, - `uv pip check` (78 packages), preflight, and diff checks passed. The - unchanged client passed complete `npm run check`; its bundle remained - 523132/523264 gzip bytes, and the schema-v3 controlled-family mocked-browser - replay passed 1/1. This is static, unit, contract, build, and mocked-browser - evidence only: no adapter/model download, model execution, generated media, - or live workload qualification occurred. The current non-LoRA controlled - artifact set and client proof labeling are closed by the following bounded - slice; future artifact families require their own reviewed receipts. - - [x] Current controlled-workflow artifact receipt closure. Backend commit - `31cbc47` advances Auto history to schema v8 and derives every current - executable controlled-artifact receipt immediately before admission. It - retains the existing exact LoRA receipts, resolves and rehashes Spandrel - upscalers through their pinned Hub snapshot or redacted local-file identity, - and binds soundtrack/lyric auxiliary Diffusers pipelines to their exact - repository, immutable revision, loader class, and descriptor digest. The - selected primary Auto loader remains owned by its existing artifact receipt; - disconnected nodes are excluded, submitted receipt claims are discarded, - and the Spandrel loader rechecks declared revision, size, and SHA-256 before - loading. Client commit `54a610a` preserves exact revision/hash/size metadata - through each current controlled builder and labels base-only proof accurately - for LoRA, upscaler, soundtrack, and lyric-video contracts. Focused backend - tests passed 150 tests with 327 subtests; the exact backend tree passed 1,183 - tests with 4 skips, 2,021 subtests, and only the existing Diffusers - deprecation warning. Ruff 0.12.7 E9/F, `uv pip check` (78 packages), - preflight, and diff checks passed. Focused client graph/template contracts - passed 112/112, the exact artifact browser cases passed 2/2, complete - `npm run check` passed, and the complete mocked Studio suite passed 97/97. - The production bundle was 523069/523264 gzip bytes, 195 bytes below the hard - cap and 67 bytes below the stricter 523136-byte safety target. This is static, - unit, contract, build, and mocked-browser evidence only: no artifact/model - download, model execution, generated media, or live workload qualification - occurred. - - [x] Z-Image Auto execution-spec closure. Backend commit `a4efd6c` adds - `z-image:text-to-image:v1` as the thirty-third backend-owned Studio - execution specification and binds the existing `z-image:auto` profile to - the exact five-node direct-image graph: `modules.DiffusersImage.LoadPipeline`, - `ZImagePipeline`, the reviewed `Tongyi-MAI/Z-Image-Turbo` artifact, and the - generic quantization/recipe/generate/preview route. The public capability, - selected Auto candidate, managed graph, runtime receipt, and backend - admission now share content hash `studio-spec-v1-0d3c1205`; wrong node - identity or a missing/stale receipt fails closed. Client commit `77ceab9` - adds the exact schema-v2 capability fixture and proves the existing generic - materializer seals the Z-Image receipt and loader class without changing - production client source. The exact final backend tree passed 1,148 tests - with 4 skips, 1,781 subtests, and only the existing Diffusers deprecation - warning. The focused backend contract passed 80 tests with 287 subtests; - Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - and diff checks passed. Complete client `npm run check` passed with the - unchanged 523132/523264-byte gzip bundle, exactly four bytes below the - stricter 523136-byte target, and the exact mocked Studio execution-spec - browser contract passed 1/1. Six exact execution-profile pairs remain - without backend-owned Studio specifications. This is static, unit, - contract, build, and mocked-browser evidence only; no model download, - inference, generated media, or live workload qualification occurred. - - [x] Qwen Image text-to-image execution-spec closure. Backend commit - `6e40bab` adds `qwen-image-2512:text-to-image:v1` as the thirty-fourth - backend-owned Studio specification. It binds the existing - `qwen-image:t2i-direct` profile, `modules.DiffusersImage.LoadPipeline`, - `QwenImagePipeline`, official `Qwen/Qwen-Image-2512` default, reviewed - prequantized fallback, and the generic quantization/recipe/generate/preview - topology to content hash `studio-spec-v1-f53ab380`. Every schema-v2 Auto - candidate carries that exact contract, and the backend independently - requires the matching managed graph receipt before execution. Client commit - `531d4b9` extends only the mocked capability fixture and verifies the - selected candidate, graph binding, shared node IDs, and direct loader class; - production client source and bundle are unchanged. The focused backend - matrix passed 86 tests with 329 subtests; the complete backend gate passed - 1,149 tests with 4 skips, 1,781 subtests, and only the existing Diffusers - deprecation warning. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 - packages), preflight, and diff checks passed. Complete client `npm run - check` passed with the unchanged 523132/523264-byte gzip bundle, four bytes - below the stricter 523136-byte target, and the exact mocked Studio - execution-spec browser contract passed 1/1. Five exact execution-profile - pairs remain without backend-owned Studio specifications. This is static, - unit, contract, build, and mocked-browser evidence only; no model download, - inference, generated media, or live workload qualification occurred. - - [x] Qwen Image Edit Modular execution-spec closure. Backend commit - `4596728` adds `qwen-image-edit:edit-image:v1` as the thirty-fifth - backend-owned Studio specification and binds the existing - `qwen-edit:modular` profile to the exact seven-role, thirteen-edge Modular - graph and fifteen form bindings. Capability publication and Auto candidates - carry content hash `studio-spec-v1-ae6a6ce8`; backend validation resolves - the reviewed pipeline's authoritative dynamic node definitions before - publishing the receipt, and execution admission independently rechecks the - exact executable node identities, edges, and bindings. Client commit - `e8aab4e` extends the bounded role vocabulary and generic specification - materializer so a Modular receipt waits for the backend-issued fields and - typed route-state handles before sealing. It adds no model-name routing - branch. The focused backend matrix passed 87 tests with 329 subtests; the - exact complete backend gate passed 1,150 tests with 4 skips, 1,781 subtests, - and only the existing Diffusers deprecation warning. Ruff 0.12.7 E9/F, - `py_compile`, `uv pip check` (78 packages), preflight, and diff checks - passed. Complete client `npm run check` passed with a 523107/523264-byte - gzip bundle (157-byte hard-cap headroom and 29 bytes below the stricter - 523136-byte target). The exact mocked browser receipt passed 1/1, and the - complete mocked Studio suite passed 87/87. Four exact execution-profile - pairs remain without backend-owned Studio specifications. This is static, - unit, contract, build, and mocked-browser evidence only; no model download, - inference, generated media, or live workload qualification occurred. - - [x] Qwen Image Edit Plus execution-spec closure. Backend commit `0e7a8f1` - adds distinct `qwen-image-edit-plus:edit-image:v1` and - `qwen-image-edit-plus:multi-image-reference-edit:v1` receipts as the - thirty-sixth and thirty-seventh backend-owned Studio specifications. Both - bind the existing `qwen-edit-plus:modular` profile and immutable - `Qwen/Qwen-Image-Edit-2511` artifact to the same reviewed seven-role, - thirteen-edge Modular edit graph and fifteen form bindings, with content - hashes `studio-spec-v1-28b9f604` and `studio-spec-v1-86a68b80`. Client - commit `57a4072` adds contract and mocked-browser coverage only; the - production materializer already consumed both receipts without a new - model-name branch or bundle change. The focused backend matrix passed 88 - tests with 329 subtests; the complete backend gate passed 1,151 tests with - 4 skips, 1,781 subtests, and only the existing Diffusers deprecation - warning. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), - preflight, and diff checks passed. Complete client `npm run check` passed - with the unchanged 523107/523264-byte gzip bundle, 29 bytes below the - stricter 523136-byte target, and the exact mocked-browser receipt passed - 1/1. Two complete 87-case mocked Studio replays each passed all exact-spec - assertions and 86/87 overall; their different unrelated late-run failures - (page bootstrap and workflow-tab scrolling) each passed immediately in - isolation. Two exact execution-profile pairs remain without backend-owned - Studio specifications. This is static, unit, contract, build, and - mocked-browser evidence only; no model download, inference, generated - media, or live workload qualification occurred. - - [x] Qwen Layered execution-spec closure. Backend commit `dd594ba` adds - `qwen-image-layered:layer-decomposition:v1` as the thirty-eighth - backend-owned Studio specification. It binds the reviewed - `qwen-layered:modular` profile and immutable - `Qwen/Qwen-Image-Layered` artifact to the exact seven-role, eleven-edge - Modular source-image/prompt/encode/denoise/decode/preview route and seventeen - form bindings, including the pinned source resolution, layer count, and - maximum sequence length. Client commit `ff3f9c6` proves the generic - materializer seals content hash `studio-spec-v1-dda194f0` without a new - model-name routing branch. The complete backend gate passed 1,152 tests - with 4 skips and 1,781 subtests. Complete client `npm run check` passed, the - exact mocked-browser receipt passed 1/1, and the production bundle was - 523118/523264 gzip bytes, 18 bytes below the stricter 523136-byte target. - Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` (78 packages), preflight, - formatting, lint, type, and diff checks passed. One exact execution-profile - pair remained at that checkpoint. This is static, unit, contract, build, - and mocked-browser evidence only; no model download, inference, generated - media, or live workload qualification occurred. - - [x] Qwen Image Control execution-spec closure. Backend commit `03c358b` - adds `qwen-image-2512:control-image:v1` as the thirty-ninth and final current - exact-pair Studio specification. It binds the existing - `qwen-image:modular` profile to eight reviewed roles, thirteen typed edges, - and thirty-two form bindings, including the separate `AutoModelLoader` - ControlNet component, exact Hub selector and immutable revision, - control-image adapter, ControlNet bundle, and route-state chain through - denoise and decode. Client commit `1102249` extends the bounded role and - binding vocabulary while keeping materialization generic; the pinned model - selector stays a Hub-selector object and the receipt content hash is - `studio-spec-v1-2b0e0b6a`. The final backend gate passed 1,153 tests with 4 - skips and 1,781 subtests; the focused final matrix passed 148 tests with 317 - subtests. Complete client `npm run check` passed, the exact browser receipt - passed 1/1, and the complete mocked Studio suite passed 87/87. The - production bundle was 523129/523264 gzip bytes, seven bytes below the - stricter 523136-byte target. Ruff 0.12.7 E9/F, `py_compile`, `uv pip check` - (78 packages), preflight, formatting, lint, type, and diff checks passed. - All 39 current execution-profile pairs now have backend-owned exact graph - receipts. This is static, unit, contract, build, and mocked-browser evidence - only; no model download, inference, generated media, or live workload - qualification occurred. - - [x] Controlled-workflow finalization-proof closure. - - [x] Bounded schema-v2 upscaler repair: exclude field-level `disabled` - from the proof because it is transient UI/signal state and is not consumed - by graph export. A previously schema-matching, complete graph may reseal - only after the already-modeled upscaler topology has an exact executable - ledger and no binding divergence. The focused graph-visual suite passed - 41/41, including partial-route, field-toggle, core-schema mutation, and - post-finalization edge-deletion cases; the complete mocked Studio suite - passed 83/83. This is not generic controlled-extension proof. - - [x] Schema-v3 controlled-workflow proof: begin only from a matching proof, - perform each trusted extension as one synchronous fail-closed transaction, - validate a strict controlled-role-to-node-key contract, preserve the - independent exact Modular core-route check, and seal every managed - extension node identity/schema, node execution-disabled state, parent/loop - semantics, and actual managed edge endpoint. Restore must revalidate the - complete hash, and abort must roll back or remain proofless. Cover LoRA - (Modular, direct image, and direct audio), video sequence and upscaler - composition, quality-sequence loops, soundtrack/export replacement, and - lyric/mux workflows. These reviewed groups are now covered by the client - proof gate; broader P0.4 receipt work and live execution remain separate. - Evidence 2026-08-10: schema-v3 now persists a bounded controlled-contract - declaration and seals the exact managed role/node identity, reviewed - registry execution shape, node execution-disabled state, parent/quality- - loop membership, and actual edge IDs/endpoints/handles. Each reviewed - builder runs inside one synchronous begin/commit/abort boundary; partial or - failed mutations restore the previous graph/proof/history, successful - replacement defers cache cleanup until after commit, and sequential/reverse - compositions retain prior receipts. Authoritative schema messages clear - the proof before mutation and may reseal only the same topology/execution - baseline; malformed persisted declarations remain quarantined until the - operator explicitly detaches the invalid receipt. Field-level `disabled` - and non-durable callbacks remain proof-neutral, while registry-declared - type/display/input/spawn/data-source shape is revalidated independently of - self-supplied hashes. Focused graph/template/run contracts passed 136/136, - the full client `npm run check` passed, and the complete mocked Studio suite - passed 85/85. The production bundle was 522753/523264 gzip bytes (511-byte - hard-cap headroom and 383 bytes below the stricter safety target). The - tracked packed-template codec regenerates deterministically in the normal - unit gate; 77 runnable and 3 planning templates remained deep-identical, - and all 360 Gallery asset paths/purpose sets remained unchanged. Independent - graph-contract and bundle-semantic audits found no remaining blocker. This - is static/unit/contract/mocked-browser CPU evidence only: no model execution, - media qualification, optional package action, or live runtime cutover ran. -- [x] **P0.5 Lazy optional Hugging Face runtime installation** - - [x] Contract/status preparation: declare one exact composite - `transformers==5.14.1` + `peft==0.20.0` profile, bind it to every current - Diffusers execution profile, and publish metadata-only status through Auto, - model capabilities, and workflow listings without changing readiness. - - [x] Staged overlay: generalize the existing package overlay, require an - exact catalog ID/spec digest plus explicit consent, validate in a fresh - process, serialize/cancel installs, and bind validated state to the base - environment identity. - - [x] Fail-closed scaffold: exact request schemas, process/worker mutation - gates, durable jobs, cancellation/watchdog plumbing, artifact-anchored - validation, restart/repair/rollback states, bounded public projections, - and legacy hashless-overlay rejection are implemented and CPU-tested. - The current candidate still rejects before lease, job, network, staging, - or subprocess creation. - - [x] Immutable wheel/installer preparation: bind the complete ten-wheel - Transformers/PEFT closure to exact official PyPI filenames, URLs, - SHA-256 values, and byte sizes for Python 3.12 on Linux, macOS, and - Windows on x86-64 and ARM64. Share one reviewed uv `0.11.26` - archive/executable identity with base setup, require a rehashed receipt, - validate complete unique wheel RECORD hashes/sizes, and place the Windows - watchdog and all descendants in a non-breakaway kill-on-close Job Object. - Install, activation, and cutover flags remain false. - - [x] Promotion storage preparation: bind the install lease to the original - staging-directory identity; use held-parent exclusive, no-replace - promotion plus handle-scoped quarantine cleanup; and persist canonical - manifest/validation digests in a bounded promotion journal. Locked - startup/install/activation/rollback reconciliation completes only one - exact prepared move, acknowledges one exact promoted move, and leaves - malformed, ambiguous, replaced, or missing states fail-closed for repair. - Windows write-through promotion/cleanup and crash-window regressions pass; - action flags remain false pending target-platform execution evidence. - - [x] Windows x86-64 isolated staging proof: under temporary, process-local - future-state flags, pinned uv installed all ten reviewed wheels - (16,930,199 archive bytes) with copy-only cache behavior; 3,441 wheel files - matched their archive anchor; isolated symbol/origin validation passed; - promotion/activation succeeded; a second fresh process loaded exact - Transformers `5.14.1` and PEFT `0.20.0` from the promoted overlay; and - rollback returned to base with no promotion journal or path-bearing - requirements file left behind. Source action/cutover flags remain false. - No model artifact was downloaded or executed. - - [x] Windows x86-64 no-weight staged workload: a second isolated run used - the production artifact-locked install, validation, promotion, - activation, fresh-process, and rollback path. With Hugging Face network - access disabled, exact Transformers `5.14.1` and PEFT `0.20.0` loaded - from the overlay; a tiny local CLIP text encoder accepted PEFT LoRA - adapters and produced a finite `[1, 4, 16]` forward result with four - trainable adapter parameters; Diffusers enabled its PEFT backend; and a - fresh post-rollback process returned to base. Ten locked wheels totaling - 16,930,199 bytes were used, no model artifact was downloaded, and source - flags stayed false. This is not clean-base, supervised server restart, - live model/media, or non-Windows evidence. - - [x] Windows x86-64 clean-base/staged-runtime matrix: a detached - prospective checkout removed Transformers and PEFT from the default - dependencies and required preflight imports, then the managed NVIDIA - installer produced a compatible 64-package CUDA base with all ten staged - distributions absent. Preflight was ready and registry discovery loaded - 132 nodes without loading the optional closure. From that base, the exact - ten-wheel/16,930,199-byte overlay passed install, validation, promotion, - activation, the finite CLIP+LoRA workload in a fresh process, and rollback - to no active environment. The detached checkout and temporary overlay - were removed afterward. This is not live-model, supervised-server, or - non-Windows evidence; the real source dependency/action/cutover flags - remain unchanged. - - [x] Windows x86-64 supervised HTTP lifecycle: from the same prospective - clean base, a real supervised server accepted explicit install consent, - published bounded `installing` and `validating` progress, and retained a - `ready` job bound to environment `runtime-1786525202-55aff828` and the - exact profile/spec digest. Explicit activation returned - `restarting: true`, replaced base worker `CF9j1A2gcK` with active worker - `8ikwPhct-9`, and the new worker reported overlay status `active` plus - Transformers `5.14.1`. Explicit rollback returned `restarting: true`, - replaced that worker with `p49cg4_HIn`, restored process status `base`, - and again reported Transformers absent. A real install first exposed an - invalid keyword call across `call_soon_threadsafe`; both optional-runtime - and legacy optimization progress dispatch now use a bound callback and - have worker-thread regression coverage. The detached checkout, staged - environment, server processes, and diagnostics were removed afterward; - port 8088 was free. Source action/cutover flags remain false. This is not - live-model, live cancellation/repair, or non-Windows evidence. - - [x] Windows x86-64 supervised cancellation and repair: a second clean-base - supervisor began the real locked install, exposed `installing`, accepted - the exact job-scoped cancellation request, and reached terminal - `cancelled` with no staging directory, no promoted environment, no worker - replacement, and Transformers still absent. A subsequent validated - environment was activated, then a one-byte managed-overlay drift was - introduced in the detached qualification tree. On restart, the worker - imported no optional package and published `repair_required` for the - process, active environment, and profile. A new install produced a - separately validated replacement; direct replacement activation was - refused while the corrupt selection remained active, explicit rollback - restarted to base, and activating the exact completed-job environment - restarted into Transformers `5.14.1`. A final rollback returned to a new - base worker with Transformers absent. Setup already prioritizes that exact - completed-job environment receipt over catalog inference. All temporary - processes and managed state were removed. This is not live model/media or - non-Windows evidence. - - [x] Windows x86-64 guarded live-model execution: a detached clean-base - checkout enabled the complete future qualified/action/cutover contract - only in that qualification tree. Model Manager downloaded the - Apache-2.0, safetensors-only, no-custom-code - `optimum-intel-internal-testing/tiny-random-qwen-image` snapshot at exact - commit `ef73a0df0cb8ccfa00cc178ec528c6e681791a10`; validation observed - 17 complete files and 41,663,402 completed bytes and confirmed - `QwenImagePipeline`. A fresh supervised worker activated the exact - ten-wheel composite overlay and ran the existing generic - `DiffusersImage.LoadPipeline -> Generate -> Image.Save` path on an RTX - 4080 at 64 by 64, one step, seed 123. Task `W44WcoUEma-X` completed in - 1.25 seconds and wrote a non-uniform RGB PNG with pixel digest - `sha256:8aef57fdb4aa58e5d2dcacb04b731893d3883f30554e1004e9c49b863a083e6f`; - its runtime receipt reported Diffusers `0.40.0.dev0`, Transformers - `5.14.1`, Torch `2.8.0+cu128`, and CUDA execution. After explicit - rollback, the same exact Qwen loader was rejected before queueing with - HTTP 409 `optional_runtime_staged`, `requiredNow: true`, and no current - task. The run exposed that custom immutable loader pins could not be - passed through Model Manager; `/hf_download` now accepts only an exact - lowercase 40-character optional `revision`, binds concurrent joins to - that revision plus file selection, and forwards it to the app-owned Hub - snapshot operation. The qualification artifact, output, overlay, and - processes were removed afterward. Production dependency, action, - qualification, and cutover declarations remain unchanged. This is not - non-Windows evidence and does not itself authorize the atomic base - dependency cutover. - - [x] Portable non-Windows qualification preparation: add an explicit- - consent, path-redacted qualification command that refuses a non-Python- - 3.12 host, an unverified managed uv executable, or any base interpreter - containing one of the ten staged distributions. In a disposable managed - root it projects the future qualified profile only in memory, uses the - production locked install/validation/promotion/activation path, runs the - offline CLIP+LoRA workload in a fresh child, rolls back, and verifies a - second fresh clean-base child. Focused contract tests cover dormant source - flags, consent-before-preflight, exact artifact selection, forged uv - rejection, and bounded no-overwrite evidence. This prepares a reproducible - Linux/macOS handoff; it is not platform evidence until executed there and - does not replace supervised HTTP restart/cancel/repair or live model/media - qualification. No local macOS host is available; macOS must remain pending - until the manual reviewed hosted workflow or a contributor-controlled Mac - produces the same reviewed evidence. Do not enable global action/cutover - flags in the interim. A Windows-and-Linux-only release would first require - an independently reviewed platform-scoped delivery contract that keeps - macOS base-delivered. - - [x] Linux x86-64 clean-base/staged-runtime and supervised lifecycle: a - detached prospective checkout at `b30c6b1` removed only Transformers and - PEFT from project dependencies. The managed Python `3.12.13` CPU installer - produced a compatible 61-package base with all ten staged distributions - absent; preflight was ready and discovery loaded 133 nodes from 20 modules - without Transformers. The portable qualifier installed and validated the - exact 10-wheel/17,457,395-byte Linux plan, ran the offline finite - `[1, 4, 16]` CLIP+LoRA workload with Transformers `5.14.1` and PEFT - `0.20.0` from overlay-bound origins, and rolled back to a fresh clean-base - process. Its 1,518-byte path-free evidence digest was - `sha256:a90c3c84826a55af615b62eda9b8ea83c356691330eabba52e54ad3b0b790cde`. - A qualification-only supervised server then proved explicit consent, - `installing`/`validating` progress, job cancellation, reinstall, - activation with worker replacement, same-size one-byte drift detection, - `repair_required` without optional imports, direct corrupt-selection - replacement refusal, rollback, exact completed-job replacement - activation, and final clean-base restart. No model/media asset or Gallery - content was downloaded. All qualification state was removed and both - ports were free. This is Linux CPU package/no-weight and HTTP lifecycle - evidence, not AMD GPU, live-model/media, macOS, or production-cutover - evidence; source flags remain dormant. - - [ ] macOS ARM64 executable qualification: the manual-only - `.github/workflows/qualify-optional-runtime-macos.yml` proposal asserts the - hosted architecture on the explicit `macos-15` ARM64 standard runner, - retains the prospective dependency diff, requires ready preflight, runs - the consented qualifier, and uploads bounded evidence for review. It has - not run and makes no macOS success claim. The - [official GitHub-hosted runner reference](https://docs.github.com/en/actions/reference/runners/github-hosted-runners) - was rechecked on 2026-08-14 and still identifies `macos-15` as ARM64; the - workflow's independent `uname -m` assertion remains the fail-closed - runtime authority. - - [x] Enabled-target executable qualification: Windows and Linux x86-64 - have reviewed wheel/installer, clean-base/staged workload, fresh-process, - restart, and rollback evidence. The four pending target rows remain - base-delivered and non-actionable until separately qualified. - - [x] First-use execution guard and client install/activation/restart flow. - - [x] Cutover-dormant guard/status scaffold: exact backend execution - profiles own a versioned seven-field requirement; every current profile - remains `base`/`requiredNow: false`; graph and field-action admission are - rechecked at the worker and pre-import boundaries; and the client exposes - read-only Setup status while blocking only an authoritative - `optional_overlay` requirement. The unqualified current catalog exposes no - install or activation control and sends no package mutation request. - Evidence 2026-08-10: the focused backend guard/status matrix passed 371 - tests with 4 skips and 744 subtests in 22.66 seconds; an independent replay - passed the same matrix in 22.53 seconds. The complete backend gate passed - 1086 tests with 4 skips and 1565 subtests in 42.02 seconds, with only the - existing Diffusers `torch_dtype` deprecation warning. Scoped `py_compile`, - Ruff E9/F, `uv pip check` (78 compatible packages), preflight, port, and - diff checks passed. Client optional-runtime contracts passed 111/111 and - graph-mutation contracts passed 24/24. The final warning-clean - `npm run check` passed with a 523072/523264-byte gzip bundle (192-byte - headroom); the complete mocked Studio suite passed 83/83, including the - exact GET-only optional-runtime Setup contract. That contract proves no - install, activate, or rollback control/request is exposed while actions - remain unavailable, and that loading, restart, repair, and qualified-active - status are rendered without stale-active authorization. Independent audits - signed only this cutover-dormant, base-neutral scaffold. These are - static/unit/contract/mocked-browser CPU results: no package action, - dependency cutover, network model download, model execution, media, or - live overlay qualification occurred. - All 26 generated client files were verified byte-identical in the backend - mirror while preserving `web/template-gallery`; the served `index.js` was - 1,005,768 bytes with SHA-256 - `3f71f642055631774a51f1e1e69c5fc5585abe82814016ebd691b1fe69517d6b`. - A fresh worker returned HTTP 200 for `/health`, `/`, and - `/assets/index.js`; the worker and temporary logs were removed and port - 8088 was free afterward. - - [x] Cutover-dormant client controls: Setup parses the bounded backend - environment/job contracts and exposes generic install or repair, progress, - cancellation, activation, and rollback controls only when the exact - profile is backend-qualified, cutover-ready, and action-enabled. Install, - activation, and rollback each require explicit consent; polling is bounded - to the returned job/profile/spec identity; duplicate staged environments - suppress activation; and the current unqualified profile remains GET-only. - Focused optional-runtime contracts passed 33/33, the qualified mocked flow - proved no mutation before consent plus install/progress/cancel/reinstall/ - activation/restart/rollback, and the complete mocked Studio gate passed - 98/98. The production bundle was 522936/523264 gzip bytes, 200 bytes below - the stricter 523136-byte safety target. This is a dormant mocked-browser - control surface, not package-action or workload qualification. - - [x] Actionable first-use qualification: enable the controls only - after executable overlay qualification, then exercise explicit consent, - bounded install progress/cancellation, activation, supervised restart, - repair, and rollback without making - discovery, template open, Auto planning, or base-delivered execution - depend on optional-runtime status. Do not mark this complete until - reviewed cross-platform wheel locks and installer containment are - qualified, backend version/symbol/origin verification succeeds in the - activated worker, supervised restart/repair/rollback are exercised, and a - staged-runtime workload passes. - - [x] Linux x86-64 qualification-only projection passed real HTTP - consent/install/progress/cancel/reinstall, fresh-worker activation, - exact version and origin checks, drift repair, rollback, exact receipt - reactivation, and final base restoration. Production controls remain - dormant because macOS and the global cutover review are still open. - - [x] Platform-scoped production cutover: commit `655baa6`, corrected by - `1e95362`, publishes all six target rows and enables only qualified - Linux/Windows x86-64. A clean committed Linux CPU checkout installed 61 - base packages with all ten staged distributions absent. Production - preflight reported the exact source target qualified without an - in-memory flag change; the 10-wheel/17,457,395-byte install, validation, - activation, finite CLIP+LoRA child, rollback, and second clean-base - child passed in 38.19 seconds. The bounded 1,552-byte evidence digest - was `sha256:dd83200dbf7132e65e2e62447a1ea002e8de5ee5e22cdd454db72dc36a1b13af`. - A fresh production worker published qualified/actionable Linux x86-64 - status, reported Transformers absent, and rejected a Z-Image graph with - HTTP 409 `optional_runtime_missing`, `requiredNow: true`, before queueing. - The temporary checkout, evidence, managed state, and processes were - removed; ports 8088/8089 were free afterward. A fresh 2026-08-14 source - revalidation of the target profile table, optional-runtime execution - guard, qualification projection, install guidance, and Diffusers - delivery contract passes 94 tests and 929 subtests. This is regression - evidence over the recorded Linux/Windows cutover, not new macOS or ARM - execution evidence. - - [x] Atomic base cutover: remove both Transformers and PEFT only after the - clean-base and staged-runtime qualification matrices pass. Before any - execution profile changes to `optional_overlay`, add exact repo-aware client - readiness for shared loader classes so local or unknown repositories match - the backend loader-identity guard instead of being shown as runtime-ready. - - [x] Repo-aware shared-loader readiness and Auto parity: the client now - resolves a managed loader by exact module/action plus model/pipeline - identity, disambiguates shared classes only with an exact Hub repository - from each profile's default/fallback/compatible set, and preserves - base-delivery neutrality when no candidate requires an overlay. Local, - malformed, unknown, or ambiguous selectors fail closed as soon as any - matching profile requires the optional runtime. Schema-v2 Auto candidates - require one consistent artifact repository receipt; applying a reviewed - plan may update the exact managed loader, while readiness and submission - require the live loader to match the effective selected repository. - Evidence 2026-08-12: focused client request/template contracts passed - 109/109; the exact wrong-repository mocked-browser regression passed 1/1; - full `npm run check` passed; and the complete mocked Studio suite passed - 99/99 in 293.7 seconds. The production bundle remained within the fixed - gate at 523108/523264 gzip bytes (28 bytes below the stricter 523136-byte - safety target). All 77 runnable and 3 planning templates remained - deep-identical after the size carve. The backend shared-loader/profile - replay passed 41 tests and 254 subtests in 31.67 seconds, including Hub, - compatible-repository, local, custom, malformed, and executable-path - selection. This closes only the repository/readiness prerequisite; it is - not clean-base, staged workload, restart, rollback, or atomic cutover - evidence. - - Backend: add reviewed package requirements to execution specifications; - generalize the staged optional-runtime installer for official Hugging Face - libraries; verify in a fresh process; support activation, restart, and - rollback; then atomically remove both direct Transformers and PEFT - dependencies only after the clean base profile passes. Bind a validated - overlay to its exact runtime-spec, Python, accelerator-profile, and pinned - Diffusers identities so stale overlays require repair instead of loading. - - Client: when Run first needs a missing runtime, show an explicit install - action and progress. Do not install on application setup, template open, - node discovery, or Auto planning. - - Tests: clean base install without Transformers or PEFT, lightweight - discovery and preflight, missing/wrong-version/repair-required readiness, - decline/cancel and concurrent-install exclusion, successful staged install, - exact version/symbol/origin validation, failed validation, - activation/restart, rollback, and an existing Diffusers workflow whose text - encoder requires the optional composite runtime. - - Assets: none. Hardware: CPU-only package and contract tests. - - Audit evidence (2026-08-09): hiding Transformers while leaving PEFT present - failed registry discovery through Diffusers `ComponentsManager` -> - `peft.helpers`; hiding both under offline flags loaded all 20 module groups - and 132 nodes without importing either package. Candidate pins - `transformers==5.14.1` and `peft==0.20.0` passed local no-weight API probes, - but remain unqualified until the managed cross-platform matrix passes. - - Contract/status evidence (2026-08-10): the standard-library-only profile - catalog publishes exact provenance, requirements, and canonical spec digest - `sha256:8e1b0b6b2baa891d4551caa3cde4d59708eced0fd74c1333b68a1aab7ff924b5`. - The executable specification names the complete overlay-owned closure: - Transformers `5.14.1`, PEFT `0.20.0`, tokenizers `0.22.2`, Typer `0.27.1`, - annotated-doc `0.0.5`, Rich `15.0.0`, markdown-it-py `4.2.0`, mdurl - `0.1.2`, Pygments `2.20.0`, and shellingham `1.5.4`. - Missing, wrong-version, unreadable, and exact-present host metadata remain - observational; exact presence is still `present_unqualified`, with cutover, - install, and activation unavailable. Strict discovery, Auto, capability, - `/listgraphs`, and template-open tests prove no optional package is loaded or - installer path invoked by those surfaces. Unknown profile IDs fail closed, - and all three host states leave Auto selection and readiness identical. - Focused implementation gates passed 64 tests and 219 subtests; an independent - adjacent audit passed 109 tests and 311 subtests plus `py_compile`, Ruff E9/F, - `uv pip check`, and diff checks. The complete backend replay passed 1009 - tests and 1400 subtests with 2 platform skips and the existing Diffusers - deprecation warning; repository-wide Ruff E9/F, package compatibility, - preflight, port, and diff checks also passed. The base dependency declarations - still intentionally include both Transformers and PEFT; no installation, - activation, dependency cutover, network access, or model execution occurred. - - Fail-closed overlay-scaffold evidence (2026-08-10): synthetic locked-wheel - tests bind filenames/hashes to the spec, verify the full ten-distribution - closure, re-anchor retained caches, and reject archive replacement, - self-consistent forged RECORDs, startup hooks, links, path aliases, and - unsafe Windows names before optional imports. Server tests cover strict - install/activate/rollback/cancel schemas, both graph/mutation admission - orders, unsupervised restart blocking, durable monotonic jobs, interrupted - job reconciliation, corrupt-state recovery, catalog bounds, and public/on- - disk redaction. Benign subprocess tests cover cross-process lease exclusion, - ordinary child/grandchild cancellation, and hard-worker-death watchdog - cleanup/reacquisition. The frozen focused matrix passed 69 tests and 66 - subtests with two privilege-only symlink skips; the complete backend replay - passed 1058 tests and 1443 subtests with four platform/privilege skips and - the existing Diffusers `torch_dtype` deprecation warning. Scoped Ruff E9/F, - `py_compile`, package compatibility, preflight, port, and diff checks - passed. An independent adversarial audit signed the currently reachable - fail-closed scaffold and explicitly did not sign enabling an overlay. This - is static/unit/no-network CPU evidence, not a staged package install or - model run. At that checkpoint package actions remained unavailable and the - staged-overlay gate remained open. Legacy hashless overlays remain - non-executable after the later platform-scoped cutover. - -### Phase 0 completion gate - -- [x] All focused backend tests pass. -- [x] Complete backend gate passes. -- [x] Client unit and mocked browser gates pass. -- [x] Existing supported exact pairs retain their public inputs and outputs. -- [x] Unknown or unsupported pairs cannot become Auto-ready. -- [x] No generated assets or model downloads were needed. -- [x] A clean base installation does not install Transformers or PEFT; a - requiring workflow remains blocked until its explicit first-use composite - runtime installation succeeds. - -Clean-base test-topology revalidation (2026-08-14): backend `e29fbf4` removes -unintended top-level Transformers use from exact fixture-only tests and scopes -40 tests that construct real optional upstream objects to hosts where the -staged runtime is base-delivered or explicitly activated. The Linux clean CPU -base therefore exercises the full data-only, security, route, installer, and -fixture surface without reinstalling Transformers or PEFT; its complete gate -passed with `1625 passed, 40 skipped, 3273 subtests` and only the existing -Diffusers `torch_dtype` deprecation warning. Those skips do not constitute -macOS evidence: the physical macOS qualifier remains pending, and the skipped -contracts still run on a base-delivered or activated qualified runtime. - -Hosted-runner maintenance (2026-08-14): backend `6e08028`, corrected by -`11d58a9`, moves the manual optional-runtime qualifier and the ordinary backend -CI matrix from GitHub's scheduled-for-deprecation macOS 14 image to the -explicit macOS 15 ARM64 standard runner. The correction found that the -qualifier's duplicated inline prospective-base patch still named an older -Diffusers pin and no longer applied. The workflow now creates one exact patch, -checks and applies that same byte sequence, and has a regression that actually -runs `git apply --check` against the current project. It remains manual-only -and preserves its ARM64 assertion, ready-preflight requirement, consented -execution, and bounded 14-day evidence upload. Both workflows parse as YAML; -the focused installer/runtime regression passed `50 tests, 126 subtests`, the -new executable patch regression passes, and the complete backend gate passes -`1687 tests, 40 skips, 3417 subtests` with Ruff E9/F, package compatibility, -portable preflight, shell syntax, and diff checks green. This is source/static -evidence only: the workflow has not run, no production flag changed, and the -physical macOS gate remains unchecked. - -## Phase 1 — Modular foundation without large model runs - -Priority: after Phase 0. Hardware: CPU and tiny fixtures. Assets: none. - -### Committable segments - -- [x] **P1.1 Reviewed custom Modular and DynamicBlock execution contract** - - Backend: remove the invalid curated default and bundled graph; accept only - `modiff_pipeline_config.json` for bounded declarative MoDiff UI metadata. - Before enabling execution, validate every repository-supplied component - library and class against MoDiff's reviewed official Hugging Face - dependency contract, validate canonical upstream block/workflow metadata, - and pin every transitive repository. Build a content-addressed private - execution snapshot so cache or local-file mutation cannot race validation. - Any repository Python path additionally needs a task-scoped explicit - operator authorization that cannot be restored from imported workflow data. - - Optional runtimes: a validated component may request Transformers or - another separately approved Hugging Face library only through the P0.5 - first-use install/consent profile. Contract preview, template browsing, - registry discovery, and Auto planning remain non-installing operations. - - Client: show a neutral repository field and actionable missing-sidecar, - unpinned-auxiliary, unapproved component, missing-runtime, immutable-snapshot, - and trust errors. Do not mention Mellon in product UI or treat a persisted - identity checksum as consent. - - Tests: exact filename, no filename fallback, missing file, hostile schema, - arbitrary installed-package dispatch, immutable main and auxiliary - revisions, cache/local mutation and validation-to-load races, imported - authorization replay, auth/network distinction, and no downloads or - optional-library installation during node discovery or contract preview. - - Status 2026-08-12: complete in backend `207d8f1`, with actionable response - guidance corrected by `5dc7313`, and client/browser proof `c3e932c`. The - invalid curated repository default, its bundled graph, and its catalog row - are removed. Only the exact `modiff_pipeline_config.json` sidecar supplies - bounded UI metadata; canonical `modular_model_index.json` supplies the - executable pipeline/block and component contract. Hub execution requires - an exact main commit and exact commits for every official Diffusers or - Transformers component repository. Path-like auxiliary sources, - repository requirements, `auto_map`, arbitrary installed-package dispatch, - and local mutable execution fail closed. The backend revalidates identity - immediately before a private content-addressed metadata snapshot and - constructs only the installed reviewed Diffusers pipeline/blocks pair. - Repository Python remains unavailable; the persisted trust field and - contract checksum are not authorization. Preview callbacks neither inspect - nor install the optional runtime, while both executable custom loader paths - use the P0.5 first-use gate. - - Evidence 2026-08-12: the focused backend contract matrix passed (`186 - passed, 628 subtests passed`); the complete backend suite passed (`1245 - passed, 3 skipped, 2107 subtests passed`), followed by the API-guidance - focused gate (`65 passed, 1 skipped, 156 subtests passed`). Ruff `E9,F`, - compile, and diff checks passed. Client `npm run check` passed, including - all unit gates, build, and the bundle budget (`523239 / 523264` total gzip - bytes); the focused mocked-browser admission test passed (`1 passed`). No - model weights, media, repository Python, or optional-runtime install was - downloaded or executed for this segment. -- [x] **P1.2 Generic upstream workflow discovery** - - Backend: derive workflows, required inputs, outputs, and components from - `available_workflows`, `get_workflow()`, block docs, and `init_pipeline()`; - keep small reviewed overlays for MoDiff aliases and UI defaults. - - Client: render task choices and fields from the normalized contract rather - than pipeline-name switches. - - Tests: Sequential, Auto, Loop, state, component reuse, schema round trip, and - unknown workflow rejection. - - Status 2026-08-12: complete in backend `50dafa6` and client `7c6bdbf`. - A checked-in schema-v1 snapshot derives 11 registered pipeline contracts, - 39 workflows, required inputs, outputs, state keys, nested block kinds/docs, - initialized execution classes, and component reuse keys from the exact - pinned no-weight Diffusers APIs. Runtime and registry consumers validate the - bounded snapshot without importing Diffusers. DynamicBlock publishes only - tasks whose required inputs its reviewed sidecar can carry, rejects fields - outside the upstream contract before construction, and filters inputs and - outputs to the selected task. The existing generic client field action - renders the backend-owned task choices and visibility map without a - pipeline-name branch. - - Evidence 2026-08-12: the focused backend matrix passed (`120 passed, 302 - subtests passed`); the complete backend suite passed (`1253 passed, 3 - skipped, 2107 subtests passed`). The pinned generator `--check`, Ruff - `E9,F`, compile, shell syntax, package compatibility, and diff checks passed. - Client `npm run check` passed, including build and the bundle budget - (`523239 / 523264` total gzip bytes), and the focused mocked-browser task - selector test passed (`1 passed`). Preflight separately reported the - existing managed CPU environment receipt as stale for this checkout; no - package install, model weight, network workflow discovery, or asset - generation was performed for this segment. -- [x] **P1.3 Complete the generic guider registry** - - Add `AdaptiveProjectedMixGuidance`, `MagnitudeAwareGuidance`, and - `PerturbedAttentionGuidance` to the existing Guider node. - - Test constructor parameters, required layers/components, signal updates, and - pinned upstream exports. - - Status 2026-08-12: complete in backend `b48355b` with generic client signal - proof `f044594`. `AdaptiveProjectedMixGuidance`, - `MagnitudeAwareGuidance`, and `PerturbedAttentionGuidance` are resolved from - the exact pinned official `diffusers.guiders` namespace. This matters for - Magnitude Aware Guidance because the pin exports it from that namespace but - omits it from the top-level Diffusers lazy-export list; no dependency pin - change or local implementation was introduced. The registry publishes its - bounded `alpha` control to every reviewed guider-capable pipeline, while - Perturbed Attention retains its exact non-empty Layers contract and all - choices remain narrowed by the connected backend pipeline signal. - - Evidence 2026-08-12: the focused pinned upstream matrix passed (`48 passed, - 212 subtests passed`); the complete backend suite passed (`1254 passed, 3 - skipped, 2108 subtests passed`). Ruff `E9,F`, compile, and diff checks - passed. Client `npm run check` passed with the unchanged bundle budget - (`523239 / 523264` total gzip bytes), and the focused generic guider signal - browser test passed (`1 passed`). Tests constructed the three guiders - without weights; no model, media, download, or generated asset was used. -- [x] **P1.4 Register current-pin missing Modular classes as contract-only** - - Split into reviewable image, video, and multimodal batches. - - Do not mark them Auto-ready or live-supported. - - Each batch has backend class/schema tests and client experimental/Expert - visibility tests. - - Status 2026-08-12: complete in backend `8e44eb5` and client `5cb2998`. - The exact pinned upstream inventory is split into six image, seven video, - and two multimodal discovery records. All 15 publish normalized checked-in - workflow/component schemas and appear in the generic Expert model selector, - but remain outside the executable registry with no repository, runnable - mode, optional-runtime, Auto, template, Gallery, or live-support claim. - Models Loader clears their pipeline signal and rejects execution before - artifact or pipeline-index resolution. - - Evidence 2026-08-12: the focused backend matrix passed (`73 passed, 341 - subtests passed`) and the complete backend suite passed (`1259 passed, 3 - skipped, 2168 subtests passed`). The generator `--check`, data-only import, - clean-base optional-import, Ruff `E9,F`, compile, package compatibility, - shell syntax, and diff gates passed. Client `npm run check` passed with the - unchanged bundle budget (`523239 / 523264` total gzip bytes), and the - focused mocked-browser Expert test passed (`1 passed`). No weight, model, - media, network artifact, or generated asset was used. - -### Phase 1 completion gate - -- [x] Complete backend and client gates pass. -- [x] Registry discovery imports no large model stack and downloads no weights. -- [x] Every exposed workflow is present in the pinned upstream block definition. -- [x] DynamicBlock has no Mellon filename, schema, option, or fallback. -- [x] No assets are generated. - -Fresh 2026-08-14 source revalidation passed the custom identity, -DynamicBlock security, contract-only registry, pinned-upstream contract, -workflow-discovery, and workflow-truth suites (`121 passed`, `24 skipped`, -`222 subtests passed`). The run used no weights, media, network artifacts, or -generated assets. - -## Phase 2 — Templates for already implemented execution paths - -Priority: first user-visible expansion. Hardware: contract tests locally; live -output and assets remotely. Assets: remote Dataset only. - -### Committable segments - -- [x] **P2.1 Generic task-template builder and validator** - - Backend: validate exact execution profile, loader identity, graph inputs, and - output contract for every graph. - - Client: generate task skeletons from generic image/audio/video contracts; - do not clone model-specific graph builders. - - Tests: graph round trips, required media, loader identity, stable IDs, and - Gallery-hidden state while qualification is pending. - - Evidence 2026-08-12: the backend now derives 39 content-addressed task - contracts from the authoritative execution specifications and validates - exact profile/loader identity, required media, and modality terminal output. - Checked-in image, video, and audio graphs passed JSON round-trip validation; - loader, output, and required-media tampering failed closed. The client - strictly consumes the same generic contract, produces planning-only - skeletons without another graph representation, ignores future unknown - model types, and keeps every pending contract out of Gallery. The complete - backend suite passed (`1263 passed, 3 skipped, 2253 subtests passed`) with - Ruff `E9,F`, compile, package, shell, and diff gates. `npm run check` passed; - the reviewed task validator increased total production JavaScript to - `524487 / 525312` gzip bytes while the entry remained - `284701 / 448512`. No model, media, network artifact, or generated asset was - used. -- [x] **P2.2 Existing image paths** - - Stable Diffusion XL basics; direct Flux img2img/inpaint/ControlNet; Flux - Kontext multi-reference; Z-Image img2img; supported Qwen img2img, - edit/inpaint, ControlNet, Edit Plus, and Layered modes; existing registered - Modular image pipelines. - - [x] **P2.2a Stable Diffusion XL base text-to-image** - - Evidence 2026-08-12: the backend now owns an exact - `StableDiffusionXLPipeline` text-to-image execution specification and a - content-addressed 1024px planning graph pinned to reviewed revision - `462165984030d82259a11f4367a4eed129e94a7b`. The client consumes the - backend-owned revision through the generic `defaultRevision` binding, - revalidates static node schemas before resealing a refreshed graph, and - fails closed for malformed revisions or tampered schemas. The template is - planning-only, Auto-disabled, and Gallery-hidden; no model, media, or - public asset was downloaded or generated. Pair-scoped generation and - verification passed for the one SDXL workflow. The complete backend suite - passed (`1243 tests, 3 skipped`), with the focused graph/catalog matrix at - `130/130` plus Ruff `E9,F`, compile, package, shell, and diff gates. - `npm run check` passed with production JavaScript at - `525295 / 525312` gzip bytes and the entry at `284983 / 448512`. - Static preflight also reported that this checkout's managed CPU - environment records an older dependency-contract digest and requires the - documented repair command before it can be used for future live proof; - that local environment drift is not source or qualification evidence. - - [x] **P2.2b Stable Diffusion XL image-to-image** - - Evidence 2026-08-12: the existing SDXL Studio model now exposes a separate - `edit_image` execution profile whose exact loader class is - `StableDiffusionXLImg2ImgPipeline`, while text-to-image retains - `StableDiffusionXLPipeline`. Both mode receipts bind the same reviewed base - repository revision without presenting the implementation classes as two - user-facing models. The canonical edit graph requires one source image, - binds the generic Edit node at strength `0.65`, and remains planning-only, - Auto-disabled, and Gallery-hidden. Pair-scoped generation and deterministic - verification passed for the one new workflow. The complete backend suite - passed (`1243 tests, 3 skipped`) with Ruff `E9,F`, compile, package, shell, - and diff gates; `npm run check` passed with production JavaScript at - `525303 / 525312` gzip bytes and the entry at `284983 / 448512`. No model, - source media, inference output, or public asset was downloaded or - generated. Preflight again reported only the already-recorded stale local - managed-CPU contract digest, so this slice makes no live qualification - claim. - - [x] **P2.2 task-contract planning foundation** - - Evidence 2026-08-12: canonical workflow generation now prefers a curated - Studio recipe when one exists and otherwise materializes the exact - backend-owned task-template skeleton. Pending SDXL text-to-image and - image-to-image graphs were regenerated through their content-addressed - task contracts, so further P2 modes no longer require placeholder Gallery - entries, prompts, or per-model client builders. Both pair-scoped - generators and deterministic verifiers passed; backend graph/catalog and - task-contract tests passed (`13 passed, 150 subtests`), the complete - client gate passed before the final redundant-template removal, and the - final focused template/task/graph suites plus build and bundle gate passed - at `525120 / 525312` total gzip bytes. No model, media, inference output, - or public asset was downloaded or generated. - - [x] **P2.2c Stable Diffusion XL inpaint** - - Evidence 2026-08-12: the existing logical SDXL model now owns an exact - `inpaint` execution profile selecting - `StableDiffusionXLInpaintPipeline` at the same reviewed base revision. - Its task contract and canonical graph require separate source-image and - mask inputs, use the generic Inpaint action, and bind the backend-owned - immutable revision without a static planning template or client - model-family graph branch. The contract-only standard adapter entry was - removed once this exact pair became supported; the separate internal - Modular SDXL path remains contract-only. Pair generation and deterministic - verification passed. The complete backend suite passed (`1263 passed, 3 - skipped, 2289 subtests`) with Ruff `E9,F`, compile, package, shell, and - diff gates; `npm run check` passed at `525128 / 525312` total production - JavaScript gzip bytes with the entry at `284983 / 448512`. No weights, - source media, inference output, or public asset was downloaded or - generated, and Auto/Gallery activation remains deferred to P2.5. - - [x] **P2.2d FLUX.1-dev image-to-image** - - Evidence 2026-08-12: the existing logical `FluxDevPipeline` model now - exposes a separate `edit_image` execution profile selecting the exact - `FluxImg2ImgPipeline` loader at reviewed FLUX.1-dev revision - `3de623fc3c33e44ffbe2bad470d0f45bccf2eb21`. Its task contract and canonical - graph require one source image, use the generic Edit action, and bind the - immutable revision without a static planning template or client - model-family graph builder. Auto remains enabled only for the already - qualified text-to-image mode; image-to-image remains Expert planning-only - and Gallery-hidden pending P2.5 qualification. Pair generation and - deterministic verification passed. The complete backend suite passed - (`1263 passed, 3 skipped, 2301 subtests`) with Ruff `E9,F`, compile, - package, shell, and diff gates; `npm run check` passed at - `525142 / 525312` total production JavaScript gzip bytes with the entry at - `284983 / 448512`. No weights, source media, inference output, or public - asset was downloaded or generated. - - [x] **P2.2e FLUX.1-dev inpaint** - - Evidence 2026-08-12: the same logical `FluxDevPipeline` model now owns an - exact `inpaint` execution profile selecting `FluxInpaintPipeline` at the - reviewed FLUX.1-dev revision. Its task contract and canonical graph - require separate source-image and mask inputs, use the generic Inpaint - action, and bind the immutable revision without a static planning - template or client model-family graph builder. The standard adapter left - the contract-only registry only after the pair became executable. Auto - remains text-to-image-only, while direct inpaint stays Expert - planning-only and Gallery-hidden pending P2.5 qualification. Pair - generation and deterministic verification passed. The complete backend - suite passed (`1263 passed, 3 skipped, 2313 subtests`) with Ruff `E9,F`, - compile, package, shell, and diff gates; `npm run check` passed at - `525140 / 525312` total production JavaScript gzip bytes with the entry at - `284983 / 448512`. No weights, source media, inference output, or public - asset was downloaded or generated. - - [x] **P2.2f Z-Image Turbo image-to-image** - - Evidence 2026-08-12: the existing logical `ZImageModularPipeline` Studio - model now exposes a separate `edit_image` execution profile selecting the - standard `ZImageImg2ImgPipeline` adapter at reviewed Z-Image Turbo - revision `f332072aa78be7aecdf3ee76d5c247082da564a6`. Its task contract and - canonical graph require one source image, use the generic Edit action, - and bind the immutable revision without a static planning template or - client model-family graph builder. The standard adapter left the - contract-only registry only after the pair became executable. Auto - remains text-to-image-only, while image-to-image stays Expert - planning-only and Gallery-hidden pending P2.5 qualification. Pair - generation and deterministic verification passed. The complete backend - suite passed (`1263 passed, 3 skipped, 2325 subtests`) with Ruff `E9,F`, - compile, package, shell, and diff gates; `npm run check` passed at - `525152 / 525312` total production JavaScript gzip bytes with the entry at - `284983 / 448512`. No weights, source media, inference output, or public - asset was downloaded or generated. - - [x] **P2.2g Qwen-Image-2512 image-to-image** - - Evidence 2026-08-12: the existing logical `QwenImageModularPipeline` - Studio model now exposes an `edit_image` execution profile selecting the - standard `QwenImageImg2ImgPipeline` adapter at reviewed Qwen-Image-2512 - revision `25468b98e3276ca6700de15c6628e51b7de54a26`. Its task contract and - canonical graph require one source image, use the generic Edit action, - retain the reviewed Qwen Expert resource policy, and bind the immutable - revision without a static planning template or client model-family graph - builder. The standard adapter left the contract-only registry only after - the pair became executable. Existing Auto modes remain unchanged; - image-to-image stays Expert planning-only and Gallery-hidden pending P2.5 - qualification. Pair generation and deterministic verification passed. - The complete backend suite passed (`1263 passed, 3 skipped, 2337 - subtests`) with Ruff `E9,F`, compile, package, shell, and diff gates; - `npm run check` passed at `525154 / 525312` total production JavaScript - gzip bytes with the entry at `284983 / 448512`. No weights, source media, - inference output, or public asset was downloaded or generated. - - [x] **P2.2h Qwen-Image-2512 inpaint** - - Evidence 2026-08-12: the same logical `QwenImageModularPipeline` Studio - model now owns an exact `inpaint` profile selecting the standard - `QwenImageInpaintPipeline` adapter at the reviewed Qwen-Image-2512 - revision `25468b98e3276ca6700de15c6628e51b7de54a26`. Its task contract and - canonical graph require separate source and mask inputs, use the generic - Inpaint action, retain the reviewed Qwen Expert resource policy, and bind - the immutable revision without a static template or client model-family - graph builder. The adapter left the - contract-only registry only for this exact mode; its unqualified outpaint - alias was not advertised. Existing Auto modes remain unchanged, while - inpaint stays Expert planning-only and Gallery-hidden pending P2.5 - qualification. Pair generation and deterministic verification passed. - The complete backend suite passed (`1263 passed, 3 skipped, 2349 - subtests`) with Ruff `E9,F`, compile, package, shell, and diff gates; - `npm run check` passed at `525153 / 525312` total production JavaScript - gzip bytes with the entry at `284983 / 448512`. No weights, source media, - inference output, or public asset was downloaded or generated. - - [x] **P2.2 existing-image-path closure** - - Evidence 2026-08-13: a manifest-derived audit deterministically verified - all 30 registered image model/mode pairs. The audit refreshed stale - canonical layouts for Qwen Image Control and both Qwen Image Edit Plus - modes. Canonical generation now reapplies reviewed immutable revisions - from the backend artifact catalog and derives every required Hub artifact - from the exported graph, preserving the Qwen base, ControlNet Union, and - Lightning LoRA identities through future regeneration. The focused - backend graph/catalog, discovery, truth, and task-contract gate passed - (`32 passed, 230 subtests passed`); the complete client check passed at - `525153 / 525312` total production JavaScript gzip bytes with the entry at - `284983 / 448512`. The complete backend source suite had already passed - for the final P2.2h slice (`1263 passed, 3 skipped, 2349 subtests`). No - weights, media, live inference, or public assets were used, and remote - Gallery qualification remains isolated to P2.5. - - [x] **P2.2i FLUX Redux multi-reference contract closure** - - Backend `cae34b8` adds the exact - `FluxReduxPipeline:multi_image_reference_edit` execution specification, - shares the reviewed direct profile with single-image Redux editing, and - binds both modes to the immutable FLUX.1-dev base requirement. The - generated generic Edit graph requires `referenceImages` and has canonical - workflow hash - `693087f46c6fdb2d948f61be5bdf2e5d5e8fcc9f77f5f0247243130c5b6b4446`. - Multi-reference Redux remains Expert-only; its addition does not expand - the Redux Auto task allowlist. - - Client `f7cd3c1` adds a catalog-wide invariant requiring every one of the - 77 public Studio templates to resolve an exact canonical workflow, so a - public template cannot again remain structurally disconnected from its - backend execution receipt. The manifest now verifies 122 canonical pairs - and 134 supported workflows. The complete backend suite passes (`1695 - passed`, `40 skipped`, `3446 subtests passed`), and the complete client - check passes with the unchanged `533425 / 533504`-byte production gzip - budget. No graph was submitted and no model or media was generated. -- [x] **P2.3 Existing audio paths** - - Stable Audio and existing ACE-Step modes using the generic audio nodes. - - Evidence 2026-08-13: Stable Audio Open 1.0 moved from the - contract-only registry into an exact `stable-audio:direct` execution - profile selecting `StableAudioPipeline` at reviewed revision - `f21265c1e2710b3bd2386596943f0007f55f802e`. Its planning graph uses only - the generic Diffusers audio loader/generator/export nodes and binds Stable - Audio's native task, steps, guidance, waveform-count, duration, and sample - rate controls. It remains Expert-only and Gallery-hidden pending P2.5 live - qualification. Pair-scoped generation and verification now include all - variants for a model/mode pair; all five canonical audio pairs and both ACE - text-to-audio LoRA variants passed deterministic verification. Regeneration - refreshed the three ACE text-to-audio layouts and preserved their exact - base/LoRA artifact receipts. The complete backend suite passed (`1264 - passed, 3 skipped, 2361 subtests passed`) with Ruff `E9,F`, compile, - package, shell, and diff gates; `npm run check` passed at - `525276 / 525312` total production JavaScript gzip bytes with the entry at - `285027 / 448512`. No weights, source media, inference output, or public - asset was downloaded or generated. -- [x] **P2.4 Existing short-video graph paths** - - Wan 2.2 I2V/TI2V, Wan Animate, Wan first/last-frame, LTX long-prompt I2V, - LTX2 joint audio/video, and Hunyuan FramePack. - - This segment commits graph/template contracts only. It does not run video on - the local machine. - - Evidence 2026-08-13: six reviewed planning adapters add ten exact generic - task contracts: Wan 2.2 A14B text-to-video; both Wan Animate character - modes; Wan first/last-frame image-to-video; LTX long-prompt - image-to-video; all four LTX2 video modes with joint video/audio export; - and Hunyuan FramePack image-to-video. The existing Wan 2.2 I2V and TI2V - paths remain covered by the same global library audit. All 58 canonical - pairs and 70 supported workflows, including refreshed deterministic FLUX - and Z-Image variants, passed graph/hash/layout verification. The backend - suite passed (`1264 passed, 3 skipped, 2463 subtests`) together with Ruff, - package, and shell checks. The complete client check passed, the focused - graph/store suites passed (`80 tests`), and the exact-spec plus pending - dynamic-schema browser cases passed after the full mocked sweep reported - `101 passed` and exposed that fixture expectation. The final production - JavaScript bundle is `525155 / 525312` gzip bytes with the entry at - `277958 / 448512`. The standalone local preflight remains non-ready only - because the already-installed CPU profile digest predates the checkout; - no environment repair, weights, source media, inference output, or public - asset was required. Auto and Gallery remain disabled pending P2.5. -- [ ] **P2.5 Remote Gallery qualification and activation** - - Generate examples remotely from the paired commits. - - Review and publish media to an immutable Dataset revision. - - Commit descriptors, hashes, rights/provenance, quality reviews, activation, - and the generated client mirror separately. - - [x] **P2.5a Clean-host campaign readiness:** client `8a93cf2` makes the - existing release-qualification campaign usable on a freshly provisioned - host whose local Auto history and ignored qualification output directory do - not exist yet. Missing history is treated as absent legacy evidence rather - than invented proof, and the report creates only its ignored evidence - directory before writing. A regression exercises that exact clean-host - boundary. The complete client gate passes with the unchanged 530,915-byte - production JavaScript gzip total. A campaign dry run now enumerates 76 - missing qualification receipts in six reusable model-family batches - (ACE-Step, FLUX, LTX, Qwen Image, Wan, and Z-Image). It submitted no graph, - generated no media, and does not satisfy the remote output, human review, - Dataset, activation, or physical macOS gates above. - - [x] **P2.5b Exact app-cache readiness:** client `a76ee04` adds a read-only - campaign preflight that compares every selected template's backend-derived - model and LoRA receipt with the running app's bounded `/hf_cache` - inventory. It accepts only an uncredentialed loopback HTTP(S) origin and - fails closed for a missing repository, wrong immutable revision, - non-installed or incomplete entry, repair requirement, malformed response, - missing artifact receipt, or mismatched execution server. Against the - current app it reports all 76 pending qualification jobs ready across all - 31 unique exact artifacts. The focused nine-test campaign matrix and the - complete client gate pass; production JavaScript remains 530,915 gzip - bytes. This is cache-readiness evidence only: no graph was submitted, no - model was executed, and no output, review, publication, activation, or - physical macOS evidence is claimed. - - [x] **P2.5c Exact default-input readiness:** client `d271a9f`, corrected by - `7738537`, extends the campaign preflight to the byte-pinned Template - Gallery defaults used by each selected job. It validates the - content-addressed runtime path against - the binding digest and checked asset manifest, rejects absent, linked, - oversized, size-mismatched, or hash-mismatched local files, and stops the - campaign before browser or inference startup when any input is unavailable. - The focused 12-test matrix and complete client gate pass; production - JavaScript remains 530,915 gzip bytes. The corrective slice checks both the - lightweight authoring location and the normal installer's durable backend - `web/` payload, and the runner resolves the same installed-app fallback. - The current source checkout reports - 38 input-free jobs ready and 38 jobs blocked by 50 absent exact inputs - totaling 33,867,388 bytes, while all 31 model/LoRA artifacts remain - app-ready. The absent payload was not downloaded outside the app and no - existing model was removed. A normal installer-managed Gallery payload is - still required on the approved qualification app host before P2.5 remote - execution begins; no graph, inference, media, review, publication, - activation, or physical macOS evidence is claimed. - - [x] **P2.5d App-owned pinned Gallery materialization:** backend `df71942` - and client `0fd0830` add the normal running-app path needed to close the - absent-input condition without bypassing the app. Setup now exposes strict - status, plan, and explicit install/repair actions for the exact anonymous - Dataset revision. The backend validates the immutable source descriptor - and canonical manifest identity, reserves the complete download and - same-volume staging copy alongside active model-download reservations and - the 64 GiB safety margin, hashes every staged byte, and atomically promotes - the verified tree. Admission is serialized with model-download space - reservations, while transfers retain the app's bounded parallelism. This - action never deletes model-cache entries. The reviewed approved-subset - descriptor resolves to 356 assets / 480,430,370 bytes and - `sha256:canonical-json:5ec869b755a6ce04a789d6835819da150493bfaef8a6bc1480f0274ba05bcab9`. - Focused backend tests passed (43 tests / 29 subtests), the complete backend - gate passed (`1625 passed, 40 skipped, 3273 subtests`), the complete client - gate and bundle budget passed, and the full mocked Studio sweep passed all - 107 tests. The currently running app predates these routes and was not - restarted because app-managed model downloads remain active; no Gallery - install POST or payload download has occurred. Until those downloads - finish, the new app code is activated by a safe restart, and the user - explicitly confirms the in-app plan, the present source-checkout result - remains 38 input-free jobs ready and 38 input-conditioned jobs blocked. No - graph, inference, media, review, publication, activation, or physical macOS - evidence is claimed. - - [x] **P2.5e Bounded parallel app downloads:** backend `c313908` closes a - mismatch between the server's two-slot download semaphore and the Hub - transfer boundary. The prior process-wide Xet lock wrapped every complete - snapshot call, so independently admitted ordinary downloads still ran one - at a time. A writer-preferring shared/exclusive mode gate now lets two - ordinary app-owned snapshots use the existing bounded slots concurrently, - while a repair waits for all normal transfers to drain, blocks new ones, - temporarily disables process-global Xet behavior, and restores its exact - prior value before ordinary work resumes. Queue-aware immutable byte - reservations and the 64 GiB safety margin are unchanged, and this path - deletes no cache entries. The focused download matrix passes 44 tests; - repeated overlap tests prove two normal transfers enter together, a third - stays queued, and repair mode never leaks in either admission order. The - complete backend gate passes (`1627 passed, 40 skipped, 3273 subtests`), - Ruff E9/F, 66-package compatibility, shell/diff checks, and portable - preflight pass; preflight intentionally observed the healthy existing app - on port 8088 rather than claiming a free-port startup. That running worker - predates this commit and was not restarted while its already-admitted - downloads remain active, so this is source/unit evidence for the next safe - worker restart, not a claim that the current transfers changed mode or that - any model/media/macOS qualification completed. - - [x] **P2.5f Bounded app-owned Hub transport:** backend `77ed298` moves - model and Gallery snapshot payloads onto the standard Hub HTTP path, whose - per-request timeouts and retries provide a bounded failure boundary while - preserving completed cache blobs. Model and Gallery work now share the - app's existing two-transfer semaphore. Ordinary payload transfers may - overlap within that limit; repair is writer-exclusive from its first cache - preparation step and restores the exact prior process-global Hub transport - policy after the exclusive window drains. Queue-aware exact byte plans, - immutable revisions, the 64 GiB reserve, and the no-deletion policy remain - unchanged. The focused matrix passes 56 tests, the concurrency/repair/ - Gallery race subset passes five repeated runs, and the complete backend - gate passes (`1630 passed, 40 skipped, 3273 subtests`) together with Ruff - E9/F, 66-package compatibility, shell/diff checks, and portable preflight. - The already-running worker still predates this source change and was not - restarted; its existing AuraFlow transfer and app-owned overnight queue - were left untouched. This records source/unit behavior only: no active - transfer was switched, no model or Gallery payload was deleted, and no - generation, review, Dataset publication, activation, remote, or physical - macOS evidence is claimed. - - [x] **P2.5g Live download-idle and Gallery-plan cutover:** backend - `578e0a3` activates the P2.5d-f source after the preserved old app queue - drained and fixes the last immutable Gallery-contract mismatch exposed by - that cutover. The pinned manifest has 186 original five-field records and - 170 image records with an exact optional `width`/`height` pair. The backend - accepts only bounded positive image dimensions while continuing to reject - partial pairs, non-image dimensions, arbitrary metadata, and identity - drift. Twelve focused tests pass, and the exact 356-file / 480,430,370-byte - manifest validates at the reviewed asset-set identity. - - The first restarted Linux worker reported runtime-ready, schema-v1 download - status with zero active transfers and reservations, and a generation-free - P2.5 campaign dry run exercised the real download-idle check. All 76 - selected jobs and 31 artifacts remained app-ready; input readiness was - correctly blocked for 38 jobs on 50 absent files / 33,867,388 bytes. The - app's Gallery plan initially refused its - 969,249,348-byte download plus same-volume staging reservation because only - 66,333,192,192 free bytes remained below the 68,719,476,736-byte safety - floor. Clearing 62.8 GiB of disposable uv package-download cache without - touching the Hugging Face cache or installed environments made a later pair - of fresh app plans fit. The app then downloaded, hashed, staged, promoted, - and verified all 356 assets / 480,430,370 bytes at the pinned revision. - - After every payload-bearing transfer reached a terminal state, a second - safe restart registered the managed static route. The current worker is - runtime-ready; Gallery status is installed, complete, and repair-free; and - `/template-gallery/manifest.json` serves 70 reviewed examples. The - generation-free command - `npm run release:qualification:run -- --dry-run --check-app-readiness - --check-download-idle --check-input-readiness --batch-by-model-family - --server http://127.0.0.1:8088` exits zero with all 76 jobs, all 31 - artifacts, and all 50 pinned input assets / 33,867,388 bytes ready across - six model-family groups while download status is idle. The complete client - gate also passes lock/license integrity, formatting, lint, typechecking, - style and unit suites, production build, and the 533,425 / 533,504-byte - total gzip budget with its entry at 282,357 / 448,512 bytes. No model cache entry - was deleted, no graph was submitted, and no inference, output, publication, - activation, or physical macOS evidence is claimed. - - [x] **P2.5h Current-attempt transfer progress reservations:** backend - `c140b3f` closes the progress-accounting edge case exposed when SDXL Turbo - resumed after the planned worker cutover. The old status calculation summed - every file in the repository cache, including an abandoned partial from the - disconnected POST and duplicate logical snapshot links. That could make the - displayed remaining bytes and the task's shrinking reservation optimistic - before the exact snapshot was complete. Live admission still rechecked - current filesystem space and refused non-fitting work, and the eventual - Turbo receipt was independently verified; no unsafe download was admitted. - - Progress now matches immutable LFS hash prefixes with the Hub's - per-attempt incomplete-file suffix, excludes partials older than the current - app task, counts current-attempt retry files as distinct disk consumption, - and treats unhashed metadata conservatively until it is complete. The - focused download/Gallery matrix passes 65 tests and 70 subtests. The - complete backend gate passes 1,694 tests, 3,438 subtests, and 40 expected - skips together with pinned Ruff E9/F, 66-package compatibility, portable - preflight, Python compilation, shell syntax, and diff checks. The current - worker now runs this change; - a brief access-gated Stable Video attempt reported its exact planning phase - and 4,509,190,375 remaining bytes instead of the old premature `.999`, then - failed closed with HTTP 403 before any payload. No transfer was interrupted, - no cache entry was deleted, and no graph, inference, output, or physical - macOS evidence is claimed. - - [x] **P2.5i Per-family cache and input revalidation:** client `a11fc77` - prevents a long campaign from treating its startup checks as durable - authorization for later inference. When the exact app-cache and pinned - default-input checks are requested, the campaign repeats both for the jobs - in every model-family group, alongside the already-mandatory download-idle - check, before submitting that group's first graph. Each checked boundary is - retained in the ignored campaign state so an interrupted run records the - exact model, download, and input readiness it observed. The focused - qualification matrix passes 55 tests and the complete client gate passes - lock/license integrity, formatting, lint, typechecking, style and unit - suites, production build, and the unchanged 533,425 / 533,504-byte gzip - budget. A fresh generation-free dry run against the current app exits zero - with 76/76 jobs, 31/31 exact artifacts, and 50/50 pinned inputs / - 33,867,388 bytes ready across six model-family groups while downloads are - idle. No graph, inference, output, publication, activation, or physical - macOS evidence is claimed. - - [x] **P2.5j Mandatory real-campaign readiness:** client `3ec4d69` makes - exact app-cache, download-idle, and byte-pinned default-input readiness - unconditional for every real campaign. The three CLI switches now opt a - generation-free dry run into those checks; omitting them can no longer let - a live campaign bypass model or input preflight. All three initial receipts - and every per-family recheck remain in the ignored campaign state. The - focused qualification matrix passes 56 tests and the complete client gate - passes lock/license integrity, formatting, lint, typechecking, style and - unit suites, production build, and the unchanged 533,425 / 533,504-byte - gzip budget. A fresh explicit dry run still exits zero with 76/76 jobs, - six groups, and all three readiness classes green. No graph, inference, - output, publication, activation, or physical macOS evidence is claimed. - - [x] **P2.5k Single-owner atomic campaign state:** client `4ac598c` - acquires a process-owned campaign lock before a real run refreshes or - writes evidence. A concurrent campaign now fails before it can replace the - first run's shared state or enter the same app execution session; an - invalid or dead-owner lock is recovered, and owner-checked cleanup cannot - remove a replacement lock. Every state receipt is flushed and atomically - renamed, with temporary files removed on failure. Generation-free dry runs - neither claim the campaign lock nor write state. The focused qualification - matrix passes 57 tests and the complete client gate passes with the - unchanged 533,425 / 533,504-byte gzip budget. No graph, inference, output, - publication, activation, or physical macOS evidence is claimed. - -Fresh 2026-08-14 app readiness after the P2.2i contract repair remains -generation-free. The running app installed, validated, activated, and restarted -the exact qualified Transformers 5.14.1 / PEFT 0.20.0 optional runtime through -its explicit first-use API. A campaign dry run then reports all 76 pending jobs, -31 immutable artifacts, and 50 byte-pinned inputs / 33,867,388 bytes ready in -six family batches while downloads are idle. The host is still CPU-only, while -P2.5 output qualification is remote-only, so no live campaign, output receipt, -quality review, publication, or activation was started. - -### Phase 2 test and asset gate - -- [x] Backend graph/catalog/profile integrity tests pass. -- [x] Client template, quality, Gallery coverage, and mocked browser tests pass. -- [ ] Every public template has a remote live-output receipt for its exact mode. -- [ ] Every media byte is in the Dataset, not either Git repository. -- [ ] Auto remains disabled for any template whose qualification is pending. - -## Phase 3 — Small, fast pipelines and speech recognition - -Priority: first new live execution. Hardware: allowlisted local smoke or remote. -Assets: generated remotely even when a local smoke is allowed. - -### Committable segments - -- [x] **P3.1 Generic unconditional image generation** - - Add an unconditional task mode/adapter, not DDPM-specific nodes. - - Integrate `DDPMPipeline`, `DDIMPipeline`, and - `ConsistencyModelPipeline` in separate exact-pair entries. - - Local smoke: tiny resolution and bounded steps; hard timeout 40 minutes. - - Evidence 2026-08-13: backend `80e4587` and client `8f2a671` - add one generic `UnconditionalGenerate` adapter, three immutable exact - model/mode pairs, prompt-free fixed-resolution Studio controls, and three - deterministic canonical workflows. `google/ddpm-cifar10-32` is pinned at - `267b167dc01f0e4e61923ea244e8b988f84deb80` for DDPM and DDIM; - `openai/diffusers-cd_imagenet64_l2` is pinned at - `5f462e4403fc37b72ec6004e806c71805db22387` for the consistency - model. Offline cached CPU node smokes completed at two DDPM steps - (`32x32`, `0.10s`), two DDIM steps (`32x32`, `0.08s`), and one - consistency step (`64x64`, `0.28s`), all far below the 40-minute bound. - The backend gate passed (`1266 passed, 3 skipped, 2511 subtests`) with - Ruff `E9,F`, package, shell, and diff checks; all 73 canonical workflows - verified; `npm run check` and the complete mocked Studio browser sweep - passed (`102 passed`). The production JavaScript bundle is - `525941 / 526336` total gzip bytes with the entry at - `278350 / 448512`. Auto and Gallery remain disabled pending remote output - review and immutable Dataset publication. No generated media was retained - or committed. The standalone preflight confirmed port 8088 was free but - remains non-ready only because the installed CPU profile digest predates - this checkout, the same unrelated local drift recorded for P2.4. -- [ ] **P3.2 Small latent image workflows** - - Stable Diffusion 1.x/2.x text-to-image, img2img, and inpaint. - - LCM 1-4 step workflows and PAG using compatible base weights. - - Local smoke: at most 512px and the minimum meaningful step count. - - [x] **P3.2a Stable Diffusion 1.5 exact generic workflows** - - Evidence 2026-08-13: backend `a0815b8` and client `5a633a9` - add immutable text-to-image, img2img, and inpaint pairs through the - existing generic image nodes. All three pairs reuse - `stable-diffusion-v1-5/stable-diffusion-v1-5` at - `451f4fe16113bff5a5d2269ed5ad43b0592e9a14` with safetensors and - CreativeML Open RAIL-M provenance; no pipeline-specific execution node - or client graph builder was added. - - Offline cached CPU node smokes completed at `64x64`: text-to-image at - one step in `0.68s`, img2img at two requested steps (`strength=0.8`, one - effective denoise step) in `0.81s`, and inpaint at two steps in `0.92s`. - The backend gate passed (`1266 passed, 3 skipped, 2562 subtests`) with - Ruff `E9,F`, package, shell, and diff checks; all 76 canonical workflows - verified; `npm run check` and the complete mocked Studio browser sweep - passed (`103 passed`). The production JavaScript bundle is - `526012 / 526336` total gzip bytes with the entry at - `278350 / 448512`. - - Auto and Gallery remain disabled pending remote quality review and - immutable Dataset publication. No generated media was retained or - committed. The standalone preflight confirmed port 8088 was free but - remains non-ready only because the installed CPU profile digest predates - this checkout, the same unrelated local drift recorded for P2.4 and - P3.1. - - [ ] **P3.2b Stable Diffusion 2.x exact generic workflows** - - Access review 2026-08-13: the official Stability AI 2.x repositories - remain gated to this unauthenticated qualification environment. No - unreviewed replacement repository or invented immutable revision was - admitted. This slice remains pending independently and does not block - the qualified 1.5, LCM, PAG, or perception work. - - App-plan recheck 2026-08-14: both known official immutable candidates, - `stabilityai/stable-diffusion-2-1-base@1f758383196d38df1dfe523ddb1030f2bfab7741` - and - `stabilityai/stable-diffusion-2-inpainting@218a32afeef278c9ed76affdcb6ea16b653cc8c0`, - still return repository-not-found/gated API responses with unknown size. - No download was submitted and no mirror was substituted. - - [x] **P3.2c Latent Consistency Model 1-4 step workflows** - - Evidence 2026-08-13: backend `6a1b579` and client `9f7b7da` - add one exact generic text-to-image pair for - `SimianLuo/LCM_Dreamshaper_v7` at immutable revision - `a85df6a8bd976cdd08b4fd8f3b73f229c9e54df5`, using the official - `LatentConsistencyModelPipeline`, safetensors, and MIT provenance. The - Studio profile exposes its native one-to-four-step range with a - four-step quality default and no unsupported negative-prompt control. - - The exact component snapshot cached in `6m58s`; the offline cached CPU - `LoadPipeline` plus generic `Generate` node smoke loaded in `0.57s` and - completed one step at `64x64` in `0.49s`. The in-memory output digest was - `fed2db181ee91a8edb6823f2959d37fc841ebd07196281232fcc9cf56112f508`; - no output file was retained. - - The backend gate passed (`1266 passed, 3 skipped, 2578 subtests`) with - Ruff `E9,F`, package, shell, and diff checks; all 77 canonical workflows - verified; `npm run check` and the complete mocked Studio browser sweep - passed (`103 passed`). The production JavaScript bundle is - `526082 / 526336` total gzip bytes with the entry at - `278350 / 448512`. Auto and Gallery remain disabled pending remote - quality review and immutable Dataset publication. The standalone - preflight confirmed port 8088 was free and remains non-ready only for the - previously recorded local CPU profile digest drift. - - [x] **P3.2d PAG workflows using compatible base weights** - - Evidence 2026-08-13: backend `662aa10` and client `42c4dd6` add one - exact generic text-to-image pair for the official - `StableDiffusionPAGPipeline`. It reuses the reviewed - `stable-diffusion-v1-5/stable-diffusion-v1-5` safetensors artifact at - immutable revision `451f4fe16113bff5a5d2269ed5ad43b0592e9a14` and - exposes bounded `pag_scale` and `pag_adaptive_scale` aliases through the - existing generic image generator. Selecting the PAG profile initializes - the upstream-compatible `3.0` and `0.0` defaults and writes both values - into the exact generated graph. - - The offline cached CPU `LoadPipeline` plus generic `Generate` node smoke - loaded in `0.57s` and completed one step at `64x64` in `0.54s` with - guidance `7.5`, PAG scale `3.0`, and adaptive scale `0.0`. The in-memory - output digest was - `69644cbb6d528adf9e18625bf17d3ce126ff54014b3af7380329787ebf45cfd6`; - no output file was retained. - - The backend gate passed (`1266 passed, 3 skipped, 2596 subtests`) with - Ruff `E9,F`, package, shell, compile, and diff checks; all 78 canonical - workflows verified. `npm run check` passed and the complete mocked - Studio browser sweep passed (`104 passed`), including the exact PAG - control/default/binding flow. The production JavaScript bundle is - `526453 / 527360` total gzip bytes with the entry at - `278617 / 448512`. Auto and Gallery remain disabled pending remote - quality review and immutable Dataset publication. The standalone - preflight confirmed port 8088 and required imports are ready but remains - non-ready only for the previously recorded local CPU profile digest - drift (`74d79558...` installed versus `60aa03fa...` current). -- [x] **P3.3 Generic perception output** - - Add prediction-map output semantics and integrate Marigold depth first. - - Add normals, intrinsics, and uncertainty only after the shared output - contract is stable. - - Evidence 2026-08-13: backend `957ab31` and client `1802291` add the - generic `Predict Map` boundary and exact Marigold depth Studio workflow. - The reviewed Apache-2.0 repository - `prs-eth/marigold-depth-lcm-v1-0` is pinned to immutable revision - `04a73502f7fd8fc5e59947b9df3b2266d71d6849`; the node returns a - schema-versioned normalized float32 NHWC relative-depth map and a - grayscale preview. Processing resolution and input-resolution matching - are explicit bounded graph bindings. Normals, intrinsics, and uncertainty - remain deliberately unavailable rather than being inferred from depth. - - The exact safetensors/config snapshot was installed with legacy unsafe - weights excluded. An offline cached CPU `LoadPipeline` plus generic - `PredictMap` smoke loaded in `0.330s` and completed one step at `64x64` in - `0.254s`. The in-memory prediction had shape `[1, 64, 64, 1]`, range - `[0.0, 1.0]`, and SHA-256 - `3cd1069551742ea3ec3c79e1897207c1f5bc97b6bd1bffad3b316b3866ce4911`; - no output file was retained. - - The backend gate passed (`1269 passed, 3 skipped, 2618 subtests`) with - Ruff `E9,F`, package, shell, compile, and diff checks; all 79 canonical - workflows verified. `npm run check` passed and the complete mocked Studio - browser sweep passed (`105 passed`). Auto and Gallery remain disabled - pending remote quality review and immutable Dataset publication. The - standalone preflight remains non-ready only for the previously recorded - local CPU profile digest drift. -- [x] **P3.4 Adopt the official Hugging Face library boundary in repository policy** - - Update backend and client `AGENTS.md`, contributor guidance, Hugging Face - standards, dependency/runtime contracts, and the former Diffusers-only - boundary tests. - - Permit reviewed official Hugging Face libraries while preserving the single - MoDiff graph executor, local execution, immutable sources, no implicit - remote code, generic contracts, rollback, and proof requirements. - - This is a documentation/contract commit and generates no media. - - Status 2026-08-14: backend `91c9a36` and client `28b12b7` implement and - validate the paired policy and executable-boundary contract. The direct - Transformers and PEFT dependency migration gap recorded at that checkpoint - was subsequently closed by the platform-scoped P0.5 cutover. -- [x] **P3.5 Hugging Face Transformers speech-to-text implementation** - - Add generic `Load Speech Recognition Model` and `Transcribe Audio` nodes. - - Support transcription, optional translation, language hint, timestamps, and - chunking through a normalized contract. - - Begin with immutable safetensors revisions of Whisper Tiny/Base or another - reviewed Hugging Face ASR model. Do not create Whisper-specific nodes. - - Local smoke: a short rights-approved fixture; hard timeout 40 minutes. - - Security tests: path/media validation, duration/size limits, no remote code, - bounded output, cleanup, and offline cached execution. - - Package the Transformers runtime through P0.5; do not restore it to default - application dependencies. - - Evidence 2026-08-13: backend `82522ba` and client `571facf` add generic - `Load Speech Recognition Model` and `Transcribe Audio` nodes plus exact - prompt-free transcription and English-translation Studio workflows. The - reviewed Apache-2.0 `openai/whisper-tiny` safetensors snapshot is pinned to - immutable revision `169d4a4341b33bc18d8881c4b69c2e104e1cc0af`. - The normalized schema-v1 result contains the task, text, timestamp mode, - duration, and bounded segments; language hints, none/segment/word - timestamps, and bounded chunk/stride controls are explicit graph bindings. - - The loader remains lazy, sets `trust_remote_code=False`, requires - safetensors, and uses the existing P0.5 Transformers+PEFT optional-runtime - profile rather than adding a base dependency. Focused tests cover managed - path validation, file/duration/sample-rate/channel limits, non-finite - media, immutable revisions, offline cached loading, malformed results, and - aggregate transcript limits. The current base observation remains - `wrong_version` because it contains Transformers 5.15.0; the exact - Transformers 5.14.1 boundary was instead installed into an isolated - temporary target for the live proof and removed afterward. - - An offline cached CPU smoke passed through the real MoDiff loader and - action using a two-second rights-safe signal authored in memory. Model load - took `0.532s`, transcription took `0.144s`, and the normalized no-timestamp - result contained 20 text characters with SHA-256 - `d54010ab982673ed0c6ddc41683626f456463ced73d1d768658e70f1487188f7`; - no fixture or output media was retained. Semantic/quality review against a - rights-reviewed spoken fixture remains a remote Dataset gate. - - The backend gate passed (`1274 passed, 3 skipped, 2646 subtests`) with Ruff - `E9,F`, package, shell, compile, and diff checks; all 81 canonical workflows - verified. `npm run check` passed and the complete mocked Studio browser - sweep passed (`106 passed`). The production JavaScript bundle is - `528630 / 529408` total gzip bytes with the entry at - `280178 / 448512`. Auto and Gallery remain disabled pending remote speech - quality review and immutable Dataset publication. - -### Phase 3 test and asset gate - -- [x] Static signature and artifact-policy tests pass for every admitted exact model/mode. -- [x] Tiny/mocked output normalization tests pass. -- [x] Paired client forms, graph bridges, readiness, errors, and browser flows - pass. -- [x] Each local smoke completes below 40 minutes or is moved to remote without - a local retry. -- [ ] Remote media and ASR fixtures pass rights review and Dataset verification. -- [x] Only exact live-qualified recipes may enter Auto. - -## Phase 4 — Medium image, audio, and 3D integrations - -Priority: after the small-model contracts are stable. Hardware: remote by -default. Assets: remote Dataset only. - -### Committable segments - -- [ ] **P4.1 Control adapters:** SD1.5 ControlNet and T2I Adapter with pinned - preprocessors and auxiliary models. - - [x] **P4.1a SD1.5 ControlNet Canny:** the generic Diffusers image loader - assembles `StableDiffusionControlNetPipeline` from the existing immutable - SD1.5 base plus `lllyasviel/control_v11p_sd15_canny` commit - `115a470d547982438f70198e353a921996e2e819`. Both repository loads require - safetensors, the auxiliary kind/class/parameter and independent revision - are backend-owned, and the admission receipt binds the complete assembly. - The canonical graph runs the existing generic Canny node at exact - thresholds `0.1/0.2` before `ControlGenerate`; a CPU tiny fixture verifies - the preprocessor extent and non-empty output. The generated workflow is the - 82nd deterministic catalog entry. Auto, Gallery, and live proof remain off - pending remote output review. - - [ ] **P4.1b SD1.5 T2I Adapter:** deferred independently. The reviewed - official `TencentARC/t2iadapter_canny_sd15v2` snapshot at - `a18baf4f0ff002f34dc2f19c4b93fe00d4cce9ee` publishes legacy PyTorch `.bin` - weights rather than safetensors. The generic loader rejects - `StableDiffusionAdapterPipeline`; no unsafe-deserialization exception or - unlicensed community conversion was admitted. - - P4.1a source commits are backend `539650a` and client `785b43e`. The final - backend gate passed (`1280 passed, 3 skipped, 2661 subtests`) with Ruff - `E9,F`, package, shell, compile, and diff checks. All 82 workflows verify; - `npm run check` passed, the mocked Studio sweep passed (`106 passed`), and - shared-control browser coverage passed (`2 passed`). The production bundle - remains within budget at `528981 / 529408` total gzip bytes and - `280348 / 448512` for the entry chunk. No weights or output media were - downloaded or retained. -- [x] **P4.2 SDXL expansion:** Turbo first, then the reviewed text, image, - inpaint, instruct, ControlNet, adapter, PAG, and related combinations. - - [x] **P4.2a SDXL Turbo text-to-image:** the generic Diffusers image loader - exposes a logical `StableDiffusionXLTurboPipeline` backed by the upstream - `StableDiffusionXLPipeline` and immutable `stabilityai/sdxl-turbo` commit - `71153311d3dbb46851df1931d3ca6e939de83304`. Loading is restricted to the - reviewed fp16 safetensors variant, inference is bounded to one through four - steps, guidance is fixed to zero, and negative prompting is hidden. The - canonical 512px one-step graph is the 83rd deterministic catalog entry. - Auto, Gallery, live output, license-surface approval, and physical macOS - qualification remain pending; no result is inferred from Linux static or - mocked evidence. - - [x] **P4.2b SDXL InstructPix2Pix image editing:** the generic Diffusers - image loader exposes the upstream - `StableDiffusionXLInstructPix2PixPipeline` against immutable - `diffusers/sdxl-instructpix2pix-768` commit - `06653d47f8d22f2c2205a5884d6a24c5e76d2ca7`. Loading requires the reviewed - safetensors-only snapshot without remote code. The exact experimental - recipe accepts one source image at 768px, 30 steps, text guidance 3, and - image guidance 1.5; the new backend-owned generic image-guidance field is - bounded to the upstream-supported range and forwarded without a - model-specific client branch. Its canonical graph is the 84th - deterministic catalog entry. Auto, Gallery, live output, and physical - macOS qualification remain pending; no output quality claim is inferred - from contract or mocked evidence. - - [x] **P4.2c SDXL ControlNet Canny:** the generic conditioned Diffusers image - loader assembles the immutable SDXL base with - `diffusers/controlnet-canny-sdxl-1.0` commit - `eb115a19a10d14909256db740ed109532ab1483c`. Both loads require the reviewed - fp16 safetensors variants; the auxiliary artifact receipt additionally - records the exact 2,502,139,136-byte file and SHA-256. The upstream - `StableDiffusionXLControlNetPipeline` receives the existing generic Canny - preprocessor output at exact thresholds 0.1/0.2 and the reviewed 1024px, - 50-step, guidance-5, conditioning-scale-0.5 recipe. Its canonical graph is - the 85th deterministic catalog entry. Auto, Gallery, live output, and - physical macOS qualification remain pending; no output quality claim is - inferred from contract or mocked evidence. - - [x] **P4.2d SDXL T2I-Adapter Canny:** the same generic conditioned image - contract now assembles the immutable SDXL base with Apache-2.0 - `TencentARC/t2i-adapter-canny-sdxl-1.0` commit - `2d7244ba45ded9129cfbf8e96a4befb7f6094210`. The reviewed fp16 component is - the exact 158,060,440-byte `diffusion_pytorch_model.fp16.safetensors` file - with SHA-256 - `e3db0d9cb3dd54c116a429a1de067d952780047944dd7dba8be032dd2e737f81`; - no remote code or unsafe deserialization is allowed. The upstream - `StableDiffusionXLAdapterPipeline` receives the generic Canny preprocessor - output at exact thresholds 0.1/0.2 and the reviewed 1024px, 30-step, - guidance-7.5, adapter-scale-0.8 recipe. Its canonical graph is the 86th - deterministic catalog entry. Auto, Gallery, live output, and physical - macOS qualification remain pending; no output quality claim is inferred - from contract or mocked evidence. - - [x] **P4.2e SDXL PAG text-to-image:** the generic Diffusers image loader - exposes upstream `StableDiffusionXLPAGPipeline` over the existing immutable - `stabilityai/stable-diffusion-xl-base-1.0` commit - `462165984030d82259a11f4367a4eed129e94a7b`. Loading remains restricted to - the reviewed fp16 safetensors variant with no remote code and no auxiliary - artifact. The backend-owned recipe follows the upstream 1024px, 50-step, - guidance-5 defaults with PAG scale 3 and adaptive scale 0; both PAG controls - use the existing generic action fields. Its canonical graph is the 87th - deterministic catalog entry. Auto, Gallery, live output, and physical - macOS qualification remain pending; no output quality claim is inferred - from contract or mocked evidence. - - [x] **P4.2f SDXL PAG image-to-image and inpaint:** the same immutable SDXL - base now loads the upstream `StableDiffusionXLPAGImg2ImgPipeline` and - `StableDiffusionXLPAGInpaintPipeline` classes through exact generic image - adapters. Both modes retain fp16 safetensors-only loading, no remote code, - and no auxiliary artifact. The reviewed MoDiff recipe uses 1024px, 50 - steps, guidance 5, strength 0.8, PAG scale 3, and adaptive scale 0; edit - requires one source image and inpaint requires one source plus one mask. - Their canonical graph hashes are - `2a74886a72cdf5c66b90a58fc4b31797d2e94073e44c61833da350979a21065f` - and `6903adcae1149856449ba860706b887214f7493b365cb2264afc0ce4ebc1f762`, - bringing the deterministic catalog to 89 workflows. Auto, Gallery, live - output, and physical macOS qualification remain pending; no output quality - claim is inferred from static, unit, or mocked evidence. - - Existing SDXL base text-to-image, image-to-image, and inpaint source slices - remain recorded under P2.2a through P2.2c. Other related combinations - require independent admission and do not reopen the completed reviewed - P4.2 set. - - P4.2a source commits are backend `fb49ed8` and client `f893514`. The final - backend gate passed (`1282 passed, 3 skipped, 2680 subtests`) with Ruff - `E9,F`, package, shell, compile, and diff checks. All 83 workflows verify; - `npm run check` passed and the complete mocked Studio sweep passed - (`106 passed`). The production bundle remains within budget at - `529013 / 529408` total gzip bytes and `280348 / 448512` for the entry - chunk. No weights or output media were downloaded or retained. - - P4.2b source commits are backend `eb2a28e` and client `b7ed626`. The - complete backend gate passed (`1283 passed, 3 skipped, 2696 subtests`) with - Ruff `E9,F`, package, shell, compile, and diff checks. All 84 workflows - verify; `npm run check` passed and the complete mocked Studio sweep passed - (`106 passed`). The production bundle remains within budget at - `529098 / 529408` total gzip bytes and `280348 / 448512` for the entry - chunk. No weights or output media were downloaded or retained. - - P4.2c source commits are backend `a4ae9ca` and client `5440570`. The - complete backend gate passed (`1284 passed, 3 skipped, 2712 subtests`) with - Ruff `E9,F`, package, shell, compile, and diff checks. All 85 workflows - verify; `npm run check` passed, shared-control browser coverage passed - (`2 passed`), and the complete mocked Studio sweep passed (`106 passed`). - The production bundle remains within budget at `529223 / 529408` total - gzip bytes and `280348 / 448512` for the entry chunk. No weights or output - media were downloaded or retained. - - P4.2d source commits are backend `2d14051` and client `b13d65d`; client - commit `c267b35` separately fixes the shared portalled-select closed-state - pointer contract exposed by the browser gate. The complete backend gate - passed (`1285 passed, 3 skipped, 2728 subtests`) with Ruff `E9,F`, package, - shell, compile, build, and diff checks. All 86 workflows verify; - `npm run check` passed, shared-control browser coverage passed (`2 passed`), - and the previously blocked media-export browser case passed after the - shared fix. The complete mocked sweep then passed 105/106; its sole - unrelated supervisor-poll overlap assertion passed immediately in an - isolated rerun. The production bundle remains within budget at - `529223 / 529408` total gzip bytes and `280348 / 448512` for the entry - chunk. The local managed CPU runtime reports a pre-existing source-digest - drift while package compatibility remains healthy; no runtime was mutated - for this source slice. No weights or output media were downloaded or - retained. - - P4.2e source commits are backend `2a9c29b` and client `379936e`. The - complete backend gate passed (`1286 passed, 3 skipped, 2745 subtests`) with - Ruff `E9,F`, package, shell, compile, build, and diff checks. All 87 - workflows verify and `npm run check` passed. The production bundle remains - within its exact budget at `529378 / 529408` total gzip bytes and - `280364 / 448512` for the entry chunk. The local managed CPU runtime still - reports the same pre-existing source-digest drift while package - compatibility remains healthy; no runtime was mutated for this source - slice. No weights or output media were downloaded or retained. - - P4.2f source commits are backend `63f9075` and client `c0f2e2b`. The - complete backend gate passed (`1286 passed, 3 skipped, 2784 subtests`) with - Ruff `E9,F`, compile, package, build, and diff checks. All 89 workflows - verify with deterministic canonical layouts. `npm run check` passed; after - the final prose-only bundle reduction, the focused 82-test profile suite, - formatting, production build, and budget check also passed. The production - bundle remains within budget at `529387 / 529408` total gzip bytes and - `280364 / 448512` for the entry chunk. The local managed CPU runtime still - reports only the pre-existing source-digest drift while imports, package - compatibility, and port availability remain healthy; no runtime was - mutated. No weights or output media were downloaded or retained. -- [x] **P4.3 Moderate image families:** DreamLite, Sana/Sana Sprint, and other - candidates admitted by the per-model checklist. - - [x] **Sana 0.6B text-to-image:** the generic Diffusers image loader exposes - upstream `SanaPipeline` against immutable - `Efficient-Large-Model/Sana_600M_1024px_diffusers` commit - `28f3af7689de15f3883d5863059a2fca0aa9b829`. The reviewed approximately - 7.70 GB snapshot is safetensors-only, requires no repository Python, and - uses its fp16 variant with the text encoder and VAE placed in bfloat16 as - documented upstream. The backend-owned recipe is 1024px, 20 steps, - guidance 4.5, and maximum sequence length 300. Apache-2.0 applies alongside - the bundled Gemma terms and prohibited-use policy. Its canonical graph hash - is `3184feed07ae57e6f0cdcc3cca8e07f36d3b205e564c6c8fdea2a83ebc94dc6d`. - - [x] **Sana Sprint 0.6B generation and editing:** the loader exposes exact - upstream `SanaSprintPipeline` and `SanaSprintImg2ImgPipeline` classes against - immutable `Efficient-Large-Model/Sana_Sprint_0.6B_1024px_diffusers` commit - `aa76e7f4f4928f378716b6716a2130fba3caf5b1`. The reviewed approximately - 7.70 GB snapshot is native bfloat16 safetensors, requires no repository - Python, and is bounded to one through four steps. The canonical recipe uses - 1024px, two steps, guidance 4.5, maximum sequence length 300, and edit - strength 0.5. Its text and edit graph hashes are - `1867366ab8b89ff35cb9f09dbe6725b6184af5674df0b6076b5f24a7dc600fbe` - and `9a9ef604ec5da99da3616dd38b118162e75657c322b80e2c57f53b745b1d9bda`. - The same Apache/Gemma rights surface remains visible for later review. - - [x] **DreamLite base and mobile generation/editing:** the reviewed Phase 6 - Diffusers pin exports `DreamLitePipeline` and `DreamLiteMobilePipeline`. - Exact immutable `diffusers`-branch snapshots - `carlofkl/DreamLite-base@751cb8dbb9072a8c8ffd8684e0f254b50f20531b` - and - `carlofkl/DreamLite-mobile@6695c3f4be230f0493fa5dbf78be3bc4d3bb2ab4` - are ungated, contain no repository Python, and expose three safetensors - weight files / 5,040,118,270 bytes each. Both remain non-commercial under - CC-BY-NC-4.0. Base uses 1024px, 28 steps, text guidance 3.5, edit image - guidance 1.5, and at most 200 prompt tokens. Mobile is bounded to one - through eight steps with four recommended; its ignored text/image guidance - inputs are omitted from the graph rather than presented as functional - controls. The base text/edit graph hashes are - `c55d65890ac84a17c61788ab37085055c9479882f7af44c98aaf5382b2c65243` - and `7b0a0a8f61125ee39cbf40383044ce8959dac5b5950622b46889555bb98ccca5`; - the mobile text/edit hashes are - `1f5a2b3b02a4a266133d695a79539191dc7652aa80607e7fb06a839e66da6281` - and `3ab28cdc0cd0782e3b7a03717c9127d0365a1639bd922b0247fdd4bc0fbec9cc`. - Upstream does not expose a per-step callback for these pipelines, so exact - step-level progress/cancellation remains unavailable pending an upstream - contract; task-level cancellation remains unchanged. - - P4.3 source commits are backend `7117c80` and `a56e9c0`, client `c41d1c6` - and `96f444b`; client generator-race fix `2c99769` made canonical graph - generation wait for authoritative capability discovery. The earlier Sana - complete backend gate passed (`1288 passed, 3 skipped, 2837 subtests`) with - Ruff `E9,F`, compile, package, build, and diff checks. DreamLite's clean - optional-runtime gate passed 158 focused backend tests; compile, JSON, - workflow, and diff checks passed, and all 103 workflows verify - deterministically. `npm run check` passed after the final DreamLite and - generator changes, including 90 template/profile cases and the production - bundle budget (`529948 / 530432` total gzip bytes and `279775 / 448512` for - the entry chunk). Auto, Gallery, live output, remote quality, and physical - macOS qualification remain pending. No weights or output media were - downloaded or retained. -- [x] **P4.4 Audio generation:** LongCat AudioDiT, Stable Audio quality recipes, - and AudioLDM2 general audio. - - [x] **Stable Audio quality contract:** the existing generic - `StableAudioPipeline` route remains pinned to automatic-gated - `stabilityai/stable-audio-open-1.0` commit - `f21265c1e2710b3bd2386596943f0007f55f802e` under the Stability AI - Community License. Its reviewed bounded recipe remains 30 seconds, 100 - steps, guidance 7, one waveform, and 48 kHz. This slice makes safe - serialization explicit in the loader. Its canonical graph hash is - `895eb04d3f30f5468df8c7eee0296963b5980f7f3578e5add0ea03e1239a7256`. - - [x] **LongCat AudioDiT:** exact upstream - `LongCatAudioDiTPipeline` support uses the reviewed Diffusers-format - `ruixiangma/LongCat-AudioDiT-1B-Diffusers` conversion at immutable commit - `f4c063ea37f262ba5e6129ebd80095a6d6a9de4d`. The approximately 5.70 GB - MIT-aligned snapshot is ungated, safetensors-only, and contains no - repository Python. The backend-owned recipe is 5 seconds, 16 steps, - guidance 4, one waveform, and native 24 kHz, with a hard 30-second bound. - Its canonical graph hash is - `294e3be641d069938edb8fa5c631f5a8aef321297795dfb0638e8cf1c8cf5be3`. - - [x] **AudioLDM2 general audio:** exact upstream `AudioLDM2Pipeline` - support uses official `cvssp/audioldm2` commit - `c8e7e189d324425c05c4c2f81214041ef4107983`. The selected approximately - 4.48 GB execution envelope is safetensors-only even though the repository - also retains legacy `.bin` files; `use_safetensors=True` prevents unsafe - fallback. CC-BY-NC-SA-4.0 remains visible for later release review. The - bounded recipe is 10 seconds, 200 steps, guidance 3.5, three waveforms, - and native 16 kHz. Its canonical graph hash is - `be80299c1142f548e7504a6276aff313d821b0e44651b1d5337359030363f283`. - - The generic audio adapter now owns family-specific duration, step, - guidance, waveform, sample-rate, callback, and output-shape contracts. - Declarative loader/generator metadata actions remain available without - activating the optional runtime, while actual model loading and execution - still require the exact qualified runtime. Client finalization invokes - those actions and waits for the backend-owned pipeline/task contract before - sealing a managed graph, fixing the previously stale ACE-default schema for - non-ACE audio workflows. - - P4.4 source commits are backend `4d6a4d3` and client `f0958e6`; client - commit `2c55d7c` separately consolidates the media fallback required to - retain the strict bundle ceiling. The complete backend gate passed (`1290 - passed, 3 skipped, 2871 subtests`) with Ruff `E9,F`, compile, package, - build, shell, workflow-generation, and diff checks. All 94 workflows verify - deterministically. `npm run check` passed, including 84 profile/template - and 43 graph-visual contract cases; the production bundle is within budget - at `528923 / 529408` total gzip bytes and `279752 / 448512` for the entry - chunk. The local managed CPU runtime still reports only the previously - recorded source-digest drift (`74d795...` installed versus `60aa03...` - current) while required imports, package compatibility, device validation, - and port availability remain healthy; no runtime was mutated. Auto, - Gallery, live output, remote quality, and physical macOS qualification - remain pending. No weights or output media were downloaded or retained. -- [ ] **P4.5 Diffusers text-to-speech:** deferred independently. The pinned - Diffusers `AudioLDM2Pipeline` exposes the required generic `prompt` plus - `transcription` speech-synthesis call contract, and upstream documentation - identifies `anhnct/audioldm2_gigaspeech` as the GigaSpeech TTS checkpoint. - The reviewed ungated snapshot at immutable commit - `c812a7861f38a69441a8e0428438e782d9864614` is approximately 5.83 GB and - contains legacy `.bin` weights for the language model, projection model, - both text encoders, UNet, VAE, and vocoder with no safetensors alternative. - The related `anhnct/audioldm2_ljspeech` snapshot at - `32ab10ffc92907e6a6741319675a1175fd925a66` has the same unsafe-only - serialization envelope. No credible immutable safetensors conversion was - found, and no unsafe-deserialization exception is approved, so MoDiff does - not expose or execute either TTS repository. Revisit this segment only when - a reviewed safetensors artifact exists or the owner approves a narrowly - documented exception with isolated conversion and provenance review. -- [x] **P4.6 Generic 3D artifacts:** Shap-E rendered output is admitted from - official `openai/shap-e` commit - `7bd337afdea1c17842e1c3cc45c4e268356dba40` through explicit safe component - assembly: the fp16 safetensors `prior`, safe text encoder, and the reviewed - pre-rename safetensors `renderer`. The renamed `shap_e_renderer` directory is - deliberately excluded because its reviewed snapshot exposes only legacy - `.bin` weights. The backend seals a bounded 1-120 frame, 64-256px rendered - orbit and rejects unsafe artifact identity, quantization, or device-map - overrides; mesh/PLY/OBJ/GLB remain unavailable until a separate safe export - contract exists. The canonical graph hash is - `da3650ce1a40f12e29760ee1ea58baaab3edb4f57b89683339be76ed4400d7c0`. - Source commits are backend `21d819f` and client `d684fc4`. The complete - backend gate passed (`1297 passed, 3 skipped, 2888 subtests`) with Ruff - `E9,F`, compile, package, build, shell, workflow-generation, and diff checks. - All 95 workflows verify deterministically; `npm run check` passed with 86 - profile/template and 43 graph-visual contract cases, and the production - bundle remains within budget at `529351 / 529408` total gzip bytes and - `279914 / 448512` for the entry chunk. The 106-case mocked Studio sweep has - complete passing evidence: 101 cases passed in the full run and the five - stale audio-mock failures all passed after the mock was aligned with the - backend-owned action contract. Auto, Gallery, live output, remote quality, - and physical macOS qualification remain pending. No model weights or output - media were downloaded or retained. - -### Phase 4 test and asset gate - -- [x] Unit and tiny-fixture tests cover adapters, outputs, and cleanup for each - completed Phase 4 segment. -- [x] Backend/client integrated gates pass for each completed independent segment. -- [x] No Phase 4 live model is required to run locally. -- [ ] Remote receipts include peak memory, runtime, dependency/model revisions, - graph hash, media checks, and cleanup result. -- [ ] Gallery activation follows rights and anonymous byte verification. - -## Phase 5 — Short video qualification - -Priority: after image/audio contracts. Hardware and assets: remote only. - -### Committable segments - -- [ ] Qualify the existing Wan, LTX/LTX2, and Hunyuan FramePack graph paths from - Phase 2 using minimal short outputs. -- [x] Add Stable Video Diffusion using documented offload and decode chunking. - The source-qualified image-to-video slice pins the official gated - `stabilityai/stable-video-diffusion-img2vid-xt-1-1` repository at commit - `043843887ccd51926e3efed36270444a838e7861`, admits only its reviewed - safetensors artifact surface, and exposes the Stability AI Community License - gate before download. The adapter follows the documented CPU-offload, - UNet-forward-chunking, and two-frame decode-chunk recipe; rejects prompt, - source-video, mask, unsafe identity, quantization, and device-map overrides; - and seals bounded image conditioning through exact execution contract - `stable-video-diffusion:image-to-video:v1`. The canonical graph hash is - `2bfcddd0ae5a6214c891f1859180abfc04a3ce0d51f47d73d7aa503910df8ef0`. - Source commits are backend `260637d` and client `d6e0eed`. The complete - backend gate passed (`1302 passed, 3 skipped, 2911 subtests`) with Ruff - `E9,F`, package, shell, JSON, workflow-generation, and diff checks. All 96 - workflows verify deterministically; `npm run check` passed with 43 - graph-visual cases, five consecutive exact-contract regression runs, and all - 106 mocked Studio cases. The production bundle remains within budget at - `529077 / 529408` total gzip bytes and `279720 / 448512` for the entry chunk. - No model weights or output media were downloaded or retained. Remote output, - quality, Dataset, Auto, Gallery, and physical macOS qualification remain - pending. -- [x] Add AnimateDiff/AnimateLCM with separately pinned base model, - `MotionAdapter`, scheduler rules, and optional LoRA. The Expert-only source - slice reuses the reviewed safetensors SD1.5 base at - `451f4fe16113bff5a5d2269ed5ad43b0592e9a14`; pins AnimateDiff v1.5.2 at - `6167b88ffe39b4441fdf2113e77b99a6f56b7906` and AnimateLCM at - `3d4d00fc113225e1040f4d3bec504b6ec750c10c`; and loads only the exact fp16 - `MotionAdapter` safetensors plus the named - `AnimateLCM_sd15_t2v_lora.safetensors` file. The loader seals the documented - linear-beta DDIM/LCM scheduler recipes, AnimateLCM adapter scale `0.8`, VAE - slicing, model CPU offload, fixed 512px output, 8-16 frames, and bounded - steps/guidance. It rejects mismatched base/motion revisions, unsafe artifact - substitution, media conditioning, quantization, device maps, and malformed - outputs before execution can be admitted. The two canonical graph identities - are `a39d33676afba9bcfd364c5f9a1d8c3afb146a537e82d8818babfe156528c4cf` - and `658dff27b265437ef3528b6cdb46d7a7bd865b69727329658603bda7804ae726`; - their checked-in file SHA-256 values are - `4b54e1ddb53c4f40f3c44d8bb272c0c5703aef2c3ecbd01fd3e55a29298bd39e` - and `f30938d1fc02c836f057cdeb838199d9d13161c12afc8c280edeb5b02e04d426`. - Source commits are backend `75dde3c` and client `220fb40`. The complete - backend gate passed (`1306 passed, 3 skipped, 2937 subtests`) with Ruff - `E9,F`, package, shell, JSON, and diff checks. All 98 workflows verify - deterministically; `npm run check` and all 106 mocked Studio cases passed. - The production bundle remains within its reviewed ceiling at - `529711 / 530432` total gzip bytes and `279775 / 448512` for the entry chunk. - Neither motion repository declares a weight license, so the client records an - explicit rights-undetermined acknowledgment and MoDiff grants no use rights. - Auto and Gallery remain disabled; no weights or output media were downloaded - or retained. Authorization review, remote execution/quality, Dataset, Auto, - Gallery, and physical macOS qualification remain pending. -- [x] Evaluate Motif Video independently before source admission. The official - public Apache-2.0 `Motif-Technologies/Motif-Video-2B` snapshot was reviewed at - immutable commit `6748a1f5861a859aca13b30a0c65dd6f9942ca35`. Although the - denoiser is described as 2B, its safetensors weight surface is approximately - 17.26 GB: an 8,599,946,488-byte text encoder, 8,151,344,960-byte transformer, - and 507,591,892-byte VAE. The official native recipe is 121 frames at - 1280x736 and 50 steps, and the documented constrained-memory path requires - model CPU offload. That total artifact and native decode envelope is not a - smaller local candidate, so executable admission is deferred to a remote - heavy-model review with measured peak accelerator/system memory. No weights - or media were downloaded and no executable or client surface was added. -- [x] Evaluate CogVideoX-2B independently after artifact-size and RAM review. - The Expert-only source slice pins the official public Apache-2.0 - `zai-org/CogVideoX-2b` repository at immutable commit - `1137dacfc2c9c012bed6a0793f4ecf2ca8e7ba01`. Its reviewed safetensors-only - weight surface is 13,774,687,212 bytes across the two T5 shards, transformer, - and VAE, with every file size and SHA-256 recorded in the artifact catalog. - The admitted graph is deliberately shorter than the publisher's - representative 49-frame/50-step recipe: exact native 720x480 output, 9-25 - frames in `4k+1` form, 1-50 steps, guidance 1-12, and a maximum prompt length - of 226. Loading requires float16, the exact artifact, safetensors, mandatory - VAE tiling, and model CPU offload; it rejects media conditioning, - quantization, device maps, alternate artifacts, multiple outputs, and - non-PIL output. The canonical graph identity is - `6b9b9badc24068955b8beab40f3f8990292bd70d0c9dfd46dc5d295133245316` - and its checked-in file SHA-256 is - `ad2669a7c80d69dcda610d81cb894373c869ca199764edf3a877b83f690b39fc`. - Source commits are backend `6501e22` and client `00a3802`. The focused gate - passed (`139 passed, 823 subtests`) against the clean Linux base; the complete - backend gate passed in an isolated reviewed Transformers/PEFT test overlay - (`1311 passed, 3 skipped, 2957 subtests`) while the delivered base remained - free of both optional packages and preflight-ready. Ruff `E9,F`, package, - compile, JSON, workflow, and diff checks passed. All 99 workflows verify - deterministically; `npm run check` and all 106 mocked Studio cases passed. - The production bundle remains within its reviewed ceiling at - `529827 / 530432` total gzip bytes and `279775 / 448512` for the entry chunk. - No weights or output media were downloaded or retained. Remote runtime, - memory, output/quality, Auto, Gallery, Dataset, and physical macOS proof - remain pending. - -### Phase 5 test and asset gate - -- [x] Static and mocked tests cover frame count, dimensions, conditioning, - scheduler/adapter compatibility, output normalization, and cleanup for each - completed Phase 5 source slice. -- [ ] Remote smoke uses the minimum supported 8-25 frames and bounded steps. -- [ ] Representative quality proof is limited to approximately 2-4 seconds. -- [ ] Non-black frames, finite tensors, duration/frame rate, and decode/mux - integrity are checked without cross-hardware pixel hashes. -- [ ] Auto remains disabled until the exact short-video recipe has live proof. - -## Phase 6 — Pin update, heavy models, and long-form workflows - -Priority: last. Hardware and assets: dedicated remote qualification only. - -### Committable segments - -- [x] Review all commits between the current and proposed Diffusers pins; update - the executable dependency, compatibility test, and upstream contract tests in - one isolated change. The exact 73-commit delta and green compatibility - evidence are recorded above; source commit is backend `5ee9e1d`, with the - unchanged client contract revalidated. -- [x] Add the post-pin Krea2 and Krea2 Turbo Modular classes. Backend - `1753384` and client `ec2a349` register both as Expert-visible, - contract-only image surfaces. The pinned upstream contracts remain distinct: - base uses `Krea2AutoBlocks`, 28 steps, negative prompting, and a guider; - Turbo uses `Krea2TurboAutoBlocks`, 8 steps, and neither negative prompting - nor a guider. Both expose only required-prompt `text_to_image`, have no - runnable mode, artifact, template, Auto, or Gallery surface, and are rejected - before artifact resolution. The deterministic snapshot now contains 28 - contracts and 80 upstream workflows; 1,313 backend tests plus 2,965 subtests, - the complete client check, and all 106 mocked Studio cases pass. No weights - were downloaded. -- [x] Complete the standard Krea 2 Raw/Turbo artifact, package, license, and - admission-gate review without accepting repository terms. Backend `e176f14` - seals exact official revisions - `krea/Krea-2-Raw@6b0ece7fffb640c5e3bcbe0a7f10f66b8e60a603` and - `krea/Krea-2-Turbo@98e0fe118d17c9e3547fbb2e25acdbae2cadf7c7`. - Each 55-file repository has a Python-free 17-file Diffusers candidate - partition with five safetensors files / 35,666,644,396 weight bytes; exact - weight identities, metadata blob receipts, duplicate root-native checkpoint - exclusions, pinned standard and Modular package source hashes, distinct - 28-step/guidance-4.5 Raw and 8-step/guidance-free Turbo recipes, and - estimate-only resource envelopes are sealed in - `data/krea2-artifact-review.json` without fetching model-weight bytes. - - Admission remains blocked rather than silently accepting rights on the - user's behalf. Both Hugging Face repositories require affirmative acceptance - of the Krea 2 Community License and its moving Acceptable Use Policy. The - pinned license limits commercial use to entities below USD 1 million in - trailing company-wide annual revenue unless an enterprise license is - obtained, carries recipient-acceptance/model-naming/license-copy/notice - distribution duties, and mandates reasonable deployment content filtering. - The package has no safety checker and no step, sequence-length, or output - pixel ceiling. The app-only plans were inspected but not submitted: with - 528,238,714,880 free bytes, a 450,696,547,273-byte existing queue, and the - 68,719,476,736-byte reserve, Raw and Turbo were respectively short by - 53,182,524,488 and 53,172,350,108 bytes. No older model was deleted. - - The complete backend overlay passes 1,631 tests, 3,580 subtests, and three - platform skips; the focused gate passes 21 tests and 104 subtests, Ruff E9/F - and package compatibility pass, and no client change is required. Legal and - product approval, task-scoped terms acceptance, downstream terms/filter - implementation, immutable AUP evidence, bounded runtime admission, app - capacity, remote real-weight review, and physical macOS execution remain - independent gates. -- [x] Complete the standard Stable Diffusion 3 artifact, package, license, and - admission-gate review without accepting repository terms. Backend `e6061d9` - seals exact official revision - `stabilityai/stable-diffusion-3-medium-diffusers@ea42f8cef0f178587cf766dc8129abd379c90671`. - Its Python-free 38-file snapshot occupies 31,012,147,557 bytes. The exact - six-file fp16 partition contains 15,499,002,486 weight bytes and reuses the - immutable base inventory already sealed by the SD3 ControlNet review; no - base weight or safetensors-header bytes were fetched for this slice. Exact - artifact identities, pinned text-to-image, image-to-image, and inpaint - pipeline source hashes, native 1024px recipes, callbacks, interrupt checks, - CPU-offload contract, and estimate-only resource bounds are sealed in - `data/stable-diffusion-3-artifact-review.json`. - - Admission remains fail-closed. The repository requires affirmative - acceptance of the Stability AI Non-Commercial Research Community License, - and production, hosted-service, and API use require a separate license. The - authenticated model index and component configs remain HTTP 401 without - acceptance; MoDiff did not accept those terms. The package pipelines have no - safety checker and do not bound steps, input pixels, or output pixels. The - app-only plan was inspected but not submitted: 524,908,945,408 free bytes - minus the 446,582,359,749-byte existing queue and 68,719,476,736-byte reserve - left 9,607,108,923 bytes, making the 31,012,147,557-byte snapshot short by - 21,405,038,634 bytes. No direct weight download occurred and no older model - was deleted. - - The focused boundary matrix passes 165 tests and 516 subtests. The complete - backend overlay passes 1,636 tests, 3,580 subtests, and three platform skips; - Ruff E9/F and package compatibility pass, and no client change is required. - Task-scoped terms acceptance, commercial-license and legal/product approval, - authenticated component review, backend-owned limits and runtime admission, - app capacity, remote real-weight output review, and physical macOS execution - remain independent gates. -- [x] Add `MiniMaxH3ModularPipeline` only through generic joint video+audio - specifications for its distinct `t2va`, `fl2va`, and `ref2va` workflows. - Validate the `transformer/` versus `transformer_ref/` partition receipt, - Qwen3-VL conditioning, separate video/audio scheduler state, reference-media - bounds, immutable artifact revision, and remote-only resource envelope before - exposing any mode. Backend `baf7271` and client `947a2ef` complete the - contract-only slice without exposing a runnable mode or default repository. - The three upstream workflows map to generic `text_to_video_with_audio`, - `first_last_frame_to_video_with_audio`, and - `reference_to_video_with_audio`; the generic contract schema now preserves - `fl2va`'s `prompt + image` or `prompt + last_image` alternatives instead of - collapsing them into an invalid conjunction. The immutable official snapshot - is `MiniMaxAI/MiniMax-H3` at - `42ed227ee7df40d41602854ae760620d6eb651fe`. Its root Modular surface is 46 - safetensors files and 210,296,909,532 weight bytes with both transformer - partitions; one workflow selects 77,735,901,100 shared bytes plus exactly one - 66,280,504,216-byte transformer, for 144,016,405,316 weight bytes. Every file - size and SHA-256 is sealed in `data/minimax-h3-artifact-review.json`, while - duplicated legacy `FL2VA/`, `Ref2VA/`, and publisher media are excluded. - The receipt also seals Qwen3-VL layer 50, video/audio scheduler shifts 12/3, - 24 fps and `17*n+5` frame alignment, 5-15-second output bounds, and the - 9-image/3-video/3-audio/12-total reference limits with audio-only reference - requests forbidden. Its MiniMax H3 Community License excludes the US, EU, - UK, and Republic of Korea and adds commercial/redistribution obligations, so - the repository is deliberately absent from MoDiff's runtime/download catalog - pending legal and remote hardware qualification. The remote resource envelope - is estimate-only, with at least 160 GiB selective-workflow disk, 256 GiB - system RAM, 192 GiB aggregate accelerator memory, and four accelerators; no - live claim is made. The complete backend suite passes at 1,318 tests plus - 2,969 subtests, the complete client check and bundle gate pass, and all 106 - mocked Studio cases pass. No weights or media were downloaded. -- [x] Add the post-pin `LTX2ModularPipeline` and `LTX25ModularPipeline` and - complete their no-weight source/recipe review. Backend `7e4f99b` and client - `9dfcf62` register both as Expert-visible, contract-only multimodal surfaces, - with four generic joint video/audio workflows each: text, image, condition, - and in-context generation. Both publish the shared conditioner, duration - head, audio VAE, and vocoder contracts and return video plus audio; in-context - generation requires an explicit frame count. LTX-2 retains convolutional VAE - decode controls, while LTX-2.5 replaces them with the diffusion decoder. - Neither class has a runnable mode, default repository, Auto path, template, - or Gallery surface, and both fail closed before artifact resolution. The - deterministic snapshot now contains all 31 reviewed Modular contracts and 91 - upstream workflows. The complete client check and focused mocked-browser - contract test pass; no weights or media were downloaded. - - Backend `b887aef` separately seals the immutable LTX-2.5 source review in - `data/ltx-2.5-artifact-review.json`. It binds distilled single-stage to the - exact eight-sigma schedule with guidance disabled; full/SFT stage 1 to dynamic - shifting plus x2 latent upsampling, the distilled LoRA, and the exact - three-sigma stage-2 tail while retaining stage-1 audio; and distilled - two-stage to the same eight/three sigma schedules with one generator and audio - latents carried into stage 2. The receipt also binds the duration head, - 48 kHz audio-VAE/vocoder handoff, diffusion decode with tiled NATTEN attention - and seeded `denormalize=False`, and the separate Gemma-4 enhancement recipe. - Prompt enhancement and NATTEN kernel provisioning are explicit execution/setup - actions and may not download during discovery or planning. -- [ ] Complete LTX-2.5 artifact and live recipe qualification after gated access - is granted and the LTX-2.x Community License is accepted. The official - `Lightricks/LTX-2.5-Diffusers` snapshot is pinned at - `a6de4b5354f078db24d9cf4778c14846788aea3d`; its public metadata seals 31 - safetensors files and 163,896,920,128 bytes, but the repository denies file - access on this machine. Consequently the gated component indexes cannot yet - resolve the concurrent four-shard/eight-shard `transformer/` layouts, no exact - selective partition or remote resource envelope is claimed, and the artifact - remains outside both runtime and download catalogs. The reviewed license - requires a paid commercial agreement at USD 10 million aggregate annual - entity revenue and places obligations on derivative transfers. Remote heavy - execution and physical macOS evidence remain pending independently. -- [x] Evaluate HunyuanVideo 1.5 without widening its existing contract-only - surface. Backend `31cafc4` seals the official - `tencent/HunyuanVideo-1.5` snapshot at - `9b49404b3f5df2a8f0b31df27a0c7ab872e7b038` and two immutable Diffusers - candidates: 480p text-to-video at - `286be7ce72277246578a3e3cc2487e95ddae5bcf` and 480p step-distilled - image-to-video at `854c04a4c8a53d990b418c7478f0802c0fc8c726`. - Their exact safetensors receipts are respectively 371,759,988,572 bytes for - the full upstream family, 53,367,753,676 bytes for the selective T2V - repository, and 34,620,593,582 bytes for the selective I2V repository. The - source recipes bind 121 frames at 24 fps, 50 steps / guidance 6 / scheduler - shift 5 for T2V, and the recommended 8-or-12-step mean-flow path with - guidance 1 / shift 7 for I2V. CPU model offload, VAE tiling, and optional - attention kernels are recorded; prompt rewriting and kernel downloads remain - forbidden during discovery. - - No runtime or download entry was admitted. The Tencent Hunyuan Community - License preamble excludes the EU, UK, and South Korea, while the formal - `Territory` definition names only the EU; it also adds a 100-million-MAU - commercial threshold, distribution notice, generated-content disclosure, - and output-territory obligations. That inconsistency requires legal review. - Remote execution and physical macOS evidence remain pending, and the resource - envelope in `data/hunyuanvideo-1.5-artifact-review.json` is estimate-only. -- [x] Evaluate Helios/Pyramid without widening the existing contract-only - surface. Backend `873f0ce` pins Helios Base, Mid, and Distilled at - `5c50b6bc90eae9bd815d2a50b0c9877e3fd2cf88`, - `477c55427ec0ea774bdebd0fbe736313cfc5a312`, and - `b991c0379a018f4de3227d95468237f56066f5bb`. Each repository contains 18 - safetensors files / 137,730,908,420 bytes, but its declared standard index - selects only the shared text encoder and VAE plus `transformer/`: 12 files / - 80,481,086,028 bytes. The extra 57,249,822,392-byte `transformer_init/` or - `transformer_ode/` partition is recorded and excluded from the candidate - runtime surface. - - The three existing Modular contracts retain exact generic text-to-video, - image-to-video, and video-to-video workflows at the reviewed 384x640 and - 132-frame defaults. The source receipt separately seals Base's 99-frame, - 50-step, guidance-5 recipe and Distilled's 240-request/264-rounded frame, - `[2, 2, 2]` pyramid-step, guidance-1, amplify-first-chunk recipe at 24 fps. - Publisher claims for approximately 6 GB group-offload memory and 19.5 H100 - fps are explicitly not live qualification evidence. No runtime or download - entry was admitted: every pinned upstream `modular_model_index.json` embeds - `revision: null` for all downloadable components, and the roughly 80.48 GB - selected partitions still require remote heavy-hardware execution. Physical - macOS evidence remains pending independently; no weights or media were - downloaded. -- [x] Evaluate Wan 2.2 A14B Modular without changing the separately registered - standard adapters. Backend `011a70b` seals the existing official T2V A14B - snapshot at `5be7df9619b54f4e2667b2755bc6a756675b5cd7` and I2V A14B - snapshot at `596658fd9ca6b7b71d5057529bbf319ecbc61d74`. Their exact - safetensors surfaces are 28 files / 126,177,598,620 bytes and 28 files / - 126,180,875,420 bytes respectively: one shared T5 encoder, one Wan VAE, and - separate twelve-shard high- and low-noise 14B experts. No weights were - downloaded. - - Neither repository publishes `modular_model_index.json`. At the pinned - Diffusers revision the immutable standard indexes instead map - `WanPipeline` plus `boundary_ratio=0.875` to `Wan22ModularPipeline`, and - `WanImageToVideoPipeline` plus `boundary_ratio=0.9` to - `Wan22Image2VideoModularPipeline`. The receipt binds that fallback without - remote code, the exact contract-only no-required-input T2V and required-image - I2V workflows, and the distinct source recipes: 720x1280 / 81 frames / 40 - steps / guidance 4+3 for T2V, and input-aspect 480x832-area / 81 frames / 40 - steps / inherited guidance 3.5 for I2V, both exported at 16 fps. The - publisher's 80 GB native GPU statement is not treated as live evidence. - Modular runtime admission remains pending remote fallback-assembly and - heavy-hardware qualification; physical macOS evidence remains independently - pending. The existing standard graph-only adapters and artifact pins are - unchanged. -- [x] Evaluate the full classic LTX/LTX2 artifact surfaces and remove the - identity-changing classic fallback. Backend `0f96a92` removes - `Lightricks/LTX-Video` from all four 13B Distilled execution profiles: at the - pinned `8984fa25007f376c1a299016d0957a37a2f797bb` revision that family - repository's standard index selects a two-shard 2B transformer, so it cannot - silently replace the six-shard 13B default. It remains pinned in the artifact - catalog for explicit review but is no longer a published execution candidate. - - Backend `474b83d` seals three immutable inventories. The 13B Distilled - repository at `7c64400e1861cc0d7b98d570a1926d5408ec60cd` contains 21 - safetensors files / 92,762,951,244 bytes, while its standard index selects 11 - files / 47,628,403,428 bytes and excludes nested duplicate encoder/transformer - shards. The classic family repository contains 27 files / - 253,809,013,320 bytes but selects only 7 files / 28,419,691,124 bytes. LTX-2 - at `47da56e2ad66ce4125a9922b4a8826bf407f9d0a` contains 44 files / - 314,290,794,056 bytes; its exact pipeline partition is 23 files / - 92,034,380,210 bytes after its model indexes exclude eight root alternatives, - the concurrent twelve-shard Diffusers-named Gemma layout, and the separately - invoked latent upsampler. Canonical full and selected inventory digests, - component index hashes, and exact sizes are recorded without downloading - weights. - - The existing two-workflow `LTXModularPipeline` contract and all eight classic - LTX/LTX2 graph surfaces remain unchanged. The LTX-2 two-stage source recipe is - bound at 768x512, 121 frames, 24 fps, 40-step guidance-4 latent generation, - then x2 latent upsampling and a three-step guidance-1 distilled tail that - reuses stage-1 audio latents. The classic 13B Distilled card's Diffusers - example targets the dev repository and its distilled YAML link points to the - dev YAML, so it is explicitly not accepted as exact recipe proof. Classic - immutable license text, LTX-2 commercial-license acceptance, remote - heavy-hardware execution, and physical macOS evidence remain pending. The - full backend gate passes (`1,341 passed, 3 skipped, 2,981 subtests`) with Ruff - `E9,F` and a dependency-clean optional overlay. -- [x] Evaluate EasyAnimate V5.1 without publishing an unqualified runtime or - download surface. Backend `3c6da73` seals three public, ungated, - Apache-2.0 Diffusers conversions at immutable revisions: 7B text-to-video at - `f605a9340b46e725da4c1953dc891884dd694314`, 12B inpaint at - `8a257d883449752ecaa6bd4990caf932e927de33`, and 12B control at - `4daad26e8f7f701b37148ac91dc36a4981303af2`. Their exact safetensors - partitions are respectively 7 files / 31,189,417,564 bytes, 7 files / - 41,159,117,300 bytes, and 7 files / 41,159,485,940 bytes. All share the same - five-shard Qwen2-VL text encoder and Magvit VAE, while their distinct - transformers use 16, 33, and 48 input channels; no fallback substitution is - allowed between them. - - The receipt binds the pinned Diffusers source and documentation contract: - 256-1024 dimensions, 1-49 frames with 49 preferred, and 8 fps export. It - separately records the exact source examples for 512x512 T2V with 50 steps - and guidance 6, 448x576 image conditioning through - `get_image_to_video_latent`, and 672x384 control through - `get_video_to_video_latent`. The inpaint and control tensor-preparation paths - still require reviewed MoDiff media adapters, and all three 31-41 GB - candidates require remote heavy-hardware execution. Therefore the family is - intentionally absent from runtime and download catalogs; resource envelopes - are estimate-only, physical macOS evidence remains pending independently, - and no weights or media were downloaded. -- [x] Evaluate the full SkyReels V2 Diffusers surface without admitting an - ambiguous-license or unqualified long-form runtime. Backend `5b53632` seals - all eight official immutable conversions: two 14B T2V, three I2V, and three - diffusion-forcing repositories across 540p and 720p families. Their exact - safetensors partitions range from 8 files / 28,973,450,372 bytes for 1.3B - diffusion forcing through 21 files / 91,339,283,940 bytes for 14B I2V. - Canonical inventory digests, component totals, model indexes, transformer - configurations, and the distinct 16-channel T2V/DF versus 36-channel I2V - contracts are recorded without downloading weights. - - The pinned source exposes base T2V/I2V plus diffusion-forcing T2V/I2V/V2V. - The reviewed DF recipe binds 97-frame windows, 30 denoising steps per - five-latent-frame block, `ar_step=5`, five blocks, and 50 total scheduler rows; - the source-only long-form example advances three windows over 257 frames with - 17-frame overlaps. That is not live qualification. The pinned documentation's - first/last-frame and V2V examples instead target the unlisted and publicly - unresolvable `SkyReels-V2-DF-1.3B-720P-Diffusers`, while the separate V2V - source example omits its required `video` argument. - - No runtime or download entry was admitted. Each model snapshot embeds only a - short notice linking a mutable PDF; the exact linked PDF was frozen at its - source commit for review, but it defines the licensed model as - `Skywork-13B`, not SkyReels V2. Legal scope review, a corrected immutable - long-form recipe, remote heavy-hardware execution, and physical macOS - evidence remain pending. Resource envelopes are estimate-only; no media was - generated or committed. -- [x] Evaluate Cosmos3 Omni and Distilled without widening their existing - contract-only surfaces. Backend `8ba89fa` seals the public Nano snapshot at - `411f42a8fdfb8c5b2583cb8786e0938f49796eaa`, Super at - `e0262be9d8f7586bc24c069a2aed2b665bdff266`, Super T2I 4-Step at - `0573a4b26b8e15d13d416e51f4680c8bc8b8c33d`, and Super I2V 4-Step - at `81da615b7f92dc710c6359b072beee06f675979c`. Their exact - safetensors surfaces are respectively 10 files / 34,894,818,144 bytes, 30 / - 132,624,780,208, 29 / 131,391,926,304, and 28 / 129,405,417,712. - Component totals, canonical inventory digests, index hashes, and the - 36-layer Nano versus 64-layer Super transformer contracts are recorded - without downloading weights. - - The existing Modular discovery contracts remain unchanged: Omni exposes ten - text/image/video, optional-sound, and action workflows; Distilled exposes - four vision workflows with the exact four-sigma schedule, guidance fixed at - 1, and negative prompts ignored. The full source recipe is sealed at - 720x1280, 189 frames, 24 fps, 35 steps, guidance 6, UniPC flow shift 10, - while prompt upsampling remains an explicit external action forbidden during - discovery. Task-pipeline safety defaults on; Modular use additionally calls - `enable_safety_checker()` and depends on `cosmos_guardrail`. - - No runtime or download entry was admitted. Nano/Super Modular descriptors - set every component revision to null, distilled descriptors omit revision, - and the Nano/Super standard indexes name `Cosmos3OmniDiffusersPipeline`, - which the pinned Diffusers build does not export. Both distilled standard - indexes configure no safety checker. OpenMDW 1.1 is linked but not embedded; - its reviewed mutable web response is recorded with distribution-notice and - litigation-termination terms. Immutable descriptors, class resolution, - guardrail and remote heavy-hardware qualification, and physical macOS - evidence remain pending; all resource envelopes are estimate-only. -- [x] Evaluate gated Cosmos 1, Predict2, Predict2.5, and Transfer2.5 as - metadata-only candidates. Backend `7c6c29c` seals eight immutable - safetensors manifests: Cosmos 1 7B Text2World and Video2World; Predict2 2B - Text2Image plus 2B/14B Video2World; Predict2.5 2B post-trained; Transfer2.5 - 2B general; and its edge ControlNet. Their exact surfaces range from one - 942,523,208-byte ControlNet file through 12 files / 41,564,530,104 bytes for - Cosmos 1 Video2World. Component totals, canonical manifest digests, branch - revisions, inaccessible model-index Git blob identities, and the pinned - Diffusers source contracts are recorded without downloading weights. - - The source recipes remain evidence only: Cosmos 1 uses 704x1280 / 121 frames - / 36 steps / 30 fps; Predict2 uses 704x1280 / 93 frames / 35 steps / 16 fps; - Predict2.5 uses 93 frames / 36 steps / 16 fps; Transfer2.5 edge control uses - 93-frame chunks, 36 steps, guidance 3, and control scale 1. All repositories - require click-through license acceptance before file bodies resolve. The - receipt independently hashes the January, April, and September 2025 NVIDIA - Open Model License prompts and preserves their license/notice, `Built on - NVIDIA Cosmos`, Trustworthy AI, and safety-guardrail obligations. No runtime - or download surface was added. License acceptance, gated component-index and - linked Trustworthy AI review, remote heavy execution, and physical macOS - evidence remain pending; resource envelopes are estimate-only. -- [x] Evaluate Kandinsky 5 Video without admitting a remote-heavy runtime. - Backend `08e2550` seals all ten public official Diffusers snapshots at - immutable revisions: Pro T2V/I2V plus Lite SFT, no-CFG, distilled-16-step, - and pretrain variants for both 5-second and 10-second generation. Every - selected artifact is safetensors-only and MIT-declared. The exact selected - surfaces are 8 files / 23,854,029,536 bytes for each Lite snapshot, - 8 files / 62,666,834,400 bytes for Pro T2V, and 8 files / - 96,526,404,752 bytes for Pro I2V; component hashes, canonical manifest - digests, model indexes, transformer configurations, and the shared - 19,280,899,008-byte text-encoder/CLIP/VAE partition are recorded without - downloading weights. - - Pinned source contracts and recipes are sealed for 512x768 Lite generation, - 121-frame 5-second and 241-frame 10-second runs at 24 fps, guidance 1 for - no-CFG/distilled variants, and the 16-step distilled schedule. Pro T2V's - documented 768x1024 recipe and required FlexAttention/compile/offload setup - are evidence only. The pinned video guide's nominal image-to-video example - incorrectly constructs the T2V pipeline and never supplies its loaded image; - the pipeline source instead requires `Kandinsky5I2VPipeline` and an `image` - argument. No runtime or download entry was admitted. Corrected upstream - recipe evidence, remote heavy-hardware execution, and physical macOS proof - remain pending; resource envelopes are estimate-only and no media was - generated. -- [x] Evaluate Kandinsky 2.1 and keep its composite runtime fail-closed. - Backend `c04f151` seals the public decoder, shared prior, and inpaint decoder - at three distinct immutable revisions. Their selected safetensors-only - surfaces are respectively 3 files / 7,428,873,150 bytes, 3 files / - 5,798,697,734 bytes, and 3 files / 7,428,942,270 bytes; duplicate legacy - `.bin` weights are excluded. Canonical inventory digests, bounded - safetensors headers, immutable metadata, all seven package pipeline sources, - and the upstream Apache-2.0 license receipt are recorded without downloading - full weights. - - No runtime, download, capability, graph, or client surface was admitted. The - combined package loader copies the decoder's immutable `revision` to the - connected prior repository, where that distinct commit does not exist; its - download path instead follows the prior's moving default branch. The prior - executes before the decoder-only legacy callback and has neither a callback - nor interrupt flag, so cancellation does not cover the full job. Missing - backend composite revision binding and bounds, absent output safety checks, - model-snapshot license-file clarification, remote execution, live output - review, and physical macOS evidence remain independent gates. -- [x] Evaluate Kandinsky 2.2 without weakening composite or serialization - policy. Backend `75bb0ac` seals all five official versioned repositories: - decoder, prior, inpaint decoder, depth ControlNet, and decoder refiner. The - first three expose exact safetensors surfaces of 2 files / 5,283,689,948 - bytes, 3 files / 10,573,556,608 bytes, and 2 files / 5,283,759,068 bytes. - Immutable metadata, six unique bounded safetensors headers, all nine package - pipeline sources, and an upstream Apache-2.0 receipt are recorded without - downloading full weights. - - Version 2.2 improves the execution contract: its combined text-to-image, - image-to-image, and inpaint pipelines expose separate modern callbacks for - the prior and decoder, so both denoising stages can be aborted. Admission is - nevertheless closed because the connected loader still reuses the primary - repository revision on the distinct prior repository, or downloads the - prior's moving branch. The official depth-ControlNet and refiner snapshots - contain only legacy pickle `.bin` weights; the refiner also names the 2.1 - pipeline class and has no model card or declared license. Backend two-stage - assembly/bounds, safe auxiliary artifacts, output guardrails, snapshot - license clarification, remote output review, and physical macOS evidence - remain pending. No runtime, download, graph, capability, client, or media - surface was added. -- [x] Admit Kandinsky 3 as a bounded single-stage Expert workflow. Backend - `b85b073` and client `3007bf3` expose the exact public snapshot - `kandinsky-community/kandinsky-3@bf79e6c219da8a94abb50235fdc4567eb8fb4632` - for text-to-image and single-image editing through the generic Diffusers - image facade. The repository is ungated, contains no Python, requires no - remote code, and exposes seven fp16 safetensors files totaling - 28,390,829,958 bytes. Its canonical weight inventory, immutable metadata, - pinned package pipeline/image-to-image/UNet source hashes, and upstream - Apache-2.0 license receipt are sealed in - `data/kandinsky-3-artifact-review.json`. The model snapshot declares - Apache-2.0 in its card but contains no license file, so that clarification - remains explicit. - - The backend-owned contract fixes both routes to the package's 1024px path, - 25 recommended steps, guidance 3, fp16 weights, at most 128 prompt tokens, - and model or sequential CPU offload. Image editing accepts exactly one - 1,048,576-pixel source and uses the documented example strength 0.75. The - package hardcodes its 128-token encoder path, so the generic sequence control - is hidden while the backend still applies that adapter-specific default and - bound. Both package routes expose the modern step callback used by MoDiff's - cancellation contract. The package has no safety checker, and the resource - envelope remains estimate-only, so Auto and Gallery are disabled and live - execution remains unqualified. - - The expanded Transformers/Diffusers symbol surface passed locked clean-base - install validation, fresh-process activation, the finite CLIP+PEFT workload, - rollback, and a second clean-base process on Linux x86-64 at profile digest - `sha256:b490f3012dbf1b01e400dc5284af1630b0fea738ce92643a7c8fe3dca0e4caca`. - The bounded 1,554-byte evidence has SHA-256 - `d05812d8c95bd1f0f0ef770d117ff7da89c10e0c1e9a15946901ca8f2e36bb61` - and retained no managed state. Two canonical graphs bring the deterministic - catalog to 123 supported workflows. The complete backend overlay passes - 1,577 tests, 3,443 subtests, and three platform skips; Ruff E9/F, - deterministic workflow verification, and the complete client gate also - pass. The intentional client surface measures 530,549 compressed JavaScript - bytes under the 531,456-byte ceiling. The exact app-managed snapshot download - was accepted only after live free-space and aggregate queue-reservation - preflight and remains queued/in progress; no older cache snapshot was - removed. Remote real-weight memory/output safety/quality review, - model-snapshot license-file clarification, and physical macOS execution - remain pending independently, and no media has been generated. -- [x] Evaluate Kolors and keep its custom model license fail-closed. Backend - `3788fe8` seals the exact public - `Kwai-Kolors/Kolors-diffusers@7e091c75199e910a26cd1b51ed52c28de5db3711` - snapshot. It is ungated, contains no repository Python, requires no remote - code, and exposes a five-file / 17,813,668,046-byte fp16 safetensors - partition. The canonical inventory, immutable metadata, and pinned - package-owned Kolors text-to-image, image-to-image, ChatGLM encoder, - tokenizer, and output source hashes are sealed in - `data/kolors-artifact-review.json` without fetching weight bytes. - - The source review records both 1024px routes, the package's 50-step, - guidance-5, 256-token defaults, image-edit strength 0.3, modern callbacks, - interrupt flag, and CPU-offload sequence. It also records the missing - package bounds for step count, input pixels, and output pixels and the absent - safety checker. Those gaps require backend bounds and remote output review, - but they are not the primary admission blocker. - - The immutable model card carries an Apache-2.0 tag and describes the code as - Apache-2.0, while the same snapshot contains a distinct 14,920-byte - `MODEL_LICENSE`. That model agreement purports to take effect on use or - access, requires source/license and enforceable restriction propagation, - prohibits using the model or its outputs to improve other large models, and - requires separate authorization for cloud vendors or licensees over 100 - million monthly users. The README separately requests commercial - registration. No task-scoped product acceptance, commercial registration, - or legal approval was supplied, so no runtime/download catalog, capability, - graph, client, Auto, or Gallery surface was added. In particular, the model - was not submitted to the app download queue. The five focused review tests - and complete 1,582-test backend overlay with 3,443 subtests and three - platform skips pass; remote heavy-hardware review and physical macOS - execution remain pending independently. -- [x] Evaluate classic Latent Diffusion and keep legacy serialization and - cancellation gaps fail-closed. Backend `79db3b8` seals the exact public - `CompVis/ldm-text2im-large-256@30de525ca11a880baea4962827fb6cb0bb268955` - snapshot, its three-file / 6,152,286,891-byte required weight inventory, - immutable metadata hashes, and pinned package-owned pipeline/encoder source - identities in `data/latent-diffusion-artifact-review.json` without fetching - any model weight bytes. - - All three required model components are legacy PyTorch pickle `.bin` - artifacts, and the immutable repository provides no safetensors partition. - The static Hub scanner's typical-Torch import result does not make executable - pickle deserialization admissible under MoDiff's managed artifact policy. - The package pipeline also exposes neither a denoising-step callback nor an - interrupt flag, has no safety checker, and does not bound steps, output sides, - or output pixels. The model card declares Apache-2.0 but the exact snapshot - contains no license file. Consequently no runtime/download catalog, - capability, graph, client, Auto, or Gallery surface was added, and the model - was not submitted to the app download queue. Five focused review tests pass; - safe official artifacts, cooperative cancellation, backend-owned resource - bounds, immutable license receipt, remote execution/output review, and - physical macOS evidence remain independent gates. -- [x] Evaluate LEDITS++ as a package transform over the already-managed Stable - Diffusion bases and keep its multi-stage lifecycle fail-closed. Backend - `7bddd15` binds the package-owned SD 1.5 and SDXL edit classes to MoDiff's - existing exact base revisions, their already-recorded OpenRAIL terms, and - the pinned Diffusers source hashes in `data/ledits-pp-source-review.json`. - LEDITS++ is not a distinct model artifact, so it requires zero new weight - bytes and no family-specific app download was submitted. - - Both routes require an inversion call followed by a separate edit call. The - edit loop exposes a modern callback, but the mandatory inversion loop exposes - neither a callback nor an interrupt flag, so the complete job cannot satisfy - MoDiff's cooperative cancellation contract. Inversion also stores request - latents/noise state on the pipeline instance; a shared generic facade would - need an explicit isolation and lifecycle contract. The package does not - bound inversion steps, image count, prompt count, dimensions, or input - pixels; the SDXL route has no safety checker; and current package - documentation warns that perfect inversion is no longer guaranteed. No - runtime/download catalog, capability, graph, client, Auto, or Gallery surface - was added. Five focused review tests pass; cancellation, request isolation, - backend-owned bounds, generic multi-prompt editing, SDXL output guardrails, - remote output review, and physical macOS evidence remain independent gates. -- [x] Admit LongCat Image generation and single-image editing without claiming - live execution. Backend `212997c` and client `d1e5d7e` expose the exact - public snapshots - `meituan-longcat/LongCat-Image@d2ea50b79a930074c37b9b97ce45e3b2ea8cf4d8` - and - `meituan-longcat/LongCat-Image-Edit@7b54ef423aa7854be7861600024be5c56ab7875a`. - Each selected partition contains seven safetensors files and approximately - 29.29 GB of weights; the immutable identities, repository metadata, pinned - package pipeline/transformer/output sources, Transformers symbol contract, - and upstream Apache-2.0 receipt are sealed in - `data/longcat-image-artifact-review.json`. The model cards declare - Apache-2.0 while both exact model snapshots omit the license file, so - snapshot-specific clarification remains explicit. - - The two Expert-only routes use distinct immutable transformers while sharing - the text encoder and VAE identities. Generation is bounded to 512-2048px - sides in 16px increments, at most 1,048,576 output pixels and 50 steps, and - disables the package's autoregressive prompt rewrite. Editing accepts exactly - one at-most-1,048,576-pixel source between 1:4 and 4:1 aspect ratio and caps - the package-derived rounded output at 1,088,000 pixels and 50 steps. Both - package loops expose an interrupt flag checked per denoising step, but no - safety checker; Auto and Gallery therefore remain disabled pending remote - output review. - - The expanded optional-runtime surface passed clean-base locked installation, - validation, fresh-process activation, a finite CLIP+PEFT workload, rollback, - and clean restoration on Linux x86-64 at profile digest - `sha256:76e4f0c1e4389bcefa0958c813ab1cbf3e745051b7435ec50df4d374f327520b`. - The bounded 1,553-byte evidence has SHA-256 - `e2a73cd9fc7320ac84bb12a31efd3277ea3d3474591fed2a2dad2d7f787214fe` - and retained no managed state. Two canonical graphs bring the deterministic - catalog to 125 supported workflows. The complete backend overlay passes - 1,598 tests, 3,469 subtests, and three platform skips; Ruff E9/F, - deterministic workflow verification, and the complete client gate also - pass. The intentional client surface measures 530,610 compressed JavaScript - bytes under the 531,456-byte ceiling. - - Both exact app-managed downloads were accepted only after per-snapshot and - aggregate queue-reservation checks: 643,626,504,192 free bytes covered the - existing 438,331,905,395-byte queue reservation, both new reservations of - 29,327,729,635 and 29,322,429,940 bytes, and the 68,719,476,736-byte safety - reserve with 77,924,962,486 bytes of headroom. They remain queued/in progress; - no older model was deleted and no media has been generated. Remote real-weight - memory/output safety/quality review, model-snapshot license-file - clarification, and physical macOS execution remain pending independently. -- [x] Admit Lumina Next and Lumina Image 2.0 text-to-image without claiming - live execution. Backend `faad48b` and client `5b2f3db` expose the exact - public snapshots - `Alpha-VLLM/Lumina-Next-SFT-diffusers@0ee5ec90043acf5cb41fe96274af36eb7fad8d95` - and - `Alpha-VLLM/Lumina-Image-2.0@53504abd8178b30685b6c4c7a4cd181ff78b73e9`. - Their four-file / 8,856,869,956-byte and six-file / 21,231,830,092-byte - safetensors partitions, immutable repository/config identities, package - pipeline and transformer hashes, Gemma symbol contract, upstream MIT and - Apache-2.0 receipts, and estimate-only resource envelopes are sealed in - `data/lumina-image-artifact-review.json`. Both model cards declare - Apache-2.0 while their exact model snapshots omit a license file, so - snapshot-specific clarification remains explicit. - - Both Expert-only routes are bounded to 512-2048px sides in 16px increments, - at most 1,048,576 output pixels, guidance 4, and 256 prompt tokens. Lumina - Next defaults to 30 steps, caps at 50, and disables optional caption - cleaning. Lumina 2.0 uses its official 50-step recipe with CFG truncation - fixed to 0.25 and normalization enabled. Its app/download contract contains - an exact 17-file allowlist that excludes the two root `.pth` pickle artifacts - and the demo asset. Both denoising loops retain the generic per-step callback, - but neither package has a safety checker; Auto and Gallery remain disabled. - - The expanded optional-runtime surface passed clean-base locked installation, - validation, fresh-process activation, a finite CLIP+PEFT workload, rollback, - and clean restoration on Linux x86-64 at profile digest - `sha256:db3485f5c9293e1cb76aac5128dd87f0089f091a9075fdd09fe541ae4132dff0`. - The bounded 1,552-byte evidence has SHA-256 - `049a0563a60756a06397699ec2531433aed18850e70613f3e7969222b67d6875` - and retained no managed state. Two canonical graphs bring the deterministic - catalog to 127 supported workflows. The complete backend overlay passes - 1,604 tests, 3,495 subtests, and three platform skips; Ruff E9/F, - deterministic workflow verification, and the complete client gate also - pass. The intentional client surface measures 530,677 compressed JavaScript - bytes under the 531,456-byte ceiling. - - Both exact downloads were submitted through the app only after aggregate - reservation preflight. At submission, 625,606,197,248 free bytes covered the - existing 487,559,232,373-byte queue reservation, the new 8,878,690,722-byte - and 21,253,711,141-byte reservations, and the 68,719,476,736-byte safety - reserve with 39,195,086,276 bytes of headroom. Fresh exact app plans on - 2026-08-14 now report all 16 Lumina Next files / 8,878,690,722 selected bytes - and all 17 Lumina Image 2.0 files / 21,253,711,141 selected bytes complete at - their immutable revisions. Both cache entries are installed, complete, and - repair-free. No older model was deleted and no media has been generated. - Remote real-weight memory/output safety/quality review, model-snapshot - license-file clarification, and physical macOS execution remain pending - independently. -- [x] Admit OmniGen v1 text generation, single-image editing, and ordered - multi-reference editing without claiming live execution. Backend `11d5c16` - and client `86500ec` expose the exact public snapshot - `Shitao/OmniGen-v1-diffusers@016e2f61d12a98303f6bbdf122687694d7984268`. - Its two-file / 8,085,311,084-byte safetensors partition, complete - 8,088,956,424-byte snapshot plan, immutable repository/config identities, - pinned package pipeline/processor/transformer hashes, Llama tokenizer symbol - contract, upstream MIT receipt, and estimate-only resource envelope are - sealed in `data/omnigen-artifact-review.json`. The model card declares MIT - while the exact model snapshot omits a license file, so snapshot-specific - clarification remains explicit. - - All three Expert-only routes are bounded to 512-2048px sides in 16px - increments, at most 1,048,576 output pixels, 50 steps, and text guidance - 2.5. Conditioned routes use image guidance 1.6 and accept at most three - references totaling 3,145,728 pixels; the package independently preprocesses - each input to a maximum 1024px side. MoDiff retains references as an ordered - list, generates the package's continuous one-based - `<|image_N|>` placeholders, rejects user-supplied reserved - placeholder syntax, and maps the generic image-guidance field to the - package's `img_guidance_scale` argument. The denoising loop retains the - generic per-step callback but does not read its declared interrupt flag, and - the package has no safety checker; Auto and Gallery remain disabled. - - The expanded optional-runtime surface passed clean-base locked installation, - validation, fresh-process activation, a finite CLIP+PEFT workload, rollback, - and clean restoration on Linux x86-64 at profile digest - `sha256:7717741eb6fed3c8fbb9645fe13fd867d0e58ad06b06fe9009187c38ae840d42`. - The bounded 1,553-byte evidence has SHA-256 - `39f6fab8538aec4e6a1aadd07edb2485d69f2e876490e44111e1f786475dfce2` - and retained no managed state. Three canonical graphs bring the deterministic - catalog to 130 supported workflows. The complete backend overlay passes - 1,610 tests, 3,525 subtests, and three platform skips; Ruff E9/F, - deterministic workflow verification, and the complete client gate also - pass. The intentional client surface measures 530,729 compressed JavaScript - bytes under the 531,456-byte ceiling. - - The exact snapshot was submitted through the app only after aggregate - reservation preflight. At submission, 612,865,241,088 free bytes covered the - existing 495,504,680,091-byte queue reservation, the new - 8,088,956,424-byte reservation, and the 68,719,476,736-byte safety reserve - with 40,552,127,837 bytes of headroom. A fresh exact app plan on 2026-08-14 - now reports all 11 files / 8,088,956,424 selected bytes complete at the - immutable revision; its cache entry is installed, complete, and repair-free. - No older model was deleted and no media has been generated. Remote - real-weight memory/output safety/quality review, model-snapshot license-file - clarification, and physical macOS execution remain pending independently. -- [x] Admit Ovis Image 7B text-to-image without claiming live execution. - Backend `df2fa98` and client `ab47b67` expose the exact public Apache-2.0 - snapshot - `ATH-MaaS/Ovis-Image-7B@41be1c5821a92c970d63d7eb595a2fd3fe32b22e`. - Its selected 21-file Diffusers snapshot contains five safetensors files / - 21,790,968,814 weight bytes and totals 21,806,937,901 download bytes. The - allowlist includes the immutable LICENSE and NOTICE while excluding both - duplicate root-native checkpoints and the complete bundled `Ovis2.5-2B/` - subtree, including its repository Python. Exact weight identities, - repository/config hashes, pinned package pipeline/transformer hashes, Qwen - runtime symbols, terms receipts, and estimate-only resource envelope are - sealed in `data/ovis-image-artifact-review.json`; remote-code trust is never - enabled. - - The Expert-only route is bounded to 512-2048px sides in 16px increments, at - most 1,048,576 output pixels, 50 steps, guidance 5, and 256 prompt tokens. - It retains the package's per-step callback, denoising-loop interrupt check, - negative prompt, and text-encoder/transformer/VAE CPU-offload sequence. The - package has no safety checker, so Auto and Gallery remain disabled pending - remote output review. - - The expanded optional-runtime surface passed clean-base locked installation, - validation, fresh-process activation, a finite CLIP+PEFT workload, rollback, - and clean restoration on Linux x86-64 at profile digest - `sha256:64d2b473aa9d8dae0472d354468e05982f0f78f23ccb2cc7e7e335914f53c3ce`. - The bounded 1,554-byte evidence has SHA-256 - `c4ee928c7be0509c1e49507e8d3b93a959f1a99bfcd75bdef070d8c9eaee14e8` - and retained no managed state. One canonical graph brings the deterministic - catalog to 131 supported workflows. The complete backend overlay passes - 1,616 tests, 3,542 subtests, and three platform skips; Ruff E9/F, package - compatibility, portable preflight, shell syntax, deterministic workflow - verification, and the complete client check also pass. The intentional - client surface measures 530,754 compressed JavaScript bytes under the - 531,456-byte ceiling. - - The exact safe selection was submitted through the app only after aggregate - reservation preflight. At submission, 590,321,881,088 free bytes covered the - existing 489,903,040,881-byte queue reservation, the new - 21,806,937,901-byte reservation, and the 68,719,476,736-byte safety reserve - with 9,892,425,570 bytes of headroom. A fresh exact app plan on 2026-08-14 - now reports all 21 files / 21,806,937,901 selected bytes complete at the - immutable revision; its cache entry is installed, complete, and repair-free. - No older model was deleted and no media has been generated. Remote - real-weight memory/output safety/quality review and physical macOS execution - remain pending independently. -- [x] Admit PRX 512 SFT text-to-image without claiming live execution. Backend - `012b101` and client `9c320a4` expose the exact public snapshot - `Photoroom/prx-512-t2i-sft@2996423bc26e8eaca48774fac1797484214dfea0`. - Its complete 19-file / 15,514,188,109-byte app selection contains five - safetensors files / 15,475,492,212 weight bytes and no repository Python. - Exact weight identities, immutable repository/config hashes, pinned package - pipeline/transformer hashes, and an estimate-only resource envelope are - sealed in `data/prx-artifact-review.json`; remote-code trust is never - enabled. The bundled Apache-2.0 LICENSE and NOTICE are retained, and the - NOTICE's incorporated T5-Gemma terms and prohibited-use policy remain - explicit in both the artifact receipt and the Expert surface. - - The Expert-only route uses the native 512px SFT recipe and is bounded to the - package's 352-704px aspect bins in 32px increments, at most 262,144 output - pixels, 28 steps, guidance 5, and 256 prompt tokens. The generic Studio token - limit maps explicitly to PRX's `tokenizer_max_length` argument. The package - exposes a per-step callback but no denoising-loop interrupt flag or safety - checker, so stop requests fail closed by raising at a step boundary while - Auto and Gallery remain disabled pending remote output review. - - The expanded PRX/T5-Gemma optional-runtime surface passed clean-base locked - installation, validation, fresh-process activation, a finite CLIP+PEFT - workload, rollback, and clean restoration on Linux x86-64 at profile digest - `sha256:1705482bef0b94433b5380f71c0ed9a1e6ccb97427e1b8b4bb9238ad1547d0e3`. - The bounded 1,553-byte evidence has SHA-256 - `996009d1a2b09be13115a637b8fffa390e43679ece8d80302e9d5e77b4e4c34c` - and retained no managed state. One canonical graph brings the deterministic - catalog to 132 supported workflows. The complete backend overlay passes - 1,621 tests, 3,559 subtests, and three platform skips; Ruff E9/F, package - compatibility, portable preflight, shell syntax, deterministic workflow - verification, and the complete client check also pass. The intentional - client surface measures 530,791 compressed JavaScript bytes under the - 531,456-byte ceiling. - - The exact snapshot was submitted through the app only after aggregate - reservation preflight. At submission, 567,349,919,744 free bytes covered the - existing 474,119,351,599-byte queue reservation, the new - 15,514,188,109-byte reservation, and the 68,719,476,736-byte safety reserve - with 8,996,903,300 bytes of headroom. A fresh exact app plan on 2026-08-14 - now reports all 19 files / 15,514,188,109 selected bytes complete at the - immutable revision; its cache entry is installed, complete, and repair-free. - No older model was deleted and no media has been generated. Remote - real-weight memory/output safety/quality review and physical macOS execution - remain pending independently. -- [x] Admit Nucleus Image 17B MoE text-to-image without claiming live - execution. Backend `86cc756` and client `8b4f002` expose the exact public - snapshot - `NucleusAI/Nucleus-Image@5e963db4fd0a65c7e4faf53ca2d4eca567c4dcfa`. - Its complete 38-file / 51,656,728,957-byte Python-free snapshot contains 12 - safetensors files / 51,633,577,862 weight bytes. Exact weight identities, - immutable repository/config hashes, pinned package pipeline/transformer - hashes, Qwen3-VL runtime symbols, and an estimate-only resource envelope are - sealed in `data/nucleus-image-artifact-review.json`; remote-code trust is - never enabled. The model card declares Apache-2.0, but the exact immutable - snapshot contains no LICENSE or NOTICE file, so owner clarification remains - a separate gate. - - The Expert-only route uses the native 1024px, 50-step, guidance-4 recipe and - admits all seven official aspect buckets through 768-1344px sides in 32px - increments, at most 1,060,864 output pixels, and 1024 prompt tokens. It - retains the package's per-step callback, denoising-loop interrupt check, - negative prompt, and text-encoder/transformer/VAE CPU-offload sequence. This - is a base checkpoint without post-training or a safety checker, so Auto and - Gallery remain disabled pending remote output and policy review. - - The expanded Nucleus/Qwen3-VL optional-runtime surface passed clean-base - locked installation, validation, fresh-process activation, a finite - CLIP+PEFT workload, rollback, and clean restoration on Linux x86-64 at - profile digest - `sha256:1cf278aa4e690212b0a50b6b1e30ad77e86fb9af6fc13dbb89b37139a35c1c45`. - The bounded 1,554-byte evidence has SHA-256 - `5213c07a29232ed6eef753a6114ed5667f03d38b7a1eabb1443d89204ee1a349` - and retained no managed state. One canonical graph brings the deterministic - catalog to 133 supported workflows. The complete backend overlay passes - 1,626 tests, 3,576 subtests, and three platform skips; Ruff E9/F, package - compatibility, portable preflight, shell syntax, deterministic workflow - verification, and the complete client check also pass. The intentional - client surface measures 530,833 compressed JavaScript bytes under the - 531,456-byte ceiling. - - The exact snapshot was not submitted after the required app-only aggregate - reservation preflight. At the latest check, 535,900,401,664 free bytes minus - the existing 451,832,786,377-byte queue reservation and the - 68,719,476,736-byte safety reserve left 15,348,138,551 bytes before this - snapshot, making the new reservation short by 36,308,590,406 bytes. No - direct weight download was performed and no older model was deleted. Owner - license clarification, app storage capacity, remote real-weight - memory/output safety/quality review, and physical macOS execution remain - pending independently. -- [x] Admit the remaining official Wan 2.1 14B Modular-compatible repository - variants without claiming live execution. Backend `e4c2385` adds exact - repository-scoped Models Loader aliases for T2V-14B at - `38ec498cb3208fb688890f8cc7e94ede2cbd7f68` and I2V-14B-720P at - `eb849f76dfa246545b65774a9e25943ee69b3fa3`, alongside the already reviewed - I2V-14B-480P and FLF-14B-720P snapshots. The four exact safetensors surfaces - are respectively 18 files / 80,385,341,396 bytes, 21 files / - 90,075,953,948 bytes for each I2V variant, and 21 files / - 90,077,762,204 bytes for FLF. Canonical inventory digests, all transformer - shard hashes, standard index/config hashes, immutable revisions, source - recipes, and estimate-only resource bounds are sealed without downloading - weights. - - The admission remains generic and fail-closed: T2V-14B must resolve from - `WanPipeline` to the pinned `WanModularPipeline`; both I2V repositories must - resolve from `WanImageToVideoPipeline` to the pinned - `WanImage2VideoModularPipeline`; and FLF retains its distinct processor, - positional-embedding, and last-image contract. I2V-480P and I2V-720P can - satisfy only image-to-video routing, while the FLF artifact can satisfy only - first/last-frame routing. Wrong repository, workflow, revision, component - type, or index class fails before block initialization. No high-level mode, - client model-name branch, Auto path, template, Gallery asset, generated - media, or live qualification claim was added. The clean optional overlay - passes the 100-test focused matrix with 175 subtests; remote heavy-hardware - execution and physical macOS evidence remain pending independently. -- [ ] Evaluate other heavy video families. - - [x] **Wan Animate 2 pinned Modular contract closure:** backend `d31d5b6` - and client `fc67a7f` register the two package exports that were absent from - the reviewed contract snapshot at Diffusers - `bb56997d4b7e87f0743f26a612f49ec4e7ce7213`: - `WanAnimate2ModularPipeline` and - `WanAnimate2DistilledModularPipeline`. Both are Expert-visible, - contract-only video records with one generic `character_animate` workflow, - required prompt/image/driving-video inputs, video output, and distinct base - versus distilled denoise steps. The generated truth now seals all 33 - exported Modular classes and 93 workflows; a pinned-overlay regression - requires the executable and contract-only registries to equal that exact - exported-class set. - - The snapshot preserves the actual composed schemas: although the upstream - distilled prose describes ten steps, both pinned classes currently publish - a 40-step default. No default repository, artifact admission, runnable - mode, Auto/template/Gallery surface, or live qualification was inferred. - The focused optional-overlay gate passes 45 tests and 664 subtests; the - complete overlay passes 1,668 tests and 3,591 subtests with three platform - skips, and the clean base passes 1,631 tests and 3,279 subtests with 40 - optional-runtime skips. Ruff, package compatibility, snapshot verification, - the complete client check, the 533,100 / 533,504-byte gzip budget, and the - 107-case mocked Studio browser sweep pass. No weights, models, or media were - downloaded, deleted, or generated for this slice. Artifact admission, - real-weight remote execution, output review, and physical macOS evidence - remain pending independently. - - [x] **Wan Animate 2 immutable artifact/source review:** backend `2b86e63` - seals the public, Python-free base - `Wan-AI/Wan2.2-Animate-2-14B-Diffusers@7d48412d7b903ff3a89f4f5a960d99e1899605a1` - and distilled - `Wan-AI/Wan2.2-Animate-2-14B-Distilled-Diffusers@59e4141466bcb1bf9733eca1bc78be6891c9fbdf` - snapshots. Each has 31 files and nine BF16 safetensors weights totaling - 45,920,934,868 bytes; five shared image/text/VAE files account for - 13,131,039,324 bytes, while the four 32,789,895,544-byte transformer - shards have distinct exact hashes. Both cards declare Apache-2.0 only in - metadata, include no license file, and provide no detailed usage or safety - guidance. - - Admission remains contract-only for exact source reasons. Both standard - indexes name `WanAnimate2Pipeline`, which the pinned package does not - export. The matching Modular indexes name the reviewed base/distilled - classes but leave four component revisions null and point both scheduler - and transformer at mutable `refs/pr/2` or `refs/pr/1` references. The - package generation path additionally requires compiled flex attention at - video resolution, carries decoded tail frames across 81-frame segments, - and retains the unresolved distilled ten-step prose versus 40-step composed - schema mismatch. No runtime/download catalog, repository default, runnable - mode, or client change was added. The focused gate passes 13 tests, one - optional-runtime skip, and 44 subtests; the complete clean-base backend gate - passes 1,636 tests, 40 skips, and 3,279 subtests with Ruff, 66-package - compatibility, shell/diff checks, and portable preflight green. No model - weights or media were downloaded, deleted, or generated. Immutable - component normalization, remote compiled execution/output review, and - physical macOS evidence remain pending. -- [x] **P6.61 Bound AuraFlow app download selection:** backend `ed3982a` - binds `fal/AuraFlow-v0.3@2cd8588f04c886002be4571697d84654a50e3af3` - to the exact 18-file runnable fp16 selection used by the admitted - `AuraFlowPipeline`. The selection contains the four reviewed safetensors - weights, their exact variant index, package configs/tokenizer metadata, and - immutable license/card receipts. It excludes the duplicate native single - file, default text encoder, three-shard transformer, default VAE, and ComfyUI - workflow surfaces. - - A real app `GET /hf_download/plan` over that explicit selection reports - 18 files / 16,837,479,394 bytes instead of the repository-wide 26 files / - 65,964,186,183 bytes. Plan and POST regressions require the same capability - allowlist, and the artifact regression requires every reviewed fp16 weight - while excluding each duplicate weight surface. The focused app/artifact - matrix passes 88 tests and 46 subtests; pinned Ruff E9/F and the 66-package - compatibility check pass. The already-running repository-wide app task was - not interrupted, replaced, or deleted, and no second AuraFlow POST was made; - this bound governs future installs and repairs after the app restarts. -- [x] **P6.62 Bound Chroma app download selection:** backend `0922243` - binds `lodestones/Chroma1-HD@0e0c60ece1e82b17cb7f77342d765ba5024c40c0` - to its exact 18-file runnable Diffusers selection. The allowlist contains the - five reviewed safetensors weights, both shard indexes, component configs, - tokenizer, and immutable card receipt. It excludes the duplicate - 17,800,038,288-byte native single-file checkpoint plus the ComfyUI workflow - and demo images. - - A real app plan reports the selected 18 files / 27,493,360,428 bytes are - already complete, versus 45,300,383,271 bytes for the repository-wide tree. - Plan and POST regressions require the identical selection and the artifact - regression binds it to every reviewed component weight. The focused - app/artifact matrix passes 95 tests and 46 subtests; pinned Ruff E9/F and the - 66-package compatibility check pass. No new POST was necessary, and the - older complete repository snapshot and its duplicate bytes were preserved. -- [x] **P6.63 Bound Allegro, Latte, and Mochi app download selections:** backend - `cb3448d` binds all three admitted video routes to exact runnable Diffusers - file allowlists. Allegro selects 18 files / 25,293,916,977 bytes and excludes - both duplicate unsafe PyTorch text-encoder shards. Latte selects 18 files / - 23,615,823,652 bytes and excludes the legacy `.pt` checkpoint plus the - unreferenced temporal decoder. Mochi selects 21 files / 40,025,271,759 bytes - and preserves the indexed four-shard T5 plus BF16 transformer/VAE route while - excluding the flat-format duplicate, unindexed two-shard T5, FP32 - transformer/VAE, and demo asset. - - Real app plans prove those selections instead of the repository-wide - 44,370,333,435-byte Allegro, 28,238,181,756-byte Latte, and - 133,509,491,072-byte Mochi trees. Both app planning and POST admission derive - the same capability files; artifact tests require every reviewed weight and - reject every recorded duplicate/unsafe surface. The focused matrix passes - 117 tests and 49 subtests with pinned Ruff E9/F and 66-package compatibility - green. None fit beside the active app queue, so no POST was submitted and no - existing complete or partial cache entry was removed. -- [x] **P6.64 Bound Stable Audio and Stable Video app download selections:** - backend `8c96321` binds both existing admitted media routes to the runnable - component sets selected by their loaders. Stable Audio selects 19 files / - 5,348,079,831 bytes, retaining its safetensors projection model, text - encoder, transformer, VAE, tokenizer/config surface, license/card, and both - dataset attribution receipts. It excludes the duplicate original `.ckpt` - and single-file safetensors checkpoints, original model configs, and demo - image from the 15,680,736,700-byte repository tree. Stable Video Diffusion - selects 12 files / 4,509,218,296 bytes, matching the loader's exact `fp16` - variant for its image encoder, UNet, and VAE. It excludes all three default - full-precision component weights, the duplicate single-file checkpoint, and - demo image from the 18,313,865,678-byte repository tree. - - App plan and POST regressions require those identical capability-derived - selections, while the artifact regressions reject unsafe and duplicate - surfaces. The focused media, capability, app, and loader matrix passes 270 - tests, two environment skips, and 544 subtests; pinned Ruff E9/F, - 66-package compatibility, and diff checks pass. Fresh exact app plans report - 400,730,861,568 free bytes, 328,468,945,405 queued reservation bytes, and the - 68,719,476,736-byte safety reserve, so neither additional request fits beside - the active queue. No duplicate POST was submitted; the pre-existing - repository-wide app transfers were not interrupted and no older or partial - cache entry was deleted. -- [x] **P6.65 Bound AudioLDM2 and Shap-E app download selections:** backend - `56faa30` makes Model Manager use the same safe component surfaces already - enforced by both loaders. AudioLDM2 selects 28 files / 4,480,959,446 bytes, - including all seven safetensors component weights and their configs, - tokenizers, scheduler, feature extractor, model index, and card. It excludes - all seven co-published legacy `.bin` duplicates, totaling 4,475,112,301 - bytes, from the 8,956,071,747-byte repository tree. Shap-E selects 14 files / - 1,332,951,857 bytes for the exact fp16 prior, CLIP text encoder, pre-rename - safe renderer, tokenizer, scheduler, configs, index, and card. It excludes - the three legacy component `.bin` files plus the renamed unsafe-only renderer - directory, totaling 3,568,036,469 bytes, from the 4,900,988,326-byte tree. - - Plan and POST regressions bind both capability-derived allowlists, and focused - artifact tests match them to their explicit loaders while rejecting every - unsafe duplicate. The media, capability, app, and loader matrix passes 277 - tests, three optional-runtime skips, and 550 subtests; pinned Ruff E9/F, - 66-package compatibility, and diff checks pass. Fresh exact app plans report - every selected byte already complete with zero remaining bytes, so no POST or - deletion was necessary; the preserved full snapshots remain available for - preview regression testing. -- [x] **P6.66 Bound Marigold app download and loader serialization:** backend - `c841203` binds `prs-eth/marigold-depth-lcm-v1-0` to the exact 14-file / - 5,161,610,352-byte float32 safetensors component surface used by its generic - prediction-map route. The loader now explicitly requires safe serialization - instead of relying on the presence of preferred files. The app allowlist - retains the default text encoder, UNet, VAE, tokenizer, scheduler, configs, - index, and card while excluding six legacy `.bin` weights and three unused - fp16 duplicate variants: 10,320,895,232 bytes from the - 15,482,505,584-byte repository tree. - - App plan and POST regressions bind the capability-derived selection, while a - focused perception regression requires the loader's safe-serialization flag, - exact default-variant weights, and complete exclusion of legacy pickle - files. The capability, app, perception, and loader matrix passes 212 tests, - three optional-runtime skips, and 867 subtests; pinned Ruff E9/F, 66-package - compatibility, and diff checks pass. A fresh exact app plan reports every - selected byte already complete with zero remaining bytes, so no POST or - deletion occurred. The older extra variants and unfinished cache blobs were - preserved for regression testing. -- [x] **P6.67 Bound Whisper Tiny app download selection:** backend `4783400` - binds `openai/whisper-tiny@169d4a4341b33bc18d8881c4b69c2e104e1cc0af` - to one exact 13-file / 155,455,649-byte Transformers runtime surface. The - selection retains the safetensors model, processor/tokenizer data, - generation/config metadata, and immutable card/attribute receipts while - excluding the duplicate 151,095,027-byte PyTorch pickle, 151,048,591-byte - Flax checkpoint, and 151,253,960-byte TensorFlow checkpoint from the - 608,853,227-byte repository tree. This matches the existing generic ASR - loader's safetensors-only, no-remote-code boundary. - - App plan and POST regressions bind the capability-derived allowlist, and a - focused speech artifact regression rejects every alternate framework and - legacy pickle surface. The capability, app, and speech matrix passes 101 - tests and 80 subtests; pinned Ruff E9/F, 66-package compatibility, and diff - checks pass. A fresh exact app plan found all runtime bytes present and only - 21,225 bytes of card/attribute receipts remaining; with - 390,247,436,288 free bytes, 309,318,466,805 queued reservation bytes, and the - 68,719,476,736-byte safety reserve, the bounded completion fit and was - submitted through the app. It remains governed by the app's existing - transfer queue; no older model or alternate cached framework file was - deleted. -- [x] **P6.68 Bound unconditional-image app downloads and loader serialization:** - backend `42fa625` makes both small-model routes safetensors-only in the loader - and Model Manager. DDPM and DDIM share the exact six-file / - 143,025,496-byte `google/ddpm-cifar10-32` selection, excluding the - 143,101,489-byte legacy `.bin`, repository Python, and four demo images from - the 286,139,260-byte tree. The consistency-model route selects six files / - 1,183,678,409 bytes from `openai/diffusers-cd_imagenet64_l2`, excluding its - 1,183,833,415-byte legacy `.bin` duplicate from the 2,367,511,824-byte tree. - Both selections retain their safetensors model, scheduler/config surface, - index, and immutable card/attribute receipts. - - App plan and POST regressions bind both capability-derived allowlists; the - focused artifact regression requires the shared DDPM/DDIM identity, explicit - safe-serialization flags, code-free selection, and complete legacy-weight - exclusion. The capability, app, unconditional, and loader matrix passes 213 - tests, three optional-runtime skips, and 872 subtests; pinned Ruff E9/F, - 66-package compatibility, and diff checks pass. Independent fresh app plans - found the runtime weights complete and only 4,277 plus 11,427 receipt bytes - remaining; both exact fitting repairs were submitted concurrently through - the app and remain governed by its existing bounded queue. No older model, - unsafe duplicate, demo, or code file was deleted. -- [x] **P6.69 Bound LCM DreamShaper app download and loader serialization:** - backend `08c34f4` binds - `SimianLuo/LCM_Dreamshaper_v7@a85df6a8bd976cdd08b4fd8f3b73f229c9e54df5` - to the exact 17-file / 5,482,979,343-byte Diffusers runtime surface. The - selection retains the safety checker, text encoder, UNet, VAE, tokenizer, - scheduler, feature-extractor/config files, index, and immutable - card/attribute receipts. It excludes the duplicate root checkpoint, ONNX - exports, repository Python, and demo images: 7,710,367,026 bytes from the - 13,193,346,369-byte repository tree. The generic image loader now explicitly - requires safe serialization. - - App plan and POST regressions bind the capability-derived allowlist, while a - focused latent-image artifact regression requires the exact component - safetensors and rejects the duplicate checkpoint, ONNX, code, and demo - surfaces. The capability, app, latent-image, and loader matrix passes 212 - tests, three optional-runtime skips, and 871 subtests; pinned Ruff E9/F, - 66-package compatibility, and diff checks pass. A fresh exact app plan found - all runtime weights complete and only 5,070 receipt bytes remaining; with - 384,460,107,776 free bytes, 303,551,304,790 queued reservation bytes, and the - 68,719,476,736-byte safety reserve, the bounded completion fit and was - submitted through the app. It remains governed by the existing transfer - queue, and no older cached artifact was deleted. -- [x] **P6.70 Bound Sana 0.6B fp16 app download selection:** backend `982c1a3` - binds - `Efficient-Large-Model/Sana_600M_1024px_diffusers@28f3af7689de15f3883d5863059a2fca0aa9b829` - to the exact 17-file / 7,700,017,758-byte fp16 Diffusers runtime surface used - by its existing safetensors-only loader. The selection retains the fp16 text - encoder shards/index, transformer and VAE weights, tokenizer, scheduler, - configs, model index, license, card, and attribute receipts. It excludes the - default text-encoder aliases plus the full-precision transformer and default - VAE alias: 8,844,837,635 logical bytes from the 16,544,855,393-byte repository - tree. Existing identical-content aliases remain in cache. - - App plan and POST regressions bind the capability-derived allowlist, and a - focused Sana artifact regression matches the loader's exact fp16 variant and - rejects every default alias or legacy serialization surface. The capability, - app, Sana, and loader matrix passes 213 tests, three optional-runtime skips, - and 872 subtests; pinned Ruff E9/F, 66-package compatibility, and diff checks - pass. A fresh exact app plan reported zero remaining bytes, with - 382,775,398,400 free bytes, 302,075,919,129 queued reservation bytes, and the - 68,719,476,736-byte safety reserve intact, so no POST or deletion occurred. -- [x] **P6.71 Bound FLUX.2 Klein component download and loader serialization:** - backend `80f7369` binds - `black-forest-labs/FLUX.2-klein-4B@e7b7dc27f91deacad38e78976d1f2b499d76a294` - to the exact 21-file / 15,980,152,900-byte Diffusers component surface used - by its text, edit, multi-reference, inpaint, and outpaint routes. The - selection retains the text encoder, transformer, VAE, tokenizer, scheduler, - configs, model index, license, card, and attribute receipts while excluding - the duplicate 7,751,105,712-byte native checkpoint and 8,748,835 bytes of - demo images from the 23,740,007,447-byte repository tree. Both generic Klein - loaders now explicitly require safe serialization. - - App plan and POST regressions bind the capability-derived allowlist; the - focused FLUX artifact regression requires the exact component safetensors, - both loader flags, and complete native-checkpoint/demo exclusion. The - capability, app, FLUX, and loader matrix passes 213 tests, three - optional-runtime skips, and 875 subtests; pinned Ruff E9/F, 66-package - compatibility, and diff checks pass. A fresh exact app plan reported zero - remaining bytes, with 379,585,880,064 free bytes, 295,904,744,282 queued - reservation bytes, and the 68,719,476,736-byte safety reserve intact, so no - POST or deletion occurred. -- [x] **P6.72 Bound primary FLUX.1 component downloads and loader - serialization:** backend `274b158` binds schnell, dev, and Krea to the exact - component surfaces consumed by the generic text/edit/inpaint loaders. The - immutable selections contain 25 / 26 / 26 files and 33,725,928,351 / - 33,746,408,222 / 33,746,412,027 bytes respectively, retaining their text - encoders, transformers, VAEs, tokenizers, schedulers, configs, indexes, and - available rights receipts. They exclude the three 23.78 GB native - checkpoints, three root autoencoders, and demo images: 72,409,924,695 logical - bytes from the combined 173,628,673,295-byte repository trees. The shared - FLUX text, image-to-image, and inpaint loaders now explicitly require safe - serialization. - - App plan and POST regressions bind all three capability-derived allowlists; - the focused FLUX artifact regression requires each exact component surface, - the shared loader flags, and complete native-checkpoint/autoencoder/demo - exclusion. The capability, app, FLUX, and loader matrix passes 213 tests, - three optional-runtime skips, and 884 subtests; pinned Ruff E9/F, 66-package - compatibility, and diff checks pass. Three concurrent fresh exact app plans - reported zero remaining bytes, with 379,114,487,808 free bytes, - 295,904,744,282 queued reservation bytes, and the 68,719,476,736-byte safety - reserve intact, so no POST or deletion occurred. All excluded cached files - remain available for preview regression testing. -- [x] **P6.73 Bound conditioned FLUX.1 component downloads and loader - serialization:** backend `d39bfe2` binds Depth, Canny, Fill, and Kontext to - the exact component surfaces consumed by their generic control, inpaint, - outpaint, edit, and multi-reference routes. Depth/Canny each retain 28 files - and 43,685,183,091 / 43,685,182,867 bytes; Fill/Kontext each retain 26 files - and 33,916,013,136 / 33,746,413,969 bytes. The allowlists preserve the text - encoders, transformers, VAEs, tokenizers, schedulers, configs, indexes, and - rights receipts while excluding the four native checkpoints, four root - autoencoders, and Kontext teaser: 96,561,961,318 logical bytes from the - combined 251,594,754,381-byte repository trees. The shared control, Fill, - Kontext, and Kontext-inpaint loaders now explicitly require safe - serialization. - - App plan and POST regressions bind all four capability-derived allowlists; - the focused FLUX artifact regression requires each component surface, every - loader flag, and complete native-checkpoint/autoencoder/demo exclusion. The - capability, app, FLUX, server, and loader matrix passes 234 tests, three - optional-runtime skips, and 925 subtests; pinned Ruff E9/F, 66-package - compatibility, and diff checks pass. Four concurrent fresh exact app plans - reported zero remaining bytes, with 374,784,069,632 free bytes, - 295,904,744,282 queued reservation bytes, and the 68,719,476,736-byte safety - reserve intact, so no POST or deletion occurred. All excluded cached files - remain available for preview regression testing. -- [x] **P6.74 Bound the shared SD1.5 image/video download union and image-loader - serialization:** backend `896a723` binds the common immutable SD1.5 base to - the exact 21-file / 8,223,292,159-byte union required by both admitted loader - families. Generic image, edit, inpaint, ControlNet, and PAG routes use the - float32 safetensors components, while AnimateDiff and AnimateLCM explicitly - use the matching fp16 variants; both are retained alongside shared - tokenizer, scheduler, feature-extractor, configs, model index, card, and - attribute receipts. The allowlist excludes legacy pickle weights, the unused - non-EMA UNet, single-file checkpoints, and inference YAML: 39,036,647,490 - bytes from the 47,259,939,649-byte repository tree. All five generic image - adapters now explicitly require safe serialization; the two video loaders - already did so. - - App plan and POST regressions bind every SD1.5-backed capability to one - identical allowlist, while a focused shared-artifact regression requires - both precision variants and rejects all pickle, checkpoint, non-EMA, and - YAML surfaces. The capability, app, SD1.5, image-loader, and video-loader - matrix passes 325 tests, five optional-runtime skips, and 1,126 subtests; - pinned Ruff E9/F, 66-package compatibility, and diff checks pass. A fresh - exact app plan found the float32 files complete and 2,740,639,959 fp16 bytes - remaining; with 374,465,064,960 free bytes, 295,904,744,282 queued reservation - bytes, and the 68,719,476,736-byte safety reserve, the repair fit and was - submitted through the app. It remains governed by the existing transfer - queue; no older SD1.5 artifact was deleted. -- [x] **P6.75 Bound Z-Image component download and loader serialization:** - backend `8f96945` binds - `Tongyi-MAI/Z-Image-Turbo@f332072aa78be7aecdf3ee76d5c247082da564a6` - to the exact 21-file / 32,848,321,404-byte Diffusers component surface used - by its text-to-image, image-to-image, and inpaint-capable generic adapters. - The allowlist retains the text encoder, transformer, VAE, tokenizer, - scheduler, configs, indexes, card, and attribute receipt while excluding the - gallery PDF and ten documentation/showcase images: 51,345,993 bytes from the - 32,899,667,397-byte repository tree. All three Z-Image adapters now - explicitly require safe serialization. - - App plan and POST regressions bind the server capability to the exact - allowlist; a focused Z-Image artifact regression requires all sharded - safetensors indexes, excludes every asset and legacy serialization surface, - and verifies the three loader flags. The capability, app, Z-Image, server, - and loader matrix passes 232 tests, three optional-runtime skips, and 914 - subtests; pinned Ruff E9/F, 66-package compatibility, and diff checks pass. A - fresh exact app plan reported zero remaining bytes, with 370,478,370,816 free - bytes, 298,645,384,241 queued reservation bytes, and the 68,719,476,736-byte - safety reserve intact, so no POST or deletion occurred. The excluded cached - assets remain available for preview regression testing. -- [x] **P6.76 Bound PixArt Sigma component download selection:** backend - `a0fe20c` binds - `PixArt-alpha/PixArt-Sigma-XL-2-1024-MS@e102b3591cc82e97071b8b4cb90d834d0c487207` - to the exact 15-file / 21,828,231,839-byte Diffusers component surface used - by its existing safetensors-only generic image loader. The allowlist retains - the text encoder, transformer, VAE, tokenizer, scheduler, configs, indexes, - card, and attribute receipt while excluding the three documentation images: - 4,258,550 bytes from the 21,832,490,389-byte repository tree. - - App plan and POST regressions bind the capability-derived selection; a - focused PixArt artifact regression requires the exact component weights, - existing safe-serialization flag, and complete documentation-asset - exclusion. The capability, app, PixArt, and loader matrix passes 212 tests, - three optional-runtime skips, and 883 subtests; pinned Ruff E9/F, 66-package - compatibility, and diff checks pass. A fresh exact app plan reported zero - remaining bytes. Current free bytes were 366,990,446,592 against - 298,645,384,241 queued reservation bytes and the 68,719,476,736-byte safety - reserve, so the aggregate queue envelope was temporarily 374,414,385 bytes - over the limit while an existing transfer finalized. PixArt required no new - bytes; no POST, interruption, or deletion occurred, and the cached - documentation assets remain preserved. -- [x] **P6.77 Bound admitted Wan video component downloads and loader - serialization:** backend `e3fc376` binds the four currently cached admitted - Wan families to their exact Diffusers runtime surfaces. Wan 2.1 T2V/video - edit retains 21 files / 28,928,908,149 bytes; Wan 2.1 VACE retains 19 files / - 19,037,151,627 bytes; Wan 2.2 dual-expert I2V retains 43 files / - 126,202,571,522 bytes; and Wan 2.2 TI2V retains 22 files / 34,201,437,893 - bytes. Each allowlist includes the required text encoder, transformer(s), - VAE, tokenizer, scheduler, configs, indexes, card, and attribute receipt while - excluding repository documentation and example media: 15,892,213 bytes from - the combined 208,385,961,404-byte trees. VACE, T2V, video-to-video, TI2V, and - dual-expert I2V loads now all pass `use_safetensors=True` to both explicit - VAE and pipeline component loads. - - App plan and POST regressions bind all four capabilities to their exact - allowlists. Focused artifact and loader regressions require every indexed - component, exclude all asset/example and legacy serialization surfaces, and - exercise safetensors propagation through each distinct Wan loader. The Wan, - capability, app, server, and video-loader matrix passes 233 tests, two - optional-runtime skips, and 338 subtests; pinned Ruff E9/F, 66-package - compatibility, and diff checks pass. Four concurrent fresh app plans - reported zero remaining bytes. Current free bytes were 364,487,782,400 - against 298,429,691,965 queued reservation bytes and the - 68,719,476,736-byte safety reserve, so the active aggregate queue envelope - was temporarily 2,661,386,301 bytes over the limit while an existing transfer - progressed. No Wan bytes were needed; no POST, interruption, or deletion - occurred, and all cached documentation/example media remains preserved. -- [x] **P6.78 Bound JoyAI Image Edit app download selections:** backend - `c0c5944` binds both admitted immutable JoyAI repositories to their exact - safetensors component surfaces. Image Edit retains 38 files / - 50,347,541,462 bytes; Image Edit Plus retains 29 files / 50,338,627,834 - bytes. The allowlists preserve their Qwen3-VL processors/text encoders, - tokenizers, transformers, Wan VAEs, schedulers, configs, indexes, cards, and - attribute receipts while excluding seven test images from Edit and three - example images plus repository `inference.py` from Plus: 16,592,417 bytes - from the combined 100,702,761,713-byte trees. Both generic adapters already - explicitly require safe serialization. - - App plan and POST regressions bind both capability-derived selections; a - focused JoyAI artifact regression requires every indexed component, rejects - repository code, examples, tests, and legacy serialization surfaces, and - verifies both existing loader flags. The JoyAI, capability, app, and loader - matrix passes 220 tests, three optional-runtime skips, and 895 subtests; - pinned Ruff E9/F, 66-package compatibility, and diff checks pass. Concurrent - fresh app plans reported Edit complete with zero remaining bytes and Plus - with 50,315,694,156 selected bytes still outstanding while its pre-existing - repository-wide app job remained active. Current free bytes were - 363,886,616,576 against 297,828,399,222 queued reservation bytes and the - 68,719,476,736-byte safety reserve, leaving the active aggregate envelope - 2,661,259,382 bytes over the limit. The differently scoped active Plus job - was not joined, replaced, cancelled, or interrupted; no new POST or deletion - occurred. -- [ ] Evaluate large image/cascaded families and DiffusionGemma only on hardware - with sufficient RAM, VRAM, and disk. - - [x] **DiffusionGemma immutable source/artifact review:** backend `42b609e` - seals official ungated - `google/diffusiongemma-26B-A4B-it@f7f5b7f5fa82ffc52addd066915886d497f5517b`. - The repository contains no Python and requires no remote code. Its exact - bfloat16 surface is 11 safetensors shards / 51,647,701,024 weight bytes; - 22 files occupy 51,680,024,015 bytes in total. The Apache-2.0 model index - selects package-owned `DiffusionGemmaForBlockDiffusion`, `Gemma4Processor`, - `BlockRefinementScheduler`, and `DiffusionGemmaPipeline` classes. Exact - metadata, weight, pinned Diffusers source, and locked Transformers 5.14.1 - source hashes are recorded in - `data/diffusiongemma-artifact-review.json`; a no-weight API probe passed. - - The reviewed recipe keeps the 256-token canvas/output, 48 denoising steps - per canvas, entropy-bound 0.1, temperature decay 0.8 to 0.4, stability 1, - confidence threshold 0.005, static cache, and compiled decoder. The pinned - Diffusers pipeline exposes per-step callbacks, but validates only positive - generation length and step counts; it has no upper bounds for prompt, - generation, image, or video-frame resources. MoDiff has no generic - diffusion-text node yet. Accordingly the candidate remains remote-only and - absent from runtime/download catalogs and every user-facing capability. - Backend-owned bounds, multimodal input/output safety policy, exact remote - heavy-hardware measurements, and physical macOS evidence remain pending. - - [x] **Stable Cascade immutable artifact/source review:** backend `b55983b` - seals the exact public prior snapshot - `stabilityai/stable-cascade-prior@7ca32c21c3b4d4e35bbb94fcfedfb4fa2259bd91` - and decoder snapshot - `stabilityai/stable-cascade@a89f66d459ae653e3b4d4f992a7c3789d0dc4d16`. - Neither repository contains Python or requires remote code. The selected - full-size bf16 partition is six safetensors files / 13,728,020,596 bytes; - exact component hashes, canonical selected/full inventory digests, model - indexes, scheduler configuration, pinned Diffusers sources, and the - identical repository license bytes are recorded in - `data/stable-cascade-artifact-review.json` without downloading weights. - - This family is not admitted. Its Stability AI Non-Commercial Research - Community License prohibits the production and hosted-service uses MoDiff - cannot silently assume. In addition, all three upstream Stable Cascade - pipelines are deprecated after Diffusers 0.35.2 while MoDiff pins a later - revision. The combined loader names the connected prior repository but not - its immutable revision, so it cannot safely identify the two distinct - snapshots with one shared revision. Any future reviewed path must load the - prior and decoder independently, pass image embeddings explicitly, and - retain the reviewed 1024px/20-step prior plus 10-step decoder recipe. No - runtime/download catalog, workflow, client, Auto, template, Gallery, or - generated-media surface was added. License resolution, a maintained - package-owned pipeline, remote heavy-hardware execution, and physical - macOS evidence remain pending independently. - - [x] **DeepFloyd IF immutable artifact/source review:** backend `8d45c9f` - seals the canonical three-stage route at exact revisions: - `DeepFloyd/IF-I-XL-v1.0@c03d510e9b75bce9f9db5bb85148c1402ad7e694`, - `DeepFloyd/IF-II-L-v1.0@609476ce702b2d94aff7d1f944dcc54d4f972901`, - and - `stabilityai/stable-diffusion-x4-upscaler@572c99286543a273bfd17fac263db5a77be12c4c`. - All three repositories contain no Python and require no remote code. The - reviewed 64px, 256px, and 1024px path selects 11 repository-scoped - safetensors files / 27,326,661,461 bytes, or 26,718,655,226 bytes after - deduplicating the identical Stage I/II safety-checker and watermarker - blobs. Full inventory digests, selected file hashes, immutable metadata - blob identities, all seven pinned package-owned pipeline source hashes, - stage defaults, prompt-embedding reuse, safety handoff, and estimate-only - resource bounds are recorded in - `data/deepfloyd-if-artifact-review.json`; a no-weight API probe passed. - - The family remains unadmitted. Stage I and II require account/contact - acceptance of the DeepFloyd License, which limits use and distribution to - noncommercial research and also restricts data produced by the software. - Anonymous access can inventory immutable blobs but returns HTTP 401 for the - gated model indexes and component configurations, so their payloads were - not treated as reviewed. The pinned pipelines provide a legacy per-step - callback and enforce prompt token/noise or strength constraints, but do not - own upper bounds for batch, image count, step count, or Stage I/II output - dimensions. No runtime/download catalog, workflow, client, Auto, template, - Gallery, or generated-media surface was added. License acceptance, - authenticated configuration review, backend-owned bounds, remote - heavy-hardware execution, and physical macOS evidence remain pending. - - [x] **PixArt Sigma 1024px source admission:** backend `4fd1a66` and - client `235c9d3` admit the exact public snapshot - `PixArt-alpha/PixArt-Sigma-XL-2-1024-MS@e102b3591cc82e97071b8b4cb90d834d0c487207` - through the generic Diffusers image facade. The OpenRAIL++ repository has - no Python, requires no remote code, and selects four safetensors files / - 21,827,405,446 bytes. Exact file hashes, canonical inventory digest, - metadata and pinned package-source hashes, the 1024px/20-step/guidance-4.5 - recipe, 300-token bound, and estimate-only resource envelope are sealed in - `data/pixart-sigma-artifact-review.json` without downloading weights. - - The workflow is Expert-only and remote-only, with Auto and Gallery - disabled. Its 104-workflow manifest entry is graph-qualified but explicitly - runtime-unqualified and requires only the immutable PixArt repository. - Backend `8bca634` and client `60b0269` also keep declarative image contract - refreshes on the non-installing base path, clear unused auxiliary model - identities, and finalize the exact loader-issued image contract. The full - 1,373-test backend overlay gate, complete client check, focused base-runtime - matrix, deterministic workflow verification, and the three affected mocked - Studio cases pass. Remote real-weight execution, output safety and quality - review, and physical macOS execution remain pending independently. - - [x] **AuraFlow v0.3 source admission:** backend `8117b39` and client - `7d52469` admit the exact public snapshot - `fal/AuraFlow-v0.3@2cd8588f04c886002be4571697d84654a50e3af3` - through the generic Diffusers image facade. The Apache-2.0 repository has - no Python, requires no remote code, and selects the four-file fp16 - safetensors partition / 16,835,036,374 bytes. Exact file hashes, canonical - inventory digest, immutable metadata and pinned package-source hashes, the - native 1536x768/50-step/guidance-3.5 recipe, 256-token bound, and - estimate-only resource envelope are sealed in - `data/auraflow-v0.3-artifact-review.json` without downloading weights. - - The workflow is Expert-only and remote-only, with Auto and Gallery - disabled. Its 105-workflow manifest entry is graph-qualified but explicitly - runtime-unqualified and requires only the immutable AuraFlow repository. - Backend-owned field contracts and preflight now enforce AuraFlow's 1536px - maximum side, while client `c5e02ea` updates the restored-queue test fixture - to model the current loader-issued image-contract signal. The 1,399-test - exact pinned backend overlay, complete client check, deterministic workflow - verification, and corrected focused mocked Studio case pass. Remote - real-weight execution, output safety and quality review, and physical macOS - execution remain pending independently; no weights or media were - downloaded or retained. - - [x] **Bria 3.2 source and admission-gate review:** backend `30a4667` - records the package-owned `BriaPipeline` and `BriaTransformer2DModel` - sources at the pinned Diffusers revision, their exact source hashes, - no-weight call signatures, callback surface, 1024px/30-step/guidance-5 - defaults, 128-token default and 512-token package maximum, and the public - `briaai/BRIA-3.2` file-tree observations in - `data/bria-3.2-source-review.json`. - - The family is not admitted. The official repository is gated, labels the - weights for non-commercial use, links CC BY-NC 4.0, and directs commercial - users to a separate paid agreement. Anonymous model APIs and file payloads - return HTTP 401, so an immutable head, exact artifact sizes and hashes, - model index, component configurations, and repository license payload - cannot be treated as reviewed. The pinned inference recipe also requires - bf16 T5 placement with every final `DenseReluDense.wo` projection restored - to float32 plus a float32 VAE when its shift factor is zero; the generic - loader does not currently own a validated per-layer dtype contract. No - runtime/download catalog, workflow, client, Auto, template, Gallery, or - generated-media surface was added. Authenticated artifact and license - review, a backend-owned precision/bounds contract, remote heavy-hardware - execution, and physical macOS evidence remain pending independently. The - focused 10-test review/catalog matrix and static checks pass; no weights or - media were downloaded. - - [x] **Bria FIBO generation/edit source and admission-gate review:** backend - `a826a5e` seals `briaai/FIBO` at - `185d92d046a8bc2f93843083894a9ea983b90b32` and - `briaai/Fibo-Edit` at - `1bee4ae9b119d2634ec9f378370f2a1ae49d89cc`. The exact current - safetensors partitions are respectively five files / 25,540,786,720 bytes - and five files / 24,131,409,512 bytes. The Edit repository's two archived - transformer shards are kept separate, bringing its complete seven-file - weight surface to 40,703,183,416 bytes. Canonical inventory digests, - individual hashes, immutable metadata blob identities, package-owned - generation/edit/transformer source hashes, structured-JSON call contracts, - mask path, 3,000-token package bound, callbacks, and current-versus-archive - identities are recorded in `data/bria-fibo-source-review.json`. - - The family is not admitted. Both model repositories are gated and their - model cards limit the public weights to non-commercial use while directing - commercial users to a separate agreement; authenticated configs and exact - gated license payloads return HTTP 401. The official quality path also uses - two CC-BY-NC-4.0 custom-code Modular promptifiers. Their exact code revisions - and hashes are sealed, but they require `trust_remote_code=True`, hard-code - `.to("cuda")`, expose an unbounded 4,096-token sampling default, and load - the nested 8,875,719,328-byte FIBO VLM snapshots without passing their - immutable revisions. Generation accepts arbitrary strings despite requiring - structured JSON for reviewed quality; Edit validates an `edit_instruction` - JSON but the local promptifier does not cover the documented mask route. - No runtime/download catalog, workflow, client, Auto, template, Gallery, or - generated-media surface was added. License acceptance, authenticated config - review, explicit remote-code authorization, backend-owned JSON/device/model - pinning and bounds, remote heavy-hardware execution, and physical macOS - evidence remain pending independently. The focused review matrix and static - checks pass; no weights or media were downloaded. - - [x] **Chroma1-HD text-to-image source admission:** backend `5d3bc8f` and - client `86cbd92` admit the exact public snapshot - `lodestones/Chroma1-HD@0e0c60ece1e82b17cb7f77342d765ba5024c40c0` - through the generic Diffusers image facade. The model-card metadata declares - Apache-2.0, the repository has no Python and requires no remote code, and the - selected Diffusers partition contains five safetensors files / - 27,492,403,238 bytes. The duplicate 17,800,038,288-byte original single-file - distribution is excluded. Exact selected and excluded file hashes, canonical - inventory digest, immutable metadata and shard-index identities, and pinned - package-owned text-to-image, image-to-image, output, and transformer source - hashes are sealed in `data/chroma1-hd-artifact-review.json` without - downloading weights. - - The admitted workflow is Expert-only and remote-only. Backend-owned - contracts bound it to the reviewed bfloat16 1024x1024/40-step/guidance-3 - recipe and at most 512 prompt tokens. Its 106-workflow manifest entry is - graph-qualified but explicitly runtime-unqualified, while Chroma - image-to-image, Auto, and Gallery remain disabled. The upstream model card - explicitly states that the model has no safety alignment, so live output - safety and quality review remains a hard gate. The complete 1,413-test - backend overlay with 3,110 subtests, project static checks, complete client - check, and deterministic workflow verification pass. Remote real-weight - execution and physical macOS execution remain pending independently; no - weights or media were downloaded or retained. - - [x] **CogView3 Plus 3B text-to-image source admission:** backend `f74c806` - and client `a4d0b99` admit the exact public snapshot - `zai-org/CogView3-Plus-3B@5d70e40732ac0efac98524c51a7fa9c82707f1e5` - through the generic Diffusers image facade. The repository is package-owned, - contains no Python, requires no remote code, and exposes seven bfloat16 - safetensors files / 25,559,227,422 bytes. Exact file hashes, canonical - inventory digest, immutable metadata identities, 2,848,836,672-parameter - safetensors metadata, and pinned package pipeline/output/transformer source - hashes are sealed in `data/cogview3-plus-3b-artifact-review.json` without - downloading weights. The model-card metadata declares Apache-2.0 and links - `LICENSE.md`, but neither that path nor `LICENSE` exists in the immutable - tree; this missing linked license file is recorded rather than silently - treated as stronger repository evidence. - - The admitted text-to-image workflow is Expert-only and remote-only. - Backend-owned contracts require bfloat16, constrain both sides to - 512-2048 pixels in 32-pixel increments, cap inference at 50 steps and 224 - prompt tokens, and preserve the reviewed 1024x1024/guidance-7 recipe. The - upstream A100 memory figures are recorded as estimates only. The - deterministic 107-workflow catalog is graph-qualified but explicitly - runtime-unqualified; Auto and Gallery remain disabled. The complete - 1,419-test backend overlay with 3,130 subtests and three platform skips, - project static checks, complete client check, and deterministic workflow - verification pass. Remote real-weight output safety/quality review and - physical macOS execution remain pending independently; no weights or media - were downloaded or retained. - - [x] **CogView4 6B text-to-image source admission:** backend `495d07d` and - client `9822baa` admit the exact public snapshot - `zai-org/CogView4-6B@63a52b7f6dace7033380cd6da14d0915eab3e6b5` - through the generic Diffusers image facade. The immutable repository carries - the complete Apache License 2.0 text, contains no Python, requires no remote - code, and exposes eight bfloat16 safetensors files / 31,108,954,670 bytes. - Exact file hashes, canonical inventory digest, immutable metadata identities, - 6,369,118,272-parameter safetensors metadata, and pinned package - pipeline/output/transformer source hashes are sealed in - `data/cogview4-6b-artifact-review.json` without downloading weights. - - The admitted text-to-image workflow is Expert-only and remote-only. - Backend-owned contracts require bfloat16, constrain both sides to - 512-2048 pixels in 32-pixel increments, enforce the model card's 2^21-pixel - ceiling across both dimensions, and cap inference at 50 steps and 1,024 - prompt tokens while preserving the reviewed 1024x1024/guidance-3.5 recipe. - The package signature's 1,024-token default disagrees with its 224-token - docstring, and the model card's 1920x1280 memory row exceeds its own stated - 2^21-pixel ceiling. Both contradictions are recorded; the executable route - follows the actual signature and the stricter declared pixel ceiling. The - upstream A100 batch-four memory figures remain estimates only. The - deterministic 108-workflow catalog is graph-qualified but explicitly - runtime-unqualified; Auto and Gallery remain disabled. The complete - 1,425-test backend overlay with 3,150 subtests and three platform skips, - dependency/preflight/static checks, complete client check, and deterministic - workflow verification pass. Remote real-weight output safety/quality review - and physical macOS execution remain pending independently; no weights or - media were downloaded or retained. - - [x] **VisualCloze source and admission-gate review:** backend `0f4d3c4` - seals the exact public full-model snapshots - `VisualCloze/VisualClozePipeline-384@59c469d2772d927ffe55f3543c4d3bd556fd46a4` - and - `VisualCloze/VisualClozePipeline-512@feaad2dd83d3d42bad197b9d31fe2f6c5b4cb1bb`. - Each immutable repository contains seven bfloat16 safetensors files / - 33,743,379,958 bytes, no Python, and no remote-code requirement. Exact file - hashes, canonical inventory digests, immutable metadata identities, - 11,902,391,360-parameter safetensors metadata, and pinned package-owned - combined/generation/processor source hashes are recorded in - `data/visualcloze-artifact-review.json` without downloading weights. Both - model cards declare Apache-2.0 in metadata, but neither immutable tree - contains a license file. The two 2,482,363,148-byte legacy `.pth` LoRA - checkpoints are separately sealed and excluded under the safe-serialization - policy; the full Diffusers snapshots do not require them. - - This family is deliberately review-only. Its task input is a nested, - rectangular image matrix containing one or more explicit null target cells, - with distinct task/content prompts and an optional second SDEdit upsampling - stage. Mapping that shape onto an existing single-image edit or inpaint - alias would be incorrect. The pinned package supplies no maximum batch, - row, column, cumulative input-pixel, denoising-step, or upsampling-dimension - bounds. Accordingly no runtime/download catalog, workflow, client, Auto, - template, or Gallery surface was added. A generic visual-context-matrix - contract, backend-owned resource bounds, license-file clarification, remote - heavy-hardware execution, live output review, and physical macOS evidence - remain independent gates. The complete 1,430-test backend overlay with - 3,154 subtests and three platform skips, project static/dependency/preflight - checks, and the focused immutable review matrix pass; no weights or media - were downloaded or retained. - - [x] **Allegro text-to-video source admission:** backend `6488462` and - client `05a2e15` admit the exact public snapshot - `rhymes-ai/Allegro@c1b9207bb5cb79e2aa08f3d139c17d26c0de55b6` - through the generic Diffusers video facade. The immutable repository - contains no Python and requires no remote code. Six selected bfloat16 - safetensors files / 25,293,069,108 bytes, their exact hashes and canonical - inventory digest, immutable metadata identities, 2,771,907,856-parameter - safetensors metadata, and pinned package pipeline/output/transformer/VAE - source hashes are sealed in `data/allegro-artifact-review.json` without - downloading weights. The duplicate 19,049,317,384-byte unsafe PyTorch `.bin` - text-encoder partition is explicitly excluded. The model card declares - Apache-2.0, but the immutable repository contains no license file. - - The admitted text-to-video workflow is Expert-only and remote-only. Its - backend-owned contract preserves the reviewed native 1280x720, 88-frame, - 100-step, guidance-7.5, 512-token, 15-FPS recipe; requires bfloat16 for the - text encoder and transformer; keeps the VAE in float32 with mandatory - tiling; and defaults to sequential CPU offload. Conservative planning - reserves 10 GiB accelerator memory, 30 GiB disk, and 48 GiB system RAM. - The deterministic 109-workflow catalog is graph-qualified but explicitly - runtime-unqualified; Auto and Gallery remain disabled. The complete - 1,441-test backend overlay with 3,175 subtests and three platform skips, - dependency/preflight/static checks, complete client check, deterministic - workflow verification, and the nine-byte remaining client bundle margin - pass. Remote real-weight output safety/quality review and physical macOS - execution remain pending independently; no weights or media were downloaded - or retained. - - [x] **AnyFlow source and admission-gate review:** backend `09d3c98` seals - all four official public Diffusers snapshots: bidirectional Wan2.1 T2V 1.3B - at `4c2ec05c7fa4dbafbca131ad32430905c7ff2974`, bidirectional T2V 14B at - `ed91e001c08a88df8bbdc18f29b43b8078459627`, FAR 1.3B at - `915af337434035df8545797ecc910d79fa78cf29`, and FAR 14B at - `6207c4512a306d2a5a564df66b04a78668923740`. The exact seven-file / - 26,074,853,036-byte and 26,075,642,740-byte 1.3B inventories and nine-file / - 51,863,430,540-byte and 51,866,062,428-byte 14B inventories are bfloat16 - safetensors only. All repositories contain complete identical license files, - no Python, and no remote-code requirement. Immutable metadata, model-card, - model-index, scheduler, transformer, VAE, artifact, and pinned package source - hashes are recorded in `data/anyflow-artifact-review.json` without - downloading weights. - - This family is deliberately review-only. The NVIDIA One-Way Noncommercial - License restricts the models and derivatives to non-commercial research - activities or publications, so legal product-admission approval remains a - hard gate. The package-owned bidirectional T2V and FAR T2V/I2V/V2V contracts, - canonical 832x480/81-frame/4-step/guidance-1/16-FPS recipe, FAR chunk - partition, callback surface, and missing backend resource bounds are sealed. - The immutable model cards still import custom upstream pipelines and use - `context_sequence`, whereas the current package-owned API uses `video`; that - mismatch is explicit. No runtime/download catalog, capability, workflow, - client, Auto, template, or Gallery surface was added. The complete - 1,446-test backend overlay with 3,183 subtests and three platform skips plus - static/dependency/preflight checks pass. Legal approval, bounded contracts, - remote execution, live output review, and physical macOS evidence remain - independent gates; no weights or media were downloaded or retained. - - [x] **ChronoEdit source and admission-gate review:** backend `70640ae` - seals the exact public snapshot - `nvidia/ChronoEdit-14B-Diffusers@26b33e0d056203dc30c733ad02b86ac225eb17d5`. - Its required package-owned core is 21 safetensors files / - 90,075,130,404 bytes; exact file hashes, canonical inventory digest, - immutable model-card/component/index identities, 16,394,878,784-parameter - Hub metadata, and pinned pipeline/output/transformer source hashes are - recorded in `data/chronoedit-artifact-review.json`. The included 8-step - distillation LoRA and the two exact public upscaler/paint-brush LoRA - revisions are separately sealed but not selected. The model repository is - public and contains no Python or remote-code requirement, but it has no - embedded license file. - - This family is deliberately review-only. The immutable card names the - mutable external NVIDIA Open Model License Agreement as governing terms and - says rights terminate if a contained safety guardrail is bypassed, disabled, - circumvented, or made less effective. The package-owned Diffusers pipeline - has no safety checker, while the bundled 102-file / 7,171,449,905-byte Cosmos - guardrail includes exact `.pth` and `.pt` artifacts that fail MoDiff's safe - serialization policy. The artifact model index also still names the generic - Wan pipeline/transformer and requires explicit ChronoEdit component - overrides. Native five-frame editing, 29-frame temporal reasoning, and - eight-step distilled recipes are sealed, along with the final-frame image - semantics and missing backend bounds. No runtime/download catalog, - capability, workflow, client, Auto, template, or Gallery surface was added. - The complete 1,453-test backend overlay with 3,183 subtests and three - platform skips plus static/dependency/preflight checks pass. Immutable or - approved governing terms, a complete safe guardrail integration, a generic - edit-plus-reasoning contract, remote execution, live output review, and - physical macOS evidence remain independent gates; no weights or media were - downloaded or retained. - - [x] **ConsisID source and admission-gate review:** backend `a52c7ec` - seals the exact public snapshot - `BestWishYsh/ConsisID-preview@950bc3f0902db44799e223a12ad972f9c52b341d`. - Its generator contains five bfloat16 safetensors files / - 22,821,396,692 bytes, no Python, and no remote-code requirement. Exact file - hashes, canonical inventory digest, immutable model-card/component/index - identities, 6,217,102,912-parameter Hub metadata, and pinned package - pipeline/output/transformer/face-utility source hashes are recorded in - `data/consisid-artifact-review.json`. The official Diffusers documentation - also names `BestWishYsh/ConsisID-1.5`, but that repository returns not found - to unauthenticated metadata lookup as of 2026-08-13 and is not invented as - an available artifact. The preview card declares Apache-2.0 in metadata but - the immutable tree contains no license file. - - This family is deliberately review-only. Its required package-owned face - preparation hard-requires InsightFace, FaceXLib, a custom EVA-CLIP package, - OpenCV, TorchVision, and ONNX Runtime; hardcodes CUDA execution providers; - and consumes an eight-artifact / 1,446,798,634-byte identity stack containing - five ONNX models plus three required unsafe `.pt`/`.pth` files. Four more - unused unsafe preprocessing/face artifacts are separately sealed. The - package describes identity tensors as crucial but does not require them in - pipeline input validation, so exposing the generator alone would silently - defeat the family's identity-preserving contract. Biometric privacy, - consent, and misuse controls are also unresolved. No runtime/download - catalog, capability, workflow, client, Auto, template, or Gallery surface - was added. The complete 1,460-test backend overlay with 3,183 subtests and - three platform skips plus static/dependency/preflight checks pass. A safe - cross-platform face stack, bounded generic identity-video contract, license - clarification, remote execution, live identity/safety review, and physical - macOS evidence remain independent gates; no weights or media were downloaded - or retained. - - [x] **Latte text-to-video source admission:** backend `180917e` and client - `847ed6c` admit the exact public snapshot - `maxin-cn/Latte-1@0653024365272f061fc44d1078134df22842b687` - through the generic Diffusers video facade. The immutable repository - contains no Python and requires no remote code. Six selected safetensors - files / 23,614,979,636 bytes, their exact hashes and canonical inventory - digest, immutable component/model-card/model-index identities, Hub - safetensors metadata, and pinned package pipeline/transformer/VAE source - hashes are sealed in `data/latte-artifact-review.json` without downloading - weights. The 4,231,339,889-byte unsafe legacy `.pt` checkpoint and the - 391,017,740-byte optional temporal VAE that is not referenced by the native - model index are explicitly excluded. The immutable card declares - Apache-2.0, but the repository contains no license file. - - The admitted text-to-video workflow is Expert-only and remote-only. Its - backend-owned contract preserves the native 512x512, 16-frame, 50-step, - guidance-7.5, 120-token, 8-FPS recipe; requires float16 safe loading; uses - one output, raw-caption encoding, the documented feature mask and temporal - attentions, 14-frame decode chunks, callbacks, and sequential CPU offload; - and rejects every image, video, and mask input. The package signature's - 50-step/guidance-7.5 defaults are authoritative and the conflicting - 100-step/guidance-7.0 docstring values are recorded explicitly. - Conservative estimate-only planning reserves 16 GiB accelerator memory, - 32 GiB disk, and 48 GiB system RAM because upstream reports A100 timing but - no peak-memory measurement. The deterministic 110-workflow catalog is - graph-qualified but explicitly runtime-unqualified; Auto and Gallery remain - disabled. The complete 1,471-test backend overlay with 3,204 subtests and - three platform skips, Ruff E9/F, compile, 66-package compatibility, - preflight, complete client check, and deterministic workflow verification - pass. The unchanged client bundle ceiling passes at 530,428 / 530,432 gzip - bytes. Remote real-weight output safety/quality review, license-file - clarification, and physical macOS execution remain pending independently; - no weights or media were downloaded or retained. - - [x] **Lucy Edit source and admission-gate review:** backend `6ab1033` - seals the exact public snapshot - `decart-ai/Lucy-Edit-Dev@cb201fdec1bca6e7c362e127392c1632c92d2576`. - Its package-owned Diffusers core contains five float32 safetensors files / - 34,182,223,896 bytes, no Python, and no remote-code requirement. Exact file - hashes, canonical inventory digest, immutable model-card/component/index - identities, 5,000,377,536-parameter Hub metadata, the externally linked - license PDF hash retrieved on 2026-08-13, and pinned package - pipeline/output/Wan-transformer/Wan-VAE source hashes are recorded in - `data/lucy-artifact-review.json`. The model repository is public and - ungated, but contains no license file; its governing license and incorporated - acceptable-use policy are mutable external documents. - - This family is deliberately review-only. The Lucy Edit 5B Model Community - License permits only non-commercial, non-production use, excludes commercial - use of outputs, and defines hosted remote access as distribution. That is - incompatible with admission to MoDiff's product runtime without a separate - commercial license and explicit legal approval. The native 832x480, - 81-frame, 50-step, guidance-5, 512-token, 24-FPS edit recipe, callback and - sequential-offload surfaces, BF16-pipeline/FP32-VAE load recommendation, and - missing backend resource bounds are sealed. The package also normalizes - `num_frames` but never uses it to select or validate the input video's frame - count. No runtime/download catalog, capability, workflow, client, Auto, - template, or Gallery surface was added. The complete 1,476-test backend - overlay with 3,204 subtests and three platform skips plus - static/dependency/preflight checks pass. Legal product-admission approval, - immutable governing terms, bounded execution, remote qualification, live - output review, and physical macOS evidence remain independent gates; no - weights or media were downloaded or retained. - - [x] **Mochi 1 Preview text-to-video source admission:** backend `fb3e39f` - and client `c0afea5` admit the exact public snapshot - `genmo/mochi-1-preview@14be5fcea23095ed330cb214647916a451e38b6e` - through the generic Diffusers video facade. The immutable repository - contains no Python and requires no remote code. Eight selected safetensors - files / 40,024,303,350 bytes, their exact hashes and canonical inventory - digest, immutable model-card/component/index identities, 10,027,677,744- - parameter Hub metadata, and pinned package pipeline/output/transformer/VAE - source hashes are sealed in `data/mochi-artifact-review.json` without - downloading weights. Duplicate original-format weights, an unindexed - two-shard T5 copy, and the default float32 transformer and VAE partitions - are explicitly excluded. The immutable card declares Apache-2.0, but the - repository contains no license file. - - The admitted text-to-video workflow is Expert-only and remote-only. Its - backend-owned contract preserves the official native 848x480, 31-frame, - 64-step, guidance-4.5, 256-token, 30-FPS recipe; requires the BF16 variant, - explicitly preloads the indexed T5 encoder, mandates VAE tiling, uses one - output with callbacks and sequential CPU offload, and rejects every image, - video, and mask input. The repository card's conflicting 84-frame example, - the package signature's 19-frame default, and its internally inconsistent - step documentation are recorded explicitly; exact 31-frame temporal-VAE - congruence is retained. Conservative estimate-only planning preserves the - publisher's 22-60 GiB accelerator-memory range. The deterministic - 111-workflow catalog is graph-qualified but explicitly runtime-unqualified; - Auto and Gallery remain disabled. The complete 1,486-test backend overlay - with 3,269 subtests and three platform skips, Ruff E9/F, compile, - 66-package compatibility, preflight, complete client check, and - deterministic workflow verification pass. The unchanged client bundle - ceiling passes at 530,428 / 530,432 gzip bytes. Remote real-weight output - safety/quality review, license-file clarification, and physical macOS - execution remain pending independently; no weights or media were downloaded - or retained. - - [x] **SANA-Video 2B 480p text/image-to-video source admission:** backend - `081a083` and client `6ed67bf` admit the exact public snapshot - `Efficient-Large-Model/SANA-Video_2B_480p_diffusers@db5f398b13ca086d09a50ce156c20527773841b1` - through the generic Diffusers video facade. The immutable repository is - ungated, contains a complete Apache-2.0 license file, contains no Python, - and requires no remote code. Its exact five-file / 13,963,813,420-byte - mixed BF16/FP32 safetensors partition, canonical inventory digest, - immutable metadata identities, parameter counts from safetensors headers, - and pinned package pipeline/output/transformer/Wan-VAE/scheduler source - hashes are sealed in `data/sana-video-artifact-review.json` without - downloading weights. The unsafe original 480p and 720p `.pth` repositories, - the distinct 18.35 GB safe 720p partition, and the duplicate-heavy separate - LongLiveSANA contract are explicitly excluded. - - The two admitted workflows are Expert-only and remote-only. Their - backend-owned contracts preserve the official native 832x480, 81-frame, - 50-step, guidance-6, 300-token, 16-FPS recipe; append the reviewed motion - score of 30; keep the transformer and text encoder in BF16; preload the Wan - VAE in FP32; mandate VAE tiling and sequential CPU offload; disable - resolution binning; and distinguish text-only input from exactly one I2V - opening image. The pinned package's MPS rotary-frequency float32 workaround - is recorded, as is its decode-OOM branch that may not produce a decoded - value; mandatory tiling does not overstate live qualification. Conservative - estimate-only planning reserves 24 GiB accelerator memory, 24 GiB selective - disk, and 48 GiB system RAM. The deterministic 113-workflow catalog is - graph-qualified but explicitly runtime-unqualified; Auto and Gallery remain - disabled. The client stays within its unchanged bundle ceiling by sharing - equivalent offload constants and removing only prose duplicated by typed - media requirements. The complete 1,498-test backend overlay with 3,270 - subtests and three platform skips, Ruff E9/F, compile, 66-package - compatibility, preflight, complete client check, and deterministic workflow - verification pass at 530,406 / 530,432 gzip bytes. Remote real-weight output - safety/quality review and physical macOS execution remain pending - independently; no weights or media were downloaded or retained. - - [x] **Hunyuan-DiT v1.2 ControlNet Canny source admission:** backend - `72185d0` and client `f134f98` admit the exact public distilled base - `Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers-Distilled@ba991d1546d8c50936c4c16398ed0a87b9b99fb1` - with the exact Canny component - `Tencent-Hunyuan/HunyuanDiT-v1.2-ControlNet-Diffusers-Canny@b2d21391ebcf78939344cfec84891932f9d53aa0` - through the generic Diffusers image facade. Both snapshots are ungated, - safetensors-only for the admitted partition, contain no Python, and require - no remote code. Their exact six-file / 17,399,623,404-byte float32 weight - inventory, canonical digest, safetensors-header parameter counts, immutable - metadata identities, and pinned package pipeline/model/VAE/scheduler/output - source hashes are sealed in - `data/hunyuan-dit-controlnet-artifact-review.json` without downloading - weights. The exact Depth and Pose component revisions are cataloged as - reviewed Expert substitutions but intentionally have no canonical graph. - - The shared Tencent Hunyuan Community License is sealed from its immutable - 14,614-byte source and requires one explicit client acknowledgement for the - base-plus-ControlNet assembly. Its 100-million-MAU commercial threshold, - distribution notices, public machine-generation disclosure, output-use, - acceptable-use, and military-use restrictions are recorded without - overstating rights. The admitted Expert-only, remote-only graph preserves - the official 1024x1024, 50-step, guidance-6, scale-1 Canny recipe, both text - ceilings, float16 loading, exact artifact revisions, model CPU offload, - and bounded control preprocessing. The base declares a required safety - checker but ships none, so Auto and Gallery remain disabled. The - deterministic 114-workflow catalog is graph-qualified but explicitly - runtime-unqualified. The complete 1,505-test backend overlay with 3,289 - subtests and three platform skips, Ruff E9/F, compile, 66-package - compatibility, modular-contract check, preflight, complete client check, - and deterministic workflow verification pass at 530,393 / 530,432 gzip - bytes. Remote real-weight memory/output safety/quality review and physical - macOS execution remain pending independently; no weights or media were - downloaded or retained. - - [x] **Stable Diffusion 3 ControlNet source and admission-gate review:** the - pinned package contains official package-owned Canny/Tile and inpainting - pipelines, `SD3ControlNetModel`, flow-matching scheduler, callback support, - model CPU offload, and finite prompt/resolution/control bounds. The exact - gated base - `stabilityai/stable-diffusion-3-medium-diffusers@ea42f8cef0f178587cf766dc8129abd379c90671`, - public InstantX Canny and Tile revisions, and public Alimama inpainting - revision are sealed in `data/sd3-controlnet-artifact-review.json`. The - review records the base's six-file / 15,499,002,486-byte selected fp16 - safetensors partition, all three auxiliary weight hashes and header-derived - parameter counts, three reproducible assembly digests, metadata and package - source hashes, native 1024px/28-step recipes, and estimate-only resource - envelopes. Only 125,040 safetensors-header bytes were fetched; no full - weights or media were downloaded. - - Admission is deliberately blocked. The required base's model index and - component configs return HTTP 401 without accepted authenticated access, - so the executable partition cannot be fully reviewed here. Its exact - immutable license is non-commercial-only and forbids production and - hosted/API use without a separate license. The Canny and Tile repositories - publish neither license metadata nor a license file. The inpainting - repository's copied Stability Community License notice does not - unambiguously reconcile its derivative-weight grant with the exact older - base license. The package pipelines also have no safety checker or - equivalent output guardrail. Accordingly no runtime/download catalog, - capability, canonical graph, client, Auto, template, or Gallery surface is - added. Authenticated artifact review, auxiliary rights resolution, legal - product approval, backend-owned optional-runtime and execution bounds, - remote heavy-hardware safety/quality review, and physical macOS execution - remain independent gates. - - [x] **DiT source and admission-gate review:** the pinned package contains - the official package-owned `DiTPipeline`, `DiTTransformer2DModel`, DDIM - scheduler, fixed ImageNet class-label contract, model CPU offload, and - finite 256px and 512px shapes. The only two exact Facebook repositories, - `facebook/DiT-XL-2-256@eab87f77abd5aef071a632f08807fbaab0b704d0` - and - `facebook/DiT-XL-2-512@101a3d462b22d64c4afdd4d0c8c59a2c0b961b99`, - are sealed in `data/dit-artifact-review.json`. The review records both - 8-file / 3,334,284,997-byte repositories, all four immutable weight - identities, repository metadata and package source hashes, exact - 1,000-class input/output contracts, the package's 25/50/250-step example, - call-default, and docstring discrepancy, and estimate-only resource - envelopes. No weight bytes or media were downloaded. - - Admission is deliberately blocked. Both official snapshots publish their - 3,334,245,438-byte selected partitions only as legacy pickle-based PyTorch - `.bin` files and publish no safetensors alternative. Both declare CC BY-NC - 4.0 only in model-card metadata, do not bundle a license file, and cannot be - admitted for commercial product use. The package pipeline has neither a - safety checker nor a step callback for cooperative cancellation. - Accordingly no community conversion, runtime/download catalog, capability, - canonical graph, client, Auto, template, or Gallery surface is added. An - official safe-serialization snapshot, commercial product rights, a bundled - license receipt, backend-owned loading/bounds and cancellation, remote - heavy-hardware safety/quality review, and physical macOS execution remain - independent gates. - - [x] **ERNIE Image Turbo text-to-image source admission:** backend - `a0b07c8` and client `0527a66` admit the exact public Apache-2.0 snapshot - `baidu/ERNIE-Image-Turbo@bc68c81e2a1730a394d5fc9fae70713dee940140` - through the generic Diffusers image facade. The immutable repository is - ungated, bundles its complete license, contains no Python, and requires no - remote code. Its exact five-file / 31,596,733,630-byte predominantly BF16 - safetensors partition, canonical inventory digest, immutable metadata - identities, parameter counts from bounded remote headers, and pinned - package pipeline/output/transformer/VAE/scheduler and Transformers model - source hashes are sealed in - `data/ernie-image-turbo-artifact-review.json` without downloading weights. - The distinct 50-step `baidu/ERNIE-Image` sibling was reviewed but is not - conflated with or exposed by the Turbo contract. - - The admitted workflow is Expert-only and remote-only. Its backend-owned - contract fixes the reviewed Turbo path to 1024x1024, 8 steps, guidance 1, - prompt enhancement enabled, the tokenizer's 2,048-token ceiling, BF16, - and model CPU offload, while retaining the package step callback for - cancellation. The package has no safety checker, so Auto and Gallery - remain disabled. Admission also extends the exact optional Transformers - profile with `Mistral3Model` and `Ministral3ForCausalLM`; the same audit - removed `StableAudioPipeline` from the LoRA adapter-method qualification - set because that class does not implement the declared methods. The - resulting clean-base Linux profile digest - `sha256:6ce66835ba72c609ce83b41ddfa3141e95b42118f86cf419f25e2dfb35409809` - passed locked install validation, activation, the finite CLIP+PEFT child, - rollback, and clean-base restoration without retaining managed state. - The deterministic 115-workflow catalog is graph-qualified but ERNIE model - execution remains unqualified. The complete 1,523-test backend overlay with - 3,315 subtests and three platform skips, Ruff E9/F, complete client check, - unchanged 530,432-byte gzip ceiling, and deterministic workflow - verification pass. Remote real-weight memory/output safety/quality review - and physical macOS execution remain pending independently; no weights or - media were downloaded or retained. - - [x] **GLM-Image text-to-image source admission:** backend `c338824` and - client `d42a15c` admit the exact public snapshot - `zai-org/GLM-Image@2c433cc0cbc293bde2ac8ca9624f279b5d23fcf4` - through the generic Diffusers image facade. The immutable repository is - ungated, contains no Python, requires no remote code, and declares MIT - terms in its model card; the incorporated `X-Omni` tokenizer weights - remain Apache-2.0. The missing bundled license/notice file is retained as - an explicit redistribution clarification rather than silently inferred - away. Its exact nine-file / 35,765,307,854-byte mixed BF16/FP32 - safetensors partition, canonical inventory digest, immutable metadata - identities, parameter counts from bounded remote headers, and pinned - Diffusers/Transformers source hashes are sealed in - `data/glm-image-artifact-review.json` without downloading weights. - - The admitted workflow is Expert-only and remote-only. Its backend-owned - contract deliberately admits text-to-image first, fixes the recipe to - 1024x1024, 50 steps, guidance 1.5, a 2,048-token ceiling, BF16 with the T5 - encoder restored to FP32, and model CPU offload, while retaining the - package step callback for cancellation. Image-to-image and multi-image - inputs remain outside this initial route. The package exposes neither a - negative-prompt parameter nor a safety checker, so negative prompt is - hidden and Auto and Gallery remain disabled. Admission extends the exact - optional runtime with the package-owned GLM pipeline/transformer and real - Transformers GLM processor/model/tokenizer symbols; it also closes the - ERNIE pipeline/transformer/VAE symbol surface missed by P6.33. The revised - clean-base Linux profile digest - `sha256:e1f6cc3420630a6591bd623553f6f43bfe84fbaa041d2ef5f3d4812542278ad8` - passed locked install validation, activation, the finite CLIP+PEFT child, - rollback, and clean-base restoration without retaining managed state. - The deterministic 116-workflow catalog is graph-qualified but GLM model - execution remains unqualified. The complete 1,529-test backend overlay - with 3,336 subtests and three platform skips, Ruff E9/F, complete client - check, unchanged 530,432-byte gzip ceiling, and deterministic workflow - verification pass. Remote real-weight memory/output safety/quality review, - bundled-license clarification, and physical macOS execution remain pending - independently; no weights or media were downloaded or retained. - - [x] **HiDream-I1 source and admission-gate review:** backend `0685ee5` - seals the exact public Full, Dev, and Fast snapshots at - `8ccbbfb270ccdae26d6bb0081df67dc81e4033bf`, - `0fad2ea0ccf9a80ddf019ea777eedb27c1ccb232`, and - `4856a5d8cd6fbd194780ed9f289bdf696d3afc10`. Each snapshot contains - an exact 12-file, approximately 47.18 GB safetensors partition with five - shared CLIP/T5/VAE files and seven variant-specific transformer shards. - Their immutable weight identities, repository metadata and package source - hashes, official 50/28/16-step recipes, seven finite resolution presets, - 128-token prompt default, negative-prompt support, model-offload sequence, - callback/interrupt surface, and estimate-only composite resource envelope - are sealed in `data/hidream-image-artifact-review.json`. No weight bytes - or media were downloaded. - - Admission is deliberately blocked rather than treating those safe public - partitions as runnable by themselves. Every model index requires - `text_encoder_4` and `tokenizer_4`, but all three immutable trees omit - those subfolders. The official loader supplies them from - `meta-llama/Llama-3.1-8B-Instruct@0e9e39f249a16976918f6564b8830bc894c89659`, - whose manual gate masks all four safetensors identities and returns HTTP - 401 for configuration before authenticated Llama 3.1 license acceptance. - None of the three model snapshots bundles a composite license/notice file, - the generic loader cannot yet assemble and receipt that separately pinned - external tokenizer/causal language model, and the package has no safety - checker. Accordingly no runtime/download catalog, capability, canonical - graph, client, Auto, template, or Gallery surface is added. Fresh - task-scoped Llama terms acceptance, authenticated exact artifact review, - a product-owned composite license receipt, backend-owned external encoder - assembly and bounds, remote heavy-hardware safety/quality review, and - physical macOS execution remain independent gates. - - [x] **HunyuanImage 2.1 source and territory-gate review:** backend `4987495` - seals the exact public package-owned conversion - `hunyuanvideo-community/HunyuanImage-2.1-Diffusers@7e7b7a177de58591aeaffca0929f4765003d7ced` - and the governing upstream receipt - `tencent/HunyuanImage-2.1@e435da11d9e8795a25e224c5ba27b099ed45c55b`. - The conversion contains an exact ten-file / 53,124,614,990-byte BF16 - safetensors partition, no Python, and no remote-code requirement. Its - immutable weight identities, repository/config hashes, package pipeline, - refiner, transformer, VAE, and guider source hashes, 2K/50-step/APG-3.5 - first-stage recipe, prompt limits, cancellation surface, and estimate-only - resource envelope are sealed in - `data/hunyuan-image-artifact-review.json`. No weight bytes or media were - downloaded. - - The family remains contract-only. The immutable Tencent license expressly - excludes the European Union, United Kingdom, and South Korea and prohibits - using the works or outputs outside that Territory. MoDiff has no - legal/product-approved territory enforcement spanning download, local and - hosted execution, output handling, or redistribution. The safe community - conversion also omits the governing LICENSE and NOTICE and links mutable - terms, while the package exposes no safety checker. Accordingly no - runtime/download catalog, capability, graph, client, Auto, template, or - Gallery surface is added. Legal territory/distribution approval, product - territory enforcement, an immutable composite terms receipt, remote - heavy-hardware safety/quality/cancellation qualification, and physical - macOS execution remain independent gates. - - [x] **Hunyuan-DiT v1.2 Distilled standalone source admission:** backend - `1e97362` and client `e6e306f` expose the already reviewed exact public - snapshot - `Tencent-Hunyuan/HunyuanDiT-v1.2-Diffusers-Distilled@ba991d1546d8c50936c4c16398ed0a87b9b99fb1` - as a distinct package-owned text-to-image workflow. The same base remains - independently reusable by the Canny ControlNet assembly; the standalone - route does not load or silently require that auxiliary component. Its - exact five-file / 14,422,655,700-byte float32 safetensors inventory, - canonical digest, immutable metadata/package-source hashes, and sealed - Tencent terms receipt are recorded in - `data/hunyuan-dit-artifact-review.json` without downloading weights. - - The backend-owned Expert contract fixes the distilled path to 1024x1024, - 25 steps, package-default guidance 5, BERT/T5 limits of 77/256, float16 - loading, and explicit CPU offload, while retaining the package callback - and interrupt surface. The snapshot declares a required safety checker - but ships none, so Auto and Gallery remain disabled. The optional runtime - now explicitly validates the base and ControlNet HunyuanDiT pipeline and - transformer symbols. Its committed clean-base Linux profile digest - `sha256:f6962cd6c533197d19b3767e56595e21955d0514f89106bb2a37b0f118c77c33` - passed locked install, validation, activation, the finite CLIP+LoRA child, - rollback, and a second fresh clean-base process. The bounded 1,553-byte - evidence has SHA-256 - `532322a065a3fce184fc8ba2bcabdde885226c4af91cc2be3764f76d9dd04c2f` - and retained no managed state. The deterministic 117-workflow catalog, - complete 1,543-test backend overlay with 3,355 subtests and three platform - skips, Ruff E9/F, complete client gates plus the new exact recipe test, and - unchanged 530,432-byte gzip ceiling pass. Remote real-weight - memory/output safety/quality review and physical macOS execution remain - pending independently; no weights or media were downloaded or retained. - - [x] **Ideogram 4 source, terms, and admission-gate review:** backend - `f09b06c` resolves the three official gated snapshots at immutable heads: - `ideogram-ai/ideogram-4-nf4-diffusers@1874bc70267ba2c823a7239e1d70dd308c8d64dc`, - `ideogram-ai/ideogram-4-nf4@f664347839e0a87bc495f5c9483cc0014b8e344e`, - and `ideogram-ai/ideogram-4-fp8@ee79a7237b519f1402ceacf952f30c8a31ec5073`. - Their anonymous metadata exposes four safetensors files and approximately - 16.10 GB, 16.10 GB, and 27.53 GB of visible weight bytes respectively, - with no repository Python. These are deliberately recorded as visible - sizes rather than exact artifact inventories: before gate acceptance the - LFS identities are masked and the immutable model index and component - configurations return HTTP 401. The package-owned pipeline, transformer, - prompt-enhancer, scheduler, VAE, modular, and output source hashes, its - 2048-square / 48-step defaults, guidance schedule constraints, - multiple-of-16 documented resolution range, callback/interrupt/offload - surfaces, absent negative-prompt and safety-checker surfaces, and the - optional prompt-enhancer boundary are sealed in - `data/ideogram4-source-review.json`. No terms gate was accepted and no - weight bytes or media were downloaded. - - Admission remains blocked. The exact June 3, 2026 Ideogram - Non-Commercial Model Agreement was reviewed without acceptance: commercial - use requires a separate agreement, hosted services and APIs count as - distribution, downstream terms and notices attach, and the incorporated - use policy makes suitable safety filters, human oversight, and disclosures - product responsibilities while the package has no safety checker. MoDiff - also lacks an authenticated exact-artifact receipt, a qualified - NF4/BitsAndBytes CUDA loading recipe, and backend-owned guidance, resource, - and safety bounds. Accordingly no runtime/download catalog, adapter, - capability, graph, client, Auto, template, or Gallery surface is added. - Task-scoped terms acceptance and legal/product approval, authenticated - artifact review, remote heavy-hardware safety/quality qualification, and - physical macOS execution remain independent gates. - - [x] **JoyAI Image Edit and Edit Plus source admission:** backend - `6d507eb` and client `b1cbde7` admit the exact public snapshots - `jdopensource/JoyAI-Image-Edit-Diffusers@4b41fb25d961f37668750178ccbb380da326201c` - and - `jdopensource/JoyAI-Image-Edit-Plus-Diffusers@c2686460c7b64d8aa11bc4d0da423fb316b33f9e` - through the generic Diffusers image facade. Both selected partitions are - ungated, use twelve BF16 safetensors files, contain 16,263,675,968 - parameters, and require no repository Python or remote code. Their exact - 50,315,602,078-byte and 50,315,602,038-byte weight inventories, immutable - repository metadata, component identities, and pinned package pipeline, - image-processor, transformer, VAE, scheduler, and output source hashes are - sealed in `data/joyimage-artifact-review.json` without downloading weight - bytes. Both cards declare Apache-2.0 but link to an absent repository - license file; the immutable upstream project's complete Apache-2.0 receipt - is recorded while exact weight-snapshot license clarification remains - explicit. - - The basic Expert-only route supports text-to-image and exactly one source - image; Edit Plus supports one to five references and maps the generic input - to the package's plural `images` argument. Both routes preserve the - package's 1024-base aspect buckets, cap each output at 1,048,576 pixels and - each side to 512-2048 in 32-pixel increments, cap text at the encoder's - effective 2,048-token ceiling, and use guidance 4 with 40 or 30 steps. - Generic generation now reports the actual decoded bucket dimensions rather - than merely echoing the requested aspect hint. The package callbacks, - interrupt surface, and sequential CPU offload are retained. The missing - safety checker and unmeasured approximately 50.32 GB runtime keep Auto and - Gallery disabled. - - The expanded Transformers/Diffusers symbol contract passed clean-base - locked installation, validation, activation, a finite fresh-process - CLIP+PEFT workload, rollback, and clean restoration on Linux x86-64 at - profile digest - `sha256:7fc2a03926b2a0d5fdee79c3178fe240707375d6b1ab2549bac4b2339220838c`. - The 1,553-byte evidence has SHA-256 - `12af06fe33d3e36315783bb21f60fd5c1ea12c457622d743e94d05b935670604` - and retained no managed state. Four canonical graphs bring the deterministic - catalog to 121 supported workflows; runtime execution remains unqualified. - The complete 1,555-test backend overlay with 3,398 subtests and three - platform skips, Ruff E9/F, deterministic workflow verification, and the - complete client check pass. The intentional profile surface measures - 530,495 compressed JavaScript bytes under a still-sub-KiB 531,456-byte - ceiling. Remote real-weight memory/output safety/quality review, model-card - license-file clarification, and physical macOS execution remain pending - independently; no weights or media were downloaded or retained. - - [x] Evaluate the remaining large image and cascaded families independently - at the source/admission tier. Every standard image family in Appendix B now - has an immutable source admission or an explicit fail-closed gate review; - this closes inventory research only. The parent heavy-hardware item remains - open for exact remote real-weight execution, output review, and physical - macOS evidence, and no static receipt is treated as live proof. -- [x] Complete an explicit immutable-code security review and keep LLaDA2 - blocked. Backend `444152a` seals the public - `inclusionAI/LLaDA2.1-mini` snapshot at - `20e64e2ad21644d0e5248586ed9c942cdd45de0f`, its exact eight-file / - 32,513,130,952-byte safetensors inventory, configuration and index hashes, - pinned Diffusers pipeline/scheduler source hashes, and both repository Python - blobs. Full visual and static AST/string/import review found no evident file, - network, process, unsafe-deserialization, dynamic-import, or dynamic-code - primitive in those two immutable files. That is narrow evidence rather than - a claim of runtime safety: importing the implementation mutates Transformers' - process-global layer-normalization registry, and static review cannot bound - dependencies or resource consumption. - - The official loading route still requires `trust_remote_code=True`, which - would execute repository Python with all backend-process permissions. The - pinned pipeline validates positive values but supplies no backend-owned upper - bounds for prompt length, generation/block length, step counts, output size, - or cooperative cancellation. This roadmap request is not fresh task-scoped - operator authorization for that exact code. Accordingly no runtime/download - catalog, capability, workflow, client, Auto, template, or Gallery surface was - added. Explicit task-scoped authorization, bounded adapter controls, remote - heavy-hardware execution, and physical macOS evidence remain independent - gates. -- [x] Build the 30-minute video workflow only after chunk generation, checkpoint - resume, deterministic stitching, audio mux, cancellation, and recovery pass - independently. - - [x] **Continuation boundary handoff:** backend `67010c7` makes the existing - generic long-video planner require and bind an opening image for continuous - LTX/Wan/FramePack plans, rejects unknown strategies, and gives the generic - shot executor an optional previous-segment input. Only jobs carrying the - explicit `uses_previous_last_frame` marker extract that segment's final - frame as the next opening anchor; a missing prior segment fails before - inference. A synthetic graph-level collection loop proves that iteration 2 - receives iteration 1's exact boundary through the normal carry contract. - The focused video/loop matrix passes 129 tests and 216 subtests; the - complete backend overlay passes 1,640 tests, 3,580 subtests, and three - platform skips, with Ruff E9/F and package compatibility green. No model, - media, or live inference was used. Durable process-restart checkpoints are - closed by the later P6.57 slice; retained-asset stitching, - audio mux/cancellation recovery, the actual 30-minute graph, remote - execution, and physical macOS evidence remain independent gates. - - [x] **In-process checkpoint, stitching, mux, and cancellation recovery:** - backend `53e22f2` revalidates the generic implementation originally landed - in `76bbafe` with stronger synthetic integration evidence. A collection - loop is interrupted after committing segment 1, clears its node cache, and - resumes at segment 2 without regenerating the completed result. Two real - temporary eight-frame MP4 segments are retained, joined through the - bounded-memory FFmpeg path with an exact two-frame transition into a - 14-frame/1.75-second result, then muxed with generated silent audio while - preserving the video duration and frame count. The focused component gate - passes 135 tests and 216 subtests; the complete backend overlay passes - 1,642 tests, 3,580 subtests, and three platform skips, with Ruff E9/F and - package compatibility green. Test media existed only in the temporary test - directory. Process-restart persistence is closed by the later P6.57 slice; - the actual 30-minute graph, remote six-hour execution, asset publication, - and physical macOS evidence remain pending. - - [x] **Bounded 30-minute chunk planning:** backend `88b5d33` extends the - generic planner's declared and enforced duration ceiling from 600 to 1,800 - seconds while retaining a separate 600-second ceiling for unqualified - single-job FramePack output. A 16-FPS LTX continuation plan using - five-second legal `8k+1` chunks and a 0.25-second overlap deterministically - yields 374 jobs, 28,802 planned frames, and 1,800.125 seconds. Every job - carries explicit FPS, width, height, steps, guidance, conditioning - strength, negative prompt, seed, and continuation state. The planner caps - jobs at 512 by default (10,000 hard maximum) and rejects duration, overlap, - control, strategy, or job-count violations before inference. The focused - component gate passes 137 tests and 216 subtests; the complete backend - overlay passes 1,644 tests, 3,580 subtests, and three platform skips, with - Ruff E9/F and package compatibility green. No model or media was used. The - process-restart persistence is closed by the following slice; the - executable 30-minute graph, remote six-hour run, asset publication, and - physical macOS evidence remain pending. - - [x] **Durable retained-segment restart recovery:** backend `d648417` and - client `19620f9` add an explicit `durable` visual-loop contract for - file-backed video segments. The Studio loop UI exposes the opt-in, the - managed quality-video sequence seals it into its schema-v3 graph proof, - and the API export carries it without making ordinary loops durable. - Durable execution requires the exact `workflowTabId` plus `runInputHash`, - accepts only bounded retained-video metadata whose file remains inside - MoDiff's managed media directory, and atomically commits each successful - iteration. A synthetic process-replacement test interrupts after segment - 1, constructs a new server with a different task ID, restores the exact - input-scoped checkpoint, and executes only segment 2. Successful graph - completion deletes checkpoint metadata; interrupted or failed exact-input - runs retain it for recovery. The focused backend video/runtime matrix - passes 205 tests and 246 subtests; the complete backend overlay passes - 1,646 tests, 3,580 subtests, and three platform skips, with Ruff E9/F and - package compatibility green. The complete client gate, the focused - controlled-workflow browser proof, and the 530,915-byte total gzip budget - pass. Test files existed only under a temporary directory. The executable - 30-minute graph, remote six-hour run, asset publication, and physical macOS - evidence remain pending. - - [x] **Executable 30-minute qualification graph:** backend `479d495`, with - schema-boundary fix `a9cf15c`, adds a qualification-only API graph - template for the exact immutable - `Lightricks/LTX-Video-0.9.8-13B-distilled` revision. It binds the BF16 - `LTXConditionPipeline`, eight-step/guidance-one recipe, opening image, - 1,800-second planner, 374-job durable continuation loop, retained pinned - segment export, 0.25-second file-native join, and six-hour runtime ceiling - in one app-submittable graph. Every connection resolves to a registered - generic node and declared output. - - The companion materializer requires the local opening-image path and its - portable app identifier to resolve to the same bytes, seals their digest - into the input-scoped recovery identity, and accepts submission only with - explicit long-run consent to a loopback app. Before `POST /graph` it also - requires the app cache to report the exact immutable LTX revision complete - and repair-free. Five direct graph/materializer tests plus the focused - video/loop matrix passes 144 tests and 219 subtests; the complete backend - overlay passes 1,651 tests, 3,583 subtests, and three platform skips, with - Ruff E9/F and package compatibility green. A CLI smoke materialized the - graph only to `/tmp`; it did not submit the graph, run inference, generate - media, or download a model. The approximately six-hour remote run, - output/safety/continuity review, asset publication, and physical macOS - evidence remain pending. - -### Phase 6 test and asset gate - -- [x] No Phase 6 live run occurs on the current development machine. -- [x] Heavy integrations can merge contract-only while clearly Expert-only and - `qualification_pending`. -- [x] Long-form component tests use synthetic/tiny segments. -- [ ] The approximately six-hour 30-minute-video qualification runs once as a - scheduled release test after all component gates pass. -- [ ] Generated video and receipts are published through the remote asset - workflow; no media is committed to Git. - -### P2-P6 source-scope audit correction (2026-08-15) - -The earlier wording that all locally identifiable work through P6 was complete -was too broad. It applied only to the finite slices already enumerated in those -phases, not to the wider goal of classifying and covering every current -Diffusers/Transformers semantic and researching a complete template catalog. - -The generic task/media architecture now has 186 exact backend execution-spec -and task-planning pairs. It covers generic image, video, audio, unconditional, -perception, 3D, speech, bounded causal-text, image/video-to-text, and -model-specific bounded AnyToAny actions; model identities and artifacts remain -separate from those task boundaries. The regenerated canonical library contains -198 unique, deterministic, independently verified graph files: the 186 base -pairs plus 12 retained variants. All 198 are executable at source/graph level. - -The 77 public templates pass their structural contracts and cover 51 canonical -workflows. Every one of the other 147 workflows has an exact Gallery-hidden, -zero-asset candidate contract: 99 image, 33 video, nine JSON, and six audio; -92 require reviewed input examples before promotion. These are authoring -contracts, not generated assets or public Gallery claims. - -The generated full-coverage ledger classifies every one of the 327 exported -non-Flax Diffusers pipeline symbols: 116 executable, 15 exact equivalents, 20 -contract-only, 120 research-blocked, 56 intentionally excluded, and zero -unreviewed. Its six finite Transformers semantics contain four executable and -two research-blocked entries. This is complete inventory and classification, -not a claim that the 140 non-executable Diffusers classes have safe artifacts, -bounded MoDiff actions, or live qualifications. Their primary blockers are -finite: 54 require a new, unadmitted, or incompatible base/auxiliary artifact -(46 research, eight contract); 46 are blocked by legal, gating, territory, or -safety constraints (41 research, five contract); 34 lack an exact safe -assembly/index/serialization/source contract (27 research, seven contract); -and six still lack bounded generic pre/post-processing or output contracts. - -The final 2026-08-15 source wave added 35 execution pairs without removing a -previous pair: 12 combined source/mask/control-image routes; five extended -AnimateDiff/CogVideoX video routes; five direct Qwen control, layered, edit, and -Edit-Plus routes; three Janus AnyToAny routes; Chroma image-to-image plus -inpaint/outpaint; FLUX.2 Klein, FLUX Kontext, and Z-Image inpaint/outpaint; and -direct LTX2 text-to-video with synchronized video and audio. Earlier same-day -work also admitted generic Transformers text/image-to-text, PAG, LCM, and -Stable Diffusion routes. At the reviewed pin, the 34 Modular classes now -partition into 11 runnable, 19 contract-only, and four exact equivalents -without distinct runnable Modular profiles. - -The official Comfy research foundation pins -`Comfy-Org/workflow_templates` at -`d9e66019b85da231b7c936ad9cb7ff08cec16557`, inventories all 581 templates and -93 blueprints, and records conservative semantic evidence without importing or -executing Comfy graphs. A separate fail-closed authoring-research ledger covers -308 records: 217 templates and 91 blueprints. It contains 54 existing-MoDiff -mapping candidates (39 templates and 15 blueprints), 138 source-review -proposals originally recorded as `new_contract_not_authored`, and 116 -`contract_undetermined` records. The later exact source-resolution ledger maps -110 of those 138 proposals to current MoDiff workflows. Eighteen are same-family -candidates, 90 reuse a task boundary but require a distinct model admission, -two are model-free built-in operations awaiting algorithm-parity review, and -28 still require a new bounded task boundary. The image-stitch proposal is now -the first of those model-free task gaps implemented as an original hidden MoDiff -workflow. Its local technical candidate has been generated but remains pending -human quality and rights review, and it is not claimed equivalent to the Comfy -graph. -All 138 of those candidates now also have exact pinned graph-source -dependency reviews, performed read-only without importing, executing, or -copying their graphs, nodes, or prompts. The two Stable Audio 3 records and the -original Qwen-Image-plus-Lightning record resolve only to existing task -boundaries and still require distinct model-generation admissions; the Chroma record remains -a same-family candidate because its FP8 repackaged component partition is not -the admitted Diffusers checkpoint. The second review tranche likewise keeps -Chroma1 Radiance, Z-Image Base/Base-Int8, and both FLUX.2 Klein 9B generations -outside the currently admitted Chroma1-HD, Z-Image-Turbo, and FLUX.2 Klein 4B -partitions. Pinned graph structure also corrects both Klein 9B records from the -catalog's text-to-image tag to the existing generic image-edit task boundary. -The Qwen tranche maps two 2511 LoRA graphs to the already admitted Edit-Plus -generation while retaining explicit auxiliary-artifact gates, retains the 2509 -FP8-plus-Lightning graph as a non-equivalent same-generation partition, and -separates Qwen Layered Control from the admitted Layered model. It also corrects -the Qwen Union Control record from text-to-image to the existing control-image -boundary, but does not equate its original-Qwen/DiffSynth assembly with MoDiff's -2512/InstantX Union assembly. Six LTX reviews retain the current text/image-to- -video task boundaries but separate the old 0.9/0.9.5 checkpoints and the 2.3 or -2.5 22B component assemblies from MoDiff's admitted -`Lightricks/LTX-2@47da56e2ad66ce4125a9922b4a8826bf407f9d0a` partition. -Three ACE-Step reviews separate the pinned v1 3.5B checkpoint from MoDiff's -admitted v1.5 XL Turbo generation: song and instrumental generation can reuse -the text-to-audio boundary after a model admission, while music-to-music still -needs a new bounded audio-edit contract as well. -Six Wan reviews correct the old 2.1 image-to-video record, identify three Fun -Camera graphs as a new camera-motion task, and map Fun Control to the existing -generic `control_video_to_video` boundary without equating it to AnimateDiff. -Their 2.1/2.2 repackaged model, VAE, LoRA, and control assemblies remain separate -admission work rather than inherited support. -Five further Qwen reviews recover two 2512 LoRA concepts from an incorrect -AuraFlow fallback and map them to the admitted Qwen generation while retaining -partition/LoRA gates. The original-Qwen Canny patch and InstantX inpainting -assembly reuse existing control/inpaint boundaries without inheriting 2512 -support, and the 2509 Relight concept remains an unadmitted auxiliary variant. -Six additional image reviews separate ERNIE Image from ERNIE Turbo, Kandinsky 5 -from Kandinsky 3, and OmniGen 2 from OmniGen v1. They also correct Lotus from a -text-to-image match to the generic depth-estimation boundary and map the -NetaYume fine-tune to Lumina 2 rather than OmniGen, without admitting either -checkpoint. -Three more FLUX.2 reviews keep 9B KV/base and FLUX.2 Dev outside the admitted -Klein 4B generation. The SDXL refiner concept retains the admitted base but -requires a new refiner stage/artifact, while the graph named “revision prompts” -is source-proven unCLIP image conditioning and therefore needs a new bounded -`reference_to_image` task plus CLIP-Vision input. -Five remaining LTX reviews separate a 2.3 reference-LoRA assembly, identify the -2.3 IA2V graph as a new `image_audio_to_video` task, and retain three LTX-2 -camera/squish/distillation concepts as same-generation candidates gated on -their LoRAs and latent upscalers. -Five motion and angle reviews retain both Qwen multiple-angle concepts as -same-generation candidates gated on their exact LoRA and Lightning assemblies, -separate causal-forcing framewise I2V from the admitted Wan generation, and -map Wan 2.2 Fun Control to the generic `control_video_to_video` boundary without -equating its model. WanMove is source-proven to require a new bounded -`motion_track_to_video` task and its distinct motion/step-distillation model -assembly. -The remaining five Wan family reviews correct Fun Inpaint to first/last-frame -generation, VACE Ref2V to reference-conditioned video, and both VACE V2V and -Wan 2.1 Fun Control to control-video tasks. Fun Inpaint can reuse the exact -two-endpoint task boundary already sealed by -`WanImage2VideoModularPipeline:image_to_video`; its 5B model remains a distinct -admission. The VACE 14B graphs retain their separate component partition and -CausVid LoRA gates; none inherits support or qualification from the admitted -1.3B VACE checkpoint. -Six utility-model reviews retain FILM interpolation, SeedVR2 image/video -upscaling, and BiRefNet background removal as distinct artifact or task -admissions. The PixelDiT graph is source-corrected from image upscaling to -Z-Image text generation followed by an unadmitted PixelDiT enlargement stage. -Four LLM reviews leave Qwen3 as bounded causal text generation, correct Qwen3.5 -and Qwen3-VL to image-conditioned text, and identify Gemma4's connected image -plus audio inputs as a new bounded `image_audio_to_text` task. Their Comfy -single-file text-encoder artifacts remain separate model admissions. -MiniMax Music 3 remains a distinct text-to-audio model admission. The three H3 -graphs are source-proven to decode synchronized audio as well as video, so their -video-only catalog modes are corrected to `text_to_video_with_audio`, -`image_to_video_with_audio`, and `reference_to_video_with_audio`. Those bounded -tasks and runnable workflows remain unauthored. -Three Wan audio-driven video reviews retain InfiniteTalk, S2V, and Wan Dancer -as separate model/patch/audio-encoder admissions behind the unauthored -`audio_to_video` boundary. Two SCAIL-2 variants reuse only the generic -`character_replace` task while retaining their SCAIL, SAM3, LoRA, vision, and -VAE dependency gates; they are not Wan Animate support claims. -Two VACE reviews correct image-labelled records to video inpaint/outpaint and -reuse the admitted VACE task boundaries while retaining the 14B, CausVid, and -SAM3 gates. Wan Fun `inp` is source-corrected to first/last-frame generation -and reuses only the existing two-endpoint task contract, whereas Anima LLLite -remains a distinct image-inpaint model/patch admission. Seven endpoint- -conditioned video reviews retain four Wan first/last-frame model generations -behind distinct model admissions while reusing the current canonical FLF task -boundary. The three LTX 2.3/2.5 graphs also decode synchronized audio, so they -are corrected to the separate -`first_last_frame_to_video_with_audio` task and retain their exact checkpoint, -text-encoder, VAE, and optional transition-LoRA gates. -Five further video reviews retain classic Hunyuan Video, HunyuanVideo 1.5 -T2V/I2V, and Kandinsky 5 T2V/I2V behind the existing generic video task -boundaries while requiring their distinct transformer, encoder, vision, and -VAE admissions. Allegro and LTX2 support is not inherited by those graphs. -Six composite/edit reviews retain Capybara I2V and Bernini image editing behind -existing task boundaries, while both model families still need new bounded -video-edit tasks. HuMo is corrected from plain T2V to -`image_audio_to_video`, and the SDXL-to-SVD composition remains a distinct -two-stage text-to-video admission rather than Allegro support. -Five HiDream reviews retain E1 edit and I1 dev/fast/full generation behind the -existing image task boundaries while requiring their distinct transformer and -shared four-encoder/VAE assembly. No HiDream variant inherits Chroma or -AuraFlow support. -Seven Capybara, Anima, and Boogu image reviews retain their edit or -text-to-image task boundaries while requiring distinct model-generation -admissions. Capybara combines its transformer with Qwen, ByT5, HunyuanVideo -VAE, and SigCLIP components; Anima combines its base or preview transformer -with Qwen and Qwen-Image VAE components; Boogu uses separate FP8, INT8, or -Turbo transformer partitions with HiDream VAE and Qwen3-VL encoding. None of -those assemblies inherits support from the nearest Chroma or AuraFlow route. -Sixteen further image reviews retain the ChronoEdit, FireRed, Ideogram 4, -Krea 2, Lens, Mage-Flow, NewBie, PixelDiT, and Stable Diffusion 3.5 -generations behind existing generic task boundaries while requiring their -exact model/component admissions. The pinned Mage-Flow Turbo T2I graph -actually loads the base INT8 transformer, so the ledger records the connected -artifact instead of inferring a Turbo checkpoint from its title. The SD3.5 -Blur graph is source-corrected from image editing to control-image generation; -it does not inherit SD1.5 Canny support. -The two model-free utility reviews separate a four-input image-stitch task from -the catalog's generic edit label and identify the purported interpolation -upscale as a built-in two-times Lanczos resize. MoDiff now has its own bounded -2-to-64-input stitch contract and canonical workflow, while exact Comfy -algorithm/output parity, input rights, execution, and quality remain pending; -the resize record likewise needs exact algorithm/output parity review rather -than a model artifact admission. Seven final 3D reviews -retain Hunyuan3D 2.0/2.1, MoGe panorama/perspective mesh conversion, and -TripoSplat image-to-splat/mesh generation behind the unauthored bounded -`image_to_3d` boundary with their exact model and export dependencies. -No Comfy graph, node package, prompt, model, or media asset was copied or -downloaded. - -Historical Gallery evidence discovery preserves all 70 approved examples. The -current release audit finds 26 records whose retained evidence remains current -and 44 stale records for targeted reconciliation; seven templates have no prior -example. Eleven templates and 11 workflows already have complete release -receipts. Visual approval remains attached to the reviewed bytes, while missing -duration, peak-memory, graph, prompt, model-revision, or node-lock proof is never -invented. Do not blanket-rerun approved examples: first reconcile exact bytes, -graph and prompt locks, model revision, node contract, and receipt; regenerate -only changed or unmatched cases plus representative canaries. - -The regenerated release report is correctly blocked rather than empty: 66 of -77 templates and 187 of 198 workflows lack complete release evidence, and none -of the 51 advertised resource recipes has a measured physical qualification -receipt on this host. Source completeness, prior visual approval, asset -authoring, and physical release qualification remain separate claims. - -A separate ignored local technical-review receipt now captures 38 completed app -workflows and 42 byte-hashed outputs. It includes all 20 model-free built-in -image, audio, video, and JSON candidates; generic SmolLM2 text generation and -SmolVLM image-to-text; and 16 real-model workflow canaries: SD1.5 text, edit, -and Canny ControlNet; LCM text and edit; DreamLite Mobile text and edit; SD1.5 -PAG; Sana Sprint text; Marigold depth; LongCat AudioDiT; Shap-E; Consistency, -DDIM, and DDPM unconditional generation; and SDXL Turbo. The media partition is -four audio, 27 image, six video, and five JSON outputs. Every receipt remains -explicitly pending human quality and rights review, with Gallery approval, -publication, and release eligibility false. - -The SmolLM2 run exposed and fixed a real generic-node gap: causal text -generation now optionally applies a bounded tokenizer chat template before -tokenization. The rerun follows the instruction checkpoint's chat format but -its 135M output is still a weak/truncated technical candidate, not an approved -example. SmolVLM produced a bounded description of the local procedural test -frame and is likewise pending review. The later image batch contains several -strong review candidates, but visual inspection here is still not human -approval: notably the SD1.5 PAG reading room, SDXL Turbo portrait, LCM and -DreamLite Mobile reading-room edits, DreamLite Mobile text output, Sana Sprint -botanical sheet, and SD1.5 control/edit samples. LongCat's 4.95-second 24 kHz -WAV is format- and signal-validated but has not received listening-based human -review. The 64 px Shap-E orbit is deliberately a low-resolution technical -preview rather than a Gallery-quality asset. - -The same live campaign preserved negative evidence instead of manufacturing -receipts. LongCat's advertised VAE slicing and tiling paths both failed in the -exact upstream VAE and were disabled before the successful rerun. A 64-step, -256 px Shap-E CPU orbit completed its diffusion prior but its non-cooperative -renderer exceeded the declared 30-minute ceiling; the app forcibly replaced -the supervised worker and no output was retained. A bounded 32-step, 64 px -retry completed in 382.86 seconds. DreamLite Mobile edit failed with bfloat16 -on CPU because the upstream timestep embedding remained Float; the same exact -graph and weights completed in float32 in about 24 seconds. A Sana Sprint edit -that materially changed the requested scene was retained only as a local -negative output and was not added to the receipt ledger. The exact fp16 -AnimateDiff text-to-video route then remained inside its upstream denoiser for -more than the declared 15-minute CPU ceiling at the minimum eight frames and -eight steps. The app replaced the non-cooperative supervised worker, released -the approximately 6.7 GB process RSS, retained no partial video, and preserved -the active optional runtime; this is measured evidence that the route still -needs accelerator execution rather than a reason to weaken its contract. - -App-only model provisioning completed exact SmolLM2, SmolVLM, and Janus -selections totaling 4,951,475,788 bytes without deleting an older model. The -separate pending campaign still contains ten exact selections totaling -408,762,812,498 bytes (408.763 GB). The latest 2026-08-15 live filesystem -snapshot reports 68,909,105,152 free bytes against the app's -68,719,476,736-byte reserve, leaving only 189,628,416 bytes before queue and -staging accounting; the app download queue is idle and no pending selection -currently fits. Stable Video -and Stable Audio additionally remain access-gated after prior HTTP 403 results -with no configured token. No direct download or reserve-bypassing submission is -permitted. - -Closeout references: backend `d6b9069` relocks Transformers main, `94d5e09` -records the app-only Janus cache receipt, `ccaf0d1`/`1df742a`/`f129a03`/ -`161710f`/`e9477bd`/`1272d31` contain the final generic and exact-class source -closure, `b2d57ac` seals the 177-workflow library, and `5e2aae9` seals coverage -and hidden candidate contracts. Client `fdadc34` bridges the final media routes, -`01d531f` admits the authoritative supported runtime state, `b969a4e` makes -canonical generation crash-bounded, and `f893dd6` preserves per-mode output -media. Comfy inventory and fail-closed authoring contracts are recorded by -backend `d9fb0a0` and `832e254`. - -The post-closeout loader and review follow-ups are backend `01dbe05` (exact -generic loader identities), `02c31c9` (bounded causal-text chat templates), and -`0214e53` (the resulting 197-workflow graph and derived-ledger reseal). The -older `b2d57ac`/`5e2aae9` references above remain historical milestones rather -than the current generated-data head. The subsequent live campaign fixes are -`47e5fb1` (mode-aware exact loader-profile resolution), `b98250a` and `da37883` -(LongCat VAE slicing/tiling exclusions), and `accef77` (the regenerated LongCat -graph and all bound coverage, candidate, authoring, and Comfy ledgers). -`4f53667` makes malformed submitted runtime receipts fail as bounded HTTP 400 -admission errors rather than uncaught HTTP 500 responses. `4b65aea` then turns -every admitted graph `maxRuntimeSeconds` budget into an automatic worker -deadline: it waits safely behind app-managed model I/O, requests cooperative -node/pipeline interruption, records `runtime_deadline_exceeded`, and persists -that failure before supervised replacement if third-party code does not return. -The restarted live app returned the malformed-receipt canary as HTTP 400 and -completed a 60-second-budget normal graph, while the 149-test queue, loop, -supervisor, security, and model-I/O matrix passed with one expected skip after -updating the affected test doubles. `6796b74` seals the first four exact pinned -Comfy graph-source dependency reviews, and `f80f83e` adds five model-partition -decisions plus the two source-proven Klein task corrections, while retaining -fail-closed model, execution, rights, and asset claims throughout. `40adea2` -then seals five Qwen source decisions, including exact workflow-generation and -task-boundary corrections without claiming auxiliary or checkpoint admission. -`5af255a` adds six LTX generation decisions without weakening model, component, -license, execution, or asset gates. `c756949` adds the three exact ACE-Step v1 -dependency decisions and keeps audio editing explicitly unauthored. `dfe18ec` -adds the six Wan source decisions and three new camera-task requirements. -`9230b11` adds five Qwen generation/control decisions and two source-proven -workflow-family corrections. `dd5591e` adds six exact image-generation and -depth-task decisions. `ac7c320` adds the remaining FLUX.2 family decisions and -the two SDXL refiner/unCLIP task distinctions. `0a48984` adds the five remaining -LTX family and image-audio conditioning decisions. `a81bd03` adds the two Qwen -multiple-angle, causal-forcing, Wan Fun Control, and WanMove decisions. -`e6205d1` finishes the five Wan/VACE family source reviews and their task -corrections. `527709b` adds the six FILM, SeedVR2, PixelDiT, and BiRefNet -utility-model decisions. `9d62715` adds the four causal and multimodal text -decisions. `80f86fd` adds MiniMax Music 3 and corrects all three H3 output/task -contracts to video plus audio. `04c0445` adds the three Wan audio-driven video -and two SCAIL-2 character-replacement decisions. `f7bf0b1` adds the four VACE, -Wan Fun, and Anima inpaint/media decisions. `24efba9` adds all seven endpoint- -conditioned Wan/LTX decisions and preserves the LTX audio output contract. -`b433ff8` adds the five classic Hunyuan, HunyuanVideo 1.5, and Kandinsky 5 -video-generation decisions. `b091868` adds the six Capybara, Bernini, HuMo, -and SDXL-to-SVD composite decisions. `5f60154` adds the five HiDream E1/I1 -variant decisions. `b8a884a` adds the seven Capybara, Anima, and Boogu image -model decisions. `620dc9a` adds the sixteen ChronoEdit, FireRed, Ideogram 4, -Krea 2, Lens, Mage-Flow, NewBie, PixelDiT, and SD3.5 decisions. `45e1d72` -closes the remaining model-free utility and seven 3D source reviews, so every -one of the 138 proposed contracts has pinned graph-source evidence. -Backend `d827762`, client `1067a90`, and backend `411eff2` then implement and -canonically generate the original bounded image-stitch task. Client `07f98db` -and `355f847` make the 198-workflow generator resilient to transient Chromium -launches and normalize backend collision suffixes plus connected-field UI -state without changing execution values or topology. Backend `3275f22` reseals -upstream coverage, all 147 hidden candidate/authoring contracts, and the Comfy -resolution ledger against that exact graph library. - -The first app execution of that stitch graph then found a real generic-facade -output mismatch: `StitchImages` returns diagnostic `rows` and `count` fields, -while `ProcessImage` declares only its stable `output`. Backend `8eb958e` now -normalizes that internal result to the declared facade contract and locks every -generic image-operation result to exactly one output key. The app-only retry -completed on CPU in 0.0404 seconds at 438,063,104 bytes process RSS and saved -the ignored technical candidate -`data/images/campaign-image-stitch-v1.png` (1,040 by 592 RGB PNG, 81,600 bytes, -SHA-256 `6df5aba74e28bdb8bb00a702ef35485b8d59c6327644c9cb298dd1f205289b22`). -Its receipt remains pending human quality and rights review; it is not a -Gallery, publication, or release-eligibility claim. - -Backend `c8bae74` also adds the explicit no-rerun evidence path used here: it -loads each receipt's historical graph from an exact Git commit, verifies the -retained canonical graph hash, compares a bounded execution-only projection, -and rebinds only when modules, actions, node enablement, topology, and parameter -values remain identical after portable data-reference normalization. It keeps -the previous graph hash, source commit, projection version, and semantic hash -in the receipt. Twenty-eight of the 37 retained pre-stitch receipts reconciled -this way; the other nine graph hashes were already current. Any execution drift -fails closed and still requires a real rerun. - -The following separate gates do remain external: - -- P2.5 and the Phase 3-5 asset gates require remote real-weight execution, - human quality/rights review, and immutable Dataset publication. -- P3.2b requires authenticated access to the official Stable Diffusion 2.x - repositories before exact artifact admission. -- P4.1b and P4.5 publish only legacy unsafe `.bin` weights at their reviewed - revisions; no serialization exception or unreviewed conversion is approved. -- Phase 5 live proof requires a qualifying remote accelerator. This host has no - CUDA, XPU, or MPS device and is not a valid substitute for that evidence. -- P6 remaining gates require accepted model terms, legal/product approval, - sufficient dedicated remote hardware, the approximately six-hour long-form - run, output review/publication, or physical macOS evidence. - -These gates remain unchecked. Static, mocked, dry-run, cache-readiness, and -CPU-smoke evidence must not be promoted into live output or release claims. -A qualified accelerator means a supported device and runtime with sufficient -RAM, VRAM, and disk that has completed the exact pinned model/recipe workload -and produced a measured result receipt. Merely having a GPU is not -qualification. The earlier NVIDIA evidence was one Windows 64-by-64, one-step -Qwen guard canary; it was not broad P0-P6, asset-quality, or resource-envelope -evidence. Source, graph, template-contract, and static security work can -continue on this Linux AMD machine's CPU runtime, while CUDA output/resource -qualification and physical macOS evidence remain separately postponable. - -## Per-integration admission checklist - -Complete this research before implementing any pipeline or model entry: - -- [ ] Confirm the class and workflow exist at the pinned Diffusers or approved - Transformers revision. -- [ ] Record the exact call signature, required inputs, optional inputs, and - return type. -- [ ] Identify the generic MoDiff task/media contract and necessary aliases. -- [ ] Inspect every model and auxiliary repository at an immutable revision. -- [ ] Record license, gating, remote-code, serialization, and redistribution - constraints. -- [ ] Record stored artifact size and a conservative RAM/VRAM/disk envelope. -- [ ] Use only upstream-supported loaders, adapters, schedulers, and offload - hooks. -- [ ] Define failure behavior and finite lower-resource retries. -- [ ] Define static, mocked/tiny, integrated, live, and asset evidence. -- [ ] Decide local-allowlisted or remote-only before downloading weights. - -## Appendix A — Classified non-runnable Modular classes - -The reviewed pin exports 34 Modular classes and 94 upstream workflows. Eleven -classes have at least one runnable MoDiff mode. Of the other 23, 19 are -registered contract-only with reviewed no-weight workflow contracts and four -are exact task equivalents without a distinct runnable Modular profile: -`ErnieImageModularPipeline`, `LTXModularPipeline`, `Wan22ModularPipeline`, and -`Wan22Image2VideoModularPipeline`. This inventory is not an artifact, template, -live-execution, or qualification claim. - -Reviewed classes from the initial current-pin inventory: - -- [x] `AnimaModularPipeline` -- [x] `Cosmos3OmniModularPipeline` -- [x] `Cosmos3DistilledModularPipeline` -- [x] `ErnieImageModularPipeline` -- [x] `Flux2ModularPipeline` -- [x] `Flux2KleinBaseModularPipeline` -- [x] `HeliosModularPipeline` -- [x] `HeliosPyramidModularPipeline` -- [x] `HeliosPyramidDistilledModularPipeline` -- [x] `HunyuanVideo15ModularPipeline` -- [x] `Ideogram4ModularPipeline` -- [x] `LTXModularPipeline` -- [x] `StableDiffusion3ModularPipeline` -- [x] `Wan22ModularPipeline` -- [x] `Wan22Image2VideoModularPipeline` - -Later classes registered and reviewed by MoDiff: - -- [x] `Krea2ModularPipeline` -- [x] `Krea2TurboModularPipeline` -- [x] `MiniMaxH3ModularPipeline` -- [x] `LTX2ModularPipeline` -- [x] `LTX25ModularPipeline` -- [x] `WanAnimate2ModularPipeline` -- [x] `WanAnimate2DistilledModularPipeline` -- [x] `MiniMaxMusic3ModularPipeline` - -## Appendix B — Missing standard pipeline families - -This is a family inventory, not a requirement to create one node per family. - -### Audio - -- [x] `audioldm2` -- [x] `longcat_audio_dit` - -### Text diffusion - -- [x] `diffusion_gemma` -- [x] `llada2` - -### 3D and perception - -- [x] `shap_e` -- [x] `marigold` -- [x] `visualcloze` - -### Video - -- [x] `allegro` -- [x] `animatediff` -- [x] `anyflow` -- [x] `chronoedit` -- [x] `cogvideo` -- [x] `consisid` -- [x] `cosmos` -- [x] `easyanimate` -- [x] `helios` -- [x] `hunyuan_video1_5` -- [x] `kandinsky5` -- [x] `latte` -- [x] `lucy` -- [x] `mochi` -- [x] `motif_video` -- [x] `sana_video` -- [x] `skyreels_v2` -- [x] `stable_video_diffusion` - -### Image, unconditional, and generic - -- [x] `aura_flow` -- [x] `bria` -- [x] `bria_fibo` -- [x] `chroma` -- [x] `cogview3` -- [x] `cogview4` -- [x] `consistency_models` -- [x] `controlnet` -- [x] `controlnet_hunyuandit` -- [x] `controlnet_sd3` -- [x] `ddim` -- [x] `ddpm` -- [x] `deepfloyd_if` -- [x] `dit` -- [x] `dreamlite` -- [x] `ernie_image` -- [x] `glm_image` -- [x] `hidream_image` -- [x] `hunyuan_image` -- [x] `hunyuandit` -- [x] `ideogram4` -- [x] `joyimage` -- [x] `kandinsky` -- [x] `kandinsky2_2` -- [x] `kandinsky3` -- [x] `kolors` -- [x] `krea2` -- [x] `latent_consistency_models` -- [x] `latent_diffusion` -- [x] `ledits_pp` -- [x] `longcat_image` -- [x] `lumina` -- [x] `lumina2` -- [x] `nucleusmoe_image` -- [x] `omnigen` -- [x] `ovis_image` -- [x] `pag` -- [x] `pixart_alpha` -- [x] `prx` -- [x] `sana` -- [x] `stable_cascade` -- [x] `stable_diffusion` -- [x] `stable_diffusion_3` -- [x] `t2i_adapter` - -## Completion ledger - -Add references only after the corresponding evidence exists. - -| Segment | Backend reference | Client reference | Live proof | Dataset revision | Status | -| --- | --- | --- | --- | --- | --- | -| P0.1 | `91c9a36` | `28b12b7` | Not required | Not required | Complete: exact-pair capability and stale-form execution checks are implemented and passed the recorded complete backend/client and browser gates. | -| P0.2 | `8fb2cb9`; corrected by `bf0af6b` | `c3e8a17`; corrected by `4cad1b2` | Not required | Not required | Complete: exact executable resource-plan targeting, bounded receipt binding, mixed/disconnected/zero-target rejection, and client fail-closed readiness/apply/run checks passed the complete backend/client and mocked-browser gates. The corrective pair makes the real schema-v2 backend publish the profile/schema fields already required by the client and binds them at admission; no model or asset execution was needed. | -| P0.3a.1 | `91c9a36` | `28b12b7` | Not required | Not required | Complete: registered Modular dynamic action safety and its backend/client gates are recorded in the paired implementation commits. | -| P0.3a.2 | `91c9a36` | `28b12b7` | Not required | Not required | Complete: safe declarative custom contract identity/preview and its backend/client/HTTP gates are recorded; executable custom admission remains deferred to P1.1. | -| P0.3b | `91c9a36` (revalidated at `8fb2cb9`) | `28b12b7`; Win32 checkpoint `d226c4b` (revalidated at `c3e8a17`) | Not required | Not required | Complete: P0.3b.1-.7 implementation, complete backend/client gates, reviewed Windows visual baselines, exact bundle mirror, and fresh HTTP smoke passed; live qualification is not part of this segment. | -| P0.3c | `e11263f`, `d81737e` | `aded6ca` (unchanged generic client contract revalidated) | Not required | Not required | Complete for the pinned upstream truth scope: all registered Modular action/component contracts and public modes are exact; SDXL base inpaint is contract-only; all 18 pinned SDXL workflows have exact generic state truth; Qwen/Layered and Wan split-state contracts close; standard image/video/audio adapters are registered only at their proved tier; and no client model-name branch was added. Multi-ControlNet/multiple-IP-Adapter expansion, templates/assets, and live qualification are distinct future gates, not evidence claimed by this phase. | -| P0.3d | `fd258d8` | `642ea9c` | Not required | Not required | Complete: backend-owned versioned Flux Schnell/Dev specifications, strict client parsing, generic graph materialization, exact proof/runtime receipt binding, complete backend/client/browser gates, byte-exact mirror verification, and local HTTP smoke passed. No model or media execution was required. | -| P0.3e | `96f70cb` (Flux Krea T2I), `a299d1d` (Flux Depth control-image), `14fef9f` (Flux Canny control-image), `6be23e7` (Flux Redux edit-image), `5f4d437` (Flux Kontext edit-image), `119c720` (Flux Kontext multi-reference edit), `544c54f` (Flux Fill inpaint), `634c485` (Flux Fill outpaint), `441cd00` (Flux2 Klein T2I), `ab3bd34` (Flux2 Klein edit-image), `4527764` (Flux2 Klein multi-reference edit), `276dd1f` (Wan TI2V text-to-video; corrected by `6983ce6`), `6983ce6` (Wan I2V image-to-video), `92cd1f5` (Wan 2.1 text-to-video), `93b1e17` (Wan 2.1 video-to-video), `09b1d4b` (Wan 2.1 color edit), `7736dd3` (LTX text-to-video), `7b8d5c1` (LTX image-to-video), `e83c760` (LTX video-to-video), `0bdc364` (LTX reference-to-video), `adcaf48` (ACE-Step text-to-audio), `5f6ffdc` (ACE-Step audio variation), `10b9b1c` (ACE-Step audio continuation), `60b87a1` (ACE-Step audio repaint), `de2160f` (Qwen Image Edit inpaint), `823357d` (Qwen Image Edit outpaint and bundle), `69a8561` (Wan VACE text-to-video; corrected by `2616014`, bundle `69247d0`), `0e9c3f2` (Wan VACE video inpaint and bundle), `b004af1` (Wan VACE video outpaint and bundle), `ed07f34` (Wan VACE control-to-video), `a4efd6c` (Z-Image Auto T2I), `6e40bab` (Qwen Image Auto T2I), `4596728` (Qwen Image Edit Modular), `0e7a8f1` (Qwen Image Edit Plus edit and multi-reference), `dd594ba` (Qwen Layered layer decomposition), `03c358b` (Qwen Image Control), `732e15c` (declarative loader-component outputs), `02afc25` (declarative layer-block allowlists), `7228c1f` (declarative Denoise image-latent dimensions), `b32241b` (generic video field overlay), `d3125dd` (Expert quantization resource policy), `f2ec7ac` (Expert MPS resource policy), `98f3841` (generic image and Modular field contracts), `fd514f7` (Expert quantization choices) | `80ac243` (Flux Krea T2I), `2de0c68` (Flux Depth control-image), `784e3c7` (Flux Canny control-image), `b709126` (Flux Redux edit-image), `8ae0dd9` (Flux Kontext edit-image), `d956a42` (Flux Kontext multi-reference edit), `38f8d81` (Flux Fill inpaint), `4c0bd05` (Flux Fill outpaint), `931621d` (Flux2 Klein T2I), `84e1d8f` (Flux2 Klein edit-image), `7180694` (Flux2 Klein multi-reference edit), `049addb` (Wan TI2V text-to-video; corrected by `60f4036`), `60f4036` (Wan I2V image-to-video), `0e359ce` (Wan 2.1 text-to-video), `651eeb3` (Wan 2.1 video-to-video), `2525937` (Wan 2.1 color edit), `9f2122f` (LTX text-to-video), `709ddd3` (LTX image-to-video), `8bd95e6` (LTX video-to-video), `cddd140` (LTX reference-to-video), `5f91ed9` (ACE-Step text-to-audio), `2a776c0` (ACE-Step audio variation), `7e69367` (ACE-Step audio continuation), `62dfe17` (ACE-Step audio repaint), `f28ff89` (Qwen Image Edit inpaint), `de2eba1` (Qwen Image Edit outpaint), `8f05541` (Wan VACE text-to-video; corrected by `4c9d40c`), `3f79ca2` (Wan VACE video inpaint), `fce224e` (Wan VACE video outpaint), `72ed446` (Wan VACE control-to-video), `77ceab9` (Z-Image Auto T2I), `531d4b9` (Qwen Image Auto T2I), `e8aab4e` (Qwen Image Edit Modular), `57a4072` (Qwen Image Edit Plus edit and multi-reference), `ff3f9c6` (Qwen Layered layer decomposition), `1102249` (Qwen Image Control), `947f7d9` (generic video field overlay), `5e8a1e7` (Expert quantization resource policy), `06ca70f` (Expert MPS resource policy), `c88e685` (generic image field switching browser proof), `a63d882` (image identity fallback removal), `5abfab9` (Expert quantization choices), `9cec2db` (exact installed-model loader identity), `d3ad700` (exact model-switch quantization retention), `229b5d1` (declarative low-memory presets), `a32b37a` (generic Modular readiness), `0c3a4c5` (exact restored quantization), `4afc515` (legacy graph fallback cleanup) | Not required | Not required | Complete: all 39 current execution-profile pairs and the shared loader, field, topology, readiness, and resource overlays are declarative and exact. The final residual audit removed active managed graph-construction model/pipeline switches while preserving explicit versioned template recipes, backend adapter normalization, and imported/manual Expert graph inference as declared boundaries. Complete client and 96/96 mocked-browser gates passed; no live model execution was required. | -| P0.3e Guider overlay | `51206e6` | `d1b2f88` | Not required | Not required | Complete: reviewed per-pipeline Guider choices, exact execution validation, scalar/multi-select dynamic option preservation, the complete backend/client gates, and the focused signal-relay mocked-browser contract passed. This closes the Guider portion of the parent P0.3e remaining-work summary. | -| P0.3e Scheduler overlay | `6779a19` | `140cab2` | Not required | Not required | Complete: pinned-upstream scheduler compatibility metadata, live-component and exact-constructor validation, complete backend/client gates, and the focused generic signal-relay mocked-browser contract passed. This closes the Scheduler portion of the parent P0.3e remaining-work summary. | -| P0.3e readiness overlay | `03c358b` (compatible exact-specification contract) | `7a02806` | Not required | Not required | Complete: readiness consumes the live managed loader identity or the unique authoritative execution specification, validates every exact role generically, and no longer routes the removed capability checks by model or pipeline name. Focused 67/67, complete client, exact browser, and final 88/88 mocked Studio gates passed; the bundle remained inside both limits. | -| P0.3e Modular readiness identity | Not required (uses the existing exact execution profile contract) | `a32b37a` | Not required | Not required | Complete: managed Run readiness identifies restored Modular graphs from generic managed roles and new graphs from the exact execution path, with no model-family branch. Focused 43/43, complete client, and final 96/96 mocked Studio gates passed; the bundle remained 429 bytes inside the stricter safety target. | -| P0.3e restored quantization admission | Not required (uses the existing exact execution profile contract) | `0c3a4c5` | Not required | Not required | Complete: bounded persisted quantization is admitted only by the exact selected execution profile, with no family filter. Focused 68/68, complete client, and final 96/96 mocked Studio gates passed; the bundle remained 395 bytes inside the stricter safety target. | -| P0.3e audio field overlay | `2a98856` | `fba496c` | Not required | Not required | Complete: reviewed audio pipeline/mode contracts publish the exact generic Generate field overlay; the backend rejects tampered overlays and the client no longer derives audio visibility from pipeline names. Complete backend/client and final 89/89 mocked Studio gates passed; no live audio execution was required. | -| P0.3e video field overlay | `b32241b` | `947f7d9` | Not required | Not required | Complete: every reviewed generic video adapter/mode owns its field visibility, required inputs, adapter controls, and strength binding; the exact backend action rejects stale contracts and the client no longer identifies LTX to choose the strength control. Complete backend/client and final 90/90 mocked Studio gates passed; no live video execution was required. | -| P0.3e Expert CUDA resource policy | `b1f514f` | `0259624` | Not required | Not required | Complete: exact Qwen execution profiles own the bounded dtype/offloaded/resident/quantized CUDA estimates, the client consumes only the policy attached to the selected exact specification, and no model-family fallback remains for these checks. Complete backend/client and final 91/91 mocked Studio gates passed; no live model execution was required. | -| P0.3e Expert quantization resource policy | `d3125dd` | `5e8a1e7` | Not required | Not required | Complete: exact Qwen execution profiles own the bounded Expert quantization/offload and generic-node component contract; the client strictly consumes it only through the selected exact specification, and direct/Modular readiness plus graph materialization no longer use a model-family quantization branch. Complete backend/client and final 91/91 mocked Studio gates passed; no live model execution was required. | -| P0.3e Expert MPS resource policy | `f2ec7ac` | `06ca70f` | Not required | Not required | Complete: exact reviewed execution profiles own the bounded Expert Apple MPS qualification and fallback advisory; the client strictly consumes it only through the selected exact specification, and no Qwen/Z/video family branch remains in MPS readiness. Complete backend/client and final 92/92 mocked Studio gates passed; no Apple Silicon or live model execution was required. | -| P0.3e image/Modular field overlay | `98f3841` | `c88e685` | Not required | Not required | Complete: exact generic image pipeline/mode contracts drive live field visibility, Modular generic nodes refresh from selected registry metadata, stale image overlays fail closed, and the complete backend/client/final 93/93 mocked Studio gates passed; no live model execution was required. | -| P0.3e image-path and Expert quantization-choice cleanup | `fd514f7` | `a63d882`, `5abfab9` | Not required | Not required | Complete: managed image topology and loader class now come only from the exact selected specification or existing managed binding; exact Qwen/Flux profiles own the bounded Expert quantization choices; controlled tab restore retains its execution-spec receipt. The complete backend/client gates and final 94/94 mocked Studio suite passed, with the bundle 259 bytes inside the stricter safety target. No live model execution was required. | -| P0.3e resource-path overlay | `8fb2cb9` (exact schema-v2 Auto target contract) | `16b7f12` | Not required | Not required | Complete: the client no longer guesses execution paths from Qwen or family identity before planning; exact selected backend candidates remain the only Auto path authority, and the complete 89/89 Studio gate passed. | -| P0.4 | `bf0af6b` (Auto schema/profile history binding), `f0ccd13` (optional-runtime receipt binding), `3a0b355` (specification-owned graph receipt binding), `e2a1bf2` (auxiliary-artifact receipt binding), `5cb785d` (executable controlled-LoRA history/cache receipt binding), `31cbc47` (current controlled-workflow artifact receipts), `a4efd6c` (Z-Image exact graph specification), `6e40bab` (Qwen Image exact graph specification), `4596728` (Qwen Image Edit Modular exact graph specification), `0e7a8f1` (Qwen Image Edit Plus exact graph specifications), `dd594ba` (Qwen Layered exact graph specification), `03c358b` (Qwen Image Control exact graph specification) | `12847d0`, `4cad1b2`, `0131ea7`, `453da03`, `54a610a` (exact controlled-artifact metadata and proof label), `77ceab9`, `531d4b9`, `e8aab4e`, `57a4072`, `ff3f9c6`, `1102249` | Not required | Not required | Complete for the current reviewed contract set: schema-v3 seals LoRA, sequence, upscaler, quality, soundtrack, and lyric/mux graph transformations; Auto candidates/history bind planner/profile/runtime/topology and all current executable auxiliary artifact receipts; every one of the 39 current execution-profile pairs has an exact backend-owned graph specification; stale, malformed, disconnected, or unreviewed receipt claims fail closed; and plan-time UI no longer presents base-only history as proof of controlled artifacts. Future controlled artifact kinds require a new reviewed receipt and qualification slice. | -| P0.5 | `4073711` (qualifier), `655baa6` (platform cutover), corrected by `1e95362`; clean-base topology revalidated by `e29fbf4` | `16046ab` (target-aware Setup status) | Windows x86-64 guarded live-model proof; Linux x86-64 clean-base/no-weight, supervised lifecycle, and production-cutover proof; macOS pending and base-delivered | Not required | Complete for qualified x86 targets: the exact six-target profile/delivery table enables explicit first-use install/activation only on Linux and Windows x86-64. Direct base dependencies remain only on macOS/ARM targets. A committed clean Linux CPU base contained 61 packages and none of the ten staged distributions; the exact overlay installed, validated, activated, passed the finite CLIP+LoRA child, rolled back, and restored a fresh clean base. The later clean-base full suite passed with 40 exact optional-upstream tests scoped to base-delivered or activated runtimes instead of reinstalling Transformers. A fresh worker exposed actionable status and rejected required execution with `optional_runtime_missing` before queueing. The 2026-08-14 focused source revalidation passes 94 tests and 929 subtests across target delivery, execution, qualification, guidance, and profile contracts. macOS and ARM rows remain explicitly non-actionable/base-delivered pending their own qualifier evidence. | -| P1.1 | `207d8f1`; actionable API message follow-up `5dc7313` | `c3e932c` | Not required | Not required | Complete: reviewed official component execution is bound to exact main/auxiliary Hub commits, an installed pinned pipeline/block pair, immediate identity revalidation, a private content-addressed metadata snapshot, and P0.5 runtime admission. Local sources remain preview-only and repository Python remains disabled without a future non-persistable task authorization. Complete backend/client and focused browser gates passed without model or asset execution. | -| P1.2 | `50dafa6` | `7c6bdbf` | Not required | Not required | Complete: the reproducible pinned snapshot normalizes Sequential, Auto, Loop, state, output, and component contracts; DynamicBlock exposes and executes only sidecar-carryable reviewed tasks; the client consumes the declarative task visibility contract generically. Complete backend/client and focused browser gates passed without model or asset execution. | -| P1.3 | `b48355b` | `f044594` | Not required | Not required | Complete: all three planned guiders use exact pinned official exports and constructor contracts; layer requirements, component compatibility, typed parameters, and backend-driven generic option signals passed complete backend/client and focused browser gates without weights. | -| P1.4 | `8e44eb5` | `5cb2998` | Not required | Not required | Complete: all 15 Modular classes present at the pin are split into image/video/multimodal contract-only batches with exact generated upstream workflow schemas and generic Expert visibility. They remain outside executable, Auto, template, Gallery, optional-runtime, and live-support registries; complete backend/client and focused browser gates passed without weights or assets. | -| P2.1 | `fa1884a` | `6f12230` | Not required | Not required | Complete: all 39 authoritative execution pairs publish a stable generic planning contract with exact execution-profile, loader, required-media, and terminal-output identity. Strict client parsing produces modality-generic skeletons and leaves every entry Gallery-hidden pending the separate qualification gate; complete backend/client gates passed without weights or assets. | -| P2.2a SDXL base text-to-image | `e5905f5` | `7581902` | Remote pending | Pending | Complete source slice: exact pinned planning graph and generic revision binding passed the complete backend/client gates; Auto and Gallery remain disabled pending live qualification and immutable assets. | -| P2.2b SDXL image-to-image | `5b03302` | `2bd15b2` | Remote pending | Pending | Complete source slice: one logical SDXL model selects an exact mode-specific img2img class and pinned planning graph; source-image, complete-suite, and bundle gates passed while Auto and Gallery remain disabled. | -| P2.2 task-contract planning foundation | `57316d9` | `2ec78a4` | Not required | Not required | Complete: the canonical generator falls back from curated recipes to exact backend task-template skeletons, and both pending SDXL base modes were regenerated without placeholder Gallery entries or model-specific client builders. | -| P2.2c SDXL inpaint | `a1faf3a` | `b9ea71e` | Remote pending | Pending | Complete source slice: exact mode-specific inpaint loader, immutable base revision, generic source/mask bindings, task-contract generation, complete suites, and bundle gate passed; Auto and Gallery remain disabled. | -| P2.2d FLUX.1-dev image-to-image | `b66439b` | `9a7280e` | Remote pending | Pending | Complete source slice: exact mode-specific img2img loader, immutable FLUX.1-dev revision, generic source-image binding, task-contract generation, complete suites, and bundle gate passed; Auto remains text-to-image-only and Gallery activation remains pending. | -| P2.2e FLUX.1-dev inpaint | `b679e4f` | `e8197c8` | Remote pending | Pending | Complete source slice: exact mode-specific inpaint loader, immutable FLUX.1-dev revision, generic source/mask bindings, task-contract generation, complete suites, and bundle gate passed; Auto remains text-to-image-only and Gallery activation remains pending. | -| P2.2f Z-Image Turbo image-to-image | `0d1d3cb` | `a586fce` | Remote pending | Pending | Complete source slice: exact standard img2img loader, immutable Z-Image Turbo revision, generic source-image binding, task-contract generation, complete suites, and bundle gate passed; Auto remains text-to-image-only and Gallery activation remains pending. | -| P2.2g Qwen-Image-2512 image-to-image | `8a4dbd8` | `538a79c` | Remote pending | Pending | Complete source slice: exact standard img2img loader, immutable Qwen-Image-2512 revision, generic source-image binding, reviewed Expert policy, task-contract generation, complete suites, and bundle gate passed; Auto remains unchanged and Gallery activation remains pending. | -| P2.2h Qwen-Image-2512 inpaint | `556be5f` | `bd8278f` | Remote pending | Pending | Complete source slice: exact standard inpaint loader, immutable Qwen-Image-2512 revision, generic source/mask bindings, reviewed Expert policy, task-contract generation, complete suites, and bundle gate passed; Auto remains unchanged, outpaint remains unadvertised, and Gallery activation remains pending. | -| P2.2 existing-image-path closure | `653168c` | `9862eea` | Remote pending | Pending | Complete: all 30 registered image pairs have deterministic canonical layouts; regeneration preserves catalog revisions and discovers base plus auxiliary Hub artifacts from each graph; focused backend integrity and complete client gates passed without weights or media. | -| P2.2i FLUX Redux multi-reference closure | `cae34b8` | `f7cd3c1` | Remote pending | Pending | Complete source slice: the public Redux multi-reference template now owns an exact backend specification, immutable base requirement, generic Edit graph, and canonical workflow hash. All 77 public templates resolve exact workflows; 122 task pairs and 134 workflows verify. The complete backend/client gates pass, Auto remains edit-only, and no graph or model ran. | -| P2.3 existing audio paths | `8e91284` | `c0170b2` | Remote pending for Stable Audio | Pending | Complete source slice: Stable Audio has an exact pinned generic task workflow; all five canonical audio pairs and both ACE LoRA variants verify deterministically; complete backend/client gates passed without weights or media. | -| P2.4 | `0ace2ae` | `8e25f5f` | Remote pending | Pending | Complete source slice: ten new exact short-video planning contracts and the existing Wan I2V/TI2V paths verify in the 70-workflow deterministic catalog; complete backend/client and focused browser gates passed without weights or media, while Auto and Gallery remain disabled pending P2.5. | -| P2.5 | Pending | Pending | Pending | Pending | Remote execution, review, publication, and activation have not started; clean-host campaign preflight is complete in P2.5a. | -| P2.5a Clean-host qualification campaign readiness | Not required | `8a93cf2` | Dry-run planning only; 76 live qualification receipts remain pending across six model-family batches | Pending | Missing local Auto history is correctly treated as no legacy evidence, ignored report directories initialize on clean hosts, and the complete client gate passes. The dry run submitted no graph and generated or published no media. | -| P2.5b Exact app-cache qualification readiness | Not required | `a76ee04` | Read-only live-app cache proof only; 76 live qualification receipts remain pending | Pending | All 76 selected jobs and 31 unique immutable model/LoRA receipts match complete, installed, repair-free app-cache entries. The loopback-only bounded preflight and complete client gate pass; no graph, inference, output, review, or publication occurred. | -| P2.5c Exact default-input qualification readiness | Not required | `d271a9f`, corrected by `7738537` | Read-only local-byte audit only; 76 live qualification receipts remain pending | Pending | The fail-closed campaign gate verifies selected Template Gallery defaults against their content-addressed bindings and asset-manifest size/hash receipts before browser or inference startup, checking both the authoring tree and the normal installer's durable backend `web/` payload. The runner uses the same installed-app fallback. The source checkout has none of the 50 required files (33,867,388 bytes), so 38 input-conditioned jobs are blocked and 38 input-free jobs are ready. No direct asset download, model deletion, graph, inference, output, review, or publication occurred. | -| P2.5g Live download-idle and Gallery-plan cutover | `578e0a3`, `c140b3f` | `3413455` | Current Linux worker, immutable app install/static-route, complete client gate, and generation-free readiness evidence only; 76 live output receipts, review/publication, activation, and physical macOS remain pending | Pending | The current worker exposes the bounded idle-status contract and corrected current-attempt progress accounting. The immutable 356-file / 480,430,370-byte Gallery manifest validates its bounded dimensions and exact identity. Its first 969,249,348-byte download/staging plan refused insufficient space; after 62.8 GiB of disposable non-model uv cache was cleared, two fresh app plans fit and the app downloaded, hashed, atomically promoted, and verified the payload. A safe post-transfer restart registered the static route, which serves 70 reviewed examples. The generation-free six-group campaign now exits zero with 76/76 jobs, 31/31 artifacts, download status, and 50/50 pinned inputs / 33,867,388 bytes ready. The complete client gate and 533,425 / 533,504-byte total gzip budget pass. No model deletion, graph, inference, or output occurred. | -| P2.5d App-owned pinned Gallery materialization | `df71942` | `0fd0830` | App install, immutable byte verification, and current static-route evidence only; 76 live qualification receipts remain pending | `b27198159c30d0c81aef397c188a7826866e5027` (`sha256:canonical-json:5ec869b755a6ce04a789d6835819da150493bfaef8a6bc1480f0274ba05bcab9` approved subset); 356 assets / 480,430,370 bytes installed | The app exposes strict status, queue-aware plan, and explicit install/repair actions for the exact anonymous Dataset revision. It reserved download plus same-volume staging bytes beside active model reservations and the 64 GiB margin, hashed every staged byte, atomically promoted the verified payload, and deleted no model cache. Current Gallery status is installed, complete, and repair-free; the registered static route serves 70 reviewed examples, and all 50 campaign inputs pass their exact size/hash bindings. Live generation remains pending. | -| P2.5e Bounded parallel app downloads | `c313908` | Not required | Source/unit concurrency proof only; current old worker and live qualification remain pending | Not required | Two ordinary app snapshot transfers can now share the existing two-slot semaphore instead of serializing behind the process-global Xet lock. Repair is writer-exclusive and restores the prior Xet mode before normal transfers resume. Queue reservations, the 64 GiB reserve, immutable revisions, and no-deletion behavior are unchanged. Focused and complete backend gates pass. The active worker was not restarted, so its existing queue remains uninterrupted and this commit makes no current-live-transfer or output claim. | -| P2.5f Bounded app-owned Hub transport | `77ed298` | Not required | Source/unit transport, concurrency, and restoration proof only; current old worker and live qualification remain pending | Not required | App-owned model and Gallery snapshot payloads use standard Hub HTTP with bounded per-request timeouts/retries and share the existing two-transfer limit. Repair is writer-exclusive from cache preparation onward, completed blobs remain intact, exact global policy restoration is tested, and all admission/reserve/no-deletion behavior is unchanged. The focused 56-test matrix, five repeated race runs, complete 1,630-test backend gate, and static/package/preflight checks pass. The old active worker was not restarted, AuraFlow and the overnight queue were not interrupted, no active transport was switched, and no model/media/macOS qualification is claimed. | -| P3.4 Official Hugging Face library boundary | `91c9a36` | `28b12b7` | Not required | Not required | Complete: backend and client policy, contributor, security, dependency/runtime, and boundary-test contracts permit separately reviewed official Hugging Face libraries only behind MoDiff's local generic graph executor and immutable admission rules. The direct Transformers/PEFT base-dependency gap present at this checkpoint was later closed by P0.5's platform-scoped optional-runtime cutover. No model or media execution was required for this policy segment. | -| P3.1 | `80e4587` | `8f2a671` | Local cached CPU smoke passed; remote quality review pending | Pending | Complete source/live-smoke slice: the generic unconditional adapter, three immutable exact pairs, 73-workflow deterministic catalog, complete backend/client gates, and 102-case mocked Studio sweep passed. Auto and Gallery remain disabled pending remote output review and Dataset publication. | -| P3.2a Stable Diffusion 1.5 | `a0815b8` | `5a633a9` | Local cached CPU node smokes passed for text-to-image, img2img, and inpaint; remote quality review pending | Pending | Complete source/live-smoke slice: three exact generic pairs reuse one immutable safetensors base, the 76-workflow deterministic catalog and complete gates passed, and no generated media was retained. Auto and Gallery remain disabled pending remote output review and Dataset publication. | -| P3.2c Latent Consistency Model | `6a1b579` | `9f7b7da` | Local cached CPU node smoke passed at one step; remote quality review pending | Pending | Complete source/live-smoke slice: the exact immutable DreamShaper LCM pair uses the generic image nodes, the 77-workflow deterministic catalog and complete gates passed, and no generated media was retained. Auto and Gallery remain disabled pending remote output review and Dataset publication. | -| P3.2b Stable Diffusion 2.x | Pending: official repository access required | Pending | Pending | Pending | Deferred independently: 2026-08-14 app plans for the known immutable official base and inpaint revisions still returned repository-not-found/gated responses with unknown size. No download was submitted and no substitute or fabricated immutable revision was admitted. | -| P3.2d Perturbed-attention guidance | `662aa10` | `42c4dd6` | Local cached CPU node smoke passed at one step; remote quality review pending | Pending | Complete source/live-smoke slice: the exact PAG pair reuses the immutable SD1.5 safetensors base through generic image nodes, both PAG controls bind through the backend specification, the 78-workflow deterministic catalog and complete gates passed, and no generated media was retained. Auto and Gallery remain disabled pending remote output review and Dataset publication. | -| P3.3 Generic perception / Marigold depth | `957ab31` | `1802291` | Local cached CPU node smoke passed at one step; remote quality review pending | Pending | Complete source/live-smoke slice: the immutable Marigold Depth LCM pair uses a generic schema-versioned prediction-map boundary, the 79-workflow deterministic catalog and complete gates passed, and no generated media was retained. Normals, intrinsics, uncertainty, Auto, and Gallery remain disabled pending their separate qualification gates. | -| P3.5 Transformers speech-to-text | `82522ba` | `571facf` | Local cached CPU loader/action smoke passed with Transformers 5.14.1; remote spoken-fixture quality review pending | Pending | Complete source/live-smoke slice: two exact generic speech pairs use the immutable Whisper Tiny safetensors snapshot through the P0.5 optional runtime; the 81-workflow catalog and complete gates passed, and no fixture or output media was retained. Auto and Gallery remain disabled pending remote rights and quality review. | -| P4.1a SD1.5 ControlNet Canny | `539650a` | `785b43e` | Remote pending | Pending | Complete source slice: the immutable safetensors-only SD1.5/ControlNet assembly, exact generic Canny preprocessor, controlled artifact receipt, 82-workflow catalog, complete backend/client gates, and 106-case mocked Studio sweep passed. Auto and Gallery remain disabled pending remote output review. | -| P4.1b SD1.5 T2I Adapter | Deferred: reviewed official snapshot is legacy `.bin` only | Pending | Not attempted | Pending | Deferred independently under the safetensors-only auxiliary policy; no unsafe exception or community conversion was admitted. | -| P4.2a SDXL Turbo text-to-image | `fb49ed8` | `f893514` | Remote and physical macOS pending | Pending | Complete source slice: immutable fp16 safetensors loading, exact one-to-four-step guidance-zero contract, 83-workflow catalog, complete backend/client gates, and 106-case mocked Studio sweep passed. Auto and Gallery remain disabled pending license-surface and live output review. | -| P4.2b SDXL InstructPix2Pix image editing | `eb2a28e` | `b7ed626` | Remote and physical macOS pending | Pending | Complete source slice: immutable safetensors-only SDXL instruction editing, exact 768px/30-step/text-guidance-3/image-guidance-1.5 contract, 84-workflow catalog, complete backend/client gates, and 106-case mocked Studio sweep passed. Auto and Gallery remain disabled pending live output review. | -| P4.2c SDXL ControlNet Canny | `a4ae9ca` | `5440570` | Remote and physical macOS pending | Pending | Complete source slice: immutable fp16 safetensors base/component assembly, exact Canny preprocessor and 1024px/50-step/guidance-5/scale-0.5 contract, 85-workflow catalog, complete backend/client gates, shared-control coverage, and 106-case mocked Studio sweep passed. Auto and Gallery remain disabled pending live output review. | -| P4.2d SDXL T2I-Adapter Canny | `2d14051` | `b13d65d` | Remote and physical macOS pending | Pending | Complete source slice: immutable fp16 safetensors base/component assembly, exact Canny preprocessor and 1024px/30-step/guidance-7.5/scale-0.8 contract, 86-workflow catalog, complete backend/client gates, and shared-control regression coverage passed. Auto and Gallery remain disabled pending live output review. | -| P4.2e SDXL PAG text-to-image | `2a9c29b` | `379936e` | Remote and physical macOS pending | Pending | Complete source slice: immutable fp16 safetensors SDXL base, upstream PAG pipeline, exact 1024px/50-step/guidance-5/PAG-3/adaptive-0 contract, 87-workflow catalog, and complete backend/client gates passed. Auto and Gallery remain disabled pending live output review. | -| P4.2f SDXL PAG image-to-image and inpaint | `63f9075` | `c0f2e2b` | Remote and physical macOS pending | Pending | Complete source slice: immutable fp16 safetensors SDXL base, exact upstream PAG edit/inpaint classes, reviewed 1024px/50-step/guidance-5/strength-0.8/PAG-3/adaptive-0 contracts, 89-workflow catalog, and complete backend/client gates passed. Auto and Gallery remain disabled pending live output review. | -| P4.3 Sana/Sana Sprint and DreamLite admission | `7117c80`, `a56e9c0` | `c41d1c6`, `96f444b` (`2c99769` generator race fix) | Remote and physical macOS pending; DreamLite upstream per-step callback unavailable | Pending | Complete source slice: exact safe immutable artifacts, upstream classes, bounded distinct base/mobile recipes, seven canonical graphs, focused DreamLite gates, and complete client gate passed. DreamLite is Expert-only and CC-BY-NC-4.0; Auto and Gallery remain disabled. | -| P4.4 generic audio generation | `4d6a4d3` | `f0958e6` | Remote and physical macOS pending | Pending | Complete source slice: Stable Audio safe loading was revalidated and exact LongCat AudioDiT plus AudioLDM2 families now use immutable reviewed artifacts, bounded native-rate recipes, backend-owned declarative task contracts, two new canonical graphs, and complete gates. Auto and Gallery remain disabled. | -| P4.5 AudioLDM2 text-to-speech | Deferred: reviewed TTS snapshots are legacy `.bin` only | Pending | Not attempted | Pending | Deferred independently: the exact generic speech API is present at the pin, but both reviewed AudioLDM2 speech repositories require unsafe deserialization and no exception was approved. | -| P4.6 Shap-E rendered output | `21d819f` | `d684fc4` | Remote and physical macOS pending | Pending | Complete source slice: exact immutable official artifacts are assembled only from reviewed safe components, a bounded rendered-orbit boundary is sealed in the 95-workflow catalog, and complete backend/client gates passed. Unsafe renamed-renderer weights and mesh/export surfaces remain excluded. Auto and Gallery remain disabled. | -| P5.1 Stable Video Diffusion image-to-video | `260637d` | `d6e0eed` | Remote and physical macOS pending | Pending | Complete source slice: the exact gated official revision, safetensors-only artifact surface, license gate, documented offload/chunking recipe, bounded prompt-free image-conditioning contract, 96-workflow deterministic catalog, complete backend/client gates, and 106-case mocked Studio sweep passed. Auto and Gallery remain disabled; no weights or media were downloaded or retained. | -| P5.2 AnimateDiff and AnimateLCM | `75dde3c` | `220fb40` | Remote and physical macOS pending | Pending | Complete Expert-only source slice: immutable SD1.5 and motion revisions, exact safetensors-only adapters/LoRA, documented scheduler recipes, bounded 512px short-video execution, two sealed graphs in the 98-workflow catalog, complete backend/client gates, and the 106-case mocked Studio sweep passed. The motion repositories declare no weight license, so rights remain undetermined and require an explicit notice; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P5.3a Motif Video evaluation | Deferred after immutable artifact/RAM review | Not required | Remote heavy-model review required | Pending | The official Apache-2.0 snapshot is safetensors-only, but its approximately 17.26 GB weight surface and native 121-frame 1280x736 recipe do not meet the smaller local-candidate premise. No executable or client surface was admitted and no weights or media were downloaded. | -| P5.3b CogVideoX-2B | `6501e22` | `00a3802` | Remote and physical macOS pending | Pending | Complete Expert-only source slice: exact Apache-2.0 safetensors artifact inventory, bounded native short-video contract, mandatory VAE tiling and model CPU offload, one sealed graph in the 99-workflow catalog, complete backend/client gates, and the 106-case mocked Studio sweep passed. Auto and Gallery remain disabled; no weights or media were downloaded. | -| P5 remaining short video | Pending | Pending | Remote pending | Pending | Existing Wan/LTX/LTX2/FramePack live qualification and any additional smaller candidates remain open. | -| P6.1 Diffusers pin update | `5ee9e1d` | Compatible client gate revalidated; no client change required | No live run required; remote model qualification remains pending | Not required | Complete isolated pin slice: the exact 73-commit delta was reviewed, existing no-weight Modular contracts remained structurally stable, required custom inputs were synchronized, the full backend suite passed at the proposed pin, and the repaired clean base is dependency-clean and preflight-ready. | -| P6.2 Krea2 Modular contracts | `1753384` | `ec2a349` | Contract-only; remote execution qualification pending | Not required | Both pinned classes are Expert-visible with exact, distinct base/Turbo contracts and fail closed before artifact resolution. The 28-contract snapshot, 1,313-test backend suite, complete client check, and 106-case mocked Studio sweep pass; no weights were downloaded. | -| P6.3 MiniMax H3 contracts and artifact review | `baf7271` | `947a2ef` | Contract-only; legal and remote heavy-hardware qualification pending | Not required | Three generic joint video/audio contracts, disjunctive FL2VA requirements, exact immutable partition/hash receipt, conditioner/scheduler/reference bounds, and an estimate-only resource envelope are sealed. The territory-restricted repository remains outside the runtime/download catalog with zero runnable modes. The 1,318-test backend suite, complete client check, and 106-case mocked Studio sweep pass; no weights or media were downloaded. | -| P6.4 LTX2/LTX2.5 contracts and source recipe review | `7e4f99b`, `b887aef` | `9dfcf62` | Contract-only; gated artifact indexes, license acceptance, remote heavy-hardware execution, and physical macOS qualification pending | Not required | All eight generic joint video/audio workflows and the distinct convolutional/diffusion decode contracts are sealed. The three official LTX-2.5 source recipes, exact sigma schedules, latent upsampler, duration head, explicit Gemma-4 enhancement, explicit NATTEN setup, and audio/video handoffs are recorded without exposing any runnable mode. Public immutable metadata covers 31 weight files / 163,896,920,128 bytes, but denied gated file access prevents exact partition selection; no weights or media were downloaded. | -| P6.5 HunyuanVideo 1.5 evaluation | `31cafc4` | Not required | Contract-only; territory/legal review and remote heavy-hardware execution pending | Not required | The existing two-workflow Modular contract, full official family, and immutable 480p T2V plus step-distilled I2V candidates are sealed with exact hashes, sizes, recipes, and estimate-only resource bounds. Conflicting territory language and additional commercial/distribution obligations keep all artifacts outside runtime and download catalogs. No weights or media were downloaded. | -| P6.6 Helios/Pyramid evaluation | `873f0ce` | Not required | Contract-only; immutable component-descriptor normalization and remote heavy-hardware execution pending | Not required | Base, Mid, and Distilled preserve their nine existing generic workflows. Exact full-repository and selected-partition receipts, distinct scheduler/guider recipes, chunk rounding, and estimate-only resource bounds are sealed. Upstream Modular indexes leave every component revision null, so no runtime or download entry was admitted. No weights or media were downloaded. | -| P6.7 Wan 2.2 A14B Modular evaluation | `011a70b` | Not required | Contract-only Modular path; remote fallback-assembly and heavy-hardware execution pending. Existing standard adapters remain graph-qualified/execution-pending. | Not required | Exact dual-expert T2V/I2V receipts, boundary-ratio fallback selection, workflow contracts, source recipes, and estimate-only resource bounds are sealed. No Modular index, new runtime/download catalog entry, weights, or media were added. | -| P6.8 classic LTX/LTX2 artifact evaluation | `0f96a92`, `474b83d` | Not required | Existing graph surfaces remain execution-pending; Modular paths, legal acceptance, and remote heavy-hardware execution remain pending | Not required | Exact full/selected inventories and source-contract receipts are sealed. The 2B family index can no longer silently replace the 13B Distilled profile. LTX-2's selected two-stage partition and license obligations are explicit. No weights or media were downloaded. | -| P6.9 Wan 2.1 14B Modular variants | `e4c2385` | Not required | Exact repository-scoped loader admission; remote heavy-hardware and physical macOS execution pending | Not required | T2V-14B and I2V-14B-720P join the already reviewed I2V-480P and FLF-720P variants under exact immutable catalog/index/component contracts. The focused clean-overlay matrix passes 100 tests plus 175 subtests. No new high-level mode, client branch, Auto/template/Gallery surface, weights, or media were added. | -| P6.10 LLaDA2 immutable-code security review | `444152a` | Not required | Static review only; explicit task-scoped authorization, bounded adapter controls, remote heavy-hardware execution, and physical macOS evidence pending | Not required | Exact remote-code blobs and eight-shard safetensors inventory are sealed. Static review found no prohibited primitive but did identify a process-global Transformers registry mutation and cannot prove runtime safety. `trust_remote_code` remains fail-closed; no runtime/download catalog or user-facing surface was admitted. | -| P6.11 DiffusionGemma artifact/source review | `42b609e` | Not required | No-weight API probe only; bounded generic diffusion-text contract, remote heavy-hardware execution, multimodal safety review, and physical macOS evidence pending | Not required | Exact official 11-shard / 51,647,701,024-byte safetensors inventory, Apache-2.0 rights, package-owned class/source hashes, 256-token/48-step entropy-bound recipe, callback support, and estimate-only resource envelope are sealed. The model remains remote-only and absent from runtime/download catalogs and user-facing capabilities. | -| P6.12 Stable Cascade artifact/source review | `b55983b` | Not required | Static artifact/source review only; license resolution, maintained package-owned pipeline support, remote heavy-hardware execution, and physical macOS evidence pending | Not required | Exact prior/decoder revisions, six-file / 13,728,020,596-byte selected bf16 partition, full inventories, source hashes, two-stage recipe, and estimate-only resource envelope are sealed. The noncommercial license, upstream deprecation, and unpinned connected-repository metadata keep the family outside runtime/download catalogs and user-facing capabilities. | -| P6.13 DeepFloyd IF artifact/source review | `8d45c9f` | Not required | Static artifact/source review and no-weight API probe only; authenticated gated-config review, backend-owned bounds, remote heavy-hardware execution, and physical macOS evidence pending | Not required | Three immutable stage revisions, 11-file / 27,326,661,461-byte repository-scoped selected surface, deduplicated weight size, source hashes, 64px-to-256px-to-1024px recipe, safety/watermark handoff, and estimate-only resource envelope are sealed. The gated noncommercial-research license keeps the family outside runtime/download catalogs and user-facing capabilities. | -| P6.14 PixArt Sigma 1024px source admission | `4fd1a66` (`8bca634` declarative-field fix) | `235c9d3` (`60b0269` exact-contract fix) | Remote real-weight, output safety/quality, and physical macOS execution pending | Not required | Exact public OpenRAIL++ revision, four-file / 21,827,405,446-byte safetensors inventory, package-owned pipeline/source hashes, bounded 1024px recipe, Expert-only remote workflow, and estimate-only resource envelope are sealed. The deterministic 104-workflow catalog is graph-qualified/runtime-unqualified; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P6.15 AuraFlow v0.3 source admission | `8117b39` | `7d52469` (`c5e02ea` exact-contract fixture) | Remote real-weight, output safety/quality, and physical macOS execution pending | Not required | Exact public Apache-2.0 revision, four-file / 16,835,036,374-byte fp16 safetensors partition, package-owned pipeline/source hashes, bounded native 1536x768 recipe, Expert-only remote workflow, and estimate-only resource envelope are sealed. The deterministic 105-workflow catalog is graph-qualified/runtime-unqualified; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P6.16 Bria 3.2 source and admission-gate review | `30a4667` | Not required | Static package/API and public gated-tree review only; authenticated exact artifact/license review, backend-owned precision and bounds, remote heavy-hardware execution, and physical macOS evidence pending | Not required | Package-owned classes, source hashes, call contract, public rounded safetensors observations, gated non-commercial terms, and HTTP-401 metadata limits are sealed without inventing an immutable artifact identity. The family remains outside runtime/download catalogs and all user-facing surfaces; no weights or media were downloaded. | -| P6.17 Bria FIBO generation/edit source and admission-gate review | `a826a5e` | Not required | Static package/API/custom-code review only; license acceptance, authenticated config/license review, explicit remote-code authorization, backend-owned structured-JSON/device/model pinning and bounds, remote heavy-hardware execution, and physical macOS evidence pending | Not required | Exact generation/edit heads, current and archived safetensors partitions, package source hashes, structured generation/edit/inpaint contracts, promptifier code revisions, and nested VLM inventories are sealed. Gated non-commercial weights plus revision-unbound CUDA-only custom promptifiers keep the family outside runtime/download catalogs and all user-facing surfaces; no weights or media were downloaded. | -| P6.18 Chroma1-HD text-to-image source admission | `5d3bc8f` | `86cbd92` | Remote real-weight, output safety/quality, and physical macOS execution pending | Not required | Exact public Apache-2.0 revision, five-file / 27,492,403,238-byte selected bfloat16 Diffusers partition, excluded duplicate single-file artifact, immutable metadata and package source hashes, bounded 1024px recipe, Expert-only remote workflow, and estimate-only resource envelope are sealed. The deterministic 106-workflow catalog is graph-qualified/runtime-unqualified; image-to-image, Auto, and Gallery remain disabled, upstream declares no safety alignment, and no weights or media were downloaded. | -| P6.19 CogView3 Plus 3B text-to-image source admission | `f74c806` | `a4d0b99` | Remote real-weight, output safety/quality, license-file clarification, and physical macOS execution pending | Not required | Exact public revision, seven-file / 25,559,227,422-byte bfloat16 Diffusers partition, immutable metadata and package source hashes, bounded 512-2048px recipe, Expert-only remote workflow, and estimate-only A100 resource envelope are sealed. Model-card metadata declares Apache-2.0, but its linked `LICENSE.md` is absent from the immutable tree. The deterministic 107-workflow catalog is graph-qualified/runtime-unqualified; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P6.20 CogView4 6B text-to-image source admission | `495d07d` | `9822baa` | Remote real-weight, output safety/quality, and physical macOS execution pending | Not required | Exact public Apache-2.0 revision, eight-file / 31,108,954,670-byte bfloat16 Diffusers partition, immutable metadata and package source hashes, bounded 512-2048px/2^21-pixel recipe, Expert-only remote workflow, and estimate-only A100 batch-four resource envelope are sealed. The signature/docstring token discrepancy and contradictory 1920x1280 memory row are explicit. The deterministic 108-workflow catalog is graph-qualified/runtime-unqualified; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P6.21 VisualCloze source and admission-gate review | `0f4d3c4` | Not required | Static artifact/source review only; generic visual-context-matrix contract, backend-owned bounds, license-file clarification, remote heavy-hardware execution, live output review, and physical macOS evidence pending | Not required | Exact public 384px and 512px revisions, two seven-file / 33,743,379,958-byte bfloat16 safetensors inventories, immutable metadata and package source hashes, nested matrix/generation/SDEdit contracts, and estimate-only resource envelopes are sealed. The legacy `.pth` LoRA artifacts are explicitly excluded. Contract mismatch and package validation gaps keep the family outside runtime/download catalogs and all user-facing surfaces; no weights or media were downloaded. | -| P6.22 Allegro text-to-video source admission | `6488462` | `05a2e15` | Remote real-weight, output safety/quality, license-file clarification, and physical macOS execution pending | Not required | Exact public revision, six-file / 25,293,069,108-byte bfloat16 Diffusers partition, excluded duplicate unsafe `.bin` partition, immutable metadata and package source hashes, bounded native 1280x720/88-frame recipe, float32 tiled VAE, Expert-only remote workflow, and conservative resource envelope are sealed. The deterministic 109-workflow catalog is graph-qualified/runtime-unqualified; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P6.23 AnyFlow source and admission-gate review | `09d3c98` | Not required | Static artifact/source review only; noncommercial-license legal approval, backend-owned bounds, remote heavy-hardware execution, live output review, and physical macOS evidence pending | Not required | All four exact public bidirectional/FAR 1.3B/14B revisions, their seven- or nine-file / 26.07-51.87 GB bfloat16 safetensors inventories, complete identical license files, immutable metadata and package source hashes, native T2V/I2V/V2V contracts, FAR chunking, and estimate-only resource envelopes are sealed. The restrictive NVIDIA license and stale custom-code model-card API examples keep the family outside runtime/download catalogs and all user-facing surfaces; no weights or media were downloaded. | -| P6.24 ChronoEdit source and admission-gate review | `70640ae` | Not required | Static artifact/source review only; governing-license approval, complete safe guardrail integration, generic edit/reasoning contract, backend-owned bounds, remote heavy-hardware execution, live output review, and physical macOS evidence pending | Not required | The exact public 21-file / 90,075,130,404-byte safetensors core, three optional LoRA artifacts, immutable metadata and package source hashes, native image-edit/temporal-reasoning recipes, stale model-index identities, and measured upstream offload figures are sealed. The external governing terms' guardrail condition, package pipeline's missing safety checker, and bundled unsafe `.pth`/`.pt` guardrail artifacts keep the family outside runtime/download catalogs and all user-facing surfaces; no weights or media were downloaded. | -| P6.25 ConsisID source and admission-gate review | `a52c7ec` | Not required | Static artifact/source review only; safe cross-platform face stack, biometric privacy/consent controls, generic identity-video contract, backend-owned bounds, license-file clarification, remote heavy-hardware execution, live identity/safety review, and physical macOS evidence pending | Not required | The exact public five-file / 22,821,396,692-byte safetensors generator and eight-artifact / 1,446,798,634-byte required identity stack, immutable metadata and package source hashes, native 720x480/49-frame recipe, and measured upstream memory figures are sealed. Required unsafe face weights, CUDA-only ONNX providers, weak identity-input validation, and the unavailable second documented checkpoint keep the family outside runtime/download catalogs and all user-facing surfaces; no weights or media were downloaded. | -| P6.26 Latte text-to-video source admission | `180917e` | `847ed6c` | Remote real-weight, output safety/quality, license-file clarification, and physical macOS execution pending | Not required | Exact public revision, six-file / 23,614,979,636-byte safetensors partition, excluded unsafe legacy `.pt` checkpoint and unreferenced optional temporal VAE, immutable metadata and package source hashes, bounded native 512x512/16-frame recipe, Expert-only remote workflow, and estimate-only resource envelope are sealed. The deterministic 110-workflow catalog is graph-qualified/runtime-unqualified; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P6.27 Lucy Edit source and admission-gate review | `6ab1033` | Not required | Static artifact/source review only; commercial-license and legal product approval, immutable governing terms, backend-owned bounds, remote heavy-hardware execution, live output review, and physical macOS evidence pending | Not required | The exact public five-file / 34,182,223,896-byte float32 safetensors inventory, immutable metadata, external license-document receipt, package source hashes, and native 832x480/81-frame edit recipe are sealed. The non-commercial/non-production license defines hosted remote access as distribution, and the package does not bind `num_frames` to input-video length, so the family remains outside runtime/download catalogs and all user-facing surfaces; no weights or media were downloaded. | -| P6.28 Mochi 1 Preview text-to-video source admission | `fb3e39f` | `c0afea5` | Remote real-weight, output safety/quality, license-file clarification, and physical macOS execution pending | Not required | Exact public revision, eight-file / 40,024,303,350-byte selected safetensors partition, excluded duplicate original-format, unindexed T5, and float32 partitions, immutable metadata and package source hashes, bounded native 848x480/31-frame recipe, explicit indexed T5 preload, mandatory VAE tiling, Expert-only remote workflow, and estimate-only resource envelope are sealed. The deterministic 111-workflow catalog is graph-qualified/runtime-unqualified; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P6.29 SANA-Video 2B 480p text/image-to-video source admission | `081a083` | `6ed67bf` | Remote real-weight, output safety/quality, and physical macOS execution pending | Not required | Exact public Apache-2.0 revision, five-file / 13,963,813,420-byte mixed-precision safetensors partition, excluded unsafe original-format, distinct 720p, and duplicate-heavy LongLive repositories, immutable metadata and package source hashes, bounded native 832x480/81-frame T2V and I2V recipes, FP32 tiled Wan VAE, Expert-only remote workflows, and estimate-only resource envelope are sealed. The deterministic 113-workflow catalog is graph-qualified/runtime-unqualified; Auto and Gallery remain disabled and no weights or media were downloaded. | -| P6.30 Hunyuan-DiT v1.2 ControlNet Canny source admission | `72185d0` | `f134f98` | Remote real-weight memory/output safety/quality and physical macOS execution pending | Not required | Exact public distilled-base and Canny revisions, six-file / 17,399,623,404-byte float32 safetensors inventory, optional exact Depth/Pose substitutions, immutable Tencent license receipt and acknowledgement, metadata and package source hashes, bounded native 1024px/50-step/guidance-6/scale-1 Canny recipe, Expert-only remote workflow, and estimate-only resource envelope are sealed. The deterministic 114-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled, and no weights or media were downloaded. | -| P6.31 Stable Diffusion 3 ControlNet source and admission-gate review | `0f924dc` | Not required | Static artifact/source review only; authenticated base-config review, auxiliary-weight rights resolution, legal product approval, backend-owned optional-runtime/bounds, remote heavy-hardware output review, and physical macOS evidence pending | Not required | Exact gated base plus public Canny, Tile, and inpainting revisions; three safetensors-only assembly receipts; immutable license/metadata and package source hashes; native 1024px recipes; and estimate-only resource envelopes are sealed. Base access/license restrictions, undeclared InstantX weight rights, ambiguous inpainting derivative terms, and absent safety guardrails keep the family outside all runtime/download and user-facing surfaces; no weights or media were downloaded. | -| P6.32 DiT source and admission-gate review | `17ccba9` | Not required | Static artifact/source review only; safe official artifacts, commercial product rights, backend-owned loader/bounds/cancellation, remote heavy-hardware output review, and physical macOS evidence pending | Not required | The only two exact Facebook 256px/512px revisions, immutable metadata and package source hashes, four legacy weight identities, fixed ImageNet class-label contracts, and estimate-only resource envelopes are sealed. Legacy pickle-only serialization, CC BY-NC licensing without a bundled license file, absent safety guardrails, and absent cooperative cancellation keep the family outside all runtime/download and user-facing surfaces; no weights or media were downloaded. | -| P6.33 ERNIE Image Turbo text-to-image source admission | `a0b07c8` | `0527a66` | Remote real-weight memory/output safety/quality and physical macOS execution pending | Not required | Exact public Apache-2.0 revision, five-file / 31,596,733,630-byte predominantly BF16 safetensors inventory, immutable metadata and package/Transformers source hashes, fixed 1024px/8-step/guidance-1/prompt-enhanced recipe, Expert-only remote workflow, and estimate-only resource envelope are sealed. The revised optional-runtime symbol contract passed a clean-base locked install/activation/workload/rollback qualification. The deterministic 115-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled, and no weights or media were downloaded. | -| P6.34 GLM-Image text-to-image source admission | `c338824` | `d42a15c` | Remote real-weight memory/output safety/quality, bundled-license clarification, and physical macOS execution pending | Not required | Exact public MIT-declared revision, incorporated Apache-2.0 tokenizer terms, nine-file / 35,765,307,854-byte mixed BF16/FP32 safetensors inventory, immutable metadata and package/Transformers source hashes, fixed 1024px/50-step/guidance-1.5 recipe, Expert-only remote workflow, and estimate-only resource envelope are sealed. The revised optional-runtime symbol contract passed a clean-base locked install/activation/workload/rollback qualification. The deterministic 116-workflow catalog is graph-qualified/runtime-unqualified; the missing negative-prompt API and safety checker keep Auto and Gallery disabled, and no weights or media were downloaded. | -| P6.35 HiDream-I1 source and admission-gate review | `0685ee5` | Not required | Authenticated Llama 3.1 terms/artifact review, composite license receipt, backend-owned external encoder assembly/bounds, remote heavy-hardware output review, and physical macOS execution pending | Not required | Exact public Full/Dev/Fast revisions, three 12-file / approximately 47.18 GB safetensors partitions, shared and variant-specific immutable weight identities, package source hashes, official 50/28/16-step recipes, callbacks, and estimate-only 63.24 GB composite runtime surface are sealed. Every public snapshot omits the required Llama tokenizer/encoder; its manual gate masks artifact identities before acceptance. No runtime/download or user-facing surface is added, and no weights or media were downloaded. | -| P6.36 HunyuanImage 2.1 source and territory-gate review | `4987495` | Not required | Legal territory/distribution approval, product territory enforcement, immutable composite terms receipt, remote heavy-hardware output review, and physical macOS execution pending | Not required | Exact public package-owned conversion and governing upstream revisions, ten-file / 53,124,614,990-byte BF16 safetensors inventory, immutable license/notice/config and package source hashes, 2K/50-step/APG-3.5 first-stage recipe, callbacks, and estimate-only resource envelope are sealed. Express EU/UK/South-Korea exclusions keep the family contract-only and outside every runtime/download and user-facing surface; no weights or media were downloaded. | -| P6.37 Hunyuan-DiT v1.2 Distilled standalone source admission | `1e97362` | `e6e306f` | Remote real-weight memory/output safety/quality and physical macOS execution pending | Not required | The existing exact public distilled base is now a standalone five-file / 14,422,655,700-byte float32 safetensors source with an independently bounded 1024px/25-step/guidance-5 Expert workflow and immutable Tencent terms acknowledgement. The expanded HunyuanDiT optional-runtime symbol surface passed clean-base locked installation, activation, finite workload, rollback, and clean restoration. The deterministic 117-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled, and no weights or media were downloaded. | -| P6.38 Ideogram 4 source, terms, and admission-gate review | `f09b06c` | Not required | Static gated source/terms review only; task-scoped terms acceptance, authenticated artifact review, commercial agreement/legal approval, remote heavy-hardware output review, and physical macOS evidence pending | Not required | Three exact gated official heads, anonymous visible weight sizes, immutable June 3, 2026 noncommercial terms, and pinned package pipeline/transformer/prompt-enhancer/scheduler/VAE/modular source hashes are sealed without accepting the gate or downloading weights. Masked LFS identities, HTTP-401 configs, commercial/hosted-distribution restrictions, and absent package safety guardrails keep the family outside every runtime/download and user-facing surface. | -| P6.39 JoyAI Image Edit and Edit Plus source admission | `6d507eb` | `b1cbde7` | Remote real-weight memory/output safety/quality, model-card license-file clarification, and physical macOS execution pending | Not required | Exact public Apache-2.0-declared basic and Plus revisions, two twelve-file / approximately 50.32 GB BF16 safetensors partitions, immutable upstream Apache receipt, metadata and package/Transformers source hashes, bounded 1024-base bucket recipes, one- and five-reference generic edit contracts, and four Expert-only remote workflows are sealed. The revised optional-runtime symbol contract passed clean-base locked install/activation/workload/rollback qualification. The deterministic 121-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled, and no weights or media were downloaded. | -| P6.40 Kandinsky 2.1 source and admission-gate review | `c04f151` | Not required | Static artifact/source review only; exact connected-prior binding, full-job cancellation, backend-owned composite bounds, output guardrails, model-snapshot license clarification, remote heavy-hardware output review, and physical macOS evidence pending | Not required | Three exact public decoder/prior/inpaint revisions, their 13.23 GB composite safetensors-only surfaces, immutable metadata and package source hashes, upstream Apache receipt, three combined-mode contracts, and bounded header evidence are sealed. The package reuses the decoder revision on the distinct prior repository or downloads its moving branch, while the prior stage has no callback; the family remains outside every runtime/download and user-facing surface, and no full weights or media were downloaded. | -| P6.41 Kandinsky 2.2 source and admission-gate review | `75bb0ac` | Not required | Static artifact/source review only; exact connected-prior binding, backend-owned two-stage assembly/bounds, safe ControlNet/refiner artifacts, output guardrails, snapshot license clarification, remote heavy-hardware output review, and physical macOS evidence pending | Not required | Five exact official repositories, safe 15.86 GB decoder/prior composite surfaces, immutable metadata and package source hashes, two-stage callback contracts, and upstream Apache receipt are sealed. The connected loader remains revision-inexact; official depth-ControlNet and refiner snapshots are legacy `.bin`-only, and the refiner names the 2.1 pipeline without card/license metadata. No runtime/download or user-facing surface was added, and no full weights or media were downloaded. | -| P6.42 Kandinsky 3 text-to-image and image-edit source admission | `b85b073` | `3007bf3` | Remote real-weight memory/output safety/quality, model-snapshot license-file clarification, and physical macOS execution pending | Not required | Exact public Apache-2.0-declared revision, seven-file / 28,390,829,958-byte fp16 safetensors partition, immutable upstream Apache receipt, metadata and package/Transformers source hashes, bounded single-stage 1024px/25-step/guidance-3 routes, and two Expert-only remote workflows are sealed. The revised optional-runtime symbol contract passed clean-base locked install/activation/workload/rollback qualification. The deterministic 123-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled. Its exact snapshot is queued through the app after aggregate free-space reservation preflight, without deleting older models; no media has been generated. | -| P6.43 Kolors source and custom-license gate review | `3788fe8` | Not required | Static artifact/source/license review only; task-scoped license acceptance, commercial registration/legal approval, downstream restriction implementation, backend-owned bounds, remote heavy-hardware output review, and physical macOS execution pending | Not required | The exact public five-file / 17,813,668,046-byte fp16 safetensors partition, immutable metadata/license and package source hashes, and two package-owned 1024px routes are sealed. The custom model agreement conflicts with the Apache-2.0 presentation, purports to trigger on use/access, propagates restrictions, and requires separate authorization for cloud vendors or licensees over 100M monthly users. No runtime/download or user-facing surface was added, and no weights or media were downloaded. | -| P6.44 Latent Diffusion source and admission-gate review | `79db3b8` | Not required | Static artifact/source review only; safe official artifacts, cooperative full-job cancellation, backend-owned resource/output bounds, immutable model-license receipt, remote output review, and physical macOS execution pending | Not required | The exact public three-file / 6,152,286,891-byte legacy weight partition, immutable metadata and package source hashes, and the package's 256px text-to-image contract are sealed without fetching weight bytes. The snapshot has only executable pickle `.bin` model components, while the package exposes no callback, interrupt flag, safety checker, or upper resource bounds. No runtime/download or user-facing surface was added. | -| P6.45 LEDITS++ source and admission-gate review | `7bddd15` | Not required | Static package/source review over existing exact SD 1.5 and SDXL bases; full-job cancellation, request-state isolation, backend-owned resource/input bounds, generic multi-prompt editing, SDXL guardrails, remote output review, and physical macOS execution pending | Not required | The two package-owned source identities and stateful invert-then-edit contracts are sealed against MoDiff's existing exact base snapshots. The mandatory inversion phase has no callback or interrupt check and stores request state on the pipeline instance. LEDITS++ needs no distinct model snapshot, so no new weight bytes or family-specific app download were required, and no runtime or user-facing surface was added. | -| P6.46 LongCat Image generation and edit source admission | `212997c` | `d1e5d7e` | Remote real-weight memory/output safety/quality, model-snapshot license-file clarification, and physical macOS execution pending | Not required | Two exact public Apache-2.0-declared revisions, two seven-file / approximately 29.29 GB safetensors partitions, immutable upstream Apache receipt, metadata and package/Transformers source hashes, bounded 1024-base generation and single-image edit recipes, and two Expert-only remote workflows are sealed. The expanded optional-runtime symbol contract passed clean-base locked install/activation/workload/rollback qualification. The deterministic 125-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled. Both exact snapshots are queued through the app after aggregate free-space reservation preflight, without deleting older models; no media has been generated. | -| P6.47 Lumina Next and Lumina Image 2.0 source admission | `faad48b` | `5b2f3db` | Remote real-weight memory/output safety/quality, model-snapshot license-file clarification, and physical macOS execution pending | Not required | Two exact public Apache-2.0-declared revisions, four-file / 8.86 GB and six-file / 21.23 GB safetensors partitions, immutable upstream MIT/Apache receipts, metadata and package/Transformers source hashes, and two bounded Expert-only text-to-image workflows are sealed. Lumina 2.0's exact app allowlist excludes both legacy pickle artifacts. The expanded optional-runtime symbol contract passed clean-base locked install/activation/workload/rollback qualification. The deterministic 127-workflow catalog is graph-qualified/runtime-unqualified; missing safety checkers keep Auto and Gallery disabled. Fresh exact app plans now report Lumina Next's 16-file / 8,878,690,722-byte and Lumina Image 2.0's 17-file / 21,253,711,141-byte bounded selections complete at their immutable revisions. Both app cache entries are installed, complete, and repair-free. No older model was deleted and no media has been generated. | -| P6.48 OmniGen v1 generation and reference-edit source admission | `11d5c16` | `86500ec` | Remote real-weight memory/output safety/quality, model-snapshot license-file clarification, and physical macOS execution pending | Not required | Exact public MIT-declared revision, two-file / 8.09 GB safetensors partition, immutable upstream MIT receipt, metadata and package/Transformers source hashes, backend-generated ordered reference placeholders, bounded one- and three-reference recipes, and three Expert-only remote workflows are sealed. The expanded optional-runtime symbol contract passed clean-base locked install/activation/workload/rollback qualification. The deterministic 130-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled. A fresh exact app plan reports all 11 files / 8,088,956,424 selected bytes complete at the immutable revision; the cache entry is installed, complete, and repair-free. No older model was deleted and no media has been generated. | -| P6.49 Ovis Image 7B text-to-image source admission | `df2fa98` | `ab47b67` | Remote real-weight memory/output safety/quality and physical macOS execution pending | Not required | Exact public Apache-2.0 revision, a 21-file / 21.81 GB Diffusers-only selection, five immutable safetensors weights, bundled LICENSE/NOTICE receipts, and explicit exclusion of duplicate native checkpoints plus the Python-bearing Ovis2.5 subtree are sealed. The bounded 1024px/50-step/guidance-5 Expert workflow retains package cancellation and offload hooks. The expanded optional-runtime symbol contract passed clean-base locked install/activation/workload/rollback qualification. The deterministic 131-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled. A fresh exact app plan reports all 21 files / 21,806,937,901 selected bytes complete at the immutable revision; the cache entry is installed, complete, and repair-free. No older model was deleted and no media has been generated. | -| P6.50 PRX 512 SFT text-to-image source admission | `012b101` | `9c320a4` | Remote real-weight memory/output safety/quality and physical macOS execution pending | Not required | Exact public Apache-2.0 revision plus incorporated T5-Gemma terms, a complete 19-file / 15.51 GB Python-free snapshot, five immutable safetensors weights, and pinned package/runtime receipts are sealed. The bounded native 512px/28-step/guidance-5 Expert workflow maps the generic token limit to PRX's exact argument and retains step-boundary cancellation. The expanded optional-runtime symbol contract passed clean-base locked install/activation/workload/rollback qualification. The deterministic 132-workflow catalog is graph-qualified/runtime-unqualified; the missing safety checker keeps Auto and Gallery disabled. A fresh exact app plan reports all 19 files / 15,514,188,109 selected bytes complete at the immutable revision; the cache entry is installed, complete, and repair-free. No older model was deleted and no media has been generated. | -| P6.51 Nucleus Image 17B MoE text-to-image source admission | `86cc756` | `8b4f002` | Model-snapshot license-file clarification, app storage capacity, remote real-weight memory/output safety/quality, and physical macOS execution pending | Not required | Exact public Apache-2.0-declared revision, complete 38-file / 51.66 GB Python-free snapshot, 12 immutable safetensors weights, and pinned package/runtime receipts are sealed. The bounded seven-bucket 1024px/50-step/guidance-4 Expert workflow retains package cancellation and offload hooks. The expanded optional-runtime symbol contract passed clean-base locked install/activation/workload/rollback qualification. The deterministic 133-workflow catalog is graph-qualified/runtime-unqualified; the missing license file, post-training, and safety checker keep Auto and Gallery disabled. The required app-only aggregate preflight was 36.31 GB short, so the snapshot was not submitted and no older model was deleted; no media has been generated. | -| P6.52 Krea 2 Raw/Turbo standard source and admission-gate review | `e176f14` | Not required | Static gated source/license review only; task-scoped terms acceptance, commercial eligibility/legal approval, downstream terms/content-filter implementation, immutable AUP receipt, backend-owned bounds/runtime admission, app capacity, remote heavy-hardware output review, and physical macOS execution pending | Not required | Two exact gated official revisions, two Python-free 17-file / 35.68 GB Diffusers candidate partitions, five immutable safetensors weights per recipe, duplicate native-checkpoint exclusions, pinned package source receipts, distinct Raw/Turbo recipes, and estimate-only envelopes are sealed. Custom terms, mandatory content filtering, missing package safety checker/bounds, and two queue-aware app preflight deficits of about 53.2 GB keep the standard family outside every runtime/download/user-facing surface. Terms were not accepted, no app POST occurred, no older model was deleted, and no weight bytes or media were fetched. | -| P6.53 Stable Diffusion 3 standard source and admission-gate review | `e6061d9` | Not required | Static gated source/license review only; task-scoped terms acceptance, commercial license/legal approval, authenticated component review, backend-owned bounds/runtime admission, app capacity, remote heavy-hardware output review, and physical macOS execution pending | Not required | The exact gated official revision, Python-free 31.01 GB snapshot, six-file / 15.50 GB fp16 base inventory shared with the prior SD3 ControlNet review, three package-owned routes, source receipts, recipes, and estimate-only envelope are sealed. Noncommercial-only terms, inaccessible gated configs, missing safety checker/bounds, and a 21.41 GB queue-aware app preflight deficit keep the family outside every runtime/download/user-facing surface. Terms were not accepted, no app POST occurred, no older model was deleted, and no base weight bytes were fetched. | -| P6.54 Long-video continuation boundary handoff | `67010c7` | Not required | Synthetic graph/loop proof only; durable restart checkpoints, retained-asset stitching, mux/cancellation recovery, final 30-minute graph, remote execution, and physical macOS pending | Not required | The generic planner now binds the first opening anchor and the generic shot executor consumes the preceding loop segment only for explicitly marked continuation jobs. Missing carry and unknown strategies fail before inference; a two-iteration synthetic graph proves exact last-frame handoff. No model, media, or live inference was used. | -| P6.55 Long-video component recovery gate | `53e22f2` (revalidates `76bbafe`) | Not required | Synthetic loop interruption/resume and temporary real-file FFmpeg proof only; process-restart persistence, final 30-minute graph, remote execution, and physical macOS pending | Not required | A completed loop segment survives node-cache clearing and cancellation recovery without regeneration. Two temporary retained MP4s stitch deterministically to 14 frames / 1.75 seconds and retain those values after audio mux. No model or live inference was used, and all test media was temporary. | -| P6.56 Bounded 30-minute chunk planner | `88b5d33` | Not required | Deterministic planning proof only; durable restart recovery follows in P6.57, while the executable graph, remote execution, and physical macOS remain pending | Not required | Exact 1,800-second LTX planning produces 374 bounded continuation jobs at 16 FPS with explicit execution controls and a 512-job default ceiling. Oversized duration/job-count and unqualified long single-job FramePack plans fail before inference. No model or media was used. | -| P6.57 Durable long-video loop restart recovery | `d648417` | `19620f9` | Synthetic process-replacement and temporary retained-file proof only; executable 30-minute graph, remote execution, and physical macOS pending | Not required | Opt-in durable loops persist bounded managed video-asset metadata after each completed iteration, bind recovery to the exact workflow/input identity, and remove the checkpoint on graph success. A replacement server with a different task ID resumes after segment 1 and runs only segment 2; generic and in-memory loops remain non-durable. | -| P6.58 Executable 30-minute LTX qualification graph | `479d495` (`a9cf15c` schema-boundary fix) | Not required | Static graph/materializer and deterministic plan proof only; remote six-hour execution, output review/publication, and physical macOS pending | Not required | One reviewed API graph binds the exact LTX revision, 374-job continuation plan, durable retained-segment loop, and file-native join. The loopback-only helper binds the staged opening-image bytes to the recovery identity, verifies the exact app-cached model revision, and requires explicit consent before app submission. No graph was submitted and no inference or media generation occurred locally. | -| P6.59 Wan Animate 2 Modular contract closure | `d31d5b6` | `fc67a7f` | Contract-only; artifact admission, real-weight remote execution, output review, and physical macOS pending | Not required | Both pinned package exports are Expert-visible with exact generic character-animation contracts and distinct base/distilled denoise steps. The 33-class / 93-workflow snapshot matches the complete pinned exported-class set; complete backend and client gates pass. No repository, runnable mode, Auto/template/Gallery surface, weights, models, or media were added or removed. | -| P6.60 Wan Animate 2 artifact/source review | `2b86e63` | Not required | Static immutable metadata/package-source review only; immutable component descriptors, remote compiled flex-attention execution/output review, and physical macOS pending | Not required | Two public Python-free 31-file snapshots and their distinct 45,920,934,868-byte safetensors receipts are sealed. The standard class is absent at the pin and both Modular indexes retain null and mutable PR component revisions, so no runtime/download catalog or runnable mode was admitted and no weights or media were fetched. | -| P6.61 Bounded AuraFlow app download selection | `ed3982a` | Not required | Exact immutable app plan only; remote real-weight execution/output review and physical macOS remain pending | Not required | Plan and POST now derive one exact 18-file / 16,837,479,394-byte fp16 runnable selection from the capability, excluding four duplicate/default weight surfaces and the unrelated single-file/ComfyUI artifacts. The pre-existing full-repository app transfer remains untouched and no cache entry was deleted. | -| P6.62 Bounded Chroma app download selection | `0922243` | Not required | Exact immutable app plan only; remote real-weight execution/output review and physical macOS remain pending | Not required | Plan and POST now derive one exact 18-file / 27,493,360,428-byte runnable Diffusers selection, excluding the 17,800,038,288-byte duplicate native checkpoint and demo artifacts. The selected files are already complete in the preserved full cache, so no POST or deletion occurred. | -| P6.63 Bounded Allegro, Latte, and Mochi app download selections | `cb3448d` | Not required | Exact immutable app plans only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 18/18/21-file runnable selections replace repository-wide planning and exclude Allegro's unsafe `.bin` duplicates, Latte's unsafe `.pt` plus unused decoder, and Mochi's 93.48 GB of duplicate/default partitions. Current queue-aware plans do not fit, so no POST or deletion occurred. | -| P6.64 Bounded Stable Audio and Stable Video app download selections | `8c96321` | Not required | Exact immutable app plans and fail-closed access evidence only; user-controlled repository-license acceptance/read-token configuration, remote real-weight execution/output review, and physical macOS remain pending | Not required | Exact 19-file / 5,348,079,831-byte Stable Audio and 12-file / 4,509,218,296-byte Stable Video runnable selections replace repository-wide planning. They exclude 24.14 GB of duplicate original, default/full-precision, and demo surfaces while retaining safetensors-only runtime components and rights receipts. After the bounded queue drained, fresh exact app plans reported both selections fit with 80,105,205,760 free bytes and no reservation. The app nevertheless rejected both POSTs with HTTP 403 `huggingface_access_required`: this installation has no configured Hugging Face token, and the repositories require the user to accept their terms and add an authorized read token through Model Manager. Stable Video has only 27,921 completed metadata bytes with 4,509,190,375 bytes remaining; Stable Audio has only 20,194 completed metadata bytes with 5,348,059,637 bytes remaining. Both cache entries remain inactive, incomplete, and repair-required with no active or corrupt files. No terms were accepted on the user's behalf, no payload byte was fetched outside the app, no older model was deleted, and no generation occurred. | -| P6.65 Bounded AudioLDM2 and Shap-E app download selections | `56faa30` | Not required | Exact immutable app plans only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 28-file / 4,480,959,446-byte AudioLDM2 and 14-file / 1,332,951,857-byte Shap-E safe-component selections replace repository-wide planning and exclude 8.04 GB of legacy pickle and unsafe duplicate surfaces. Both selections are already complete in the preserved full caches, so no POST or deletion occurred. | -| P6.66 Bounded Marigold app download and loader serialization | `c841203` | Not required | Exact immutable app plan and prior local tiny smoke only; remote quality/output review and physical macOS remain pending | Not required | One exact 14-file / 5,161,610,352-byte float32 safetensors selection replaces repository-wide planning, excludes 10.32 GB of legacy pickle and duplicate fp16 surfaces, and makes the loader's safe-serialization requirement explicit. The selection is already complete; no POST or deletion occurred, and older extra/unfinished blobs remain preserved. | -| P6.67 Bounded Whisper Tiny app download selection | `4783400` | Not required | Exact immutable app plan and prior local in-memory ASR smoke only; remote semantic/output review and physical macOS remain pending | Not required | One exact 13-file / 155,455,649-byte safetensors/processor selection excludes 453,397,578 bytes of duplicate PyTorch, Flax, and TensorFlow weights. A fresh exact app plan on 2026-08-14 reports all selected bytes complete after the pre-existing app-only repair finished; the cache entry is installed, complete, and repair-free with no active, missing, or corrupt files. No duplicate POST or deletion occurred. | -| P6.68 Bounded unconditional-image app downloads and loader serialization | `42fa625` | Not required | Exact immutable app plans and prior local tiny smokes only; remote quality/output review and physical macOS remain pending | Not required | Exact six-file safetensors selections for shared DDPM/DDIM CIFAR-10 and ImageNet64 consistency routes exclude 1.33 GB of legacy pickle plus repository code/demo surfaces and make all three loader serialization requirements explicit. Fresh exact app plans on 2026-08-14 report all 143,025,496 CIFAR-10 bytes and all 1,183,678,409 ImageNet64 consistency bytes complete after the pre-existing app-only repairs finished. Both cache entries are installed, complete, and repair-free with no active, missing, or corrupt files; no duplicate POST or deletion occurred. | -| P6.69 Bounded LCM DreamShaper app download and loader serialization | `08c34f4` | Not required | Exact immutable app plan and prior local tiny smoke only; remote quality/output review and physical macOS remain pending | Not required | One exact 17-file / 5,482,979,343-byte safetensors component selection excludes 7,710,367,026 bytes of duplicate single-file, ONNX, repository-code, and demo surfaces and makes the generic loader's safe-serialization requirement explicit. A fresh exact app plan on 2026-08-14 reports all selected bytes complete after the pre-existing app-only repair finished; the cache entry is installed, complete, and repair-free with no active, missing, or corrupt files. No duplicate POST or deletion occurred. | -| P6.70 Bounded Sana 0.6B fp16 app download selection | `982c1a3` | Not required | Exact immutable app plan only; remote real-weight execution/output review and physical macOS remain pending | Not required | One exact 17-file / 7,700,017,758-byte fp16 Diffusers selection matches the loader variant and excludes 8,844,837,635 logical bytes of default/full-precision aliases. The selected files are already complete in the preserved cache, so no POST or deletion occurred. | -| P6.71 Bounded FLUX.2 Klein component download and loader serialization | `80f7369` | Not required | Exact immutable app plan and prior local live smokes only; remote output review and physical macOS remain pending | Not required | One exact 21-file / 15,980,152,900-byte component selection excludes the 7,751,105,712-byte native duplicate and demo images while making both generic Klein loaders safetensors-only. The selected files are already complete in the preserved cache, so no POST or deletion occurred. | -| P6.72 Bounded primary FLUX.1 component downloads and loader serialization | `274b158` | Not required | Exact immutable app plans only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 25/26/26-file component selections for schnell, dev, and Krea total 101,218,748,600 bytes and exclude 72,409,924,695 logical bytes of native checkpoints, root autoencoders, and demos while making all three shared loaders safetensors-only. Every selected file is already complete in the preserved caches, so no POST or deletion occurred. | -| P6.73 Bounded conditioned FLUX.1 component downloads and loader serialization | `d39bfe2` | Not required | Exact immutable app plans only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 28/28/26/26-file component selections for Depth, Canny, Fill, and Kontext total 155,032,793,063 bytes and exclude 96,561,961,318 logical bytes of native checkpoints, root autoencoders, and demo media while making all four loader paths safetensors-only. Every selected file is already complete in the preserved caches, so no POST or deletion occurred. | -| P6.74 Bounded shared SD1.5 image/video download union and image-loader serialization | `896a723` | Not required | Exact immutable app plan plus prior local image/video smokes only; remote output review and physical macOS remain pending | Not required | One exact 21-file / 8,223,292,159-byte union covers the float32 image/PAG routes and fp16 AnimateDiff/AnimateLCM routes, excludes 39,036,647,490 bytes of pickle, non-EMA, single-file, and YAML surfaces, and makes all five image adapters safetensors-only. A fresh exact app plan on 2026-08-14 reports all selected bytes complete after the pre-existing bounded app request finished; the cache entry is installed, complete, and repair-free with no active, missing, or corrupt files. No duplicate POST or deletion occurred, and older cache files remain preserved. | -| P6.75 Bounded Z-Image component download and loader serialization | `8f96945` | Not required | Exact immutable app plan and prior local live smokes only; remote output review and physical macOS remain pending | Not required | One exact 21-file / 32,848,321,404-byte component selection excludes 51,345,993 bytes of gallery/PDF assets and makes all three generic Z-Image adapters safetensors-only. Every selected file is already complete in the preserved cache, so no POST or deletion occurred. | -| P6.76 Bounded PixArt Sigma component download selection | `a0fe20c` | Not required | Exact immutable app plan only; remote real-weight execution/output review and physical macOS remain pending | Not required | One exact 15-file / 21,828,231,839-byte component selection excludes 4,258,550 bytes of documentation images and matches the existing safetensors-only loader. Every selected file is already complete; the active aggregate queue temporarily exceeded the safety envelope by 374,414,385 bytes, so no POST, interruption, or deletion occurred. | -| P6.77 Bounded admitted Wan video component downloads and loader serialization | `e3fc376` | Not required | Exact immutable app plans plus prior local Wan smokes only; remote output review and physical macOS remain pending | Not required | Exact 21/19/43/22-file component selections for Wan 2.1 T2V/edit, VACE, Wan 2.2 I2V, and TI2V total 208,370,069,191 bytes, exclude 15,892,213 bytes of documentation/example media, and make every corresponding VAE/pipeline load explicitly safetensors-only. Every selected file is already complete; the active aggregate queue temporarily exceeded the safety envelope by 2,661,386,301 bytes, so no POST, interruption, or deletion occurred. | -| P6.78 Bounded JoyAI Image Edit app download selections | `c0c5944` | Not required | Exact immutable app plans only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 38/29-file component selections total 100,686,169,296 bytes and exclude 16,592,417 bytes of repository code plus test/example media while retaining both existing safetensors-only loader contracts. Edit is complete; the pre-existing repository-wide Plus app job remains active and differently scoped, so no new POST, join, interruption, or deletion occurred. | -| P6.79 Bounded ACE-Step component download and loader serialization | `96b71f9` | Not required | Exact immutable app plan only; remote real-weight execution/output review and physical macOS remain pending | Not required | One exact 21-file / 11,101,507,113-byte component selection excludes the 3,841,215-byte raw `silence_latent.pt` debug artifact; the runtime silence latent is already baked into the condition-encoder safetensors. The ACE-Step loader now explicitly requires safe serialization. The selected files are already complete; the active aggregate queue plus reserve exceeded free space by 2,662,033,371 bytes, so no POST, interruption, or deletion occurred. | -| P6.80 Bounded Sana Sprint app download selection | `da6a631` | Not required | Exact immutable app plan only; remote real-weight execution/output review and physical macOS remain pending | Not required | One exact 17-file / 7,703,536,042-byte selection seals the complete reviewed bfloat16 safetensors snapshot, including its license and bundled tokenizer terms, so future repository additions cannot silently expand the app download. Every selected file is already complete; the active aggregate queue plus reserve exceeded free space by 2,662,840,236 bytes, so no POST, interruption, or deletion occurred. | -| P6.81 Bounded DreamLite app download selections | `bcad80a` | Not required | Exact immutable app plans only; remote real-weight execution/output review and physical macOS remain pending | Not required | One shared exact 27-file component inventory seals the Base and Mobile snapshots at 10,143,788,478 logical bytes, including the separately consumed processor and tokenizer trees and the CC-BY-NC model card, with safetensors-only weights. Base was already complete. A fresh exact app plan on 2026-08-14 now reports all 27 Mobile files / 5,071,894,300 selected bytes complete at immutable revision `6695c3f4be230f0493fa5dbf78be3bc4d3bb2ab4` after the app-owned transfer finished. No duplicate POST, interruption, or deletion occurred, and all older model caches remain preserved. | -| P6.82 Bounded CogView4, ERNIE Image Turbo, and GLM-Image downloads | `9345725` | Not required | Exact immutable app plans only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 21/24/27-file component selections seal 98,566,385,977 logical bytes across the three safetensors-only pipelines. Each manifest retains every model-index component and its applicable license/model-card evidence, including ERNIE's distinct prompt-enhancer tokenizers and GLM's processor plus vision-language encoder. All selected files are complete; the latest active aggregate queue plus reserve exceeded free space by 2,730,885,389 bytes, so no POST, interruption, or deletion occurred. | -| P6.83 Bounded Qwen Image family downloads and loader serialization | `54991a3` | Not required | Exact immutable app plans only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 30/39/35/33-file component selections seal 230,866,011,411 logical bytes for Qwen Image 2512, Edit, Edit 2511, and Layered. Each manifest preserves its distinct processor/tokenizer and transformer shard topology, while all six standard 2512/Edit adapters now explicitly require safetensors; the Modular loader already did. Every selected file is complete; the active aggregate queue plus reserve exceeded free space by 2,664,392,168 bytes, so no POST, interruption, or deletion occurred. | -| P6.84 Bounded FLUX.1 Redux prior download and loader serialization | `1090d3e` | Not required | Exact immutable app plan only; remote real-weight execution/output review and physical macOS remain pending | Not required | One exact 9-file / 985,539,827-byte prior selection excludes 129,528,462 bytes of duplicate single-file weights and demo media while retaining both actual prior weight components, preprocess configuration, license, and model index. Both Redux and its fixed FLUX.1-dev base now load with explicit safe serialization. The selection is already complete; the active aggregate plan fit by only 73,247,222 bytes, so no redundant POST, interruption, or deletion occurred. | -| P6.85 Bounded CogVideoX-2B app download selection | `4b54458` | Not required | Exact immutable app plan plus live app transfer/cache evidence only; remote real-weight execution/output review and physical macOS remain pending | Not required | One exact 17-file / 13,775,557,177-byte component selection retains the complete reviewed Apache-2.0 safetensors runtime and excludes 15,561 bytes of `.gitignore` and duplicate-language documentation. The existing loader requires safe serialization, VAE tiling, and model CPU offload. A fresh exact app plan on 2026-08-14 reports all selected bytes complete at immutable revision `1137dacfc2c9c012bed6a0793f4ecf2ca8e7ba01` after the bounded app-owned transfer finished. The cache entry is installed, complete, and repair-free with no active, missing, or corrupt files. No model byte was deleted or downloaded outside the app, and no generation occurred. | -| P6.86 Bounded SANA-Video text/image app download selection | `15bd08f` | Not required | Exact immutable app plan plus live app transfer/cache evidence only; remote real-weight execution/output review and physical macOS remain pending | Not required | One exact 20-file / 14,002,562,288-byte component selection seals the complete reviewed Apache-2.0 safetensors snapshot shared by the text-to-video and image-to-video routes. The existing loaders retain their BF16 text/transformer, FP32 Wan VAE, safe serialization, mandatory VAE tiling, and sequential CPU offload contracts. A fresh exact app plan on 2026-08-14 reports every selected byte complete at immutable revision `db5f398b13ca086d09a50ce156c20527773841b1` after the bounded app-owned transfer finished. The cache entry is installed, complete, and repair-free with no active, missing, or corrupt files. No model byte was deleted or downloaded outside the app, and no generation occurred. | -| P6.87 Bounded LongCat AudioDiT app download selection | `ddb37ec` | Not required | Exact immutable app plan only; remote real-weight execution/output review, snapshot license declaration clarification, and physical macOS remain pending | Not required | One exact 13-file / 5,701,278,543-byte component selection seals the complete reviewed safetensors conversion used by the native 24 kHz audio route. The existing loader already requires safe serialization and sequential CPU offload; the model card retains the recorded upstream MIT-aligned provenance while the snapshot metadata itself does not declare a license. A fresh exact app plan on 2026-08-14 reports zero remaining bytes after the pre-existing app-owned transfer completed. No duplicate POST, interruption, or deletion occurred, and the preserved cache remains available for preview regression testing. | -| P6.88 Bounded SDXL Base, Turbo, and InstructPix2Pix downloads | `18248e8` | Not required | Exact immutable app plans plus prior source/contract qualification only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 21/21/20-file component selections seal 26,125,893,532 logical bytes across SDXL Base, Turbo, and InstructPix2Pix. Base and Turbo retain only their fp16 Diffusers variants; InstructPix2Pix retains its component-only safetensors tree and excludes 600 validation/demo files. All eight base-backed generic adapters now explicitly request the fp16 safetensors variant, while InstructPix2Pix remains safetensors-only without a variant suffix. Fresh exact app plans on 2026-08-14 report all 21 Base files / 6,941,211,875 selected bytes complete at immutable revision `462165984030d82259a11f4367a4eed129e94a7b`, all 21 Turbo files / 6,941,207,118 selected bytes complete at immutable revision `71153311d3dbb46851df1931d3ca6e939de83304`, and all 20 InstructPix2Pix files / 12,243,474,539 selected bytes complete at immutable revision `06653d47f8d22f2c2205a5884d6a24c5e76d2ca7`. Base and InstructPix2Pix finished through their pre-existing app-owned transfers. Turbo preserved and resumed the partial payload left when the planned source restart disconnected the old monitor's hidden POST thread; the current app completed reconstruction, removed only redundant transient incomplete files, and validated the exact selection. All three cache entries are installed, complete, and repair-free with no active, missing, or corrupt files. No model byte was deleted or downloaded outside the app, and no generation occurred. | -| P6.89 Bounded CogView3 Plus, Kandinsky 3, and Hunyuan-DiT downloads | `43480cb` | Not required | Exact immutable app plans plus prior source/contract qualification only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 20/19/20-file component selections seal 68,379,122,391 logical bytes across CogView3 Plus, Kandinsky 3, and the shared Hunyuan-DiT v1.2 distilled base. They preserve each model-index runtime tree and its bfloat16, fp16-variant, or unsuffixed safetensors topology while excluding deployment-only configuration, duplicate-language documentation, and 11 Kandinsky demo images. Fresh exact app plans on 2026-08-14 report all 20 CogView3 Plus files / 25,560,117,047 selected bytes complete at immutable revision `5d70e40732ac0efac98524c51a7fa9c82707f1e5` and all 19 Kandinsky 3 files / 28,391,690,623 selected bytes complete at immutable revision `bf79e6c219da8a94abb50235fdc4567eb8fb4632` after their pre-existing app-owned transfers finished. The Hunyuan-DiT base was not newly submitted; the separately admitted four-file Hunyuan Canny component subsequently completed through its preserved app-owned request and is recorded in P6.93. No duplicate POST, interruption, or deletion occurred, and all older model caches remain preserved. | -| P6.90 Bounded Lumina Next and OmniGen downloads | `1d1784c` | Not required | Exact immutable app plans plus prior source/contract qualification only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 16/11-file safetensors component selections seal 16,967,647,146 logical bytes across Lumina Next and OmniGen, including every immutable snapshot file used by their package-owned pipelines. The long-prompt LTX image-to-video capability now also publishes the same existing 22-file bounded selection as the ordinary LTX capability instead of appearing unbounded. Fresh exact app plans on 2026-08-14 report both selections complete at immutable revisions `0ee5ec90043acf5cb41fe96274af36eb7fad8d95` and `016e2f61d12a98303f6bbdf122687694d7984268`; both cache entries are installed, complete, and repair-free after their app-owned transfers. No duplicate POST, interruption, or deletion occurred. | -| P6.91 Bounded FramePack three-repository assembly | `76697aa` | `aeb0055` | Exact immutable app plans plus prior source/contract qualification only; remote real-weight execution/output review, repository-license clarification, and physical macOS remain pending | Not required | Exact 7/21/5-file selections seal 42,866,796,087 logical bytes across the FramePack transformer, HunyuanVideo scheduler/encoder/tokenizer/VAE base components, and FLUX Redux SigLIP processor/vision encoder. The base selection omits its unused 25.64 GB transformer tree, the vision selection omits the unused image embedder, every weight-bearing loader now requires safetensors, Studio setup exposes both auxiliary repositories, and the app resolves reviewed dependency selections even when an older client omits explicit file arguments. A fresh exact app plan on 2026-08-14 reports all five FLUX Redux vision files / 856,508,718 bytes complete at immutable revision `45b801affc54ff2af4e5daf1b282e0921901db87`; its cache entry is installed, complete, and repair-free after the pre-existing app-owned transfer finished. The FramePack transformer and HunyuanVideo base selections remain pending. No duplicate POST, interruption, or deletion occurred, and older cache files remain preserved. | -| P6.92 Bounded remaining admitted LongCat and Wan downloads | `e57b677` | Not required | Exact immutable app plans plus prior source/contract qualification only; remote real-weight execution/output review, recorded license clarifications, and physical macOS remain pending | Not required | Exact 31/32/43/41-file component selections seal 274,936,785,599 logical bytes across LongCat Image, LongCat Image Edit, Wan 2.2 T2V A14B, and Wan 2.1 first/last-frame video. The LongCat selections retain both separately loaded processor/tokenizer trees while excluding root deployment configuration and demo assets; the Wan selections retain every safetensors model-index component while excluding documentation media. Pinned remote path parity and 193 focused tests pass. Fresh exact app plans on 2026-08-14 now report all 31 LongCat Image files / 29,316,541,013 selected bytes complete at immutable revision `d2ea50b79a930074c37b9b97ce45e3b2ea8cf4d8` and all 32 LongCat Image Edit files / 29,316,540,813 selected bytes complete at immutable revision `7b54ef423aa7854be7861600024be5c56ab7875a` after their pre-existing app-owned transfers finished. Both cache entries are installed, complete, and repair-free; both Wan selections remain pending. No duplicate POST, interruption, or deletion occurred, and all older model caches remain preserved. Every admitted Studio primary model repository now has a bounded manifest except LTX-2's gated artifact and Wan Animate's unresolved component-revision contract, which remain deliberately fail-closed. | -| P6.93 Bounded admitted auxiliary downloads | `cc748d0` | Not required | Exact immutable app plans plus prior source/contract qualification only; remote real-weight execution/output review and physical macOS remain pending | Not required | Exact 4/4/4/4/4/4/5-file selections seal 13,476,063,409 logical bytes across Qwen Image ControlNet Union, Hunyuan-DiT v1.2 Canny ControlNet, SDXL T2I-Adapter Canny, SDXL ControlNet Canny, AnimateDiff MotionAdapter, SD1.5 ControlNet Canny, and AnimateLCM MotionAdapter plus LoRA. The selections exclude repository Python, demo media, legacy `.bin`/`.ckpt` weights, and unused full-precision duplicates while retaining the precise safe variant each existing loader consumes. Every admitted dependency repository now resolves a non-empty app selection; the FLUX Redux base requirement reuses its already-bounded primary FLUX.1-dev selection. Fresh exact app plans on 2026-08-14 report all seven selections complete at their immutable revisions: 3,536,036,150 bytes for Qwen Image ControlNet Union, 2,976,971,742 for Hunyuan Canny, 158,071,198 for SDXL T2I-Adapter Canny, 2,502,145,849 for SDXL ControlNet Canny, 1,815,334,485 for AnimateDiff, 1,445,176,301 for SD1.5 ControlNet Canny, and 1,042,327,684 for AnimateLCM. Every cache entry is installed, complete, and repair-free with no active, missing, or corrupt files. The final 5,404-byte SDXL ControlNet receipt repair was submitted only after two fresh app plans fit. The current worker runs the bounded-selection source. The affected 277-test matrix passes with 645 subtests and five expected skips; the complete backend gate passes 1,690 tests, 3,438 subtests, and 40 expected skips together with Ruff, 66-package compatibility, shell, preflight, and diff checks. No model deletion, generation, output, or physical macOS qualification occurred. | -| P6.94 Reconnect-safe app download visibility | `33d1754` | `b0c106b` | Source/unit and exact bundled-client evidence only; current-worker activation, active-transfer completion, remote generation, and physical macOS remain pending | Not required | The app now retains each active model transfer's latest bounded progress receipt, exposes a read-only schema-v1 `/hf_download/status` response without selected file paths, and includes the same authoritative active list in every WebSocket welcome. A reconnect replaces stale active client entries while preserving terminal history, so refreshing the browser no longer loses app-owned queue visibility or invents completion. The exact client build is mirrored into the backend. Focused backend coverage passes 41 tests and 95 subtests; the complete backend gate passes 1,691 tests, 3,438 subtests, and 40 expected skips with Ruff, compile, shell, and 66-package compatibility green. The complete client check passes, including the new reconnect regression, and the production bundle remains within its 533,504-byte ceiling at 533,425 gzip bytes. The Playwright scenario was not executed because this Linux checkout has no installed browser binary; no browser package was downloaded merely for this slice. The old live worker was not restarted while app transfers remain active, and no model/cache byte was downloaded outside the app, deleted, generated, or treated as macOS evidence. | -| P6.95 Exact join-aware app download planning | `a0a161a` | Not required | Source/unit evidence only; current-worker activation, active-transfer completion, remote generation, and physical macOS remain pending | Not required | A queue-aware plan for the same repository, immutable revision, and exact file selection as an active app task now excludes that task's existing reservation instead of counting it twice. The response identifies the already-queued task and reports a join as fitting without weakening the atomic reservation check or the HTTP 409 boundary for a conflicting revision/selection. Focused model/Gallery coverage passes 28 tests and 66 subtests; the complete backend gate passes 1,692 tests, 3,438 subtests, and 40 expected skips with Ruff and compile green. The old live worker still reports the pre-fix planning shape and was not restarted while transfers remain active. No task was duplicated, cancelled, interrupted, deleted, generated, or treated as macOS evidence. | -| P6.96 Qualification/download mutual exclusion | Not required | `3413455` | Source/unit and production-build evidence only; current-worker activation, active-transfer completion, remote generation, and physical macOS remain pending | Not required | A real release-qualification campaign now requires the app's bounded schema-v1 download status to be idle before startup and rechecks it before every model-family group; the Gallery runner performs the same fail-closed check before startup and immediately before every selected template. Dry runs can request the check explicitly with `--check-download-idle`. Focused campaign/runner coverage passes 55 tests, and the complete client check passes formatting, lint, typechecking, all unit suites, production build, and the 533,425 / 533,504-byte gzip bundle budget. A generation-free dry run against the old live worker still selects 76 jobs in six groups and reports 76/76 model-ready jobs, but correctly blocks 38 jobs on 50 absent Gallery input assets totaling 33,867,388 bytes; the new status route was not called because that worker predates it. The worker was not restarted while app transfers remain active, and no graph was submitted, no model/cache byte was deleted or downloaded outside the app, and no Linux result was treated as physical macOS evidence. | -| P6.97 Catalog-wide app transfer coverage audit | Not required | Not required | Exact immutable read-only app plans and live-caller/monitor inventory only; transfer completion, remote generation, and physical macOS remain pending | Not required | All 66 admitted capabilities collapse to 47 unique primary repository/revision/file selections: 23 are exact-complete and 24 retain bytes. Twenty-three pending primaries were already represented by a long-lived app request or the bounded admission monitor; the sole omission was the exact 38-file / 51,656,728,957-byte `NucleusAI/Nucleus-Image@5e963db4fd0a65c7e4faf53ca2d4eca567c4dcfa` selection. A dedicated app-only monitor now performs two fresh queue-aware plans before any future submission. Its first plan reported 178,564,902,912 free bytes, 116,682,001,651 queued reservation bytes, and the 68,719,476,736-byte reserve, so it correctly did not fit and no POST occurred. All eight unique auxiliary model requirements are likewise represented by existing live app callers or monitors. The audit downloaded no model byte directly, deleted no cache, submitted no graph, and made no output or macOS claim. | -| P6.98 Live bounded-transfer cutover and pinned Gallery manifest dimensions | `578e0a3` | Not required | Current Linux worker activation, focused source/unit tests, immutable manifest validation, live app install/static-route, and generation-free campaign evidence only; remote generation and physical macOS remain pending | Not required | After the preserved old queue drained, the supervisor was restarted onto the bounded source. The immutable 356-file / 480,430,370-byte Gallery manifest validates its exact bounded shape and asset-set identity. After disposable non-model uv cache cleanup, two fresh plans admitted the exact 969,249,348-byte download/staging reservation and the app installed and verified every asset. A second safe restart after all payload transfers became terminal registered `/template-gallery/`, which now serves 70 reviewed examples. The current worker is runtime-ready and its generation-free campaign exits zero with 76/76 jobs, 31/31 artifacts, download idle, and all 50 pinned inputs / 33,867,388 bytes ready across six groups. No model or asset byte was deleted or fetched outside the app, and no graph, inference, output, or physical macOS claim occurred. | -| P6.99 Current-attempt transfer progress accounting | `c140b3f` | Not required | Complete backend plus current-worker progress evidence only; generation and physical macOS remain pending | Not required | Resumed downloads no longer count abandoned pre-task partials or duplicate logical cache links as current progress. Immutable LFS hash prefixes are matched across standard Hub per-attempt suffixes; only files touched by the current task shrink its reservation, while retry files remain conservatively accounted as distinct disk consumption and unhashed metadata remains reserved until complete. The 65-test / 70-subtest focused matrix and complete 1,694-test / 3,438-subtest / 40-skip backend gate pass with Ruff, compatibility, preflight, compile, shell, and diff checks. The current worker runs the correction: a Stable Video attempt reported planning with the exact 4,509,190,375 bytes remaining, then failed closed on repository access before payload transfer. No cache entry was deleted and no graph or inference ran. | -| P6.100 Per-family qualification readiness and fitting-model audit | Not required | `a11fc77` | Complete client, generation-free campaign, and exact read-only app-plan evidence only; remote generation and physical macOS remain pending | Not required | Requested exact app-cache and byte-pinned input readiness now revalidate before every model-family batch, alongside the mandatory download-idle check, and the campaign retains each boundary receipt. The focused 55-test matrix and complete client gate pass with the unchanged 533,425 / 533,504-byte gzip bundle. A fresh dry run reports 76/76 jobs, 31/31 artifacts, and 50/50 inputs / 33,867,388 bytes ready across six groups. A read-only audit of all 19 candidates represented by the bounded admission monitor found every fitting ungated selection complete; only access-gated Stable Video (4,509,190,375 bytes remaining) and Stable Audio (5,348,059,637) fit with 80,037,949,440 free bytes and the 68,719,476,736-byte reserve. A separate low-frequency Stable Audio monitor performs two fresh app plans before any POST, uses only `/hf_download`, and failed closed with HTTP 403 before payload while no token is configured; this lets the two fitting Stability selections use the app's bounded two-transfer capacity after the user accepts their terms and configures an authorized read token. Every larger pending selection remains no-fit. No cache entry was deleted and no graph, inference, output, or macOS qualification occurred. | -| P6.101 Mandatory real-campaign readiness | Not required | `3ec4d69` | Complete client and generation-free campaign evidence only; remote generation and physical macOS remain pending | Not required | Every real release-qualification campaign now requires exact app-cache, download-idle, and byte-pinned default-input readiness even when its operator omits the corresponding dry-run switches. The complete selection is checked before startup and exact jobs are rechecked before every model-family batch; the Gallery runner retains its per-template download interlock. The focused 56-test matrix and complete client gate pass with the unchanged 533,425 / 533,504-byte gzip bundle, while a fresh dry run reports all 76 jobs and all three readiness classes green across six groups. No graph, inference, output, publication, activation, cache deletion, or macOS qualification occurred. | -| P6.102 Single-owner atomic qualification state | Not required | `4ac598c` | Complete client and generation-free state/lock evidence only; remote generation and physical macOS remain pending | Not required | A real qualification campaign now owns one process lock before refreshing or writing evidence, so a concurrent campaign cannot overwrite the active run's shared state or enter its app execution session. Invalid/dead-owner locks recover safely, owner-checked cleanup preserves replacements, and each state receipt is flushed then atomically renamed with temporary cleanup on failure. Dry runs remain non-owning and do not write campaign state. The focused 57-test matrix and complete client gate pass with the unchanged 533,425 / 533,504-byte gzip bundle. No graph, inference, output, publication, activation, cache deletion, or macOS qualification occurred. | -| P6 remaining | Pending | Pending | Remote pending | Pending | LTX-2.5 gated artifact/live qualification, other heavy families, and long-form workflow qualification remain open as independent segments. Kandinsky5 Video artifact/source evaluation is complete in backend `08e2550`, with corrected recipe evidence and remote execution still pending. | diff --git a/docs/hugging-face-visual-node-system-plan.md b/docs/hugging-face-visual-node-system-plan.md deleted file mode 100644 index f841d19e..00000000 --- a/docs/hugging-face-visual-node-system-plan.md +++ /dev/null @@ -1,1574 +0,0 @@ -# Hugging Face Visual Node System Plan - -> **Architecture correction — 2026-09-01:** Registered Cluster Nodes and User -> Nodes must use one composite-node schema, canvas type, renderer, boundary, -> expansion, and persistence system. The dedicated Cluster renderer and -> destructive Cluster-to-User reconstruction recorded later in this historical -> tracker are superseded and are not considered complete product behavior. The -> authoritative replacement contract and active implementation status are in -> [Unified Composite Node Contract and Implementation Plan](unified-composite-node-implementation-plan-2026-09-01.md). - -## Purpose - -MoDiff is a local visual authoring and execution surface for reviewed Hugging -Face Diffusers, Modular Diffusers, and Transformers workflows. This plan adds a -first-party node library that exposes Cluster Nodes containing complete -pipelines, reusable Block Nodes, Component Nodes, and direct Transformers Nodes -without storing built-in definitions as User Nodes or introducing another graph -executor. - -This document is an implementation tracker. Except in sections explicitly -marked historical or superseded, a checked item means the code, contract tests, -compatible client work, and stated proof level are complete. It does not claim -that every upstream-visible pipeline is executable in MoDiff. - -## Upstream contracts reviewed for this work - -The design follows the public contracts documented by Hugging Face: - -- [Diffusers pipeline overview](https://huggingface.co/docs/diffusers/main/api/pipelines/overview): a standard `DiffusionPipeline` is an end-to-end inference object containing models, schedulers, and processors. -- [Modular Diffusers overview](https://huggingface.co/docs/diffusers/main/modular_diffusers/overview): blocks are reusable, composable, and intended to be mixed, matched, swapped, and shared. -- [Modular Diffusers quickstart](https://huggingface.co/docs/diffusers/main/modular_diffusers/quickstart): pipelines expose `blocks`, `available_workflows`, `get_workflow()`, and nested `sub_blocks`. -- [Modular pipeline blocks API](https://huggingface.co/docs/diffusers/main/api/modular_diffusers/pipeline_blocks): leaf, sequential, loop, conditional, and Auto block types retain different execution semantics and build pipelines through `init_pipeline()`. -- [Modular Diffusers state guide](https://huggingface.co/docs/diffusers/main/modular_diffusers/modular_diffusers_states): `PipelineState` and `BlockState` are the public communication mechanism between blocks. -- [ModularPipeline guide](https://huggingface.co/docs/diffusers/main/modular_diffusers/modular_pipeline): component loading is lazy, components may be shared, and a modified block definition must be used to construct a new pipeline. -- [ControlNet guide](https://huggingface.co/docs/diffusers/main/en/using-diffusers/controlnet) and [SDXL ControlNet API](https://huggingface.co/docs/diffusers/main/en/api/pipelines/controlnet_sdxl): a prepared control image and separately loaded ControlNet component condition the base denoiser; conditioning scale and guidance start/end are runtime controls. -- [Transformers pipeline guide](https://huggingface.co/docs/transformers/main/pipeline_tutorial): direct Transformers inference is task-oriented and uses generic or task-specific pipeline contracts. - -MoDiff remains pinned to the reviewed Diffusers revision declared by the -executable dependency contract. Documentation for upstream `main` is research -input; code is admitted only after it matches the pinned revision and the -generated no-weight compatibility snapshot. - -## Product terminology and ownership - -Custom blocks created by a user are **User Nodes**. First-party Hugging Face -definitions remain immutable library registrations. Every row below inserts -the same canonical composite `block` canvas type; the section controls -discovery, ownership, provenance, and permissions, not rendering or interface -inference. The intended library sections are: - -| Section | Ownership | Examples | -| ----------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| Diffusers Cluster Nodes | Immutable, generated from reviewed Diffusers contracts; each contains a complete upstream pipeline/workflow | Qwen Image — Text to Image | -| Modular Diffusers Block Nodes | Immutable, generated from reviewed block contracts | Text Encoder, VAE Encoder, Denoise, Decode | -| Diffusers Component Nodes | Immutable MoDiff adapters over reviewed library components | Load Components, Scheduler, Guider, Adapter | -| Transformers Cluster Nodes | Immutable composites over an exact reviewed task graph, artifact revision, and optional runtime | SmolLM2 — Text Generation, Whisper — Speech to Text | -| Transformers Nodes | Immutable task-generic MoDiff adapters used inside Transformers Clusters or directly | Model Loader, Generate Text, Transcribe Audio | -| Utilities | Built into MoDiff | Preview, media processing, export | -| User Nodes | User-owned, mutable custom block definitions | A modified Qwen Control cluster | - -The existing `/studio/blocks` storage remains user-owned. Built-in definitions -must use a separate read-only contract and must never be written into that -directory. - -## Reuse model: shared core plus narrow deltas - -Pipeline data is model/workflow-specific only where upstream behavior differs. -There is significant overlap across Qwen, Flux, Wan, LTX, Cosmos, Helios, and -other families. The implementation must represent that overlap explicitly. - -The contract is layered as follows: - -1. **Generic task contract** — task identity and stable user concepts such as - text-to-image, image-to-image, inpaint, text-to-video, image-to-video, - prompt, source media, mask, seed, size, duration, and output media. -2. **Reusable block-role contract** — common roles such as text encoding, image - encoding, VAE encoding, latent preparation, timestep preparation, denoising, - decoding, and postprocessing. -3. **Pipeline/workflow definition** — the exact upstream blocks selected by - `get_workflow()`, their hierarchy and order, field aliases, state flow, - optional stages, and component requirements. -4. **Model/artifact execution profile** — immutable repository revision, - component classes, dtype, quantization, device/offload limits, optional - runtime requirements, qualification, and Auto eligibility. -5. **Composite block instance state** — prompt, seed, dimensions, steps, user - media, exposed component choices, presentation overrides, and a copy-on-write - effective graph when the workflow instance is structurally customized. - -The frontend renderer, canvas type, public-boundary model, graph compiler, -persistence, expansion, state validation, and Auto canonicalization are -generic. Pipeline and model identity remain backend-declared data. Source kind -must never select a different renderer or re-infer inputs, outputs, or controls. - -### Text-to-image overlap - -Qwen Image and Flux text-to-image share the task, prompt-to-latent-to-image -shape, loader/component lifecycle, seed and sizing concepts, denoising progress, -preview/output handling, persistence, and most graph interactions. They differ -in exact text encoders, prompt fields, latent preparation, guidance semantics, -transformer inputs, scheduler compatibility, default values, and artifact/resource -profiles. Those differences are adapter/profile data, not separate frontend -node implementations. - -The current pinned catalog groups 16 workflow definitions under the shared -`diffusers.task.text_to_image.v1` contract. Flux and Qwen have common semantic -paths such as text encoding, denoise input, denoiser, after-denoiser, and -decode, but their exact upstream leaf classes are respectively Flux- and -Qwen-specific. MoDiff therefore reuses the task UI, graph mechanics, and block -roles while retaining the exact class and hierarchy declared by each reviewed -workflow. - -### Image-to-video overlap - -Wan image-to-video overlaps with Cosmos, Helios, Hunyuan Video, LTX, and other -image-to-video workflows in the generic source-image + prompt -> temporal -conditioning -> latent denoising -> video decode/export flow. Reusable concepts -include source-media validation, prompt encoding, frame geometry, seed, temporal -length, denoising progress, video output, preview/export, resource planning, and -workflow persistence. - -Pipeline-specific deltas include image-encoder requirements, one-image versus -first/last-frame conditioning, VAE latent layout, frame packing, temporal RoPE, -noise and timestep schedules, transformer signatures, decode tiling/chunking, -supported resolutions/frame counts, audio coupling, and exact component -artifacts. A shared image-to-video contract therefore does not imply that a Wan -block can be connected to an LTX or Cosmos block without compatibility proof. - -The current pinned catalog groups nine workflow definitions under -`diffusers.task.image_to_video.v1`, including Wan, Wan 2.2, LTX, Cosmos, -Helios, and Hunyuan Video families. Wan and LTX share semantic paths for text -encoding, VAE encoding, timestep and latent preparation, the denoise loop, and -decode, while Cosmos uses a different vision packing/update structure. These -are variations of one reusable task flow, not nine unrelated frontend -implementations and not one interchangeable set of model-specific leaf blocks. - -## User experience - -First-party catalog rows are searchable and immutable. Every reviewed -definition is insertable as an exact structural Cluster, including -**Catalog only** entries. Structural insertion permits expansion, block -inspection/editing, composition, and persistence; it does not authorize Run. -An inserted Cluster can prepare a qualification run only when its publication -is **Graph qualified** and its exact runtime, artifact, dependency, graph, and -resource receipts pass. This volatile authority does not change the immutable -public executability claim. - -### Complete Cluster Node - -1. The user opens **Diffusers Cluster Nodes** or **Transformers Cluster Nodes** - and selects a reviewed entry. -2. MoDiff inserts one collapsed Cluster Node with important inputs and output. -3. Run is available only for an exact graph-qualified execution admission; - Auto applies only a qualified resource recipe. Catalog-only Clusters remain - safely editable structural graphs. -4. Expanding a Diffusers Cluster reveals the selected top-level Modular blocks; - expanding a Transformers Cluster reveals its exact MoDiff loader, task - action, input, and preview nodes. -5. In a Modular Diffusers Cluster, expanding a compound or loop block reveals - nested `sub_blocks` without changing sequential/loop/conditional semantics. -6. Editing a child parameter updates the collapsed projection and only that - Cluster Node instance. -7. Collapse/expand is presentation state. Both views export the same semantic - graph and produce the same cache identity. - -### Workflow and model changes - -- Changing a workflow uses the reviewed equivalent of `get_workflow()`, keeps - compatible instance values, adds newly required inputs, and removes stale - workflow-only state. -- Changing to a compatible artifact within the same pipeline family keeps the - block structure but invalidates loaded components and the Auto plan. -- Changing pipeline family is an explicit replacement with a preview of values - retained, reset, and removed. - -### Composition - -In Expert mode, a user may insert, remove, replace, or reorder compatible -blocks. The editor validates required and produced state keys, component -requirements, container semantics, and outputs. A structural edit updates the -same workflow instance copy-on-write; it never mutates the built-in definition, -changes the canvas renderer, or creates a reusable User Node implicitly. - -Canonical Cluster Nodes and parameter overrides may remain Auto-managed. -Unreviewed structural edits switch to Expert until the resulting composition -matches a reviewed workflow or composition recipe. - -## Technical architecture - -### First-party library manifest - -A bounded read-only manifest owns provider, library revision, pipeline class, -blocks class, workflow, generic task contract, hierarchy, inputs, outputs, -components, integration status, and a content hash. Stable definition identity -is derived from the provider, pinned revision, pipeline class, workflow, and -block path. - -The existing generated `data/modular-workflow-contracts.json` remains the -upstream structural source. Runtime/profile registries remain authoritative for -execution and Auto eligibility. Visibility never implies runnability. - -The resolved workflow and block snapshots intentionally remain stable. The -separate reviewed `data/modular-conditional-contracts.json` companion retains -the original unpruned `pipeline.blocks` trees for UI and composition consumers: -34 pipeline classes, 94 workflows, 632 static definitions, 1,051 placements, -and 73 conditional selectors with complete presence/absence truth tables and -workflow execution traces. This avoids silently changing existing Cluster -identities while preserving official inactive branches and skipped-block -semantics. - -The current manifest contains 102 immutable Cluster definitions and 505 -deduplicated block contracts. The 94 pinned Modular Diffusers workflows account -for 483 exact contracts: 393 visible child placements plus 90 aggregate root -contracts that retain selected-workflow `PipelineState` initialization -metadata. Eight reviewed Transformers composites contribute 22 task-graph -blocks. These Transformers children are MoDiff's exact generic task nodes; -Transformers does not expose the Modular Diffusers `blocks`/`sub_blocks` API. -Schema v5 additionally publishes 321 content-addressed Block Role adapters for -all 48 contract-only workflows and the seven equivalent-standard definitions. -Nine custom loop-state adapters supplement the 12 Helios, MiniMax Music, and -Wan Animate workflows. All remain explicitly structural; they do not imply an -artifact, resource recipe, or executable Studio route. - -### Graph representation - -The visible `useFlowStore` graph remains the only executable graph. A generic -compiler expands a reviewed pipeline/workflow definition into existing MoDiff -nodes and edges. It adds explicit component bindings and state connections; -there is no hidden browser runtime and no second pipeline executor. - -`PipelineState`-like data must be represented by an explicit, typed graph value -whose declared keys are inspectable and validated. Loop blocks remain control -flow containers. Runtime tensors and component handles are process-local and -are regenerated on the next execution after a refresh. - -### Persistence - -A saved first-party instance stores the exact definition reference and embedded -snapshot, library revision, content hash, instance parameter values, explicit -public boundary, selected static execution admission and Studio-spec receipt, -copy-on-write effective graph when structurally changed, and presentation -state. Loading checks all receipts before applying values. Unsupported drift -produces a migration-required state and never silently resets parameters or -re-derives ports. The canonical persistence target is `BlockInstanceV2` in the -[unified contract](unified-composite-node-implementation-plan-2026-09-01.md). - -### Auto - -Before graph fingerprinting, readiness, managed-control synchronization, or -resource planning, first-party Cluster Node containers are normalized to stable -semantic child identities. Expansion, collapse, and container layout are -excluded from the execution fingerprint. Structural changes are included. - -## Implementation tracker - -### P0 — Research and durable contract - -- [x] Review repository graph, security, persistence, and Auto constraints. -- [x] Review the official Diffusers/Modular Diffusers/Transformers documents listed above. -- [x] Confirm the pinned snapshot discovers 34 Modular pipeline classes and 94 upstream workflows without loading weights. -- [x] Define the shared-task versus pipeline-delta architecture in this document. -- [x] Add the first-party library manifest and strict validation. -- [x] Add tests proving different pipeline families reference shared generic task contracts. - -### P1 — Read-only backend catalog - -- [x] Publish the reviewed first-party manifest through a bounded read-only API. -- [x] Keep contract-only and equivalent-standard routes distinguishable from reviewed MoDiff contracts. -- [x] Add response-size, schema, revision, hash, and no-model-import tests. -- [x] Document the API contract. - -### P2 — Client catalog and ownership separation - -- [x] Add a strict client parser/store for the first-party manifest. -- [x] Add Diffusers Cluster Nodes, Modular Diffusers Block Nodes, Diffusers Component Nodes, Transformers Cluster Nodes, and Transformers Nodes sections. -- [x] Keep custom blocks named User Nodes. -- [x] Keep first-party definitions immutable and separate from `/studio/blocks`. -- [x] Add search, readiness, accessibility, and mocked client contract tests. -- [x] Add the full mocked-browser catalog interaction test to the existing - Studio harness. - -### P3 — Legacy Cluster composite (historical; V2 remediation open) - -The checked rows in this section record the implementation that existed before -the 2026-09-01 architecture correction. They are not proof of the unified -composite acceptance contract. In particular, a separate root type/renderer -and destructive reconstruction into a User Node are now rejected designs. - -- [x] Define a strict persisted Cluster Node instance contract with exact - definition revision/hash, isolated parameter overrides, presentation-only - expansion state, and stable semantic child identities derived from block - paths. -- [x] Add a generic nested hierarchy projection, collapsed input parameter - projection, JSON round-trip proof, and a semantic snapshot that excludes - expand/collapse state. -- [x] Extract provider-neutral child identity, ownership, and descendant - mechanics while preserving existing User Node child IDs. -- [x] Materialize the stable semantic child identities as recursively nested, - definition-backed graph containers with deterministic layout. -- [ ] Remove the legacy dedicated Cluster Node renderer. Route registered and - user-owned definitions through the canonical `block` renderer and common - V2 store actions while retaining catalog ownership and authority as - metadata/capabilities. -- [x] Rebuild derived child containers from the exact reviewed definition after - persistence, retain per-instance parameter overrides, and fail closed on a - revision/content-hash mismatch. -- [x] Prove discovery-only Cluster containers are excluded from execution and - that expanding/collapsing them cannot change the surrounding executable - graph export. -- [x] Persist an exact per-instance execution-mode/spec receipt and only the - backend-declared tunable binding sources; reject artifact, pipeline-class, - and revision overrides, migrate schema-v1 instances, and invalidate stale - derived execution children after a mode or execution-parameter change. -- [x] Annotate every materialized bound child field with its backend binding - source and persistence policy. Expanded child edits now update the owning - root instance (and collapsed projection) for upstream inputs or execution - overrides, while reviewed artifact/model/revision fields are disabled and - rejected as sealed. -- [x] Give exact upstream leaf Block Nodes persisted parameter disclosures. - Only workflow inputs declared by both the pinned block and owning Cluster are - editable; intermediate tensor/state fields are not presented as ordinary - values. A block edit updates the isolated root instance and collapsed - projection. -- [x] Define a stable execution fingerprint over the exact definition, - admission/spec receipt, persisted parameters, sealed artifact values, and - auxiliary dependencies; presentation state and layout are excluded, while - mode or parameter changes alter the fingerprint. -- [x] Project execution-child progress and preview updates onto the collapsed - Cluster root, while expanded Clusters keep updates on the exact child node; - derived preview fields are removed when execution authority is invalidated or - rebuilt after refresh. -- [x] Prove collapsed/expanded equivalence again after P4 compiles Cluster - children into executable nodes and edges. - -### P4 — Workflow-to-graph compiler - -- [x] Enrich the pinned no-weight snapshot with each block's declared inputs, - intermediate outputs, required inputs, component/config requirements, and - ordered container membership from the official block APIs. -- [x] Capture the selected workflow's aggregate root block contract so - `PipelineState` initialization retains named and variadic `kwargs_type` - metadata before any child block runs. -- [x] Add a fail-closed structural compiler plan with exact block identities, - state sequencing, container entry/exit, loop feedback, component/config - bindings, unresolved adapter identities, and no execution claim. -- [x] Parse and structurally compile all 94 pinned workflows without importing - model libraries or downloading weights: 82 are statically state-closed and - 12 truthfully report upstream container-local state that requires an exact - runtime adapter. -- [x] Join 33 existing reviewed generic action/state-edge contracts to their 31 - exact pipeline/workflow definitions as backend-declared discovery metadata; - keep every definition at `executionClaim: discovery_only` until - materialization and runtime qualification pass. -- [ ] Compile reviewed pipeline/workflow definitions into the existing graph. -- [ ] Represent component bindings and declared state flow explicitly. -- [ ] Preserve sequential, loop, conditional, and Auto block semantics. -- [ ] Reject unknown block classes, paths, revisions, or incompatible state. -- [ ] Cover pinned upstream parity without model downloads. - -The first materialization phase now exists for statically admitted definitions. -It consumes the sealed Studio execution-spec receipt and the live generic node -registry, creates stable per-Cluster node and edge identities, applies only -backend-declared instance-input and execution-parameter bindings, and reports -missing dynamic fields or binding sources. The client has no handwritten -prompt/media alias table. Selection and tunable values survive JSON -save/refresh independently per Cluster instance. Materialized nodes remain -disabled and excluded from graph export until a volatile runtime/resource -authority matches them. Admission schema v3 also declares the -ordered loader/input field actions and value sources required to obtain each -dynamic Modular node schema. A provider-neutral runner now attaches the -isolated skeleton to the visible graph, applies those backend-declared actions, -waits for the WebSocket node-definition updates, and rebinds the exact fields -and typed edges with stable IDs. It still returns `executable: false`; -Admission schema v3 now supplies every sealed literal/artifact binding and its -exact auxiliary model dependencies; planner values are accepted only for -declared execution-parameter sources and cannot override prompts, artifacts, -pipeline classes, or revisions. When reconciliation is complete it can issue the same bounded -`studioExecutionSpec` node-ID receipt that the existing backend graph admission -already validates. Completing this P4 item requires component/state proof plus -resource/runtime admission. - -The 12 custom-container structural closures are now represented by nine exact -loop-state adapters: six Helios variants, MiniMax Music 3, Wan Animate 2, and -Wan Animate 2 Distilled. They retain the upstream iteration source, -initializers, published `latent_chunks` or `segment_frames` state, and progress -semantics from each subclass `__call__`. This closes static planning without -inventing a generic edge. Runtime Studio/profile/artifact admission for those -12 workflows remains open. - -All 94 reviewed Diffusers definitions are now structurally insertable from the -live frontend independently of execution admission. A visible browser proof -inserted **Helios Pyramid Distilled — Text To Video**, expanded its pinned -block hierarchy, expanded the exact `HeliosTextEncoderStep` parameter -disclosure, edited the prompt inside that block, saved, refreshed, and verified -the same prompt and disclosure state before collapsing back to the identical -root projection. Evidence is preserved under -`data/qualification/local-review/hugging-face-clusters/`. - -#### Diffusers readiness inventory — 2026-08-26 - -The pinned manifest still contains 94 exact Modular Diffusers workflow -definitions. They are intentionally split by proof level rather than treated -as 94 interchangeable executions: - -| Proof level | Definitions | Current meaning | -| ------------------------------------- | ----------: | ---------------------------------------------------------------------------------------------------------------- | -| Reviewed MoDiff action/state contract | 39 | Exact generic action roles and state edges exist for the pinned workflow. | -| Contract only | 47 | Exact upstream block hierarchy exists, but a MoDiff execution adapter/profile has not yet been admitted. | -| Equivalent standard route | 7 | MoDiff has an exact workflow-scoped standard Diffusers route; that does not claim split-block Modular execution. | -| Official whole-workflow route | 1 | MiniMax Music 3 uses its package-owned Modular workflow through a sealed top-level block execution contract. | - -Forty-eight execution admissions across 47 definitions now pass the static -gate: the original 40 reviewed-action joins, seven exact equivalent-standard -routes, and the sealed MiniMax whole-workflow route. Every admission remains graph-qualified and -insertable but publishes `executable: false`, `autoEligible: false`, and -`liveProof: false` pending exact runtime/resource authority, visible-frontend -evidence, and manual approval. - -No definition in the reviewed-action bucket is waiting for a static graph -adapter. All 47 contract-only definitions and all seven equivalent-standard -definitions now have exact Block Role coverage, and the remaining custom-container -workflows additionally have their nine exact loop-state supplements. They -remain at their existing readiness levels because structural coverage is not a -split-block Studio execution route. The seven equivalent-standard definitions still need -separate split-block Modular execution admissions; a working standard Diffusers route is -not treated as proof of that equivalence. - -### P5 — Initial executable Cluster Node publication - -- [x] Validate all 40 admitted exact model-type/mode joins to declarative - Studio execution specs: 11 Qwen joins, six FLUX-family joins, three Wan - joins, two Z-Image joins, and all 18 pinned SDXL Modular workflows. Validate - role/state compatibility because identifier equality alone is not proof. -- [x] Publish expandable composites only for workflows with an existing reviewed MoDiff execution contract. -- [x] Keep contract-only entries visible only at their truthful proof level. -- [x] Add live parity evidence separately for each currently admitted model/workflow/artifact/hardware recipe. - -The static admission audit now accepts all 39 definitions with reviewed MoDiff -actions. This includes Qwen Image text-to-image and image-conditioned routes, -Qwen Image Edit inpainting, FLUX Kontext, FLUX.2 Klein, Wan text and single- -image video, and both Z-Image workflows in addition to the earlier joins. The -Wan FLF graph explicitly joins its -user-facing `image_to_video` Studio mode to the upstream `flf2v` state-flow -adapter and FLF2V artifact, plus all 18 pinned SDXL workflows: base, -ControlNet, ControlNet Union, IP-Adapter, and the ordinary/Union ControlNet plus -IP-Adapter compositions for text-to-image, image-to-image, and inpainting. -The inpainting graphs explicitly carry the source image, mask, encoded mask, -masked-image latents, image latents, and route state. Public executable and -Auto-eligible publication flags remain false until their exact live evidence is -manually approved. - -The current Z-Image Studio spec remains rejected because it uses the standard -`ZImagePipeline` loader/generator graph, -not `ZImageModularPipeline` actions, so it cannot materialize the expandable -Modular workflow without a separate reviewed Modular execution spec. - -All 30 admitted routes currently declare `live_proof: false`; the additional -Z-Image Modular candidate is rejected against its standard-pipeline Studio -spec. Graph-qualified -definitions may be inserted, and an exact volatile authority may enable a -qualification run, but the immutable catalog -must not claim public executability or Auto eligibility until the evidence is -reviewed and the admission receipt is promoted. - -### P6 — Auto and managed graph integration - -- [ ] Canonicalize composites before binding divergence and fingerprints. -- [x] Keep Auto active across collapse/expand and managed parameter overrides. -- [ ] Replan resources after compatible artifact, workflow, or structural changes. -- [ ] Switch unreviewed structural User Node forks to Expert with an actionable reason. - -The current static resource join finds exact declared Auto pairs for the -admitted Qwen and SDXL contracts. Wan FLF remains Expert-only until a measured -recipe is reviewed. The admitted execution profiles that declare an optional -overlay require the reviewed Transformers/PEFT runtime on this host; -the immutable node-library contract records no volatile installed/active -state, and catalog browsing must not install or activate that overlay. - -### P7 — Composition editor - -- [ ] Publish compatible block insertion/replacement points. -- [ ] Validate required/produced state, components, outputs, and container kinds. -- [ ] Rebuild a modified upstream block definition through `init_pipeline()`. -- [ ] Save modifications as User Nodes. - -### P8 — Transformers composites - -- [x] Present existing task-generic Transformers nodes under the correct provider. -- [x] Add composite loader/action/preview definitions for the seven initially - reviewed text, vision-language, any-to-any, and speech routes. -- [x] Add a separately sealed Wav2Vec2 CTC speech-to-text Cluster using the - official `AutoProcessor` and `AutoModelForCTC` boundary. -- [x] Preserve explicit optional-runtime consent, exact target-profile selection, - digest verification, and activation/rollback through Setup. -- [x] Do not install Transformers during discovery, browsing, insertion, or Auto planning. - -### P9 — Migration and release gates - -- [ ] Preserve existing user-block and workflow formats through explicit migrations. -- [ ] Run backend, client, browser, graph round-trip, and security-boundary gates. -- [ ] Record static, mocked, artifact, runtime, and live-output evidence separately. -- [ ] Update user-facing documentation only for behavior that is actually shipped. - -### Promotion and rollout plan - -The remaining work is delivered through one fail-closed promotion path rather -than by changing catalog flags by hand. Each promoted Cluster Node must carry -the exact definition, graph-adapter, Studio execution-spec, artifact revision, -optional-runtime, resource-recipe, and live-evidence receipts that authorized -its publication. Missing or stale receipts return the entry to **Catalog only** -without changing saved instance parameters. - -#### R1 — Qwen and Wan publication foundation - -- [x] Add a backend-owned publication receipt that distinguishes discoverable, - insertable, and executable admissions without consulting volatile installed - state while browsing. -- [x] Make the client catalog derive its readiness and insertion behavior only - from that receipt; the frontend must not contain a Qwen/Wan allowlist. -- [x] Insert a selected definition as one Cluster root with an isolated - per-instance execution selection and deterministic child identities. -- [x] Finalize dynamic Modular fields through the existing backend actions, - verify the exact Studio-spec node receipt, then enable execution children only - after the required runtime and resource checks pass. -- [ ] Promote Qwen Image Edit first, followed by Qwen Image Edit Plus, Qwen - Layered, Qwen Control, and Wan FLF. Each promotion remains independently - reversible if its artifact, runtime, or evidence receipt changes. - -The schema-v4 static admission now includes a nested publication receipt. -Thirty reviewed admissions publish as **Graph qualified** and insertable; the rejected -Z-Image join remains **Catalog only**. Every publication still has -`executable: false` and `autoEligible: false`. Insertion is generic, selects a -unique admission when possible, and projects visible Studio values only through -the backend-declared input and execution-parameter bindings. A prepared Cluster -materializes and finalizes its isolated child graph. Its children are enabled -only by a volatile authority joining the live runtime fingerprint, exact -optional runtime, installed artifact revision, backend Auto candidate, -dependencies, and Studio execution spec. This permits qualification runs -without mutating the immutable publication claim. - -The visible frontend now prepares and executes all five admitted Qwen routes -independently. Qwen Image Edit, Edit Plus single- and multi-image, Layered, and -Control each have a completed Cluster-route output. The Layered route preserved -all four alpha outputs, and Control joined its exact pinned ControlNet -dependency. The preparation/live-execution matrix is stored beside the Qwen -Image Edit run in -`test-review/frontend-pipeline-e2e-2026-08-24/qwen-cluster-qualification/`. -The Layered pass found that Studio's fixed `add alpha` image-loader mode had -been classified as an execution parameter; admission schema v4 now seals both -alpha-operation literals and the strict client parser accepts them only as -sealed sources. - -#### R2 — Semantic and persistence equivalence - -- [x] Canonicalize a Cluster to the same executable graph irrespective of its - collapsed or expanded presentation state. -- [x] Prove identical executable node/edge payloads and execution fingerprints - for collapsed and expanded views. -- [x] Save and reload the workflow, rebuild derived children from the pinned - definition, re-run admission/finalization, and prove the executable payload - and per-instance parameters are unchanged. -- [x] Prove that editing one Cluster, changing its model/workflow selection, or - invalidating its runtime authority cannot modify another Cluster instance. - -The materializer now emits a canonical execution snapshot that excludes -presentation, layout, selection, progress, preview, and pre-admission disabled -state. Contract tests prove this snapshot is identical for collapsed and -expanded materializations and after JSON round-trip/rematerialization. Enabled -contract tests now also prove byte-equivalent API node/path exports for -collapsed and expanded views and after persisted-root rematerialization plus -reauthorization. The visible-frontend Qwen qualification run proves the -collapsed live path; a second full model run is not required for the -presentation-only parity already established at the exported API boundary. - -SDXL Modular now adds a full live equivalence proof. A collapsed 12-step run -was saved, reopened through **My workflows** in a fresh frontend session, and -expanded into ten upstream Modular blocks. The reopened root retained its -prompt and step override. After exact Expert requalification, the expanded run -used the same execution fingerprint and run-input hash as the collapsed run and -produced a byte-identical WebP. See -`test-review/frontend-pipeline-e2e-2026-08-24/sdxl-cluster-qualification/receipt.json`. - -The independently admitted SDXL `image2image` workflow now has the same proof. -The visible frontend imported its source image, inserted the seven-node Cluster, -and ran an eight-step recipe with strength `0.65`. The saved workflow was then -reopened in a fresh browser session and expanded into 11 upstream Modular block -containers. Source image, prompt, steps, dtype, and strength all survived. The -collapsed and expanded exports were identical after removing only volatile -session/check timestamps, both runs used execution fingerprint -`hfcluster_3d5f730b` and run-input hash `run_352e4da3`, and both produced the -same WebP hash. See -`test-review/frontend-pipeline-e2e-2026-08-24/sdxl-image2image-cluster-qualification/receipt.json`. - -This run also closed an overlapping-parameter ambiguity. When a Studio binding -source is itself an exact upstream workflow input (`dtype` and `strength` for -this SDXL workflow), admission now persists it in the Cluster instance rather -than in a second execution-only override. The collapsed field, expanded child, -saved snapshot, and executable graph therefore read one value. - -The independently admitted SDXL `inpainting` workflow extends that proof to a -second visible media input and mask-specific state. The frontend imported the -source image and mask through **Assets**, persisted `image`, `mask_image`, -`strength`, `dtype`, prompt, dimensions, and steps on the Cluster instance, -and ran its eight-node API graph as task `Dgwx0JmMEHTT`. The saved workflow was -then opened from **My workflows** in a fresh browser, expanded, requalified, -and run as task `WQgtLGPh8TXP`. After removing only volatile `checkedAt` and -`sid` fields, both authorized API graphs have SHA-256 -`04e2401ae3d947d469bd0aa74d98244fc12cab6ec237ebebd07b667a749e13d8`; -both runs produced the same 126,790-byte WebP with SHA-256 -`578d5dfbc7c11ee32054e2c72d093067273bb9f6c889195a4379c45c50f00810`. -See -`test-review/frontend-pipeline-e2e-2026-08-24/sdxl-inpainting-cluster-qualification/receipt.json`. - -The independently admitted SDXL `controlnet_text2image` workflow adds a pinned -standalone component without borrowing Qwen's different routed-ControlNet -shape. Its eight-node graph loads the ordinary Canny ControlNet at revision -`eb115a19a10d14909256db740ed109532ab1483c` with the sealed `fp16` weight -variant, passes the prepared control image directly into the upstream SDXL -ControlNet block, and passes only its `controlnet_bundle` to Denoise. There is -no invented base-VAE-to-ControlNet or ControlNet route-state edge. The visible -frontend saved workflow `DYyGqzuYGEmNLNTPGwoRd`, reopened it in a fresh -browser, expanded 11 upstream block containers, and reran it. Both runs used -execution fingerprint `hfcluster_dbf1ea60`; after removing only volatile -`checkedAt` and `sid` fields the API graphs have SHA-256 -`bd27db2b205d8d7b6bc18917f2cd2bc2570242aed3d79aae2bd72d83f4dfc88b`, -and both runs produced the same 84,534-byte WebP with SHA-256 -`6ce69421c7d167cd1acd2810434148403fd40dfdf7e028ce63d425d3088746b2`. -See -`test-review/frontend-pipeline-e2e-2026-08-24/sdxl-controlnet-cluster-qualification/receipt.json`. - -The independently admitted SDXL `controlnet_image2image` workflow composes -that ordinary ControlNet route with the reviewed source-image VAE encoding -route. Its ten-node API graph sends source-image latents and route state to -Denoise and sends only the separately loaded ControlNet's `controlnet_bundle` -to Denoise. The visible frontend imported distinct source and control images, -saved workflow `ZC3xJjRzAbS0DXi12AuTB`, reopened it in a fresh browser, -expanded 12 upstream block containers, and reran it. Both runs used execution -fingerprint `hfcluster_d4e29c61` and run-input hash `run_00529272`; after -removing only volatile `checkedAt` and `sid` fields the API graphs have SHA-256 -`0408406437a87c41a84170c2837b1f12f80d19fb8f6bee6bc3ce43cc87696b31`, -and both runs produced the same 85,516-byte WebP with SHA-256 -`1dc341e295498735693d8c5b4991489bea7da7882610b0407ea251404e439a51`. -See -`test-review/frontend-pipeline-e2e-2026-08-24/sdxl-controlnet-image2image-cluster-qualification/receipt.json`. - -The independently admitted SDXL `controlnet_inpainting` workflow completes the -ordinary ControlNet trio. Its eleven-node API graph joins the reviewed source -image, mask, masked-image latents, image latents, and route state with the -standalone ControlNet bundle only at Denoise. The visible frontend imported all -three distinct media inputs, saved workflow `W7DH2wiuUEdtoG82i2JCI`, reopened -it in a fresh browser, expanded 12 upstream block containers, and reran it. -Both runs used execution fingerprint `hfcluster_ab00644e` and run-input hash -`run_bfa5ef83`; their normalized API graphs have SHA-256 -`5a23f0d57fc6a486c810d5e92d02ab884ae72f2d8f4bac7d7ca67371f9a4d82b`, -and both produced the same 217,654-byte WebP with SHA-256 -`69b2db4e4546120fd407c27f5d0549d6d6520e06c007bf1b66daf6b7b8e9b77e`. -See -`test-review/frontend-pipeline-e2e-2026-08-24/sdxl-controlnet-inpainting-cluster-qualification/receipt.json`. - -A visible browser save/refresh proof now covers the remaining lifecycle -boundary. Before refresh the root owned one volatile authority and seven -derived execution nodes. After refresh, its exact prompt, negative prompt, -source image, steps, seed, and guidance remained, while authority and derived -execution-node counts were both zero. Re-preparation restored one authority and -the same seven-node graph; the exported API nodes and paths were exactly equal -to the pre-refresh export. The receipt and screenshots are under -`test-review/frontend-pipeline-e2e-2026-08-24/qwen-cluster-qualification/`. -The store contract also exercises two simultaneous Cluster roots: editing the -first changes only its root values and clears only its authority, while the -second root's prompt and authority remain byte-for-byte intact. - -#### R3 — Auto and resource integration - -- [x] Request Auto plans from the canonical Cluster execution identity and bind - only backend-declared execution parameter sources. -- [x] Preserve Auto across collapse/expand and ordinary parameter edits; replan - after artifact, workflow, or structural changes. -- [x] Keep Wan FLF Expert-only until it has a reviewed Auto resource pair. -- [x] Keep Run disabled with an actionable Setup/install state when the exact - optional runtime, model artifact, or auxiliary dependency is unavailable. - -Wan FLF now follows the explicit Expert branch without creating a fake Auto -candidate. The backend issues a volatile qualification receipt only after the -exact cached commit and shard integrity, Modular execution profile, active -optional runtime, device/offload pairing, and dependencies pass. The client -strictly parses and cross-checks that receipt against the admission before it -enables the nine-node graph. A visible frontend preparation proof and screenshot -are stored in -`test-review/frontend-pipeline-e2e-2026-08-24/wan-cluster-qualification/`. -The same visible frontend then submitted task `6gfz2uw4hqt-` through the -collapsed Cluster route. All nine derived nodes completed in 73.6 seconds and -produced a hash-sealed five-frame H.264 MP4 plus first/middle/last review -frames in that folder. The immutable catalog remains non-executable and -non-Auto-eligible pending manual publication review; the runtime proof does not -weaken that boundary. - -SDXL Modular follows the same fail-closed publication boundary. Qualification -runs may use an exact backend-declared Auto pair while immutable public -`executable`, `autoEligible`, and `liveProof` flags remain false. Its first live -run exposed an artifact/loader mismatch: Studio had installed the reviewed -`*.fp16.safetensors` selection, while `ModelsLoader` requested default weight -filenames. The loader now applies a repository-scoped reviewed `fp16` variant -before component-reuse matching and loading. This deliberately does not infer -variants from dtype for Qwen, Wan, or arbitrary repositories. A second live -diagnostic found and fixed the missing loader-VAE-to-denoise edge required by -the upstream SDXL denoise block. The subsequent inpainting promotion adds the -upstream source image, mask image, encoded mask, masked-image latents, image -latents, and route state without treating any of them as an implicit browser -side channel. The ordinary ControlNet promotions additionally seal their exact -standalone component revision and `fp16` filename variant while retaining the -upstream SDXL component/state boundaries. The image-to-image variant joins -source-image latents and route state at Denoise without inventing -VAE-to-ControlNet or ControlNet route-state edges. The inpainting variant adds -the exact mask and masked-image-latent edges at Denoise. All six boundaries -have regression coverage. - -#### R4 — Remaining pinned Modular Diffusers definitions - -- [x] Admit every definition that already has a reviewed generic Modular - action/state contract: 40 workflow/mode joins across all 39 definitions. -- [x] Promote only the LTX-2 `text2video` and `image2video` workflows through - their exact standard condition-pipeline overlap; keep `condition` and - `in_context` contract-only. -- [x] Promote the remaining statically closed workflows by reusable task and - block-role contracts, retaining model/workflow differences as backend data. -- [x] Add exact structural container-state adapters for the 12 custom-container - workflows without making an execution claim. -- [ ] Add exact runtime action/profile/admission routes for those 12 workflows - before considering them executable. -- [ ] Keep contract-only and equivalent-standard definitions at their truthful - readiness levels until a reviewed Modular execution route exists. - -All 18 SDXL R4 definitions are now graph-qualified and insertable through -reviewed task/state-flow adapters. Visible-frontend live evidence exists for the -base and ordinary ControlNet trios, ControlNet Union text-to-image, standalone -IP-Adapter text-to-image, and the maximal IP-Adapter + ControlNet Union -inpainting composition. The other admitted SDXL definitions still require -definition-specific live execution evidence. Every immutable publication claim -remains non-public-executable and non-Auto-eligible pending manual approval. - -All 38 ordinary contract-only workflows now have complete backend-declared -Block Role coverage: 209 deduplicated exact block definitions map to the shared -workflow, text/VAE/image encoding, prompt transform, duration, condition, -reference, pre-encode, denoise, decode, and post-decode roles. The client -attaches only these sealed role receipts and reports whether a structural plan -is fully role-adapted; it contains no model-family role inference. These -workflows intentionally remain `contract_only` and non-executable until their -artifact, component, Studio execution, resource, and live-evidence gates are -individually admitted. - -The same schema completes deduplicated role adapters for the custom-container -and equivalent-standard workflows, bringing the non-reviewed workflow role -catalog to 321. Nine exact container-state adapters make the 12 -custom workflows statically closed while preserving their distinct loop -semantics. This completes structural adaptation only; runtime execution -adapters and admissions remain pending. - -#### R5 — Transformers composites - -- [x] Define provider-neutral composite receipts for the existing generic text - generation, image-to-text, any-to-any, and speech nodes. -- [x] Publish graph-qualified loader/action/preview Cluster Nodes only for exact - target optional-runtime profiles and immutable artifact revisions. -- [x] Preserve explicit installation consent and never install Transformers as - a side effect of catalog discovery, insertion, or Auto planning. - -The catalog now contains eight independently immutable Transformers -definitions: SmolLM2 -135M text generation; SmolVLM 256M image-to-text; Janus Pro 1B text generation, -image-to-text, and text-to-image; and Whisper Tiny speech transcription and -translation; plus Wav2Vec2 Base 960h CTC speech transcription. They reuse five -task contracts and 22 deduplicated composite -blocks. This is coverage of the generic task graphs MoDiff currently reviews, -not a claim that all Transformers models or pipeline tasks are supported. - -For the current delivery phase, Transformers work is narrowed to automatic -speech recognition and begins only after the Diffusers completion gates. The -app-managed Transformers runtime is pinned to commit -`96fe6dce36cc929a5ffd3e34296554c4cb6b669e`. Its official -`AutomaticSpeechRecognitionPipeline` has five distinct execution branches, so -the implementation order is architectural rather than a separate hard-coded -node for every checkpoint: - -1. [x] the Whisper sequence-to-sequence branch through the official - `AutoModelForSpeechSeq2Seq`/`AutomaticSpeechRecognitionPipeline` contract, - with transcription first and speech-to-English translation second; -2. [x] the plain CTC branch through the official `AutoModelForCTC` - contract, beginning with an immutable reviewed Wav2Vec2 artifact and then - admitting compatible HuBERT, WavLM, MMS/XLS-R, or other CTC checkpoints only - when their exact config architecture, processor, artifact revision, runtime, - and frontend evidence pass the same gates; -3. [ ] the generic speech sequence-to-sequence branch for models in - `MODEL_FOR_SPEECH_SEQ_2_SEQ_MAPPING_NAMES`, with no Whisper-only language or - timestamp controls; -4. [ ] the transducer/TDT branch shared by the pinned Parakeet TDT/RNNT and - Nemotron ASR model types, using their `generate()` and non-grouping decode - contract; and -5. [ ] CTC with a language-model decoder as a separate optional dependency and - artifact contract, because it requires `pyctcdecode`, decoder assets, and - permits word timestamps only. - -The branch list above is derived from the -[exact pinned Transformers ASR source](https://github.com/huggingface/transformers/blob/96fe6dce36cc929a5ffd3e34296554c4cb6b669e/src/transformers/pipelines/automatic_speech_recognition.py), -not from a manually maintained list of every audio model name. Models such as -Speech2Text, Moonshine, Parakeet, Nemotron ASR, HuBERT, WavLM, MMS, or XLS-R are -admitted through the matching reviewed branch only when their immutable model -and processor contracts pass qualification. - -The shared product surface is **Transformers Speech-to-Text Cluster Nodes**; -family-specific loader behavior stays inside the expanded Cluster. Translation -is shown only for a model family that explicitly supports it, so a CTC model -cannot accidentally receive Whisper-only `task` or `language` generation -arguments. SmolLM2, SmolVLM, Janus, and all non-ASR Transformers families are -deferred. Every admitted speech Cluster must receive the same -collapsed/expanded, save/refresh, runtime/artifact admission, Auto/Expert, -visible-frontend output, and preserved-review-asset proof before any public -flag changes. - -The SmolLM2 definition has visible-frontend proof for insertion, typed numeric -parameter editing, exact runtime/artifact qualification, collapsed execution, -save plus fresh-page restoration, expanded execution, API-graph equivalence, -and deterministic output equivalence. Public `executable`, `autoEligible`, and -`liveProof` flags remain false until manual review. Both Whisper task variants -and the Wav2Vec2 CTC definition now also have visible-frontend output evidence. -The four non-speech definitions are deferred from the current phase. - -#### Rollout gates - -For every promoted definition, record these proof levels separately: - -1. no-download upstream/source and schema parity; -2. graph materialization and dynamic-field reconciliation; -3. collapsed/expanded and save/refresh export equivalence; -4. optional-runtime, artifact, dependency, and resource admission; -5. visible-frontend live output on each claimed hardware recipe; -6. manual review approval before changing the public readiness claim. - -### 2026-08-24 runtime recovery and app execution proof - -With explicit user consent, the reviewed Transformers/PEFT optional environment -was repaired and activated using the frontend Setup flow. Live testing exposed -and fixed two generic client defects: Setup hid rollback-to-base when the broken -active optional environment had no previous optional environment, and graph -export generated random values outside declared Diffusers seed bounds. Focused -unit, typecheck, and browser regression tests pass, and a post-restart MoDiff -DDPM graph completed on ROCm with its output and run receipt preserved under -`test-review/hugging-face-cluster-initial-2026-08-24/runtime-repair/`. -The development proxy now exposes the read-only Hugging Face library endpoint, -and a fresh no-bridge browser smoke renders all 94 definitions. - -A later backend restart correctly returned the active overlay to a state that -required verification. The visible Setup flow repaired and staged a replacement, -rolled back to the base process, and activated the repaired environment. That -flow exposed a client ambiguity when the catalog contained both an explicitly -named previous environment and a new same-spec staged environment. The client -now reserves the previous environment for the distinct **Rollback** action. A -catalog-only `staged_unchecked` environment is presented as **Repair**, not as -safely activatable; **Activate** is offered only for the environment ID returned -by the completed validation job in the current flow. Request-contract -regressions cover both cases, and activation still uses the backend's full -integrity verification rather than trusting the client-side selection alone. - -This proves the generic app Diffusers execution path can run with the activated -optional overlay. It does not satisfy P5's per-Cluster live-parity requirements -and does not change an admission's `executable` flag. Insertability is now a -separate graph-qualified publication state. - -### 2026-08-25 visible-frontend pipeline execution evidence - -The current reviewed Studio execution graphs were exercised through visible -frontend controls, with receipts and generated media preserved under -`test-review/frontend-pipeline-e2e-2026-08-24/`. Successful runs cover Qwen -Image Edit, Qwen Image Edit Plus (single- and multi-reference), Qwen Image -Layered (four outputs), Qwen Image 2512 Control, Wan first/last-frame video, -and SDXL Modular text-to-image, image-to-image, inpainting, and ordinary -ControlNet Cluster routes. - -Wan FLF was installed through Studio's visible missing-model action at the -pinned Hub revision `17c30769b1e0b5dcaa1799b117bf20a9c31f59d7`. Its final -visible-frontend smoke run selected Expert mode, two ordered images, two -denoising steps, five frames, and 512 x 512 geometry, then submitted task -`fEJrHEd2APrQ`. The exact nine-node Modular graph finalized with 19 managed -edges. Model loading, first/last-frame image embedding and VAE conditioning, -two denoising steps, route-authenticated decode, and video export completed in -57.6 seconds. The preserved output is a five-frame H.264 MP4 at 512 x 512 and -15 fps; extracted first, middle, and last review frames confirm that the first -and last source images reached their distinct upstream `image` and -`last_image` inputs. - -Live testing fixed four generic frontend contracts rather than weakening -backend validation: Auto-to-Expert changes now rebuild the managed graph when -its fingerprint changes; programmatic dynamic signal actions are not executed -again by the rendered handle effect; number fields expose their generated -label target ID; and FLF's ordered two-image Studio value is split into one -first-frame loader and one last-frame loader while readiness requires both. -The implementation follows the pinned upstream Wan auto-block contract, where -the FLF workflow is selected only when both `image` and `last_image` are -provided. - -Regression evidence after the live run: 58/58 graph-visual tests, 41/41 run -coordinator/issue-store tests, 119 backend Hugging Face download and Modular -route-state tests (one expected skip), TypeScript typecheck, production build, -and diff checks pass. The complete media hashes, screenshots, receipts, and -diagnostic attempts are indexed by the review-folder README. - -This is live parity evidence for the existing reviewed Studio execution paths. -It is not collapsed-versus-expanded Cluster Node equivalence and therefore -does not by itself complete P3's remaining equivalence proof or promote any P5 -catalog definition to executable. - -### 2026-08-25 first executable Cluster qualification - -The graph-qualified **Qwen Image Edit — Edit Image** definition now has a -fail-closed volatile execution authority. Preparing it runs the backend-declared -dynamic field actions and verifies the bounded Studio node receipt, live runtime -fingerprint, installed artifact revision, active optional runtime, exact model -dependencies, and selected backend Auto candidate before enabling its derived -children. Changing the Cluster input, execution parameters, mode, or children -revokes that authority and removes the derived executable graph. - -The collapsed Cluster was inserted, prepared, and submitted through visible -frontend controls as task `NcYIqr7a53cu`. It completed on ROCm in 112.8 seconds -using `Qwen/Qwen-Image-Edit` at revision -`ac7f9318f633fc4b5778c59367c8128225f1e3de`, bfloat16, model CPU offload, -two denoising steps, seed 42, and guidance 4. The 1024 x 1024 output has SHA-256 -`c75676c1d09bdf3d19bf8d12df082cdb864609ed11c06b703b07d9d7f37148c2`. -The complete receipt, source, output, and before/after screenshots are under -`test-review/frontend-pipeline-e2e-2026-08-24/qwen-cluster-qualification/`. - -The run exposed a provenance-only client defect: the submitted graph carried -the correct Cluster Auto authority, but the output snapshot cloned the global -canvas form, which custom graphs label Expert. The coordinator now computes the -run-input hash from, and captures, the authority's effective Auto form. A -dedicated regression test covers this behavior. The original execution and -artifact remain valid; its receipt records the metadata discrepancy explicitly. - -The subsequent refresh proof exposed and fixed two restored-root lifecycle -defects. Persisted checkpoints now omit all definition-derived block/execution -children and preview fields, and the root fetches its exact pinned definition -while disabling Prepare until it arrives. The backend generic prompt encoder -also now republishes its dynamic schema when a deterministic node ID requests -the same model type after refresh, matching the idempotent behavior already -implemented by the other generic Modular actions. - -This is an internal qualification execution, not an immutable public -`executable` or `autoEligible` publication claim. Manual visual approval and the -remaining definition-by-definition promotions are still open. - -### 2026-08-25 SDXL IP-Adapter and combined-workflow qualification - -The SDXL IP-Adapter artifact contract now selects the three exact files at -`h94/IP-Adapter@018e402774aeeddd60609b4ecdb7e298259dc729`: - -- `sdxl_models/ip-adapter_sdxl.safetensors` — 702,585,376 bytes, SHA-256 - `ba1002529e783604c5f326d49f0122025392d1d20ac8d573b3eeb3e6dea4ebb6`; -- `sdxl_models/image_encoder/config.json` — 2,013 bytes, SHA-256 - `1d53c2b4b74c5f85171d313adda3e3b8771ff5c698ee66a29710d0ac822298e4`; -- `sdxl_models/image_encoder/model.safetensors` — 3,689,912,664 bytes, - SHA-256 - `657723e09f46a7c3957df651601029f66b1748afb12b419816330f16ed45d64d`. - -The frontend Model Manager installed that bounded selection and changed its -exact requirement to Ready. The visible Setup flow then rolled a stale active -optional runtime back to base, repaired it from reviewed locked artifacts, -activated environment `runtime-1787677346-2d28819c`, and verified the restarted -process as active. Browsing, insertion, and Auto planning still perform neither -operation implicitly. - -Visible-frontend execution exposed two normal upstream transformations that the -initial process-local receipt treated as tampering. Diffusers/Accelerate moves -the same adapter `Parameter` objects between CPU and the execution device under -model CPU offload, so device placement is no longer part of the immutable -parameter seal; identity, Torch mutation version, shape, and dtype remain -sealed. The pinned SDXL before-denoise block also assigns `torch.cat` results -back into both IP-Adapter embedding lists even at batch size one. Denoise now -passes shallow copies of only those list containers upstream, preserving the -backend-issued bundle and tensor provenance without weakening its validation. - -The collapsed standalone `ip_adapter_text2image` Cluster completed as task -`3C9uMRbryjfR` in 35.3 seconds and produced one 512 x 512 WebP (119,354 bytes, -SHA-256 -`88ff5d2521b0c8fc3b1b942bfd3df16b10de738832480473f1e61b1ce3fa310e`). -The maximal `ip_adapter_controlnet_union_inpainting` Cluster imported its source, -mask, control, and IP reference through the Assets UI, prepared in Auto mode, -expanded 13 upstream block containers, executed its 14-node API graph as task -`UGuDu4XZrwbe`, and saved workflow `Jl5be9qjnslt12x_enZMa`. A fresh browser -reopened that workflow, verified every persisted input, collapsed it, prepared -it again, and completed task `cuty3PzM8b4K`. Both runs used execution fingerprint -`hfcluster_585895fb`; their executable nodes and paths have the same SHA-256 -`3f05ff24ac4d13bdfbbfcc3b2ea14256ff762bf9b8599f8cc1c3d5a55490ec1d`, -and both produced the same 119,216-byte WebP with SHA-256 -`5e70bbf7f85bd171e073b48e9f1d2772da3b342ed22cee698d1f41069ef930da`. -Auto evidence correctly advanced from static “This should work” to local -“Ran here” after the first success; that observational metadata is not part of -the executable-graph equivalence projection. - -Evidence is indexed under -`test-review/frontend-pipeline-e2e-2026-08-25/`. Consolidated verification after -these runs passed 371 backend tests with 1,537 parameterized subtests (19 -expected skips and the existing Diffusers `torch_dtype` deprecation warning), -plus frontend typecheck, style audit, all unit suites, and the production build. -The remaining promotion work is manual approval of these outputs, individual -live proof for admitted workflows not yet executed, additional pinned Modular -workflow adapters, and definition-specific live proof for the remaining six -initial Transformers Cluster Nodes. - -### 2026-08-25 Transformers Cluster qualification - -The immutable catalog now merges 94 pinned Modular Diffusers workflow -definitions with seven reviewed Transformers composites. Its provider-neutral -parser, insertion, persistence, graph ownership, runtime authority, and API -export paths accept both providers while enforcing provider-specific IDs, -surfaces, task contracts, artifacts, and optional runtimes. Transformers -discovery performs no import, download, installation, activation, or model -load. - -The visible Setup flow verified that Linux x86-64 requires the exact -`huggingface-transformers-main-96fe6dce-peft-0.20.0` target profile with digest -`sha256:189e8c337059de39d06025b1f29d641b45d5c7b78cdb54c0c9dd909d86d5e487`; -the earlier cross-platform 5.14.1 candidate was rolled back rather than being -silently accepted. The exact SmolLM2 artifact was already complete at commit -`12fd25f77366fa6b3b4b768ec3050bf629380bac`. - -Through visible frontend controls, a user inserted **SmolLM2 135M Instruct — -Text Generation**, edited `max new tokens` to 32, prepared the exact Expert -CUDA recipe, and ran task `AnTO3UhekQjK`. Numeric root controls are normalized -to their reviewed JSON integer/float types before persistence; a regression -test rejects invalid numeric overrides. The workflow was saved and opened in a -fresh page, where the prompt and token override remained while volatile derived -nodes and authority were correctly absent. Re-preparation restored the same -execution fingerprint, expansion exposed the three exact generic task nodes, -and task `QwwczUYWr_QA` completed from the expanded view. - -Both views exported byte-identical API `nodes` and `paths` with SHA-256 -`0fc7f74f6c97e0b5baaf07d00f62a5b0244c665657a0f76b9d9503bee6a5e9c2`, -used execution fingerprint `hfcluster_b7d88a02`, and generated identical text -with SHA-256 -`9fba278361b8d347e1d96f767a0b3a9fa3c05018bb4664c616d72ea89e29f41c`. -The generated result independently records the exact model revision, -`cuda:0`, `float32`, 39 input tokens, and 32 generated tokens. Receipts, -focused state captures, the generated text/result, repeatable browser harness, -and screenshots are under -`test-review/frontend-pipeline-e2e-2026-08-25/transformers-cluster-smollm2-text-generation-v1/`. -This remains internal qualification evidence and does not change a public -publication flag. - -### 2026-08-26 Diffusers-first and speech-to-text qualification - -The scoped catalog now publishes 94 exact Diffusers Cluster definitions and -eight Transformers Cluster definitions backed by 505 deduplicated block -contracts. All Diffusers definitions are structurally insertable and -expandable. Structural support is deliberately distinct from executable -support: contract-only, equivalent-standard, and custom-container workflows -retain their truthful readiness until their exact runtime adapter, artifact, -resource recipe, and live evidence are admitted. - -Five additional Diffusers qualification runs were submitted through visible -frontend controls and preserved under -`data/qualification/local-review/hugging-face-clusters/`: - -| Cluster | Task | Preserved output SHA-256 | -| --------------------- | -------------- | ------------------------------------------------------------------ | -| LTX text-to-video | `EeiDMyJJS9KS` | `063e3d69ce32a65fc404715b159e1b9b4d6c035db67d750d1bd77cf61da742dd` | -| LTX image-to-video | `Arfu4PbTvHHf` | `ec30a24ad211245bebbd8c35ab7bb98626afbf75ae856396e256bba49e39c08c` | -| Wan2.2 image-to-video | `CU-hnQlxdCVQ` | `eea11d7f0f21f370a9f50c3500cacd709f00b5fbade520a341c92c287131345d` | -| Qwen Image Edit | `wgs2hPSzAvrC` | `0e1a8007ca9ae7ac60cda9da3a659c0f78ce68bb2dcd7d38927a68039f4a9a93` | -| ERNIE Image | `yFCZBqKXIqhy` | `8b7811e87bc36e749dd5fc4d9a9212e43b07966e1cb8490a5aa6184571310d55` | - -These outputs are technical qualification evidence. In particular, the short, -low-step video runs prove media routing and temporal output but are not -showcase-quality generation examples. They are not part of a new visual-review -request: the user has already reviewed the earlier review queue and its -recorded decisions remain authoritative. Showcase prompts, durations, frame -counts, and quality parameters will be tuned only after the implementation and -qualification gates in this plan are complete. - -The exact Wan2.2 text-to-video snapshot could not be installed safely: its 49 -files total 126,200,628,126 bytes (117.53 GiB), while the host had 67 GiB free -at the check. No unrelated cache was deleted. The bounded blocker receipt is -`diffusers-wan22-text-to-video-blocker.json`; the consolidated successful and -blocked result index is -`diffusers-equivalent-video-clusters-live-results.json`. - -Official LTX-2 documentation and the pinned `LTX2AutoBlocks` source establish -two exact overlaps: modular `text2video` and `image2video` can use the reviewed -standard `LTX2ConditionPipeline` graph and retain synchronized video/audio -output. Those two definitions are now independently graph-qualified. The -`condition` workflow remains contract-only because it accepts arbitrary lists -of image/video conditions at latent indices, and `in_context` remains -contract-only because it requires separate IC-LoRA reference-condition -semantics. The app therefore supports partial workflow promotion without -relabeling an entire model family. - -The LTX-2 frontend Install contract is sealed to 45 exact standard-pipeline -files at revision `47da56e2ad66ce4125a9922b4a8826bf407f9d0a`; it excludes the -unrelated root single-file variants and duplicate Diffusers-format text-encoder -weights. The bounded selection is still 92,074,139,514 bytes (85.75 GiB), so it -cannot fit in the current 65 GiB free space and was not partially downloaded. -The receipt is -`diffusers-ltx2-equivalent-cluster-storage-blocker.json`. - -Transformers work in this phase is restricted to speech-to-text. The visible -frontend already completed Whisper Tiny transcription and speech-to-English -translation. It now also installed and activated the exact reviewed optional -runtime, installed `facebook/wav2vec2-base-960h` at immutable revision -`22aad52d435eb6dbaf354bdad9b0da84ce7d6156` with the bounded file allowlist, -inserted the CTC Cluster, edited its audio and timestamp parameters, saved the -workflow, refreshed the page, and proved those effective values and overrides -were unchanged. The collapsed and expanded four-node API graph exports were -identical. Visible task `bstoqkySg96n` transcribed the 5.12-second review -fixture as “AS FOR ETCHINGS THEY ARE OF TWO KINDS BRITISH AND FOREIGN” and -returned word timestamps. The receipt is -`transformers-asr/wav2vec2-ctc-cluster-frontend-result.json`. - -Live qualification exposed and fixed two integration defects without relaxing -the contracts: a stale active optional environment may now be replaced only by -a freshly validated environment owned by the same trust class, and the client -now has an exact static CTC resource/model profile instead of dereferencing an -unknown profile during planning. Studio's explicit Install action also sends -the backend-declared immutable revision and bounded download selection. - -On the same date, the visible Model Manager used a new explicit local-eviction -contract to remove six exact completed-proof or deferred model revisions while -retaining every saved workflow and canonical definition. The confirmation -warns that retained workflows must redownload the exact revision before their -next run; active graphs, queued work, downloads, ambiguous revisions, and stale -plan hashes still block deletion. The operation reclaimed 210,565,616,193 -bytes and increased free space from about 65 GiB to 260.7 GiB. Its receipt and -screenshots are under `storage-turnover/`. This resolves the LTX-2 and Wan2.2 -text-to-video capacity blockers; their exact frontend downloads and live runs -are now pending rather than storage-blocked. - -The first unblocked install exposed a generic Model Manager gap: exact -Expert-only profiles were visible but could only show an inert **Expert** badge -when the Auto planner correctly returned `manual_only`. Model Manager now -offers an explicit Install/Repair action in Expert mode only when the -authoritative backend capability supplies one repository, one immutable -40-character revision, a bounded validated file selection, and an active exact -optional runtime. The client cannot invent or widen any of those fields. A -mocked visible-browser test proves the `/hf_download` request carries the exact -reviewed revision and files. LTX-2 installation is now running through that -visible frontend path; after it finishes the gated Cluster test will save, -refresh, compare effective parameters, compare collapsed and expanded API -graphs, execute the graph, and preserve the generated media as qualification- -only evidence. Wan2.2 text-to-video follows after LTX-2 is explicitly evicted -through the frontend to retain the configured free-space safety margin. - -The shared Wan image-to-video class also exposed a second generic schema gap: -one upstream pipeline class can own multiple workflows whose immutable model -artifacts are different. Capabilities now publish validated per-workflow -`artifactSelections` instead of collapsing those repositories into one -ambiguous revision list. The first admission binds `single_image_to_video` to -`Wan-AI/Wan2.1-I2V-14B-480P-Diffusers@b184e23a8a16b20f108f727c902e769e873ffc73` -with 36 selected files (90,097,576,675 bytes), and `image_to_video` to -`Wan-AI/Wan2.1-FLF2V-14B-720P-diffusers@17c30769b1e0b5dcaa1799b117bf20a9c31f59d7` -with 41 selected files (90,104,408,960 bytes). Studio readiness and Model -Manager resolve the exact selection by workflow mode, display separate Expert -install rows, and still reject duplicate mode ownership, mutable revisions, -unsafe paths, or incomplete selections. The backend app-download planner uses -the same selection records. A visible mocked-browser proof now renders both -rows with their distinct artifact labels and verifies that each Install action -sends only its matching repository, immutable revision, and bounded file list. -The same proof exposed and fixed a generic merge defect: an optional field -omitted by an otherwise authoritative backend capability can no longer erase a -known static profile value by replacing it with `undefined`. - -The immutable Wan2.2 text-to-video repository contains 49 files totaling -126,200,628,126 bytes. Its executable install contract intentionally excludes -the six `assets/` illustrations, leaving the 43 runtime files and -126,199,294,813 bytes asserted by the backend planner and downloader. The -upcoming visible install must use that bounded selection rather than the full -repository tree. - -MiniMax Music 3 is the first reviewed current-pin workflow whose faithful -execution graph is the package-owned Modular workflow itself rather than an -equivalent standard pipeline or MoDiff's older encode/denoise/decode adapters. -The exact repository commit is -`MiniMaxAI/MiniMax-Music3@fbdf52fbaaca799592917417eb05f1899f1255ec`. -Its full repository is 57,353,379,600 bytes; the reviewed runtime closure is 23 -files and 28,517,608,999 bytes and excludes the legacy `qwen_7B/`, -`flowmatching_vae.pth`, `dav.pth`, examples, and publisher assets. The artifact -receipt is `data/minimax-music3-artifact-review.json`. - -The repository uses the MiniMax-Music3 Community License, not Apache-2.0. It -requires revision-bound review before installation or execution, contains -commercial-product attribution and hosted-generation safeguard terms, and -requires separate authorization above its stated annual-revenue threshold. -The UI must display and record that exact immutable license before this model -can be exposed for download or Run. - -The reusable official-workflow executor foundation is implemented as three -Block Nodes matching the pinned upstream top-level sequence: -`semantic_generator -> denoise -> decode`. Each node selects the reviewed -package block, calls `init_pipeline()`, and uses the normal `ModularPipeline` -call interface. The implementation does not call a block's internal -components/state protocol and does not reproduce upstream autoregressive, -flow-matching, scheduler, or vocoder loops. State passed between visual nodes -is process-local, sealed to the exact loader execution and workflow, immutable, -and deliberately nonserializable; saved graphs persist parameters and edges, -then recreate runtime state by rerunning from the first block. - -MiniMax remains fail-closed for real execution while the following gates are -tracked independently: - -- [x] Pin and hash the exact artifact, component descriptors, official block - sources, workflow order, outputs, and license. -- [x] Add an all-component Models Loader bundle and official package-block - executors with isolated contract tests. -- [x] Add the exact `text_to_audio` Studio execution specification and Cluster - adapter: Models Loader -> Semantic Generation -> Denoise -> Decode Audio -> - Export Audio. -- [x] Add revision-bound license acknowledgement to the frontend install, - qualification, and Run flows. -- [x] Publish the exact 23-file bounded Model Manager selection only after the - preceding policy gate is enforceable. -- [x] Add the reviewed optional-runtime/resource profile. Its activation must - still be requalified after the profile digest change. -- [x] Qualify the - collapsed/expanded, save/refresh, API-equivalence, and real CUDA frontend - flow. Auto remains disabled until that evidence is manually approved. - -The frontend parser and materializer now accept the exact `workflow` adapter -identity, the official whole-workflow integration status, its sealed workflow -and block paths, and the 44.1 kHz numeric output binding. A focused client test -materializes the five-node execution graph, proves its three pipeline-component -fan-out edges and two sealed state edges, and compares canonical collapsed and -expanded snapshots after a JSON save/restore round trip. A visible mocked -Model Manager test proves no download request occurs before acknowledgement -and that confirmation sends only the exact revision and 23-file allowlist. - -### Demo acceptance matrix for the original one-node/customizable-node goal - -Catalog size is not the completion metric. The current immutable library has -94 Diffusers Cluster definitions. Forty-eight workflow admissions across 47 -Diffusers definitions have an exact graph-qualified execution contract; the -remaining 47 definitions are structurally insertable/expandable but catalog- -only. All new publication `liveProof` flags remain false pending explicit -approval. - -The minimum demo set is one image, one video, and one audio Cluster. Each demo -must perform the same visible user journey in one test and preserve its own -receipt and generated asset: - -1. Insert the first-party Cluster from **Diffusers Cluster Nodes**. -2. Run it collapsed as the single “magic” node experience. -3. Expand it, edit at least one parameter through an internal Modular block, - and prove the owning Cluster instance changes. -4. Save the workflow, refresh the browser, reopen it, and prove the edited - effective parameters are unchanged rather than reset to model defaults. -5. Prepare the exact runtime graph again (volatile weights/runtime state are - correctly not serialized), compare collapsed and expanded canonical/API - graphs, then generate through visible frontend controls. -6. Prove another Cluster instance and another workflow retain their own model, - prompt, and parameters. - -Current evidence against that combined standard: - -| Modality | Candidate | Real frontend output | Save/refresh parameter proof | One combined demo | -| -------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Image | Qwen Image text-to-image | Cluster task `R5flQQqzyeQB` and customized User Node task `376IQyhhHcq1` generated real 256x256 WebP assets through the visible frontend | Prompt, width, height, steps, seed, device, dtype, guidance, and offload values are byte-for-byte equal before and after browser refresh | Complete; collapsed/expanded, Cluster/customized User Node, and User-Node/save/refresh execution graphs are equal; an existing canvas node was adopted with a real pointer drag; both generated assets have SHA-256 `61cba26f...ccec2` | -| Video | LTX-2 text-to-video with audio | Task `gcO67kuvAufh` generated a real MP4 through the visible frontend from the exact 92.07 GB snapshot | Prompt, dimensions, steps, frame count/rate, seed, guidance, dtype, device, and offload values are equal before and after browser refresh | Complete; collapsed/expanded graphs are equal and output hash is `8d6c532f...ea965` | -| Audio | MiniMax Music 3 | Task `KqYefJpE0m8c` generated a real 2-second 44.1 kHz stereo WAV through the visible frontend from the exact 28.52 GB snapshot | Prompt, multiline lyrics, duration, steps, seed, device, dtype, and offload values are byte-for-byte equal before and after browser refresh | Complete; collapsed/expanded graphs are equal and output hash is `a296989b...41f1` | - -The existing generated image/video assets are technical qualification evidence, -not showcase media. Showcase prompt and quality tuning remains after these -implementation proofs. - -Expanding a first-party Cluster exposes its exact upstream Modular hierarchy -and editable shared parameters. The legacy implementation then reconstructed a -topology-changing instance as a different User Node. That reconstruction is -superseded: it changed controls and public ports, could discard external edges, -and conflated workflow customization with reusable-definition creation. The -following bullets describe historical evidence, not the accepted product flow: - -- **Use Cluster** keeps the reviewed first-party Cluster immutable and gives the - one-node experience. -- **Customize as User Node** materialized the internal execution graph through - the generic User Node derivation path, removed first-party execution authority, - and replaced the wrapper. Automatic conversion on a child drag is now a known - defect rather than intended behavior. -- Cluster Nodes and User Nodes cannot be nested. The converted instance receives - a workflow-context name, and its Save action offers **Update existing User - Node**, **Save as new User Node**, or **Keep only in this workflow**. -- Saving/refreshing either representation persists only graph structure, - parameters, immutable artifact identities, and presentation state; model - objects and intermediate tensors are recreated at Run time. -- **Import from Hugging Face Hub** must accept only an immutable commit and a - declarative reviewed sidecar, show the discovered blocks before insertion, - and import the result as User Nodes unless it exactly matches a first-party - reviewed definition. Repository Python remains disabled without separate, - fresh operator authorization. - -The historical implementation checklist was: - -- [x] Add **Customize as User Node**, preserving the exact finalized graph, - parameters, artifact revision, and origin receipt while removing Cluster - ownership and execution-admission authority. -- [x] Allow a normal library node dropped inside an expanded User Node to be - adopted into its editable topology; prove the injected topology survives - collapse and JSON save/restore without mutating the source Cluster. -- [x] Adopt both new library nodes and existing ordinary canvas nodes into an - expanded User Node; automatically customize an expanded Cluster first; - reject User Node/Cluster nesting; and keep each canvas gesture one - undoable graph transaction. -- [x] Add the three reusable-definition choices: update the current reusable - User Node, save a new opaque User Node definition, or keep the embedded - change only in the current workflow. -- [x] Complete the visible live execution proof for the customized User Node. The - Qwen combined frontend test executes the first-party Cluster, customizes it, - saves and refreshes the User Node, and executes it through the ordinary - graph executor. The two generated outputs are byte-identical for the same - immutable model, parameters, seed, and internal graph. -- [x] Add a visible immutable Hub import entry that creates a pinned User Node, - hands the exact repository to Model Manager, keeps repository Python off, - and preserves repository/revision parameters across browser refresh. -- [x] Extend Hub import into a single install -> preview -> admission summary -> - save-to-library wizard and translate the official - `mellon_pipeline_config.json` sidecar without importing repository Python. -- [x] Complete one real official-Hub browser proof for the importer. -- [ ] Design a - separately authorized sandbox boundary for repositories that require - custom Python. -- [x] Complete the combined image, video, and audio live demo set. - -The active corrective implementation is tracked in the -[unified composite-node plan](unified-composite-node-implementation-plan-2026-09-01.md): - -As of 2026-09-01, the synchronized V2 schemas, backend User Node definition -store, registered-definition compiler, fail-closed capability resolver, pure -runtime/projector, workflow persistence canonicalizer, and shared V2 -`BlockNodeFrame` renderer branch are implemented. The -runtime's 17 focused tests fail closed for stale/spoofed projections, missing -bindings, invalid internal connection semantics, duplicate identities, and ID -collisions; the compiler removes legacy Cluster-prefixed field options after -translating them into the source-neutral V2 contract. Already-V2 API export, -run readiness, and run-target selection now use the same concrete internal -graph in collapsed and expanded views. Public connector discovery and the -shared connect/remove/status/signal paths derive their sockets from the -explicit V2 boundary, and admitted same-owner internal edge changes update the -instance effective graph copy-on-write. Public port IDs are unique across both -input and output directions so the shared root handle namespace is -unambiguous; both client and backend V2 validators enforce that invariant. - -These slices are intentionally not proof of broad production convergence: the -exact Qwen Image text-to-image catalog admission now uses the V2 route, while -the remaining registered catalog admissions and Auto still have legacy -Cluster routes. The -general structural capability remains disabled until replacement, move-out, -public-interface crossing, full reconnection, and their failure recovery all -durably update `effectiveGraph`; disconnected ordinary-node adoption and -eligible internal-node deletion are already copy-on-write, undoable, and -fail-closed at unsafe boundaries. Configure -interface remains disabled until `BlockInstanceV2` can persist a strictly -validated effective-interface snapshot. The three V2 save choices are now -wired: workflow-only performs no reusable-definition write, save-as-new -preserves the exact effective graph/interface/defaults/preview bindings and -parent provenance, and update-existing is limited to mutable user-owned -definitions. API rollback, semantic-race rejection, target-only rebasing, -open-workflow isolation, and independent click/drag reinsertion have focused -coverage; boundary-only valued inputs fail closed because `BlockPortV2` has no -reusable default. This is not yet a browser E2E claim. A migration-window -containment fix coalesces concurrent capability loads and preserves legacy -child layout across Save/refresh for two independent Qwen instances; that -regression is safety evidence, not acceptance of the legacy renderer. - -- [ ] Persist registered and user-owned composites as `BlockDefinitionV2` and - `BlockInstanceV2` through one canvas type and renderer. -- [x] Treat internal movement as presentation-only and retain internal layout - by stable semantic node ID. -- [ ] Apply topology edits copy-on-write to the same workflow instance without - replacing its wrapper or creating a reusable User Node. -- [ ] Preserve explicit controls, inputs, outputs, values, external edges, - dimensions, preview, and provenance across customization. -- [ ] Separate reviewed/Auto authority invalidation from source identity and - rendering. -- [ ] Migrate and requalify legacy Cluster and Cluster-derived User Node data. - -### 2026-08-26 legacy implementation and live-test evidence - -This section records the now-superseded implementation and remains only as -migration/test evidence. The first-party Cluster-to-User-Node customization was -implemented in the visible Cluster header and automatic structural-edit paths. -Customization materialized the exact backend-admitted execution skeleton, -finished backend-declared dynamic fields, stripped Cluster ownership and -authority, enabled ordinary parameters (including formerly sealed values), -saved the result in **User Nodes**, and replaced only that Cluster instance. -That behavior does not satisfy the V2 contract even though the source catalog -definition remained unchanged. Existing canvas nodes and new -library nodes can be adopted directly; nested User/Cluster Nodes are rejected. -Both forms use the existing MoDiff graph executor; User Nodes do not have a -second runtime. - -The visible Hub import wizard is implemented under **User Nodes**. It accepts -only `owner/repository` plus an exact 40-character commit, requires an explicit -review checkbox, waits for Model Manager to install that revision, inspects the -bounded sidecar, translates official Mellon block/port metadata, previews -admission and remote-code status, and saves the result as a User Node. A mocked -browser test proves the complete flow and browser-refresh persistence. A -Mellon-only repository becomes a disabled visual User Node because its custom -Python is never imported. A MoDiff sidecar can become executable only when it -resolves entirely to already reviewed, installed official classes/components. - -Focused client verification is green: typecheck, lint, and 34 focused graph and -Cluster/User-Node tests, including exact Cluster/customized API-export equality, editable unsealed -fields, normal-node adoption inside an expanded User Node, and persistence of -the modified topology after collapse and JSON serialization. The targeted -backend Cluster/Hub/Modular Diffusers suite passes 66 tests, 9 skips, and 78 -subtests under the managed ROCm environment. Two focused mocked browser tests -also pass. - -The exact LTX-2 snapshot finished installing through the visible Model Manager: -`Lightricks/LTX-2@47da56e2ad66ce4125a9922b4a8826bf407f9d0a`, -45 selected files and 92,074,139,514 bytes. The combined LTX frontend test now -performs internal-block prompt editing, save, browser refresh, parameter -comparison, collapsed/expanded API comparison, and real generation. During the -first live reruns, the current backend added four app-owned built-in capability -profiles whose `builtin://modiff/.../v1` artifact identity was rejected by the -client's Hugging Face-only repository validator. That production refresh bug -is fixed: versioned app-owned identities validate only for `artifactKind` = -`builtin`, malformed paths still fail closed, and the full live response now -parses 112 recognized profiles plus 223 task contracts. Workflow GET/PUT/DELETE -uses a bounded 120-second timeout so a cache-heavy startup cannot silently -turn an explicit Save into a 15-second false failure. - -The LTX live run also found two execution-boundary defects that are now fixed. -`LTX2ModularPipeline` is explicitly qualified against its reviewed equivalent -standard `LTX2ConditionPipeline` executor. That executor resolves the exact -40-character commit to the already installed, bounded local snapshot and gives -Diffusers that local directory; it no longer asks Hugging Face Hub to validate -unrelated files outside MoDiff's reviewed 45-file closure. Focused backend -regressions cover both the equivalent-executor receipt and the exact local -snapshot boundary. The complete browser run generated task `gcO67kuvAufh` and -saved its receipt and MP4 under the phase review directory. - -The combined Qwen Image test now covers insert -> expand the official -`text_encoder` block -> edit prompt -> save -> refresh -> compare effective -parameters -> compare collapsed/expanded API exports -> generate -> drag an -existing Data Viewer node into the expanded Cluster -> automatically customize -that one instance as a workflow-named User Node -> remove the injected proof -node -> expand/collapse -> save/refresh -> compare API export -> generate again -through the ordinary User Node executor. Its installed immutable artifact -is `Qwen/Qwen-Image-2512@25468b98e3276ca6700de15c6628e51b7de54a26`. -The latest complete browser run passes in 1.5 minutes with Cluster task -`R5flQQqzyeQB` and customized User Node task `376IQyhhHcq1`. It preserves both generated WebP files and the -full receipt under -`test-review/frontend-pipeline-e2e-2026-08-26/qwen-image-cluster/`. - -Run readiness now validates a collapsed User Node against the same expanded -internal execution graph used by graph export and Run. This fixes the final -refresh defect where the saved internal Preview existed but the collapsed -wrapper incorrectly reported that no output node was connected. Structure, -model, and device/offload checks all use that common execution view. The -58-test graph suite includes a collapsed internal-Preview regression. - -The combined MiniMax Music 3 test now covers the same flow through the official -`semantic_generator -> denoise -> decode` Modular blocks, including editing the -prompt and lyrics in `semantic_generator.tokenize`, exact 23-file frontend -installation, collapsed/expanded equality, refresh persistence, and real audio -output. The user's exact revision-bound license acknowledgement was applied, -and the latest visible-frontend run passed in 2.0 minutes with task -`KqYefJpE0m8c`. It persisted structure tags on their own lines in the new -multiline lyrics control and produced a 16-bit stereo 44.1 kHz WAV. Future runs -remain gated on the same explicit legal acknowledgement environment switch. - -The remote-code-disabled Hub wizard is complete. The real official-Hub proof -installs -`diffusers/gemini-prompt-expander-mellon@0562591cbb2144060ce641aaf101fea686a4cb71`, -translates `mellon_pipeline_config.json`, previews its block hierarchy and -`prompt`/`out_prompt` ports, reports preview-only admission, confirms a disabled -User Node with repository Python off, and proves its exact identity survives -Save/browser refresh. Repositories requiring repository Python remain a -separate explicit sandbox/authorization feature estimated at two to four -working days; they fail closed in this demo build. - -All integrity ledgers were regenerated from the exact pinned Diffusers package, -the reviewed Transformers Git checkout, and the locked production wheel after -these admissions. The complete backend gate now passes 2,156 tests, 46 skips, -and 6,507 parameterized subtests. The reviewed LTX-2.5 and Wan Animate 2 source -receipts remain explicitly contract-only because they were authored against a -newer Diffusers revision than the app pin; their tests now prove that boundary -instead of conflating future source research with current runtime support. -The previously approved gallery runtime files were restored to the backend -asset endpoint for fixture resolution, but they remain outside this phase's -review queue. - -The remaining repository gates are also green: Ruff's fatal-error/undefined- -name check passes, the managed backend environment has no dependency conflicts, -and preflight reports the AMD/ROCm runtime ready on an unused validation port -while the live frontend download continues on the intentionally reused app -port. The complete client gate passes formatting, lint, type checking, all unit -suites, production build, and bundle budgets; the final JavaScript total is -574,001 gzip bytes against the 575,488-byte cap. The per-workflow Model Manager -browser proof passes independently. - -Public `executable`, `autoEligible`, and `liveProof` flags remain false for all -of these new receipts pending the explicit publication decision recorded by -the promotion gate; this does not reopen previously reviewed media. The remaining Diffusers work is -definition-by-definition runtime admission and live evidence for catalog-only -or not-yet-run workflows, including the 12 custom-container runtime adapters; -the newly unblocked Wan2.2 text-to-video and LTX-2 runs are next. The -remaining Transformers work in this phase is limited to the pinned pipeline's -generic speech-seq2seq, transducer/TDT, and CTC-with-LM branches after the -Diffusers gates; non-speech Transformers families remain deferred. - -### 2026-08-27 FLUX.2 Klein Cluster qualification expansion - -The next definition-by-definition Diffusers wave live-qualified both admitted -FLUX.2 Klein workflows without changing their immutable publication flags: - -| Definition | Task | Expanded blocks | Generated WebP SHA-256 | -| ---------- | ---- | --------------: | ---------------------- | -| `diffusers.modular:Flux2KleinModularPipeline:text2image` | `06_fORfy8Bw2` | 10 | `407f58958b2f6d23556f08f3088db866e106252c6145b6270643d872cd5fdcac` | -| `diffusers.modular:Flux2KleinModularPipeline:image_conditioned` | `VTL8AsHT5m8b` | 13 | `d569d38cb4431bc4642c090f95fb86fa4b3557be9a461eeb1ff61f24ad96cff8` | - -The visible frontend inserted each Cluster from **Diffusers Cluster Nodes**, -changed its prompt, dimensions, steps, seed, dtype, and offload recipe, saved -the workflow, refreshed the browser, and compared the complete effective input -and execution-override snapshots. Both snapshots survived exactly. Each -Cluster then expanded to its pinned upstream hierarchy without changing the API -graph, collapsed to the same graph, prepared against the authoritative runtime -capability, and generated a real 256 x 256 image from -`black-forest-labs/FLUX.2-klein-4B@e7b7dc27f91deacad38e78976d1f2b499d76a294`. -The edit route consumed the first task's preserved backend asset and visibly -changed the red cube into translucent green glass. - -The run exposed and closed two generic integrity gaps. First, 37 -image-oriented Modular Diffusers specifications had sealed the repository but -had not bound `ModelsLoader.revision`; all now bind the exact reviewed commit, -and all 48 admitted routes are covered by a regression requiring their sealed -revision to equal the admission artifact revision. Second, the pinned FLUX.2 -model index serializes the legacy `Qwen2TokenizerFast` name while Transformers -5 consolidates that export into the tokenizers-backed `Qwen2Tokenizer` class. -MoDiff now admits only that exact repository-scoped official alias and still -rejects arbitrary tokenizer classes. The closed client model registry also now -retains the three admitted FLUX Modular capability families, and Cluster -preparation refreshes a missing authoritative capability snapshot before -failing. - -The combined receipt, browser screenshots, and generated assets are under -`test-review/frontend-pipeline-e2e-2026-08-26/flux2-klein-clusters/`. -They are qualification-only outputs with `showcaseApproved: false`. -`executable`, `autoEligible`, and `liveProof` remain false until the user -explicitly approves each definition's output and promotion receipt. The next -coverage candidates are the already-admitted FLUX.1-dev and FLUX Kontext -definitions; catalog-only definitions still require real runtime -action/profile/admission work before live qualification is possible. - -The post-change release gate is green: all 2,206 backend tests ran with 47 -intentional skips, the complete client lint/type/unit/build/bundle check -passed, and all 114 mocked Studio browser tests passed in one 6.2-minute run. -The browser regression now also requires a saved -`Flux2KleinModularPipeline` loader to retain that exact Modular identity after -hydration instead of falling back to the standard pipeline sharing its -repository. - -## Acceptance invariants - -- Registered Cluster and user-owned definitions use one canonical composite - canvas type, renderer, public-boundary model, persistence contract, expansion - mechanism, and connection behavior. -- Built-in Hugging Face definitions never appear as User Nodes. -- The frontend does not select Python classes or contain model-family graph branches. -- Shared task behavior is implemented once; family/model differences are narrow backend data or adapters. -- Upstream visibility does not imply MoDiff executability or Auto readiness. -- Collapsing or expanding a Cluster Node never changes its execution semantics. -- Moving an existing internal node changes only that instance's saved internal - layout; it never reconstructs the wrapper or creates a reusable User Node. -- Structural customization is copy-on-write within the same instance and keeps - controls, public ports, values, dimensions, preview, and external edges until - the user explicitly changes the interface. -- Parameters and structurally customized User Node instances survive - save/refresh without silent reset. -- A model change cannot mutate prompts or parameters in another graph instance. -- Browsing nodes performs no model download, weight load, optional-runtime installation, or remote-code execution. -- The existing MoDiff graph remains the only graph executor. diff --git a/docs/image-demo.md b/docs/image-demo.md new file mode 100644 index 00000000..000488fe --- /dev/null +++ b/docs/image-demo.md @@ -0,0 +1,78 @@ +# Image demo checkpoint — 22 September 2026 + +Open MoDiff at `http://127.0.0.1:8088` on the qualified workstation. In the **Workflows** sidebar, under **My workflows**, search for `Demo -`. Use the saved workflow settings; the Qwen developer starter otherwise defaults to a larger 2048-pixel output. + +| Saved workflow | Purpose | Settings | +| ------------------------------ | -------------------------------------------------------------- | ------------------- | +| Demo - FLUX Schnell | Fast generic image generation | 1024×1024, 4 steps | +| Demo - Qwen 2.1 Text | Complex prompts, typography and native attention-context reuse | 1024×1024, 40 steps | +| Demo - Qwen 2.1 Reference Edit | Two uploaded reference images and an edit instruction | 1024×1024, 40 steps | +| Demo - Saved Block | Reinsert a saved generic image graph and edit its seed | 1024×1024, 4 steps | +| Demo - Modular Z-Image | Visible native Modular Diffusers composition | 1024×1024, 8 steps | + +The named workflows and JSON backups are local demo artifacts. To recreate a graph, +choose Developer → New → Text to image (or Multiple reference images), select the +model, preview and create the workflow, select Custom resource policy, then use the +sizes and step counts above. Choose `ZImageModularPipeline` for the Modular example. +Upload your own two references for the Qwen edit; JSON backups do not embed those +files. The saved recipes use bfloat16 and model CPU offload. + +## Suggested demonstration + +1. Open FLUX Schnell, change the prompt or seed and Run. Repeat unchanged to show output reuse. +2. Switch Creator / Developer. The editable graph and resource policy persist; the starting dialog and authoring tools differ. +3. Open Modular Z-Image and inspect Load Models, Encode Prompt, Denoise and Decode. Change the prompt or seed and run again. +4. Open Qwen 2.1 Text. Inspect **Reuse attention context**. This controls native KV reuse within one generation; it is separate from MoDiff's reuse between graph runs. +5. Open Qwen 2.1 Reference Edit and inspect the Load Image connection. Keep the provided references for a predictable demonstration. +6. In Developer, use Export → Service package to expose prompt and seed, and name the preview output. For reference editing, also name the Load Image file input so callers can supply both references. The exported package runs through MoDiff's queue and runtime; it is not standalone Diffusers Python. + +## Qualification limits + +This checkpoint covers representative image demonstrations on AMD Radeon 8060S / ROCm 7.2 with approximately 121 GiB shared system RAM. It does not prove Windows 16 GiB VRAM / 32 GiB RAM viability. Qwen 2.1 uses standard Diffusers: the reviewed upstream snapshot has no native Qwen 2.1 Modular pipeline. + +The exhaustive W8/W9 image/audio campaign remains open. Audio, video and unsupported optional-runtime targets are not newly qualified by this image demo. Qwen weights remain subject to the publisher's research license. + +## Checks completed for the implementation + +- Backend base gate: `./scripts/with-runtime-env.sh .venv/bin/python -m pytest -q` — 3,538 passed, 525 skipped, 10,109 subtests passed. +- Reviewed optional runtime gate: `./scripts/with-runtime-env.sh .venv/bin/python scripts/test_reviewed_optional_runtime.py -q tests` — 4,101 passed, 21 skipped, 10,820 subtests passed. These overlap with the base gate; do not add the counts together. +- `uvx --from ruff==0.12.7 ruff check . --select E9,F` passed. +- Client `npm run check` passed, including types, unit tests, production build and bundle checks. +- Full mocked browser run: 239 passed, 3 failed. After excluding generated review evidence from Vite's file watcher, all three exact failed cases passed in an isolated rerun. The full 242-case suite was not repeated after that configuration-only fix. +- Production frontend bytes match the tested client build. Preservation checks cover 112 original model snapshots and 1,696 files: targets, sizes, modification times and recorded metadata hashes are unchanged; operator approvals are byte-identical. This is not a fresh hash of every weight payload. + +## AMD reference-edit fix + +On this ROCm stack, SDPA in Qwen's vision encoder produced non-finite prompt embeddings and a black/transparent output. The shared runtime now selects Transformers' public eager-attention implementation for SDPA vision subconfigs on ROCm. Text and Diffusers denoiser attention retain their existing implementation; non-ROCm hosts and other explicit vision implementations are unchanged. The full two-reference UI edit now yields a valid image. This does not add a model-specific node or frontend branch. + +## Live evidence and scope + +FLUX Schnell passed eight UI/service cases, including reopening and prompt/seed +changes. Qwen 2.1 text generation passed ten cases, including native KV on/off, +forced recomputation, service export and Creator/Developer invariance. After the +ROCm fix, the two-reference edit passed UI baseline, cache-off, forced cache-on +and reopening. That campaign exposed a service scalar/list mismatch, which was +fixed; its separate eight-case recovery passed multi-file service execution, +repeat reuse, seed/prompt edits, UI/service agreement and workspace invariance. +Modular Z-Image passed seven UI/API/CLI service cases. The Saved Block was created +from a real graph, reinserted, seed-edited, executed, saved and restored. + +For two 1024-pixel references and 40 steps, a warm Qwen edit took 598.26 seconds +with native KV reuse off and 280.36 seconds with it on: 2.13× whole-task speedup +in this single paired observation on this AMD host. Warm cache-on output matched +the original byte-for-byte. The text-only comparison was 181.10 versus 172.14 +seconds (1.05×); its run overlapped mocked browser checks. Neither result is a +claim about the publisher's A100 benchmark or general speedup across prompts. + +The final executable backend source was tested at `7d412c6`; the client was +`03d4c22`. Earlier image/cache observations used `9cc1ba1`/`267683b` and +`433a19e`/`03d4c22`. The subsequent changes fix ROCm vision attention, development +file watching and multi-file service validation; the final reference baseline +reproduced the earlier fixed output byte-for-byte. Documentation commits do not +change the tested production bundle or executable code. Export service packages +from the final running app, since their requirements include the backend revision. + +Detailed local receipts, original failures, recovery evidence and JSON workflow +backups remain under `reviews/creator-developer-w9`, `reviews/image-demo` and +`reviews/creator-developer-w10-upgrade`; they are not shipped as public Gallery +assets. This is a demo checkpoint, not completion of W8, W9, W10 or H1. diff --git a/docs/image-native-variant-review.md b/docs/image-native-variant-review.md new file mode 100644 index 00000000..e3ae2b9b --- /dev/null +++ b/docs/image-native-variant-review.md @@ -0,0 +1,62 @@ +# Exact image variant review + +Reviewed against Diffusers `fbf49e7f35857f76bc57b177e26f12b03687c668`. +These are implementation decisions, not live-image or resource qualification. +The source-bound machine-readable decisions are in +`modiff/image_native_variant_reviews.py` and the generated readiness ledger. +Existing standard profiles and saved graphs are retained. +Exact operation recipes carry `operation_recipe: true`: they remain selectable +in the operation picker but do not replace legacy Auto model/task owners. +The new ordered-reference recipes are ordinary operation graphs, not new +compiled Cluster admissions. + +## Native PAG + +Select `sdxl-pag:modular` on the ordinary SDXL Modular task picker. Text-to-image, +image-to-image, inpaint, ControlNet text-to-image and ControlNet image-to-image +use the existing editable stages and one shared official PAG Guider. The explicit +Layers node targets all ten mid-block transformer layers and can be edited. +CFG scale defaults to 5, PAG scale to 3, and the native +perturbation interval to 0–1. + +This is an alternative, not a silent conversion of the standard PAG executor: +the pinned native guider has an **exclusive start boundary**, so step zero is +unperturbed, and has no adaptive-PAG-scale argument. One-step runs therefore do +not perturb attention. Use the retained standard profile when those semantics +are required. The native recipe defaults to 50 steps. Tests exercise the actual +upstream attention hook, perturbation, numerical combination and hook removal +using tiny CPU tensors; they do not establish visual quality of pretrained weights. + +## Twelve exact variant decisions + +| Advertised route | Decision | +| --- | --- | +| FLUX dev image-to-image | Native `flux-dev:modular/image_to_image`; standard `edit_image` is the task alias, not Kontext editing. | +| FLUX dev FP8 image-to-image | Artifact blocked: root checkpoint has no reviewed component-layout/conversion recipe. | +| Kontext multi-reference | Native `flux-kontext:modular/multi_image_reference_edit`; visible Stitch Images operation constructs the same ordered equal-height RGB canvas as the standard adapter. | +| Kontext NVFP4 multi-reference | Artifact blocked: root checkpoint is not an interchangeable full component repository. | +| FLUX Krea text-to-image | Native `flux-krea:modular`, exact Krea artifact, 28 steps, guidance 3.5. | +| FLUX Schnell text-to-image | Native `flux-schnell:modular`, four steps, guidance 0, 256-token prompt default/limit in the starter. Upstream reads guidance-embedding support from the actual transformer config. | +| FLUX.2 dev multi-reference | Native ordered-reference task; separate latent/token ranges and reference position IDs, not image batching or a canvas. | +| Klein 9B KV text-to-image | Native `flux2-klein-kv:t2i-modular`. The exact cached index at `a6dfb36eca3a3906eb2fd460795adfb844e5fcce` declares distilled `Flux2KleinPipeline` and standard component classes. The upstream no-reference branch does not use KV caching. No 4B memory proof is inherited. | +| Klein 9B KV edit | Retained standard exception: modular Klein has no reference-K/V extract/cached loop at this pin. | +| Klein 9B KV multi-reference | Same exact upstream limitation; per-generation KV state is not cross-run caching. | +| Klein 4B multi-reference | Native ordered-reference task using the exact distilled 4B profile. | +| SDXL Turbo text-to-image | Native `sdxl-turbo:modular`, 512px, one step, CFG 0, repository scheduler and exact fp16 files. | + +Reference recipes allow at most eight images. Horizontal composition checks input +and output pixel limits before allocating a canvas. FLUX.2 preserves individual +reference ordering. No license acceptance, artifact conversion, library upgrade, +new executor, or browser-side model-specific branch is implied. + +## Verification boundary + +Use the focused native PAG/reference, operation starter, optional runtime, image +processing and readiness tests when changing these adapters. Run client operation +contract/parser/export tests for their authoring projections. A backend-only fix +does not require the entire browser suite. Batch the final shared backend gate; +run only relevant browser journeys when actual UI behavior changes. + +Missing downloads, blocked checkpoint layouts, hardware limits and image quality +remain separate from reviewed integration. Never turn a static review, a tiny +CPU attention test, or a base-model output into an exact pretrained-model pass. diff --git a/docs/image-runtime-recovery.md b/docs/image-runtime-recovery.md new file mode 100644 index 00000000..eb82b40d --- /dev/null +++ b/docs/image-runtime-recovery.md @@ -0,0 +1,39 @@ +# Image runtime pressure and recovery + +The existing graph executor and process supervisor remain the only execution +owners. Recovery does not resume a partially completed denoising step. + +- Reserve all managed components in a dispatch before loading any of them. + Proactive accelerator eviction and CPU-pressure eviction must skip these + leases, including nested dispatches. +- Discarding a model removes references; it must not materialize a full CPU copy + of an offloaded model. CPU offload remains a separate, explicit operation. +- A cached node whose managed handle was evicted executes again, with the cache + reason `models_evicted`. Successful unchanged runs still reuse resident models. +- Memory-observation failure stops that eviction pass. Cleanup errors are logged + and must not replace the original execution failure. +- The memory manager never replays an opaque failed callback: its generator or + pipeline may already be mutated. OOM reaches the graph's existing bounded retry + policy, which discards failed-attempt caches and rebuilds declared inputs/seeds. +- Unexpected worker exits reconcile the durable queue: the active run fails and + queued runs are cancelled with their workflow/navigation information retained. + A fresh worker can accept new work; interrupted model code is not resumed. +- Five rapid worker failures stop automatic replacement. Delays are 1, 2, 4 and + 8 seconds and are interruptible by shutdown. A worker lasting at least 60 seconds + resets this rapid-failure counter. Intentional forced-cancel replacements use + the existing separate path. Fix the reported cause before manually restarting + after exhaustion; the supervisor does not spin indefinitely on an import or + device failure. + +The campaign runner owns isolated backend processes and has separate per-job, +case, startup, request and teardown bounds. A terminal failed case is recorded; +later cases/routes continue where safe. An unknown or still-running model call +ends that route's owned process tree instead of overlapping another model run. +`--cases baseline,seed,guidance` selects focused verification and records +`selected_cases_only` in the receipt; it is not full modification coverage. + +Fault-injection tests cover pressure, missing handles, nested leases, cleanup +failure, queue recovery and restart exhaustion without exhausting host RAM or +deliberately destabilizing the GPU driver. These are not Windows/ROCm/CUDA/MPS +hardware-OOM qualification. No software can guarantee recovery from every OS, +driver or hardware failure. diff --git a/docs/modularity-demo.md b/docs/modularity-demo.md new file mode 100644 index 00000000..7cded87c --- /dev/null +++ b/docs/modularity-demo.md @@ -0,0 +1,192 @@ +# Image modularity demo + +> **Historical technical rehearsal, not approved visual demo content.** The robot +> sequence was rejected for weak imagery and insufficient changes between chapters. +> Use the [SoHo fashion editorial replacement](fashion-editorial-demo.md) for the +> revised creative direction. The technical instructions below remain useful as +> recorded compatibility evidence, not a recommendation to present these images. + +This walkthrough uses ordinary editable nodes, not a hidden template or a new +executor. All ten choices below have completed real frontend-submitted generation +with the custom image/mask node and connected SDXL refinement at 512×512. Prompt, +custom/refiner values, connections and refresh persistence were checked. This +qualifies the demonstrated sequence, not every possible model/node combination. + +## Preparation + +On the rehearsed machine, open **My workflows** and search **Demo Ready**. +There are thirteen separate checkpoints: 01 baseline, 02 custom image/mask, +03 connected refinement, 04–11 native models/variants, 12 whole-pipeline Qwen 2.1, +and 13 the native return. These are editable workflows, not prerecorded runs. +Save a presentation copy before editing a checkpoint. + +Use Developer mode and a fresh workflow. The top-bar **Custom** memory policy +keeps the explicitly selected loader/offload settings. Check the required model +files in Model Manager before presenting. Clear finished **Session activity** +notifications if they cover connectors; this only dismisses notifications, not +saved outputs. **Arrange graph** fits the graph after inserting nodes. + +In **Nodes → Custom nodes**, stage `examples/custom_nodes/LightPaletteDirector` +under a unique module name, review the source, check consent and **Enable code**. +Saved workflows do not grant custom-code execution permission on another machine. + +## 1. Build and run native text to image + +1. In Nodes, choose `ZImageModularPipeline`, task **text to image**. +2. **Preview connected starter → Add starter to canvas** adds Load Models, + Encode Prompt, Denoise and Decode Latents with their typed connections. +3. Add **Preview Image** and connect Decode Latents **Images → Image**. +4. Set 512×512, eight steps, guidance `1`, fixed seed `271828`. Use this prompt: + + > Studio product photograph of a small retro robot, cream enamel and teal metal, + > two round eyes, on a peach pedestal, warm side lighting, full body, simple + > backdrop, no text. + +5. Run, then Save As `Modularity Demo - 01 - Native Z-Image`. + +To retain an earlier chapter, **Save As the next chapter before editing**. +Editing an already-saved tab may autosave into that record. Separate imported +`Demo Ready` checkpoints are available on the rehearsed machine; duplicate one +before a presentation so the originals remain useful fallback checkpoints. + +Explain that model loading, prompt encoding, denoising and decoding are independently +visible stages. The seed is fixed to make subsequent changes easier to compare. + +## 2. Add meaningful custom Python + +1. Add **Light & Palette Director** from the custom module in Nodes. +2. Branch Decode Latents **Images → Image** on the custom node. Keep the original + preview connected as the before image. +3. Add two previews: **Directed image → Image** and **Soft mask → Image**. +4. Set Palette strength to `0.80`. Show Region X/Y, width/height, feather, + shadow/midtone/highlight colors and light angle/intensity. +5. Run and compare the original, directed image and mask. Save a second checkpoint. + +The custom node computes a soft elliptical mask, luminance-dependent palette and +directional gradient while preserving source detail. It runs on CPU using the +existing Pillow/NumPy dependencies. It is not a semantic segmenter or a physical +relighting model. Its decoded-image boundary is independent of the generator. + +## 3. Connect custom results to diffusion refinement + +1. Add a second connected starter: `StableDiffusionXLModularPipeline`, **inpaint**. + Do not replace the first generator. +2. Connect custom **Directed image → Image** and **Soft mask → Mask Image** on + the new Image Encode node. White mask regions are the editable region. +3. Connect the second Decode Latents to a final Preview Image. +4. Set the refiner prompt, 512×512, 20 steps, strength `0.5` and CFG 5; + use fixed seed `271828`. Keep Image Encode and Denoise geometry in sync; + the new SDXL starter shares those geometry controls. +5. Run and compare the three stages. Save, refresh, inspect all values and wires, + then run again. + +This makes the custom code influence a subsequent model, rather than merely +filtering the final output. Refinement strength and palette strength are different +controls. A soft input mask is processed according to the selected pipeline's mask +semantics; it does not promise arbitrary soft blending inside diffusion. + +## 4. Change only the first generator + +Use **Choose model** on the first Load Models node, inspect the change review, +then apply it. Leave the custom node and the separate SDXL refiner untouched. +First change `teal metal` to `cobalt blue metal` in the generator prompt. After +each switch, show that this edit survives without retyping it. Keep custom +Palette strength `0.80` and the refiner at 20 steps/CFG 5 throughout. + +| Chapter | Model | Rehearsed execution | +| --- | --- | --- | +| Main | Z-Image Turbo | Editable native stages; 8 steps, guidance 1 | +| Main | SDXL Base | Editable native stages; 25 steps, CFG 5 | +| Main | FLUX.2 Klein 4B distilled | Editable native stages; 4 steps, guidance 1 | +| Extended | FLUX.1 dev | Editable native stages; 28 steps, guidance 3.5 | +| Extended | Qwen-Image-2512 | Editable native stages; 28 steps, CFG 4 | +| Variants | SDXL PAG | 25 steps/CFG 5, native Guider + explicit Layers, same SDXL checkpoint | +| Variants | SDXL Turbo | Native stages; 512px, one step, CFG 0 | +| Variants | FLUX.1 Schnell | Native stages; four steps, unused guidance hidden/fixed at 0 | +| Variants | FLUX.1 Krea dev | Native stages; 28 steps, guidance 3.5 | +| Compatibility | Qwen-Image 2.1 | Whole pipeline; 40 steps, guidance 1, same custom image boundary | + +For PAG, expand **Other implementations** and select `sdxl-pag:modular`. +The repository is shared with SDXL Base, but the Guider must read +`PerturbedAttentionGuidance`; showing the same checkpoint alone does not prove PAG. +For Turbo, set CFG on the connected Guider, not an unused Denoise scalar. + +Before changing model-specific steps/guidance, show that the authored prompt, +custom settings and external connections remain. Verify **Editable nodes** versus +**Whole pipeline** in the picker; do not silently substitute a standard route for +a native demonstration. Model-specific components/latents are not interchangeable. +Unsupported settings are retained inactive for restoration, not falsely active. + +Save each chapter, refresh, run and return to Z-Image. Larger models can take +longer to load; a compatible graph is not a guarantee of adequate memory. +The one-step Turbo example is a speed/settings demonstration; its rehearsed +output is visibly lower-fidelity than the main models, not quality-equivalent. + +## 5. Whole-pipeline compatibility and runtime changes + +Qwen 2.1 replaces the generator's internal stages with its supported whole-pipeline +operation. Its decoded image still feeds the same custom node. This demonstrates +portability at the image boundary, not native Modular Diffusers support for Qwen 2.1. + +The reviewed Transformers 5.17 + PEFT runtime explicitly satisfies the earlier +5.14.1 and exact-main runtime contracts. On a host with that validated environment +already active, these model changes do not need a Python restart. This does not +imply compatibility with optional quantization/media packages or arbitrary versions. + +If Setup says another runtime is required, save the workflow, finish or stop active +and queued runs, explicitly install/activate the reviewed environment, and wait for +reconnection. The supervised backend restarts its worker; the UI polls readiness. +An unsupervised launch requires a manual restart. Installation is never implicit, +and failed activation must not silently run the wrong runtime or replay a generation. + +## 6. Optional code reload + +Edit the installed custom module's `main.py`: replace its smoothstep mask line +with `mask = mask ** 2`. In Custom nodes, **Review reload**, inspect the changed +hash, consent and enable. Keep the same ports. Run the existing graph to show +that changed code affects the mask and downstream refinement without rewiring. +See the [custom example](../examples/custom_nodes/LightPaletteDirector/README.md). + +This exact edit was rehearsed on the deployed frontend: explicit code review and +consent, page refresh, then the complete graph ran successfully. The generator +image was byte-identical to the baseline while the custom image changed; the +refiner also completed and the browser reported no uncaught errors. The installed +demo copy is restored to the original smoothstep version after rehearsal. + +## Presenter checklist + +- Start the app and confirm models are downloaded before the presentation. +- Use **Demo Ready - 01** or a fresh workflow for the build-up; keep checkpoint + **03** available if you want to jump straight to the connected custom/refiner + graph. Zoom into the stage you are explaining; Arrange fits the whole graph. +- Change the prompt once, then point out its retention before adjusting each + model's steps/guidance. Show custom strength and the refiner staying unchanged. +- Use **12** to explain whole-pipeline compatibility, then **13** for the native + return. Do not describe model-specific latents/components as universal. +- Save, refresh, inspect values and wires, and Run. Model loading and generation + take real time; the supported switch itself does not promise instant inference. +- For the code edit, modify the installed module, not the example source file; + review and consent again. Finish by restoring and reapproving the original. + +## Rehearsal tooling + +The sibling client contains `tests/e2e/live-backend/modularity-demo.spec.ts`. +It uses real frontend authoring gestures and saves per-case workflow JSON, run +receipts, screenshots and images. It requires `MODIFF_RUN_MODULARITY_DEMO=1`, +uses bounded generation waits, and records independent model failures. It does +not run the full UI suite or automatically retry a failed model. + +From the sibling client checkout, with the backend running and model files ready: + +```bash +MODIFF_RUN_MODULARITY_DEMO=1 node scripts/run-modularity-demo.mjs +``` + +Each case records its failure and proceeds to the next. Generation waits are +bounded at ten minutes; the runner also has an outer deadline. Automatic worker +recovery is limited to the local supervised demo backend, the runner's own +active task, and an otherwise empty queue. It must not stop another user's run. +Retain the output directory: `MODIFF_DEMO_RESUME_WORKFLOW`, +`MODIFF_DEMO_SKIP_CASES` and `MODIFF_DEMO_REUSE_GENERATIONS` allow targeted +continuation instead of replaying successful generations. Reused receipts are +explicitly labelled; authoring-only success is not image-generation proof. diff --git a/docs/nested-modular-diffusers-migration-plan-2026-09-04.md b/docs/nested-modular-diffusers-migration-plan-2026-09-04.md deleted file mode 100644 index 04e404b6..00000000 --- a/docs/nested-modular-diffusers-migration-plan-2026-09-04.md +++ /dev/null @@ -1,389 +0,0 @@ -# Nested Modular Diffusers Migration Plan - -Date: 2026-09-04 - -Implementation status: recursive hierarchy, progressive disclosure, and the -shared internal Block projection are implemented. Per-model execution and -publication qualification continue as a separate evidence campaign. - -## Outcome - -Migrate every registered Diffusers Cluster and every upstream Modular Diffusers -block used by those Clusters to one recursive `BlockDefinitionV2` experience: - -- a registered Cluster remains the immutable, collapsed one-node pipeline; -- expanding it reveals the active upstream Modular Diffusers hierarchy rather - than one flattened row of children; -- every sequential, conditional, auto, loop, and leaf block is an ordinary - selectable, movable, resizable, connectable canvas node; -- every upstream block is also searchable and draggable from the left panel - under **Modular Diffusers Blocks**; -- edits are copy-on-write workflow-instance data and never mutate a registered - catalog definition; -- selecting any hierarchy level offers **Save as new User Node**; an existing - user-owned definition additionally offers **Update existing User Node**; -- save, browser refresh, backend restart, collapse, and re-expansion preserve - exact prompts, parameters, component/model choices, topology, interfaces, - hierarchy expansion, sizes, and positions. - -No alternate executor or client-owned pipeline format is introduced. The -backend graph remains execution authority, and modified Modular workflows are -rebuilt through the pinned Diffusers `init_pipeline()` path. - -## Audited upstream and application facts - -The pinned unpruned contract covers 34 Modular pipeline classes and 94 public -workflows. It contains 632 exact block definitions: 598 definitions placed in -the upstream trees and 34 pipeline roots. The deepest upstream placement is -five Modular levels beneath the pipeline root, so the deepest visible tree is -six levels when the outer registered Cluster is counted. - -The current selected-execution snapshot contains 483 exact definitions, but -`get_execution_blocks()` resolves conditionals and flattens some selected -container names. That snapshot remains useful for execution admission; it is -not sufficient as the visual hierarchy source. The unpruned conditional -snapshot plus its workflow execution traces are the hierarchy authority. - -MoDiff Client uses `@xyflow/react` 12.8.4. React Flow supports recursive -subflows through chained `parentId` relationships, relative positions, -`extent`, and `expandParent`. Current MoDiff V2 projection assigns every child -directly to the outer Block and therefore discards its already-persisted -`parentPlacementPath` relationship. - -Current catalog discovery also marks every Modular Diffusers block -`insertable: false`. Discoverability is not sufficient for modular editing. - -## Terminology and invariants - -### Registered Cluster - -The immutable first-party full workflow. Registration controls provenance, -execution admission, publication, and Auto authority. It does not select a -different renderer. - -### Modular Diffusers Block - -One exact upstream container or leaf definition. A container may expose its -children recursively. It is not a nested registered Cluster and it is not a -nested User Node. - -### User Node - -A user-owned `BlockDefinitionV2` materialized from the current state of a full -Cluster, a Modular container subtree, a loop, a leaf, or an ordinary selected -subgraph. It retains provenance but cannot claim registered ownership. - -Nested User Node or Cluster references remain prohibited. Saving a subtree -copies and rebases its semantic graph into a new independent top-level User -Node; it never stores a live reference to the containing Cluster. - -## Contract strategy - -`BlockDefinitionV2.graph` remains the canonical flat execution graph. Exact -Modular containment is already hash-covered by -`BlockGraphNodeV2.modularDiffusers.placementPath` and -`parentPlacementPath`. Recursive React Flow parents are a projection derived -from those fields, not a second execution graph. - -Every upstream placement is rendered through the same Block V2 frame whether -it is the registered root, a structural container, an executable container, -or a leaf. Internal frames are projections, not nested persistence -authorities: changing a control, moving a child, deleting/replacing a subtree, -or reconnecting an edge writes to the one owning workflow -`BlockInstanceV2.effectiveGraph`/`values`/`presentation`. Saving an internal -frame as a User Node materializes a new independent top-level definition. - -Fresh hierarchical instances seed `presentation.collapsedContainerNodeIds` -with every upstream placement that owns children. Expanding the outer Cluster -therefore reveals only its immediate internal Blocks. Expanding one of those -reveals the next level while deeper descendants remain collapsed. The array is -presentation-only, persists with the workflow, and may name executable -`custom` nodes as well as structural `group` nodes because upstream ownership, -not a renderer type, defines a Modular container. - -An upstream node copied from the left-panel catalog additionally retains the -three-part immutable origin `sourceDefinitionId`, `sourcePlacementPath`, and -`sourceExecutionScope`. Its `placementPath` may change when the workflow owner -moves or replaces that instance; the source triplet never changes. This is how -the client can lower an edited canvas back to an exact pinned insert/replace -recipe without guessing from a class name. - -`BlockInstanceV2.presentation` gains only presentation data required for -recursive editing: - -- expansion state keyed by stable semantic container node ID; -- layout entries interpreted relative to each node's immediate semantic - parent; -- deterministic fallbacks for definitions saved before recursive projection. - -`internalLayout` records a container's compact own box, not the recursively -expanded dimensions of its descendants. Expanded bounds are a projection: -visible children are measured bottom-up, each ancestor receives explicit -header/right/bottom padding, and colliding siblings are shifted without -changing their durable preferred coordinates. A late control or connector -measurement must rematerialize the complete ancestor chain in one store update. -Collapse therefore restores the compact box and can never preserve a stale -expanded envelope or clip descendants after a later re-expansion. Legacy -entries whose stored dimensions already contain their children are normalized -to compact own bounds during projection. - -Any contract addition must update the client and backend validators, -canonicalization where execution identity is affected, API documentation, -cross-runtime fixtures, size bounds, and legacy normalization in the same -change. Presentation-only fields do not enter definition content hashes. - -Subtree sockets follow the same rule in both collapsed and expanded states. A -visible internal container derives typed input/output handles only for public -bindings and semantic edges that cross that subtree boundary. When expanded, -the exact internal edge remains attached to the visible leaf, while the -container retains its interface for ordinary external connections. Those -canvas handles keep a runtime-only map to the exact leaf sockets; they never -become fields in `BlockDefinitionV2` or `effectiveGraph`. Reconnect resolves -the handle back to the leaf endpoint, reopening restores the exact original -edge, and execution uses every semantic edge regardless of which containers -are collapsed. Edges whose endpoints are both hidden by the same container are -not projected onto its public surface. - -## Phase 1 — Recursive hierarchy compiler and renderer - -1. Join each registered workflow with its unpruned pipeline tree and exact - workflow selection trace. -2. Include active sequential/conditional/auto/loop container definitions in - the registered `BlockDefinitionV2`; do not expose inactive alternatives as - active execution nodes. -3. Preserve exact class, kind, contract hash, placement path, parent path, and - immutable Diffusers revision for every node. -4. Project parents before descendants and assign each child to its immediate - semantic parent. -5. Add independent expand/collapse controls for every Modular container. -6. Recalculate bounds recursively without allowing collapsed user sizing to - constrain expanded descendants. -7. Preserve child dragging, resizing, edge routing, z-order, toolbar behavior, - and root public sockets at every depth. - -Acceptance: - -- all 94 definitions compile deterministically into 1,794 active graph nodes; -- maximum compiled hierarchy depth is five and no child renders outside its - immediate visible container; -- Qwen text-to-image visibly renders its active text encoder, denoise branch, - denoise loop members, decoder branch, loader, and preview at their correct - levels; -- parameter-only edits modify one value and no graph, interface, or unrelated - presentation field. -- first expansion is progressively disclosed, and internal Blocks use the - shared Block frame, controls, connectors, resize behavior, selection, and - subtree-save action instead of a dashed Group-only renderer. - -## Phase 2 — Insertable Modular Diffusers catalog - -1. Generate catalog entries from every exact unpruned placed definition. -2. Group entries by pipeline family, class, and block kind while keeping every - exact context/hash selectable. -3. Compile a leaf drop into an ordinary runtime node with exact typed inputs, - outputs, configs, and component requirements. -4. Compile a container drop into the shared V2 Block renderer with recursive - descendants. -5. Permit drops on the top-level canvas and inside compatible expanded - containers. -6. Keep incompatible/context-incomplete nodes addable. Highlight unresolved - `PipelineState`, loop state, component, and value connections and expose a - precise Fix suggestion instead of silently rejecting the drop. - -Acceptance: - -- every block referenced by all 94 workflows is searchable and draggable; -- a dropped item uses the same renderer and fields as the same item projected - from a Cluster; -- catalog browsing and dropping never download a model or enable remote code. - -## Phase 3 — Composition and semantic validation - -Extend the pinned backend composition recipe from `remove`, `move`, and -`duplicate` to exact `insert` and `replace` operations. Validate: - -- destination container kind and ordering; -- input/output and `kwargs_type` compatibility; -- `PipelineState` and loop-member ownership; -- component type and immutable artifact requirements; -- prevention of cycles, orphaned children, and invalid cross-container edges. - -A valid changed tree is rebuilt through upstream `init_pipeline()`. Invalid -changes remain visible and editable but cannot silently execute the old -registered graph. Studio and Fix must identify the first invalid node/edge and -the concrete compatible action. - -## Phase 4 — Save any hierarchy level as a User Node - -Add one shared node-toolbar action for Cluster roots, Modular containers, -loops, and leaves: - -1. collect the selected node and all semantic descendants; -2. include internal edges and derive boundary ports from crossing edges; -3. rebase placement paths and layouts relative to the selected root; -4. copy current instance values into the new definition defaults, including - prompts, parameters, reviewed model choice, and included component bindings; -5. retain immutable source revision and parent-definition provenance; -6. create a user-owned, mutable `BlockDefinitionV2` with new identities and - canonical hashes; -7. leave the current workflow instance unchanged after saving. - -Save choices: - -- registered source: **Save as new User Node** or **Keep in workflow**; -- user source: **Update existing User Node**, **Save as new User Node**, or - **Keep in workflow**. - -Suggested names include workflow and hierarchy context, for example -`Qwen Image / Text to Image / Denoise`. - -## Phase 5 — Persistence and migration - -1. Normalize existing flat V2 instances deterministically into recursive - presentation without changing values, ports, definition references, or - execution identity. -2. Keep legacy V1 instances behind the existing hash-bound archive/equivalence - recovery boundary; never guess a registered replacement. -3. Persist hierarchy expansion, relative layouts, subtree edits, current - values, effective interfaces, and user-definition decisions through the - existing workflow backend. -4. Make undo/redo atomic for expansion, node adoption, structural edits, and - subtree saving. -5. Preserve independent instances across workflows and tabs. - -## Phase 6 — Verification and rollout - -Focused contract tests: - -- snapshot counts, exact IDs, roots, placements, and maximum depth; -- hierarchy compilation and parent ordering for all 94 workflows; -- client/backend V2 normalization and hash agreement; -- every catalog block is insertable without model loading; -- compatible and incompatible composition validation; -- subtree boundary derivation and current-value defaults; -- update/new/workflow-only save behavior; -- persistence, restart, migration, undo/redo, and multi-instance isolation. - -Browser tests: - -- drag a Cluster into an empty graph and observe immediate insertion feedback; -- recursively expand to the deepest available level; -- move and resize children at several levels; -- edit one parameter and verify no collateral change; -- insert, replace, remove, and reconnect a Modular block; -- save root, intermediate container, loop, and leaf User Nodes; -- save, refresh, restart, and compare the serialized instance byte-for-byte; -- collapse/re-expand and verify public inputs/outputs and internal topology; -- run collapsed and expanded Qwen workflows through the frontend and compare - execution/output fingerprints. - -After the Qwen vertical slice passes, enable the same generic compiler for all -94 registered Diffusers workflows. Real model qualification and publication -remain separate evidence gates; structural availability never implies that a -model is installed, licensed, resource-qualified, or publicly promoted. - -## Estimated completion - -- recursive schema/compiler/renderer and Qwen proof: 2–3 focused days; -- all-block catalog, insertion, composition, and subtree saving: 3–5 focused - days; -- migration, all-94 structural audit, browser regression, and production - bundle smoke: 2–3 focused days. - -Total functional migration: approximately 7–11 focused engineering days, -excluding downloads, legal acknowledgements, hardware-specific execution -qualification, and showcase approval. - -## Current implementation checkpoint - -- [x] Pinned all-94 and unpruned hierarchy counts audited. -- [x] React Flow recursive-subflow capability verified. -- [x] Current flattening and non-insertable catalog boundaries identified. -- [x] Recursive hierarchy compiler for every exact admitted Modular route. -- [x] Recursive renderer, relative layouts, per-container collapse, recursive - bounds, and compact parameterless leaves. -- [x] Unpruned Modular Diffusers catalog: 1,051 contextual placements / 598 - unique placed block definitions are searchable and draggable without a - model download. -- [x] Exact leaf adoption into a selected nested container with immutable - catalog provenance and save/refresh rematerialization. -- [x] Insert/replace backend recipe validation and upstream-tree rebuild: - mutate pinned unpruned `pipeline.blocks`, then select the workflow and - call `init_pipeline()`. -- [x] Effective V2 graph lowering for parameter-only, leaf insert, leaf - replace, move, and remove operations, with a visible backend rebuild - receipt action. -- [x] Save-subtree User Node lifecycle for root, intermediate container, loop, - and leaf selections. -- [x] Existing flat V2 normalization and V1 recovery compatibility remain - behind their explicit archive/equivalence boundary. -- [x] All-94 upstream hierarchy audit (maximum upstream depth five). -- [x] All 94 reviewed workflows compile to the shared structural - `BlockDefinitionV2`: 1,794 active ordinary internal nodes, maximum upstream - depth five, and no nested Cluster/User node payloads. -- [x] Container catalog drops materialize the complete reviewed subtree as a - source-neutral V2 fragment. Dropping that fragment into an expanded - compatible Block flattens and rebases its ordinary descendants; the - exact immutable source triplet survives Save/refresh. -- [x] Catalog-only reviewed Diffusers Clusters remain structurally insertable - through the same V2 renderer, while carrying no execution/Auto authority. -- [x] Fresh admitted insertion uses a build-time generated catalog containing - all 90 current route admissions (82 Diffusers and eight Transformers), - validated by both runtimes and fetched without hidden field actions, - optional-runtime activation, Hub access, or model loading. -- [x] The compiled catalog is reproducible through - `npm run catalog:block-v2:generate`; both main validation gates run the - independent all-route byte-for-byte staleness check and always remove - their temporary audit directory. -- [x] Invalid V2 topology is reported as a Block-structure issue, targets the - implicated projected node when possible, and offers a recoverable Fix - that restores reviewed structure while preserving compatible additions. -- [x] The complete 10-route Qwen family browser lifecycle proves empty-graph - insertion, creator defaults, public ports, recursive containment, - parameter edits, Save, refresh, byte-identical instance restoration, - and contained re-expansion. Current evidence is isolated under - `data/review/nested-modular-v2-2026-09-04/`. -- [x] Qwen production-browser resize proof covers collapsed-root resizing, - expansion that ignores the collapsed constraint, nested-node resizing, - collapse/re-expansion, and retained internal size. -- [x] Collapsed internal Blocks derive real typed boundary sockets for hidden - descendant connections. Shrinking a projected Block preserves its - connector tray and crossing links; reconnect targets the exact semantic - leaf socket; Save/refresh restores the same surface; collapsed and - expanded execution retain the complete flat graph. -- [x] Structural editing is proven in the complete browser gate: ordinary - nodes can be adopted into and moved out of nested Blocks, compatible - nodes can be replaced, public ports can be configured and reconnected, - and all changes persist through Save/refresh. The V2 root no longer - applies the legacy User Block auto-fit rule while a descendant is being - dragged out. -- [x] Generated V2 definitions are the primary insertion path. The client - verifies and materializes the backend-served immutable definition before - graph mutation; the field-action compiler remains only a bounded legacy - recovery fallback. -- [x] Full backend regression: 2,452 passed, 54 skipped, and 6,879 subtests. -- [x] Full client static/unit/build/bundle gate (`npm run check`). -- [x] Full browser gate: two shared-control tests and all 126 Studio mocked - tests. This includes recursive containment, catalog drops, nested - composition, all User Node persistence choices, restart/isolation, - invalid-topology recovery, and legacy migration/rollback. -- [x] Backend-served production-bundle smoke at `127.0.0.1:8088`: collapsed - root resize, recursive expansion, every projected Qwen node contained, - internal model-loader resize, collapse/re-expand, and retained internal - size. -- [ ] Re-run real Qwen media generation through the current generated-catalog - build and compare collapsed/expanded execution fingerprints. This is an - execution/publication qualification item, not a structural migration - blocker. - -## Remaining work outside the structural migration - -- Qualify real execution, resources, installed immutable artifacts, and output - quality independently for each route before enabling executable, - `liveProof`, publication, or Auto flags. -- Re-run Qwen collapsed/expanded generation through the current production - bundle and compare execution and output fingerprints. -- Preserve historical V1 instances behind archive/equivalence recovery where - their exact definition is no longer current; never infer equivalence from a - display name or model family. -- Promote only definition-specific receipts whose generated media is approved. diff --git a/docs/optional-runtime-optimizations.md b/docs/optional-runtime-optimizations.md index f0f2fe6b..43ccdacc 100644 --- a/docs/optional-runtime-optimizations.md +++ b/docs/optional-runtime-optimizations.md @@ -502,6 +502,13 @@ disabled until explicitly selected or applied by an exact Auto receipt: components. - Existing model-specific quantization and offload recipes. +VAE memory configuration reports slicing or tiling as unsupported when the +method is absent or the upstream implementation raises `NotImplementedError`. +An inherited method alone does not prove support. Ordinary audio loading also +skips unimplemented tiling and logs that ordinary decoding will be used; other +configuration errors still fail the load. No memory-saving effect is claimed +for an unsupported feature. + The following upstream features are deliberately visible but not enableable: - generic quantization combined with offload; diff --git a/docs/qwen-image-21.md b/docs/qwen-image-21.md new file mode 100644 index 00000000..fccf90d4 --- /dev/null +++ b/docs/qwen-image-21.md @@ -0,0 +1,76 @@ +# Qwen-Image 2.1 in generic image workflows + +The standard Diffusers adapter uses the existing **Load Pipeline**, **Generate +Image**, **Edit Image** and **Decode Image Latents** nodes. Choose Qwen Image 2.1 +in Developer → Workflows, then choose text-to-image, image edit or multiple +reference images. The graph remains editable and can be saved or exported +through the existing workflow and service-package paths. + +The model revision is `790c92633540aa0cb11d9abf19eb46d861714758` in +[`Qwen/Qwen-Image-2.1`](https://huggingface.co/Qwen/Qwen-Image-2.1). +Weights use the publisher's [Qwen Research License](https://huggingface.co/Qwen/Qwen-Image-2.1/blob/790c92633540aa0cb11d9abf19eb46d861714758/LICENSE), +which limits use to non-commercial research/evaluation unless separately +licensed. Model weights and generated Gallery assets are not distributed with +this integration. + +## Runtime and current qualification + +The reviewed Diffusers source is +[`fbf49e7f35857f76bc57b177e26f12b03687c668`](https://github.com/huggingface/diffusers/commit/fbf49e7f35857f76bc57b177e26f12b03687c668). +Modular Diffusers is part of that same package. Qwen 2.1 requires the separate +Transformers 5.17.0 / PEFT 0.20.0 optional profile, with Tokenizers 0.23.1. +Browsing a workflow does not install it; use the app's explicit runtime setup +action. This new optional profile has passed isolated Linux x86-64 installation, +symbol, PEFT computation, activation and rollback qualification. Other targets +remain pending for this profile. Its exact compatibility aliases cover the reviewed +base and main Transformers profiles used by existing image/audio workflows; +GGUF, bitsandbytes and other optional extras still require their own profiles. + +Generic adapter contracts, a tiny real CPU denoiser/VAE test, and full-weight +1024×1024/40-step text generation and two-reference editing pass on the reviewed +Linux AMD runtime. Tests include native KV on/off, prompt/seed edits, reopening +and UI/service agreement. The ROCm vision encoder uses eager attention to avoid +non-finite SDPA embeddings; text and denoiser attention remain unchanged. +See the [image demo checkpoint](image-demo.md) for measured timings, exact test +scope and source revisions. Full release acceptance and reference-replacement +coverage remain open; this does not establish Windows 16 GB VRAM / 32 GB RAM viability. + +The reviewed upstream source has **no native Qwen 2.1 Modular pipeline**. This +integration runs its standard Diffusers pipeline. Exposing a latent output and +an explicit decoder does not imply independently composable native prompt and +denoise blocks. + +## Reuse attention context + +**Reuse attention context** forwards the native `use_kv_cache` boolean. It is +enabled by default for this pipeline and appears only on compatible generic +nodes. During a generation, the first denoising step prefills prompt and +reference context; later steps reuse the fixed context. The upstream pipeline +owns a fresh cache for each invocation, including separate positive and +negative guidance caches when applicable. + +This differs from MoDiff reusing loaded models, prompt embeddings or a completed +node output between graph runs. The execution receipt records the actual cache +flag passed to Diffusers. It does not estimate cache hits or claim that one +request reused another request's KV state. + +Caching trades memory for less repeated computation. Cache on/off may produce +different reduced-precision images; fix the flag when comparing repeated +samples. The publisher's A100 timing is not a benchmark for other hardware. +See the [native pipeline contract](https://github.com/huggingface/diffusers/blob/fbf49e7f35857f76bc57b177e26f12b03687c668/src/diffusers/pipelines/qwenimage21/pipeline_qwenimage21.py). + +## Images, references and latents + +The generation and editing routes share one pipeline. Multiple-reference edit +accepts up to ten references within the generic cumulative input-pixel budget. +RGB and RGBA media remain intact through the adapter. New workflows use the +publisher's 2048×2048, 40-step recommendation with guidance 1.0. Dimensions use +32-pixel increments, with a 4096-pixel side and 5-megapixel output ceiling; +these bounds also permit the publisher's wide 2K presets. + +Choose latent output to connect **Decode Image Latents** explicitly. Use the +same loaded pipeline and original dimensions. The decoder applies that VAE's +native mean/standard-deviation normalization and preserves its alpha channel; +latents from another model are not interchangeable merely because the port is +a tensor. Controls absent from the native pipeline, such as edit strength or a +maximum sequence-length override, remain hidden. diff --git a/docs/runtime-support-matrix.md b/docs/runtime-support-matrix.md index bcfc1063..bbc39dbb 100644 --- a/docs/runtime-support-matrix.md +++ b/docs/runtime-support-matrix.md @@ -13,6 +13,15 @@ “Supported” describes installation/runtime qualification, not model performance. `/model_capabilities` remains the source of graph capability. Exact model, dtype, placement, optimization, driver, and hardware qualification is receipt-specific; an unqualified recipe may be runnable with a warning but must not be described as optimized. +Graph resource measurements use peak-accounting version 2. The serial worker +preserves accelerator high-water marks across per-node counter resets, including +offloaded generation followed by a small Preview. Resource fingerprints include +this version: earlier proof records remain on disk but cannot qualify the new +resource identity. An incomplete allocator observation omits the graph peak +rather than reporting the lower final-node value. Endpoint RSS samples are not +continuous process-memory peaks; dedicated VRAM and accessible shared memory +remain distinct. No cross-platform qualification follows from this accounting fix. + ## Model families and support boundaries The backend catalog and exact execution specifications determine available diff --git a/docs/service-prototyping.md b/docs/service-prototyping.md new file mode 100644 index 00000000..fe824e85 --- /dev/null +++ b/docs/service-prototyping.md @@ -0,0 +1,128 @@ +# Service prototyping with an API graph + +In the Developer workspace, choose **Export → Service package**. MoDiff lowers the same graph +used by API graph export, including expanded Blocks and Modular composition. +Name the controls and file inputs callers should supply and the preview outputs they +should receive. Leave a field blank to keep it internal. Named inputs have no +exported default: supply every value on every invocation. At least one preview +output is required. The Creator workspace keeps its compact Export menu. + +The resulting `modiff-service-v1` file contains the existing API graph, a named +interface, and an observed execution manifest. It runs through the local MoDiff +server's normal graph queue, caching, resource planning, cancellation and output +persistence. It is **not standalone Diffusers Python**, a hosted endpoint, or an +authenticated multi-user server. Arbitrary-graph Python generation remains a +separate feature. Keep the server at its supported loopback boundary. + +## Model-free example + +Start the backend with the [developer setup commands](developer-setup.md). In a +second terminal, from the backend checkout on Linux: + +```bash +.venv/bin/python -m modiff.service inspect examples/service/text-api.json +.venv/bin/python -m modiff.service build examples/service/text-api.json --interface examples/service/interface.json --output text-service.json +.venv/bin/python -m modiff.service run text-service.json --inputs examples/service/inputs.json +``` + +On Windows, replace `.venv/bin/python` with `.venv/Scripts/python.exe`. The client +uses standard-library HTTP and never imports the node registry or model runtime. +`--output` creates a new JSON file and refuses to overwrite one. The example uses +Text Value → Data Viewer, needs no models, and returns a named `text` preview. +No service file generated from a local runtime belongs in the repository. + +The interface file maps names to exact lowered node/field identities: + +```json +{ + "inputs": { "prompt": [{ "nodeId": "prompt", "field": "text" }] }, + "outputs": { "text": [{ "nodeId": "preview", "field": "preview" }] } +} +``` + +A CLI-authored binding can target multiple fields of the same declared type, +useful when a graph shares prompt or seed values. The dialog assigns one target +per name. Inputs cannot replace connected fields or model/code identity controls. +Text prompts are exposed as strings for both ordinary Diffusers and Modular +nodes, including textarea fields declared with the registry's `text` alias. +This scalar interface does not accept a list of prompts. +File browsers whose trusted registry declares `multiple: true` expose a `files` +input. Supply one non-empty path string or a list of 1–128 non-empty path strings; +for example, `{"references": ["@data/images/front.webp", "@data/images/side.webp"]}`. +The normal loader's file-access and media limits still apply, including any lower +model-specific reference limit. Imported workflow metadata cannot enable list +inputs on scalar controls. Expose local file selections as required inputs when +exporting; the package does not include the files themselves. +Built-in Modular stages use the dynamic schema of their single connected, +reviewed Models Loader. This exposes controls such as prompt and seed without +trusting imported field types. Unbound or ambiguously owned stages and +contract-only custom model identities do not gain dynamic service inputs. +Previously generated preview values are omitted: exporting after a run does not +package its local output URLs or expose those results as callable inputs. +Execution values, including input file selections, still undergo portability checks. +Outputs select persisted `ui_text`, `ui_image`, `ui_audio` or `ui_video` fields; +add a preview node for tensors or other values. Output values are arrays of +records so repeated loop outputs retain their existing representation. + +## HTTP contract + +`POST /service_package` accepts three bounded JSON operations: + +- `{"operation":"inspect","graph":...}` returns `inputs` and `outputs` candidates. +- `{"operation":"build","graph":...,"interface":...}` returns `package`. +- `{"operation":"prepare","package":...,"values":{"prompt":"..."},"sid":"service_example"}` + returns `graph`, ready for the existing `POST /graph` route. + +All responses have `error:false` on success; invalid contracts return HTTP 400 +with an actionable `message`. The body limit is 8 MiB; duplicate JSON keys and +non-finite numbers are rejected. Inspection/build/prepare do not execute a graph, +install code, enable extensions, or download weights. + +Post the returned graph unchanged to `/graph`. Its `task_id` identifies the run. +Poll `/queue`, then fetch `/runs/{task_id}` on completion. The CLI returns only +matching task/node/preview values and durable URLs, without copying private graph +snapshots into its response. A timeout prints the task ID and leaves execution +alone; inspect that run before retrying to avoid duplicate work. Existing stop +and queue controls apply. Media URLs refer to this local server and are not +permanent public hosting links. + +Requirements are checked during preparation **and when execution begins**. A +queued package cannot silently pick up different approved custom source or +installed dependencies. Named values change no graph topology. Auto gets a fresh +graph-bound plan receipt for those values; the existing executor resolves current +hardware/resources and refuses unsupported plans. Manual retains exported +execution settings. Neither mode changes model or creative inputs to fit memory. + +## Portability and limits + +The manifest records backend commit/source fingerprint, Python minor version, +managed accelerator profile and dependency-contract digest, observed installed +package versions, required optional-runtime profile IDs, explicit Hub model +references and immutable revisions, and enabled custom-node code/dependency +identities. Cataloged repositories reuse the reviewed pin. Unknown repositories +require an explicit 40-character revision. Model selectors connected to other +nodes require a literal pinned selection before this version can be exported. + +This is a strict observed-environment snapshot, not an automatic environment +installer or a claim of bit-identical images across devices. Recreate the same +reviewed profile/packages and backend source before replay. Extra or changed +installed packages currently require re-export. Cross-profile execution requires +reviewing and exporting a package on the target profile. The service manifest +never authorizes code: stage/review/enable custom nodes independently on each +machine. It does not bundle custom source, weights or their licenses. Custom node +authors must expose external model dependencies explicitly; hidden downloads in +arbitrary Python cannot be inferred by graph inspection. + +Session IDs and UI snapshots are omitted. Execution parameters and execution +hints are retained; the exporter refuses detected credentials, local model +selections and local paths in portable material. Required inputs let callers +provide file identifiers or private text at invocation without embedding them in +the package. Opaque/custom values and file upload convenience controls are not +part of the first scalar interface. Review remaining prompts and metadata before +sharing: arbitrary secrets written as ordinary prose cannot be identified +reliably. Export errors are not permission to remove execution constraints; +resolve the named field and export again. + +The committed example and HTTP smoke prove a model-free API round trip. Diffusion +model output, optional-runtime availability, Windows execution and particular +GPU memory envelopes require their own validation evidence. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index d6c54bc3..3029b837 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -96,6 +96,18 @@ active model-download reservations, and the 64 GiB safety reserve fit. It does not delete cached models. After it completes, wait for active downloads to finish, restart MoDiff, and then verify `/template-gallery/manifest.json`. +## Loading models without network access + +Set `HF_HUB_OFFLINE=1` before starting the backend to use Hugging Face's offline +mode. Modular pipeline and standalone component loaders pass this choice to +Diffusers explicitly, including sharded weights whose metadata otherwise triggers +a Hub request. This preserves normal online loading when offline mode is unset. + +The exact selected revision, every required weight shard, and its configuration, +tokenizer and processor files must already be cached. If a required file is absent, +finish its installation through Models while online, then restart offline. +The presence of some weight files alone does not establish a complete model. + ## CUDA is not detected - Check `hardware.devices` and `hardware.torch` in preflight or `GET /system_stats`. @@ -169,6 +181,19 @@ For supported Intel graphics on x86-64 Linux or Windows, install or repair the p - Set `[huggingface] cache_dir`, `HF_HOME`, or `HF_HUB_CACHE` to a writable volume with sufficient free space. A configured `cache_dir` is exported as `HF_HUB_CACHE` by the backend. - Avoid pointing multiple applications at partially compatible cache layouts unless you understand how snapshots and revisions are resolved. +Exact snapshot lookup checks the same cache roots as Model Manager, preferring +the configured cache before secondary locations. Audio pipeline loading uses the +resolved immutable local snapshot. Selecting a cache for new downloads does not +hide an existing reviewed snapshot in another discovered cache, and inference +does not download missing files or substitute another revision. + +ACE-Step reads lyric embeddings and condition tensors outside ordinary model +forward calls. Its group offload uses leaf hooks for the text encoder; its +condition encoder and Oobleck VAE stay resident for all offload policies. Oobleck's +weight-normalization pre-hooks otherwise rebuild CPU weights before leaf transfer +hooks run. Budget these components' VRAM as well as the active offload group. A selectable policy is not hardware +qualification; inspect the actual run result and available memory. + Opening or refreshing Model Manager can require a full index and artifact validation pass over a very large cache. Those scans run in background worker threads and simultaneous refresh requests share the same work, so health, @@ -207,6 +232,12 @@ solely to switch transports. Cleanup can release MoDiff's node cache, managed Diffusers components, memory-manager entries, and accelerator cache. It cannot free memory owned by another process, and it does not guarantee that the same workflow fits afterward. +Automatic planning refreshes available host RAM between runs, including after +cache release. Runtime identity remains cached separately from this capacity +sample. During active inference, planning retains the existing non-blocking +snapshot behavior; it does not enter accelerator probes from that control path. +Available memory and a resident model alone do not qualify a resource recipe. + ## A run is taking much longer than expected ### Resource monitoring during execution @@ -229,6 +260,11 @@ running source. Distinguish a slow worker from a dead worker using supervisor status. Do not delete history, weaken timeout assertions, or reduce generation settings to conceal the problem. +Weight-loading counters publish intermediate updates at most four times per +second per progress bar. Initial/final counts and named component transitions +remain immediate. This bounds render bursts from fast tensor loading without +changing model loading or suppressing completion and failure events. + ### Inference and recovery First distinguish slow progress from a stalled worker. A step counter that diff --git a/docs/unified-composite-node-implementation-plan-2026-09-01.md b/docs/unified-composite-node-implementation-plan-2026-09-01.md deleted file mode 100644 index 82255ad0..00000000 --- a/docs/unified-composite-node-implementation-plan-2026-09-01.md +++ /dev/null @@ -1,2266 +0,0 @@ -# Unified Composite Node Contract and Implementation Plan — 2026-09-01 - -## Status and authority - -This document is the authoritative product and persistence contract for every -composite node shown on the MoDiff canvas. It supersedes the separate Cluster -renderer and destructive Cluster-to-User-Node conversion described in the -older [visual node system plan](hugging-face-visual-node-system-plan.md) and -[2026-08-27 demo plan](demoable-hugging-face-nodes-plan-2026-08-27.md). - -The implementation is not complete until the status checklist at the end of -this document is green. Earlier tests that proved the legacy conversion flow -remain useful historical evidence, but they do not satisfy this contract. - -The normative terms **MUST**, **MUST NOT**, **SHOULD**, and **MAY** have their -usual requirements meaning. - -### Canonical implementation sources - -This document defines the product semantics. The executable V2 contract is -maintained in the following synchronized source files: - -- `MoDiff-client/src/studio/blockSchemaV2.ts` — TypeScript types, strict - definition and instance validators, canonical serialization/hashing, - instance construction, and the additive V1 User Node reader; -- `MoDiff/modiff/block_definition_v2.py` — backend `TypedDict` definitions, - strict reusable-definition validation, canonical serialization/hashing, and - the user-store ownership boundary; -- `MoDiff/modiff/server.py` — `/studio/blocks` V1/V2 dispatch and persistence; -- `MoDiff-client/src/studio/registeredBlockAdapterV2.ts` — the model-family- - neutral compiler from one exact registered Diffusers or Transformers - admission plus its complete materialized execution skeleton into - `BlockDefinitionV2` and an independent `BlockInstanceV2`; -- `MoDiff-client/src/studio/registeredBlockV2Routes.ts` — the explicit, - fail-closed catalog route ledger. Each entry pins one schema-v6 definition - content hash, admission, immutable library/artifact revisions, exact Studio - receipt, dynamic-field actions, and reviewed executable public boundary. - The ledger MUST NOT infer a route from a pipeline class or family name; -- `MoDiff-client/src/studio/registeredBlockV2FanOutRoutes.ts` — the generated - exact-route extension for admissions that require declared logical fan-out, - aliased public IDs, or terminal-media adaptation. It remains subject to the - same route audit, V2 content hash, and canonical SHA-256 pins as the primary - ledger and is never a permissive family fallback; -- `MoDiff-client/src/studio/blockValueTypeCompatibilityV2.ts` — the V2-only - canonicalization boundary for Modular Diffusers scalar type aliases. It MUST - NOT broaden or rewrite the global canvas type system; -- `MoDiff-client/src/studio/huggingFaceClusterInsertion.ts` — the registered - catalog insertion boundary. Each exact admission must compile atomically to - one durable V2 `block` root; transient compiler nodes must never persist, - export, enter undo history, or remain after failure; -- `MoDiff-client/src/studio/huggingFaceNodeCatalog.ts` — the discovery versus - insertion boundary. Catalog metadata remains searchable, but a row is - graph-qualified, draggable, and insertable only when one exact admission - resolves through the pinned V2 route ledger. A catalog-only definition MUST - remain disabled and MUST NOT create a legacy `cluster` root as a fallback; -- `MoDiff-client/src/studio/compositeBlockCapabilitiesV2.ts` — the - source-neutral, fail-closed action resolver. Capabilities are transient UI - policy and MUST NOT be serialized into either V2 contract or an authority - receipt; -- `MoDiff-client/src/studio/blockDefinitionPersistenceV2.ts` and - `blockPersistenceV2.ts` — the reusable-definition persistence adapter and - three-choice workflow coordinator. They preserve the effective graph, - explicit interface, declared current defaults, preview bindings, and parent - provenance; reject registered-definition updates; validate the exact server - response; and rebase only the initiating instance after race checks; -- `MoDiff-client/src/studio/blockRuntimeV2.ts` — the source-neutral V2 canvas - projection, mutation, execution-expansion, and persistence-canonicalization - boundary. It derives React Flow roots, controls, connectors, children, and - bridge edges from one `BlockInstanceV2`; those projections are never a - second persisted authority; -- `MoDiff-client/src/studio/nodeConnectorResolution.ts` and - `MoDiff-client/src/stores/flowConnectionMutations.ts` — the common connector - lookup and mutation boundary. Public V2 sockets are derived from the - definition's explicit boundary, external connections terminate on the - durable root, and admitted same-owner internal edge changes update the - instance effective graph copy-on-write; -- `MoDiff-client/src/components/BlockNode.tsx`, `BlockNodeFrame.tsx`, and - `BlockNodeV2.tsx` — the single React Flow `block` renderer entry, shared - visual/action frame, and source-neutral V2 projection view. Source kind may - gate actions but MUST NOT select another frame; -- `MoDiff-client/src/stores/useFlowStore.ts` — the workflow integration point - that invokes V2 persistence canonicalization and copy-on-write presentation - and value actions for already-V2 roots, expands those roots into their - concrete graph during API export, resolves root execution targets to a - deterministic preview child, and applies a saved reusable definition to one - targeted instance without refreshing sibling snapshots; -- `MoDiff-client/src/stores/useUserBlockStore.ts`, - `src/components/NodeList.tsx`, and `src/workflow/useWorkflowDrop.ts` — the - mixed V1/V2 User Node library boundary. User-owned V2 definitions are - fetched, optimistically saved with failure rollback, listed, and reinserted - by click or drag as new independent top-level `BlockInstanceV2` roots; and -- `MoDiff-client/src/studio/runReadiness.ts` and `runCoordinator.ts` — the - already-V2 readiness and submission boundary. Both inspect the same concrete - execution graph used by export, reject malformed V2 authority fail-closed, - and do not send a composite wrapper to the backend; -- `MoDiff-client/src/studio/graphFixer.ts` — the explicit recovery boundary for - malformed workflow-local V2 structure. It validates the effective graph - through execution expansion, targets the owning root, restores registered - baseline nodes/edges only after user selection, and retains compatible - custom additions. It MUST NOT mutate `definitionSnapshot`, update a reusable - definition, or silently repair a graph during Run; -- `MoDiff-client/src/studio/blockAutoAuthorityV2.ts` and - `MoDiff/modiff/huggingface_cluster_runtime.py` — the instance-scoped Auto - authority boundary. The client submits the complete normalized instance and - explicit planner form; the backend validates and recomputes all identity, - canonical-definition, resource, runtime, admission, and artifact material - before issuing a short-lived receipt; -- `MoDiff-client/src/studio/blockAutoEligibilityV2.ts` and - `blockRunFormV2.ts` — the source-neutral registered-instance eligibility and - exact run-form/provenance projectors. Auto accepts one current pinned root - with parameter-only customization and reviewed companion nodes; Expert - remains executable without Auto authority. Output history is repaired from - the immutable V2 graph snapshot and exact preview-node identity rather than - unrelated Studio form state; -- `MoDiff-client/src/studio/userBlocks.ts` and - `src/stores/websocketMessageHandler.ts` — deterministic mapping from concrete - V2 runtime nodes back to their durable collapsed owner and declared preview - binding. WebSocket output/progress is stored in instance-local - `previewStates`, never mirrored into root `NodeData.params` or undo history; -- `MoDiff/modiff/composite_migration_inventory.py` and - `MoDiff/scripts/inventory_legacy_composites.py` — the deterministic, - read-only legacy inventory builder and stdout-only operator CLI. They inspect - saved workflow JSON and reusable User Node records without converting, - deleting, merging, recovering, or writing either store; -- `MoDiff/modiff/composite_migration.py`, - `studio_persistence_lock.py`, `workflow_store.py`, and `server.py` — the - separately authorized V1-to-V2 and exact compiler-supplemented registered- - Cluster preview/application and exact-backup recovery boundary. It shares - one process-local lock with ordinary workflow/User Node writes, verifies - reviewed source/target/compiler hashes, and never force-overwrites, deletes, - renames, or merges a record; and -- `MoDiff-client/src/studio/registeredClusterCompilerSupplement.ts` and - `src/components/CompositeMigrationCard.tsx` — the visible exact-current - legacy compiler-supplement generator and review/recovery UI. Generation - reads exact saved workflow bytes, uses hidden transient compilation, binds - every mapping receipt, performs a final backend re-preview, and never writes - a workflow before the separately confirmed Apply operation; and -- `MoDiff/tests/test_composite_migration_inventory.py` — mixed legacy - Cluster/V1 User Node/already-V2 inventory fixtures, ambiguity/error coverage, - deterministic hashing, input immutability, and filesystem/CLI non-mutation - evidence. `test_composite_migration.py` and - `test_composite_migration_server.py` cover synchronized V1 conversion, - registered compiler supplements, identity/value/interface/layout/edge - preservation, pin/tamper rejection, explicit authority, exact backups, - stale-plan conflicts, idempotency, rollback, reapply, and the HTTP contract. - -Live Auto qualification remains a production gate. The schema-v6 compiler -audit now covers all 90 exact registered definition/admission pairs: 82 -Diffusers and eight Transformers. This includes Qwen Image and Flux, -MiniMax Music 3 text-to-audio, and all three currently admitted speech routes. -Each admission remains an explicit independently reviewed route; broad routing -does not permit model-family inference, partial dynamic-field finalization, or -reuse of another admission's compiler pins. - -Catalog visibility is not execution authority. Definitions without an exact -current `registeredBlockV2Route` remain visible as **Catalog only** and are -structurally draggable/insertable through the shared source-neutral V2 -renderer, but receive no Run, Auto, or publication authority. Programmatic -execution and promotion boundaries repeat the exact-route check. -This defense-in-depth rule prevents a stale hash, missing route, or future -discovery-only definition from reviving the legacy renderer and reintroducing -different controls, ports, persistence, or expansion behavior. - -The client and backend definition canonicalizers MUST produce identical -`graphHash` and `contentHash` values for the same JSON payload. Neither -implementation is a fallback copy that may drift. Any reusable-definition -schema or hashing change MUST update both sides and their cross-runtime -fixtures in the same change. Backend migration also uses the same canonical -JSON/FNV contract for `blockInterfaceHashV2`; newly migrated durable instances -MUST materialize `effectiveInterface` rather than relying on the client's -missing-field legacy normalizer. The registered adapter is also part of that -synchronized change whenever a source, graph, boundary, control, suggestion, -preview, or provenance field changes. - -`/studio/blocks` stores reusable user-owned definitions, not workflow -instances. `BlockInstanceV2` is embedded in the saved workflow so its values, -layout, preview state, and effective graph remain insertion-local. Registered -definitions use the same schema in memory but remain read-only catalog data. - -### Historical legacy mismatch and integration boundary - -The 2026-09-01 pre-V2 audit found two independent legacy systems whose -differences explain the original defect. New registered insertions no longer -use this split; the description remains normative migration context for saved -legacy records: - -- a registered Cluster inserts as React Flow `type: "cluster"`, renders through - `HuggingFaceClusterNode`, stores `huggingFaceClusterInstance`, reconstructs - derived execution children from the live catalog, and translates its - boundary during export; and -- a User Node inserts as `type: "block"`, renders through `BlockNode`, stores a - V1 `userBlockSnapshot`, and expands/collapses through the V1 User Block - functions, which may normalize or re-derive its public interface. - -This is why moving from the former representation to the latter can change -controls, handles, action placement, dimensions, and internal fields. It is -not an acceptable source-category difference and it MUST NOT be preserved in -the V2 implementation. - -During the migration window, the legacy registered path has a containment fix: -concurrent capability discovery shares one in-flight request, workflow-epoch -changes rematerialize every registered instance independently, and moved -execution-child positions persist as parent-relative presentation state. The -mocked two-instance Save/refresh test proves that this prevents the earlier -cross-instance rematerialization/reset race. It does not make the legacy -`cluster` root or `HuggingFaceClusterNode` the accepted V2 implementation. - -Changing only the legacy Cluster root's `type` to `block` is also invalid. -`BlockNode` currently assumes a V1 `userBlockSnapshot`; doing that would create -two competing authorities (`BlockInstanceV2` versus V1 snapshot/params) whose -values could diverge on the first edit, collapse, refresh, duplicate, or run. -The migration gate therefore requires the V2 runtime projector and controller -before any registered insertion is switched to the common renderer. - -For a canonical V2 root, the only persisted composite payload is: - -```ts -{ - type: "block", - data: { - type: "block", - blockInstanceV2: BlockInstanceV2, - }, -} -``` - -Catalog labels, badges, controls, connectors, previews, child nodes, and bridge -edges are derived views. A V2 root MUST NOT also contain -`huggingFaceClusterInstance`, `userBlockSnapshot`, or another mutable copy of -its values or graph. - -## Product decision - -MoDiff has one composite-node system. A registered Diffusers Cluster, a -registered Transformers Cluster, an imported Hub block, and a User Node use -the same canvas type, renderer, interface model, expansion mechanics, -persistence format, and connection behavior. - -`Cluster` is a catalog and provenance classification. It is not a second kind -of canvas widget. - -The product differences are limited to: - -- where the reusable definition is registered; -- who owns and may update the reusable definition; -- immutable source and artifact provenance; -- reviewed execution and Auto authority; and -- catalog badges, discovery categories, and permitted save actions. - -Those differences MUST NOT select a different renderer, infer a different -public interface, alter saved parameter values, or replace a canvas node. - -## User-visible invariants - -1. Two insertions of the same definition initially have the same controls, - ports, actions, minimum size, resize behavior, and expansion affordance. -2. Running one instance or viewing its output may update its run status and - preview only. It MUST NOT alter its definition, public interface, or - presentation contract. -3. Expanding a block reveals ordinary connected graph nodes with their normal - controls and sockets. Collapse is presentation-only. -4. Moving an existing internal node changes only that instance's internal - layout. It MUST NOT create a User Node, call `/studio/blocks`, recalculate - controls or ports, or invalidate execution authority. -5. Editing an exposed value changes only that workflow instance. The saved - value wins over definition defaults after Save and browser refresh. -6. Adding, removing, replacing, or reconnecting an internal node is a - copy-on-write structural edit of the same workflow block instance. The - reusable registered source remains immutable. -7. A structural edit may invalidate reviewed execution or Auto authority, but - MUST NOT change the renderer, canvas type, public boundary, name, prompt, - dimensions, or external connections. -8. A reusable User Node is created or updated only after an explicit save - choice. Structural editing alone MUST NOT add a library entry. -9. Inputs and outputs change only through the explicit **Configure interface** - action. Internal movement or topology edits never infer a new boundary. -10. Cluster Nodes and User Nodes cannot be nested. Ordinary nodes can be moved - into or out of an expanded composite with one undoable mutation. - -## Terminology - -| Term | Meaning | -| ------------------- | ------------------------------------------------------------------------------------------------------------------- | -| Block definition | Reusable graph, explicit interface, exposed controls, defaults, preview bindings, and source metadata. | -| Block instance | One canvas insertion with its own values, effective graph snapshot, layout, size, preview, and customization state. | -| Registered Cluster | A block definition owned by the reviewed Diffusers or Transformers catalog. | -| User Node | A reusable block definition owned by the user. | -| Workflow-only block | A customized instance whose reusable definition has not been created or updated. | -| Structural edit | Addition, deletion, replacement, adoption, or reconnection that changes the effective internal graph. | -| Presentation edit | Move, resize, expand, collapse, pan, selection, or viewport change. | -| Public boundary | Ordered, stable input and output ports visible outside the composite. | -| Authority | A separately calculated permission or receipt for reviewed execution, Auto management, or publication. | - -## Canonical persisted contracts - -The following TypeScript-shaped definitions are normative at the semantic -level. Implementation types may split them into files, but field meaning and -invariants MUST remain equivalent. Persisted JSON uses `schemaVersion: 2`. - -```ts -type BlockJsonPrimitive = string | number | boolean | null; -type BlockJsonValue = - BlockJsonPrimitive | BlockJsonValue[] | { [key: string]: BlockJsonValue }; -type BlockJsonObject = { [key: string]: BlockJsonValue }; -``` - -Definition and instance payloads accept JSON values only. Runtime objects, -callbacks, class instances, tensors, and non-finite numbers are rejected. - -### `BlockDefinitionV2` - -```ts -type BlockDefinitionV2 = { - schemaVersion: 2; - definitionId: string; - displayName: string; - description?: string; - contentHash: string; - - source: BlockSourceV2; - graph: BlockGraphV2; - boundary: BlockBoundaryV2; - controls: BlockControlV2[]; - suggestedInputs?: SuggestedInputSetV2[]; - previews: BlockPreviewBindingV2[]; - - // Describes reusable-definition ownership, not canvas rendering. - ownership: { - kind: "registered" | "user"; - definitionMutable: boolean; - }; -}; -``` - -Required invariants: - -- `definitionId` is stable and opaque. Display names are never identity. -- `contentHash` covers exactly the canonical graph (without its derived - `graphHash` field), boundary, ordered controls including declared defaults, - ordered previews, and these execution-relevant source bindings when present: - `provider`, `library`, `libraryRevision`, `pipelineClass`, `blocksClass`, - `workflow`, `manifestDefinitionId`, `manifestContentHash`, `repository`, and - `repositoryRevision`, plus `executionAdmissionId` when declared. -- `contentHash` deliberately excludes `schemaVersion`, `definitionId`, - `displayName`, `description`, `suggestedInputs`, `ownership`, and the source's - `kind`, `catalogCategory`, and `parent` ancestry. Those fields remain strictly - validated and persisted even though they are not execution-content identity. -- A registered definition has `ownership.kind = "registered"` and - `definitionMutable = false`. -- Every `user` or `hub_import` source has `ownership.kind = "user"` and - `definitionMutable = true`. Updating it is still an explicit user operation; - `definitionMutable` does not authorize silent writes. -- The graph contains stable semantic node and edge IDs. IDs do not depend on - canvas position, expansion state, workflow title, or insertion order. -- Defaults exist only in the definition. Current workflow values exist only in - the instance. -- The definition does not contain a volatile resource plan, loaded component, - model object, tensor, task status, or live execution receipt. - -### `BlockSourceV2` - -```ts -type BlockSourceKindV2 = - "diffusers_catalog" | "transformers_catalog" | "hub_import" | "user"; - -type BlockSourceV2 = { - kind: BlockSourceKindV2; - catalogCategory?: "diffusers" | "transformers"; - provider?: string; - library?: "diffusers" | "transformers"; - libraryRevision?: string; - pipelineClass?: string; - blocksClass?: string; - workflow?: string; - manifestDefinitionId?: string; - manifestContentHash?: string; - // Exact reviewed execution admission. Required for registered definitions - // that may receive reviewed-execution or Auto authority. - executionAdmissionId?: string; - repository?: string; - repositoryRevision?: string; - parent?: { - definitionId: string; - contentHash: string; - sourceKind: BlockSourceKindV2; - }; -}; -``` - -Source invariants: - -- Repository-backed execution references use immutable revisions. A moving - branch is not a reusable execution identity. -- `repository` and `repositoryRevision` are an all-or-nothing pair; - `hub_import` requires the pair, and `repositoryRevision` is an exact - lowercase 40-hex commit. -- Registered sources require matching catalog/library kinds plus - `libraryRevision`, `manifestDefinitionId`, `manifestContentHash`, - `pipelineClass`, and `workflow`. Hub and User sources cannot claim a - registered `catalogCategory`. -- `executionAdmissionId`, when present, is an exact immutable catalog identity, - is covered by `contentHash`, and must match the admission used to compile the - registered graph. It is not inferred from `definitionId`, display text, model - class, or workflow mode. Reviewed-execution and Auto receipts for a registered - definition fail closed when it is absent or stale. -- `parent` records ancestry when a reusable User Node is saved from a - registered or imported definition. It does not make the child definition - registered or reviewed. -- Source metadata is never removed merely because an instance is customized. -- Source kind controls catalog placement and provenance display only. It MUST - NOT select a canvas renderer or port-inference algorithm. - -### `BlockGraphV2` - -```ts -type BlockGraphV2 = { - nodes: BlockGraphNodeV2[]; - edges: BlockGraphEdgeV2[]; - executionOrder?: string[]; - graphHash: string; -}; - -type BlockGraphNodeV2 = { - nodeId: string; - nodeType: string; - data: BlockJsonObject; - semanticRole?: string; - upstreamBlockPath?: string; - modularDiffusers?: BlockGraphNodeModularDiffusersV2; - containerInterface?: BlockContainerInterfaceV1; - parentNodeId?: string; -}; - -type BlockGraphEdgeV2 = { - edgeId: string; - sourceNodeId: string; - sourcePortId: string; - targetNodeId: string; - targetPortId: string; -}; - -type BlockContainerInterfaceV1 = { - schemaVersion: 1; - boundary: BlockBoundaryV2; // mode MUST be explicit - controls: Omit[]; - previews?: BlockPreviewBindingV2[]; -}; -``` - -`containerInterface` is an optional, canonical/hash-covered local public -surface on an existing semantic node, not a nested composite instance. Its -ports and controls bind only to that node and its semantic descendants. -Primary/mirror bindings must resolve to existing direction- and -type-compatible fields; duplicate input targets, duplicate control targets, -and a shared input/control ID with different complete target sets are rejected. -Every current edge crossing a declared container boundary must have a matching -local port. Declaration edits that remove or rebind a connected port fail -before any graph mutation. Sealed controls cannot be weakened by this editor. - -Root/local type compatibility has the same narrow file-transport exception: -an explicitly matching media file browser can bind a media port and a string -path control with the same full target set. Video file browsers also admit -declared sequences of PIL/image frames. Preview widgets qualify URL/base64 -transport by their exact media display, rather than pretending URLs are image -tensors. Other string fields and mismatched modalities remain rejected. - -There is still one flat effective execution graph and one owning instance. -Local controls MUST NOT store defaults or values independently. They write -existing effective fields or the root logical override when that field is -already exposed; shared root mirror consumers retain one value identity. -Renaming/reordering a local interface does not rewrite the root interface or -any field values. Newly wired non-baseline crossings retain a durable socket -after disconnection; neither projection nor persistence recreates a wire. -Collapsed mirrored sockets group only identical visible wires and carry exact -semantic-edge receipts for atomic connect/reconnect/delete. Execution always -uses the original flat graph, not those grouped canvas edges. -An undeclared deeper view inherits the nearest ancestor-local controls before -root controls, without adding a declaration during a value edit. A mirrored -public input remains one socket with its complete subtree-local target set. - -Subtree adoption validates the complete incoming graph atomically and remaps -local bindings with semantic IDs. Replacement preserves compatible local IDs -and rebinds their targets; deletion reports declarations held by surviving -containers rather than silently dropping them. Saving a configured subtree as -a User Node promotes that exact local surface to the new root boundary and -controls, baking current field values/defaults into the copy. It does not -modify the source workflow or register a new catalog Cluster. - -Local `previews`, when declared, bind existing compatible preview/output fields -within that same subtree. They share one owner `previewStates` inventory rather -than copying run state per view. Inventory order is root-definition bindings, -then first-seen local bindings in semantic graph-node order, deduplicated by -node/output port. Shared sources MUST agree on media type; a local `primary` -belongs only to that view, not to a newly introduced inventory entry. At most -one primary is allowed per surface. Root and local views filter this inventory -using their own declarations. The paired fixture is -`tests/fixtures/block_container_previews_v1.json`. - -Nesting a whole multi-root Block introduces one generic non-executing group in -the target's flat graph. Its local interface is the source's public boundary, -controls and previews. All semantic IDs, edges, parent relations, exact upstream -placements and declarations are rebased together; effective values are baked -into copied fields. A single container with a different independently declared -surface also receives a wrapper, preserving both interfaces. The inserted outer -container begins collapsed. Moving within a workflow preserves completed media -references but never transfers in-flight execution authority. Saving it as a -new reusable definition preserves current prompts/settings and preview bindings, -not transient output media or publication approval. Generic grouping imposes no -pipeline family; actual upstream control-owner adoption remains pinned-family -checked. Typed execution connections remain authoritative in either case. - -Compatibility: omitted declarations preserve old canonical bytes and hashes. -Legacy interfaces are derived until an explicit interface or non-baseline -wiring edit requires persistence. Readers without this optional-field contract -reject it under strict validation; they must not discard it. This requires -coordinated frontend/backend deployment, not an automatic historical-definition -rewrite. The cross-runtime fixture is -`tests/fixtures/block_container_interface_v1.json`; both strict validators, -hash implementations and API/workflow round trips are release gates. - -`parentNodeId` is optional, canonical/hash-covered semantic ownership for an -ordinary node or a saved User subtree placed inside another semantic container. -It references a group or an exact non-leaf upstream block in the same graph. -Missing parents, self-parenting, cycles, leaf parents, and conflicts with an -already resolved upstream placement parent are rejected. Existing upstream -placements keep their exact provenance; ordinary utility nodes MUST NOT acquire -fabricated `modularDiffusers` identity merely because they are nested. Both -explicit ownership and upstream placement resolve through one parent relation -for projection, descendant scopes, validation and subtree persistence. - -Moving/copying a subtree remaps internal parent IDs and local interface bindings; -saving it independently removes only its external parent reference. Current -field values and root overrides are baked into the reusable copy. Ownership -does not add an executor, second instance, new parameter value authority or -implicit wire. Palette insertion/adoption is atomic: failure removes the draft -insertion, and one Undo restores the prior graph. A whole Block with multiple -independent roots uses a generic wrapper carrying its already explicit public -surface. It never guesses a boundary or silently discards crossing wires. -Ordinary nodes and User containers can change parent within an instance while -preserving IDs, fields, wires and execution order. A retained local declaration -that would cross outside its owner must be rebound explicitly before the move. -Empty generic groups remain expandable insertion targets; inactive empty -upstream branch annotations do not become executable containers. -Whole-subtree move-out creates a workflow-owned User Block, not a library entry. -It preserves local controls and completed preview references; crossing wires -require exact public boundaries and complete fan-out. Existing root-owned -controls and previews are protected until explicitly rebound. The shared fixture is -`tests/fixtures/block_parent_node_v2.json`. Omission preserves pre-extension -hashes; older strict readers reject the new field, requiring paired deployment. - -`modularDiffusers`, when present, is canonical, hash-covered semantic identity -for one expanded registered/imported Modular Diffusers node. It distinguishes -MoDiff infrastructure such as component loading and preview from an exact -upstream block. Every entry pins the pipeline class, blocks class, workflow, -Diffusers revision, and runtime role. An upstream entry additionally pins the -block definition ID, concrete class, kind, contract hash, exact placement path, -parent placement path, and component names. It is provenance and lowering -metadata, never a parallel value or layout store. Registered semantic graphs -must not replace exact upstream block placements with broader convenience -stages; any optimized runtime lowering is explicit, versioned, hash-bound, and -must preserve the meaning of every supported edit. - -Graph positions and reusable layout hints do not belong in -`BlockDefinitionV2`. Initial layout is supplied while constructing an instance -and is persisted only in `BlockInstanceV2.presentation`; instance layout always -wins. - -Recursive Modular UI state is likewise instance presentation, not definition -identity. `collapsedContainerNodeIds` contains semantic graph-node IDs whose -exact `placementPath` is referenced by at least one descendant's -`parentPlacementPath`. The owning node may be a structural `group` or an -executable `custom` block; canvas renderer type is not container authority. -New hierarchical instances seed all such owners as collapsed for progressive -one-level expansion. Client and backend validators must derive and validate -that same set from the hash-covered graph metadata. - -`graphHash` covers node and edge content plus the optional ordered -`executionOrder`, but not the `graphHash` field itself. Canonicalization sorts -the node array by `nodeId` and the edge array by `edgeId`, so their storage order -is not identity; explicit `executionOrder` order remains semantic. Node IDs and -edge IDs are independently unique, edges reference declared nodes, and an -explicit execution order contains unique declared node IDs. Nested composite -nodes are rejected. - -### `BlockBoundaryV2` - -```ts -type BlockBoundaryV2 = { - mode: "explicit" | "derived"; - inputs: BlockPortV2[]; - outputs: BlockPortV2[]; - derivation?: { - algorithmVersion: string; - derivedAtDefinitionHash: string; - }; -}; - -type BlockPortV2 = { - portId: string; - label: string; - valueType: string; - required: boolean; - multiple?: boolean; - binding: { - nodeId: string; - fieldOrPortId: string; - }; - // Inputs only. The one public logical value is projected atomically to the - // primary binding above and every canonically ordered mirror below. - mirrorBindings?: { - nodeId: string; - fieldOrPortId: string; - }[]; -}; -``` - -Boundary rules: - -- Registered Diffusers and Transformers definitions MUST use `explicit`. -- Hub imports that declare a validated interface SHOULD use `explicit`. -- A User Node created from an arbitrary graph selection MAY begin as - `derived`. Derivation runs once while creating the reusable definition and - persists the resulting ordered ports. -- `derived` does not mean continuously recomputed. Graph edits never - automatically add, remove, or reorder ports. -- Re-derivation is an explicit **Configure interface** operation, produces a - preview of affected external edges, and is one undoable mutation. -- `portId` remains stable when its label changes. Deleting a connected port - requires explicit confirmation and a useful connection-loss report. -- Every public `portId` is unique across both `inputs` and `outputs`, not only - within one direction. The shared root connector map and React Flow handle - namespace cannot represent an input and output with the same handle ID - without ambiguity, so both client and backend validators reject that - definition. -- `mirrorBindings` is permitted on public inputs only. It represents genuine - semantic fan-out of one logical input to multiple internal consumers; it is - not a fallback list and the primary binding remains required. Mirrors are - non-empty when present, sorted lexically by `nodeId` then `fieldOrPortId`, - unique, distinct from the primary, bound to declared non-output fields, and - type-compatible with the public port. Public outputs remain single-binding - until an explicit aggregation contract is designed and reviewed. -- The collapsed socket still has exactly one `portId`. An external edge into - that socket, a stored instance value, or an internal adoption gesture fans - out atomically to the primary and every mirror. Expanded root bridge edges - show all of those targets. Execution export expands the one external input - into deterministic internal edges; move-out and collapse coalesce the exact - fan-out back to the one public connection. Partial or divergent fan-out - fails closed instead of silently selecting one consumer. -- Copy-on-write customization of a registered instance starts with the same - exact boundary. It MUST NOT expose every unconsumed internal input or every - internal output. - -### `BlockControlV2` - -```ts -type BlockControlV2 = { - controlId: string; - label: string; - binding: { - nodeId: string; - fieldId: string; - }; - // Additional internal fields that consume the same logical control value. - mirrorBindings?: { - nodeId: string; - fieldId: string; - }[]; - valueType: string; - defaultValue?: BlockJsonValue; - required?: boolean; - sealed?: boolean; - order: number; - group?: string; - help?: string; -}; - -type SuggestedInputSetV2 = { - suggestionId: string; - label: string; - source?: string; - values: Record; // keyed by controlId -}; - -type BlockPreviewBindingV2 = { - nodeId: string; - outputPortId: string; - mediaType: "image" | "video" | "audio" | "text" | "file"; - primary?: boolean; -}; -``` - -Control rules: - -- The collapsed surface and Studio inspector consume the same ordered control - descriptors and instance values. -- A control with `mirrorBindings` is still one logical control and has one - `controlId`, one displayed value, and one entry in `BlockInstanceV2.values`. - Runtime projection writes that value atomically to its primary field and all - mirrors. At compile time, actual/default values across the declared targets - must agree; disagreement is an invalid registered route rather than a reason - to pick one field. -- Control mirrors are non-empty when present, canonically sorted by `nodeId` - then `fieldId`, unique, distinct from the primary, bound to declared - non-output fields, and type-compatible with the control. Registered compiler - routes declare the complete target set explicitly; the adapter never infers - fan-out merely because labels or current values happen to match. -- `defaultValue` is a starter value, not a load-time reset policy. -- A missing instance value may resolve to the definition default only when the - field was never set. A saved explicit empty string, `false`, `0`, or `null` - MUST NOT be treated as missing. -- Suggested creator prompts belong in `suggestedInputs`; applying one is an - explicit user action that writes instance values. `source` records where the - suggestion came from; it does not grant execution authority. -- Control IDs are unique and `order` is contiguous from zero. Suggested values - may reference declared control IDs only, not boundary-only input IDs. -- A control may share an ID with a public input only when both bind the same - internal node and field; validators reject a shared ID with divergent - bindings. -- Sealed controls may be displayed for provenance or runtime setup but cannot - be silently unlocked by customization. Changing a sealed artifact or class - requires an explicit supported workflow/model replacement path. - -### `BlockInstanceV2` - -```ts -type BlockInstanceV2 = { - schemaVersion: 2; - instanceId: string; - definitionRef: { - definitionId: string; - contentHash: string; - }; - - // A self-contained insertion; prevents library drift or deletion from - // resetting an existing workflow. - definitionSnapshot: BlockDefinitionV2; - effectiveGraph: BlockGraphV2; - effectiveInterface: { - boundary: BlockBoundaryV2; - controls: BlockControlV2[]; - baseInterfaceHash: string; - effectiveInterfaceHash: string; - }; - values: Record; - - customization: { - state: "unchanged" | "parameters_changed" | "structure_changed"; - baseGraphHash: string; - effectiveGraphHash: string; - }; - - presentation: { - expanded: boolean; - position: { x: number; y: number }; - // Durable collapsed/user-resized size. This is not the expanded wrapper - // size and MUST NOT be overwritten by expansion or child measurement. - size: { width: number; height: number }; - internalLayout: Record< - string, - { x: number; y: number; width?: number; height?: number } - >; - }; - - previewStates: BlockPreviewStateV2[]; - authorities: BlockAuthorityReceiptV2[]; - routeSelection?: { - schemaVersion: 1; - routeSetId: string; - selectedRouteKey: string; - inactiveDrafts: Record; - }; -}; - -type BlockRouteDraftV1 = { - schemaVersion: 1; - routeKey: string; - definitionRef: BlockInstanceV2["definitionRef"]; - definitionSnapshot: BlockDefinitionV2; - effectiveGraph: BlockGraphV2; - effectiveInterface: BlockInstanceV2["effectiveInterface"]; - values: Record; - customization: BlockInstanceV2["customization"]; - internalLayout: BlockInstanceV2["presentation"]["internalLayout"]; -}; - -type BlockPreviewStateV2 = { - binding: BlockPreviewBindingV2; - mediaReference?: string; - taskId?: string; - status?: "idle" | "queued" | "running" | "complete" | "failed"; -}; -``` - -Instance invariants: - -- Every insertion receives a distinct `instanceId` and independent `values`, - `effectiveGraph`, `presentation`, preview, and authority state. -- `definitionSnapshot` makes workflow reload deterministic. Refresh never - fetches new defaults over saved instance values. -- `effectiveGraph` initially equals the definition graph. A structural edit - replaces it copy-on-write inside that instance; it does not change the - reusable definition or canvas node type. -- `effectiveInterface` initially equals the definition boundary and controls. - Configure Interface replaces it copy-on-write, preserves stable port/control - IDs unless the user explicitly adds or removes an entry, and never mutates - `definitionSnapshot`. Its base hash is always the definition interface hash; - its effective hash covers the ordered boundary and ordered controls. -- Presentation edits update `internalLayout` only and do not change - `effectiveGraphHash`, `contentHash`, or authority. -- Parameter edits update `values`. Only execution-relevant values affect an - execution fingerprint. That fingerprint covers the effective-interface hash - as well as current values, so a boundary/control customization cannot reuse a - receipt issued for a different public contract; neither controls nor the - public boundary are re-derived. -- `values` is keyed by a stable declared logical ID: a `controlId`, a public - input `portId`, or one ID intentionally shared by both projections. Labels - and array positions are never value identity. If two logical IDs bind the - same internal field, their values must agree or execution fails closed. -- Primary and mirror bindings do not add value IDs. Reading, editing, saving, - loading, and execution use the one public/control logical ID and project it - to the complete declared target set. A graph that contains only part of a - declared fan-out, or whose mirrored internal values disagree, is invalid. -- Preview state is instance-local and non-authoritative. Viewing or replacing - it cannot modify the graph or interface. Multiple preview bindings remain - ordered by the definition; at most one is the primary collapsed preview. -- Replacing an internal node that owns a preview or sealed control preserves - that node's stable semantic `nodeId`. The replacement must provide every - referenced field with compatible type and direction; a preview replacement - must additionally preserve its declared media and display role. This swaps - the implementation behind the existing immutable binding instead of - rebinding the preview or sealed control. A replaced preview's stale media, - task, and status are cleared to an idle state. -- Runtime output is accepted into preview state only when its deterministic - projected node ID and output port match one declared preview binding. The - matching artifact URL is preferred over a bounded value fallback. A - successful update replaces stale media/task state for that instance only and - is not an undoable graph edit. -- Authority receipts are keyed by `kind`; an instance may independently carry - reviewed-execution, Auto, and publication receipts. A receipt never grants a - different renderer or public interface. -- External workflow edges target `instanceId + portId`; they do not target a - renderer-specific node identity. -- `routeSelection` is an optional instance-only generic shell. Its selected - route is always the instance's active exact definition; an inactive draft is - a bounded, non-recursive registered route snapshot and never a nested Block. - Drafts exclude instance IDs, canvas position/size, previews, task IDs, and - authority receipts. Switching preserves the one root ID and compatible - external edges, clears volatile output/authority state, and restores each - route's values, effective graph/interface, and internal layout independently. -- Same-pipeline/same-workflow checkpoint selection does not use - `routeSelection`. It is one ordinary bound `modelVariant` control whose exact - repository options are part of the canonical `BlockDefinitionV2` graph and - whose chosen value lives in `BlockInstanceV2.values`. Changing it must leave - the definition reference, effective graph/interface, all other values, and - presentation byte-for-byte unchanged. Execution independently resolves the - selected repository to an immutable reviewed revision. - -### Configure-interface instance contract - -`BlockInstanceV2.effectiveInterface` is the explicit, strictly validated -instance-only interface snapshot. The value namespace, renderer controls, -public connectors, external-edge validation, persistence, and execution -translation all consume this one snapshot. Older V2 workflow instances that -do not contain the field migrate deterministically to the embedded definition -boundary and controls; an unknown field, stale base/effective hash, missing -binding, incompatible field type, duplicate ID/order, or divergent shared -input/control binding fails closed. - -Configure Interface edits the ordered boundary and controls copy-on-write. -Removing a currently connected port is blocked with an edge-impact count until -the user disconnects it. Sealed controls cannot be removed or rebound. The -reusable `BlockDefinitionV2` remains unchanged until the user explicitly -chooses **Save as new User Node** or, for an already user-owned definition, -**Update existing User Node**. - -The editor exposes entry reordering and the complete multi-target contract. -For a public input or editable control, the user can add or remove additional -type-compatible internal consumers while retaining one logical socket/control -and one instance value. The editor excludes the primary and existing mirrors, -stores additions in canonical binding order, never offers mirrors for public -outputs, and disables mirror mutation for sealed controls. - -Re-labeling or reordering an interface entry preserves its declared mirrors. -Rebinding the primary explicitly clears its mirrors until the user reviews and -declares the new complete fan-out. Removing/rebinding an entry, replacing or -deleting an internal node, and sealed-control checks examine the primary and -every mirror together. Interface hashes cover all mirror bindings, so a fan-out -change invalidates stale execution authority even when the public ID and label -do not change. - -This is an instance-only contract change. It follows the synchronized -instance-change rules below and requires workflow migration/lifecycle tests, -but it MUST NOT add a mutable interface field to the registered definition or -silently reuse `NodeData.params` as interface authority. - -### `BlockAuthorityReceiptV2` - -```ts -type BlockAuthorityReceiptV2 = { - kind: "reviewed_execution" | "auto" | "publication"; - definitionId: string; - definitionContentHash: string; - effectiveGraphHash: string; - executionParameterHash: string; - artifactRevisions: Record; - admissionId: string; - issuedAt: string; - expiresAt?: string; -}; -``` - -Authority rules: - -- Provenance says where a block came from. Authority says what its exact - current graph and parameters are permitted to do. They are not equivalent. -- A parameter change invalidates only receipts whose parameter hash no longer - matches. -- A structural edit invalidates reviewed execution, Auto, and publication - receipts unless the effective graph independently matches another reviewed - admission. -- Invalid authority may block Auto or a reviewed route. In non-Auto/Expert - mode, the ordinary graph remains runnable when its concrete dependencies are - available, and real preparation/runtime errors must be shown to the user. -- A finding deliberately demoted to a non-blocking Expert warning remains - visible and actionable in Graph Fix. Model, environment, and media warnings - retain their explicit **Models**, **Setup**, or **Gallery** action, but do not - disable **Run** and are never applied silently. A malformed concrete V2 graph - remains a blocking structural error; warning presentation does not weaken - strict graph validation. -- The issuing backend MUST strictly validate the submitted V2 execution - material and recompute `definitionContentHash`, `effectiveGraphHash`, and - `executionParameterHash`; it cannot treat client-provided hash strings as - evidence and echo them into a receipt. It must also resolve the exact - catalog admission, immutable artifacts, installed revisions, execution - profile, optional runtime, and selected resource candidate. Hash or graph, - interface, value, artifact, recipe, or admission substitution fails closed. -- Registered Auto admission pins both the public V2 definition content hash - and a SHA-256 of the complete canonical definition material. The submitted - planner form includes every resource-impacting field explicitly; a missing - dimension, step/frame count, dtype, device, quantization, or offload field is - an invalid authority request rather than permission to assume a default. -- Multiple independently scoped receipts may coexist in `authorities`. Losing - or replacing one receipt does not silently remove another kind. -- Losing authority never changes the block renderer, boundary, controls, - values, source provenance, or external connections. - -## One renderer and one canvas type - -All composite definitions MUST insert as the existing canonical `block` canvas -type and render through one shared block component. Catalog badges, immutable -source details, qualification status, and save permissions are injected as -capabilities. - -The shared surface includes: - -- header, source badge, name, status, and action placement; -- selection toolbar and keyboard actions; -- width and height resize handles; -- collapsed controls and suggested inputs; -- input and output connector trays; -- generated preview/output presentation; -- expansion into ordinary connected internal nodes; -- internal node movement and resizing; -- error, progress, and retry presentation; and -- accessible labels and focus order. - -The durable root connector trays remain mounted in both collapsed and expanded -states. External workflow edges always terminate on the root's stable public -port IDs; expansion only adds ordinary internal projections and bridge links. -Hiding the root handles while expanded is invalid because it makes existing -external edges point at missing handles and prevents composing the expanded -Block with the surrounding workflow. - -The same connector contract applies recursively. A collapsed internal Block -projects only connections that cross its subtree boundary, using typed visible -handles with runtime-only bindings to the exact hidden leaf sockets. It must -not discard those links, expose links wholly internal to the hidden subtree, -or rewrite semantic endpoints to the container. Resize minimums include the -header and connector tray. Presentation collapse and sizing never filter the -flat graph used by execution. - -The expanded root frame MUST contain the rendered bounding box of every owned -ordinary child. Its canvas width and height are a derived projection of the -durable `internalLayout` plus measured child dimensions and safe header, -connector, and edge padding. `presentation.size` remains the collapsed, -user-resized size; expanding, measuring, or moving one child MUST NOT rewrite -that size, values, effective interface, definition identity, preview state, or -any sibling instance. Collapse MUST restore the exact prior collapsed size. -Any browser lifecycle that claims expansion support MUST assert containment -before and after a child move and again after workflow Save plus browser -refresh; execution/export parity alone is insufficient. - -There MUST NOT be a catalog-specific renderer whose private schema produces a -different visual or graph interface. - -A control and a public input may intentionally share the same logical ID and -internal binding (for example, Qwen `prompt`). The shared renderer MUST derive -two views from the one V2 contract: editable body controls and a connector -tray. It MUST NOT force both through one `NodeData.params` entry, because a -single entry cannot simultaneously behave as a textarea and an input socket. - -The renderer receives capabilities as a computed view, not as another node -schema: - -```ts -type CompositeNodeCapabilitiesV2 = { - editInstanceValues: boolean; - editInstanceStructure: boolean; - configureInterface: boolean; - keepWorkflowOnly: boolean; - saveAsNewUserNode: boolean; - updateReusableDefinition: boolean; -}; -``` - -Capabilities are calculated from definition ownership, instance state, and the -active workspace/user permission boundary. They are not persisted as execution -authority and do not affect canonical graph hashes. A registered definition -normally permits instance edits, workflow-only changes, and **Save as new User -Node**, but never **Update reusable definition**. Registered and user-owned -instances now permit structural editing and Configure Interface when the -workflow/workspace permission boundary allows them; ownership changes only -the reusable-definition save choices. The common connection path derives -public sockets from V2 and commits admitted same-owner internal edge -additions/removals/reconnections copy-on-write. The -safe internal-node deletion path also updates `effectiveGraph` copy-on-write, -clears affected authority, preserves external edges, participates in undo, and -atomically rejects deletion of nodes still referenced by the explicit public -interface, exposed controls, or previews. The pure source-neutral -`addBlockEffectiveGraphNodeV2` reducer can also adopt one persistence-filtered -ordinary semantic node copy-on-write: it validates stable and projection-safe -identity, rejects nested composites, writes explicit execution order and -layout, recomputes graph/customization hashes, and clears authority atomically. -Production gestures route through those reducers: dropping a disconnected -ordinary node adopts it; an edge already connected directly to a declared -root port is translated into an internal edge without inferring a new port; -dropping a compatible ordinary node on an internal projection replaces that -semantic node while preserving compatible edge/public IDs; dragging an -eligible internal node out translates incident links through already-declared -public ports; and React Flow edge reconnection updates either the instance -effective graph or an external workflow edge atomically. Arbitrary external -crossings do not invent an interface: the user disconnects, moves, configures -the explicit interface, and reconnects through the root. Preview-owning and -sealed-control-owning replacements use the stricter identity-preserving path: -the protected semantic ID and binding stay unchanged, all edge/public/control -fields must remain compatible, preview media/display compatibility is checked, -and stale preview run state is cleared. All gestures preserve the wrapper, -participate in undo/redo, invalidate authority on semantic changes, and reject -invalid/nested/incompatible states without a partial mutation. - -### Atomic registered-definition compilation gate - -A registered block may be inserted through V2 only after its exact execution -node schemas and bindings are complete. For example, the real Qwen text-to- -image admission requires dynamic actions for model type and component signals; -an unfinalized static skeleton is not a valid `BlockDefinitionV2` graph. - -The insertion factory MUST resolve the immutable admission/spec/registry, -materialize and finalize an ephemeral persistence-filtered draft, reconcile all -fields and bindings, compile the registered V2 definition/instance, discard the -draft, and then atomically insert one `type: "block"` root. On failure it removes -the draft and shows the exact error. It MUST NOT insert a visible legacy -Cluster first, replace it later, or silently fall back to the legacy renderer. -Legacy `huggingFaceCluster*` field options and projection/receipt markers are -compiler inputs only. The compiler MUST translate their meaning into explicit -V2 controls, boundary, values, provenance, and authority inputs, then omit all -of those legacy-prefixed fields from the source-neutral definition graph. - -Initial routing is gated per exact admission (Qwen text-to-image first). A -pipeline-class-wide switch is not permitted until every route with that class -has completed the same dynamic-finalization proof. - -## Copy-on-write customization flow - -### Parameter or presentation edit - -1. Update the instance value or layout. -2. Preserve the registered definition reference and snapshot. -3. Recalculate only affected execution authority. -4. Save the workflow instance normally. -5. Do not create or update a reusable User Node. - -### Structural edit - -1. Detect an actual graph mutation, not a child drag within the same owner. -2. Clone `effectiveGraph` inside the same instance/history transaction. -3. Apply the mutation and validate typed ports, component/state flow, and - container semantics. -4. Mark `customization.state = "structure_changed"`. -5. Preserve the explicit boundary, controls, values, provenance, layout, - preview, external edges, name, and `instanceId`. -6. Clear incompatible authority receipts and show the resulting Expert/Auto - status without changing presentation. -7. Persist the workflow-only customized instance. - -No destructive Cluster-to-User replacement occurs. - -### Reusable save choices - -After customization the user may choose: - -1. **Keep changes only in this workflow** — persist only the embedded - `BlockInstanceV2`; perform no reusable-definition write. -2. **Save as new User Node** — create a new `BlockDefinitionV2` from the exact - effective graph and existing explicit interface, retain preview bindings - and direct source ancestry, apply current instance values as reusable - defaults for declared controls, assign a new opaque definition ID, and - point only this instance at an embedded snapshot of that definition. -3. **Update existing User Node** — available only when the current reusable - definition has a user-owned source and mutable user ownership; update that - definition explicitly. Other open or saved instances keep their embedded - snapshots until the user explicitly refreshes them. - -A registered definition never offers **Update existing**. - -`BlockPortV2` has no reusable `defaultValue`. Therefore a save-as-new or -update-existing operation MUST fail closed when `instance.values` contains a -public boundary input that is not also a declared control. The UI may instead -keep that value workflow-only, or a later explicit Configure-interface flow -may expose it as a control; the persistence adapter MUST NOT silently drop it. - -The client writes a reusable definition optimistically but rolls the library -state back when the API fails. After a successful write, it verifies the exact -normalized definition response and a semantic signature of the initiating -instance before rebasing. A concurrent semantic edit aborts the rebase. The -target instance retains its ID, values, presentation, and matching preview -state; sibling insertions and snapshots in other open workflows are not -refreshed. A saved V2 User Node can then be clicked or dragged from the Node -Library to create a new independent top-level instance. Focused store/runtime -tests cover rollback, races, sibling/open-workflow isolation, and independent -reinsertion. A mocked browser lifecycle also exercises all three visible save -choices, adoption into the edited User Node, workflow Save/refresh, and the -rebased instance after **Update existing**. The complete release browser suite -remains a separate gate; that does not make this persistence lifecycle pending. - -## Migration plan - -Migration is additive, deterministic, idempotent, and non-destructive. - -### Inventory and backup - -The first non-mutating inventory slice is implemented. Run it from the backend -repository with: - -```bash -./scripts/with-runtime-env.sh ./.venv/bin/python \ - scripts/inventory_legacy_composites.py --data-dir /path/to/data -``` - -The CLI reads only `user-workflows/*.json` and `studio/blocks/*.json` below the -selected data directory and writes the report to stdout. `--compact` selects -canonical one-line JSON. `--fail-on-errors` still prints the complete report -and then exits with status 2 when it contains an error-level issue. The scanner -does not follow symlink files or source directories, enforces bounded file and -aggregate input sizes, and reports unreadable, malformed UTF-8/JSON, unsafe, -or oversized sources instead of modifying or skipping them silently. - -The schema-v1 report has this stable top-level shape: - -```json -{ - "schemaVersion": 1, - "kind": "legacy_composite_migration_inventory", - "mode": "read_only_dry_run", - "boundary": { - "readsSavedWorkflowJson": true, - "readsReusableBlockJson": true, - "writesFiles": false, - "convertsRecords": false, - "deletesRecords": false, - "mergesRecords": false, - "authorizesRecovery": false, - "containsPromptAndParameterValues": true - }, - "sourceLayout": { - "workflows": "user-workflows/*.json", - "reusableBlocks": "studio/blocks/*.json" - }, - "summary": {}, - "workflows": [], - "reusableBlocks": [], - "issues": [], - "reportHash": "sha256:..." -} -``` - -Workflow entries classify legacy Cluster roots and their derived children, V1 -`userBlockSnapshot` roots and children, already-V2 roots and projections, and -ambiguous mixed-authority nodes. Each composite inventory includes its root -and source IDs, canvas position/size, persisted presentation, prompts and -parameters, instance overrides/values, public ports and bindings, derived -child IDs, and external edges. Reusable records classify V1 and V2 definitions -and include their source ancestry, graph IDs/counts, declared ports, prompts, -parameters, and strict V2 validation result. Source SHA-256 values and a -deterministic report hash make two scans comparable without adding a mutable -timestamp. - -Definition resolution against the reusable User Node store is informational. -Embedded workflow snapshots remain authoritative for later migration, and -registered catalog definitions are deliberately reported as external because -this scanner does not read the live catalog. Duplicate IDs, missing or -mismatched owners/endpoints/definitions, invalid V2 records, nested composites, -and mixed authorities produce explicit issues for human review. - -The report contains exact persisted prompts and parameter values and therefore -may be sensitive. Redirecting stdout creates an operator-managed plaintext -copy; the tool itself accepts no report destination and writes no application -file. A clean inventory is not a backup, a conversion plan, execution evidence, -or authorization to recover/delete/merge anything. - -The separately implemented backend preview defaults to -`GET /studio/composite-migrations/preview`. It computes a deterministic -`migrationId` and `planHash` over exact source and proposed-target hashes. -Preview remains read-only. `POST /studio/composite-migrations/apply` requires -that exact identity, literal `APPLY_BLOCK_V2_MIGRATION` confirmation, and an -explicit `allowBlockedCandidates` choice when the plan includes records that -cannot be converted safely. It converts V1 reusable definitions and embedded -V1 workflow instances only. Exact original bytes are stored below -`studio/composite-migrations/{migration_id}/backups` before replacement. - -Registered Clusters remain blocked in the default GET. The additive -`POST /studio/composite-migrations/preview` accepts one strict -`registered_cluster_v2_compiler_supplement` and is still read-only. Each -compiler output is tied to an exact workflow source SHA-256 and legacy -composite hash; embeds a backend-validated `BlockInstanceV2`; matches the -backend-pinned admission's public content hash and canonical SHA-256; and -contains exhaustive one-to-one projection-node, public-port, persisted-value, -preview-output, and absorbed-internal-edge receipts. Durable input/control -values preserve the root and child prompts/parameters exactly. Volatile -`display: "output"` parameters are never treated as durable values; a non-null -media reference must instead map exactly to a matching V2 `previewState`. -The conversion also preserves the root ID, position, size, expanded state, -internal layout, external edge IDs, and outside endpoints. Missing, stale, -duplicate, structurally forked, unpinned, ambiguous, or value-changing output -remains blocked without affecting V1 or other exact conversions. Apply must -resend the same supplement so recomputing the plan fails closed before writes -if the compiler output changed. Multiple exact Cluster conversions in one -workflow are accumulated into one proposed target and therefore one atomic -workflow replacement and exact-byte backup. An invalid sibling remains visible -and byte-for-byte unchanged; an exact sibling may be included in that same -atomic target only after the operator explicitly allows blocked candidates to -remain. - -Status/list endpoints report applied, rolled-back, interrupted, and conflicting -targets. `POST /studio/composite-migrations/{migration_id}/rollback` requires -literal `ROLLBACK_BLOCK_V2_MIGRATION`, verifies every backup/current hash, and -refuses independently changed or missing files. Apply and rollback are -idempotent; a rolled-back plan can be explicitly reapplied. There is no force, -delete, rename, merge, or implicit resume operation. - -Setup → Advanced diagnostics now provides the default client preview and -recovery surface. Its strict parser rejects unknown/missing fields, -inconsistent counters, unsafe target paths, stale identities, invalid hashes, -and conflicting journal actions before rendering them. The review dialog lists -every exact target and blocker path. Apply requires the full literal plus a -separate `allowBlockedCandidates` opt-in when needed; rollback refreshes the -exact journal and requires its own full literal. `403`, `409`, and backend -errors stay visible, and both preview and journals refresh after success. -Nothing auto-applies. Mocked component/API tests exercise these controls -without applying against real user data. Backend compiler-supplement preview, -apply, byte-backup, rollback, idempotency, and HTTP tests are complete and use -only temporary fixtures. The client now offers a visible **Generate exact -supplement** action for exact-current registered receipts. It refreshes the -backend-owned source/composite hashes, reads the raw saved workflow, invokes -the same hidden registered V2 compiler/finalizer as insertion, removes all -transients, populates inspectable JSON, and POSTs the exact parsed object for a -final read-only preview. Apply reuses that identical object. Generation exposes -progress/cancel state, aborts on workflow replacement, and warns that dirty -tabs are not part of the saved backend bytes. The paste path remains available -for externally produced exact evidence. A disposable mocked browser lifecycle -proves Generate → Preview → Apply → List/Status → Rollback without writing a -real store. - -The supported generator remains fail-closed for missing execution receipts and -historical manifest hashes without checked-in review authority. A strict -semantic-equivalence receipt can now name one exact destination compiler route; -the client validates the previewed receipt metadata, compiles through a detached -destination-compatible root, and references only its ID/hash. The backend -reloads the checked-in receipt, checks both complete identities plus exact V2 -graph/interface pins, and repeats every instance-preservation check. Matching -only by definition/admission name is forbidden. The semantic-equivalence -ledger currently has zero receipts. A separate exact-body ledger now registers -six archived manifest/admission/Studio tuples covering 42 instances in the -read-only recovery audit and retains one HunyuanVideo 1.5 body as non- -convertible because its historical execution admission is absent. These bodies -are evidence, not compiler mappings. The separately reviewed mapping ledger now -covers 15 instances across three identities; 339 instances across 97 identities -remain blocked. All workflow bytes remain unchanged until exact supplement -preview and explicit Apply. The inventory CLI remains read-only and is not -conversion authority. - -A separate recovery-audit-only lane now preserves the evidence that can be -recovered safely without weakening that boundary. Two byte-pinned, ignored -frontend captures yield 18 strict Studio execution-spec bodies that match 20 -historical manifest identities, 21 manifest/admission/Studio-spec tuples, and -65 instances. The checked-in ledger contains only those public bodies, source -byte receipts, and collision-resistant canonical body hashes; the extractor -never ingests workflow paths, instance IDs, prompts, parameter values, or full -browser state. A schema-v4 recovery audit and -`GET /studio/composite-migrations/recovery-audit` expose the recovered body and -an empty/manual partial-review status alongside the still-missing manifest, -interface, block hierarchy, artifact authority, compiler mapping, and semantic -equivalence. Neither `verified_partial_evidence` nor `rejected_evidence` is -accepted by preview, conversion, compiler supplementation, or execution. - -### Legacy registered Cluster migration - -- Convert the wrapper to the common `block` canvas representation while - preserving its canvas ID. -- Resolve the exact registered definition and embed a V2 snapshot. -- Map legacy controls and explicit Cluster ports by stable binding, not label. -- For a collapsed root whose persisted port/preview metadata references a - deliberately absent execution child, resolve only the deterministic - historical child ID derived from an exact unique V2 `semanticRole`; reject - missing, duplicate, or conflicting roles without inference. -- Preserve instance values, external edges, position, dimensions, preview, and - execution provenance. -- Preserve internal layout when available; otherwise use deterministic default - layout without affecting graph hashes. -- If revision or content hash cannot be resolved, retain a visible - migration-required block. Never substitute current defaults. - -### Legacy Cluster-derived User Node migration - -- Compare its effective graph and values with its recorded registered parent. -- When it is semantically unchanged, offer **Restore registered source** while - preserving its instance values, preview, layout, and external edges. -- When it is structurally changed, migrate it as a workflow-owned or - user-owned V2 block with source ancestry and its existing explicit interface. -- Never recalculate its interface during migration. -- Audit test-generated reusable definitions separately. Deletion requires a - reviewed list and explicit approval. - -### Compatibility window - -- Readers support V1 and V2 during one release window. -- All new writes use V2 after the migration gate is enabled. -- A legacy export remains importable through the V1-to-V2 migrator. -- Removing V1 support requires fixture coverage and a separately documented - release decision. - -## Implementation sequence - -### Phase 0 — containment and golden failures (2–4 hours) - -- Prevent moving an already-owned Cluster child from triggering conversion or - `/studio/blocks` persistence. -- Add the exact two-Qwen-instance regression described below and make it fail - before broader refactoring. -- Stop automatic reusable-definition creation on structural edits. - -### Phase 1 — V2 contracts and canonicalization (6–10 hours) - -- Add runtime validators and TypeScript types for `BlockDefinitionV2`, - `BlockInstanceV2`, boundaries, controls, source, preview, and authority. -- Add canonical hashing and explicit missing-versus-empty value handling. -- Create adapters from current registered Cluster definitions and current User - Node definitions into the common schema. -- Add JSON round-trip and invalid-contract tests. - -### Phase 2 — shared renderer and store actions (8–12 hours) - -- Add the pure V2 runtime projector/reducer and persistence canonicalizer - before routing any production definition through it. -- Route every composite through the common canvas type and renderer. -- Make dimensions, actions, controls, previews, and port trays capability- - driven rather than source-kind-driven. -- Keep body-control and connector projections separate even when they share a - logical ID and binding. -- Store internal layout overrides by stable internal node ID. -- Remove renderer-specific connection and serialization branches. - -### Phase 3 — copy-on-write structure and interface editing (6–10 hours) - -- Classify layout versus topology gestures correctly. -- Apply structural mutations to instance `effectiveGraph` without replacing - the wrapper. -- Preserve explicit boundaries and existing external edges. -- Add the instance effective-interface contract, migration, explicit Configure - interface operation, and edge-impact preview before enabling its capability. -- Connect the three reusable save choices to V2 definitions. - -### Phase 4 — authority and execution integration (4–8 hours) - -- Add and synchronize the additive multi-target control/public-input binding - contract used when one reviewed logical value fans out to multiple ordinary - graph fields; validate every target and apply one instance value/edge to all - of them. -- Calculate reviewed/Auto authority from definition, effective graph, - execution parameters, immutable artifacts, and admission. -- Keep non-Auto graph execution independent of catalog publication authority. -- Reuse the same compiled graph for collapsed and expanded views. -- Show precise preparation, dependency, resource, and worker-failure issues. - -### Phase 5 — migration and recovery (6–12 hours) - -- [x] The deterministic read-only inventory/dry-run report is implemented. -- [x] Backend V1-to-V2 readers and a separately authorized conversion preview - are implemented. -- [x] Safe V1 workflow migration preserves canvas IDs, values, explicit ports, - internal layout/effective topology, external edge IDs, and a materialized - effective interface with synchronized base/effective hashes. -- [x] Exact byte backups, stale-plan detection, idempotent apply, recovery - status, fail-closed rollback, and reapply are implemented and tested. -- [x] Connect the preview/recovery UI with literal-confirmation apply/rollback, - exact target/blocker display, visible conflicts, and no automatic writes. -- [x] Connect exact registered-catalog compiler output to a strict backend - supplement preview/apply path with source/pin/mapping validation, exact - backups, rollback, idempotency, and preserved ambiguous records. -- [x] Accept, strictly parse, preview, review, and reuse an exact - compiler-produced supplement in the visible migration UI; never send or - apply it automatically. -- [x] Generate exact-current compiler supplements through a visible reviewed - action with saved-byte input, progress/cancel, inspectable JSON, final - backend re-preview, identical-object Apply, and disposable browser - generate/apply/list/rollback coverage. -- [x] Register the six recovered archived definitions with exact historical - admissions in the read-only recovery audit; retain the seventh body-only - record as non-convertible and never infer compatibility from a current - definition/admission name. -- [x] Add the strict historical compiler-mapping ledger, loader, compiler- - supplement reference, preview/apply validation, journal evidence, exact - backup/refresh/rollback path, tamper rejection, and visible browser - lifecycle. Three independently compiled destination mappings are now - sealed for Wan 2.2 TI2V 5B, MiniMax Music 3, and Transformers CTC speech - recognition. They cover 15 saved historical instances and make those - instances eligible for exact compiler-supplement preview; they still do - not mutate a workflow without literal Apply authority. -- [ ] Isolate and seal Qwen plus the two SDXL destination mappings. Historical - instance compilation currently publishes a different dynamic schema than - an independent current registered insertion, so those mappings remain - blocked rather than letting historical values redefine the destination - BlockDefinitionV2. HunyuanVideo 1.5 remains body-only and cannot receive - a mapping without its missing historical execution admission. - The principled implementation path is to compile the destination once - from the exact current registered route and its current default binding - form, validate that immutable definition against all destination pins, - and only then map historical values into declared boundary/control IDs on - `BlockInstanceV2.values`. Historical values must never participate in - destination dynamic-field publication. Unknown values, values requiring - a graph-parameter rewrite, type-incompatible values, and values whose - target is absent from the frozen current interface must remain blocked. - The backend now enforces this final boundary explicitly for archived - compiler mappings; focused tests prove graph-target rejection, unchanged - destination definition/effective graph, cross-workflow isolation, exact - backup, and byte-identical rollback. Qwen/SDXL can be sealed only after a - clean-current compiler produces the reviewed definition and a separate - value adapter proves every retained historical source maps to that frozen - interface without schema publication. -- [x] Verify the complete browser restart and mixed V1/V2 workspace behavior. - The mocked browser fixture persists one V1 User Node and one V2 User - Node in the same workflow, reloads the full application, proves that - neither authority cross-converts, checks both prompt values and exact V2 - content identity, and expands both through the shared canvas entry. - -### Phase 6 — real frontend qualification and release regression (6–10 hours) - -- [x] Run the bounded Qwen image golden flow through the real frontend and - backend in Expert and Auto modes. -- [x] Qualify one current V2 video and one current V2 audio registered Block - through the same real frontend lifecycle. Existing pre-V2 or unrelated - media evidence does not satisfy this gate. -- [x] Complete the final post-change client checks/build/bundle, mocked browser - suite, clean-browser smoke, and Git/evidence audit. On 2026-09-02 the - complete `npm run check` gate, 124-case mocked Studio suite, two-case - shared-control suite, focused live-backend Graph Fix smoke, and direct - production-build clean-browser reload all passed. The two worktrees have - 414 source/evidence status entries rather than generated-test explosions; - no model/media/archive/test-report/temp artifact is unignored, and both - worktrees pass `git diff --check`. The combined V2 contract gate, full - backend suite, migration UI gate, and route-ledger validations have also - passed independently. Publication readiness remains a separate evidence - gate and is not implied by this regression result. - -Estimated implementation time is 4–6 focused working days, assuming migration -does not uncover ambiguous user-created legacy records. A Qwen-only demo gate -should be reachable in approximately 1.5–2 focused days. - -### Phase 7 — generic registered route sets without fake generic definitions - -The composite engine is source-neutral, but a reviewed execution definition is -necessarily exact to one pipeline class, workflow, admission, repository, and -immutable revision. A Qwen loader value therefore must not be edited into a -FLUX loader inside the same definition: doing that would retain stale graph, -interface, artifact, resource, and Auto authority. - -The generic user experience is an instance-owned registered route set around -one active exact `BlockDefinitionV2`: - -- [x] Add a strict optional `routeSelection` contract to `BlockInstanceV2` and - synchronized client/backend validators. It records the route-set ID, - selected route key, and bounded inactive per-route drafts; drafts exclude - instance IDs, previews, tasks, authorities, and recursive route sets. -- [x] Register the initial Diffusers text-to-image route set with exact Qwen - Image, FLUX, and Stable Diffusion XL routes. Route-set membership is - catalog metadata and must never weaken the existing per-route - hash/admission checks. -- [x] Extend registered generic selection only where the reviewed definitions - retain the same task semantics and a stable compatible public output. - The current exact sets are text-to-image (3 routes), image-to-image (4), - instruction edit (5), inpainting (3), control image (2), text-to-video - (9), and image-to-video (7). Every member resolves through the immutable - definition/admission registry; no model name is substituted into another - model's graph. -- [x] Compile the destination route in the existing transient exact compiler, - preflight every connected public port, and atomically replace only the - active definition/graph/interface/value projection while preserving the - root instance ID, canvas position, size, and compatible external edges. -- [x] Preserve each inactive route's values, effective graph/interface, and - internal layout. A first visit carries only explicitly portable semantic - values (`prompt` and `seed` initially); revisiting a route restores its - prior draft byte-for-byte. Switching clears previews and all execution, - resource, Auto, and publication authority. -- [x] Expose the selector in the shared `BlockNodeV2` renderer for both - registered and customized workflow instances. Customized instances must - offer keep-as-workflow-draft, save-active-route-as-new-User-Node, or - cancel; no registered definition is overwritten implicitly. -- [x] Cover Qwen → FLUX → Qwen → SDXL → Qwen restoration, save/refresh/app restart, - multi-instance isolation, one-step undo/redo, compatible-edge retention, - incompatible-port refusal, exact active-route execution export, and real - frontend generation for the selected Qwen, FLUX, and SDXL routes. - -Completed on 2026-09-03: the shared renderer exposes the exact Qwen Image 2512, -FLUX.1 Dev, and Stable Diffusion XL 1.0 routes. A customized active route can remain as an inactive -workflow draft or be saved as a new User Node before the same root switches; -the latter passed a live backend write/delete lifecycle without rebasing the -workflow insertion during the save. The strengthened browser proof covers two -independent roots; Qwen → FLUX → Qwen → SDXL → Qwen draft restoration; -undo/redo; save/refresh; and a fresh-browser reopen after a full backend -restart. Execution expansion and the submitted executable node -map contain only the selected route; the full workflow snapshot intentionally -retains inactive drafts as non-executable provenance. Current-V2 Qwen, FLUX, -and SDXL routes completed real visible-frontend generation with pinned Hub -artifacts. The FLUX route-set proof used BF16, no quantization, model CPU -offload, 768×768, 20 steps, and seed 271828; its fresh review evidence is under -`review-pending/qwen-flux-generic-v1-20260903/qwen-flux-route-switch-v1/`. -The SDXL proof used FP16, no quantization, model CPU offload, 1024×1024, -30 steps, guidance 5, and seed 161803; its isolated current-run evidence is -under -`review-pending/qwen-flux-sdxl-generic-v1-20260903/qwen-flux-sdxl-route-switch-v1/`. -The exact checked production build was then copied into the backend `web/` -tree. A clean headless browser opened the retained backend workflow, rendered -both V2 roots, and exercised the deployed route selector without a JavaScript -page error. The retained manual-demo workflow is -`O6Xsgtn29NIMNuBP9zi8l` (`Demo — Generic Qwen / FLUX Image Block`). - -Expanded on 2026-09-03: exact compatible model selection now also covers -Qwen/FLUX/Anima/SDXL image-to-image; Qwen Edit/Edit Plus/FLUX Kontext/FLUX.2 -Klein/Klein Base instruction editing; Qwen/Qwen Edit/SDXL inpainting; -Qwen/SDXL control image; and Wan 2.1/Wan 2.2/LTX/LTX-2/HunyuanVideo 1.5/ -Helios/Helios Pyramid/Helios Pyramid Distilled/Cosmos 3 Omni text-to-video. -Image-to-video covers Cosmos 3 Distilled/Omni, HunyuanVideo 1.5, LTX/LTX-2, -and Wan 2.1/2.2. The three current Helios image-to-video definitions are -excluded because their reviewed public boundaries expose only source height -and width rather than a video output; they must be corrected and recompiled -before they can safely join a generic video-output set. -The registry audit proves that each entry maps to one exact registered route, -that a route is not ambiguously assigned to multiple sets, and that every set -retains a stable shared output type. A production-browser test inserts a fresh -Block for all seven sets and verifies every visible option. A second browser -test performs real Qwen Edit → FLUX Kontext → Qwen Edit and Wan → LTX → -Wan round trips while preserving the workflow prompt. - -Exact inactive drafts whose compiled definition ID, content hash, and canonical -SHA-256 still match the current registry are now restored without rebuilding -the hidden transient Modular graph. The restored value is still passed through -the complete `BlockInstanceV2` validator, receives a fresh execution-neutral -instance projection, and has previews and authorities cleared. Missing or stale -drafts continue through the full compiler/rebase path. The three-route lifecycle -fell from 44.5 seconds to 33.6 seconds, while the new edit and video round trips -complete together in 16.4 seconds on the qualification host. - -## Required tests and acceptance evidence - -### Primary two-instance browser regression - -1. Insert two identical Qwen registered blocks. -2. Assert byte-equivalent V2 definitions and equal rendered controls, actions, - port IDs/types/order, size behavior, and starter prompt. -3. Run the left instance and open its generated asset. -4. Expand it and move one existing owned internal node. -5. Collapse it. -6. Assert it is still the same `block` instance with the same definition - reference, controls, values, ports, name, authority, and external edges. -7. Assert only preview/run state and internal layout may differ. -8. Assert no reusable User Node API request occurred. -9. Save, refresh, reopen, and repeat the equality assertions. -10. Run again through the visible frontend. - -### Structural customization tests - -- Adopt a compatible ordinary node, reconnect it, collapse, Save, refresh, - re-expand, and Run. -- Preserve wrapper ID, public ports, external connections, values, layout, and - preview while authority changes predictably. -- Reject invalid type, state, component, and container connections with a - useful message. -- Undo/redo movement, adoption, reconnection, deletion, and replacement. -- Verify all three save choices and multi-instance isolation. -- Confirm a registered definition cannot be overwritten. - -### Schema and migration tests - -- Valid and invalid V2 fixtures for every source kind and boundary mode. -- Stable canonical hashes across JSON key order and presentation changes. -- Explicit empty values survive round trip and are not replaced by defaults. -- Stable port identities preserve external edges across renderer migration. -- V1 Cluster, V1 User Node, modified derived node, missing source, and stale - revision fixtures. -- Read-only inventory output and `reportHash` are repeatable for identical - inputs; caller-owned values and scanned workflow/block bytes and mtimes remain - unchanged. -- A future conversion preview is deterministic and a future authorized - migrator is idempotent; applying it twice produces no additional changes. - -### Cross-modality tests - -- One registered image, video, and audio block use the same composite renderer - contract. -- Each exposes a task-appropriate explicit boundary and creator input examples. -- Each survives edit, Save, refresh, expansion, layout movement, collapse, and - real frontend execution without value reset. - -## Definition maintenance requirements - -Any change to `BlockDefinitionV2` or one of its nested contracts MUST include, -in the same change: - -1. the client type and strict validator in - `MoDiff-client/src/studio/blockSchemaV2.ts`; -2. the synchronized backend type and strict reusable-definition validator in - `MoDiff/modiff/block_definition_v2.py`; -3. matching client and backend canonical serialization and hashing updates; -4. a schema fixture for each affected source/boundary variant; -5. an explicit migration or a proof that the change is backward-compatible; - when V1 conversion semantics are affected, update both - `migrateUserBlockDefinitionV1` in the client and - `migrate_user_block_definition_v1` in - `MoDiff/modiff/composite_migration.py` and prove the same graph/definition - hashes; -6. registered-source adapter changes in - `MoDiff-client/src/studio/registeredBlockAdapterV2.ts` when the field is - produced from the Hugging Face catalog; -7. updates to this document and linked API documentation; and -8. round-trip, cross-runtime hash, migration, and browser coverage appropriate - to the field. - -The minimum synchronized test set is: - -- `MoDiff/scripts/verify-block-v2-contract.sh` is the single local gate for - the client schema, registered adapter, runtime, renderer, capability policy, - reusable-definition persistence, backend definition-store, workflow-store, - migration inventory, conversion, transaction, recovery, and HTTP suites. - `MODIFF_CLIENT_ROOT` may point it at a non-sibling client checkout; - -- `MoDiff-client/scripts/block-schema-v2.test.mjs` for strict definition and - instance validation, hash stability, explicit-empty values, V1 reading, and - source/boundary variants, including cross-direction public-port uniqueness - and deterministic effective-interface migration/hash validation. The - current client schema gate contains ten focused tests, including exact - client/backend V1 effective-interface hash parity and cross-runtime mirrored - control/public-input hash parity; -- `MoDiff/tests/test_composite_migration.py` for the synchronized V1 reader, - workflow identity/value/interface/layout/edge preservation, exact backups, - stale-plan rejection, idempotency, and fail-closed rollback whenever schema - or migration semantics change; -- `MoDiff-client/scripts/composite-migration-ui.test.mjs` for strict preview, - journal, status, and mutation-response validation; exact apply/rollback - request authority; visible `403`/`409` errors; exact-path rendering; and - confirmation/blocked-candidate gates against mocked APIs only; -- `MoDiff-client/scripts/registered-block-v2-adapter.test.mjs` for stable - semantic graph IDs, exact registered provenance/interface compilation, and - separation of definition state from instance values/layout/run state; -- `MoDiff-client/scripts/registered-block-v2-route-audit.test.mjs` for the - current live `/huggingface/node-library` schema-v6 inventory. It compiles - every graph-qualified admission against its exact Studio receipt and live - node fields, executes every declared dynamic field action in declaration - order using only sealed catalog/default inputs, replays the browser's exact - shallow publication merge, repeats each witness to reject nondeterminism, - requires every compiler success to have one explicit route, - pins the current 90/90 split (82 Diffusers, eight Transformers), verifies - both the public definition content hash and canonical-definition SHA-256, - and rejects catalog-hash, revision, artifact, spec, admission, action, and - boundary drift; -- `MoDiff-client/scripts/block-runtime-v2.test.mjs` for single-authority root - persistence, separate control/connector projections, explicit-empty values, - internal layout, copy-on-write mutations, authority invalidation, and - execution expansion. It must also fail closed for spoofed, orphan, or stale - projections; missing control, sealed-control, boundary, or preview bindings; - incompatible internal handles/types; unsupported node types; duplicate - identities; and projection-ID collisions. Its current 24 focused tests also - cover mirrored value/edge fan-out, root-to-root fan-out, identical - collapsed/expanded execution expansion, and fail-closed - readiness/export behavior. A runtime-only green test does not imply that - broad registered catalog insertion or Auto has been routed through V2; -- `MoDiff-client/scripts/composite-block-capabilities-v2.test.mjs` when - ownership, permission, save-action, or effective-interface behavior changes. - `configureInterface` resolves only from a valid definition/instance pair and - an explicit workspace interface-edit permission; -- `MoDiff-client/scripts/block-persistence-v2.test.mjs` for all three save - choices, registered-update rejection, exact reusable graph/interface/default - preservation, boundary-only value fail-closed behavior, API rollback, - semantic save races, sibling/open-workflow isolation, and independent User - Node reinsertion. Its current focused gate contains nine tests; and -- `MoDiff/tests/test_studio_blocks.py` for backend canonical hashes, fail- - closed V2 validation, exact round-trip persistence, registered-source write - rejection, V1 compatibility, and cross-direction public-port uniqueness. The - current backend contract gate contains ten tests with 26 subtests. - -Changing only one canonicalizer is a contract defect even when its local tests -pass. Before merging a V2 field or hash change, at least one shared fixture -MUST be hashed by both runtimes and produce the same exact strings. - -An instance-only `BlockInstanceV2` change does not require inventing a backend -definition field. It MUST still update the client type, strict instance -validator, workflow serialization/migration, schema fixtures, and lifecycle -tests together. If backend code starts interpreting that instance field, its -validator and tests join the same synchronized-change rule. - -Field removal or semantic reuse requires a new schema version. Optional fields -may be added within V2 only when old readers fail closed or ignore them without -changing execution, interface, values, or authority. A field name MUST NOT be -reused with a new meaning. - -Definition generators MUST validate before publication and produce stable -canonical hashes. Client code MUST consume declared definitions; it must not -contain model-family branches that reconstruct boundaries or controls. - -## Status checklist - -### Contract and documentation - -- [x] Corrected one-composite product contract documented. -- [x] `BlockDefinitionV2` and `BlockInstanceV2` semantic contracts documented. -- [x] Boundary, provenance, authority, migration, and maintenance rules - documented. -- [x] Legacy visual/demo plans marked as superseded where they conflict. - -### Functional implementation - -- [x] Moving an owned registered-block child is layout-only and cannot create - or persist a User Node. -- [x] Mocked two-instance Qwen regression preserves root identity, controls, - ports, values, renderer class, and performs zero `/studio/blocks` writes. -- [x] Common client `BlockDefinitionV2`/`BlockInstanceV2` types, strict - validators, canonical hashes, explicit-empty value behavior, and V1 - User Node reader are implemented and covered by focused tests. -- [x] Backend `BlockDefinitionV2` types, strict validation, canonical hashes, - user-owned V2 `/studio/blocks` round trips, registered-source write - rejection, and V1 compatibility are implemented and tested. -- [x] The client and backend parity fixture asserts the same fixed - `block-graph-v2-d23b144c` and `block-definition-v2-dde5d4d9` hashes. -- [x] The generic registered Diffusers/Transformers adapter produces stable - semantic graph IDs, explicit ordered boundaries and controls, creator - suggestions, preview bindings, immutable provenance, and independent - instance values/layout without hashing volatile run or UI state. -- [x] The source-neutral capability resolver is implemented and fail-closed; - registered definitions cannot be updated, while structural editing and - Configure Interface are permission-gated equally for registered and - user-owned workflow instances. -- [x] The pure source-neutral V2 runtime/projector and persistence canonicalizer - create one `block` root from `BlockInstanceV2`, keep controls separate - from connector trays, project ordinary internal nodes and links, preserve - explicit-empty values and presentation-only layout, and canonicalize an - already-V2 workflow without persisting derived children. This slice is - implemented with fail-closed projection, binding, type, handle, duplicate, - and collision validation and is covered by focused tests. All 90 exact - registered admissions currently use this route. -- [x] Already-V2 roots use React Flow `type: "block"`, the `BlockNode` entry, - and the same `BlockNodeFrame` used by legacy User Nodes. Focused tests - cover action placement, resize minimums, controls, explicit connector - types, independent values, expansion, parent-relative layout, - duplication, and non-history refresh rematerialization. All 90 exact - registered admissions named by the live route table are routed to this - branch. -- [x] The expanded V2 wrapper derives its temporary canvas bounds from every - owned ordinary child's persisted layout and measured dimensions while - retaining the exact durable collapsed size. A focused runtime invariant - and the real-backend Qwen text-to-image browser lifecycle assert that - all five Modular Diffusers children remain inside the root before and - after moving the prompt node and after Save/refresh/re-expansion. The - same lifecycle proves unchanged definition/interface/value authority, - collapsed/expanded API export parity, independent sibling state, and - successful registered-Block and saved-User-Node execution. -- [x] Every one of the ten visible Qwen catalog Blocks passes a real-browser - empty-graph insertion/expansion contract: the library row is draggable, - insertion creates one V2 root, every projected Modular Diffusers node is - visually contained by that root, and collapsing leaves the serialized - instance byte-identical. The same family gate requires at least one - explicit public input and output, a non-empty connected internal graph, - and exact preservation of the publisher-sourced starter prompt whenever - the upstream definition provides one. A definition with multiple reviewed admissions - uses its first explicitly ordered admission as the deterministic empty- - graph default; an explicit matching Studio task/model may select another - admission. Selection still fails closed unless the exact registered V2 - route, immutable artifact, and execution receipt match. -- [x] Fresh registered compiler values are now explicitly marked as the - insertion baseline rather than workflow customization. Runtime edits use - the same default comparison, so changing only a prompt changes only that - value and the customization marker, while restoring the reviewed value - can make the instance clean again. Qwen Edit Plus exposes separate - single-image and multi-reference choices in the shared instruction-edit - route selector; exact admission lookup no longer assumes one admission - per catalog definition. The complete ten-entry real-browser family gate - exercises the multi-reference switch and asserts every new root begins - unchanged. -- [x] Already-V2 public inputs and outputs are resolved from the explicit V2 - boundary throughout connection lookup, type/color rendering, pane drop, - connect/remove, status, and signal paths without copying socket state - into root `params`. External edges stay on the durable root; projected - children accept only same-instance internal connections. Internal edge - additions/removals update that instance's `effectiveGraph` copy-on-write. -- [x] API export, run readiness, and run-target resolution expand already-V2 - roots into the same concrete graph in collapsed and expanded views. A - malformed V2 graph produces a blocking - `composite_execution_graph_invalid` issue even in Expert mode, and the - wrapper/instance contract is never submitted as an executable node. -- [x] Concrete V2 WebSocket outputs and progress resolve deterministically to - the owning collapsed root and declared preview binding. Generated media, - task ID, and status update only that instance's `previewStates`; root - `params`, definition state, sibling instances, and undo history remain - unchanged. The focused renderer and run-coordinator gates cover this - route. -- [x] The exact Qwen Image text-to-image catalog admission now fetches one - build-time generated, hash-pinned V2 entry and atomically returns one - source-neutral `block` root. Fresh insertion creates no compiler nodes, - runs no field actions, imports no optional runtime, accesses no Hub, and - loads no model. Source identity and canonical SHA-256 are checked by - both runtimes before graph mutation. The same generated-catalog gate now - admits all 90 pinned routes (82 Diffusers and eight Transformers). The - hidden dynamic-field compiler remains only as a bounded, receipt-bound - V1 recovery fallback; timeout, stale-hash, and workflow-replacement tests - prove atomic cleanup with no partial graph mutation. -- [x] The exact Qwen Image text-to-image mocked browser lifecycle covers - catalog drag, one durable V2 root, creator prompt and explicit public - ports, five linked ordinary internal nodes, internal layout movement, - collapse, value edit, workflow Save/reload, stable root/definition/ - interface/value/layout authority, and zero legacy Cluster receipts or - `/studio/blocks` writes. This is not a real-backend generation claim. -- [x] Deleting an eligible expanded V2 internal node is an atomic - copy-on-write structural edit with incident-edge/execution-order/layout - cleanup, authority invalidation, undo/redo, and Save/refresh - rematerialization coverage. Deletion fails visibly and without partial - mutation when the node still owns a public port, exposed control, or - preview. Deleting the root still removes the whole composite. -- [x] The pure source-neutral V2 runtime can add/adopt one already-filtered - ordinary semantic node copy-on-write with stable ID, nested-composite, - duplicate, reserved-projection, execution-order, layout, graph-hash, and - authority-invalidation checks. One safe store/canvas gesture now adopts - a disconnected top-level ordinary node into an expanded V2 Block with - exact relative layout, one-step undo/redo, canonical Save/refresh - rematerialization, unchanged explicit ports, and visible atomic - rejection. The renderer/store suite has 16 focused tests and a mocked - real-pointer browser lifecycle covers both success and connected-node - rejection. A second mocked real-pointer registered-V2 lifecycle covers - compatible replacement, incompatible atomic rejection, declared-public- - edge adoption, Configure Interface edge-impact rejection and explicit - mirror fan-out, internal and external reconnection, move-out through an - existing public port, undo/redo, collapse, Save, refresh, re-expansion, - and zero reusable User Node writes. Move-out now records and excludes - its source Block from later target scans in the same drag-stop gesture, - preventing the ordinary materialized node from being immediately - re-adopted while still permitting a direct drop into another Block. -- [x] The legacy two-Qwen containment regression covers independent insertions, - one-instance prompt editing, child-layout movement, Save, refresh, - rematerialization, and zero `/studio/blocks` writes. It is migration - safety evidence, not shared-renderer completion. -- [x] Complete the current Qwen golden lifecycle as two bounded proofs. The - real-backend frontend proof covers two-instance isolation, edited values, - internal layout persistence, collapsed/expanded concrete-graph parity, - Expert execution/output viewing, and one exact Auto execution. The mocked - real-pointer proof covers the structural-edit gestures listed above - through the production V2 renderer/store route. This does not claim that - the structural gesture fixture generated a real model output. -- [x] Route every admitted registered definition through V2 insertion and - saved workflow loading instead of retaining the legacy `cluster` - wrapper. All 90 admitted/graph-qualified routes pass the exact live - generated-catalog gate (82 Diffusers and eight Transformers). Control/public-input - fan-out and exact terminal-output projection use additive source-neutral - V2 mirror contracts. Every future route MUST continue to fail closed - until its own exact semantic boundary, fully finalized dynamic schema, - content hash, and canonical SHA invariants pass. -- [x] Keep discovery-only or route-mismatched catalog definitions visible and - structurally insertable through the shared V2 renderer, but without Run, - Auto, or publication authority. Executable catalog projection still - requires an exact registered V2 route; no new interaction can create a - legacy `cluster` root as a fallback, and route drift leaves the - canvas and undo history unchanged. -- [x] Bind every remaining catalog-only definition to one immutable review - record through `huggingface-cluster-catalog-gates.v1.json`. The original - audit covered 28 workflows across seven families. Fourteen have since - received exact V2 route admissions; the current catalog-only remainder - is 14 workflows across five families: Cosmos 3 (five), LTX-2.5 (four), - Krea 2/Turbo (two), Stable Diffusion 3 (two), and Ideogram 4 (one). The - ledger validates each evidence-file SHA-256 and unresolved artifact, - access/license, runtime/resource, or guardrail gate. The current eligible - route count remains zero, so none of those 14 is mislabeled draggable - before its own exact route contract is admitted. -- [x] One canonical `block` canvas type and one shared renderer for registered - and user-owned blocks is in production. Registered versus User Node is - catalog ownership/provenance only; it does not select a second renderer, - canvas type, control surface, connector path, or structural editor. -- [x] Copy-on-write structural editing preserves the wrapper: connector - add/remove/reconnect, safe deletion, disconnected adoption, declared- - public-edge adoption, compatible node replacement, and move-out through - existing public ports are integrated with undo/redo and atomic failure. - Arbitrary crossings intentionally require disconnect → move → Configure - Interface → reconnect. Preview/sealed-owner replacement is supported only - through the compatible identity-preserving route, so immutable bindings - and sealed values are never rebound. The focused runtime and mocked - browser lifecycles qualify protected replacement, atomic rejection, - undo/redo, and Save/refresh persistence without broadening those limits. -- [x] Explicit, stable boundary preservation and Configure Interface flow use - the validated instance `effectiveInterface`; connected-port removal, - invalid bindings/types, sealed-control mutation, and stale hashes fail - closed. -- [x] The three explicit V2 persistence choices are wired: workflow-only makes - no reusable-definition write; save-as-new preserves the exact effective - graph, explicit interface, declared current defaults, preview bindings, - and parent provenance; and update-existing is restricted to mutable - user-owned definitions. API failures roll back the User Node library, - semantic save races abort target rebasing, sibling and other-open- - workflow snapshots remain unchanged, and saved V2 User Nodes reinsert by - click or drag as independent instances. Boundary-only valued inputs fail - closed because `BlockPortV2` has no reusable default. Focused tests cover - failure, race, isolation, and reinsertion semantics; a mocked browser - lifecycle exercises all three visible choices and refreshes the updated - instance. -- [x] Finish the primary real-backend reviewed-execution/Auto browser - qualification for the exact current Qwen Image text-to-image route. - Already-V2 manual execution expansion and fail-closed readiness are - implemented. Routed registered V2 instances now request a short-lived, - instance-local Auto receipt from the backend. The backend strictly - validates the normalized instance, admission, immutable artifact, and - complete resource form; recomputes graph/interface/value hashes; and - verifies both the public V2 definition hash and a canonical-definition - SHA-256 pin. Structural/interface customization fails Auto without - weakening independent Expert/manual execution. The 2026-09-02 live - browser proof now covers catalog drag, exact V2 definition pin, edited - instance values, instance-local Auto receipt, unchanged values after - authority, five-node concrete submission, completed backend execution, - frontend preview update, generated image retrieval, and post-run browser - capture. It also exposed and fixed a backend receipt timestamp that did - not round-trip through the shared strict V2 validator. This proof is for - the exact Qwen route; it does not claim that every registered route has - received a real model run. -- [x] Deterministic, machine-readable legacy inventory/dry-run report for saved - workflows and reusable User Node records. It identifies legacy Cluster, - V1 User Node, and already-V2 authorities plus children, interfaces, - values, topology, definition resolution, and ambiguity without modifying - either store. Focused pure/filesystem/CLI tests pass; this is inventory - evidence only and does not authorize migration or recovery. -- [x] The backend now has a deterministic V1 User Node definition/instance - reader, read-only conversion preview, explicitly authorized safe-target - application, exact byte backups, idempotency, transaction status, and - fail-closed rollback. Root IDs, values, declared ports, internal layout/ - topology, and external edge IDs are preserved. Legacy registered - Clusters remain blocked by default; an exact pinned catalog compiler can - now supply a strict source-bound supplement whose V2 instance, public and - canonical definition pins, one-to-one node mappings, explicit port - bindings, all persisted values, absorbed projection edges, layout, and - external edge rewrites are validated before the candidate becomes - convertible. The backend never reconstructs execution authority from - presentation children. -- [x] Add the migration preview/recovery UI in Setup → Advanced diagnostics: - strict typed preview/journal/mutation parsing, exact target and blocker - paths, literal-confirmation review dialogs, explicit blocked-candidate - opt-in, visible `403`/`409`/backend errors, refreshed state after actions, - and mocked tests that never mutate real user data. There is no automatic - apply or force rollback. -- [x] Add the backend registered-Cluster compiler handoff with deterministic - read-only preview, exact-supplement apply, byte backup, rollback, and - tempfile-only tests. No delete, rename, or merge behavior exists. -- [x] Add visible exact-current client supplement generation, receipt review, - final read-only submission, identical-object Apply, and recovery UI; - keep the default preview blocked and never auto-apply. -- [x] Add the fail-closed historical-manifest semantic-equivalence mechanism: - a checked-in, strictly hashed receipt ledger pins the complete old - manifest/admission/Studio-spec identity and reviewed historical graph/ - interface hashes to one exact current manifest plus - `BlockDefinitionV2` content/canonical/graph/interface hashes. The client - shows the issuer/timestamp/notes and old-to-new hash diff in read-only - preview, compiles only the receipt's exact destination route, and sends a - receipt ID/hash reference. The backend re-resolves that checked-in - authority and still requires exhaustive instance mappings, literal Apply, - exact backup, and rollback. The ledger is intentionally empty: this adds - no fabricated authority and converts no saved workflow. -- [ ] Add individually reviewed historical compiler mappings or compatibility - receipts before offering generation for each legacy identity that does - not exactly match a current route. - The read-only 2026-09-02 audit currently finds 375 legacy Cluster - instances across 102 manifest identities. Only 21 instances across two - identities are exact-current. The remaining 354 instances span 100 - historical identities (343 with receipts and 11 without). Exact retained-artifact recovery found - seven complete historical bodies. Six have exact execution-tuple - agreement and cover 42 instances; HunyuanVideo 1.5 covers three instances - but has no historical execution admission and remains body-only. The - other 312 instances across 94 identities still require historical - evidence. Three reviewed mappings now cover 15 instances across three - archived identities, leaving 339 instances across 97 identities blocked. - All 354 workflow instances remain byte-unchanged because mapping presence - alone is not mutation authority. Any added mapping/receipt must - pin the complete historical source/execution tuple and exact destination - V2 definition/graph/interface; the associated compiler output must still - receipt every semantic node/value/port/preview/topology mapping. - Matching labels, model IDs, presentation children, or a current catalog - route are not authority. - The schema-v4 evidence audit now makes this actionable per identity and - reports the disjoint `15 mapping-ready / 339 still blocked` aggregate: all - 375 saved roots are identity-only references, zero embed a complete - definition, 364 retain a complete admission/Studio-spec tuple, nine roots - retain 63 non-authoritative presentation children, and no migration - journal/backup exists. It also distinguishes manifest/admission matching - from exact Studio execution-tuple matching, reports the same current - `21 / 2` on both axes, and supplies precise missing-evidence codes plus - safe recovery/review actions without exposing private workflow data. - Two exact local capture receipts recover 18 self-hash-valid Studio bodies - covering 20 of those historical identities, 21 exact execution tuples, - and 65 instances. Those bodies are checked-in partial evidence only; the - manual-review ledger starts empty and conversion authority remains zero. - A separate read-only audit recovered the complete self-hash-valid Qwen - manifest body - `sha256:06fc8c7d2ec50311261f70c6942d479f3151f61026fbcf42b582c4c180536b85` - from retained local execution - evidence for the two instances made historical by the current route - reseal. Its graph, interface, artifact, admission, and Studio execution - tuple are identical to the current route; only publication metadata and - the resulting definition pins differ. It is an archive candidate, not - authority: Git does not authenticate that retained body, and both legacy - roots persist six values (`attention_kwargs`, `generator`, `latents`, - `num_images_per_prompt`, `output_type`, and `sigmas`) that the frozen - current V2 interface cannot represent. One root also persists a required - null prompt. The compiler correctly refuses to discard, coerce, or hide - those values. Recovery therefore requires an explicit reviewed dormant- - value preservation contract or a newly reviewed V2 interface; matching - graph/interface hashes alone is insufficient. - The independent archived-definition ledger is collision-resistant and - fail-closed: all seven definition, source-record, record, and ledger - hashes are validated; only the six definitions with recovered execution - admissions enter the read-only audit. Raw transcript records, private - workflow values, paths, and instance IDs are not checked in. - The generic checked-in compiler-mapping mechanism is now implemented and - tested independently: it binds the archive record and complete historical - manifest/admission/Studio tuple to exact destination manifest, - BlockDefinitionV2 content/canonical, graph, and interface hashes. The - visible generator emits only an ID/hash reference; the backend resolves - the ledger again, rejects duplicates/drift/tampering, and revalidates the - compiled destination before the existing literal-confirmation Apply. - Focused backend tests cover preview, Apply, persisted refresh, rollback, - tampering, and the Hunyuan body-only refusal; a mocked browser test covers - generate, review, Apply, recovery listing, and rollback. Destination - Three exact records are sealed and expose mapping authority for 15 saved - instances. Qwen and two SDXL records remain pending dynamic-schema - isolation; Hunyuan remains body-only. - -### Qualification - -- [x] Primary real Qwen two-instance Expert regression and exact single- - instance Auto regression pass through the frontend. The preserved - evidence lives under `review-assets/v2-live-2026-09-02/`; proof-instance - 256×256 and two-step values are workflow-local overrides and do not - change catalog defaults. -- [x] One real current-V2 image lifecycle passes through Qwen Image text-to- - image in both Expert and Auto modes. -- [x] The current-source Qwen golden path uses the exact pinned - `Qwen/Qwen-Image-2512` creator prompt and negative prompt. A real frontend - run proves two-instance isolation, parameter-only save/refresh parity, - collapsed/expanded export parity, Cluster-to-User-Node ancestry and - byte-identical reload, and execution of both the registered instance and - saved User Node. A separate 1328×1328, 50-step, seed-314159 frontend run - completed in BF16 with no quantization and no provenance blockers. -- [x] The complete ten-entry visible Qwen catalog family now has a strengthened - real-browser lifecycle gate. Each entry is inserted into an empty graph, - receives a workflow-local prompt edit, is saved, survives a full browser - refresh byte-for-byte, and re-expands with every linked Modular Diffusers - child contained by the shared Block frame. The Qwen Edit Plus entry uses - its explicit multi-reference route in this pass. The isolated receipt is - `review-pending/qwen-v2-family-lifecycle-20260903/qwen-v2-family-lifecycle/frontend-result.json`; - all ten records report Save/refresh identity and post-refresh containment. -- [x] Execute the ten Qwen admissions not covered by the completed Image - text-to-image proof through that same current-V2 frontend lifecycle: - Image image-to-image/inpaint/three ControlNet routes, Edit image/inpaint, - Edit Plus single/multi-reference, and Layered decomposition. Preserve - every technical output and incremental route receipt under the isolated - `review-pending/qwen-v2-admission-executions-20260903/` directory. Do not - change catalog defaults; low-cost test values are workflow-instance - overrides recorded in each receipt. - The first current-V2 image-to-image attempt exposed and is now guarded - against a missing execution-validation rule: an explicitly reviewed - `media_file_path` boundary may retain its publisher media union while it - binds to an internal file browser. Only that narrow media/file-browser - adaptation is accepted; unrelated type mismatches still fail closed. - The same first run exposed an intermediate Image Encode text summary - that lexical ordering had marked as the primary preview. Run-from-Block - now prefers a terminal declared preview, with `primary` used only as a - terminal tie-breaker/fallback, so diagnostic branches cannot truncate - generation. - Completed on 2026-09-03: all ten routes have completed frontend tasks - and retained media in - `review-pending/qwen-v2-admission-executions-20260903/qwen-v2-admission-executions/`. - Qwen Layered now exposes its upstream-required `resolution` as one - explicit fan-out control bound to both prompt encoding and image - encoding. Its Studio specification, compiled `BlockDefinitionV2`, and - immutable client route pins were updated together and re-audited. -- [x] Repeat the structural lifecycle against the current Qwen Image V2 graph - and retain the modified execution path. The frontend proof replaces the - prompt node with a compatible ordinary node, verifies that the original - node alone is removed and all effective-interface bindings follow the - replacement, deletes and visibly reconnects its embeddings edge, saves, - refreshes byte-identically, re-expands with contained linked children, - and completes a real model run. Parameter-only mutation is separately - compared against the complete instance so no unrelated field, graph, - interface, definition, or presentation state can change. Evidence is in - `review-pending/qwen-v2-structural-execution-20260903/qwen-v2-structural-execution/`. -- [x] Structurally customized Blocks remain ineligible for exact route, - publication, and Auto authority, but now derive a narrow cleanup-only - model/artifact identity from the submitted loader. This prevents a prior - large model from remaining resident merely because customization removed - the exact route receipt. The derived identity cannot provide a Studio - execution receipt or Auto candidate; it can only trigger process-wide - cache release. The real Qwen structural run exposed this on ROCm after a - Qwen Layered run, and the corrected run completed after supervised - cleanup with no stale model state. -- [x] The current-V2 structural golden path now also executes the modified - effective graph rather than removing the inserted node before Run. From - the visible frontend it inserts the registered Qwen Cluster, changes only - workflow-local values, uses a real visible pointer drag to adopt a normal - Data Viewer, connects it to the decoded image, saves the result as a - user-owned `BlockDefinitionV2`, - refreshes, and runs the complete graph. The exact run produced both the - 640×640 image and the terminal Data Viewer output. A subsequent full - backend restart and clean browser load preserved the definition ancestry, - graph hash, semantic connection, prompt, and parameters. The retained - manual demo records are workflow `CjFV7JYwMYzeG8XzXaV00` and User Node - `user-block-v2-ns8dy0yLgzsqZLZW`; fresh evidence is isolated under - `review-pending/qwen-v2-custom-user-node-20260903/`. The verified - production bundle was then deployed, the backend restarted, and the same - retained workflow run from the backend-served UI as task - `3OkNYjDNDyk-`; it reproduced both the original image and Data Viewer - byte hashes. -- [x] Workflow backend reconciliation now clears a delayed dirty marker only - when the complete current document is byte-signature-equivalent to the - backend-owned document. A dirty-only state transition triggers the sync - loop, while any genuinely newer local content remains dirty. This closes - the explicit-save/autosave timer race encountered by the real structural - lifecycle without weakening conflict protection. -- [x] Random-field controls project their persisted `{ value, isRandom }` - payload into the numeric execution seed and explicit randomization flag; - this is covered independently of the Qwen browser run so presentation - metadata cannot leak into backend parameter types. -- [x] Graph Fix detects invalid V2 effective structure through the same - execution validator used by Run, highlights the owning Block, and offers - an explicit reviewed-structure recovery. Focused tests prove registered - nodes/links are restored, compatible custom additions survive, and valid - customized Blocks receive no structural issue. A visible mocked-browser - lifecycle additionally corrupts a current registered Qwen instance, - requires the explicit recovery choice, retains a compatible custom node, - records one undo step, saves, refreshes, and revalidates the repaired - execution graph. -- [x] Expanded V2 projection reserves a header-safe inset for older reviewed - execution layouts while preserving their relative coordinates and - immutable semantic definition. A real-backend Helios Pyramid Distilled - test proves the first ordinary Modular node no longer overlaps or - intercepts the shared Block action bar after Save and refresh; the same - test checks all 95 reviewed Diffusers definitions remain draggable and - that collapsed/expanded concrete execution is identical. -- [x] Real current-V2 video and audio frontend lifecycles pass. The retained - 2026-09-02 frontend proofs use Wan 2.2 TI2V 5B for video and MiniMax - Music 3 for audio. Each proof starts from the registered V2 catalog - Block, edits workflow-local controls, saves and reloads the workflow, - verifies collapsed/expanded execution parity, submits the concrete graph - through the visible frontend, waits for the exact backend task, and - retrieves the attributed MP4 or WAV. Their bounded proof values do not - replace the creator defaults, and neither proof grants public showcase, - executable, Auto, or Gallery authority. -- [x] The combined client/backend V2 contract gate, full backend suite, - migration UI gate, all 90 exact route audits, and focused registered-route - promotion/runtime tests pass. -- [x] The final post-change full client check, complete mocked browser suite, - clean-browser restart, and Git/evidence audit pass as one engineering - regression gate. The latest Qwen-completion checkpoint on 2026-09-04 is - 126/126 mocked Studio browser tests, 2/2 shared-control browser tests, - 2,452 backend tests (54 optional-runtime skips), and a green production - client build/bundle gate. The Qwen regression also verifies every - retained admission-output byte or layered collection hash against its - receipt. A stale mocked compiler helper was found and corrected during - this gate: fixture pinning now applies the same reviewed initial-handle - visibility stabilization and graph reconciliation as the production - compiler, so tests cannot silently pin a pre-stabilized - `BlockDefinitionV2`. The final move-out browser gesture now releases at - a visible canvas point outside the Block instead of beyond the viewport. - The supervised AMD backend is ready and idle. This does not override - broader definition-specific video/audio, historical migration, resource - qualification, or publication-evidence gates below. -- [x] The read-only legacy audit is complete: 375 instances span 102 immutable - manifest identities, including 21 exact-current instances across two - identities. The current Qwen route reseal made two previously current - saved instances historical; they require separate reviewed recovery - authority rather than silent identity rewriting. -- [ ] The remaining 339 blocked instances across 97 historical identities need - reviewed compiler mappings or backend-pinned semantic-equivalence - receipts; no cleanup or promotion may treat a current route, matching - label, or model ID as migration authority. The 15 mapping-ready instances - still require exact per-instance compiler output, read-only preview, - explicit Apply, byte backup, and rollback before any workflow changes. - Exhaustive local recovery found seven complete historical bodies only in - byte-pinned retained live-node-library transcript records. Six exact - manifest/admission/Studio tuples cover 42 instances and are registered in - the read-only audit; the HunyuanVideo 1.5 body has no historical execution - admission and remains explicitly non-convertible. No other historical - hash occurs in Git history, the exact 2026-08-24 response, gzip frontend - captures, reports, browser traces/caches, or retained uncompressed JSON - artifacts. The migration journal/backup store is absent and the checked- - in equivalence ledger is empty. The remaining 312 instances across 94 - identities need an authenticated old export/build artifact or an - independently reviewed exact graph/interface equivalence receipt. -- [x] The checked-in publication-evidence audit now treats the former Qwen - Image and MiniMax Music 3 schema-v1 approvals as historical and - non-authorizing. The active schema-v2 promotion receipt ledger is empty, - so both exact routes require explicit workspace-owner reapproval against - their current admission, Studio-spec, artifact, manifest, and - `BlockDefinitionV2` identities; Wan has no historical approval to reuse. - No route currently receives `liveProof`, public executable, Auto, - Gallery, or release-eligibility authority from the promotion ledger. -- [x] Three independent exact-route resource receipts now bind Qwen Image - text-to-image, Wan 2.2 TI2V 5B text-to-video, and MiniMax Music 3 - text-to-audio to two distinct current-V2 frontend proofs apiece. Every - receipt explicitly sets `familyCoverageDeclared`, `publicationAuthority`, - and `autoAuthority` to false. They populate only the exact - `routeQualifications` lane; the separate family recipe report remains - 12/51 and must not infer cross-template or cross-workflow coverage from - these measured workloads. -- [x] The first generic registered text-to-image Block now offers Qwen Image - 2512, FLUX.1 Dev, and Stable Diffusion XL 1.0 as explicit exact routes. - Each route remains one immutable reviewed `BlockDefinitionV2`; switching - stores an independent inactive draft instead of mutating a loader or - silently changing another Block. The retained backend workflow - `1ZEaXrQ2Hl6kNBFXKavZB` was migrated through the deployed frontend to the - superseded coarse Qwen pin `block-definition-v2-fa9fae25`, with its Qwen prompt and - both FLUX and SDXL drafts preserved across Save. -- [x] Stale inactive registered drafts now rebase onto the current exact - destination definition when—and only when—they contain no structural - edits. All still-compatible controls and surviving internal layout are - retained. A stale structurally modified draft fails closed with guidance - to save it as a User Node, preventing either silent schema drift or loss - of user structure. -- [x] Public connector projection is cached by immutable Block-instance - identity. The graph fixer therefore no longer repeatedly normalizes the - same registered Block while resolving every connector. Focused tests - prove reuse for an unchanged instance and invalidation for copy-on-write - updates. -- [x] Optimize generic route switching with multiple inactive drafts. An exact - current inactive draft is restored by immutable ID/hash/SHA validation - instead of recompiling the same hidden Modular graph. Stale or - structurally incompatible drafts still fail closed or use the reviewed - rebase path. -- [x] The development-proxy conditional-schema race is removed from the formal - three-route Playwright proof. Qwen's reviewed route now deterministically - replays the ordinary HandleField initial visibility result inside the - isolated compiler, independent of whether React happened to mount the - hidden transient connector first. The all-routes audit and the live Vite - lifecycle both pass; the latter inserts a generic Block, edits Qwen, - switches through FLUX and SDXL with undo/redo and sibling isolation, then - saves, refreshes, and verifies all three independent drafts in 44.5 - seconds. The compiler's non-sensitive received hash/SHA diagnostic is - retained for future route qualification. diff --git a/docs/windows-fashion-demo.md b/docs/windows-fashion-demo.md new file mode 100644 index 00000000..2fbda032 --- /dev/null +++ b/docs/windows-fashion-demo.md @@ -0,0 +1,78 @@ +# Windows fashion demo setup + +The backend ships the matching built frontend. Pulling MoDiff is sufficient to +test the application; the separate client checkout is only needed for development +and recording/rehearsal scripts. Use the paired revision identifiers supplied +with the handoff, not a mixture of old and new contracts. + +## Update and launch + +In your existing MoDiff checkout, preserve uncommitted local work before switching: + +```powershell +git fetch origin +git switch feat/generic-diffusers-workbench +git pull --ff-only +.\install.ps1 -Accelerator nvidia +.\run.ps1 +``` + +The installer provisions the reviewed Windows environment; do not copy a Linux +virtual environment or optional-runtime directory. Open the local address shown +by the launcher. Use Setup's explicit optional-runtime installation/activation +when a selected workflow requires it, then follow any restart instruction. +Opening a workflow or enabling Developer mode does not grant code consent. + +## Transfer the curated chapters + +The separately supplied `fashion-demo-windows.zip` contains `manifest.json`, +thirteen `workflows/*.workflow.json` files and `data/fashion-demo/` images. Git does +not contain these generated images, model weights or machine-specific approvals. +Extract the archive into a new folder. Compare the archive SHA-256 with the +handoff receipt before using it: + +```powershell +Get-FileHash .\fashion-demo-windows.zip -Algorithm SHA256 +``` + +1. Copy the extracted `data/fashion-demo` directory into your MoDiff checkout's + `data` directory. Keep the `fashion-demo` directory name: workflow paths use + `@data/fashion-demo/.`. Files are content-addressed; + don't replace an existing different file under the same name. +2. In **Nodes → Custom nodes**, stage + `examples/custom_nodes/EditorialRegions` with module name + **FashionEditorialRegions**. Inspect the source/dependencies, then explicitly + enable it. This exact name matches the saved graphs. Do not copy another + machine's approval database. +3. Enter Developer mode. Drag one numbered workflow JSON onto an empty canvas. + Use **Save As** with the corresponding title from `manifest.json`. Repeat for + the chapters you want to present. Import does not download or load models. +4. Open each saved chapter, inspect its image full-size, refresh and reopen. + Check the custom fields, prompt, edges and source checkpoint. A missing image + means the media directory is misplaced, not that it should be regenerated. +5. Before Run, use Model Manager/Setup to resolve the exact declared model and + runtime requirements. Review model license/access requirements with your own + account. The package contains no access token. + +## RTX 4080, 16 GB VRAM, 32 GB system RAM + +Start with chapter 11's CPU-only custom processing to verify the transfer and +extension, then chapter 01 (Z-Image Turbo) and a Klein 4B editing chapter. The +curated generation recipes use batch one, BF16 and model-CPU offload. Monitor +both system RAM and VRAM; CPU offload is not unlimited memory. + +The saved images are 1024×1024. For an initial hardware smoke test, make a +**Save As** copy and lower resolution if necessary. Model alignment requirements +still apply. Review region placement against the new image; the curated polygons +were authored for the retained composition, not arbitrary regenerated people. + +Kontext and Qwen Image 2.1 chapters are optional live chapters on this machine: +their successful Linux runs do not establish that these full-weight recipes fit +within 16 GB VRAM / 32 GB RAM on Windows. It is valid to inspect their retained +results and graph transitions without claiming live Windows qualification. +Do not silently substitute a different quantization or model revision. + +For presentation, follow the [13-chapter guide](fashion-editorial-demo.md). Label +retained results and omitted generation waits explicitly. Exact pixel protection +applies outside the compositor mask; it is not an identity guarantee inside a +generative edit. diff --git a/docs/workbench-acceptance.md b/docs/workbench-acceptance.md new file mode 100644 index 00000000..dcf42478 --- /dev/null +++ b/docs/workbench-acceptance.md @@ -0,0 +1,132 @@ +# Workbench acceptance and qualification boundaries + +This guide consolidates the durable requirements of the retired workbench, +workspace, image-readiness and integration plans. It is an acceptance standard, +not a new implementation plan or a claim that every route has passed. +Dated checklists, run IDs, machine inventories and abandoned proposals remain +recoverable in Git history. Keep this guide identical in both repositories. + +## Current contract, not historical UX + +Use the current workflow-authoring guide and custom-node documentation for +product behavior. The old Creator/Developer audience switch, separate approval +wizard and destructive Cluster-to-User conversion are superseded. Memory +Automatic/Custom is independent of authoring. One backend graph executor, +resource owner and task-generic node contract remain authoritative. +Model/library-specific behavior belongs in reviewed backend adapters. + +Native stage support means real editable upstream stages, not several decorative +nodes around one pipeline call. Standard whole-pipeline adapters remain valid +when the exact upstream task has no usable native stage chain. Document each +exception with artifact/task/revision, upstream evidence, supported controls, +unavailable stage editing/reuse, transition behavior and a revisit trigger. +A missing MoDiff adapter is not an upstream exception. Family resemblance and +nominal tensor types do not establish semantic compatibility. + +Model/task edits must be atomic and undoable. Preserve compatible prompts, +authored settings (including values equal to old defaults), custom nodes, +connections, layout and explicit interface removals. Keep unsupported values +recoverable, explain incompatible roles and require review for destructive +changes. Resolve compatibility from declared roles, conditioning/latent formats, +lineage and capabilities; unresolved compatibility is not guaranteed compatibility. + +## Freeze the qualification denominator + +For a broad image release, enumerate the union of advertised routes from +operation contracts, starters, model pickers, templates, execution profiles, +custom examples and supported-model documentation. Cross-check exact native +upstream exports independently of the current adapters. Freeze a snapshot/hash; +advertisement or execution-contract changes reopen affected rows. + +Each row identifies the immutable artifact revision, task, implementation, +adapter/component variant, advertisement entry points, installed dependencies, +required inputs/controls, native-stage or whole-pipeline disposition, resource +policy, exceptions and evidence. Keep contract-only inventory distinct from +advertised runnable routes. Do not hide missing integrations to obtain a pass. + +Separate structural, automated, real-browser, actual-generation, media-quality, +reuse/resource, service-export and platform evidence. Generated inventory hashes +and route counts do not promote live execution or public asset approval. +A bounded installed-model Windows test is not an all-model release gate. + +## Required integrated journeys + +- Create fresh text-to-image, image-to-image and edit workflows. Required media + starts in a separate connected loader with a working preview. +- Encode Inputs retains ordinary node layout and resizing. Supported optional + image sockets stay connectable; text and image encoding can coexist. +- Connect compatible sources directly. Preview output can feed a separate + downstream branch or saver; an upstream cycle is rejected with a clear toast. +- Switch models/tasks and back with edited prompts, custom nodes and wires. + Test distinct semantic/topology transitions, not only aliases of one family. +- Change guidance, remove interface rows, save/reopen, refresh, Undo/Redo and + reorder workflow tabs; compare the effective graph and actual consumed inputs. +- Exercise reviewed local discovery, Python-file drop, import errors, + registration, compatible suggestions, disable/enable and edited-source Reload. + Discovery and workflow import never grant execution consent. +- For supported audio, verify separate loaders, decoded input previews, + generated playback, task-appropriate duration/rate/channels and valid wires. +- Run an exported service from an edited graph with changed prompt/seed/files + and verify named outputs and declared code/runtime requirements. + +For each claimed artifact/task, prove a cold baseline and applicable unchanged, +prompt, seed, guidance/steps, size/batch/reference, persistence, recompute, +offload/release and failure/cancel/retry cases. Record consumed values and +load/encode/reuse observations. Use task-specific equivalents for analysis, +upscale, unconditional and other non-prompt workflows; justify N/A explicitly. +Representative transition tests require a coverage mapping and do not replace +each advertised artifact's execution evidence. + +## Outstanding scope is not erased by documentation cleanup + +Historical plans left broad inventory/native-stage parity, all-model variants, +adapter/control compositions, service/demo handoff and cross-platform resource +qualification open or partial. Reconcile these against current source and +receipts before claiming closure; old unchecked rows are not automatically +current defects, and old checked rows are not current qualification. + +In particular, re-audit advertised native variants (including previously +equivalent-only ERNIE routes), quantized component bindings, conditioned/edit +routes, repeated-run reuse and final continuous custom-node/model-switch +journeys. Retain exact per-model blockers and whole-pipeline exceptions. +Windows Qwen on a 16 GB VRAM / 32 GB RAM host remains unqualified unless an +appropriate exact-route receipt proves otherwise; other machines' results +cannot close that boundary. + +## Bounded execution, trust and publication + +Use one workflow at a time with an explicit wall-clock bound and cancellation +plan. Preserve the task identity, failure and cleanup result; do not resubmit an +unknown active run. Inspect an unchanged recipe's failure before retrying. +Research quality recipes against the exact official source. After two quality +failures, require a changed engineering/recipe hypothesis or explicit exception. + +Never infer permission to download weights, install optional runtimes, execute +remote code, delete cached models, publish assets or commit from a request to +test. Review immutable custom sources and dependency hashes. Keep approvals +outside source packages. Before any separately authorized cache deletion, +enumerate exact-revision dependencies and preserve outputs/receipts; verify +queue state and the app's current capacity/download plan before installations. + +Validate generated bytes and media behavior separately from aesthetic quality: +decoded dimensions/count or audio duration/rate/channels, finite/nonempty output, +task fidelity and visible/listening assessment. A short technical audio smoke +is not music-showcase approval; hashes establish identity, not quality. +Public media needs rights/provenance review and immutable Dataset/hash metadata, +not generated binaries in Git. Auto and publication eligibility require their +own exact-route authority and evidence. + +## Proof and handoff + +Record paired commit IDs plus dirty-source identity, process-start identity, +served bundle hashes, exact models/runtimes, steps, expected/actual result, +proof level and screenshot/log/receipt locations. Preserve failures and skips. +Reproduce suspected regressions on the baseline where feasible. + +Use focused failing/passing regressions while iterating, then the applicable +CONTRIBUTING gates at a stable candidate. Reuse unchanged evidence with its +original scope; documentation-only cleanup does not require more inference. +Retest changed execution contracts and validate emitted production assets. +Do not call a handoff complete while required integrated gates remain failed, +unrun or blocked. Report those limits and leave commit/publication decisions +to the maintainer. diff --git a/docs/workflow-authoring-ux.md b/docs/workflow-authoring-ux.md new file mode 100644 index 00000000..2e057348 --- /dev/null +++ b/docs/workflow-authoring-ux.md @@ -0,0 +1,160 @@ +# Workflow authoring and model selection + +Creator opens Templates. Developer opens Workflows. Both use the same editable +nodes, Blocks, execution path and independent memory policy. + +## Start a workflow + +In Developer, search for an action or filter by Image, Audio, Video, 3D, or Text & +Utilities. Click a task once to create connected nodes. Select the model on the +loader and edit the prompt or parameters on the graph. Creating a graph does not +load models or install packages. Custom node source review remains available at +the bottom of the chooser and through Nodes → Custom nodes. + +The backend resolves a task against published execution profiles. It prefers the +last explicitly selected compatible model when its exact artifacts and runtime +are available, otherwise an installed composable route, preferring the lower +estimated memory requirement among suitable routes. Automatic memory also +checks the existing read-only resource planner; Custom memory leaves that choice +to the operator. No selection loads weights or changes creative defaults. If no +route qualifies, the connected draft has an empty required model field. Select a +model there before running. Run independently rechecks inputs and resources. +Remembered selections are bounded local authoring preferences, not execution approval. + +Composable routes expose meaningful independent nodes such as Load Models, +Encode Prompt, Denoise and Decode Latents. Tasks can need additional image, mask, +conditioning or audio operations. Whole-pipeline routes expose Generate/Edit +operations instead; those nodes do not claim independently replaceable denoising. +Qwen-Image 2.1 uses its supported whole-pipeline route at the reviewed Diffusers pin. + +## Change the model + +### Guidance control visibility + +Removing a Guidance row through Configure interface hides the row, not the +underlying operation or its effect. The current value is retained on its original +stage. Explicit removals are recorded by `{nodeId, fieldId}` in the optional +instance `presentation.removedControlBindings` list and survive reload and dynamic +field refresh. Re-exposing a control clears its removal record. Reusable-node +definitions can carry the same optional `removedControlBindings` list as initial +presentation; it is excluded from execution identity. Both client and backend +validate bounded, unique bindings. Historical omissions without a removal record +are not guessed to be deliberate removals, and opening a workflow does not rewrite it. + +### Model selection + +The model field of a canonical loader opens **Choose model for Load Models**. +Search by model or repository and optionally filter to Downloaded. Names precede +repository IDs; download status is a trailing badge. Selecting a row resolves the +backend starter and updates the owned operation graph through the normal graph +transaction. Edited compatible values and unrelated branches remain intact. +An untouched starter can change between composable and whole-pipeline routes +without leaving disabled old nodes: its internal edges and controls are checked +against a backend-resolved baseline, and its unique media preview is reconnected. +Edited values, custom wiring or ambiguous outputs use the preservation/review +planner. Schema or connection differences requiring review are shown before applying. +Undo/Redo and saved workflow reload use the same graph history and persistence. + +A raw implementation/component loader has a picker restricted to compatible +installed artifacts. In a connected workflow, model selection derives the Pipeline +Type and applies both atomically. A raw implementation loader exposes Pipeline Type +only when no workflow/model-choice contract can infer it. Clicking a row +applies its repository to that field. **Manage model files** opens management; +copying a repository ID is not required for selection. Canonical switching does +not broaden a legacy loader's accepted runtime contract. + +Model choices remain backend-declared. Standard and Modular routes for one +repository share a model row. The current implementation stays primary; otherwise +ready composable operations are preferred. Other implementations remain available +under the row's disclosure. An explicit successful selection is remembered per task. + +**Restore model defaults** resets unconnected creative controls using the selected +profile's starter. Model ownership, memory settings, media and custom branches +remain intact; Undo restores the prior graph. Public Block model controls resolve +the owning internal loader before using the existing atomic Block transaction. Imported custom sources still require explicit review +and approval; opening a picker or choosing Developer does not enable code. + +## Names, examples and library + +Raw loaders use distinct names: **Load Image Pipeline**, **Load Audio Pipeline**, +**Load Modular Components**, and **Load Model Component**. Canonical workflows use +**Load Models**. Other corrected names include **Prepare Outpaint Canvas**, +**Generate Image with Control**, **Preview Latents**, and **Export Video Asset**. +Names are selected by execution identity, not guessed from class-name substrings. +Serialized identities and custom titles are preserved; previous built-in labels +remain searchable. + +New resolved operation schemas receive task examples. FLUX Schnell, FLUX.2 Klein +4B, Z-Image Turbo, original Qwen Image, Stable Audio Open and AudioLDM2 have +concise creator-derived examples with source links. Earlier Qwen 2.1 examples remain intact. Other +mapped tasks use labeled MoDiff examples. Numeric settings continue to come from +reviewed execution profiles. This does not migrate saved prompts or replace edited +values. Full creator-example coverage and all legacy insertion paths remain tracked +work; the presence of a sample does not qualify a model's output. + +The sidebar offers **Start**, **My workflows**, **Example workflows** and +**Recovery drafts**. Start uses the same task browser as the Developer launcher; +it creates a separate tab without replacing the current graph. Open workflow +shows My workflows. Lists remain bounded and paginated. + +New documents autosave as recovery drafts. Explicit Save promotes them to My +workflows; a delayed autosave cannot demote a saved document. Existing documents +without an intent marker are treated as saved without rewriting their files. +Unknown historical documents are not reclassified or deleted. + +Shared select, combobox and multiselect menus use an 18rem preferred cap, further +limited by available viewport space. The cap is passed through Headless UI's +anchoring constraint so its inline styles cannot override the intended limit. + +## Verification boundary + +Regression tests cover naming, task creation, model changes, edited prompts, +Undo/Redo, reload, cancellation and long option lists. Backend tests resolve +contracts without loading weights. These checks establish authoring behavior; +they are not all-model image/audio output qualification. Video output qualification +and Windows Qwen on 16GB VRAM / 32GB RAM remain separate pending acceptance. + +Native Modular execution has also been checked with Z-Image Turbo and FLUX.2 +Klein 4B using the same generic graph. An unchanged Z-Image rerun reused all +nodes; changing its seed retained models and prompt encoding; changing its prompt +retained models. Switching to FLUX preserved the edited prompt and generated a +new image. This is representative execution evidence, not every-model coverage. +A first Run click immediately after switching and editing parameters failed to +submit during this check; a later click succeeded. That timing issue remains +under investigation; a fresh-session first-click repetition passed. + +A later instrumented repetition of baseline, unchanged, seed, prompt and FLUX +model-switch runs submitted successfully on the first click in all five cases. +The original intermittent failure is retained as unresolved; this is diagnostic +evidence, not a claimed fix. + +## Final representative authoring acceptance + +The final production-build journey created a Z-Image Turbo text-to-image workflow +from the task browser, restored its creator-derived prompt and completed all five +independent Modular nodes on the first Run click. The workflow was explicitly +saved, a second workflow was created from sidebar Start, and the first workflow +was converted to a user Block. Its public model control then switched the same +Block to FLUX.2 Klein 4B and completed all five nodes on the first Run click. Both +1024×1024 outputs were visually reviewed: the restored Z-Image prompt produced +the intended portrait and the FLUX prompt produced a cat with the legible requested +sign. + +That journey exposed one compatibility defect before it passed. A derived Block +interface can contain optional fields from its source model that the target model +does not implement. Model replacement now removes only unavailable fields that +were generated automatically, remain at their defaults, have no stored boundary +value and have no outside connection. Explicitly configured interfaces, connected +inputs, sealed controls and edited values continue to reject an unsafe switch and +use the existing review path. The focused operation-authoring suite covers both +the allowed and protected cases; the final client check, production build and +bundle limits passed. The mirrored backend web bundle matches the tested client +distribution byte-for-byte. + +This is representative acceptance of task-first creation, defaults, save/draft +separation, Block ownership and cross-family Modular switching. It does not expand +the all-model image/audio qualification boundary above. The browser harness ended +with a bookkeeping assertion that expected the still-open recovery draft in the +mocked saved-document list; application execution, output capture and error checks +had already passed, and only the explicitly saved workflow had correctly been +written there. diff --git a/examples/custom_nodes/AudioEnvelope.py b/examples/custom_nodes/AudioEnvelope.py new file mode 100644 index 00000000..05a2ef44 --- /dev/null +++ b/examples/custom_nodes/AudioEnvelope.py @@ -0,0 +1,53 @@ +"""Standalone CPU audio example: bounded trim, gain and equal-power fades. + +Drop this file on the canvas, then connect Load Audio -> Audio Envelope -> +Preview Audio. Uses MoDiff's normalized waveform boundary; never loads a model. +""" + +from modiff.NodeBase import NodeBase + +MODIFF_RUNTIME_ROLE = "data" + + +class AudioEnvelope(NodeBase): + label = "Audio Envelope" + category = "Audio" + params = { + "audio": {"type": "audio", "display": "input", "label": "Audio"}, + "start_seconds": {"type": "float", "default": 0.0, "min": 0, "max": 300}, + "duration_seconds": {"type": "float", "default": 3.0, "min": 0.01, "max": 300}, + "gain_db": {"type": "float", "default": -6.0, "min": -60, "max": 12}, + "fade_seconds": {"type": "float", "default": 0.3, "min": 0, "max": 10}, + "output": {"type": "audio", "display": "output", "label": "Audio"}, + } + + def execute(self, audio, start_seconds, duration_seconds, gain_db, fade_seconds): + import math + import numpy as np + from modules.Audio.main import _bounded_audio_object + + for name, value, minimum, maximum in ( + ("start_seconds", start_seconds, 0, 300), + ("duration_seconds", duration_seconds, 0.01, 300), + ("gain_db", gain_db, -60, 12), + ("fade_seconds", fade_seconds, 0, 10), + ): + if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or not minimum <= value <= maximum: + raise ValueError(f"{name} must be finite and between {minimum} and {maximum}.") + source = _bounded_audio_object(audio, label="Audio Envelope input") + rate = source["sample_rate"] + start = round(start_seconds * rate) + samples = source["samples"][start:start + round(duration_seconds * rate)].copy() + if not len(samples): + raise ValueError("Start is beyond the end of the source audio.") + samples *= 10 ** (gain_db / 20) + count = min(round(fade_seconds * rate), len(samples) // 2) + if count: + ramp = np.sin(np.linspace(0, np.pi / 2, count, dtype=np.float32))[:, None] + samples[:count] *= ramp + samples[-count:] *= ramp[::-1] + return {"output": { + "samples": np.clip(samples, -1, 1), "sample_layout": "frames_first", + "sample_rate": rate, "channels": source["channels"], + "duration_seconds": len(samples) / rate, + }} diff --git a/examples/custom_nodes/EditorialRegions/README.md b/examples/custom_nodes/EditorialRegions/README.md new file mode 100644 index 00000000..ef0ab9cd --- /dev/null +++ b/examples/custom_nodes/EditorialRegions/README.md @@ -0,0 +1,20 @@ +# Editorial Regions + +A model-independent custom image node with editable polygon mask algebra (union, +subtraction, intersection), feathering, linear-light exposure, opposed color gels +and saturation. It outputs an RGB image, L mask and diagnostics. Coordinates are +normalized image coordinates; this is explicit art direction, **not segmentation**. + +Stage this directory in Nodes → Custom nodes, inspect and consent to Enable code. +Connect decoded images to Source image. Draw a polygon by editing the JSON; add a +subtract polygon to protect a face or another region. White mask pixels are editable. +Connect Directed image and Edit mask to a compatible inpaint operation. + +Use the second node, Protected Editorial Composite, after diffusion: connect the +original image, generated edit and the same mask. Pixels with zero mask remain +byte-identical to the RGB original. A protected region is not identity conditioning +inside the mask. Inspect feathered seams and the generated result before presenting. + +Bounds: one image (or batch of one), 4,194,304 pixels, 16 polygons, 32 points per +polygon, 16KiB JSON. No filesystem/network/model loading and no new dependencies; +uses existing Pillow and NumPy. The source image is not modified in place. diff --git a/examples/custom_nodes/EditorialRegions/__init__.py b/examples/custom_nodes/EditorialRegions/__init__.py new file mode 100644 index 00000000..a0feb78f --- /dev/null +++ b/examples/custom_nodes/EditorialRegions/__init__.py @@ -0,0 +1 @@ +from .main import EditorialRegions, ProtectedComposite # noqa: F401 diff --git a/examples/custom_nodes/EditorialRegions/main.py b/examples/custom_nodes/EditorialRegions/main.py new file mode 100644 index 00000000..c3c59f68 --- /dev/null +++ b/examples/custom_nodes/EditorialRegions/main.py @@ -0,0 +1,139 @@ +"""Explicit, bounded image-space art direction. No semantic mask inference.""" + +import json +import math + +from modiff.NodeBase import NodeBase + + +def number(value, name, low, high): + if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value): + raise ValueError(f"{name} must be a finite number.") + if not low <= value <= high: + raise ValueError(f"{name} must be between {low} and {high}.") + return float(value) + + +def image_value(value): + from PIL import Image + + if isinstance(value, (list, tuple)) and len(value) == 1: + value = value[0] + if not isinstance(value, Image.Image): + raise ValueError("Expected one decoded PIL image (or a one-image batch).") + if not 1 <= value.width * value.height <= 4_194_304: + raise ValueError("Image must contain at most 4,194,304 pixels.") + return value + + +def rgb_image(value): + from PIL import Image + + value = image_value(value) + return Image.alpha_composite(Image.new("RGBA", value.size, "white"), value.convert("RGBA")).convert("RGB") + + +def region_mask(size, regions, feather): + from PIL import Image, ImageChops, ImageDraw, ImageFilter + + number(feather, "feather", 0, 0.1) + if not isinstance(regions, str) or len(regions) > 16_384: + raise ValueError("Regions must be a JSON string of at most 16,384 characters.") + try: + shapes = json.loads(regions) + except (ValueError, TypeError) as exc: + raise ValueError("Regions must be valid JSON.") from exc + if not isinstance(shapes, list) or not 1 <= len(shapes) <= 16: + raise ValueError("Supply 1 to 16 region polygons.") + result = Image.new("L", size, 0) + for shape in shapes: + if not isinstance(shape, dict) or set(shape) != {"operation", "points"}: + raise ValueError("Each region requires only operation and points.") + if shape["operation"] not in ("add", "subtract", "intersect"): + raise ValueError("Region operation must be add, subtract or intersect.") + points = shape["points"] + if not isinstance(points, list) or not 3 <= len(points) <= 32: + raise ValueError("Each polygon requires 3 to 32 normalized [x,y] points.") + coordinates = [] + for point in points: + if not isinstance(point, list) or len(point) != 2: + raise ValueError("Each point must be [x,y].") + coordinates.append(tuple(number(v, "coordinate", 0, 1) * (size[i] - 1) for i, v in enumerate(point))) + layer = Image.new("L", size, 0) + ImageDraw.Draw(layer).polygon(coordinates, fill=255) + if feather: + layer = layer.filter(ImageFilter.GaussianBlur(feather * min(size))) + if shape["operation"] == "add": + result = ImageChops.lighter(result, layer) + elif shape["operation"] == "subtract": + result = ImageChops.subtract(result, layer) + else: + result = ImageChops.multiply(result, layer) + return result + + +class EditorialRegions(NodeBase): + label = "Editorial Regions" + category = "Image" + resizable = True + params = { + "image": {"label": "Source image", "type": "image", "display": "input"}, + "regions": {"label": "Region polygons (JSON)", "type": "string", "display": "textarea", + "default": '[{"operation":"add","points":[[0,0],[1,0],[1,1],[0,1]]}]'}, + "feather": {"label": "Edge feather", "type": "float", "default": 0.015, "min": 0, "max": 0.1, "step": 0.001}, + "exposure": {"label": "Exposure stops", "type": "float", "default": 0, "min": -3, "max": 3, "step": 0.1}, + "temperature": {"label": "Warm cool split", "type": "float", "default": 0, "min": -1, "max": 1, "step": 0.05}, + "saturation": {"label": "Saturation", "type": "float", "default": 1, "min": 0, "max": 2, "step": 0.05}, + "out_image": {"label": "Directed image", "type": "image", "display": "output"}, + "mask": {"label": "Edit mask", "type": "image", "display": "output"}, + "diagnostics": {"label": "Region diagnostics", "type": "dict", "display": "output"}, + } + + def execute(self, image, regions='[{"operation":"add","points":[[0,0],[1,0],[1,1],[0,1]]}]', + feather=0.015, exposure=0, temperature=0, saturation=1): + import numpy as np + from PIL import Image + + source = rgb_image(image) + mask = region_mask(source.size, regions, feather) + exposure = number(exposure, "exposure", -3, 3) + temperature = number(temperature, "temperature", -1, 1) + saturation = number(saturation, "saturation", 0, 2) + rgb = np.asarray(source, dtype=np.float32) / 255 + # Exposure in linear light; no hidden model, downloads or file access. + linear = np.where(rgb <= 0.04045, rgb / 12.92, ((rgb + 0.055) / 1.055) ** 2.4) + luma = linear @ np.array([0.2126, 0.7152, 0.0722], dtype=np.float32) + directed = (luma[..., None] + saturation * (linear - luma[..., None])) * (2 ** exposure) + # Opposed horizontal gels: warm on the left, cool on the right. + gradient = np.linspace(1, -1, source.width, dtype=np.float32)[None, :, None] + directed *= np.exp(gradient * temperature * np.array([0.8, 0.0, -0.8], dtype=np.float32)) + directed = np.clip(directed, 0, 1) + srgb = np.where(directed <= 0.0031308, directed * 12.92, 1.055 * directed ** (1 / 2.4) - 0.055) + edited = Image.fromarray(np.rint(np.clip(srgb, 0, 1) * 255).astype(np.uint8)) + output = Image.composite(edited, source, mask) + return {"out_image": output, "mask": mask, + "diagnostics": {"width": source.width, "height": source.height, + "mask_coverage": float(np.asarray(mask).mean() / 255), + "semantic_segmentation": False}} + + +class ProtectedComposite(NodeBase): + label = "Protected Editorial Composite" + category = "Image" + resizable = True + params = { + "original": {"label": "Protected original", "type": "image", "display": "input"}, + "edited": {"label": "Generated edit", "type": "image", "display": "input"}, + "mask": {"label": "Edit mask", "type": "image", "display": "input"}, + "out_image": {"label": "Final image", "type": "image", "display": "output"}, + } + + def execute(self, original, edited, mask): + from PIL import Image + + original, edited, mask = rgb_image(original), rgb_image(edited), image_value(mask) + if original.size != edited.size or original.size != mask.size: + raise ValueError("Original, edit and mask must have identical dimensions; resize explicitly upstream.") + if mask.mode != "L": + raise ValueError("Edit mask must be grayscale L, not an ambiguous RGB image.") + return {"out_image": Image.composite(edited, original, mask)} diff --git a/examples/custom_nodes/EditorialRegions/modiff_extension.json b/examples/custom_nodes/EditorialRegions/modiff_extension.json new file mode 100644 index 00000000..318a745e --- /dev/null +++ b/examples/custom_nodes/EditorialRegions/modiff_extension.json @@ -0,0 +1 @@ +{"runtimeRole": "data"} diff --git a/examples/custom_nodes/LightPaletteDirector/README.md b/examples/custom_nodes/LightPaletteDirector/README.md new file mode 100644 index 00000000..67a2a208 --- /dev/null +++ b/examples/custom_nodes/LightPaletteDirector/README.md @@ -0,0 +1,21 @@ +# Light & Palette Director + +Stage this directory using **Nodes → Custom nodes**, review the source and enable +its exact hash. It is ordinary operator-approved Python, not a model or a prompt +wrapper. It uses the existing Pillow/NumPy dependencies and normal graph executor. + +Connect any generator's **decoded images** to Image. Directed image is RGB; +Soft mask is L (white = editable region). Connect these to an image/mask-capable +refinement operation, and optionally to separate Preview nodes. The elliptical +region, feather, three-color luminance palette and directional gradient are +deterministic CPU operations. This is geometric art direction, not semantic +segmentation or physically accurate relighting. Transparent inputs are composited +on white explicitly. Each image is limited to 4,194,304 pixels and batches to four. +Coordinates/radii are normalized to image dimensions. Strength zero preserves the +RGB source exactly but does not disable the mask; refinement strength is separate. + +For a code-reload demonstration, edit the **installed** module's `main.py`, replace +the smoothstep line with `mask = mask ** 2`, then Review reload and approve the new +hash. Ports and saved settings stay unchanged. Do not edit a staged source copy +and expect the already-installed module to change. Changing code never grants +approval automatically; importing a saved workflow does not grant it either. diff --git a/examples/custom_nodes/LightPaletteDirector/__init__.py b/examples/custom_nodes/LightPaletteDirector/__init__.py new file mode 100644 index 00000000..187e7ce1 --- /dev/null +++ b/examples/custom_nodes/LightPaletteDirector/__init__.py @@ -0,0 +1 @@ +from .main import LightPaletteDirector # noqa: F401 diff --git a/examples/custom_nodes/LightPaletteDirector/main.py b/examples/custom_nodes/LightPaletteDirector/main.py new file mode 100644 index 00000000..f06e1bf6 --- /dev/null +++ b/examples/custom_nodes/LightPaletteDirector/main.py @@ -0,0 +1,105 @@ +"""Deterministic, model-independent art direction and an inpainting mask.""" + +from modiff.NodeBase import NodeBase + + +def direct_image(image, *, shadow, midtone, highlight, center_x, center_y, + radius_x, radius_y, feather, strength, light_angle, light_intensity): + import math + + import numpy as np + from PIL import Image, ImageColor + + controls = { + "center_x": (center_x, 0, 1), "center_y": (center_y, 0, 1), + "radius_x": (radius_x, 0.01, 1), "radius_y": (radius_y, 0.01, 1), + "feather": (feather, 0.01, 1), "strength": (strength, 0, 1), + "light_angle": (light_angle, -180, 180), "light_intensity": (light_intensity, 0, 1), + } + for name, (value, low, high) in controls.items(): + if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value): + raise ValueError(f"{name} must be a finite number.") + if not low <= value <= high: + raise ValueError(f"{name} must be between {low} and {high}.") + if not isinstance(image, Image.Image): + raise ValueError("Light & Palette Director expects decoded PIL images, not tensors or latents.") + width, height = image.size + if width < 1 or height < 1 or width * height > 4_194_304: + raise ValueError("Light & Palette Director supports up to 4,194,304 pixels per image.") + colors = [] + for name, value in (("shadow", shadow), ("midtone", midtone), ("highlight", highlight)): + if not isinstance(value, str) or len(value) != 7 or not value.startswith("#"): + raise ValueError(f"{name} must be an RGB hex color, for example #204060.") + try: + colors.append(np.asarray(ImageColor.getrgb(value), dtype=np.float32) / 255) + except ValueError as exc: + raise ValueError(f"Invalid {name} RGB color.") from exc + + # Composite transparent pixels on white, yielding an explicit RGB diffusion boundary. + rgba = image.convert("RGBA") + source = Image.alpha_composite(Image.new("RGBA", image.size, "white"), rgba).convert("RGB") + rgb = np.asarray(source, dtype=np.float32) / 255 + x = (np.arange(width, dtype=np.float32) + 0.5) / width + y = (np.arange(height, dtype=np.float32) + 0.5) / height + dx = (x[None, :] - center_x) / radius_x + dy = (y[:, None] - center_y) / radius_y + distance = np.sqrt(dx * dx + dy * dy) + mask = np.clip((1 - distance) / feather, 0, 1) + mask = mask * mask * (3 - 2 * mask) # Smoothstep: editable falloff for the reload demo. + luma = rgb @ np.array([0.2126, 0.7152, 0.0722], dtype=np.float32) + lower = colors[0] + (colors[1] - colors[0]) * np.minimum(luma * 2, 1)[..., None] + upper = colors[1] + (colors[2] - colors[1]) * np.maximum(luma * 2 - 1, 0)[..., None] + palette = np.where((luma < 0.5)[..., None], lower, upper) + # Retain source detail/chroma residual instead of replacing the image with flat colors. + palette = palette + 0.35 * (rgb - luma[..., None]) + angle = math.radians(light_angle) + light = np.clip((dx * math.cos(angle) + dy * math.sin(angle)) / 2, -1, 1) + directed = np.clip(palette + light[..., None] * light_intensity * 0.3, 0, 1) + blend = mask[..., None] * strength + result = np.clip(np.rint((rgb * (1 - blend) + directed * blend) * 255), 0, 255).astype(np.uint8) + return ( + Image.fromarray(result), + Image.fromarray(np.rint(mask * 255).astype(np.uint8)), + {"width": width, "height": height, "mask_coverage": float(mask.mean()), + "settings": {key: value[0] for key, value in controls.items()}, + "palette": {"shadow": shadow, "midtone": midtone, "highlight": highlight}}, + ) + + +class LightPaletteDirector(NodeBase): + label = "Light & Palette Director" + category = "Image" + resizable = True + params = { + "image": {"label": "Image", "type": "image", "display": "input"}, + "shadow": {"label": "Shadow color", "type": "string", "default": "#173B60"}, + "midtone": {"label": "Midtone color", "type": "string", "default": "#E49C72"}, + "highlight": {"label": "Highlight color", "type": "string", "default": "#FFF1CC"}, + "center_x": {"label": "Region X", "type": "float", "default": 0.5, "min": 0, "max": 1, "step": 0.01}, + "center_y": {"label": "Region Y", "type": "float", "default": 0.5, "min": 0, "max": 1, "step": 0.01}, + "radius_x": {"label": "Region width", "type": "float", "default": 0.45, "min": 0.01, "max": 1, "step": 0.01}, + "radius_y": {"label": "Region height", "type": "float", "default": 0.48, "min": 0.01, "max": 1, "step": 0.01}, + "feather": {"label": "Feather", "type": "float", "default": 0.35, "min": 0.01, "max": 1, "step": 0.01}, + "strength": {"label": "Palette strength", "type": "float", "default": 0.65, "min": 0, "max": 1, "step": 0.01}, + "light_angle": {"label": "Light angle", "type": "float", "default": -45, "min": -180, "max": 180, "step": 1}, + "light_intensity": {"label": "Light intensity", "type": "float", "default": 0.5, "min": 0, "max": 1, "step": 0.01}, + "out_image": {"label": "Directed image", "type": "image", "display": "output"}, + "mask": {"label": "Soft mask", "type": "image", "display": "output"}, + "diagnostics": {"label": "Diagnostics", "type": "dict", "display": "output"}, + } + + def execute(self, image, shadow="#173B60", midtone="#E49C72", highlight="#FFF1CC", + center_x=0.5, center_y=0.5, radius_x=0.45, radius_y=0.48, feather=0.35, + strength=0.65, light_angle=-45, light_intensity=0.5): + images = image if isinstance(image, (list, tuple)) else [image] + if not 1 <= len(images) <= 4: + raise ValueError("Light & Palette Director accepts one to four decoded images per run.") + results = [direct_image(item, shadow=shadow, midtone=midtone, highlight=highlight, + center_x=center_x, center_y=center_y, radius_x=radius_x, + radius_y=radius_y, feather=feather, strength=strength, + light_angle=light_angle, light_intensity=light_intensity) for item in images] + def unpack(values): + return values[0] if len(values) == 1 else values + return {"out_image": unpack([item[0] for item in results]), + "mask": unpack([item[1] for item in results]), + "diagnostics": unpack([item[2] for item in results])} diff --git a/examples/custom_nodes/LightPaletteDirector/modiff_extension.json b/examples/custom_nodes/LightPaletteDirector/modiff_extension.json new file mode 100644 index 00000000..38d516ea --- /dev/null +++ b/examples/custom_nodes/LightPaletteDirector/modiff_extension.json @@ -0,0 +1 @@ +{ "runtimeRole": "data" } diff --git a/examples/custom_nodes/ModularImageReconstruction/README.md b/examples/custom_nodes/ModularImageReconstruction/README.md new file mode 100644 index 00000000..17aeed7c --- /dev/null +++ b/examples/custom_nodes/ModularImageReconstruction/README.md @@ -0,0 +1,19 @@ +# VAE Image Reconstruction + +Stage this directory through Developer → Add local source, inspect it and enable +the exact code. It is also a valid source layout for a pinned Hub Modular block. +It deliberately omits `model_input_names`, as some published Mellon sidecars do. +MoDiff derives a Models socket after approval from `expected_components`. + +Connect Load Models → Pipeline Components to Models, a decoded image to Image, +and expose Amount as a control (0 to 1) on a containing Block through Configure +Interface. Send the Image output to Preview Image. The block reconstructs the image using the connected `AutoencoderKL` and +blends it with the source. The loader continues to own weights, precision and +offload policy; this source installs no dependencies and downloads no models. + +Use an image with dimensions divisible by the VAE's spatial scale. The block uses +deterministic posterior mode and preserves the normal VAE forward hooks. It supports +compatible AutoencoderKL implementations, not every VAE class or modality. +When the VAE requests `force_upcast`, half-precision weights are temporarily used +in float32 and restored afterward, including on failure. This temporary memory +cost still needs to fit the selected resource policy. diff --git a/examples/custom_nodes/ModularImageReconstruction/block.py b/examples/custom_nodes/ModularImageReconstruction/block.py new file mode 100644 index 00000000..53183877 --- /dev/null +++ b/examples/custom_nodes/ModularImageReconstruction/block.py @@ -0,0 +1,54 @@ +"""Reuse an AutoencoderKL supplied by any compatible model loader.""" + +import torch +from PIL import Image +from diffusers import AutoencoderKL +from diffusers.image_processor import VaeImageProcessor +from diffusers.modular_pipelines import ComponentSpec, InputParam, ModularPipelineBlocks, OutputParam + + +class ImageReconstruction(ModularPipelineBlocks): + @property + def expected_components(self): + return [ComponentSpec(name="vae", type_hint=AutoencoderKL)] + + @property + def inputs(self): + return [InputParam(name="image", type_hint=Image.Image), InputParam(name="amount", type_hint=float, default=1.0)] + + @property + def intermediate_outputs(self): + return [OutputParam(name="images", type_hint=list)] + + @property + def description(self): + return "Reconstruct an image with a connected VAE and blend it with the source." + + @torch.no_grad() + def __call__(self, pipeline, state): + amount = state.get("amount") + if not 0 <= amount <= 1: + raise ValueError("Reconstruction amount must be between 0 and 1.") + vae = pipeline.vae + processor = VaeImageProcessor(vae_scale_factor=2 ** (len(vae.config.block_out_channels) - 1)) + original_dtype = vae.dtype + needs_upcast = original_dtype == torch.float16 and vae.config.force_upcast + # Calling forward preserves the loader's existing device/offload hooks. + # Posterior mode makes unchanged inputs deterministic; no generator is + # consumed and no new model is loaded or independently placed. + try: + # AutoencoderKL delegates this precision policy to its caller. + # Respect its declared setting without selecting a model family, + # and restore the shared component even when forward fails. + if needs_upcast: + vae.to(dtype=torch.float32) + sample = processor.preprocess(state.get("image")).to(device=pipeline._execution_device, dtype=vae.dtype) + reconstructed = vae(sample, sample_posterior=False, return_dict=False)[0] + finally: + if needs_upcast: + vae.to(dtype=original_dtype) + if not torch.isfinite(reconstructed).all(): + raise ValueError("The connected VAE produced non-finite values; review its precision settings.") + images = processor.postprocess(sample.lerp(reconstructed, amount), output_type="pil") + state.set("images", images) + return pipeline, state diff --git a/examples/custom_nodes/ModularImageReconstruction/mellon_pipeline_config.json b/examples/custom_nodes/ModularImageReconstruction/mellon_pipeline_config.json new file mode 100644 index 00000000..da143941 --- /dev/null +++ b/examples/custom_nodes/ModularImageReconstruction/mellon_pipeline_config.json @@ -0,0 +1,15 @@ +{ + "label": "VAE Image Reconstruction", + "node_params": { + "custom": { + "label": "VAE Image Reconstruction", + "params": { + "image": { "label": "Image", "type": "image", "display": "input" }, + "amount": { "label": "Amount", "type": "float", "default": 1.0, "min": 0, "max": 1 }, + "out_images": { "label": "Image", "type": "image", "display": "output" } + }, + "input_names": ["image", "amount"], + "output_names": ["out_images"] + } + } +} diff --git a/examples/custom_nodes/ModularImageReconstruction/modiff_extension.json b/examples/custom_nodes/ModularImageReconstruction/modiff_extension.json new file mode 100644 index 00000000..8b83de42 --- /dev/null +++ b/examples/custom_nodes/ModularImageReconstruction/modiff_extension.json @@ -0,0 +1 @@ +{ "runtimeRole": "connected_components" } diff --git a/examples/custom_nodes/ModularImageReconstruction/modular_config.json b/examples/custom_nodes/ModularImageReconstruction/modular_config.json new file mode 100644 index 00000000..32e20613 --- /dev/null +++ b/examples/custom_nodes/ModularImageReconstruction/modular_config.json @@ -0,0 +1,4 @@ +{ + "auto_map": { "ModularPipelineBlocks": "block.ImageReconstruction" }, + "requirements": {} +} diff --git a/examples/custom_nodes/ModularPrompt/block.py b/examples/custom_nodes/ModularPrompt/block.py new file mode 100644 index 00000000..efa93085 --- /dev/null +++ b/examples/custom_nodes/ModularPrompt/block.py @@ -0,0 +1,19 @@ +from diffusers.modular_pipelines import ModularPipelineBlocks, InputParam, OutputParam + + +class PromptSuffix(ModularPipelineBlocks): + @property + def inputs(self): + return [InputParam(name="text", type_hint=str, default="hello")] + + @property + def intermediate_outputs(self): + return [OutputParam(name="result", type_hint=str)] + + @property + def description(self): + return "A small custom Modular block with no model dependencies." + + def __call__(self, pipeline, state): + state.set("result", state.get("text") + " — modular") + return pipeline, state diff --git a/examples/custom_nodes/ModularPrompt/mellon_pipeline_config.json b/examples/custom_nodes/ModularPrompt/mellon_pipeline_config.json new file mode 100644 index 00000000..824d35f4 --- /dev/null +++ b/examples/custom_nodes/ModularPrompt/mellon_pipeline_config.json @@ -0,0 +1,15 @@ +{ + "label": "Modular Prompt", + "node_params": { + "custom": { + "label": "Modular Prompt", + "params": { + "text": { "label": "Prompt", "type": "string", "default": "hello" }, + "out_result": { "label": "Prompt", "type": "string", "display": "output" } + }, + "input_names": ["text"], + "model_input_names": [], + "output_names": ["out_result"] + } + } +} diff --git a/examples/custom_nodes/ModularPrompt/modiff_extension.json b/examples/custom_nodes/ModularPrompt/modiff_extension.json new file mode 100644 index 00000000..38d516ea --- /dev/null +++ b/examples/custom_nodes/ModularPrompt/modiff_extension.json @@ -0,0 +1 @@ +{ "runtimeRole": "data" } diff --git a/examples/custom_nodes/ModularPrompt/modular_config.json b/examples/custom_nodes/ModularPrompt/modular_config.json new file mode 100644 index 00000000..6fb54221 --- /dev/null +++ b/examples/custom_nodes/ModularPrompt/modular_config.json @@ -0,0 +1,4 @@ +{ + "auto_map": { "ModularPipelineBlocks": "block.PromptSuffix" }, + "requirements": {} +} diff --git a/examples/custom_nodes/PromptTools/__init__.py b/examples/custom_nodes/PromptTools/__init__.py new file mode 100644 index 00000000..f44ef41a --- /dev/null +++ b/examples/custom_nodes/PromptTools/__init__.py @@ -0,0 +1 @@ +from .main import PromptPrefix # noqa: F401 diff --git a/examples/custom_nodes/PromptTools/main.py b/examples/custom_nodes/PromptTools/main.py new file mode 100644 index 00000000..154825f6 --- /dev/null +++ b/examples/custom_nodes/PromptTools/main.py @@ -0,0 +1,35 @@ +from modiff.NodeBase import NodeBase + + +class PromptPrefix(NodeBase): + """Add a reusable prefix to a prompt without loading any models.""" + + label = "Prompt Prefix" + category = "Text" + resizable = True + params = { + "text": { + "label": "Prompt", + "type": "string", + "display": "textarea", + "default": "a quiet observatory", + }, + "prompt_input": { + "label": "Prompt Input", + "type": "string", + "display": "input", + "required": False, + "description": "Optional connected prompt. When connected, this replaces the inline Prompt value.", + }, + "prefix": { + "label": "Prefix", + "type": "string", + "display": "textarea", + "default": "Watercolor:", + }, + "result": {"label": "Prompt", "type": "string", "display": "output"}, + } + + def execute(self, text, prefix, prompt_input=None): + prompt = prompt_input if prompt_input is not None else text + return {"result": f"{prefix} {prompt}".strip()} diff --git a/examples/custom_nodes/PromptTools/modiff_extension.json b/examples/custom_nodes/PromptTools/modiff_extension.json new file mode 100644 index 00000000..38d516ea --- /dev/null +++ b/examples/custom_nodes/PromptTools/modiff_extension.json @@ -0,0 +1 @@ +{ "runtimeRole": "data" } diff --git a/examples/service/inputs.json b/examples/service/inputs.json new file mode 100644 index 00000000..807339f9 --- /dev/null +++ b/examples/service/inputs.json @@ -0,0 +1 @@ +{"prompt": "A small service prototype"} diff --git a/examples/service/interface.json b/examples/service/interface.json new file mode 100644 index 00000000..3b77a47a --- /dev/null +++ b/examples/service/interface.json @@ -0,0 +1,4 @@ +{ + "inputs": {"prompt": [{"nodeId": "prompt", "field": "text"}]}, + "outputs": {"text": [{"nodeId": "preview", "field": "preview"}]} +} diff --git a/examples/service/text-api.json b/examples/service/text-api.json new file mode 100644 index 00000000..15b53c01 --- /dev/null +++ b/examples/service/text-api.json @@ -0,0 +1,35 @@ +{ + "sid": "example", + "nodes": { + "prompt": { + "module": "modules.Primitive", + "action": "TextValue", + "params": { + "text": { + "value": "A small service prototype" + } + } + }, + "preview": { + "module": "modules.Primitive", + "action": "DataViewer", + "params": { + "value": { + "sourceId": "prompt", + "sourceKey": "output" + }, + "preview": { + "display": "ui_text", + "sourceKey": "output" + } + } + } + }, + "paths": [ + [ + "prompt", + "preview" + ] + ], + "runtimeHints": {} +} diff --git a/main.py b/main.py index 31a5d3cd..cec1016a 100644 --- a/main.py +++ b/main.py @@ -31,11 +31,15 @@ import asyncio import signal import subprocess +import threading +import time from pathlib import Path logger = logging.getLogger('modiff') SUPERVISED_RESTART_EXIT_CODE = 75 +MAX_RAPID_WORKER_FAILURES = 5 +STABLE_WORKER_SECONDS = 60.0 def handle_loop_exception(loop, context): @@ -122,6 +126,8 @@ def run_supervisor(): worker = None shutting_down = False + shutdown_event = threading.Event() + rapid_failures = 0 control_port = int(os.environ.get("MODIFF_SUPERVISOR_CONTROL_PORT", str(int(CONFIG.server["port"]) + 1))) requested_control_host = str(os.environ.get("MODIFF_SUPERVISOR_CONTROL_HOST", "127.0.0.1")) if requested_control_host not in {"127.0.0.1", "localhost"}: @@ -151,6 +157,7 @@ def run_supervisor(): def forward_signal(signum, _frame): nonlocal shutting_down shutting_down = True + shutdown_event.set() controller.set_shutting_down() if worker is not None and worker.poll() is None: worker.send_signal(signum) @@ -163,6 +170,7 @@ def forward_signal(signum, _frame): worker_env = os.environ.copy() worker_env["MODIFF_WORKER_SUPERVISED"] = "1" worker_env["MODIFF_SUPERVISOR_QUEUE_STATE"] = str(queue_state_path) + worker_started_at = time.monotonic() worker = subprocess.Popen(worker_process_command(worker_env), env=worker_env) controller.set_worker(worker) return_code = worker.wait() @@ -179,11 +187,24 @@ def forward_signal(signum, _frame): worker_pid=worker_pid, return_code=return_code, ) + if time.monotonic() - worker_started_at >= STABLE_WORKER_SECONDS: + rapid_failures = 0 + rapid_failures += 1 + if rapid_failures >= MAX_RAPID_WORKER_FAILURES: + logger.error( + "Backend worker failed %s times without a stable run; automatic replacement stopped. " + "Review the worker error and available memory before restarting MoDiff.", rapid_failures, + ) + return return_code + delay = min(2 ** (rapid_failures - 1), 16) logger.error( - "Backend worker %s exited unexpectedly with code %s; starting a clean replacement.", + "Backend worker %s exited unexpectedly with code %s; replacement in %ss (failure %s/%s).", worker_pid, return_code, + delay, rapid_failures, MAX_RAPID_WORKER_FAILURES, ) + if shutdown_event.wait(delay): + return return_code continue return return_code finally: diff --git a/modiff/NodeBase.py b/modiff/NodeBase.py index f493c9a7..c2287d0b 100644 --- a/modiff/NodeBase.py +++ b/modiff/NodeBase.py @@ -5,6 +5,7 @@ from contextvars import ContextVar from modiff.modelstore import modelstore from modiff.execution_input_provenance import capture_generation_inputs +from modiff.node_cache_identity import implementation_identity, input_snapshot from utils.memory_menager import memory_manager import numpy as np import torch @@ -91,7 +92,8 @@ def _emit(self, current, *, item=None, starting=False): progress = min(99, max(0, int(round(ratio * 100)))) step = max(0, min(int(current), int(total))) item_label = _loading_item_label(item) - if starting and item_label and "component" in self._description.lower(): + component_start = starting and item_label and "component" in self._description.lower() + if component_start: component_scope = "pipeline" if "pipeline component" in self._description.lower() else "model" message = f"Loading {component_scope} component {int(current) + 1}/{int(total)}: {item_label}" step = min(int(total), int(current) + 1) @@ -99,10 +101,14 @@ def _emit(self, current, *, item=None, starting=False): label = self._description or "Loading" message = f"{label} {step}/{int(total)}" now = time.monotonic() + # Weight iterators emit a start and completion for every tensor. Keep + # component transitions and boundary counts immediate, but rate-limit + # intermediate counts even when their percentage changes. Otherwise a + # cold load can queue hundreds of renders ahead of the user's next edit. if ( - not starting + self._last_report_progress is not None + and not component_start and step not in (0, int(total)) - and progress == self._last_report_progress and now - self._last_report_at < 0.25 ): return @@ -332,6 +338,9 @@ def __init__(self, node_id=None): self._has_changed = False self._cache_invalidated = False self._cache_valid = False + self._cache_reason = "empty" + self._cache_input_snapshot = None + self._cache_implementation = None self._execution_time = { 'last': None, 'min': None, 'max': None } self._memory_usage = { 'last': None, 'min': None, 'max': None } self._mm_models = [] @@ -477,15 +486,29 @@ def matches_option(candidate, option): key: value for key, value in params.items() if key in ignored_cache_params } ignored_params_changed = not deep_equal(previous_ignored_params, current_ignored_params) + snapshot = input_snapshot(current_cache_params) + implementation = implementation_identity(self) + # Authority checks in Modular adapters must run even when the frozen + # snapshot detects an in-place edit. Never turn tampering into a miss + # that bypasses their pre-dispatch validation. + params_equal = self._cache_invalidated or self._cache_params_equal(previous_cache_params, current_cache_params) # If any load-relevant value changed, or no successful result exists, execute the # node. Validated passthrough inputs are still recorded below so # diagnostics reflect the current graph invocation. - if ( - self._cache_invalidated - or (not self._cache_params_equal(previous_cache_params, current_cache_params)) - or not self._cache_valid - ): + self._cache_reason = ( + "invalidated" if self._cache_invalidated + else "models_evicted" if self._cache_valid and any( + memory_manager.get_model(model_id) is None for model_id in self._mm_models + ) + else "implementation_changed" if self._cache_valid and self._cache_implementation != implementation + else "inputs_changed" if self._cache_valid and self._cache_input_snapshot != snapshot + else "inputs_changed" if not params_equal + else "empty" if not self._cache_valid + else "usage_changed" if ignored_params_changed + else "unchanged_inputs" + ) + if self._cache_reason in {"invalidated", "models_evicted", "inputs_changed", "implementation_changed", "empty"}: self._cache_invalidated = False self._cache_valid = False self._has_changed = True @@ -534,6 +557,8 @@ def matches_option(candidate, option): # returned mapping (or fully populated trigger outputs) is reusable; # an exception or a missing result must leave this node invalid. self._cache_valid = isinstance(output, dict) or all(v is not None for v in self.output.values()) + self._cache_input_snapshot = snapshot + self._cache_implementation = implementation else: # A cache-ignored value can reconfigure the same resident output # without repeating its expensive construction. Preserve that diff --git a/modiff/authoring_examples.py b/modiff/authoring_examples.py new file mode 100644 index 00000000..5468d268 --- /dev/null +++ b/modiff/authoring_examples.py @@ -0,0 +1,81 @@ +"""Prompt examples for newly resolved operations, never saved graph migration. + +Creator-derived examples are deliberately short paraphrases. Numeric settings +continue to come from the reviewed execution profile, independently of examples. +""" + +_TASK_EXAMPLES = { + "text_to_image": "A red ceramic teapot on a wooden table beside a window, soft morning light, detailed photograph.", + "image_to_image": "A watercolor painting of the scene in the input image, preserving its composition and main subjects.", + "edit_image": "Change the background to a sunlit garden. Keep the main subject, its pose and its appearance unchanged.", + "multi_image_reference_edit": "Place the subjects from the reference images together in a sunlit garden, preserving their appearances.", + "inpaint": "A small vase of yellow flowers in the masked area, matching the surrounding lighting and perspective.", + "outpaint": "Continue the scene naturally beyond the original border, matching its lighting, perspective and style.", + "control_image": "A detailed photograph following the structure of the control image, with soft natural lighting.", + "text_to_audio": "Gentle rain falling on leaves, distant thunder, a quiet natural atmosphere without speech or music.", + "audio_continuation": "Continue the recording with the same atmosphere, rhythm and instrumentation.", + "audio_variation": "A variation of the input recording with the same style and mood.", + "audio_repaint": "Blend the regenerated section naturally with the surrounding audio.", +} + +# Reviewed creator model-card examples, paraphrased rather than fetched during +# discovery. Each entry is scoped to its actual repository and supported task. +_CREATOR_EXAMPLES = { + ("black-forest-labs/FLUX.2-klein-4B", "text_to_image"): ( + 'A curious cat holding a small sign reading "Welcome home", soft daylight and detailed fur.', + "https://huggingface.co/black-forest-labs/FLUX.2-klein-4B", + ), + ("Qwen/Qwen-Image", "text_to_image"): ( + 'A cozy cafe entrance with a chalkboard reading "Morning Coffee — $2" beside a glowing neon sign, cinematic composition.', + "https://huggingface.co/Qwen/Qwen-Image", + ), + ("stabilityai/stable-audio-open-1.0", "text_to_audio"): ( + "Clear, repeated hammer taps against a wooden workbench, close recording with natural room ambience.", + "https://huggingface.co/stabilityai/stable-audio-open-1.0", + ), + ("cvssp/audioldm2", "text_to_audio"): ( + "A hammer tapping a wooden board, distinct impacts and a quiet workshop background.", + "https://huggingface.co/cvssp/audioldm2", + ), +} + + +def seed_operation_example(node, repository=None): + """Called only while creating a resolved node schema, before authoring begins.""" + operation = node.get("operation", {}) + task = operation.get("task") + example = _TASK_EXAMPLES.get(task) + field = node.get("params", {}).get("prompt") + if ( + not example or not field or field.get("hidden") or field.get("display") == "output" + or ((field.get("value") or field.get("default")) and not field.get("fieldOptions", {}).get("exampleAttribution")) + ): + return + if repository is None and field.get("fieldOptions", {}).get("exampleAttribution"): + return + source = None + if (repository, task) in _CREATOR_EXAMPLES: + example, source = _CREATOR_EXAMPLES[(repository, task)] + elif operation.get("pipelineClass") == "QwenImage21Pipeline" and task in {"text_to_image", "edit_image"}: + source = "https://github.com/QwenLM/Qwen-Image-2.1" + if task == "text_to_image": + example = 'A glowing shop sign reading "HELLO", seen on a rainy evening with reflections across the street.' + elif task == "edit_image": + example = "Replace the background with a beach at sunset, preserving the subject." + elif repository == "black-forest-labs/FLUX.1-schnell" and task == "text_to_image": + source = "https://huggingface.co/black-forest-labs/FLUX.1-schnell" + example = 'A friendly cat presenting a handwritten sign reading "Welcome", detailed fur and soft daylight.' + elif repository == "Tongyi-MAI/Z-Image-Turbo" and task == "text_to_image": + source = "https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" + example = ( + "A portrait of a woman wearing embroidered crimson traditional clothing and a gold hair ornament, " + "holding a painted fan. A softly illuminated pagoda and colorful evening lights form the background." + ) + options = dict(field.get("fieldOptions") or {}) + options["exampleAttribution"] = "Adapted from creator guidance" if source else "MoDiff task example" + if source: + options["exampleSource"] = source + else: + options.pop("exampleSource", None) + field.update(value=example, fieldOptions=options) + node.setdefault("values", {})["prompt"] = example diff --git a/modiff/auto_resource.py b/modiff/auto_resource.py index ef7d6cdb..3f220c7b 100644 --- a/modiff/auto_resource.py +++ b/modiff/auto_resource.py @@ -1380,6 +1380,7 @@ def _normalized_measurement(measurement: dict[str, Any] | None) -> dict[str, Any "reservedBytes", "driverAllocatedBytes", "processRssBytes", + "peakMeasurementVersion", ): value = measurement.get(key) if value is None: diff --git a/modiff/block_definition_v2.py b/modiff/block_definition_v2.py index 2ad9ae71..8e010bfc 100644 --- a/modiff/block_definition_v2.py +++ b/modiff/block_definition_v2.py @@ -217,6 +217,7 @@ class BlockDefinitionV2(TypedDict): boundary: BlockBoundaryV2 controls: list[BlockControlV2] suggestedInputs: NotRequired[list[SuggestedInputSetV2]] + removedControlBindings: NotRequired[list[dict[str, str]]] previews: list[BlockPreviewBindingV2] ownership: BlockOwnershipV2 @@ -1473,6 +1474,22 @@ def _validate_previews(value: Any, *, node_ids: set[str]) -> None: raise ValueError("BlockDefinitionV2.previews may contain at most one primary binding.") +def _validate_removed_control_bindings(value: Any) -> None: + label = "removedControlBindings" + if not isinstance(value, list) or len(value) > 4096: + raise ValueError(f"{label} must be a bounded array.") + seen = set() + for entry in value: + binding = _object(entry, label) + _exact_keys(binding, label, required={"nodeId", "fieldId"}) + _string(binding["nodeId"], f"{label}.nodeId", maximum=384, pattern=_ID_RE) + _string(binding["fieldId"], f"{label}.fieldId", maximum=384) + key = (binding["nodeId"], binding["fieldId"]) + if key in seen: + raise ValueError(f"{label} must contain unique bindings.") + seen.add(key) + + def validate_block_definition_v2(payload: Any) -> BlockDefinitionV2: """Validate and clone one canonical ``BlockDefinitionV2``. @@ -1497,7 +1514,7 @@ def validate_block_definition_v2(payload: Any) -> BlockDefinitionV2: "previews", "ownership", }, - optional={"description", "suggestedInputs"}, + optional={"description", "suggestedInputs", "removedControlBindings"}, ) if definition["schemaVersion"] != 2 or isinstance(definition["schemaVersion"], bool): raise ValueError("BlockDefinitionV2.schemaVersion must be 2.") @@ -1537,6 +1554,8 @@ def validate_block_definition_v2(payload: Any) -> BlockDefinitionV2: ) if "suggestedInputs" in definition: _validate_suggested_inputs(definition["suggestedInputs"], control_ids=control_ids) + if "removedControlBindings" in definition: + _validate_removed_control_bindings(definition["removedControlBindings"]) _validate_previews(definition["previews"], node_ids=node_ids) block_instance_preview_bindings_v2(definition, graph) @@ -1796,8 +1815,9 @@ def _validate_route_selection_v1(value: Any) -> BlockRouteSelectionV1: raise ValueError(f"{path}.routeKey must match its inactiveDrafts key.") definition = validate_block_definition_v2(draft["definitionSnapshot"]) if ( - definition["ownership"] != {"kind": "registered", "definitionMutable": False} - or definition["source"]["kind"] not in _CATALOG_SOURCE_KINDS + not (route_set_id == "diffusers.definition-switch:v1" and definition["source"]["kind"] == "user") + and (definition["ownership"] != {"kind": "registered", "definitionMutable": False} + or definition["source"]["kind"] not in _CATALOG_SOURCE_KINDS) ): raise ValueError(f"{path} must contain one immutable registered definition.") validated = validate_block_instance_v2( @@ -1991,9 +2011,11 @@ def validate_block_instance_v2(payload: Any) -> BlockInstanceV2: presentation, "BlockInstanceV2.presentation", required={"expanded", "position", "size", "internalLayout"}, - optional={"internalLayoutMode", "collapsedContainerNodeIds"}, + optional={"internalLayoutMode", "collapsedContainerNodeIds", "removedControlBindings"}, ) _bool(presentation["expanded"], "BlockInstanceV2.presentation.expanded") + if "removedControlBindings" in presentation: + _validate_removed_control_bindings(presentation["removedControlBindings"]) position = _object(presentation["position"], "BlockInstanceV2.presentation.position") _exact_keys(position, "BlockInstanceV2.presentation.position", required={"x", "y"}) _finite_number(position["x"], "BlockInstanceV2.presentation.position.x") diff --git a/modiff/controlled_artifacts.py b/modiff/controlled_artifacts.py index 3148d1c4..d5583646 100644 --- a/modiff/controlled_artifacts.py +++ b/modiff/controlled_artifacts.py @@ -192,6 +192,82 @@ def _selection(value: Any) -> tuple[str, str, Mapping[str, Any]]: return source, selected, value +def portable_upscaler_selection(selection: Any) -> dict[str, Any]: + """Describe a complete pin without resolving cache refs, bytes or networks. + + Service portability is not execution approval: the normal artifact resolver + still checks repository containment, exact bytes and digest when invoked. + """ + source, selected, metadata = _selection(selection) + if source != "hub": + raise ValueError("A portable upscaler requires an exact Hub file selection.") + parts = selected.split("/") + if len(parts) < 3: + raise ValueError("A portable upscaler requires a repository and filename.") + size = metadata.get("byteSize") + if type(size) is not int or size <= 0: + raise ValueError("A portable upscaler requires a positive exact byteSize.") + return { + "repository": _exact_repository("/".join(parts[:2])), + "weightName": _exact_weight_name("/".join(parts[2:])), + "revision": _exact_revision(metadata.get("revision")), + "sha256": _exact_sha256(metadata.get("sha256"), required=True), + "byteSize": size, + } + + +def pin_upscaler_model_selection(selection: Any) -> dict[str, Any]: + """Persist an installed artifact's identity when the operator edits its selector. + + This authoring action may resolve the cache's current ref once, but execution + still requires the resulting immutable revision and rechecks its bytes. A + complete existing pin remains editable on a machine awaiting installation. + No repository enumeration, download or model deserialization is performed. + """ + source, selected, metadata = _selection(selection) + pinned = {"source": source, "value": selected} + digest = _exact_sha256(metadata.get("sha256"), required=False) + size = metadata.get("byteSize") + if size is not None and (isinstance(size, bool) or not isinstance(size, int) or size <= 0): + raise ValueError("Controlled artifact byteSize must be a positive integer.") + revision = metadata.get("revision") + resolved_cached_ref = False + if source == "hub": + parts = selected.split("/") + if len(parts) < 3: + raise ValueError("A controlled Hub upscaler must include repository and filename.") + repository = _exact_repository("/".join(parts[:2])) + weight_name = _exact_weight_name("/".join(parts[2:])) + revision = revision or resolve_model_revision(repository, source="hub") + if revision is None: + from utils.huggingface import cached_file_path + + cached = cached_file_path(repository, weight_name) + if not isinstance(cached, str): + raise FileNotFoundError("The selected upscaler file is not installed. Install it through Model Manager.") + snapshot = Path(cached) + for _part in PurePosixPath(weight_name).parts: + snapshot = snapshot.parent + # resolve_upscaler_artifact below also verifies the exact cache alias, + # repository containment and content before publishing this pin. + revision = snapshot.name + resolved_cached_ref = True + pinned["revision"] = _exact_revision(revision) + elif revision not in (None, ""): + raise ValueError("A local controlled artifact cannot carry a Hub revision.") + if digest is not None: + pinned["sha256"] = digest + if size is not None: + pinned["byteSize"] = size + if isinstance(metadata.get("license"), str): + pinned["license"] = metadata["license"] + if digest is not None and size is not None and not resolved_cached_ref: + return pinned + resolved = resolve_upscaler_artifact(pinned) + return {**pinned, "sha256": resolved.receipt["artifact"]["sha256"], + "byteSize": resolved.path.stat().st_size} + + def resolve_upscaler_artifact( selection: Any, *, diff --git a/modiff/custom_extension_api.py b/modiff/custom_extension_api.py new file mode 100644 index 00000000..c3b7f50b --- /dev/null +++ b/modiff/custom_extension_api.py @@ -0,0 +1,235 @@ +"""HTTP administration for operator-enabled Python, using the existing cache lease.""" + +import asyncio +import tempfile +from pathlib import Path +import nanoid +from aiohttp import web + +from modiff.custom_extensions import ExtensionStore, module_name, python_node_files + + +class CustomExtensionAPI: + def _extension_store(self): + return ExtensionStore() + + def _require_custom_module_enabled(self, module): + if isinstance(module, str) and module.startswith("custom."): + return self._extension_store().require_enabled(module.removeprefix("custom.")) + return None + + def _extension_payload(self, *, message=None, removed=()): + items = self._extension_store().list() + return { + "error": False, + "modules": items, + "count": len(items), + "instance": self.instance, + "message": message, + "removedCacheNodes": list(removed), + "root": str(self._extension_store().root), + } + + async def custom_modules_list(self, request): + # Listing/refresh are file inspection, not Python reload. + try: + return web.json_response(await asyncio.to_thread(self._extension_payload)) + except (ValueError, OSError) as error: + return web.json_response({"error": True, "message": str(error)}, status=400) + + async def custom_modules_refresh(self, request): + return await self.custom_modules_list(request) + + async def custom_modules_resolve(self, request): + # Network metadata inspection stays outside the execution/import lease. + from modiff.custom_extension_source import resolve_hub_extension + + try: + body = await self._strict_runtime_control_json( + request, allowed={"source", "revision"}, required={"source"} + ) + resolved = await asyncio.to_thread(resolve_hub_extension, **body) + return web.json_response({"error": False, "source": resolved}) + except (ValueError, OSError) as error: + return web.json_response({"error": True, "message": str(error)}, status=400) + + async def custom_modules_inspect(self, request): + try: + item = await asyncio.to_thread(self._extension_store().inspect, module_name(request.match_info["name"])) + return web.json_response({"error": False, "module": item}) + except (ValueError, OSError, SyntaxError) as error: + return web.json_response({"error": True, "message": str(error)}, status=400) + + async def _extension_mutation(self, operation): + # Do not change imports under either an active graph or a waiting graph. + if ( + self.current_task or not self.main_queue.empty() + or self._node_cache_lock.locked() or self._field_metadata_lock.locked() + ): + return web.json_response( + { + "error": True, + "message": "Wait for the running and queued work and active field updates to finish before changing custom code.", + }, + status=409, + ) + + async def guarded(): + self._node_cache_teardown_active = True + try: + return await self._run_executor_callback(operation) + finally: + self._node_cache_teardown_active = False + + try: + # Import/reload changes the registry used by presentation callbacks. + # Hold both leases, in this order, until even cancelled threads drain. + async with self._field_metadata_lock: + return web.json_response(await self._with_node_cache_lease(guarded)) + except (ValueError, OSError, SyntaxError) as error: + # Report resulting disabled state too; a failed import is not rollback + # of Python side effects, and an earlier approval must not survive it. + return web.json_response({"error": True, "message": str(error)[:2048]}, status=400) + + async def custom_modules_install(self, request): + try: + body = await self._strict_runtime_control_json( + request, allowed={"kind", "source", "name", "revision"}, required={"kind", "source", "name"} + ) + except ValueError as error: + return web.json_response({"error": True, "message": str(error)}, status=400) + + def stage(): + item = self._extension_store().stage(**body) + return { + **self._extension_payload(message="Source staged with execution disabled. Review before enabling."), + "module": item, + } + + return await self._extension_mutation(stage) + + async def custom_modules_add(self, request): + """The explicit Add action authorizes this exact import, never discovery.""" + try: + body = await self._strict_runtime_control_json( + request, allowed={"kind", "source", "name", "revision", "content", "consent"}, + required={"kind", "name", "consent"}, + # A 2 MiB Python file can expand sixfold in JSON \u escapes. + max_bytes=12 * 1024 * 1024 + 4096, + ) + if body["consent"] is not True: + raise ValueError("Adding a node requires permission to run its Python code.") + name = module_name(body["name"]) + kind = body["kind"] + if kind not in {"local", "hub", "git", "file"}: + raise ValueError("Select Local, Hugging Face or Git.") + if kind == "file": + content = body.get("content") + if not isinstance(content, str): + raise ValueError("Provide the Python node file contents.") + files = python_node_files(content.encode("utf-8")) + else: + if "content" in body: + raise ValueError("File contents are only accepted for a Python file import.") + source = body.get("source") + if not isinstance(source, str): + raise ValueError("Provide a source path or repository.") + if kind in {"git", "hub"}: + from modiff.custom_extension_source import resolve_git_extension, resolve_hub_extension + + resolver = resolve_git_extension if kind == "git" else resolve_hub_extension + resolved = await asyncio.to_thread(resolver, source, body.get("revision")) + source, body["revision"] = resolved["source"], resolved["revision"] + except (ValueError, OSError, SyntaxError) as error: + return web.json_response({"error": True, "message": str(error)}, status=400) + + def add(): + from modules import MODULE_MAP + + store = self._extension_store() + if kind == "file": + with tempfile.TemporaryDirectory(prefix="modiff-node-upload-") as temporary: + for filename, data in files.items(): + (Path(temporary) / filename).write_bytes(data) + item = store.stage(kind="local", source=temporary, name=name) + else: + item = store.stage(kind=kind, source=source, name=name, revision=body.get("revision")) + registry = store.enable(name, code_hash=item["codeHash"], consent=True) + self.modules[item["moduleKey"]] = registry + MODULE_MAP[item["moduleKey"]] = registry + self.instance = nanoid.generate(size=10) + return {**self._extension_payload(message="Custom nodes added and enabled."), "module": store.inspect(name)} + + return await self._extension_mutation(add) + + async def custom_modules_enable(self, request): + try: + name = module_name(request.match_info["name"]) + body = await self._strict_runtime_control_json( + request, allowed={"codeHash", "consent"}, required={"codeHash", "consent"} + ) + # Validate consent and preview identity before clearing any caches. + store = self._extension_store() + item = await asyncio.to_thread(store.inspect, name) + if body["consent"] is not True or body["codeHash"] != item["codeHash"]: + raise ValueError("Explicit consent for the current code hash is required. Inspect the source again.") + except (ValueError, OSError, SyntaxError) as error: + return web.json_response({"error": True, "message": str(error)}, status=400) + + def enable(): + from modules import MODULE_MAP + + key = f"custom.{name}" + # Check again under the execution lease, before invalidating results. + if store.inspect(name)["codeHash"] != body["codeHash"]: + raise ValueError("Source changed after inspection; review again.") + removed = self._release_cached_nodes( + [node_id for node_id, node in self.node_cache.items() if getattr(node, "module_name", None) == key] + ) + self.modules.pop(key, None) + MODULE_MAP.pop(key, None) + registry = store.enable(name, code_hash=body["codeHash"], consent=body["consent"]) + self.modules[key] = registry + MODULE_MAP[key] = registry + self.instance = nanoid.generate(size=10) + return self._extension_payload( + message="Custom code enabled. Its cached dependents were released.", removed=removed + ) + + return await self._extension_mutation(enable) + + async def custom_modules_reload(self, request): + return await self.custom_modules_enable(request) + + async def custom_modules_disable(self, request): + try: + name = module_name(request.match_info["name"]) + await self._strict_runtime_control_json(request, allowed=set(), allow_empty=True) + except ValueError as error: + return web.json_response({"error": True, "message": str(error)}, status=400) + + def disable(): + from modules import MODULE_MAP + + key = f"custom.{name}" + removed = self._release_cached_nodes( + [node_id for node_id, node in self.node_cache.items() if getattr(node, "module_name", None) == key] + ) + self._extension_store().disable(name) + self.modules.pop(key, None) + MODULE_MAP.pop(key, None) + self.instance = nanoid.generate(size=10) + return self._extension_payload( + message="Custom code disabled. Restart to remove any import side effects.", removed=removed + ) + + return await self._extension_mutation(disable) + + async def custom_modules_update(self, request): + return web.json_response( + { + "error": True, + "message": "Moving-branch updates are retired. Stage an exact revision under a new module name, inspect it, then enable explicitly.", + }, + status=409, + ) diff --git a/modiff/custom_extension_source.py b/modiff/custom_extension_source.py new file mode 100644 index 00000000..201d2d43 --- /dev/null +++ b/modiff/custom_extension_source.py @@ -0,0 +1,85 @@ +"""Resolve an operator-selected Hub source without downloading or importing code.""" + +import re +from urllib.parse import unquote, urlsplit + +from modiff.custom_extensions import ExtensionError, immutable_revision + + +def resolve_git_extension(source, revision=None): + """Resolve remote refs without checkout, hooks, terminal prompts or code imports.""" + import os + import subprocess + + if not isinstance(source, str) or len(source) > 2048: + raise ExtensionError("Enter an HTTPS Git repository URL.") + url = urlsplit(source) + if url.scheme != "https" or not url.hostname or url.username or url.password or url.query or url.fragment: + raise ExtensionError("Use an HTTPS Git URL without credentials, query or fragment.") + requested = revision or "HEAD" + if not isinstance(requested, str) or len(requested) > 256 or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._/-]*", requested) or ".." in requested: + raise ExtensionError("The revision must be a branch, tag or exact commit.") + if re.fullmatch(r"[a-f0-9]{40}", requested): + resolved = requested + else: + refs = [requested] if requested == "HEAD" else [f"refs/heads/{requested}", f"refs/tags/{requested}", f"refs/tags/{requested}^{{}}"] + try: + result = subprocess.run( + ["git", "-c", "credential.interactive=false", "ls-remote", "--exit-code", source, *refs], + capture_output=True, timeout=30, + env={**os.environ, "GIT_TERMINAL_PROMPT": "0", "GCM_INTERACTIVE": "Never"}, + ) + matches = [line.split() for line in result.stdout.decode("utf-8").splitlines()] + if result.returncode or not matches: + raise ValueError("Missing ref") + # Prefer an annotated tag's peeled commit; reject ambiguous branch/tag names. + peeled = {row[0] for row in matches if len(row) == 2 and row[1].endswith("^{}")} + commits = {row[0] for row in matches if len(row) == 2 and not row[1].startswith("refs/tags/")} + commits |= peeled or {row[0] for row in matches if len(row) == 2 and row[1].startswith("refs/tags/")} + if len(commits) != 1: + raise ValueError("Ambiguous ref") + resolved = immutable_revision(commits.pop()) + except (OSError, ValueError, subprocess.TimeoutExpired) as error: + raise ExtensionError("Could not resolve Git revision. Check URL, access and revision; use an exact commit for ambiguous names.") from error + return {"kind": "git", "source": source, "requestedRevision": requested, "revision": resolved} + + +def resolve_hub_extension(source, revision=None): + from huggingface_hub import HfApi + from huggingface_hub.utils import validate_repo_id + + if not isinstance(source, str) or not source.strip() or len(source) > 2048: + raise ExtensionError("Enter a Hugging Face repository ID or HTTPS repository URL.") + source = source.strip() + url_revision = None + if "://" in source: + url = urlsplit(source) + if url.scheme != "https" or url.netloc != "huggingface.co" or url.query or url.fragment: + raise ExtensionError("Use a huggingface.co HTTPS repository URL without credentials, query or fragment.") + parts = url.path.strip("/").split("/") + if len(parts) == 4 and parts[2] == "tree": + url_revision = unquote(parts.pop()) + parts.pop() + if len(parts) not in {1, 2} or parts[0] in {"datasets", "spaces"}: + raise ExtensionError("Use the repository root or its /tree/ URL, not a file or subfolder.") + source = "/".join(parts) + validate_repo_id(source) + if revision is not None and not isinstance(revision, str): + raise ExtensionError("The revision must be a branch, tag or exact commit.") + revision = (revision or "").strip() + if url_revision and revision and url_revision != revision: + raise ExtensionError("The URL and revision field disagree. Choose one revision before resolving.") + requested = revision or url_revision or "main" + if len(requested) > 256 or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._/-]*", requested) or ".." in requested: + raise ExtensionError("The revision must be a branch, tag or exact commit.") + try: + info = HfApi(endpoint="https://huggingface.co").model_info(source, revision=requested, timeout=20) + except Exception as error: + # Provider exceptions can contain authenticated URLs or request details. + raise ExtensionError( + "Could not resolve the source. Check the repository, revision, network and Hub access." + ) from error + resolved = immutable_revision(info.sha) + if re.fullmatch(r"[a-f0-9]{40}", requested) and resolved != requested: + raise ExtensionError("Hugging Face returned a different revision. No source was staged.") + return {"kind": "hub", "source": source, "requestedRevision": requested, "revision": resolved} diff --git a/modiff/custom_extensions.py b/modiff/custom_extensions.py new file mode 100644 index 00000000..4b00b980 --- /dev/null +++ b/modiff/custom_extensions.py @@ -0,0 +1,699 @@ +"""Explicit, content-bound local extension approvals. This is not a sandbox. + +Staging and inspection only read files/metadata. Imports happen on enable or at +startup for an unchanged approved package. Dependencies are never installed here. +""" + +from __future__ import annotations + +import ast +import hashlib +import importlib +import importlib.abc +import importlib.machinery +import importlib.metadata +import json +import os +from pathlib import Path +import re +import subprocess +import sys +import tempfile +import tomllib +from types import ModuleType + +from packaging.requirements import Requirement, InvalidRequirement + +MAX_FILES = 256 +MAX_FILE_BYTES = 2 * 1024 * 1024 +MAX_TOTAL_BYTES = 16 * 1024 * 1024 +SOURCE_SUFFIXES = {".py", ".json", ".toml", ".txt", ".md", ".yaml", ".yml", ".js", ".jsx", ".css", ".svg"} +MODULE_NAME = re.compile(r"^[A-Za-z][A-Za-z0-9_]{0,63}$") +REVISION = re.compile(r"^[a-f0-9]{40}$") + + +class ExtensionError(ValueError): + pass + + +def module_name(value): + if not isinstance(value, str) or not MODULE_NAME.fullmatch(value): + raise ExtensionError( + "Module name must start with a letter and contain up to 64 letters, numbers or underscores." + ) + return value + + +def immutable_revision(value): + if not isinstance(value, str) or not REVISION.fullmatch(value): + raise ExtensionError("Remote extensions require an exact lowercase 40-character commit revision.") + return value + + +def _linked(path): + return path.is_symlink() or bool(getattr(path, "is_junction", lambda: False)()) + + +def json_object(content, label): + try: + value = json.loads(content) + except (ValueError, RecursionError) as error: + raise ExtensionError(f"{label} must contain bounded valid JSON.") from error + if not isinstance(value, dict): + raise ExtensionError(f"{label} must contain a JSON object.") + return value + + +def source_files(root, *, hub=False): + """Read a bounded code package; never copy weights, environments or credentials.""" + if not _linked(root) and root.is_file() and root.suffix == ".py": + if root.stat().st_size > MAX_FILE_BYTES: + raise ExtensionError("Python node file exceeds 2 MiB.") + with root.open("rb") as reader: + return python_node_files(reader.read(MAX_FILE_BYTES + 1)) + if _linked(root) or not root.is_dir(): + raise ExtensionError("Extension source must be a regular directory, not a link.") + files, total, count = {}, 0, 0 + pending = [(root, 0)] + while pending: + parent, depth = pending.pop() + for path in sorted(parent.iterdir()): + count += 1 + if count > 16384 or depth > 16: + raise ExtensionError("Extension directory exceeds the bounded scan limit.") + if path.name.startswith(".") or path.name == "__pycache__": + continue + if _linked(path): + # Hub snapshots use file links into this repository's blobs. + blob_root = root.parent.parent / "blobs" + if not hub or path.is_dir() or _linked(blob_root): + raise ExtensionError("Extension sources cannot contain links.") + try: + path.resolve(strict=True).relative_to(blob_root.resolve(strict=True)) + except (ValueError, OSError) as error: + raise ExtensionError("Hub source link escaped its repository blobs.") from error + if path.is_dir(): + pending.append((path, depth + 1)) + elif path.suffix.lower() in SOURCE_SUFFIXES: + if not path.is_file() or path.stat().st_size > MAX_FILE_BYTES: + raise ExtensionError("Extension file is not regular or exceeds 2 MiB.") + with path.open("rb") as reader: + content = reader.read(MAX_FILE_BYTES + 1) + total += len(content) + if len(content) > MAX_FILE_BYTES or total > MAX_TOTAL_BYTES or len(files) >= MAX_FILES: + raise ExtensionError("Extension source exceeds its file/byte limit.") + files[path.relative_to(root).as_posix()] = content + return files + + +def python_node_files(content): + """Wrap a single declared node without executing it or guessing dependencies.""" + if not isinstance(content, bytes) or len(content) > MAX_FILE_BYTES: + raise ExtensionError("Python node file exceeds 2 MiB.") + try: + text = content.decode("utf-8-sig") + tree = ast.parse(text, filename="main.py") + except (UnicodeError, SyntaxError) as error: + raise ExtensionError(f"Invalid Python node: {error}") from error + declarations = {} + for statement in tree.body: + if isinstance(statement, ast.Assign): + for target in statement.targets: + if isinstance(target, ast.Name) and target.id in {"MODIFF_RUNTIME_ROLE", "MODIFF_REQUIREMENTS"}: + try: + declarations[target.id] = ast.literal_eval(statement.value) + except (ValueError, TypeError) as error: + raise ExtensionError(f"{target.id} must be a literal declaration.") from error + requirements = declarations.get("MODIFF_REQUIREMENTS", []) + if not isinstance(requirements, list) or any(not isinstance(item, str) or "\n" in item for item in requirements): + raise ExtensionError("MODIFF_REQUIREMENTS must be a list of package requirement strings.") + files = { + "main.py": text.encode("utf-8"), + "__init__.py": b"from .main import *\n", + "modiff_extension.json": json.dumps({"runtimeRole": declarations.get("MODIFF_RUNTIME_ROLE", "manual")}).encode(), + "requirements.txt": "\n".join(requirements).encode(), + } + runtime_role(files) + if not _preview(files)["nodes"]: + raise ExtensionError("No NodeBase node definition found. Declare a NodeBase subclass with params and execute().") + return files + + +def dependencies(files): + declared = [] + if "requirements.txt" in files: + declared.extend( + line.strip() + for line in files["requirements.txt"].decode().splitlines() + if line.strip() and not line.lstrip().startswith("#") + ) + if "pyproject.toml" in files: + project = tomllib.loads(files["pyproject.toml"].decode()).get("project", {}) + values = project.get("dependencies", []) if isinstance(project, dict) else None + if not isinstance(values, list) or any(not isinstance(value, str) for value in values): + raise ExtensionError("Project dependencies must be a list of requirement strings.") + declared.extend(values) + if "modular_config.json" in files: + requirements = json_object(files["modular_config.json"], "modular_config.json").get("requirements") or {} + if isinstance(requirements, dict): + declared.extend(f"{name}{specifier}" for name, specifier in requirements.items()) + elif isinstance(requirements, list): + declared.extend(requirements) + else: + raise ExtensionError("Modular requirements must be a package/specifier mapping or list.") + if len(declared) > MAX_FILES: + raise ExtensionError("An extension may declare at most 256 dependencies.") + result = [] + if any(not isinstance(text, str) for text in declared): + raise ExtensionError("Dependency declarations must be strings.") + for text in dict.fromkeys(declared): + if not isinstance(text, str) or len(text) > 512: + raise ExtensionError("Dependency declarations must be bounded requirement strings.") + try: + requirement = Requirement(text) + if requirement.url: + raise InvalidRequirement("Direct URL dependencies require manual review.") + installed = importlib.metadata.version(requirement.name) + applies = requirement.marker is None or requirement.marker.evaluate() + status = "satisfied" if not applies or installed in requirement.specifier else "incompatible" + except importlib.metadata.PackageNotFoundError: + installed = None + status = "satisfied" if requirement.marker and not requirement.marker.evaluate() else "missing" + except InvalidRequirement: + installed, status = None, "manual_review" + result.append({"requirement": text, "installed": installed, "status": status}) + return result + + +def code_identity(files, deps): + digest = hashlib.sha256(b"modiff-extension-v1\0") + for key, content in sorted(files.items()): + digest.update(key.encode() + b"\0" + hashlib.sha256(content).digest()) + digest.update(json.dumps(deps, sort_keys=True).encode()) + return "sha256:" + digest.hexdigest() + + +def runtime_role(files): + """Operator-reviewed resource declaration; never model qualification.""" + config = json_object(files.get("modiff_extension.json", b"{}"), "modiff_extension.json") + if not isinstance(config, dict) or set(config) - {"runtimeRole"}: + raise ExtensionError("modiff_extension.json only accepts runtimeRole.") + role = config.get("runtimeRole", "manual") + if not isinstance(role, str) or role not in {"manual", "data", "connected_components"}: + raise ExtensionError("runtimeRole must be manual, data or connected_components.") + return role + + +def _preview(files): + """Literal Python metadata or validated Mellon/MoDiff JSON; no eval/import.""" + sidecar = next( + (name for name in ("modiff_pipeline_config.json", "mellon_pipeline_config.json") if name in files), None + ) + if sidecar and "modular_config.json" in files: + from modules.ModularDiffusers.pipeline_schema import MoDiffPipelineConfig + from modules.ModularDiffusers.dynamic_node import _custom_node_contract + + config = MoDiffPipelineConfig.from_json_bytes( + files[sidecar], source_label=sidecar, allow_omitted_custom_model_inputs=True + ) + contract = _custom_node_contract(config) + for name in [*contract["input_names"], *contract["model_input_names"]]: + if name not in contract["params"]: + raise ExtensionError(f"Modular sidecar input {name} needs a declared field.") + contract["params"][name]["isInput"] = True + return { + "kind": "modular", + "nodes": { + "Block": { + "type": "custom", + "label": contract.get("label") or config.label or "Custom Modular Block", + "category": "Custom", + "description": "Operator-enabled Modular Diffusers block.", + "params": contract["params"], + } + }, + "contract": contract, + "diagnostics": [], + } + if "__init__.py" not in files or "main.py" not in files: + raise ExtensionError("Provide __init__.py and main.py, or modular_config.json and a Mellon/MoDiff sidecar.") + nodes, diagnostics = {}, [] + for filename, content in files.items(): + if not filename.endswith(".py"): + continue + tree = ast.parse(content, filename=filename) + if filename != "main.py": + continue + constants = {} + for statement in tree.body: + if isinstance(statement, ast.Assign): + try: + value = ast.literal_eval(statement.value) + for target in statement.targets: + if isinstance(target, ast.Name): + constants[target.id] = value + except (ValueError, TypeError): + pass + if not isinstance(statement, ast.ClassDef) or not any( + isinstance(base, ast.Name) and base.id == "NodeBase" for base in statement.bases + ): + continue + node = { + "type": "custom", + "label": statement.name, + "category": "Custom", + "description": ast.get_docstring(statement) or "", + "params": {}, + } + for field in statement.body: + if not isinstance(field, ast.Assign): + continue + for target in field.targets: + if not isinstance(target, ast.Name) or target.id not in node: + continue + try: + node[target.id] = ( + constants[field.value.id] + if isinstance(field.value, ast.Name) + else ast.literal_eval(field.value) + ) + except (KeyError, ValueError, TypeError): + diagnostics.append( + f"{statement.name}.{target.id} requires import; shown after explicit enable." + ) + nodes[statement.name] = node + if len(nodes) > 256: + raise ExtensionError("An extension may declare at most 256 nodes.") + for action, node in nodes.items(): + if not isinstance(node["label"], str) or not node["label"]: + node["label"] = action + if not isinstance(node["params"], dict) or len(node["params"]) > 256: + raise ExtensionError("Node params must be an object with at most 256 fields.") + for key, field in node["params"].items(): + if ( + not isinstance(key, str) + or key in {"__proto__", "constructor", "prototype"} + or not isinstance(field, dict) + ): + raise ExtensionError("Node params must map safe field names to objects.") + json.dumps(node, allow_nan=False) + return {"kind": "python", "nodes": nodes, "diagnostics": diagnostics} + + +class _FreshSourceLoader(importlib.machinery.SourceFileLoader): + def __init__(self, fullname, path, content): + super().__init__(fullname, path) + self.content = content + + def get_code(self, fullname): + # Python's timestamp/size pyc cache can serve stale code after a fast edit. + return compile(self.content, self.path, "exec", dont_inherit=True) + + +class _PackageFinder(importlib.abc.MetaPathFinder): + def __init__(self, prefix, root, files): + self.prefix, self.root, self.files = prefix, root, files + + def find_spec(self, fullname, path=None, target=None): + if fullname != self.prefix and not fullname.startswith(self.prefix + "."): + return None + relative = fullname[len(self.prefix) :].lstrip(".").split(".") if fullname != self.prefix else [] + base = self.root.joinpath(*relative) + package_key = "/".join([*relative, "__init__.py"]) + source_key = "/".join(relative) + ".py" + selected = package_key if package_key in self.files else source_key + filename = self.root / selected + if selected in self.files: + return importlib.util.spec_from_file_location( + fullname, + filename, + loader=_FreshSourceLoader(fullname, str(filename), self.files[selected]), + submodule_search_locations=[str(base)] if selected == package_key else None, + ) + prefix = "/".join(relative) + "/" + if relative and any(name.startswith(prefix) for name in self.files): + spec = importlib.machinery.ModuleSpec(fullname, loader=None, is_package=True) + spec.submodule_search_locations = [] + return spec + raise ModuleNotFoundError( + f"{fullname} is absent from the approved source snapshot. Review and reload changed code." + ) + + +class ExtensionStore: + def __init__(self, root=None): + self.root = Path(root or "custom").absolute() + if _linked(self.root): + raise ExtensionError("The custom module root must not be a link.") + + def path(self, name): + name = module_name(name) + for path in (self.root / name, self.root / ".disabled" / name): + if path.exists() or path.is_symlink(): + if _linked(path) or _linked(path.parent): + raise ExtensionError("Installed module directories must not be links.") + return path + standalone = self.root / f"{name}.py" + if standalone.exists() or standalone.is_symlink(): + if _linked(standalone): + raise ExtensionError("Custom node files must not be links.") + return standalone + return self.root / name + + def _state(self): + file = self.root / ".extensions.json" + if not file.exists(): + return {} + if _linked(file) or file.stat().st_size > MAX_FILE_BYTES: + raise ExtensionError("Invalid extension approval file.") + state = json_object(file.read_text(), "extension approval file") + if any(not isinstance(record, dict) for record in state.values()): + raise ExtensionError("Invalid extension approval file.") + return state + + def _save(self, name, record): + self.root.mkdir(parents=True, exist_ok=True) + state = self._state() + state[name] = record + fd, temporary = tempfile.mkstemp(prefix=".extensions-", dir=self.root) + try: + with os.fdopen(fd, "w") as writer: + json.dump(state, writer, sort_keys=True) + os.replace(temporary, self.root / ".extensions.json") + finally: + Path(temporary).unlink(missing_ok=True) + + def inspect(self, name): + path = self.path(name) + files = source_files(path) + deps = dependencies(files) + code_hash = code_identity(files, deps) + record = self._state().get(name, {}) + enabled = record.get("enabled") is True and record.get("codeHash") == code_hash + preview = _preview(files) + approved_nodes = record.get("nodes") if enabled else None + if not isinstance(approved_nodes, list) or any(not isinstance(node, str) for node in approved_nodes): + approved_nodes = list(preview["nodes"]) + return { + "name": name, + "moduleKey": f"custom.{name}", + "source": "custom", + "kind": record.get("kind", "local"), + "revision": record.get("revision"), + "runtimeRole": runtime_role(files), + "enabled": enabled, + "status": "enabled" if enabled else "changed" if record.get("enabled") else "disabled", + "path": str(path), + "codeHash": code_hash, + "approvedHash": record.get("codeHash"), + "files": [ + {"name": key, "bytes": len(value), "sha256": hashlib.sha256(value).hexdigest()} + for key, value in sorted(files.items()) + ], + "dependencies": deps, + "preview": preview, + "diagnostic": record.get("diagnostic"), + "nodes": sorted(approved_nodes), + "nodeCount": len(approved_nodes) if enabled else 0, + "hasInit": "__init__.py" in files, + "hasMain": "main.py" in files, + "hasGit": record.get("kind") == "git", + "canUpdate": False, + "canDisable": bool(record.get("enabled")), + "canEnable": not enabled, + } + + def list(self): + names = set() + for directory in (self.root, self.root / ".disabled"): + if directory.is_dir() and not _linked(directory): + names.update(p.name for p in directory.iterdir() if p.is_dir() and MODULE_NAME.fullmatch(p.name)) + if self.root.is_dir(): + names.update(p.stem for p in self.root.glob("*.py") if MODULE_NAME.fullmatch(p.stem) and p.stem != "__init__") + result = [] + for name in sorted(names): + try: + result.append(self.inspect(name)) + except (OSError, ValueError, SyntaxError) as error: + result.append( + { + "name": name, + "moduleKey": f"custom.{name}", + "source": "custom", + "enabled": False, + "status": "error", + "path": str(self.root / name), + "codeHash": None, + "dependencies": [], + "files": [], + "preview": None, + "diagnostic": f"{type(error).__name__}: {error}"[:2048], + "nodes": [], + "nodeCount": 0, + "hasGit": False, + "canUpdate": False, + "canDisable": True, + "canEnable": False, + } + ) + return result + + def stage(self, *, kind, source, name, revision=None): + name = module_name(name) + if self.path(name).exists(): + raise ExtensionError("Module already exists. Edit/reload it, or stage a revision under a new name.") + if not isinstance(source, str) or not source.strip() or len(source) > 4096: + raise ExtensionError("A bounded source string is required.") + if not isinstance(kind, str) or kind not in {"local", "git", "hub"}: + raise ExtensionError("Source kind must be local, git or hub.") + if kind != "local": + immutable_revision(revision) + with tempfile.TemporaryDirectory(prefix="modiff-extension-") as temporary: + if kind == "git": + from urllib.parse import urlsplit + + url = urlsplit(source) + if ( + url.scheme != "https" + or not url.hostname + or url.username + or url.password + or url.query + or url.fragment + ): + raise ExtensionError("Git sources require an HTTPS URL without credentials, query or fragment.") + checkout = Path(temporary) / "checkout" + + def git(*args): + try: + result = subprocess.run( + ["git", "-c", f"core.hooksPath={temporary}/empty-hooks", *args], + capture_output=True, + timeout=120, + env={**os.environ, "GIT_TERMINAL_PROMPT": "0"}, + ) + except (OSError, subprocess.TimeoutExpired) as error: + raise ExtensionError( + "Git staging could not finish. Verify local Git access and retry." + ) from error + if result.returncode: + raise ExtensionError("Git staging failed; verify the URL, commit and local Git access.") + return result.stdout + + git("init", str(checkout)) + git("-C", str(checkout), "remote", "add", "origin", source) + git("-C", str(checkout), "config", "remote.origin.promisor", "true") + git("-C", str(checkout), "config", "remote.origin.partialclonefilter", "blob:none") + git("-C", str(checkout), "fetch", "--filter=blob:none", "--depth=1", "origin", revision) + if git("-C", str(checkout), "rev-parse", "FETCH_HEAD").decode().strip() != revision: + raise ExtensionError("Git returned a different revision.") + files, total = {}, 0 + entries = git("-C", str(checkout), "ls-tree", "-rz", "--full-tree", "FETCH_HEAD").split(b"\0") + if len(entries) > 16384: + raise ExtensionError("Git source exceeds the bounded tree scan limit.") + for entry in entries: + if not entry: + continue + header, raw_path = entry.split(b"\t", 1) + mode, object_type, object_id = header.decode().split(" ") + relative = raw_path.decode("utf-8") + parts = relative.split("/") + if any(part.startswith(".") or part == "__pycache__" for part in parts): + continue + if mode == "120000" or object_type != "blob": + raise ExtensionError( + "Git source links and submodules must be replaced with reviewed regular source files." + ) + if Path(relative).suffix.lower() not in SOURCE_SUFFIXES: + continue + if ( + len(parts) > 16 + or any(part in {"", ".."} for part in parts) + or "\\" in relative + or ":" in relative + ): + raise ExtensionError("Invalid Git source path.") + size = int(git("-C", str(checkout), "cat-file", "-s", object_id)) + if size > MAX_FILE_BYTES or total + size > MAX_TOTAL_BYTES or len(files) >= MAX_FILES: + raise ExtensionError("Git source exceeds its file/byte limit.") + files[relative] = git("-C", str(checkout), "cat-file", "blob", object_id) + total += size + elif kind == "hub": + from huggingface_hub import snapshot_download + from huggingface_hub.utils import validate_repo_id + + validate_repo_id(source) + snapshot = Path( + snapshot_download( + source, revision=revision, allow_patterns=["*.py", "*.json", "*.toml", "*.txt", "*.md"] + ) + ) + if ( + snapshot.name != revision + or snapshot.parent.name != "snapshots" + or any(_linked(path) for path in (snapshot, snapshot.parent, snapshot.parent.parent)) + ): + raise ExtensionError("Hub returned a different snapshot revision.") + files = source_files(snapshot, hub=True) + else: + files = source_files(Path(source).expanduser().absolute()) + _preview(files) + dependencies(files) + runtime_role(files) + staged = Path(temporary) / "package" + staged.mkdir() + for relative, content in files.items(): + target = staged / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(content) + self.root.mkdir(parents=True, exist_ok=True) + # Copy into a same-filesystem temporary directory before atomic publication. + import shutil + + with tempfile.TemporaryDirectory(prefix=".stage-", dir=self.root) as publish: + shutil.copytree(staged, Path(publish) / name) + os.rename(Path(publish) / name, self.root / name) + self._save(name, {"kind": kind, "revision": revision, "enabled": False}) + return self.inspect(name) + + def require_enabled(self, name): + item = self.inspect(name) + if not item["enabled"]: + raise ExtensionError( + "Custom module is disabled or its code/dependencies changed. Review and enable/reload it in Custom nodes." + ) + return item + + def asset(self, name, file): + if ( + not isinstance(file, str) + or not file + or "\\" in file + or any(part in {".", "..", ""} for part in file.split("/")) + ): + raise ExtensionError("Invalid custom asset path.") + files = source_files(self.path(name)) + record = self._state().get(name, {}) + if record.get("enabled") is not True or record.get("codeHash") != code_identity(files, dependencies(files)): + raise ExtensionError("Custom browser assets require approval for the current source.") + try: + return files["web/" + file] + except KeyError as error: + raise FileNotFoundError("Custom asset not found in the approved package.") from error + + def unload(self, name): + prefix = f"custom.{module_name(name)}" + for key in list(sys.modules): + if key == prefix or key.startswith(prefix + "."): + sys.modules.pop(key, None) + sys.meta_path[:] = [ + finder + for finder in sys.meta_path + if not isinstance(finder, _PackageFinder) + or not (finder.prefix == prefix or finder.prefix.startswith(prefix + ".")) + ] + parent = sys.modules.get("custom") + if parent is not None: + parent.__dict__.pop(name, None) + importlib.invalidate_caches() + + def _load(self, item): + name = item["name"] + files = source_files(Path(item["path"])) + if code_identity(files, dependencies(files)) != item["codeHash"]: + raise ExtensionError("Source changed before import. Review and reload after editing is complete.") + self.unload(name) + # Never execute a root custom/__init__.py while discovering packages. + if "custom" not in sys.modules: + namespace = ModuleType("custom") + namespace.__path__ = [] + sys.modules["custom"] = namespace + if item["preview"]["kind"] == "modular": + from modiff.custom_modular_extension import load_modular_extension + + return load_modular_extension(item, files) + sys.meta_path.insert(0, _PackageFinder(item["moduleKey"], Path(item["path"]), files)) + package = importlib.import_module(item["moduleKey"]) + main = importlib.import_module(f"{item['moduleKey']}.main") + from modules import parse_node_class + from modiff.NodeBase import NodeBase + + registry = dict(getattr(package, "MODULE_MAP", {})) + for stem in getattr(package, "MODULE_PARSE", ["main"]): + if not isinstance(stem, str) or not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", stem): + raise ExtensionError("MODULE_PARSE must list simple Python file stems.") + tree = ast.parse(files.get(f"{stem}.py", b""), filename=f"{stem}.py") + for node in ast.walk(tree): + if ( + isinstance(node, ast.ClassDef) + and node.name not in registry + and any(isinstance(base, ast.Name) and base.id == "NodeBase" for base in node.bases) + ): + registry[node.name] = parse_node_class(node, package) + if not registry: + raise ExtensionError("No NodeBase classes were registered; export them through main.py and __init__.py.") + for action in registry: + klass = getattr(main, action, None) + if not isinstance(klass, type) or not issubclass(klass, NodeBase): + raise ExtensionError(f"{action} must be a NodeBase subclass exported from main.py.") + if not callable(getattr(klass, "execute", None)): + raise ExtensionError(f"{action} must implement execute() returning its declared outputs.") + json.dumps(registry, allow_nan=False) + return registry + + def enable(self, name, *, code_hash, consent): + if consent is not True: + raise ExtensionError("Explicit code execution consent is required.") + item = self.inspect(name) + if code_hash != item["codeHash"]: + raise ExtensionError("Source or dependencies changed after inspection. Review the current code hash.") + if any(dep["status"] != "satisfied" for dep in item["dependencies"]): + raise ExtensionError( + "Resolve declared dependencies manually, then inspect again. No packages were installed." + ) + record = {**self._state().get(name, {}), "enabled": False, "diagnostic": None} + self._save(name, record) + try: + registry = self._load(item) + if self.inspect(name)["codeHash"] != code_hash: + raise ExtensionError("Source changed during import. Review and reload after editing is complete.") + except Exception as error: + self.unload(name) + record["diagnostic"] = f"{type(error).__name__}: {error}"[:2048] + self._save(name, record) + raise ExtensionError(record["diagnostic"]) from error + self._save(name, {**record, "enabled": True, "codeHash": code_hash, "nodes": sorted(registry)}) + return registry + + def disable(self, name): + self._save(module_name(name), {**self._state().get(name, {}), "enabled": False}) + self.unload(name) + + def load_enabled(self, registry): + for item in self.list(): + if item["enabled"]: + try: + registry[item["moduleKey"]] = self.enable(item["name"], code_hash=item["codeHash"], consent=True) + except ExtensionError: + # Stored diagnostics remain discoverable; one failed extension cannot break startup. + continue diff --git a/modiff/custom_modular_extension.py b/modiff/custom_modular_extension.py new file mode 100644 index 00000000..874962ea --- /dev/null +++ b/modiff/custom_modular_extension.py @@ -0,0 +1,123 @@ +"""Adapt explicitly enabled Modular Python to ordinary NodeBase dispatch. + +The historical contract-only DynamicBlock path remains fail closed. This path +only accepts files already staged and approved by ExtensionStore, never a graph +supplied repository or trust flag. Model components come from connected loaders. +""" + +from copy import deepcopy +import importlib +import json +from pathlib import Path +import sys +from types import ModuleType + +from modiff.custom_extensions import ExtensionError, _PackageFinder + + +def load_modular_extension(item, files): + from diffusers import ModularPipelineBlocks + from diffusers.utils.dynamic_modules_utils import resolve_trust_remote_code + from modiff.NodeBase import NodeBase + + key = item["moduleKey"] + root = Path(item["path"]) + config = json.loads(files["modular_config.json"]) + reference = config.get("auto_map", {}).get("ModularPipelineBlocks") + if not isinstance(reference, str) or len(reference.split(".")) != 2: + raise ExtensionError("modular_config.json must declare auto_map.ModularPipelineBlocks as module.Class.") + module_file, class_name = reference.split(".") + if not module_file.isidentifier() or not class_name.isidentifier(): + raise ExtensionError("Custom Modular entry points must be simple Python identifiers.") + # Preserve the upstream operator kill switch. Only this pre-approved local + # package can be imported; no graph-supplied trust flag enters this path. + resolve_trust_remote_code(True, item["name"], True) + package = ModuleType(key) + package.__path__ = [] + sys.modules[key] = package + # Keep source main.py separate from the ordinary NodeBase dispatch adapter. + source_key = key + "._modular_source" + source_package = ModuleType(source_key) + source_package.__path__ = [] + sys.modules[source_key] = source_package + package._modular_source = source_package + sys.meta_path.insert(0, _PackageFinder(source_key, root, files)) + block_class = getattr(importlib.import_module(source_key + "." + module_file), class_name) + if not isinstance(block_class, type) or not issubclass(block_class, ModularPipelineBlocks): + raise ExtensionError("Custom Modular code must declare upstream ModularPipelineBlocks.") + # Upstream's local from_pretrained loader aliases all packages into + # diffusers_modules.local. Import our isolated package, then use native + # ConfigMixin construction and init_pipeline instead of that shared alias. + blocks = block_class.from_config(config) + contract = deepcopy(item["preview"]["contract"]) + inputs = {field.name for field in blocks.inputs if field.name} + outputs = {field.name for field in blocks.intermediate_outputs} + output_names = {name: name.removeprefix("out_") for name in contract["output_names"] if name != "doc"} + if set(contract["input_names"]) - inputs or set(output_names.values()) - outputs: + raise ExtensionError("The sidecar input/output names do not match the imported Modular block contract.") + + node_definition = deepcopy(item["preview"]["nodes"]["Block"]) + pretrained_names = [ + spec.name for spec in blocks.expected_components if spec.default_creation_method == "from_pretrained" + ] + # Some published Mellon sidecars omit model ports because Mellon loads the + # block's default repositories itself. Derive one bundle socket only after + # approval permits inspecting the Python contract. Keep author fields and + # their saved identities intact, including a colliding data input name. + if pretrained_names and not contract["model_input_names"]: + port = "pipeline_components" + while port in node_definition["params"] or port in inputs or port in output_names.values(): + port = "modiff_" + port + node_definition["params"][port] = { + "label": "Models", + "display": "input", + "type": "diffusers_modular_pipeline_components", + "description": "Connect Pipeline Components from Load Models. Required: " + ", ".join(pretrained_names), + } + contract["model_input_names"] = [port] + + def execute(self, **kwargs): + from modiff.modular_requirements import validate_runtime_component_requirements + from modules.ModularDiffusers import components + from modules.ModularDiffusers.utils import collect_model_ids + + # Native initialization creates fresh scheduler/guider/state per executed + # node call. Connected weights remain owned/reused by the existing manager. + pipeline = blocks.init_pipeline(components_manager=components, collection=self.node_id) + ids = collect_model_ids(kwargs, contract["model_input_names"], pipeline.pretrained_component_names) + if ids: + connected = components.get_components_by_ids(ids=ids, return_dict_with_names=True) + pipeline.update_components(**connected) + missing = [name for name in pipeline.pretrained_component_names if getattr(pipeline, name, None) is None] + if missing: + raise ExtensionError("Connect loaded components before running this custom block: " + ", ".join(missing)) + validate_runtime_component_requirements(blocks, pipeline, path=(key, "Block")) + values = {name: kwargs[name] for name in contract["input_names"] if name in kwargs} + result = pipeline(**values, output=list(output_names.values())) + mapped = {name: result[pipeline_name] for name, pipeline_name in output_names.items()} + if "doc" in contract["output_names"]: + mapped["doc"] = blocks.doc + return mapped + + klass = type( + "Block", + (NodeBase,), + { + "__module__": key + ".main", + "execute": execute, + "params": node_definition["params"], + "label": node_definition["label"], + }, + ) + main = ModuleType(key + ".main") + main.Block = klass + from modiff.custom_modular_models import build_models_loader + + registry = {"Block": node_definition} + model_loader = build_models_loader(blocks, key, node_definition["label"]) + if model_loader is not None: + main.LoadModels, registry["LoadModels"] = model_loader + package.main = main + sys.modules[key] = package + sys.modules[key + ".main"] = main + return registry diff --git a/modiff/custom_modular_models.py b/modiff/custom_modular_models.py new file mode 100644 index 00000000..7fda8630 --- /dev/null +++ b/modiff/custom_modular_models.py @@ -0,0 +1,188 @@ +"""Model suppliers derived from an already approved Modular block contract. + +Only installed official component classes and explicitly pinned, cached Hub +sources are loadable. This is ordinary NodeBase/ComponentsManager execution; +discovery never loads weights and this supplier is not Auto-qualified. +""" + +from copy import deepcopy +import hashlib +import importlib +from pathlib import Path +import re + +from modiff.custom_extensions import ExtensionError, immutable_revision + + +def _official_type(spec): + cls = spec.type_hint + if not isinstance(cls, type) or not callable(getattr(cls, "from_pretrained", None)): + return False + library = cls.__module__.split(".")[0] + return library in {"diffusers", "transformers"} and getattr(importlib.import_module(library), cls.__name__, None) is cls + + +def build_models_loader(blocks, module_key, label): + """Return a generated node only when every pretrained type is supported.""" + from modiff.NodeBase import NodeBase + from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype + from modiff.diffusers_offload import offload_mode_param + + specs = deepcopy([s for s in blocks.expected_components if s.default_creation_method == "from_pretrained"]) + if not specs or any(not _official_type(s) for s in specs): + return None + if len(specs) > 64 or any(not isinstance(s.name, str) or not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]{0,127}", s.name) for s in specs): + raise ExtensionError("Custom model components require at most 64 simple, named component declarations.") + if len({s.name for s in specs}) != len(specs): + raise ExtensionError("Custom model component names must be unique.") + + params = {} + for spec in specs: + prefix = f"source__{spec.name}__" + repo = spec.pretrained_model_name_or_path + params[prefix + "repo"] = { + "label": f"{spec.name} model", "display": "modelselect", "type": "string", + "value": {"source": "hub", "value": repo if isinstance(repo, str) else ""}, + # The cache inventory indexes class names from component configs in + # every downloaded subfolder. Keep this generated supplier scoped + # to the exact component class declared by the approved block; + # otherwise every cached pipeline (including unrelated audio + # repositories) is presented as a plausible source for a VAE, + # tokenizer, scheduler, etc. + "fieldOptions": { + "sources": ["hub"], + "noValidation": True, + "filter": {"hub": {"className": [spec.type_hint.__name__]}}, + }, + } + params[prefix + "revision"] = { + "label": f"{spec.name} revision", "type": "string", "value": spec.revision or "", + "description": "Exact 40-character Hub commit. Download this revision in Models before Run.", + } + params[prefix + "subfolder"] = {"label": f"{spec.name} subfolder", "type": "string", "value": spec.subfolder or ""} + params[prefix + "variant"] = {"label": f"{spec.name} variant", "type": "string", "value": spec.variant or ""} + params.update({ + "dtype": {"label": "Precision", "type": "string", "options": ["float32", "float16", "bfloat16"], + "value": "bfloat16", "postProcess": str_to_dtype}, + "device": {"label": "Device", "type": "string", "value": DEFAULT_DEVICE, "options": DEVICE_LIST}, + "offload_mode": offload_mode_param(), + "pipeline_components": {"label": "Pipeline Components", "type": "diffusers_modular_pipeline_components", "display": "output"}, + }) + definition = {"type": "custom", "category": "Custom", "resizable": True, "label": f"Load Models — {label}", + "description": "Load pinned, downloaded components for this approved block. Requires Custom memory policy.", + "params": params} + + def selected_sources(values): + from huggingface_hub.utils import validate_repo_id + from huggingface_hub import snapshot_download + from modules.ModularDiffusers.loaders import _normalize_reviewed_component_subfolder, _reviewed_hub_component_config_path + + selected = [] + # Validate the entire selection before any lookup or model allocation. + for spec in specs: + prefix = f"source__{spec.name}__" + choice = values.get(prefix + "repo") + if not isinstance(choice, dict) or choice.get("source") != "hub" or not isinstance(choice.get("value"), str): + raise ExtensionError(f"Select a Hub model for {spec.name}.") + repo = choice["value"] + validate_repo_id(repo) + revision = immutable_revision(values.get(prefix + "revision")) + # Native Transformers loaders require the root folder as "", not + # None; preserve the upstream ComponentSpec loading convention. + subfolder = _normalize_reviewed_component_subfolder(values.get(prefix + "subfolder")) or "" + variant = values.get(prefix + "variant") or None + if variant is not None and (not isinstance(variant, str) or not re.fullmatch(r"[A-Za-z0-9_.-]{1,128}", variant)): + raise ExtensionError(f"Invalid weight variant for {spec.name}.") + selected.append((spec.name, repo, revision, subfolder, variant)) + identities = [] + for name, repo, revision, subfolder, variant in selected: + try: + # Model Manager intentionally downloads selected model files, + # not every repository artifact. Do not require README, Git + # metadata, alternate pickle weights or unrelated components. + # Native from_pretrained validates its own cached weight files. + pattern = f"{subfolder}/" if subfolder else "" + root = Path(snapshot_download(repo, revision=revision, local_files_only=True, + allow_patterns=[pattern + "*config.json"])) + except OSError as error: + raise ExtensionError(f"Download {repo}@{revision} in Models before loading {name}.") from error + # Pin small cached metadata in the NodeBase reuse key as well as the + # repository identity. Weights remain immutable Hub artifacts. + folder = root / subfolder if subfolder else root + digest = hashlib.sha256() + metadata = sorted(folder.glob("*config.json")) + if not metadata or len(metadata) > 64: + raise ExtensionError(f"Download a complete component configuration for {name} (1–64 config files).") + total = 0 + for path in metadata: + read_path = _reviewed_hub_component_config_path(path, repository=repo, revision=revision) + if read_path.stat().st_size > 2 * 1024 * 1024: + raise ExtensionError(f"Component metadata exceeds 2 MiB: {path.name}.") + with read_path.open("rb") as reader: + content = reader.read(2 * 1024 * 1024 + 1) + total += len(content) + if len(content) > 2 * 1024 * 1024 or total > 16 * 1024 * 1024: + raise ExtensionError(f"Component metadata exceeds its byte limit for {name}.") + digest.update(path.name.encode() + b"\0" + content) + identities.append((name, repo, revision, subfolder, variant, digest.hexdigest())) + return identities + + class LoadModels(NodeBase): + def __call__(self, **kwargs): + # Graph values cannot substitute a prepared identity or bypass pin + # validation by hitting a previously loaded node's cache. + self._selected_sources = selected_sources(kwargs) + result = super().__call__(**kwargs) + self._cached_sources = self._selected_sources + return result + + def _cache_params_equal(self, previous, current): + return (getattr(self, "_cached_sources", None) == self._selected_sources + and super()._cache_params_equal(previous, current)) + + def execute(self, dtype, device, offload_mode, **kwargs): + from modules.ModularDiffusers import components + from modiff.diffusers_offload import apply_model_offload, normalize_offload_mode + from modiff.modular_requirements import _matches_component_type, validate_runtime_component_requirements + from modules.ModularDiffusers.loaders import load_components_strict, reusable_component_ids, record_pipeline_component_runtime_policy, node_get_component_info + + sources = selected_sources(kwargs) + if getattr(self, "_selected_sources", sources) != sources: + raise ExtensionError("Component metadata changed after cache validation; retry the run.") + mode = normalize_offload_mode(offload_mode, auto_offload=True, device=device) + self.loader = blocks.init_pipeline(components_manager=components, collection=self.node_id) + missing = [] + for name, repo, revision, subfolder, variant, digest in sources: + spec = self.loader.get_component_spec(name) + spec.pretrained_model_name_or_path = repo + spec.revision, spec.subfolder, spec.variant = revision, subfolder, variant + self.loader._component_specs[name] = spec + ids = reusable_component_ids(components, name=name, load_id=spec.load_id, dtype=dtype, + requested_quantization=None, offload_mode=mode, device=device, node_id=self.node_id) + compatible = [i for i in ids + if _matches_component_type(components.get_one(component_id=i), spec.type_hint) + and getattr(components.get_one(component_id=i), "_modiff_custom_metadata_hash", None) == digest] + if compatible: + self.loader.update_components(**{name: components.get_one(component_id=compatible[0])}) + else: + missing.append(name) + with self.diffusers_loading_progress(): + load_components_strict(self.loader, missing, required_names=missing, model_id=label, + dtype=dtype, offload_mode=mode, quant_config=None, diagnostics={}, + component_load_kwargs={"torch_dtype": dtype, "local_files_only": True, + "trust_remote_code": False, "use_safetensors": True, + "weights_only": True}) + for name in missing: + apply_model_offload(getattr(self.loader, name), component_name=name, mode=mode, + device=device, node_id=self.node_id, scope="custom-modular") + getattr(self.loader, name)._modiff_custom_metadata_hash = next(s[-1] for s in sources if s[0] == name) + record_pipeline_component_runtime_policy(self.loader, offload_mode=mode, device=device, node_id=self.node_id) + validate_runtime_component_requirements(blocks, self.loader, path=(module_key, "LoadModels")) + return {"pipeline_components": { + spec.name: node_get_component_info(node_id=self.node_id, manager=components, name=spec.name) + for spec in specs + }} + + LoadModels.__module__ = module_key + ".main" + LoadModels.params, LoadModels.label = params, definition["label"] + return LoadModels, definition diff --git a/modiff/dev.py b/modiff/dev.py new file mode 100644 index 00000000..0a2173d8 --- /dev/null +++ b/modiff/dev.py @@ -0,0 +1,68 @@ +"""Script-free entry point for the existing managed installer and runtime. + +Bootstrap with ``uv run --no-project --no-sync --python 3.12 -m modiff.dev``. +This module intentionally uses only the standard library until dispatch. +""" + +from __future__ import annotations + +import os +from pathlib import Path +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[1] + + +def runtime_command(command: str, arguments: list[str]) -> tuple[list[str], dict[str, str]]: + from modiff.install import _rocm_environment + from modiff.runtime_profile import read_state + + venv = ROOT / ".venv" + python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + if not python.is_file(): + raise ValueError("No managed environment exists. Run modiff.dev plan, then modiff.dev setup.") + state = read_state(venv) or {} + environment = os.environ.copy() + if state.get("profile") == "amd-rocm-linux": + environment = _rocm_environment() + environment.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") + target = ["-m", "modiff.preflight"] if command == "check" else [str(ROOT / "main.py")] + return [str(python), *target, *arguments], environment + + +def main(argv: list[str] | None = None) -> int: + arguments = list(sys.argv[1:] if argv is None else argv) + if not arguments or arguments[0] in {"-h", "--help"}: + print( + "Usage: python -m modiff.dev {plan|setup|check|run} [arguments]\n" + "plan/setup: managed installer arguments; setup preserves an existing .venv unless --repair is explicit.\n" + "check: preflight arguments; run: main.py arguments. See docs/developer-setup.md." + ) + return 0 + command, *arguments = arguments + try: + if command in {"plan", "setup"}: + from modiff import install + + parsed = install.parser().parse_args(arguments) + read_only = command == "plan" or parsed.dry_run or parsed.system_check or parsed.guide + if not read_only and ((ROOT / ".venv").exists() or (ROOT / ".venv").is_symlink()) and not parsed.repair: + raise ValueError( + "Existing .venv preserved. Use modiff.dev check; replacing it requires explicit --repair." + ) + return install.main([*arguments, *(["--dry-run"] if command == "plan" else [])]) + if command in {"check", "run"}: + cmd, environment = runtime_command(command, arguments) + # Inherit the terminal and signals, without a shell or interpreter resolution. + return subprocess.call(cmd, cwd=ROOT, env=environment) + raise ValueError(f"Unknown command: {command}. Use --help.") + except (OSError, ValueError) as error: + print(str(error), file=sys.stderr) + return 2 + except KeyboardInterrupt: + return 130 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/modiff/diffusers_offload.py b/modiff/diffusers_offload.py index 8833b316..2f231c89 100644 --- a/modiff/diffusers_offload.py +++ b/modiff/diffusers_offload.py @@ -190,6 +190,7 @@ def apply_component_group_offload( mode, node_id, scope="pipeline", + leaf_level_components: Iterable[str] = LEAF_LEVEL_GROUP_COMPONENTS, ): requested_mode = normalize_offload_mode(mode, auto_offload=mode != OFFLOAD_MODE_NONE) normalized_device = normalize_execution_device(device) @@ -214,7 +215,7 @@ def apply_component_group_offload( applied = [] for component_name in component_names: - offload_type = "leaf_level" if component_name in LEAF_LEVEL_GROUP_COMPONENTS else "block_level" + offload_type = "leaf_level" if component_name in leaf_level_components else "block_level" applied_name = _apply_group_to_module( getattr(pipeline, component_name, None), component_name=component_name, @@ -406,6 +407,8 @@ def apply_pipeline_offload( scope="pipeline", component_names: Iterable[str] = DEFAULT_GROUP_COMPONENTS, prefer_pipeline_group=True, + leaf_level_components: Iterable[str] = LEAF_LEVEL_GROUP_COMPONENTS, + resident_components: Iterable[str] = (), ): requested_mode = normalize_offload_mode(mode, auto_offload=mode != OFFLOAD_MODE_NONE) normalized_device = normalize_execution_device(device) @@ -427,6 +430,16 @@ def apply_pipeline_offload( reset_pipeline_device_map_for_runtime(pipeline) + # Some reviewed pipelines read a component's tensors directly, outside its + # forward hook. Preserve those components explicitly rather than exposing + # meta tensors or disk placeholders to the pipeline. This is per-instance; + # never mutate Diffusers' class-level exclusion list. + resident_components = tuple(resident_components) + if resident_components: + pipeline._exclude_from_cpu_offload = list(dict.fromkeys(( + *getattr(pipeline, "_exclude_from_cpu_offload", ()), *resident_components, + ))) + if mode == OFFLOAD_MODE_NONE: pipeline.to(normalized_device) return OffloadResult( @@ -470,9 +483,14 @@ def apply_pipeline_offload( name for name, module in owned_components.items() if isinstance(module, torch.nn.Module) )))) + component_names = tuple(name for name in component_names if name not in resident_components) + for name in resident_components: + module = getattr(pipeline, name, None) + if isinstance(module, torch.nn.Module): + module.to(normalized_device) if mode != OFFLOAD_MODE_GROUP_DISK and prefer_pipeline_group and hasattr(pipeline, "enable_group_offload"): try: - leaf_components = sorted(name for name in LEAF_LEVEL_GROUP_COMPONENTS + leaf_components = sorted(name for name in leaf_level_components if isinstance(getattr(pipeline, name, None), torch.nn.Module)) pipeline.enable_group_offload( onload_device=normalized_device, @@ -484,7 +502,7 @@ def apply_pipeline_offload( non_blocking=use_stream, use_stream=use_stream, record_stream=False, - exclude_modules=leaf_components, + exclude_modules=list(dict.fromkeys((*leaf_components, *resident_components))), ) # The pipeline-wide block-level helper hooks `forward`, but # pipelines call VAE encode/decode directly. Exclude those @@ -496,6 +514,7 @@ def apply_pipeline_offload( apply_component_group_offload( pipeline, component_names=leaf_components, device=normalized_device, mode=mode, node_id=node_id, scope=scope, + leaf_level_components=leaf_level_components, ) detail = "Pinned asynchronous prefetch enabled." if use_stream else "Synchronous group transfers." return OffloadResult( @@ -516,6 +535,7 @@ def apply_pipeline_offload( mode=mode, node_id=node_id, scope=scope, + leaf_level_components=leaf_level_components, ) if not result.applied: raise RuntimeError("No compatible torch.nn.Module components were available for group offload.") diff --git a/modiff/diffusers_profiles.py b/modiff/diffusers_profiles.py index cd94c195..0d17c0e3 100644 --- a/modiff/diffusers_profiles.py +++ b/modiff/diffusers_profiles.py @@ -261,6 +261,10 @@ def __post_init__(self) -> None: "modules.HuggingFaceSpeech", "LoadCTCSpeechRecognitionModel", ), + "direct-huggingface-transformers-depth": ( + "modules.HuggingFaceTransformers", + "LoadDepthEstimationModel", + ), "direct-huggingface-transformers-text": ( "modules.HuggingFaceTransformers", "LoadTextGenerationModel", @@ -330,6 +334,11 @@ def __post_init__(self) -> None: ): raise ValueError(f"Diffusers execution profile {self.id!r} has invalid optional-runtime delivery targets.") + @property + def operation_recipe(self) -> bool: + """Exact operation selection, not a second legacy model/Auto owner.""" + return self.execution_path == "modular-diffusers" and self.model_type != self.pipeline_class + @property def backend_path(self) -> str: """Return the legacy combined loader key from the explicit target.""" @@ -387,11 +396,21 @@ def to_public_dict( *, observe_optional_runtime: bool = False, optional_runtime_catalog_resolver=None, + platform_name: str | None = None, + machine: str | None = None, ) -> dict: + # Static inventories may select a reference target. Installed-runtime + # observations describe this process only, never another platform. + if observe_optional_runtime and (platform_name is not None or machine is not None): + raise ValueError("Runtime observations cannot use an explicit target") data = asdict(self) public = {key: list(value) if isinstance(value, tuple) else value for key, value in data.items()} - public["optional_runtime_delivery"] = self.optional_runtime_delivery_for_target() - public["optional_runtime_profiles"] = list(self.optional_runtime_profile_ids_for_target()) + public["optional_runtime_delivery"] = self.optional_runtime_delivery_for_target( + platform_name=platform_name, machine=machine, + ) + public["optional_runtime_profiles"] = list(self.optional_runtime_profile_ids_for_target( + platform_name=platform_name, machine=machine, + )) public["optional_runtime_platform_deliveries"] = [ {"platform": platform_name, "machine": machine, "delivery": delivery} for platform_name, machine, delivery in self.optional_runtime_platform_deliveries @@ -413,6 +432,8 @@ def to_public_dict( if not self.expert_mps_policy: public.pop("expert_mps_policy") public["backend_path"] = self.backend_path + if self.operation_recipe: + public["operation_recipe"] = True if observe_optional_runtime: # Lazy to keep the declarative profile module independent of # overlay storage during registry import. @@ -425,7 +446,9 @@ def to_public_dict( catalog_resolver=optional_runtime_catalog_resolver, ) else: - requirement = optional_runtime_requirement_for_profiles((self,)) + requirement = optional_runtime_requirement_for_profiles( + (self,), platform_name=platform_name, machine=machine, + ) public["optionalRuntimeRequirement"] = requirement return public @@ -932,7 +955,8 @@ def execution_profiles_for_execution( return tuple( profile for profile in DIFFUSERS_EXECUTION_PROFILES.values() - if profile.model_type == normalized_model_type and (not normalized_mode or normalized_mode in profile.modes) + if not profile.operation_recipe + and profile.model_type == normalized_model_type and (not normalized_mode or normalized_mode in profile.modes) ) @@ -1000,7 +1024,7 @@ def selected_optional_runtime(profiles: tuple[DiffusersExecutionProfile, ...]): identity_key = "model_type" if action == "ModelsLoader" else "pipeline_class" raw_identity = "DummyCustomPipeline" if action == "DynamicBlockNode" else values.get(identity_key) identity = raw_identity.strip() if isinstance(raw_identity, str) else "" - expected_identity = exact_profile.model_type if action == "ModelsLoader" else exact_profile.pipeline_class + expected_identity = exact_profile.pipeline_class if not identity: return selected_optional_runtime((exact_profile,)), "loader_identity_missing" if identity != expected_identity: @@ -1028,10 +1052,18 @@ def selected_optional_runtime(profiles: tuple[DiffusersExecutionProfile, ...]): else: repository = "" repository_source = "" + workflow_repositories = () + if exact_profile.execution_path == "modular-diffusers": + from modiff.modular_workflow_contracts import PINNED_MODULAR_WORKFLOW_REPOSITORY_VARIANTS + + workflow_repositories = PINNED_MODULAR_WORKFLOW_REPOSITORY_VARIANTS.get( + (exact_profile.pipeline_class, values.get("workflow_id")), (), + ) if repository and repository_source == "hub" and repository not in { exact_profile.default_repo, exact_profile.fallback_repo, *exact_profile.compatible_repos, + *workflow_repositories, }: return selected_optional_runtime((exact_profile,)), "loader_execution_profile_mismatch" return selected_optional_runtime((exact_profile,)), None @@ -1045,7 +1077,10 @@ def selected_optional_runtime(profiles: tuple[DiffusersExecutionProfile, ...]): matching = tuple( profile for profile in backend_profiles - if (profile.model_type == identity if action == "ModelsLoader" else profile.pipeline_class == identity) + if profile.pipeline_class == identity + # PAG is a guidance recipe over the same SDXL model owner, selected by + # its ordinary Guider/Layers nodes, not a second copy of the weights. + and not (action == "ModelsLoader" and profile.id == "sdxl-pag:modular") ) if not matching: return selected_optional_runtime(backend_profiles), "loader_selection_unregistered" @@ -1210,11 +1245,15 @@ def public_execution_profiles( *, observe_optional_runtime: bool = False, optional_runtime_catalog_resolver=None, + platform_name: str | None = None, + machine: str | None = None, ) -> list[dict]: return [ profile.to_public_dict( observe_optional_runtime=observe_optional_runtime, optional_runtime_catalog_resolver=optional_runtime_catalog_resolver, + platform_name=platform_name, + machine=machine, ) for profile in DIFFUSERS_EXECUTION_PROFILES.values() if profile.public diff --git a/modiff/execution_input_provenance.py b/modiff/execution_input_provenance.py index 84dd2e71..da8c3fae 100644 --- a/modiff/execution_input_provenance.py +++ b/modiff/execution_input_provenance.py @@ -18,6 +18,7 @@ "guidance_scale": "guidanceScale", "true_cfg_scale": "trueCfgScale", "max_sequence_length": "maxSequenceLength", "num_images_per_prompt": "imagesPerPrompt", "num_frames": "numFrames", "fps": "fps", "frame_rate": "fps", + "audio_duration": "audioDuration", "sample_rate": "sampleRate", "strength": "strength", "repo_id": "repo", "model_id": "repo", "revision": "revision", "dtype": "dtype", "device": "device", "model_type": "modelType", "pipeline_class": "modelType", @@ -27,6 +28,9 @@ "control_guidance_start": "controlGuidanceStart", "control_guidance_end": "controlGuidanceEnd", "prompt_embeds_scale": "reduxPromptEmbedsScale", "pooled_prompt_embeds_scale": "reduxPooledPromptEmbedsScale", + "processing_resolution": "processingResolution", + "match_input_resolution": "matchInputResolution", "depth_convention": "depthConvention", + "use_kv_cache": "attentionContextReuse", } NODE_FIELD_NAMES = { ('DiffusersImage', 'ControlComponent'): { @@ -199,7 +203,14 @@ def build_resolved_execution_inputs(graph, records, *, task_id, attempt_index, n if incomplete: unavailable.update(candidates) summary = {} + from modiff.workflow_task_identity import graph_task_receipts + + graph_tasks = ( + graph_task_receipts({key: nodes[key] for key in ancestors}, {r["nodeId"]: r for r in captured}) + if not incomplete and not missing else [] + ) return { + **({"graphTasks": graph_tasks} if graph_tasks else {}), "schemaVersion": 1, "source": "backend-execution", "taskId": str(task_id), "attemptIndex": int(attempt_index or 0), "nodeId": str(node_id), "nodes": captured, "summary": summary, "ambiguousFields": ambiguous, @@ -220,6 +231,9 @@ def apply_resolved_execution_inputs(output, receipt): return output output["resolvedExecutionInputs"] = deepcopy(receipt) summary = receipt["summary"] + tasks = {item['task'] for item in receipt.get('graphTasks', [])} + if len(tasks) == 1 and None not in tasks: + output['mode'] = next(iter(tasks)) for key in OUTPUT_FIELDS: if receipt["truncated"] or key in receipt["ambiguousFields"] or key in receipt["unavailableFields"]: output.pop(key, None) diff --git a/modiff/field_metadata.py b/modiff/field_metadata.py new file mode 100644 index 00000000..dca931ca --- /dev/null +++ b/modiff/field_metadata.py @@ -0,0 +1,67 @@ +"""Exact built-in callbacks reviewed for model-independent authoring. + +Registry/custom-node declarations cannot opt into this boundary. Unknown and +queued callbacks keep the graph's model-ownership lease. +""" + +MODULAR_METADATA_ACTIONS = { + "ModelsLoader": frozenset({"set_filters", "refresh_pipeline_identity"}), + "AutoModelLoader": frozenset({"set_filters"}), + "EncodePrompt": frozenset({"update_node"}), + "Denoise": frozenset({"update_node"}), + "DecodeLatents": frozenset({"update_node"}), + "ImageEncode": frozenset({"update_node"}), + "ImageEmbeddings": frozenset({"update_node"}), + "IPAdapter": frozenset({"update_node"}), + "Controlnet": frozenset({"update_node"}), + "Scheduler": frozenset({"updateNode"}), + "Guider": frozenset({"updateNode"}), + "Layers": frozenset({"set_blocks"}), + "DynamicBlockNode": frozenset({"update_node"}), +} + +METADATA_ACTIONS = { + "modules.Spandrel": {"Upscaler": frozenset({"update_model_selection"})}, + "modules.Video": {"UpscaleVideo": frozenset({"update_model_selection"})}, + "modules.ModularDiffusers": MODULAR_METADATA_ACTIONS, + "modules.DiffusersImage": { + "LoadPipeline": frozenset({"update_pipeline_contract"}), + **{action: frozenset({"update_image_contract"}) for action in ( + "Generate", "Edit", "LayerDecompose", "Inpaint", "ControlGenerate", + "ControlEdit", "ControlInpaint", "UnconditionalGenerate", "PredictMap", + )}, + }, + "modules.DiffusersAudio": {action: frozenset({"update_audio_contract"}) for action in ("LoadPipeline", "Generate")}, + "modules.DiffusersThreeD": {action: frozenset({"update_three_d_contract"}) for action in ("LoadPipeline", "GenerateRenderedArtifact")}, + "modules.DiffusersVideo": { + "LoadPipeline": frozenset({"select_adapter"}), + **{action: frozenset({"update_adapter_modes"}) for action in ("Generate", "GenerateVideoAudio", "GenerateLTX2", "GenerateSequence")}, + }, +} + + +def is_metadata_field_action(data): + if not isinstance(data, dict) or not isinstance(data.get("module"), str): + return False + action, method = data.get("action"), data.get("fn") + return ( + data.get("queue", False) is False + and isinstance(action, str) + and isinstance(method, str) + and method in METADATA_ACTIONS.get(data["module"], {}).get(action, ()) + ) + + +def metadata_field_callback(action, method, *, node_id, sid, module="modules.ModularDiffusers"): + if method not in METADATA_ACTIONS.get(module, {}).get(action, ()): + raise ValueError("This field action is not reviewed as model-independent metadata.") + # Import only after authoritative field authorization and runtime checks. + if module == "modules.ModularDiffusers": + from modules.ModularDiffusers.field_metadata import metadata_field_callback as create + + return create(action, method, node_id=node_id, sid=sid) + from importlib import import_module + from modiff.field_metadata_context import ordinary_field_callback + + node_class = getattr(import_module(f"{module}.main"), action) + return ordinary_field_callback(node_class, method, node_id=node_id, sid=sid) diff --git a/modiff/field_metadata_context.py b/modiff/field_metadata_context.py new file mode 100644 index 00000000..b76c1326 --- /dev/null +++ b/modiff/field_metadata_context.py @@ -0,0 +1,28 @@ +"""Node message transport without executable node construction or ownership.""" + +from copy import deepcopy +from types import MethodType + +from modiff.NodeBase import NodeBase + + +class FieldMessageContext: + _queue_dynamic_node_message = NodeBase._queue_dynamic_node_message + send_node_definition = NodeBase.send_node_definition + set_field_visibility = NodeBase.set_field_visibility + set_field_value = NodeBase.set_field_value + set_field_params = NodeBase.set_field_params + get_signal_value = NodeBase.get_signal_value + + def __init__(self, *, node_id, sid, class_name): + self.node_id = node_id + self._sid = sid + self.class_name = class_name + + +def ordinary_field_callback(node_class, method, *, node_id, sid): + # Some reviewed callbacks inspect their static field names through + # self.__class__.params. Copy just that declaration, never the executable MRO. + presentation = type("FieldMetadata", (FieldMessageContext,), {"params": deepcopy(node_class.params)}) + context = presentation(node_id=node_id, sid=sid, class_name=node_class.__name__) + return MethodType(getattr(node_class, method), context) diff --git a/modiff/huggingface_cluster_admission.py b/modiff/huggingface_cluster_admission.py index dda2dc38..f287ab4f 100644 --- a/modiff/huggingface_cluster_admission.py +++ b/modiff/huggingface_cluster_admission.py @@ -17,6 +17,7 @@ from copy import deepcopy from typing import Any, Mapping +from modiff.modular_action_bindings import MODULAR_ACTION_BINDINGS as _ACTION_ROLE_CONTRACTS from modiff.huggingface_cluster_promotions import promotion_receipt_for_admission from modiff.model_artifact_catalog import catalog_repository_pin, require_catalog_revision from modiff.modular_contract_only_registry import equivalent_modular_targets @@ -63,209 +64,6 @@ def _equivalent_standard_execution_class(pipeline_class: str, workflow_id: str) ) -_ACTION_ROLE_CONTRACTS = { - "text_encoder": ("prompt", "modules.ModularDiffusers.EncodePrompt"), - "image_encoder": ("imageEmbeddings", "modules.ModularDiffusers.ImageEmbeddings"), - "vae_encoder": ("imageEncode", "modules.ModularDiffusers.ImageEncode"), - "controlnet": ("controlnet", "modules.ModularDiffusers.Controlnet"), - "ip_adapter": ("ipAdapter", "modules.ModularDiffusers.IPAdapter"), - "denoise": ("denoise", "modules.ModularDiffusers.Denoise"), - "decoder": ("decode", "modules.ModularDiffusers.DecodeLatents"), - "semantic_generator": ("prompt", "modules.ModularDiffusers.WorkflowSemanticGeneration"), - "workflow_denoise": ("denoise", "modules.ModularDiffusers.WorkflowDenoise"), - "workflow_audio_decoder": ("decode", "modules.ModularDiffusers.WorkflowDecodeAudio"), - "workflow_text_encoder": ("prompt", "modules.ModularDiffusers.WorkflowTextEncode"), - "workflow_image_encoder": ("imageEncode", "modules.ModularDiffusers.WorkflowImageEncode"), - "workflow_video_image_encoder": ( - "imageEncode", - "modules.ModularDiffusers.WorkflowVideoImageEncode", - ), - "workflow_video_encoder": ("videoEncode", "modules.ModularDiffusers.WorkflowVideoEncode"), - "workflow_image_denoise": ("denoise", "modules.ModularDiffusers.WorkflowImageDenoise"), - "workflow_image_decoder": ("decode", "modules.ModularDiffusers.WorkflowDecodeImage"), - "workflow_video_denoise": ("denoise", "modules.ModularDiffusers.WorkflowVideoDenoise"), - "workflow_video_decoder": ("decode", "modules.ModularDiffusers.WorkflowDecodeVideo"), - "workflow_hunyuan_video15_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowHunyuanVideo15TextEncode", - ), - "workflow_hunyuan_video15_vae_encoder": ( - "imageEncode", - "modules.ModularDiffusers.WorkflowHunyuanVideo15VaeEncode", - ), - "workflow_hunyuan_video15_image_encoder": ( - "imageEmbeddings", - "modules.ModularDiffusers.WorkflowHunyuanVideo15ImageEncode", - ), - "workflow_hunyuan_video15_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowHunyuanVideo15Denoise", - ), - "workflow_hunyuan_video15_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowHunyuanVideo15Decode", - ), - "workflow_stable_diffusion3_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowStableDiffusion3TextEncode", - ), - "workflow_stable_diffusion3_vae_encoder": ( - "imageEncode", - "modules.ModularDiffusers.WorkflowStableDiffusion3VaeEncode", - ), - "workflow_stable_diffusion3_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowStableDiffusion3Denoise", - ), - "workflow_stable_diffusion3_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowStableDiffusion3Decode", - ), - "workflow_krea2_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowKrea2TextEncode", - ), - "workflow_krea2_turbo_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowKrea2TurboTextEncode", - ), - "workflow_krea2_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowKrea2Denoise", - ), - "workflow_krea2_turbo_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowKrea2TurboDenoise", - ), - "workflow_krea2_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowKrea2Decode", - ), - "workflow_ideogram4_prompt_upsample": ( - "prompt", - "modules.ModularDiffusers.WorkflowIdeogram4PromptUpsample", - ), - "workflow_ideogram4_text_encoder": ( - "textEncode", - "modules.ModularDiffusers.WorkflowIdeogram4TextEncode", - ), - "workflow_ideogram4_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowIdeogram4Denoise", - ), - "workflow_ideogram4_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowIdeogram4Decode", - ), - "workflow_cosmos3_distilled_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowCosmos3DistilledTextEncode", - ), - "workflow_cosmos3_distilled_vae_encoder": ( - "imageEncode", - "modules.ModularDiffusers.WorkflowCosmos3DistilledVaeEncode", - ), - "workflow_cosmos3_distilled_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowCosmos3DistilledDenoise", - ), - "workflow_cosmos3_distilled_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowCosmos3DistilledDecode", - ), - "workflow_cosmos3_omni_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowCosmos3OmniTextEncode", - ), - "workflow_cosmos3_omni_vae_encoder": ( - "imageEncode", - "modules.ModularDiffusers.WorkflowCosmos3OmniVaeEncode", - ), - "workflow_cosmos3_omni_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowCosmos3OmniDenoise", - ), - "workflow_cosmos3_omni_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowCosmos3OmniDecode", - ), - "workflow_cosmos3_omni_after_decode": ( - "afterDecode", - "modules.ModularDiffusers.WorkflowCosmos3OmniAfterDecode", - ), - "workflow_minimax_h3_before_encode": ( - "beforeEncode", - "modules.ModularDiffusers.WorkflowMiniMaxH3BeforeEncode", - ), - "workflow_minimax_h3_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowMiniMaxH3TextEncode", - ), - "workflow_minimax_h3_vae_encoder": ( - "imageEncode", - "modules.ModularDiffusers.WorkflowMiniMaxH3VaeEncode", - ), - "workflow_minimax_h3_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowMiniMaxH3Denoise", - ), - "workflow_minimax_h3_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowMiniMaxH3Decode", - ), - "workflow_ltx25_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowLTX25TextEncode", - ), - "workflow_ltx25_duration": ( - "duration", - "modules.ModularDiffusers.WorkflowLTX25Duration", - ), - "workflow_ltx25_vae_encoder": ( - "imageEncode", - "modules.ModularDiffusers.WorkflowLTX25VaeEncode", - ), - "workflow_ltx25_condition_encoder": ( - "conditionEncode", - "modules.ModularDiffusers.WorkflowLTX25ConditionEncode", - ), - "workflow_ltx25_reference_encoder": ( - "referenceEncode", - "modules.ModularDiffusers.WorkflowLTX25ReferenceEncode", - ), - "workflow_ltx25_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowLTX25Denoise", - ), - "workflow_ltx25_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowLTX25Decode", - ), - "workflow_wan_animate_text_encoder": ( - "prompt", - "modules.ModularDiffusers.WorkflowWanAnimateTextEncode", - ), - "workflow_wan_animate_image_encoder": ( - "imageEmbeddings", - "modules.ModularDiffusers.WorkflowWanAnimateImageEncode", - ), - "workflow_wan_animate_video_encoder": ( - "videoEncode", - "modules.ModularDiffusers.WorkflowWanAnimateVideoEncode", - ), - "workflow_wan_animate_vae_encoder": ( - "imageEncode", - "modules.ModularDiffusers.WorkflowWanAnimateVaeEncode", - ), - "workflow_wan_animate_denoise": ( - "denoise", - "modules.ModularDiffusers.WorkflowWanAnimateDenoise", - ), - "workflow_wan_animate_decoder": ( - "decode", - "modules.ModularDiffusers.WorkflowWanAnimateDecode", - ), -} _AUXILIARY_NODE_KEYS = { _MODELS_LOADER, "modules.ModularDiffusers.AutoModelLoader", @@ -374,6 +172,7 @@ def _equivalent_standard_execution_class(pipeline_class: str, workflow_id: str) "mode", "defaultWorkflow", "workflowId", + "workflowPromptEnhancerBlock", "workflowTextEncoderBlock", "workflowBeforeEncodeBlock", "workflowImageEncoderBlock", @@ -431,6 +230,12 @@ def _equivalent_standard_execution_class(pipeline_class: str, workflow_id: str) "workflow_video_encoder": {("models", "pipeline_components", "videoEncode", "pipeline_components")}, "workflow_image_denoise": {("models", "pipeline_components", "denoise", "pipeline_components")}, "workflow_image_decoder": {("models", "pipeline_components", "decode", "pipeline_components")}, + "workflow_ernie_prompt_enhancer": { + ("models", "pipeline_components", "promptEnhance", "pipeline_components") + }, + "workflow_ernie_text_encoder": {("models", "pipeline_components", "prompt", "pipeline_components")}, + "workflow_ernie_image_denoise": {("models", "pipeline_components", "denoise", "pipeline_components")}, + "workflow_ernie_image_decoder": {("models", "pipeline_components", "decode", "pipeline_components")}, "workflow_video_denoise": {("models", "pipeline_components", "denoise", "pipeline_components")}, "workflow_video_decoder": {("models", "pipeline_components", "decode", "pipeline_components")}, "workflow_hunyuan_video15_text_encoder": {("models", "pipeline_components", "prompt", "pipeline_components")}, @@ -766,6 +571,15 @@ def _equivalent_standard_execution_class(pipeline_class: str, workflow_id: str) "studioSpecId": "anima:modular-text-to-image:v1", "executionRoute": "official_modular_workflow", }, + { + "pipelineClass": "ErnieImageModularPipeline", + "workflowId": "text2image", + "adapterSource": "workflow", + "adapterId": "official_top_level_blocks", + "studioMode": "text_to_image", + "studioSpecId": "ernie-image-turbo:modular-text-to-image:v1", + "executionRoute": "official_modular_workflow", + }, { "pipelineClass": "AnimaModularPipeline", "workflowId": "img2img", @@ -1012,15 +826,6 @@ def _equivalent_standard_execution_class(pipeline_class: str, workflow_id: str) ) REVIEWED_CLUSTER_EXECUTION_CANDIDATES += ( - { - "pipelineClass": "ErnieImageModularPipeline", - "workflowId": "text2image", - "adapterSource": "mode", - "adapterId": "equivalent_standard_route", - "studioMode": "text_to_image", - "studioSpecId": "ernie-image:equivalent-standard-text-to-image:v1", - "executionRoute": "equivalent_standard", - }, { "pipelineClass": "LTXModularPipeline", "workflowId": "text2video", @@ -1533,6 +1338,7 @@ def _audit_candidate( "classifierFreeGuidance": "ClassifierFreeGuidance", "defaultWorkflow": "default", "workflowId": workflow_id, + "workflowPromptEnhancerBlock": "prompt_enhancer", "workflowTextEncoderBlock": "text_encoder", "workflowBeforeEncodeBlock": "before_encode", "workflowImageEncoderBlock": "vae_encoder", diff --git a/modiff/huggingface_cluster_runtime.py b/modiff/huggingface_cluster_runtime.py index b87fdea3..b951f7b4 100644 --- a/modiff/huggingface_cluster_runtime.py +++ b/modiff/huggingface_cluster_runtime.py @@ -178,9 +178,9 @@ def _effective_reviewed_artifact( "block-definition-v2-8c3a003b", "sha256:c8de506963cfb8c7aaf8e102d7aec7689033a32c10e5105e155f7b52c9ddc731", ), - "diffusers.cluster-admission:ErnieImageModularPipeline:text2image:mode:equivalent_standard_route": ( - "block-definition-v2-30138b23", - "sha256:3792671213c6136ad61d90642fca28a851d88e2203c017ca9a15f3145c9ec22c", + "diffusers.cluster-admission:ErnieImageModularPipeline:text2image:workflow:official_top_level_blocks": ( + "block-definition-v2-8e2880d6", + "sha256:d777a3bb8098c6e1a4a37b9442d64df03326ba21697192f9b9c2db4fff094d15", ), "diffusers.cluster-admission:Flux2KleinKVPipeline:edit_image:mode:edit_image": ( "block-definition-v2-be5bd288", @@ -722,9 +722,14 @@ def qualify_huggingface_cluster_expert_runtime( model_type = str(definition.get("pipelineClass") or "") mode = str(admission.get("studioMode") or "") provider = str(definition.get("provider") or "") - profiles = list(execution_profiles_for_execution(model_type, mode)) - profile = _single(profiles, "the exact backend execution profile is unavailable.") spec = admission.get("studioExecutionSpec") + sealed_profile_id = str(spec.get("executionProfileId") or "") if isinstance(spec, Mapping) else "" + profiles = [ + profile + for profile in execution_profiles_for_execution(model_type, mode) + if profile.id == sealed_profile_id + ] + profile = _single(profiles, "the exact backend execution profile is unavailable.") public_spec = studio_execution_spec_for_pair(model_type, mode) if ( not isinstance(spec, Mapping) diff --git a/modiff/huggingface_node_library.py b/modiff/huggingface_node_library.py index ec215ad6..baf13476 100644 --- a/modiff/huggingface_node_library.py +++ b/modiff/huggingface_node_library.py @@ -282,7 +282,7 @@ def _integration_status(pipeline_class: str, workflow_id: str) -> str: } -def _graph_adapter_contracts( +def graph_adapter_contracts( pipeline_class: str, workflow_id: str, workflow: Mapping[str, Any], @@ -396,7 +396,7 @@ def _definition( # never an execution claim. "executionClaim": "discovery_only", "executionAdmissions": [], - "graphAdapterContracts": _graph_adapter_contracts(pipeline_class, workflow_id, workflow), + "graphAdapterContracts": graph_adapter_contracts(pipeline_class, workflow_id, workflow), "inputs": _json_clone(workflow["inputs"]), "outputs": _json_clone(workflow["outputs"]), "requiredInputs": _json_clone(workflow["requiredInputs"]), @@ -834,7 +834,7 @@ def validate_huggingface_node_library(value: Any) -> dict[str, Any]: ) ): raise HuggingFaceNodeLibraryError("Hugging Face node definition collections are malformed.") - if definition["graphAdapterContracts"] != _graph_adapter_contracts( + if definition["graphAdapterContracts"] != graph_adapter_contracts( definition["pipelineClass"], definition["workflowId"], definition ): raise HuggingFaceNodeLibraryError("Hugging Face node definition graph adapter contracts are invalid.") diff --git a/modiff/image_native_variant_reviews.py b/modiff/image_native_variant_reviews.py new file mode 100644 index 00000000..99178bf3 --- /dev/null +++ b/modiff/image_native_variant_reviews.py @@ -0,0 +1,81 @@ +"""Exact, pinned image-variant decisions, separate from live qualification. + +Source paths below are relative to the reviewed Diffusers package unless they +start with ``modules/``. No base-family output proves an exact variant works. +""" +from copy import deepcopy + +from modiff.modular_workflow_contracts import PINNED_DIFFUSERS_REVISION + +_REVIEWED_REVISION = "fbf49e7f35857f76bc57b177e26f12b03687c668" + +def _native(profile, task, source, limitations=()): + return {"decision": "native_recipe", "nativeProfileId": profile, "nativeTask": task, + "source": source, "limitations": list(limitations)} + + +_PAG_LIMITS = ( + "Native PAG uses the pinned guider's exclusive start boundary: step zero is unperturbed.", + "Fixed PAG scale only; adaptive PAG scale remains a standard-pipeline feature.", + "Explicit mid-block preset targets all ten SDXL transformer layers; not bitwise equivalence.", +) +_REVIEWS = { + f"{profile}/{task}": _native("sdxl-pag:modular", native_task, + "guiders/perturbed_attention_guidance.py", _PAG_LIMITS) + for profile, task, native_task in ( + ("sdxl-pag:direct", "text_to_image", "text_to_image"), + ("sdxl-pag:img2img-direct", "edit_image", "image_to_image"), + ("sdxl-pag:inpaint-direct", "inpaint", "inpaint"), + ("sdxl-pag-controlnet-canny:direct", "control_image", "control_image"), + ("sdxl-pag-controlnet-canny:img2img-direct", "control_edit_image", "control_edit_image"), + ) +} +_REVIEWS.update({ + "flux-dev:img2img-direct/edit_image": _native( + "flux-dev:modular", "image_to_image", "modular_pipelines/flux/modular_blocks_flux.py", + ("The standard edit_image task names the native image2image workflow, not Kontext editing.",)), + "flux-krea:direct/text_to_image": _native( + "flux-krea:modular", "text_to_image", "modular_pipelines/flux/before_denoise.py", + ("Separate Krea artifact; 28 steps and guidance 3.5; no inherited dev output proof.",)), + "flux-schnell:direct/text_to_image": _native( + "flux-schnell:modular", "text_to_image", "modular_pipelines/flux/before_denoise.py", + ("Four steps, guidance zero, 256-token encoder limit; guidance_embeds comes from the transformer config.",)), + "sdxl-turbo:direct/text_to_image": _native( + "sdxl-turbo:modular", "text_to_image", "modular_pipelines/stable_diffusion_xl/denoise.py", + ("512px, one step, CFG disabled, repository scheduler and exact fp16 weight variant.",)), + "flux2-dev:direct/multi_image_reference_edit": _native( + "flux2:modular", "multi_image_reference_edit", "modular_pipelines/flux2/encoders.py", + ("One to eight ordered independent references, not an image batch or stitched canvas.",)), + "flux2-klein:direct/multi_image_reference_edit": _native( + "flux2-klein:modular", "multi_image_reference_edit", "modular_pipelines/flux2/encoders.py", + ("Distilled 4B weights; one to eight ordered independent references; no Klein KV equivalence.",)), + "flux2-klein-kv:direct/text_to_image": _native( + "flux2-klein-kv:t2i-modular", "text_to_image", "pipelines/flux2/pipeline_flux2_klein_kv.py", + ("Text-only takes the standard-forward branch, with no reference K/V cache. Exact 9B KV index declares Flux2KleinPipeline, is_distilled=true, and the normal Flux2 transformer/VAE; this does not admit its cached-reference tasks or inherit 4B memory qualification.",)), + "flux-kontext:direct/multi_image_reference_edit": _native( + "flux-kontext:modular", "multi_image_reference_edit", "modules/ImageOperations/main.py:stitch_reference_images", + ("Visible bounded equal-height horizontal composition precedes the native VAE encoder. Native lists alone represent a batch, not multiple references for one result.",)), +}) +for _route in ( + "flux-dev:img2img-direct/edit_image@black-forest-labs/FLUX.1-dev-FP8", + "flux-kontext:direct/multi_image_reference_edit@black-forest-labs/FLUX.1-Kontext-dev-NVFP4", +): + _REVIEWS[_route] = { + "decision": "artifact_blocked", "reason": "root_checkpoint_has_no_reviewed_modular_component_layout", + "source": "modules/ModularDiffusers/loaders.py:_validate_reviewed_pipeline_index", + "limitations": ["A root FP8/NVFP4 checkpoint is not a Diffusers component repository. No reviewed native conversion or dequantization recipe is declared; do not substitute base weights."], + } +for _task in ("edit_image", "multi_image_reference_edit"): + _REVIEWS[f"flux2-klein-kv:direct/{_task}"] = { + "decision": "upstream_exception", "reason": "pinned_modular_klein_has_no_kv_extract_cached_path", + "source": "pipelines/flux2/pipeline_flux2_klein_kv.py", + "limitations": ["The standard KV pipeline extracts reference K/V once and reuses it on later steps. Pinned modular Klein does not implement kv_cache_mode=extract/cached; keep the exact standard KV executor for reference tasks."], + } + + +def image_native_variant_review(route_id): + if PINNED_DIFFUSERS_REVISION != _REVIEWED_REVISION: + raise ValueError("Image variant decisions must be re-reviewed for the new Diffusers pin.") + review = _REVIEWS.get(route_id) + return {"status": "reviewed", "diffusersRevision": PINNED_DIFFUSERS_REVISION, + "liveQualification": "not_established_by_review", **deepcopy(review)} if review else None diff --git a/modiff/image_prototyping_readiness.py b/modiff/image_prototyping_readiness.py new file mode 100644 index 00000000..e722338a --- /dev/null +++ b/modiff/image_prototyping_readiness.py @@ -0,0 +1,615 @@ +"""Deterministic image-route ledger for the prototyping handoff. + +This inventory joins existing public execution profiles, model capabilities, +artifact pins, the pinned Diffusers operation inventory, and the reviewed +Modular workflow snapshot. It performs no registry import, custom-extension +load, network request, package installation, or model construction. + +The checked-in snapshot uses a Linux/x86_64 reference target so its content is +identical on every generator host. It is not the current machine's runtime +readiness; live public profiles retain platform-specific runtime selection. + +The ledger deliberately reports implementation gaps. A native upstream +workflow does not become an allowed whole-pipeline exception merely because +MoDiff currently routes the same repository through a standard adapter. +""" + +from __future__ import annotations + +import hashlib +import json +from collections import Counter +from pathlib import Path +from typing import Any, Mapping + +from modiff.diffusers_profiles import public_execution_profiles +from modiff.image_native_variant_reviews import image_native_variant_review +from modiff.model_artifact_catalog import catalog_download_inventory, catalog_repository_pin +from modiff.modular_action_bindings import MODULAR_ACTION_BINDINGS +from modiff.modular_whole_workflow_contracts import reviewed_whole_workflow_graph_adapter +from modiff.modular_workflow_contracts import ( + PINNED_DIFFUSERS_REVISION, + PINNED_MODULAR_WORKFLOW_TRUTH, + PINNED_MODULAR_WORKFLOW_REPOSITORY_VARIANTS, +) +from modiff.modular_workflow_discovery import load_reviewed_modular_workflow_snapshot +from modiff.operation_inventory import OPERATION_INVENTORY_PATH, load_operation_inventory +from modiff.studio_execution_specs import ( + reviewed_repository_download_files, studio_capability_definitions, studio_execution_spec_for_pair, +) + + +IMAGE_PROTOTYPING_READINESS_SCHEMA_VERSION = 1 +IMAGE_PROTOTYPING_READINESS_PATH = ( + Path(__file__).resolve().parents[1] / "data" / "image-prototyping-readiness.v1.json" +) + +_IMAGE_TASKS = frozenset( + { + "control_edit_image", + "control_image", + "control_inpaint", + "control_union_edit_image", + "control_union_image", + "control_union_inpaint", + "depth_estimation", + "edit_image", + "image_adjustment", + "image_channels", + "image_crop", + "image_filter", + "image_stitch", + "image_tile", + "image_to_image", + "image_to_text", + "image_upscale", + "inpaint", + "inpainting", + "ip_adapter_control_edit_image", + "ip_adapter_control_image", + "ip_adapter_control_inpaint", + "ip_adapter_control_union_edit_image", + "ip_adapter_control_union_image", + "ip_adapter_control_union_inpaint", + "ip_adapter_edit_image", + "ip_adapter_image", + "ip_adapter_inpaint", + "layer_decomposition", + "mask_composite", + "modular_image_to_image", + "modular_inpainting", + "modular_text_to_image", + "multi_image_reference_edit", + "outpaint", + "text_to_image", + "unconditional_image", + } +) + +_TASK_ALIASES = { + "modular_image_to_image": "image_to_image", + "modular_inpainting": "inpaint", + "modular_text_to_image": "text_to_image", + "inpainting": "inpaint", + "image2image": "image_to_image", + "controlnet_image2image": "control_edit_image", + "controlnet_inpainting": "control_inpaint", + "image_conditioned_inpainting": "inpaint", + "controlnet_union_text2image": "control_union_image", + "controlnet_union_image2image": "control_union_edit_image", + "controlnet_union_inpainting": "control_union_inpaint", + "ip_adapter_text2image": "ip_adapter_image", + "ip_adapter_image2image": "ip_adapter_edit_image", + "ip_adapter_inpainting": "ip_adapter_inpaint", + "ip_adapter_controlnet_text2image": "ip_adapter_control_image", + "ip_adapter_controlnet_image2image": "ip_adapter_control_edit_image", + "ip_adapter_controlnet_inpainting": "ip_adapter_control_inpaint", + "ip_adapter_controlnet_union_text2image": "ip_adapter_control_union_image", + "ip_adapter_controlnet_union_image2image": "ip_adapter_control_union_edit_image", + "ip_adapter_controlnet_union_inpainting": "ip_adapter_control_union_inpaint", +} + +_NON_DIFFUSION_PATHS = frozenset( + { + "builtin-image-operation", + "direct-huggingface-transformers-any-to-any", + "direct-huggingface-transformers-depth", + "direct-huggingface-transformers-image-text", + "spandrel-image-upscale", + } +) + +_TOP_LEVEL_STAGE_ROLES = { + "after_decode": "postprocess_image", + "decode": "decode_latents", + "denoise": "denoise", + "image_encoder": "encode_image", + "prompt_enhancer": "rewrite_prompt", + "prompt_upsample": "rewrite_prompt", + "text_encoder": "encode_prompt", + "vae_encoder": "encode_image", +} + +_TASK_REQUIRED_NATIVE_ROLES = { + "layer_decomposition": frozenset({"decode_latents"}), +} +_DEFAULT_REQUIRED_NATIVE_ROLES = frozenset({"encode_prompt", "denoise", "decode_latents"}) + +# Independent upstream family audit. These are candidates, NOT claims that a +# variant (PAG, Turbo, KV, ControlNet, etc.) is interchangeable with the base. +# An absent MoDiff artifact binding cannot establish an upstream exception. +_STANDARD_NATIVE_CANDIDATES = { + "ErnieImagePipeline": "ErnieImageModularPipeline", + "FluxPipeline": "FluxModularPipeline", + "FluxImg2ImgPipeline": "FluxModularPipeline", + "Flux2Pipeline": "Flux2ModularPipeline", + "Flux2KleinPipeline": "Flux2KleinModularPipeline", + "Flux2KleinKVPipeline": "Flux2KleinModularPipeline", + "FluxKontextPipeline": "FluxKontextModularPipeline", + "QwenImagePipeline": "QwenImageModularPipeline", + "QwenImageEditPipeline": "QwenImageEditModularPipeline", + "QwenImageEditPlusPipeline": "QwenImageEditPlusModularPipeline", + "StableDiffusionXLPipeline": "StableDiffusionXLModularPipeline", + "StableDiffusionXLImg2ImgPipeline": "StableDiffusionXLModularPipeline", + "StableDiffusionXLInpaintPipeline": "StableDiffusionXLModularPipeline", + "StableDiffusionXLTurboPipeline": "StableDiffusionXLModularPipeline", + "StableDiffusionXLPAGPipeline": "StableDiffusionXLModularPipeline", + "StableDiffusionXLPAGImg2ImgPipeline": "StableDiffusionXLModularPipeline", + "StableDiffusionXLPAGInpaintPipeline": "StableDiffusionXLModularPipeline", + "StableDiffusionXLControlNetPAGImg2ImgPipeline": "StableDiffusionXLModularPipeline", + "StableDiffusion3Pipeline": "StableDiffusion3ModularPipeline", + "ZImagePipeline": "ZImageModularPipeline", +} + + +class ImagePrototypingReadinessError(ValueError): + """Raised when the deterministic image readiness ledger is malformed.""" + + +def _content_hash(value: Mapping[str, Any]) -> str: + encoded = json.dumps(value, allow_nan=False, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return "sha256:" + hashlib.sha256(encoded.encode("utf-8")).hexdigest() + + +def _file_hash(path: Path) -> str: + return "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest() + + +def _canonical_task(task: str) -> str: + return _TASK_ALIASES.get(task, task) + + +def _workflow_stage_roles(workflow: Mapping[str, Any]) -> list[str]: + roles = set() + for step in workflow.get("steps", []): + path = str(step.get("path") or "") + prefix = path.split(".", 1)[0] + roles.add(_TOP_LEVEL_STAGE_ROLES.get(prefix, f"upstream:{prefix}")) + return sorted(roles) + + +def _matching_workflow( + workflows: Mapping[tuple[str, str], list[dict[str, Any]]], + pipeline_class: str, + task: str, +) -> dict[str, Any] | None: + route = _native_mode(pipeline_class, task) + # Public task names differ from upstream workflow identifiers (notably + # SDXL Union/IP-Adapter). Join through the reviewed mode contract, not a + # guessed spelling or the first vaguely related workflow. + if route is not None: + matches = [ + workflow + for (candidate_class, _task), candidates in workflows.items() + if candidate_class == pipeline_class + for workflow in candidates + if workflow["id"] == (route.upstream_workflow or "default") + ] + else: + matches = workflows.get((pipeline_class, _canonical_task(task)), []) + return matches[0] if len(matches) == 1 else None + + +def _native_mode(pipeline_class: str, task: str): + truth = PINNED_MODULAR_WORKFLOW_TRUTH.get(pipeline_class) + if truth is None: + return None + canonical_task = _canonical_task(task) + if pipeline_class == "StableDiffusionXLModularPipeline" and task == "edit_image": + canonical_task = "image_to_image" + if pipeline_class in {"Flux2ModularPipeline", "Flux2KleinModularPipeline", "FluxKontextModularPipeline"} and task == "multi_image_reference_edit": + canonical_task = "edit_image" + matches = [route for mode, route in truth.modes if _canonical_task(mode) == canonical_task] + if not matches: + matches = [route for name, route in truth.state_flows if _canonical_task(name) == canonical_task] + return matches[0] if len(matches) == 1 else None + + +def _integrated_native_route(pipeline_class: str, task: str, workflow: Mapping[str, Any] | None) -> bool: + route = _native_mode(pipeline_class, task) + if workflow is None: + return False + whole = reviewed_whole_workflow_graph_adapter(pipeline_class, workflow["id"]) + actions = whole["actionSequence"] if whole else route.action_sequence if route else () + edges = whole["stateEdges"] if whole else route.state_edges if route else () + if not actions or not edges or any(action not in MODULAR_ACTION_BINDINGS for action in actions): + return False + required = _TASK_REQUIRED_NATIVE_ROLES.get(_canonical_task(task), _DEFAULT_REQUIRED_NATIVE_ROLES) + if not required.issubset(_workflow_stage_roles(workflow)): + return False + # Generic stage adapters predate the optional whole-workflow block list. + # Its absence is not a missing implementation. When provided, verify every + # named block against the independent upstream snapshot. + prefixes = {str(step["path"]).split(".", 1)[0] for step in workflow.get("steps", [])} + sequence = whole["upstreamBlockSequence"] if whole else route.upstream_block_sequence + return all(block.split(".", 1)[0] in prefixes for block in sequence) + + +def _artifact_inventory(profile: Mapping[str, Any], capability: Mapping[str, Any] | None) -> dict[str, Any]: + repository = str(profile.get("default_repo") or "") + pin = catalog_repository_pin(repository, model_type=str(profile.get("model_type") or "")) if repository else None + files = list((capability or {}).get("downloadFiles") or []) if ( + (capability or {}).get("defaultRepo") == repository + ) else [] + if not files: + selections = [item for item in (capability or {}).get("artifactSelections", []) + if item.get("repo") == repository] + if len(selections) == 1: + files = list(selections[0].get("downloadFiles") or []) + if not files: + files = reviewed_repository_download_files(repository) + pinned_files = [item for item in (pin or {}).get("files", []) if isinstance(item, Mapping)] + exact_bytes = None + if pinned_files and all(type(item.get("byteSize")) is int and item["byteSize"] > 0 for item in pinned_files): + exact_bytes = sum(item["byteSize"] for item in pinned_files) + inventory = catalog_download_inventory(repository, (pin or {}).get("revision"), files) + if inventory: + exact_bytes = inventory["exactBytes"] + return { + "repository": repository or None, + "revision": (pin or {}).get("revision"), + "license": (pin or {}).get("license"), + "gated": (pin or {}).get("gated", (inventory or {}).get("gated")), + "declaredFileCount": len(files) if files else None, + "exactBytes": exact_bytes, + "inventoryState": "exact_bytes" if exact_bytes is not None else "paths_only" if files else "unknown", + } + + +def _display_name(profile: Mapping[str, Any], capability: Mapping[str, Any] | None) -> str: + return str( + (capability or {}).get("displayName") + or (capability or {}).get("label") + or profile.get("model_type") + or profile.get("id") + ) + + +def _evidence(profile: Mapping[str, Any], *, native_integrated: bool) -> dict[str, str]: + return { + "structural": "implemented_awaiting_qualification" if native_integrated else "not_applicable_or_missing", + "browser": "pending_final_candidate", + "nativeOutput": "historical_not_final_revision" if profile.get("live_proof") else "pending", + "parameterConsumption": "pending", + "reuse": "pending", + "service": "pending", + "platform": "pending", + } + + +def build_image_prototyping_readiness(root: Path) -> dict[str, Any]: + """Build the backend-owned image-route denominator without side effects.""" + + root = root.resolve() + operation_inventory = load_operation_inventory() + modular_snapshot_path = root / "data" / "modular-workflow-contracts.json" + modular_snapshot = load_reviewed_modular_workflow_snapshot(modular_snapshot_path) + capabilities = studio_capability_definitions() + profiles = public_execution_profiles( + observe_optional_runtime=False, platform_name="linux", machine="x86_64", + ) + + workflows: dict[tuple[str, str], list[dict[str, Any]]] = {} + for pipeline in modular_snapshot["contracts"]: + for workflow in pipeline["workflows"]: + workflows.setdefault((pipeline["pipelineClass"], _canonical_task(workflow["taskId"])), []).append(workflow) + + image_profiles = [ + profile + for profile in profiles + if any(mode in _IMAGE_TASKS for mode in profile.get("modes", [])) + ] + # Ordinary operation recipes also advertise ordered-reference tasks. They + # do not add legacy Cluster modes or silently change its compiled admission. + image_profiles = [ + {**profile, "modes": [*profile["modes"], "multi_image_reference_edit"]} + if profile["id"] in {"flux2:modular", "flux2-klein:modular", "flux-kontext:modular"} + else profile for profile in image_profiles + ] + # Compatible/fallback artifacts are selectable routes, not aliases for the + # default weights. Their architecture, dtype and quantization evidence may + # differ even when they share the execution profile and stage graph. + advertised_profiles = [] + for profile in image_profiles: + advertised_profiles.append(profile) + alternatives = { + repo: set(profile["modes"]) + for repo in (profile.get("fallback_repo"), *(profile.get("compatible_repos") or [])) + if repo and repo != profile.get("default_repo") + } + if profile["execution_path"] == "modular-diffusers": + for mode in profile["modes"]: + native = _native_mode(profile["pipeline_class"], mode) + if native is None: + continue + for repo in PINNED_MODULAR_WORKFLOW_REPOSITORY_VARIANTS.get( + (profile["pipeline_class"], native.upstream_workflow), (), + ): + if repo != profile.get("default_repo"): + alternatives.setdefault(repo, set()).add(mode) + advertised_profiles.extend( + {**profile, "default_repo": repo, "_artifact_variant": repo, "modes": sorted(modes)} + for repo, modes in sorted(alternatives.items()) + ) + modular_by_repository_task: dict[tuple[str, str], list[dict[str, Any]]] = {} + for profile in advertised_profiles: + if profile["execution_path"] != "modular-diffusers": + continue + for mode in profile["modes"]: + if mode in _IMAGE_TASKS: + modular_by_repository_task.setdefault( + (str(profile.get("default_repo") or "").lower(), _canonical_task(mode)), [] + ).append(profile) + + inventory_by_class = {item["pipelineClass"]: item for item in operation_inventory["pipelines"]} + routes = [] + seen_route_ids = set() + for profile in sorted(advertised_profiles, key=lambda item: (item["id"], item.get("_artifact_variant", ""))): + capability = capabilities.get(profile["model_type"]) + for mode in sorted(item for item in profile["modes"] if item in _IMAGE_TASKS): + task = _canonical_task(mode) + route_id = f"{profile['id']}/{mode}" + if profile.get("_artifact_variant"): + route_id += f"@{profile['_artifact_variant']}" + variant_review = image_native_variant_review(route_id) + if route_id in seen_route_ids: + raise ImagePrototypingReadinessError(f"Duplicate image route {route_id!r}.") + seen_route_ids.add(route_id) + + own_workflow = _matching_workflow(workflows, profile["pipeline_class"], task) + model_workflow = _matching_workflow(workflows, profile["model_type"], task) + stage_roles = _workflow_stage_roles(own_workflow) if own_workflow else [] + required_roles = _TASK_REQUIRED_NATIVE_ROLES.get(task, _DEFAULT_REQUIRED_NATIVE_ROLES) + required_roles_present = bool(own_workflow and required_roles.issubset(stage_roles)) + full_stage_chain = _integrated_native_route(profile["pipeline_class"], task, own_workflow) + native_integrated = profile["execution_path"] == "modular-diffusers" and full_stage_chain + + same_artifact_native = modular_by_repository_task.get( + (str(profile.get("default_repo") or "").lower(), task), [] + ) + integrated_alternatives = [ + peer["id"] for peer in same_artifact_native + if peer["model_type"] == peer["pipeline_class"] and _integrated_native_route( + peer["pipeline_class"], task, _matching_workflow(workflows, peer["pipeline_class"], task) + ) + ] + native_candidate = _STANDARD_NATIVE_CANDIDATES.get(profile["pipeline_class"]) + if any(marker in profile["pipeline_class"] for marker in ("PAG", "Turbo", "KV")): + integrated_alternatives = [] # variant equivalence requires its own reviewed adapter + if variant_review and variant_review["decision"] == "native_recipe": + reviewed_task = variant_review["nativeTask"] + integrated_alternatives = [ + peer["id"] for peer in advertised_profiles + if peer["id"] == variant_review["nativeProfileId"] + and peer["default_repo"] == profile["default_repo"] + and reviewed_task in peer["modes"] + and peer["execution_path"] == "modular-diffusers" + and _integrated_native_route(peer["pipeline_class"], reviewed_task, + _matching_workflow(workflows, peer["pipeline_class"], reviewed_task)) + ] + elif variant_review: + integrated_alternatives = [] + usable_native_alternative = bool( + same_artifact_native + or ( + model_workflow + and _integrated_native_route(profile["model_type"], task, model_workflow) + ) + # Keep a native export that MoDiff has not integrated visible as + # a gap rather than laundering it into an upstream exception. + or (model_workflow and profile["model_type"].endswith("ModularPipeline")) + ) + artifact = _artifact_inventory(profile, capability) + + if variant_review and variant_review["decision"] in {"artifact_blocked", "upstream_exception"}: + disposition = ("blocked" if variant_review["decision"] == "artifact_blocked" + else "documented_whole_pipeline_exception") + reason_code = variant_review["reason"] + elif variant_review and variant_review["decision"] == "native_recipe" and not integrated_alternatives: + disposition = "native_integration_missing" + reason_code = "reviewed_native_recipe_stage_chain_unavailable" + elif profile["execution_path"] in _NON_DIFFUSION_PATHS: + disposition = "non_diffusion_image_task" + reason_code = "task_has_no_diffusion_stage_semantics" + elif native_integrated: + disposition = "native_integrated" + reason_code = "complete_native_stage_chain_declared" + elif profile["execution_path"] == "modular-diffusers": + disposition = "native_integration_missing" + reason_code = "public_modular_profile_lacks_complete_stage_chain" + elif integrated_alternatives: + disposition = "preserved_standard_alternative" + reason_code = "exact_artifact_task_has_integrated_native_alternative" + elif usable_native_alternative: + disposition = "native_integration_missing" + reason_code = "public_whole_pipeline_route_has_usable_native_alternative" + elif native_candidate: + disposition = "blocked" + reason_code = "native_family_requires_exact_artifact_variant_task_review" + elif profile["execution_path"].startswith("direct-"): + disposition = "documented_whole_pipeline_exception" + reason_code = "no_pinned_modular_workflow_for_exact_profile" + else: + disposition = "blocked" + reason_code = "unclassified_execution_path" + + spec = studio_execution_spec_for_pair(profile["model_type"], mode) + advertisement_entry_points = [f"execution_profile:{profile['id']}"] + if profile.get("_artifact_variant"): + advertisement_entry_points.append(f"execution_profile_artifact:{profile['_artifact_variant']}") + if capability is not None: + advertisement_entry_points.append(f"model_capability:{profile['model_type']}") + if spec is not None and spec.get("executionProfileId") == profile["id"]: + advertisement_entry_points.append(f"studio_execution_spec:{spec['id']}") + advertisement_entry_points.append( + f"node_model_dropdown:{profile['loader_module']}.{profile['loader_action']}" + ) + + upstream_record = inventory_by_class.get(profile["pipeline_class"]) + known_defects = [] + if disposition in {"native_integration_missing", "blocked"}: + known_defects.append(reason_code) + if artifact["revision"] is None and profile["execution_path"] not in _NON_DIFFUSION_PATHS: + known_defects.append("missing_immutable_artifact_revision") + if artifact["inventoryState"] != "exact_bytes" and profile["execution_path"] not in _NON_DIFFUSION_PATHS: + known_defects.append("exact_artifact_byte_inventory_not_in_catalog") + + routes.append( + { + "routeId": route_id, + "displayName": _display_name(profile, capability), + "modelType": profile["model_type"], + "task": mode, + "canonicalTask": task, + "artifactVariantOf": f"{profile['id']}/{mode}" if profile.get("_artifact_variant") else None, + "implementation": { + "profileId": profile["id"], + "executionPath": profile["execution_path"], + "pipelineClass": profile["pipeline_class"], + "loader": f"{profile['loader_module']}.{profile['loader_action']}", + }, + "advertisementEntryPoints": sorted(advertisement_entry_points), + "artifact": artifact, + "optionalRuntime": profile["optionalRuntimeRequirement"], + "upstream": { + "diffusersRevision": PINNED_DIFFUSERS_REVISION, + "inventoryCoverage": (upstream_record or {}).get("coverage"), + "nativePipelineClass": (own_workflow or model_workflow or {}).get("executionPipelineClass"), + "nativeWorkflowId": (own_workflow or model_workflow or {}).get("id"), + "candidateNativePipelineClass": native_candidate, + "stageRoles": stage_roles + if own_workflow + else _workflow_stage_roles(model_workflow) + if model_workflow + else [], + "requiredCoreRolesPresent": required_roles_present, + "fullStageChain": full_stage_chain, + }, + "disposition": disposition, + "dispositionReason": reason_code, + "standardAndNativeAlternativesCoexist": bool( + profile["execution_path"] != "modular-diffusers" and same_artifact_native + ), + "integratedNativeAlternativeProfileIds": sorted(integrated_alternatives), + "exactVariantReview": variant_review, + "exception": { + "checkedDiffusersRevision": PINNED_DIFFUSERS_REVISION, + "limitations": [ + "no_independent_stage_editing", + "no_cross_stage_prompt_or_latent_cache", + "component_substitution_limited_to_standard_pipeline_contract", + ], + "revisitTrigger": "reviewed_diffusers_pin_or_native_profile_change", + } + if disposition == "documented_whole_pipeline_exception" + else None, + "evidence": _evidence(profile, native_integrated=native_integrated), + "knownDefects": sorted(set(known_defects)), + } + ) + + dispositions = Counter(route["disposition"] for route in routes) + semantic = { + "schemaVersion": IMAGE_PROTOTYPING_READINESS_SCHEMA_VERSION, + "kind": "image_prototyping_readiness", + "diffusersRevision": PINNED_DIFFUSERS_REVISION, + "coverageBoundary": { + "backendExecutionProfiles": "snapshotted", + "backendModelCapabilities": "snapshotted", + "backendArtifactPins": "snapshotted", + "upstreamModularWorkflows": "snapshotted", + "clientTemplatesAndTaskChoosers": "requires_paired_client_gate", + "customFirstPartyExamples": "requires_paired_custom_e2e", + }, + "sources": { + "operationInventory": _file_hash(OPERATION_INVENTORY_PATH), + "modularWorkflowSnapshot": _file_hash(modular_snapshot_path), + "modelArtifactCatalog": _file_hash(root / "data" / "model-artifact-catalog.json"), + "publicExecutionProfiles": _content_hash({"profiles": image_profiles}), + "exactVariantReviews": _file_hash(root / "modiff" / "image_native_variant_reviews.py"), + }, + "imageTasks": sorted(_IMAGE_TASKS), + "routes": routes, + "summary": { + "routeCount": len(routes), + "profileCount": len(image_profiles), + "dispositions": dict(sorted(dispositions.items())), + "nativeIntegrationMissingCount": dispositions["native_integration_missing"], + "knownDefectRouteCount": sum(bool(route["knownDefects"]) for route in routes), + "denominatorFrozen": False, + "freezeBlockers": [ + "paired_client_advertisement_gate_not_recorded", + "custom_first_party_examples_not_recorded", + *( + ["native_integration_gaps_present"] + if dispositions["native_integration_missing"] + else [] + ), + ], + }, + } + return {**semantic, "contentHash": _content_hash(semantic)} + + +def render_image_prototyping_readiness(value: Mapping[str, Any]) -> str: + validate_image_prototyping_readiness(value) + return json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + + +def validate_image_prototyping_readiness(value: Any) -> dict[str, Any]: + if not isinstance(value, Mapping): + raise ImagePrototypingReadinessError("Image readiness ledger must be an object.") + normalized = json.loads(json.dumps(value, allow_nan=False)) + if normalized.get("schemaVersion") != IMAGE_PROTOTYPING_READINESS_SCHEMA_VERSION: + raise ImagePrototypingReadinessError("Unsupported image readiness schema.") + if normalized.get("diffusersRevision") != PINNED_DIFFUSERS_REVISION: + raise ImagePrototypingReadinessError("Image readiness ledger targets the wrong Diffusers revision.") + semantic = {key: item for key, item in normalized.items() if key != "contentHash"} + if normalized.get("contentHash") != _content_hash(semantic): + raise ImagePrototypingReadinessError("Image readiness content hash is invalid.") + routes = normalized.get("routes") + if not isinstance(routes, list) or not routes or len(routes) > 1024: + raise ImagePrototypingReadinessError("Image readiness routes are missing or oversized.") + route_ids = [route.get("routeId") for route in routes if isinstance(route, Mapping)] + if len(route_ids) != len(routes) or len(route_ids) != len(set(route_ids)): + raise ImagePrototypingReadinessError("Image readiness route IDs are invalid or duplicated.") + allowed = { + "native_integrated", + "native_integration_missing", + "preserved_standard_alternative", + "documented_whole_pipeline_exception", + "non_diffusion_image_task", + "blocked", + } + if any(route.get("disposition") not in allowed for route in routes): + raise ImagePrototypingReadinessError("Image readiness route has an invalid disposition.") + return normalized + + +def load_image_prototyping_readiness( + path: Path = IMAGE_PROTOTYPING_READINESS_PATH, +) -> dict[str, Any]: + raw = path.read_bytes() + if len(raw) > 4 * 1024 * 1024: + raise ImagePrototypingReadinessError("Image readiness ledger exceeds 4 MiB.") + return validate_image_prototyping_readiness(json.loads(raw)) diff --git a/modiff/integrated_operation_contracts.py b/modiff/integrated_operation_contracts.py new file mode 100644 index 00000000..fe3f1735 --- /dev/null +++ b/modiff/integrated_operation_contracts.py @@ -0,0 +1,80 @@ +"""Authoring declarations for existing actions that own and execute a model. + +An integrated action remains one ordinary node. This module constructs neither +models nor an alternate execution graph, and never resolves artifact files. +""" + +from copy import deepcopy + +from modiff.operation_contracts import build_pipeline_operation_contract +from modiff.upscaler_contracts import REAL_ESRGAN_X2_REPO, real_esrgan_x2_model_selection + + +_IMAGE_UPSCALE = ("SpandrelImageUpscaleV1", "image_upscale", "modules.Spandrel.Upscaler") +_BUILTIN_CLASS = "BuiltinImageOperationV1" +_BUILTIN_KEY = "modules.ImageOperations.ProcessImage" +_BUILTIN_FIELDS = { + "image_adjustment": {"brightness", "contrast", "saturation", "sharpness", "gamma", "temperature", "tint"}, + "image_filter": {"filter_operation", "amount", "threshold", "seed"}, + "image_crop": {"x", "y", "width", "height"}, + "image_upscale": {"resize_width", "resize_height", "resize_fit_mode", "resize_resampling"}, + "image_stitch": {"stitch_columns", "stitch_spacing", "stitch_background", "stitch_match_size"}, + "image_tile": {"rows", "columns"}, + "image_channels": {"channel"}, + "mask_composite": {"mask", "composite_mask_channel", "composite_invert_mask"}, +} + + +def integrated_operation_fields(contract, modules): + if contract["pipelineClass"] != _BUILTIN_CLASS: + return {} + task = contract["task"] + visible = _BUILTIN_FIELDS[task] | {"image", "output", "operation"} + return { + **{name: {"hidden": name not in visible} for name in modules["modules.ImageOperations"]["ProcessImage"]["params"]}, + "image": {"required": True}, + "mask": {"required": task == "mask_composite", "hidden": task != "mask_composite"}, + "operation": {"options": [task]}, + } + + +def get_integrated_operation_contracts(modules): + contracts = [] + for task in _BUILTIN_FIELDS: + binding = {"pipelineClass": _BUILTIN_CLASS, "task": task} + if "ProcessImage" not in modules.get("modules.ImageOperations", {}): + continue + contract = build_pipeline_operation_contract( + modules, pipeline_class=_BUILTIN_CLASS, task=task, operation_id="image.process", + node_key=_BUILTIN_KEY, field_overrides=integrated_operation_fields(binding, modules), + ) + contract.update(nodeType="integrated", decomposition="integrated") + contracts.append(contract) + pipeline, task, key = _IMAGE_UPSCALE + contract = build_pipeline_operation_contract( + modules, pipeline_class=pipeline, task=task, + operation_id="image.upscale", node_key=key, + ) + if contract is None: + return contracts + contract.update(nodeType="integrated", decomposition="integrated") + return [*contracts, contract] + + +def integrated_operation_values(contract, *, profile=None): + identity = (contract["binding"]["pipelineClass"], contract["task"], contract["nodeKey"]) + if identity[0] == _BUILTIN_CLASS and identity[1] in _BUILTIN_FIELDS and identity[2] == _BUILTIN_KEY: + if profile is not None and ( + profile.execution_path != "builtin-image-operation" + or profile.default_repo != "builtin://modiff/image-operations/v1" + ): + raise ValueError("The profile does not match the built-in operation.") + return {"pipeline_class": _BUILTIN_CLASS, "operation": identity[1]} + if identity != _IMAGE_UPSCALE: + raise ValueError("No reviewed integrated operation binding.") + if profile is not None and ( + profile.execution_path != "spandrel-image-upscale" + or profile.default_repo != REAL_ESRGAN_X2_REPO + ): + raise ValueError("The model profile does not match the integrated operation artifact.") + return deepcopy({"model_id": real_esrgan_x2_model_selection()}) diff --git a/modiff/ltx25_execution_contract.py b/modiff/ltx25_execution_contract.py index 81cc0030..f4ac96da 100644 --- a/modiff/ltx25_execution_contract.py +++ b/modiff/ltx25_execution_contract.py @@ -80,7 +80,7 @@ def configure_ltx25_distilled_denoise_components( """ if guider_factory is None: - from diffusers.modular_pipelines.ltx2.guider import LTX2Guidance + from diffusers.guiders.ltx2_guidance import LTX2Guidance guider_factory = LTX2Guidance update_components = getattr(pipeline, "update_components", None) diff --git a/modiff/model_artifact_catalog.py b/modiff/model_artifact_catalog.py index 47571e51..ebbdcc4d 100644 --- a/modiff/model_artifact_catalog.py +++ b/modiff/model_artifact_catalog.py @@ -143,6 +143,35 @@ def catalog_artifact_file(repo: str, filename: str) -> dict[str, Any] | None: return {**contract, "sha256": digest} +def catalog_download_inventory(repo: str, revision: str | None, files: list[str]) -> dict[str, Any] | None: + """Return exact selected-file metadata, never a download/runtime approval. + + Keep this separate from single-file GGUF execution contracts. Any changed + revision or app file selection invalidates the inventory instead of + inheriting a base-family byte count. + """ + if not revision or not files: + return None + matches = [item for item in read_model_artifact_catalog().get("downloadInventories", []) + if str(item.get("repo", "")).lower() == repo.lower() and item.get("revision") == revision] + if not matches: + return None + if len(matches) != 1: + raise ValueError(f"Duplicate download inventories for {repo!r} at {revision!r}.") + inventory = matches[0] + entries = inventory.get("files") + if not isinstance(entries, list) or not entries: + raise ValueError(f"Invalid download inventory for {repo!r}.") + names = [entry.get("path") for entry in entries] + if (any(not isinstance(name, str) or not name for name in names) + or len(set(names)) != len(names) + or any(type(entry.get("byteSize")) is not int or entry["byteSize"] < 0 for entry in entries)): + raise ValueError(f"Invalid download file metadata for {repo!r}.") + if set(names) != set(files): + return None + return {**inventory, "exactBytes": sum(entry["byteSize"] for entry in entries)} + + def catalog_revision(repo: str, *, model_type: str | None = None) -> str | None: """Return and validate the immutable revision recorded for ``repo``.""" diff --git a/modiff/modular_action_bindings.py b/modiff/modular_action_bindings.py new file mode 100644 index 00000000..3f0b40d5 --- /dev/null +++ b/modiff/modular_action_bindings.py @@ -0,0 +1,235 @@ +"""Existing Modular actions shared by graph admission and operation discovery. + +These identities select the existing executors; this is not another graph or +runtime registry. Keep saved action names stable. +""" + +MODULAR_ACTION_BINDINGS = { + "text_encoder": ("prompt", "modules.ModularDiffusers.EncodePrompt"), + "image_encoder": ("imageEmbeddings", "modules.ModularDiffusers.ImageEmbeddings"), + "vae_encoder": ("imageEncode", "modules.ModularDiffusers.ImageEncode"), + "controlnet": ("controlnet", "modules.ModularDiffusers.Controlnet"), + "ip_adapter": ("ipAdapter", "modules.ModularDiffusers.IPAdapter"), + "denoise": ("denoise", "modules.ModularDiffusers.Denoise"), + "decoder": ("decode", "modules.ModularDiffusers.DecodeLatents"), + "semantic_generator": ("prompt", "modules.ModularDiffusers.WorkflowSemanticGeneration"), + "workflow_denoise": ("denoise", "modules.ModularDiffusers.WorkflowDenoise"), + "workflow_audio_decoder": ("decode", "modules.ModularDiffusers.WorkflowDecodeAudio"), + "workflow_text_encoder": ("prompt", "modules.ModularDiffusers.WorkflowTextEncode"), + "workflow_image_encoder": ("imageEncode", "modules.ModularDiffusers.WorkflowImageEncode"), + "workflow_video_image_encoder": ( + "imageEncode", + "modules.ModularDiffusers.WorkflowVideoImageEncode", + ), + "workflow_video_encoder": ("videoEncode", "modules.ModularDiffusers.WorkflowVideoEncode"), + "workflow_image_denoise": ("denoise", "modules.ModularDiffusers.WorkflowImageDenoise"), + "workflow_image_decoder": ("decode", "modules.ModularDiffusers.WorkflowDecodeImage"), + "workflow_ernie_prompt_enhancer": ( + "promptEnhance", + "modules.ModularDiffusers.WorkflowErniePromptEnhance", + ), + "workflow_ernie_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowErnieTextEncode", + ), + "workflow_ernie_image_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowErnieImageDenoise", + ), + "workflow_ernie_image_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowErnieDecodeImage", + ), + "workflow_video_denoise": ("denoise", "modules.ModularDiffusers.WorkflowVideoDenoise"), + "workflow_video_decoder": ("decode", "modules.ModularDiffusers.WorkflowDecodeVideo"), + "workflow_hunyuan_video15_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowHunyuanVideo15TextEncode", + ), + "workflow_hunyuan_video15_vae_encoder": ( + "imageEncode", + "modules.ModularDiffusers.WorkflowHunyuanVideo15VaeEncode", + ), + "workflow_hunyuan_video15_image_encoder": ( + "imageEmbeddings", + "modules.ModularDiffusers.WorkflowHunyuanVideo15ImageEncode", + ), + "workflow_hunyuan_video15_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowHunyuanVideo15Denoise", + ), + "workflow_hunyuan_video15_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowHunyuanVideo15Decode", + ), + "workflow_stable_diffusion3_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowStableDiffusion3TextEncode", + ), + "workflow_stable_diffusion3_vae_encoder": ( + "imageEncode", + "modules.ModularDiffusers.WorkflowStableDiffusion3VaeEncode", + ), + "workflow_stable_diffusion3_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowStableDiffusion3Denoise", + ), + "workflow_stable_diffusion3_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowStableDiffusion3Decode", + ), + "workflow_krea2_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowKrea2TextEncode", + ), + "workflow_krea2_turbo_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowKrea2TurboTextEncode", + ), + "workflow_krea2_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowKrea2Denoise", + ), + "workflow_krea2_turbo_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowKrea2TurboDenoise", + ), + "workflow_krea2_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowKrea2Decode", + ), + "workflow_ideogram4_prompt_upsample": ( + "prompt", + "modules.ModularDiffusers.WorkflowIdeogram4PromptUpsample", + ), + "workflow_ideogram4_text_encoder": ( + "textEncode", + "modules.ModularDiffusers.WorkflowIdeogram4TextEncode", + ), + "workflow_ideogram4_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowIdeogram4Denoise", + ), + "workflow_ideogram4_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowIdeogram4Decode", + ), + "workflow_cosmos3_distilled_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowCosmos3DistilledTextEncode", + ), + "workflow_cosmos3_distilled_vae_encoder": ( + "imageEncode", + "modules.ModularDiffusers.WorkflowCosmos3DistilledVaeEncode", + ), + "workflow_cosmos3_distilled_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowCosmos3DistilledDenoise", + ), + "workflow_cosmos3_distilled_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowCosmos3DistilledDecode", + ), + "workflow_cosmos3_omni_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowCosmos3OmniTextEncode", + ), + "workflow_cosmos3_omni_vae_encoder": ( + "imageEncode", + "modules.ModularDiffusers.WorkflowCosmos3OmniVaeEncode", + ), + "workflow_cosmos3_omni_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowCosmos3OmniDenoise", + ), + "workflow_cosmos3_omni_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowCosmos3OmniDecode", + ), + "workflow_cosmos3_omni_after_decode": ( + "afterDecode", + "modules.ModularDiffusers.WorkflowCosmos3OmniAfterDecode", + ), + "workflow_minimax_h3_before_encode": ( + "beforeEncode", + "modules.ModularDiffusers.WorkflowMiniMaxH3BeforeEncode", + ), + "workflow_minimax_h3_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowMiniMaxH3TextEncode", + ), + "workflow_minimax_h3_vae_encoder": ( + "imageEncode", + "modules.ModularDiffusers.WorkflowMiniMaxH3VaeEncode", + ), + "workflow_minimax_h3_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowMiniMaxH3Denoise", + ), + "workflow_minimax_h3_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowMiniMaxH3Decode", + ), + "workflow_ltx25_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowLTX25TextEncode", + ), + "workflow_ltx25_duration": ( + "duration", + "modules.ModularDiffusers.WorkflowLTX25Duration", + ), + "workflow_ltx25_vae_encoder": ( + "imageEncode", + "modules.ModularDiffusers.WorkflowLTX25VaeEncode", + ), + "workflow_ltx25_condition_encoder": ( + "conditionEncode", + "modules.ModularDiffusers.WorkflowLTX25ConditionEncode", + ), + "workflow_ltx25_reference_encoder": ( + "referenceEncode", + "modules.ModularDiffusers.WorkflowLTX25ReferenceEncode", + ), + "workflow_ltx25_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowLTX25Denoise", + ), + "workflow_ltx25_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowLTX25Decode", + ), + "workflow_wan_animate_text_encoder": ( + "prompt", + "modules.ModularDiffusers.WorkflowWanAnimateTextEncode", + ), + "workflow_wan_animate_image_encoder": ( + "imageEmbeddings", + "modules.ModularDiffusers.WorkflowWanAnimateImageEncode", + ), + "workflow_wan_animate_video_encoder": ( + "videoEncode", + "modules.ModularDiffusers.WorkflowWanAnimateVideoEncode", + ), + "workflow_wan_animate_vae_encoder": ( + "imageEncode", + "modules.ModularDiffusers.WorkflowWanAnimateVaeEncode", + ), + "workflow_wan_animate_denoise": ( + "denoise", + "modules.ModularDiffusers.WorkflowWanAnimateDenoise", + ), + "workflow_wan_animate_decoder": ( + "decode", + "modules.ModularDiffusers.WorkflowWanAnimateDecode", + ), +} + + +# Existing typed media helpers are offered where a reviewed stage consumes their +# output. They are ordinary nodes, not additional upstream stages or state edges. +MODULAR_AUXILIARY_OPERATION_BINDINGS = { + "minimax_h3_references": ( + "diffusion.assemble_references", + "modules.ModularDiffusers.WorkflowMiniMaxH3ReferenceAssembler", + ), +} diff --git a/modiff/modular_block_contracts.py b/modiff/modular_block_contracts.py index 36c913dd..9cb273f5 100644 --- a/modiff/modular_block_contracts.py +++ b/modiff/modular_block_contracts.py @@ -237,8 +237,12 @@ def build_modular_block_contracts(pipeline: Any) -> dict[str, Any]: workflows = [] for workflow_id_value in workflow_ids: workflow_id = _name(workflow_id_value, "Workflow id") - selected = blocks.get_workflow(workflow_id) if workflow_map is not None else blocks - workflow = selected.get_execution_blocks() + if pipeline_class == "ErnieImageModularPipeline" and workflow_id == "text2image": + selected = blocks + workflow = selected.get_execution_blocks(use_pe=True) + else: + selected = blocks.get_workflow(workflow_id) if workflow_map is not None else blocks + workflow = selected.get_execution_blocks() root_definition = _block_definition(workflow) previous_root_definition = definitions.get(root_definition["id"]) if previous_root_definition is not None and previous_root_definition != root_definition: diff --git a/modiff/modular_block_role_adapters.py b/modiff/modular_block_role_adapters.py index 1776748b..fe8f514a 100644 --- a/modiff/modular_block_role_adapters.py +++ b/modiff/modular_block_role_adapters.py @@ -41,6 +41,7 @@ { ("AnimaModularPipeline", "img2img"), ("AnimaModularPipeline", "text2image"), + ("ErnieImageModularPipeline", "text2image"), ("Cosmos3DistilledModularPipeline", "image2video"), ("Cosmos3DistilledModularPipeline", "text2image"), ("Cosmos3DistilledModularPipeline", "text2video"), @@ -102,6 +103,7 @@ "video_encoder": "video_encoder", "semantic_generator": "semantic_generator", "prompt_upsample": "prompt_transform", + "prompt_enhancer": "prompt_transform", "duration": "duration", "condition_encoder": "condition_encoder", "reference_encoder": "reference_encoder", diff --git a/modiff/modular_conditional_contracts.py b/modiff/modular_conditional_contracts.py index 42a63484..9b455bda 100644 --- a/modiff/modular_conditional_contracts.py +++ b/modiff/modular_conditional_contracts.py @@ -231,6 +231,15 @@ def visit(block: Any, path: tuple[str, ...], order: int) -> None: workflow_id = _name(workflow_id_value, "Workflow id") cases = [] for requirement_case in _workflow_requirement_cases(predicates[workflow_id], workflow_id): + # The reviewed ERNIE-Image-Turbo route deliberately enables the + # upstream optional prompt-enhancer branch. Keep the unpruned + # conditional trace aligned with the exact resolved workflow + # snapshot instead of silently documenting upstream's default-off + # branch as the route that MoDiff executes. + if pipeline_class == "ErnieImageModularPipeline" and workflow_id == "text2image": + requirement_case = { + "presentInputs": [*requirement_case["presentInputs"], "use_pe"], + } trace = _trace_execution(blocks, requirement_case["presentInputs"]) cases.append({**requirement_case, **trace}) workflows.append({"id": workflow_id, "cases": cases}) diff --git a/modiff/modular_contract_only_registry.py b/modiff/modular_contract_only_registry.py index d1f857c8..a1f6fc5f 100644 --- a/modiff/modular_contract_only_registry.py +++ b/modiff/modular_contract_only_registry.py @@ -78,7 +78,6 @@ class ContractOnlyModularPipeline: # contract-only registry: equivalence is a reviewed routing decision, not a # runnable Modular profile or loader claim. CURRENT_PIN_EQUIVALENT_MODULAR_TARGETS = { - "ErnieImageModularPipeline": ("ErnieImagePipeline",), "LTXModularPipeline": ("LTXConditionPipeline",), "LTX2ModularPipeline": ("LTX2ConditionPipeline", "LTX2InContextPipeline"), "Wan22ModularPipeline": ("WanPipeline",), diff --git a/modiff/modular_task_adapters.py b/modiff/modular_task_adapters.py new file mode 100644 index 00000000..1372f7f9 --- /dev/null +++ b/modiff/modular_task_adapters.py @@ -0,0 +1,34 @@ +"""Task projections shared by operation discovery and executable graph inspection.""" + +from modiff.huggingface_node_library import graph_adapter_contracts +from modiff.modular_whole_workflow_contracts import reviewed_whole_workflow_graph_adapter +from modiff.modular_workflow_contracts import PINNED_MODULAR_WORKFLOW_TRUTH + + +def _with_reviewed_reference_tasks(pipeline, adapters): + result = dict(adapters) + if pipeline in {"Flux2ModularPipeline", "Flux2KleinModularPipeline", "FluxKontextModularPipeline"} and "edit_image" in result: + # The official encoder retains individual references and ordered IDs; + # Kontext adds its explicit canvas-composition operation at projection. + result["multi_image_reference_edit"] = result["edit_image"] + return list(result.items()) + + +def modular_task_adapters(pipeline, workflow): + adapters = graph_adapter_contracts(pipeline, workflow["id"], workflow) + whole = reviewed_whole_workflow_graph_adapter(pipeline, workflow["id"]) + if whole: + truth = PINNED_MODULAR_WORKFLOW_TRUTH.get(pipeline) + tasks = ( + [mode for mode, route in truth.modes if (route.upstream_workflow or "default") == workflow["id"]] + if truth + else [] + ) + return _with_reviewed_reference_tasks(pipeline, [(task, whole) for task in tasks or [workflow["taskId"]]]) + result = {} + for adapter in adapters: + if not adapter["actionSequence"] or "full_pipeline" in adapter["actionSequence"]: + continue # A whole standard call does not establish Modular stages. + task = adapter["adapterId"] if adapter["source"] == "mode" else workflow["taskId"] + result.setdefault(task, adapter) + return _with_reviewed_reference_tasks(pipeline, result.items()) diff --git a/modiff/modular_whole_workflow_contracts.py b/modiff/modular_whole_workflow_contracts.py index 31fb9a5a..88e10912 100644 --- a/modiff/modular_whole_workflow_contracts.py +++ b/modiff/modular_whole_workflow_contracts.py @@ -13,6 +13,38 @@ PINNED_WHOLE_WORKFLOW_GRAPH_ADAPTERS: dict[tuple[str, str], dict[str, Any]] = { + ("ErnieImageModularPipeline", "text2image"): { + "schemaVersion": 1, + "adapterId": "official_top_level_blocks", + "requiredInputs": ["prompt"], + "actionSequence": [ + "workflow_ernie_prompt_enhancer", + "workflow_ernie_text_encoder", + "workflow_ernie_image_denoise", + "workflow_ernie_image_decoder", + ], + "stateEdges": [ + { + "producerAction": "workflow_ernie_prompt_enhancer", + "producerOutput": "state_out", + "consumerAction": "workflow_ernie_text_encoder", + "consumerInput": "state_in", + }, + { + "producerAction": "workflow_ernie_text_encoder", + "producerOutput": "state_out", + "consumerAction": "workflow_ernie_image_denoise", + "consumerInput": "state_in", + }, + { + "producerAction": "workflow_ernie_image_denoise", + "producerOutput": "state_out", + "consumerAction": "workflow_ernie_image_decoder", + "consumerInput": "state_in", + }, + ], + "upstreamBlockSequence": ["prompt_enhancer", "text_encoder", "denoise", "decode"], + }, ("AnimaModularPipeline", "text2image"): { "schemaVersion": 1, "adapterId": "official_top_level_blocks", diff --git a/modiff/modular_workflow_contracts.py b/modiff/modular_workflow_contracts.py index d1e37b46..d2e1b749 100644 --- a/modiff/modular_workflow_contracts.py +++ b/modiff/modular_workflow_contracts.py @@ -15,7 +15,7 @@ from dataclasses import dataclass -PINNED_DIFFUSERS_REVISION = "2f7e0154a9db246e95c9ede43edba7db5b130805" +PINNED_DIFFUSERS_REVISION = "fbf49e7f35857f76bc57b177e26f12b03687c668" WAN_T2V_REPOSITORY = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" WAN_T2V_14B_REPOSITORY = "Wan-AI/Wan2.1-T2V-14B-Diffusers" WAN_I2V_REPOSITORY = "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers" @@ -42,12 +42,22 @@ HELIOS_BASE_REPOSITORY = "BestWishYsh/Helios-Base" HELIOS_MID_REPOSITORY = "BestWishYsh/Helios-Mid" HELIOS_DISTILLED_REPOSITORY = "BestWishYsh/Helios-Distilled" +ERNIE_IMAGE_TURBO_REPOSITORY = "baidu/ERNIE-Image-Turbo" # One installed Modular pipeline class can have multiple reviewed weight/config # contracts. Keep the allowed repositories beside the pinned workflow truth; # the loader still requires an immutable catalog revision for the selected # repository, and the downstream action validates the workflow/repository pair. PINNED_MODULAR_REPOSITORY_VARIANTS = { + "FluxModularPipeline": ( + FLUX_DEV_REPOSITORY, "black-forest-labs/FLUX.1-schnell", "black-forest-labs/FLUX.1-Krea-dev", + ), + "StableDiffusionXLModularPipeline": ( + "stabilityai/stable-diffusion-xl-base-1.0", "stabilityai/sdxl-turbo", + ), + "Flux2KleinModularPipeline": ( + FLUX2_KLEIN_REPOSITORY, "black-forest-labs/FLUX.2-klein-9b-kv", + ), # The official checkpoints and the catalog-pinned community BNB conversion # serialize the same QwenImagePipeline component classes. The latter uses # its saved quantization config through the official component loaders. @@ -74,6 +84,7 @@ WAN_I2V_720P_REPOSITORY, WAN_FLF_REPOSITORY, ), + "ErnieImageModularPipeline": (ERNIE_IMAGE_TURBO_REPOSITORY,), } # Repository membership above is useful for immutable artifact review, but it @@ -83,6 +94,7 @@ # the BlockDefinitionV2 graph. A missing entry means the registered artifact # remains the sole choice for that workflow. PINNED_MODULAR_WORKFLOW_REPOSITORY_VARIANTS = { + ("ErnieImageModularPipeline", "text2image"): (ERNIE_IMAGE_TURBO_REPOSITORY,), ("QwenImageModularPipeline", "text2image"): ( QWEN_IMAGE_REPOSITORY, QWEN_IMAGE_2512_REPOSITORY, @@ -97,6 +109,7 @@ PINNED_MODULAR_REPOSITORY_WEIGHT_VARIANTS = { "StableDiffusionXLModularPipeline": { "stabilityai/stable-diffusion-xl-base-1.0": "fp16", + "stabilityai/sdxl-turbo": "fp16", }, } @@ -112,6 +125,20 @@ def reviewed_modular_weight_variant(model_type: str, repository: str) -> str | N # while the installed Modular blocks declare their reviewed base/factory types. # These are exact repository-scoped aliases, not general subclass admission. PINNED_MODULAR_REPOSITORY_COMPONENT_TYPES = { + # Turbo's pinned standard index explicitly omits the optional IP-Adapter + # image encoder and uses the ancestral scheduler for its distilled recipe. + "stabilityai/sdxl-turbo": { + "image_encoder": (None, None), + "feature_extractor": (None, None), + "scheduler": ("diffusers", "EulerAncestralDiscreteScheduler"), + }, + # ERNIE Turbo's immutable standard index serializes the Transformers 5 + # tokenizer implementation for both encoders; the official native blocks + # request AutoTokenizer. Do not admit this alias for other repositories. + ERNIE_IMAGE_TURBO_REPOSITORY: { + "pe_tokenizer": ("transformers", "TokenizersBackend"), + "tokenizer": ("transformers", "TokenizersBackend"), + }, # The reviewed FLUX.2-dev index and processor configs serialize Pixtral; # native Modular encoders request the AutoProcessor factory. The loader # separately checks the immutable repository revision before this alias. @@ -149,6 +176,12 @@ def reviewed_modular_weight_variant(model_type: str, repository: str) -> str | N FLUX_DEV_REPOSITORY: { "tokenizer_2": ("transformers", "T5TokenizerFast"), }, + "black-forest-labs/FLUX.1-schnell": { + "tokenizer_2": ("transformers", "T5TokenizerFast"), + }, + "black-forest-labs/FLUX.1-Krea-dev": { + "tokenizer_2": ("transformers", "T5TokenizerFast"), + }, FLUX_KONTEXT_REPOSITORY: { "tokenizer_2": ("transformers", "T5TokenizerFast"), }, @@ -885,6 +918,54 @@ def _cosmos3_omni_workflow_truth() -> PinnedModularPipelineTruth: PINNED_MODULAR_WORKFLOW_TRUTH: dict[str, PinnedModularPipelineTruth] = { + "ErnieImageModularPipeline": PinnedModularPipelineTruth( + blocks_class="ErnieImageAutoBlocks", + workflows=(_workflow("text2image", "prompt"),), + modes=( + ( + "text_to_image", + ModularModeTruth( + "text2image", + frozenset({"prompt"}), + ( + "workflow_ernie_prompt_enhancer", + "workflow_ernie_text_encoder", + "workflow_ernie_image_denoise", + "workflow_ernie_image_decoder", + ), + ( + StateEdgeTruth( + "workflow_ernie_prompt_enhancer", + "state_out", + "workflow_ernie_text_encoder", + "state_in", + ), + StateEdgeTruth( + "workflow_ernie_text_encoder", + "state_out", + "workflow_ernie_image_denoise", + "state_in", + ), + StateEdgeTruth( + "workflow_ernie_image_denoise", + "state_out", + "workflow_ernie_image_decoder", + "state_in", + ), + ), + ( + "prompt_enhancer.prompt_enhancer", + "text_encoder", + "denoise.input", + "denoise.set_timesteps", + "denoise.prepare_latents", + "denoise.denoise", + "decode", + ), + ), + ), + ), + ), "AnimaModularPipeline": PinnedModularPipelineTruth( blocks_class="AnimaAutoBlocks", workflows=( @@ -1132,7 +1213,7 @@ def _cosmos3_omni_workflow_truth() -> PinnedModularPipelineTruth: ModularStateFlowTruth( "controlnet_union_text2image", frozenset({"control_image", "control_mode", "prompt"}), - _SDXL_CONTROLNET_BLOCK_SEQUENCE, + tuple(block for block in _SDXL_CONTROLNET_BLOCK_SEQUENCE if block != "vae_encoder"), ("text_encoder", "controlnet", "denoise", "decoder"), _SDXL_ROUTE_CONTROL_TO_OUTPUT_EDGES, ), diff --git a/modiff/modular_workflow_discovery.py b/modiff/modular_workflow_discovery.py index ae5d294a..86617c0b 100644 --- a/modiff/modular_workflow_discovery.py +++ b/modiff/modular_workflow_discovery.py @@ -195,8 +195,15 @@ def build_modular_workflow_contract( workflows = [] for workflow_id in available: workflow_id = _name(workflow_id, "Workflow id") - selected_workflow = blocks.get_workflow(workflow_id) if workflow_map is not None else blocks - workflow = selected_workflow.get_execution_blocks() + if pipeline_class == "ErnieImageModularPipeline" and workflow_id == "text2image": + # ERNIE's workflow map deliberately prunes the optional conditional + # prompt enhancer. The reviewed Turbo route enables that package- + # owned branch, so inventory the exact selected top-level graph. + selected_workflow = blocks + workflow = selected_workflow.get_execution_blocks(use_pe=True) + else: + selected_workflow = blocks.get_workflow(workflow_id) if workflow_map is not None else blocks + workflow = selected_workflow.get_execution_blocks() initialized = workflow.init_pipeline() execution_pipeline_class = _name(type(initialized).__name__, "Execution pipeline class") input_fields = [field for field in workflow.inputs if isinstance(getattr(field, "name", None), str)] diff --git a/modiff/node_cache_identity.py b/modiff/node_cache_identity.py new file mode 100644 index 00000000..45300a67 --- /dev/null +++ b/modiff/node_cache_identity.py @@ -0,0 +1,56 @@ +"""Process-local input snapshots; never copy resident model weights.""" +import hashlib + +import numpy as np +import torch +from PIL.Image import Image + + +def input_snapshot(value, depth=0, _memo=None): + """Freeze data mutations separately from NodeBase's authority/equality checks. + + Tensor version counters detect ordinary in-place operations without a device + readback or weight copy. Opaque runtime objects remain identity-bound; their + producers must invalidate descendants when changing adapters/components. + """ + kind = type(value) + if value is None or kind in (str, bytes, int, float, bool): + return kind, value + if depth > 32: + return kind, id(value) + if isinstance(value, torch.Tensor): + try: + version = value._version + except RuntimeError: # Inference tensors do not expose a version counter. + version = None + return kind, id(value), version, value.device, value.dtype, tuple(value.shape) + if isinstance(value, torch.Generator): + return kind, id(value), hashlib.sha256(value.get_state().numpy().tobytes()).digest() + if isinstance(value, np.ndarray): + return kind, value.dtype.str, value.shape, hashlib.sha256(value.tobytes()).digest() + if isinstance(value, Image): + return kind, value.mode, value.size, hashlib.sha256(value.tobytes()).digest() + if kind in (dict, list, tuple, set, frozenset): + memo = {} if _memo is None else _memo + if id(value) in memo: + return memo[id(value)] + # Runtime inputs may contain cycles or repeated container aliases. + # Visit each once; never expand a branching cycle to the depth limit. + memo[id(value)] = (kind, id(value)) + if kind is dict: + snapshot = kind, frozenset((input_snapshot(k, depth + 1, memo), input_snapshot(v, depth + 1, memo)) + for k, v in value.items()) + elif kind in (list, tuple): + snapshot = kind, tuple(input_snapshot(item, depth + 1, memo) for item in value) + else: + snapshot = kind, frozenset(input_snapshot(item, depth + 1, memo) for item in value) + memo[id(value)] = snapshot + return snapshot + return kind, id(value) + + +def implementation_identity(node): + """Identify executable code actually loaded, including local reloads/patches.""" + callback = getattr(node, node.CALLBACK) + callback = getattr(callback, '__func__', callback) + return type(node), getattr(callback, '__code__', callback) diff --git a/modiff/operation_catalog.py b/modiff/operation_catalog.py new file mode 100644 index 00000000..83bedb68 --- /dev/null +++ b/modiff/operation_catalog.py @@ -0,0 +1,511 @@ +"""Canonical authoring operations over existing executors and dependency gates. + +A resolved binding is a node schema, never a graph recipe or execution receipt. +Runtime validation still checks the connected pipeline, state and resource owner. +""" + +from collections import defaultdict +from copy import deepcopy +import json + +from modiff.operation_contracts import _identifier, operation_owns_model, with_operation_semantics +from modiff.operation_inventory import load_operation_inventory + +_STANDARD_DEFAULT_MODULES = { + "modules.DiffusersImage", "modules.DiffusersAudio", "modules.DiffusersVideo", "modules.DiffusersThreeD", +} + + +def seed_standard_operation_defaults(node, profile): + """Initialize a new ordinary operation from its reviewed model profile. + + This is authoring only: never call it on saved nodes or dynamic field updates. + Shared pipeline classes (for example Flux dev/schnell/Krea) must use the + selected profile, not whichever repository is the adapter's default. + """ + from modiff.authoring_examples import seed_operation_example + + seed_operation_example(node, profile.default_repo) + if node["module"] not in _STANDARD_DEFAULT_MODULES | {"modules.ModularDiffusers", "modules.HuggingFaceTransformers"} or profile.loader_module != node["module"]: + return + from modiff.studio_execution_specs import studio_capability_definition + + capability = studio_capability_definition(profile.model_type) + if node["action"] == profile.loader_action: + defaults = {"dtype": capability.get("defaultDtype")} + elif node["module"] == "modules.DiffusersAudio": + # The owner overlay identifies the controls used by this task. Seed only + # visible fields below; the ordinary generator also holds inactive + # controls for other adapters, which must keep their historical defaults. + defaults = { + "audio_duration": capability.get("recommendedDuration"), + "sample_rate": capability.get("recommendedSampleRate"), + "num_inference_steps": capability.get("recommendedSteps"), + "stable_audio_steps": capability.get("recommendedSteps"), + "guidance_scale": capability.get("recommendedGuidance"), + "stable_audio_guidance": capability.get("recommendedGuidance"), + } + else: + size = capability.get("defaultSize", {}) + defaults = { + "num_inference_steps": capability.get("recommendedSteps"), + "guidance_scale": capability.get("recommendedGuidance"), + "width": size.get("width"), + "height": size.get("height"), + } + if node["module"] == "modules.DiffusersVideo": + defaults.update( + num_frames=capability.get("recommendedFrames"), + frame_rate=capability.get("recommendedFps"), + max_sequence_length=capability.get("recommendedMaxSequenceLength"), + ) + elif node["module"] == "modules.DiffusersThreeD": + defaults["frame_size"] = size.get("width") + if node["module"] == "modules.DiffusersImage" and node["action"] != "LoadPipeline": + from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS + + adapter = IMAGE_PIPELINE_ADAPTERS[profile.pipeline_class] + if adapter.secondary_guidance_parameter == "guidance_scale": + # Historical fields route primary guidance to true CFG. The + # reviewed model recommendation is distilled guidance; opt new + # drafts into its separate control without reinterpreting saved + # values or changing the adapter's legacy invocation contract. + defaults.update( + guidance_scale=1.0, + guidance_scale_2=capability.get("recommendedGuidance", adapter.secondary_guidance_default), + use_guidance_scale_2=True, + ) + for key, value in defaults.items(): + field = node["params"].get(key) + if value is None or field is None or field.get("hidden") or field.get("display") == "output": + continue + node["params"][key]["value"] = deepcopy(value) + node["values"][key] = deepcopy(value) + + +def _standard_sources(): + from modules.DiffusersImage.main import get_image_operation_contracts + from modules.DiffusersVideo.main import get_video_operation_contracts + from modules.DiffusersAudio.main import get_audio_operation_contracts + from modules.DiffusersThreeD.main import get_three_d_operation_contracts + from modules.HuggingFaceTransformers.main import get_depth_operation_contracts, get_image_text_operation_contracts + from modiff.integrated_operation_contracts import get_integrated_operation_contracts + + return ( + get_image_operation_contracts, + get_video_operation_contracts, + get_audio_operation_contracts, + get_three_d_operation_contracts, + get_depth_operation_contracts, + get_image_text_operation_contracts, + get_integrated_operation_contracts, + ) + + +def _standard_schema(contract, modules): + """Reuse the same owner overlays/signals as dynamic field callbacks.""" + module, action = contract["nodeKey"].rsplit(".", 1) + pipeline = contract.get("binding", {}).get("pipelineClass", contract["pipelineClass"]) + task = contract["task"] + fields, values, signal = {}, deepcopy(contract.get("binding", {}).get("values", {})), None + if contract["decomposition"] == "integrated": + from modiff.integrated_operation_contracts import integrated_operation_values, integrated_operation_fields + + values = integrated_operation_values(contract) + fields = integrated_operation_fields(contract, modules) + elif module == "modules.DiffusersImage": + from modules.DiffusersImage.main import ( + IMAGE_PIPELINE_ADAPTERS, + image_pipeline_contract, + image_loader_field_params, + image_operation_loader_defaults, + image_action_field_params, + _image_output_options, + ) + + adapter = IMAGE_PIPELINE_ADAPTERS[pipeline] + signal = image_pipeline_contract(adapter, task) + fields = ( + image_loader_field_params(adapter) + if action == "LoadPipeline" + else image_action_field_params(signal, action) + ) + if action != "LoadPipeline" and "output_type" not in fields: + fields["output_type"] = {"options": _image_output_options(adapter, action)} + values = {"pipeline_class": pipeline, "mode": task} if action == "LoadPipeline" else {"image_contract": signal} + if action == "LoadPipeline": + values.update(image_operation_loader_defaults(adapter)) + elif module == "modules.DiffusersVideo": + from modules.DiffusersVideo.main import VIDEO_PIPELINE_ADAPTERS, get_video_mode_field_contract, _adapter_signal + + adapter = VIDEO_PIPELINE_ADAPTERS[pipeline] + signal = _adapter_signal(adapter) + if action == "LoadPipeline": + values = {"pipeline_class": pipeline} + else: + fields = get_video_mode_field_contract(adapter, task).field_param_overlay() + values = {"video_contract": _adapter_signal(adapter), "mode": task} + elif module == "modules.DiffusersAudio": + from modules.DiffusersAudio.main import AUDIO_PIPELINE_ADAPTERS + + adapter = AUDIO_PIPELINE_ADAPTERS[pipeline] + mode = adapter.contract_for_mode(task) + signal = mode.signal_value(pipeline, adapter.default_repo) + if action == "LoadPipeline": + values = {"pipeline_class": pipeline, "mode": task} + else: + fields = mode.field_param_overlay() + values = {"audio_contract": mode.signal_value(pipeline, adapter.default_repo), "task_type": mode.task_type} + elif module == "modules.DiffusersThreeD": + from modules.DiffusersThreeD.main import THREE_D_PIPELINE_ADAPTERS + + adapter = THREE_D_PIPELINE_ADAPTERS[pipeline] + signal = adapter.signal_value() + if action == "LoadPipeline": + values = {"pipeline_class": pipeline, "mode": task} + else: + fields = adapter.signal_value()["fieldParams"] + values = {"three_d_contract": adapter.signal_value()} + elif module == "modules.HuggingFaceTransformers": + if action in {"LoadDepthEstimationModel", "PredictDepth"}: + values = {"pipeline_class": pipeline} if action == "LoadDepthEstimationModel" else {} + else: + from modules.HuggingFaceTransformers.main import image_text_operation_schema + + fields, values = image_text_operation_schema(pipeline, task, action) + params = deepcopy(modules[module][action]["params"]) + if action == "LoadPipeline": + from modiff.model_artifact_catalog import catalog_revision + + if "execution_recipe" in params: + # These loaders use their own resource fields when no override is + # connected. New stage drafts must not propose a spurious repair. + params["execution_recipe"]["required"] = False + values.update( + model_id={"source": "hub", "value": adapter.default_repo}, + revision=getattr(adapter, "revision", None) or catalog_revision(adapter.default_repo) or "", + ) + if "execution_profile_id" in params: + from modiff.diffusers_profiles import ( + execution_profiles_for_execution, + resolve_execution_profiles_for_loader, + ) + + _, reason = resolve_execution_profiles_for_loader(module, action, values) + if reason == "loader_profile_ambiguous": + # Direct and reviewed equivalent identities can share a loader. + # Bind only the unique public route the user actually selected. + # Keep this runtime value out of identifier-only discovery hints. + selected = [ + profile for profile in execution_profiles_for_execution(contract["pipelineClass"], task) + if profile.public and profile.pipeline_class == pipeline + and profile.loader_module == module and profile.loader_action == action + ] + if len(selected) == 1: + values["execution_profile_id"] = selected[0].id + params["pipeline"]["signal"]["value"] = deepcopy(signal) + if "audio_contract" in params: + values["audio_contract"] = signal + if "three_d_contract" in params: + values["three_d_contract"] = signal + if module == "modules.DiffusersImage": + values["conditioning_kind"] = adapter.conditioning_kind or "none" + if adapter.default_conditioning_repo: + values["conditioning_model_id"] = {"source": "hub", "value": adapter.default_conditioning_repo} + values["conditioning_revision"] = catalog_revision(adapter.default_conditioning_repo) or "" + else: + # A new unconditioned operation must not inherit the generic + # loader's hidden ControlNet model and demand its installation. + values["conditioning_model_id"] = "" + values["conditioning_revision"] = "" + if "mode" in params: + params["mode"]["options"] = [adapter.mode] if module == "modules.DiffusersThreeD" else list(adapter.modes) + # Selector defaults must agree with the resolved operation before any field + # callback fires. This also prevents connecting a stale default signal. + + for name, overlay in fields.items(): + if name in params: + params[name].update(deepcopy(overlay)) + return params, {key: value for key, value in values.items() if key in params} + + +def build_operation_catalog(modules, profiles, *, catalog_resolver=None): + from modules.ModularDiffusers.modular_utils import get_modular_operation_contracts + from modules.ModularDiffusers.operation_contracts import get_modular_task_operation_contracts + from modiff.optional_runtime_execution import ( + loader_optional_runtime_requirement, + optional_runtime_requirement_blocks_execution, + ) + + inventory = load_operation_inventory() + contracts = [with_operation_semantics(c) for c in get_modular_operation_contracts(modules)] + standard = [with_operation_semantics(c) for source in _standard_sources() for c in source(modules)] + for contract in standard: + # Only lightweight selector values belong in discovery. Full dynamic + # signals and field overlays are resolved on demand from their owner. + module, action = contract["nodeKey"].rsplit(".", 1) + params = modules[module][action]["params"] + contract["binding"]["values"] = { + key: value + for key, value in {"pipeline_class": contract["pipelineClass"], "mode": contract["task"]}.items() + if key in params + } + contracts.extend(standard) + contracts.extend(get_modular_task_operation_contracts(modules)) + for contract in contracts: + contract["binding"]["pipelineClass"] = contract["pipelineClass"] + + # Existing reviewed equivalence decisions select whole standard calls. + # Never expose their upstream hierarchy as an executable stage sequence. + identities = {(c["pipelineClass"], c["task"], c["operationId"]) for c in contracts} + stage_tasks = {(c["pipelineClass"], c["task"]) for c in contracts if c["decomposition"] in {"block", "bundle"}} + for pipeline in inventory["pipelines"]: + if not pipeline["equivalentTo"]: + continue + # Exact public profiles retain task-scoped equivalence and reviewed local + # aliases (for example Wan22Pipeline). A class-level review decision alone + # must not promote extra modes of the destination implementation. + routes = [ + p + for p in profiles + if p["model_type"] == pipeline["pipelineClass"] and p["pipeline_class"] != pipeline["pipelineClass"] + ] + for candidate in standard: + if (pipeline["pipelineClass"], candidate["task"]) in stage_tasks: + continue # Keep one coherent loader/stage path for this task. + identity = (pipeline["pipelineClass"], candidate["task"], candidate["operationId"]) + if identity in identities or not any( + p["pipeline_class"] == candidate["pipelineClass"] + and candidate["task"] in p["modes"] + and candidate["nodeKey"].startswith(p["loader_module"] + ".") + for p in routes + ): + continue + contract = deepcopy(candidate) + contract["pipelineClass"] = pipeline["pipelineClass"] + contracts.append(contract) + identities.add(identity) + contracts.sort(key=lambda c: (c["pipelineClass"], c["task"] or "", c["operationId"])) + if len(contracts) != len(identities): + raise ValueError("Operation identities must be unique.") + + by_pipeline = defaultdict(lambda: defaultdict(list)) + for contract in contracts: + if contract["task"] is not None: + by_pipeline[contract["pipelineClass"]][contract["task"]].append(contract) + reviewed = {p["pipelineClass"]: p for p in inventory["pipelines"]} + support = [] + for pipeline in sorted(set(reviewed) | {c["pipelineClass"] for c in contracts}): + entry = deepcopy( + reviewed.get( + pipeline, + { + "pipelineClass": pipeline, + "coverage": "local-adapter", + "reason": "Existing MoDiff adapter alias.", + "equivalentTo": [], + "upstreamTasks": [], + }, + ) + ) + tasks = [] + task_records = by_pipeline[pipeline] + for task in sorted(set(task_records) | {t["task"] for t in entry["upstreamTasks"]}): + records = task_records.get(task, []) + loaders = [c for c in records if operation_owns_model(c)] + selected_profiles = [ + p + for p in profiles + if any( + p["pipeline_class"] == c["binding"]["pipelineClass"] + and c["nodeKey"] == p["loader_module"] + "." + p["loader_action"] + # Modular workflow owners establish stage task support. Studio's + # curated profile modes are not the ordinary graph allowlist. + and (task in p["modes"] or ( + p["execution_path"] == "modular-diffusers" and p["model_type"] == p["pipeline_class"] + )) + for c in loaders + ) + ] + complete = bool(loaders and any(c["decomposition"] != "loader" for c in records)) + runtime_requirements = [p["optionalRuntimeRequirement"] for p in selected_profiles] + for contract in records: + if operation_owns_model(contract): + continue # Its exact profiles are already accounted for above. + module, action = contract["nodeKey"].rsplit(".", 1) + requirement = loader_optional_runtime_requirement( + module, action, contract["binding"]["values"], catalog_resolver=catalog_resolver + ) + if requirement["requiredNow"]: + runtime_requirements.append(requirement) + runtime_requirements = list({json.dumps(r, sort_keys=True): r for r in runtime_requirements}.values()) + deps = ( + "unknown" + if not selected_profiles + else ( + "blocked" + if any(optional_runtime_requirement_blocks_execution(r) for r in runtime_requirements) + else "ready" + ) + ) + tasks.append( + { + "task": task, + "execution": "adapter" + if selected_profiles and complete + else ("declared" if records else "unavailable"), + "decomposition": "stages" + if any(c["decomposition"] in {"block", "bundle"} for c in records) + else ("pipeline" if any(c["decomposition"] in {"pipeline", "integrated"} for c in records) else "none"), + "operationIds": sorted(c["operationId"] for c in records), + "executionProfileIds": sorted(p["id"] for p in selected_profiles), + "dependencies": deps, + "runtimeRequirements": deepcopy(runtime_requirements), + } + ) + entry["tasks"] = tasks + support.append(entry) + return contracts, support + + +def resolve_operation(modules, contracts, selection): + """Resolve one canonical operation into an existing ordinary node definition.""" + if not isinstance(selection, dict) or set(selection) != {"pipelineClass", "task", "operationId"}: + raise ValueError("Select an exact pipeline, task and operation.") + _identifier(selection["pipelineClass"]) + if selection["task"] is not None: + _identifier(selection["task"]) + operation = selection["operationId"] + if not isinstance(operation, str) or len(operation.split(".")) != 2: + raise ValueError("Invalid operation identity.") + for part in operation.split("."): + _identifier(part) + matches = [c for c in contracts if all(c[key] == value for key, value in selection.items())] + if len(matches) != 1: + raise ValueError("The selected pipeline/task has no unique operation binding.") + contract = matches[0] + module, action = contract["nodeKey"].rsplit(".", 1) + definition = deepcopy(modules[module][action]) + definition["label"] = contract["operationId"].split(".")[1].replace("_", " ").title() + values = dict(contract["binding"]["values"]) + if module == "modules.ModularDiffusers": + if contract["nodeType"] == "loader": + from modules.ModularDiffusers.modular_utils import get_all_model_types, get_model_type_metadata + from modules.ModularDiffusers.loaders import ( + ModelsLoader, + MODELS_LOADER_IDENTITY_OUTPUTS, + CUSTOM_PIPELINE_IDENTITY_FIELD, + ) + from modiff.model_artifact_catalog import require_catalog_revision + + metadata = get_model_type_metadata(contract["binding"]["pipelineClass"]) + definition["params"]["model_type"]["options"] = get_all_model_types(include_contract_only=True) + repository = metadata["default_repo"] + values["dtype"] = metadata["default_dtype"] + if repository: + values["repo_id"] = {"source": "hub", "value": repository} + values["revision"] = require_catalog_revision( + repository, model_type=contract["binding"]["pipelineClass"] + ) + variants = ModelsLoader._reviewed_workflow_variants( + model_type=contract["binding"]["pipelineClass"], + workflow_id=contract["workflowId"], + default_repository=repository, + ) + definition["params"]["reviewed_variant"].update(options=list(variants), hidden=len(variants) < 2) + values["reviewed_variant"] = repository if repository in variants else "" + definition["params"]["repo_id"]["fieldOptions"]["filter"] = { + "hub": {"className": [contract["binding"]["pipelineClass"]]}, + } + signal_value = ( + "" if metadata.get("execution_status") == "contract_only" else contract["binding"]["pipelineClass"] + ) + for name in MODELS_LOADER_IDENTITY_OUTPUTS: + definition["params"][name]["signal"] = { + "direction": "output", + "origin": CUSTOM_PIPELINE_IDENTITY_FIELD, + "value": signal_value, + } + for port in contract["ports"]: + if "component" in port["roles"]: + definition["params"][port["name"]]["hidden"] = port["hidden"] + elif action in {"Guider", "Layers"}: + from modules.ModularDiffusers.modular_utils import get_model_type_metadata + + metadata = get_model_type_metadata(contract["binding"]["pipelineClass"]) + if action == "Guider": + definition["params"]["guider"]["options"] = list(metadata["guider_options"]) + else: + definition["params"]["blocks_select"]["options"] = list(metadata["layer_block_options"]) + values["blocks_select"] = [] + elif not action.startswith("Workflow"): + from modules.ModularDiffusers.modular_utils import get_model_type_metadata + + metadata = get_model_type_metadata(contract["binding"]["pipelineClass"]) + config = metadata["node_params"][contract["nodeType"]] + for name, overlay in config["params"].items(): + definition["params"].setdefault(name, {}).update(deepcopy(overlay)) + if action == "Controlnet": + # A standalone ControlNet has no ModelsLoader payload from + # which to recover this identity after instance recreation. + # Persist the same explicit choice normally set by its reverse + # canvas signal. Component ownership is still checked at run. + values["model_type"] = contract["binding"]["pipelineClass"] + else: + definition["params"], values = _standard_schema(contract, modules) + for key, value in values.items(): + if key not in definition["params"]: + raise ValueError("Operation binding targets an undeclared field.") + definition["params"][key]["value"] = deepcopy(value) + result = {**definition, "module": module, "action": action, "values": values, "operation": deepcopy(contract)} + if module in _STANDARD_DEFAULT_MODULES: + from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS + from modules.DiffusersAudio.main import AUDIO_PIPELINE_ADAPTERS + from modules.DiffusersVideo.main import VIDEO_PIPELINE_ADAPTERS + from modules.DiffusersThreeD.main import THREE_D_PIPELINE_ADAPTERS + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES + + adapters = { + "modules.DiffusersImage": IMAGE_PIPELINE_ADAPTERS, + "modules.DiffusersAudio": AUDIO_PIPELINE_ADAPTERS, + "modules.DiffusersVideo": VIDEO_PIPELINE_ADAPTERS, + "modules.DiffusersThreeD": THREE_D_PIPELINE_ADAPTERS, + }[module] + adapter = adapters[contract["binding"]["pipelineClass"]] + profiles = [ + p for p in DIFFUSERS_EXECUTION_PROFILES.values() + if p.public and p.loader_module == module and p.pipeline_class == adapter.pipeline_class + and p.default_repo == adapter.default_repo and contract["task"] in p.modes + ] + if len(profiles) == 1: + seed_standard_operation_defaults(result, profiles[0]) + from modiff.authoring_examples import seed_operation_example + + seed_operation_example(result) + return result + + +def operation_port_compatibility(output, input_): + """Advisory only; model-owned objects always require runtime validation.""" + from modiff.block_definition_v2 import _block_value_types_are_compatible_v2 + + if output["direction"] != "output" or input_["direction"] != "input": + return "incompatible" + if not _block_value_types_are_compatible_v2(output["types"], input_["types"]): + return "incompatible" + left, right = output["semantics"], input_["semantics"] + if left["scope"] != right["scope"] or left["kind"] != right["kind"]: + return "incompatible" + if right["state"] is not None and left["state"] != right["state"]: + return "incompatible" + if left["kind"] == "component" and left["members"] and right["members"]: + available = {m["name"]: m["type"] for m in left["members"]} + if any( + m["name"] not in available + or (m["type"] != available[m["name"]] and "opaque" not in {m["type"], available[m["name"]]}) + for m in right["members"] + ): + return "incompatible" + if left["owner"] == "same_loader" or right["owner"] == "same_loader" or left["kind"] == "opaque": + return "runtime_validation" + return "compatible" diff --git a/modiff/operation_contracts.py b/modiff/operation_contracts.py new file mode 100644 index 00000000..2ca1769b --- /dev/null +++ b/modiff/operation_contracts.py @@ -0,0 +1,251 @@ +"""Read-only operation identities projected from existing node/adapter configs. + +These declarations are neither graph recipes nor execution/connection authority. +In particular, equal tensor types or semantic names do not establish compatible +conditioning across pipelines. Keep the enclosing pipelineClass with each port. +This module deliberately imports no model libraries. +""" + +from collections.abc import Mapping +from dataclasses import dataclass +import re + +OPERATION_CONTRACT_SCHEMA_VERSION = 3 + + +@dataclass(frozen=True) +class ModularStageOperation: + id: str + action: str + label: str + + +# Names of existing actions, not a second implementation or model registry. +MODULAR_STAGE_OPERATIONS = { + "text_encoder": ModularStageOperation("diffusion.encode_prompt", "EncodePrompt", "Encode Prompt"), + "image_encoder": ModularStageOperation("diffusion.image_embeddings", "ImageEmbeddings", "Image Embeddings"), + "vae_encoder": ModularStageOperation("diffusion.encode_image", "ImageEncode", "Encode Image"), + "denoise": ModularStageOperation("diffusion.denoise", "Denoise", "Denoise"), + "decoder": ModularStageOperation("diffusion.decode_latents", "DecodeLatents", "Decode Latents"), + "controlnet": ModularStageOperation("diffusion.controlnet", "Controlnet", "ControlNet"), + "ip_adapter": ModularStageOperation("diffusion.ip_adapter", "IPAdapter", "IP-Adapter Embeddings"), +} + +# Upstream stage names, shared across families. Additional media processing +# stages retain their own identities instead of being forced into denoising. +WORKFLOW_STAGE_OPERATIONS = { + **{key: value.id for key, value in MODULAR_STAGE_OPERATIONS.items()}, + "decode": "diffusion.decode_latents", + "video_encoder": "diffusion.encode_video", + "semantic_generator": "diffusion.generate_semantics", + "prompt_upsample": "diffusion.rewrite_prompt", + "prompt_enhancer": "diffusion.rewrite_prompt", + "before_encode": "diffusion.prepare_media", + "after_decode": "diffusion.postprocess_media", + "duration": "diffusion.prepare_duration", + "condition_encoder": "diffusion.encode_condition", + "reference_encoder": "diffusion.encode_reference", +} +_IDENTIFIER = re.compile(r"[A-Za-z_][A-Za-z0-9_]{0,127}\Z") +_RESERVED = frozenset({"__proto__", "prototype", "constructor"}) + + +def operation_owns_model(contract): + """Model ownership does not require a separate loader node.""" + return contract["decomposition"] in {"loader", "integrated"} + + +def _identifier(value): + if not isinstance(value, str) or not _IDENTIFIER.fullmatch(value) or value in _RESERVED: + raise ValueError("Invalid operation-contract identifier.") + return value + + +def _port_types(raw_types): + types = [raw_types] if isinstance(raw_types, str) else raw_types + if not isinstance(types, (list, tuple)) or not 1 <= len(types) <= 16: + raise ValueError("Invalid operation-contract port types.") + return sorted({_identifier(value) for value in types}) + + +def with_operation_semantics(contract, *, workflow_id=None, values=None): + """Add scoped semantic and binding metadata without claiming type equivalence.""" + from copy import deepcopy + + result = deepcopy(contract) + result["workflowId"] = workflow_id + result["binding"] = {"values": dict(values or {})} + for port in result["ports"]: + types = set(port["types"]) + name = port["semanticName"] + kind = "value" + if "pipeline" in port["roles"]: + kind = "pipeline" + elif types & {"controlnet_bundle", "custom_controlnet", "ip_adapter_bundle", "custom_ip_adapter"}: + # Existing bundles carry conditioning alongside model references. + # Both ends use that same bundle contract, not a whole component set. + kind = "conditioning" + elif "component" in port["roles"]: + kind = "component" + elif types & {"modular_workflow_state", "modular_route_state"}: + kind = "state" + elif types & {"embeddings", "image_embeddings", "conditioning", "controlnet_bundle", "ip_adapter_bundle"}: + kind = "conditioning" + elif "latent" in name and types & {"tensor", "latents"}: + kind = "latents" + elif types & {"tensor", "object", "dict", "list", "custom_lora", "quant_config"}: + kind = "opaque" + elif types & {"image", "video", "audio", "prediction_map"}: + kind = "media" + elif not types <= {"string", "str", "text", "int", "float", "number", "bool", "boolean", "seed"}: + kind = "opaque" + scoped = kind in {"component", "conditioning", "latents", "state", "pipeline", "opaque"} + port["semantics"] = { + "kind": kind, + "scope": (f"{result['pipelineClass']}:{workflow_id}" if kind == "state" and workflow_id + else result["pipelineClass"]) if scoped else None, + "state": None, + "owner": "same_loader" if kind in {"component", "conditioning", "latents", "state", "pipeline"} else "none", + "members": [], + } + return result + + +def build_pipeline_operation_contract( + modules: Mapping, *, pipeline_class: str, task: str, operation_id: str, + node_key: str, field_overrides: Mapping | None = None, loader: bool = False, +) -> dict | None: + """Describe an existing whole-pipeline action using its owner's field overlay. + + Hidden fields remain declared: visibility is presentation, not readiness or + permission. Options, defaults, conditional requirements and tensor semantics + still belong to the existing dynamic schema and execution preflight. + """ + _identifier(pipeline_class) + _identifier(task) + if len(operation_id.split(".")) != 2 or len(node_key.split(".")) != 3 or not node_key.startswith("modules."): + raise ValueError("Invalid operation-contract action.") + for part in (*operation_id.split("."), *node_key.split(".")): + _identifier(part) + module, action = node_key.rsplit(".", 1) + declaration = modules.get(module, {}).get(action) + if declaration is None: + return None + ports = [] + for name, base in declaration["params"].items(): + field = {**base, **(field_overrides or {}).get(name, {})} + if "type" not in field or field.get("display") == "button": + continue + direction = "output" if field.get("display") == "output" else "input" + ports.append({ + "name": _identifier(name), + "semanticName": name, + "direction": direction, + "roles": ["pipeline" if name == "pipeline" else "value"], + "types": _port_types(field["type"]), + "required": direction == "input" and field.get("required") is True, + "hidden": field.get("hidden") is True, + }) + if len(ports) > 128: + raise ValueError("Too many operation-contract ports.") + decomposition = "loader" if loader else "pipeline" + return { + "pipelineClass": pipeline_class, "task": task, "operationId": operation_id, + "nodeKey": node_key, "nodeType": decomposition, "blockName": None, + "decomposition": decomposition, "support": "declared", "ports": ports, + } + + +def build_modular_operation_contracts(configs: Mapping, modules: Mapping) -> list[dict]: + """Project declarations without constructing pipelines or resolving blocks. + + ``configs`` comes from ModiffPipelineRegistry. Deserialized custom previews + have no reviewed node_specs and are handled by their existing trust boundary. + A missing stage/action is omitted, never inferred from a pipeline class name. + """ + if len(configs) > 512: + raise ValueError("Too many operation-contract pipelines.") + actions = modules.get("modules.ModularDiffusers", {}) + result = [] + for pipeline_class, config in sorted(configs.items()): + _identifier(pipeline_class) + if config.node_specs is None: + continue + node_params = config.node_params + for node_type, operation in MODULAR_STAGE_OPERATIONS.items(): + spec = config.node_specs.get(node_type) + if spec is None or operation.action not in actions: + continue + formatted = node_params[node_type] + ports = [] + ports_by_key = {} + for group, names_key, direction, kind, required_key in ( + ("inputs", "input_names", "input", "value", "required_inputs"), + ("model_inputs", "model_input_names", "input", "component", "required_model_inputs"), + ("outputs", "output_names", "output", "value", None), + ): + params = spec.get(group, []) + names = formatted[names_key] + required = spec.get(required_key, []) if required_key else [] + if len(params) != len(names) or not set(required).issubset({p.name for p in params}): + raise ValueError("Invalid operation-contract port declaration.") + for param, name in zip(params, names, strict=True): + _identifier(name) + _identifier(param.name) + types = _port_types(param.to_dict().get("type")) + key = (direction, name) + if key in ports_by_key: + existing = ports_by_key[key] + # Some conditioning bundles supply both pipeline values + # and components through one existing socket. + if ( + kind in existing["roles"] + or existing["semanticName"] != param.name + or existing["types"] != types + ): + raise ValueError("Conflicting operation-contract port declaration.") + existing["roles"].append(kind) + existing["required"] = existing["required"] or param.name in required + continue + port = { + "name": name, + "semanticName": param.name, + "direction": direction, + "roles": [kind], + "types": types, + "required": param.name in required, + "hidden": param.to_dict().get("hidden") is True, + } + ports_by_key[key] = port + ports.append(port) + # EncodePrompt's wrapper accepts a connected string in preference + # to its inline prompt. This is an explicit execution alias, not a + # display-label heuristic, and must participate in migration and + # wiring contracts even though it is not an upstream block input. + if operation.action == "EncodePrompt": + connected = actions[operation.action]["params"].get("prompt_input") + if connected and any(p["semanticName"] == "prompt" for p in ports): + ports.append({ + "name": "prompt_input", "semanticName": "prompt", "direction": "input", + "roles": ["value"], "types": _port_types(connected["type"]), + "required": False, "hidden": connected.get("hidden") is True, + }) + if len(ports) > 128: + raise ValueError("Too many operation-contract ports.") + block_name = spec.get("block_name") or None + if block_name is not None: + _identifier(block_name) + result.append( + { + "pipelineClass": pipeline_class, + "task": None, + "operationId": operation.id, + "nodeKey": f"modules.ModularDiffusers.{operation.action}", + "nodeType": node_type, + "blockName": block_name, + "decomposition": "block" if block_name else "bundle", + "support": "declared", + "ports": ports, + } + ) + return result diff --git a/modiff/operation_inventory.py b/modiff/operation_inventory.py new file mode 100644 index 00000000..b4890f9d --- /dev/null +++ b/modiff/operation_inventory.py @@ -0,0 +1,187 @@ +"""Pinned Diffusers export/task inventory; discovery reads the checked artifact. + +AutoPipeline mappings describe dispatch tasks, not interchangeable model inputs. +Exports without named Auto/Modular tasks retain their direct pipeline-call surface +and the existing explicit review decision. No model imports are needed to rebuild. +""" + +import ast +import hashlib +import json +from pathlib import Path + +from modiff.operation_contracts import _identifier +from modiff.modular_workflow_contracts import PINNED_DIFFUSERS_REVISION +from modiff.modular_workflow_discovery import load_reviewed_modular_workflow_snapshot + +OPERATION_INVENTORY_PATH = Path(__file__).resolve().parents[1] / "data" / "diffusers-operation-inventory.v1.json" +_AUTO_TASKS = { + "TEXT2IMAGE": "text_to_image", + "IMAGE2IMAGE": "image_to_image", + "INPAINT": "inpaint", + "TEXT2VIDEO": "text_to_video", + "IMAGE2VIDEO": "image_to_video", + "VIDEO2VIDEO": "video_to_video", + "CONDITION2VIDEO": "condition_to_video", + "TEXT2AUDIO": "text_to_audio", +} + + +def _hash(value): + return ( + "sha256:" + + hashlib.sha256( + json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode() + ).hexdigest() + ) + + +def _auto_tasks(source): + """Read all declared entries, including conditional dependency registrations.""" + result = {} + names = {} + tree = ast.parse(source) + for node in ast.walk(tree): + if not isinstance(node, ast.Assign) or len(node.targets) != 1: + continue + target = node.targets[0] + if isinstance(target, ast.Name) and target.id.endswith("_PIPELINES_MAPPING"): + name = target.id + task_key = ( + name.removeprefix("_") + .removeprefix("AUTO_") + .removesuffix("_PIPELINES_MAPPING") + .removesuffix("_DECODER") + ) + if task_key not in _AUTO_TASKS: + raise ValueError(f"Unreviewed AutoPipeline task mapping: {name}") + names[name] = _AUTO_TASKS[task_key] + value = node.value + if ( + not isinstance(value, ast.Call) + or not isinstance(value.func, ast.Name) + or value.func.id != "OrderedDict" + ): + raise ValueError("Unsupported AutoPipeline mapping declaration.") + for entry in value.args[0].elts: + pipeline = entry.elts[1] + if not isinstance(pipeline, ast.Name): + raise ValueError("Unsupported AutoPipeline class reference.") + result.setdefault(pipeline.id, set()).add(names[name]) + for node in ast.walk(tree): + if isinstance(node, ast.Assign) and len(node.targets) == 1: + target = node.targets[0] + if isinstance(target, ast.Subscript) and isinstance(target.value, ast.Name) and target.value.id in names: + if not isinstance(node.value, ast.Name): + raise ValueError("Unsupported conditional AutoPipeline class reference.") + result.setdefault(node.value.id, set()).add(names[target.value.id]) + return result + + +def build_operation_inventory(root: Path, *, diffusers_source=None): + # Source auditing stays out of ordinary startup/discovery. + from modiff.upstream_coverage import ( + _pipeline_coverage, + _verify_diffusers_source_revision, + installed_diffusers_source, + ) + + source = diffusers_source or installed_diffusers_source() + _verify_diffusers_source_revision(source, PINNED_DIFFUSERS_REVISION) + version, coverage = _pipeline_coverage(root, source) + auto_path = source / "pipelines" / "auto_pipeline.py" + auto = _auto_tasks(auto_path.read_text()) + snapshot_path = root / "data" / "modular-workflow-contracts.json" + snapshot = load_reviewed_modular_workflow_snapshot(snapshot_path) + modular = {c["pipelineClass"]: c for c in snapshot["contracts"]} + symbols = {p["name"] for p in coverage} + if set(auto) - symbols or set(modular) - symbols: + raise ValueError("Task inventory refers to an unexported pipeline.") + pipelines = [] + for item in coverage: + name = item["name"] + tasks = [{"task": task, "source": "auto", "workflowId": None} for task in sorted(auto.get(name, ()))] + tasks.extend( + {"task": w["taskId"], "source": "modular", "workflowId": w["id"]} + for w in modular.get(name, {}).get("workflows", []) + ) + if not tasks: + tasks = [{"task": "pipeline_call", "source": "export", "workflowId": None}] + pipelines.append( + { + "pipelineClass": name, + "coverage": item["status"], + "reason": item["reason"], + "equivalentTo": item["equivalentTo"], + "upstreamTasks": tasks, + } + ) + result = { + "schemaVersion": 1, + "diffusersRevision": PINNED_DIFFUSERS_REVISION, + "diffusersVersion": version, + "sources": { + name: "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest() + for name, path in ( + ("exports", source / "__init__.py"), + ("autoTasks", auto_path), + ("modularTasks", snapshot_path), + ) + }, + "pipelines": pipelines, + } + return {**result, "contentHash": _hash(result)} + + +def load_operation_inventory(path=OPERATION_INVENTORY_PATH): + raw = path.read_bytes() + if len(raw) > 2 * 1024 * 1024: + raise ValueError("Operation inventory is oversized.") + value = json.loads(raw) + semantic = {key: item for key, item in value.items() if key != "contentHash"} + if ( + set(value) != {"schemaVersion", "diffusersRevision", "diffusersVersion", "sources", "pipelines", "contentHash"} + or type(value.get("schemaVersion")) is not int + or value.get("schemaVersion") != 1 + or value.get("diffusersRevision") != PINNED_DIFFUSERS_REVISION + or value.get("contentHash") != _hash(semantic) + ): + raise ValueError("Operation inventory does not match the reviewed source contract.") + if not isinstance(value["pipelines"], list) or not 1 <= len(value["pipelines"]) <= 512: + raise ValueError("Invalid operation inventory pipeline list.") + names = set() + for pipeline in value["pipelines"]: + if set(pipeline) != {"pipelineClass", "coverage", "reason", "equivalentTo", "upstreamTasks"}: + raise ValueError("Invalid operation inventory pipeline record.") + name = _identifier(pipeline["pipelineClass"]) + if name in names or pipeline["coverage"] not in { + "executable", + "equivalent", + "contract-only", + "research-blocked", + "intentionally-excluded", + "unreviewed", + }: + raise ValueError("Invalid operation inventory review decision.") + names.add(name) + if not isinstance(pipeline["reason"], str) or not 1 <= len(pipeline["reason"]) <= 2048: + raise ValueError("Missing operation inventory review reason.") + if not isinstance(pipeline["equivalentTo"], list) or len(pipeline["equivalentTo"]) > 32: + raise ValueError("Invalid operation inventory equivalents.") + for equivalent in pipeline["equivalentTo"]: + _identifier(equivalent) + tasks = pipeline["upstreamTasks"] + if not isinstance(tasks, list) or not 1 <= len(tasks) <= 512: + raise ValueError("Missing operation inventory task surface.") + seen_tasks = set() + for task in tasks: + if set(task) != {"task", "source", "workflowId"} or task["source"] not in {"auto", "modular", "export"}: + raise ValueError("Invalid operation inventory task.") + _identifier(task["task"]) + if task["workflowId"] is not None: + _identifier(task["workflowId"]) + identity = (task["task"], task["source"], task["workflowId"]) + if identity in seen_tasks: + raise ValueError("Duplicate operation inventory task.") + seen_tasks.add(identity) + return value diff --git a/modiff/operation_starters.py b/modiff/operation_starters.py new file mode 100644 index 00000000..de6534a6 --- /dev/null +++ b/modiff/operation_starters.py @@ -0,0 +1,350 @@ +"""Small ordinary graph drafts derived from existing operation/workflow owners. + +This is an authoring projection, not an execution recipe or qualification receipt. +Unbound conditioning/components remain explicit. No models are constructed here. +""" + +from modiff.operation_catalog import resolve_operation, seed_standard_operation_defaults +from modiff.operation_contracts import _identifier, operation_owns_model + + +def _modular_route(pipeline, task): + from modiff.modular_workflow_discovery import load_reviewed_modular_workflow_snapshot + from modiff.modular_task_adapters import modular_task_adapters + + for entry in load_reviewed_modular_workflow_snapshot()["contracts"]: + if entry["pipelineClass"] == pipeline: + for workflow in entry["workflows"]: + for name, adapter in modular_task_adapters(pipeline, workflow): + if name == task: + return workflow["id"], adapter + raise ValueError("No reviewed stage route for this task. Add individual operations instead.") + + +def _bind_execution_profile(loader, task, identity, repository=None): + """Select an existing reviewed loader profile without importing model code.""" + from copy import deepcopy + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES, resolve_execution_profiles_for_loader + from modiff.model_artifact_catalog import require_catalog_revision + + if not isinstance(identity, str) or identity != identity.strip(): + raise ValueError("Select an exact execution profile.") + profile = DIFFUSERS_EXECUTION_PROFILES.get(identity) + if ( + profile is None + or not profile.public + or profile.pipeline_class != loader["operation"]["binding"]["pipelineClass"] + or profile.backend_path != f"{loader['module']}.{loader['action']}" + or (task not in profile.modes and ( + profile.execution_path != "modular-diffusers" or profile.model_type != profile.pipeline_class + )) + ): + raise ValueError("The execution profile does not belong to this pipeline/task.") + if repository is None: + repository = profile.default_repo + from modiff.modular_workflow_contracts import PINNED_MODULAR_WORKFLOW_REPOSITORY_VARIANTS + + workflow_repositories = PINNED_MODULAR_WORKFLOW_REPOSITORY_VARIANTS.get( + (profile.pipeline_class, loader["operation"].get("workflowId")), (), + ) if profile.execution_path == "modular-diffusers" else () + if repository is not None and (not isinstance(repository, str) or repository not in { + profile.default_repo, profile.fallback_repo, *profile.compatible_repos, *workflow_repositories, + }): + raise ValueError("The repository is not an exact reviewed artifact for this execution profile.") + if loader["operation"]["decomposition"] == "integrated": + if repository != profile.default_repo: + raise ValueError("Integrated artifact variants require their own reviewed execution profile.") + from modiff.integrated_operation_contracts import integrated_operation_values + + for key, value in integrated_operation_values(loader["operation"], profile=profile).items(): + if key not in loader["params"]: + raise ValueError("Integrated model selection targets an undeclared field.") + loader["params"][key]["value"] = deepcopy(value) + loader["values"][key] = deepcopy(value) + # Integrated actions have no synthetic pipeline_class/revision fields. + # The exact profile and artifact are checked above; execution rehashes + # the selected file through the action's existing artifact resolver. + return + params = loader["params"] + repo_field = "repo_id" if loader["action"] == "ModelsLoader" else "model_id" + if repo_field not in params or "revision" not in params: + raise ValueError("This loader does not expose a reviewed model selection.") + values = { + repo_field: {"source": "hub", "value": repository}, + "revision": require_catalog_revision(repository, model_type=profile.model_type), + } + if "execution_profile_id" in params: + values["execution_profile_id"] = profile.id + if "reviewed_variant" in params: + options = params["reviewed_variant"].get("options", []) + values["reviewed_variant"] = repository if repository in options else "" + for key, value in values.items(): + params[key]["value"] = deepcopy(value) + loader["values"][key] = deepcopy(value) + resolved, reason = resolve_execution_profiles_for_loader(loader["module"], loader["action"], loader["values"]) + runtime_profile_id = "sdxl-base:modular" if profile.id == "sdxl-pag:modular" else profile.id + if reason or runtime_profile_id not in {p.id for p in resolved}: + raise ValueError("The selected model does not resolve to its reviewed loader profile.") + + +def resolve_operation_starter(modules, contracts, selection): + if not isinstance(selection, dict) or set(selection) not in ( + {"pipelineClass", "task"}, + {"pipelineClass", "task", "executionProfileId"}, + {"pipelineClass", "task", "executionProfileId", "repository"}, + ): + raise ValueError("Select an exact pipeline and task.") + pipeline, task = (_identifier(selection[key]) for key in ("pipelineClass", "task")) + if "repository" in selection and not isinstance(selection["repository"], str): + raise ValueError("Select an exact reviewed repository string.") + binding = {"pipelineClass": pipeline, "task": task} + selected = [c for c in contracts if c["pipelineClass"] == pipeline and c["task"] == task] + if not selected or sum(operation_owns_model(c) for c in selected) != 1: + raise ValueError("No complete operation binding for this pipeline/task.") + nodes = { + c["operationId"]: resolve_operation(modules, contracts, {**binding, "operationId": c["operationId"]}) + for c in selected + } + edges = [] + targets = set() + + def connect(source, source_handle, target, target_handle): + from modiff.block_definition_v2 import _block_value_types_are_compatible_v2 + + left = nodes[source]["params"].get(source_handle) + right = nodes[target]["params"].get(target_handle) + if ( + not left + or not right + or left.get("hidden") + or right.get("hidden") + or left.get("display") != "output" + or not (right.get("display") == "input" or right.get("isInput")) + or not _block_value_types_are_compatible_v2(left.get("type"), right.get("type")) + ): + raise ValueError( + f"The declared connection {source}.{source_handle} → {target}.{target_handle} is unavailable." + ) + edge = {"source": source, "sourceHandle": source_handle, "target": target, "targetHandle": target_handle} + if edge in edges: + return + if (target, target_handle) in targets: + raise ValueError("The starter has competing input connections.") + edges.append(edge) + targets.add((target, target_handle)) + + loader = next(c for c in selected if operation_owns_model(c))["operationId"] + if "executionProfileId" in selection: + _bind_execution_profile(nodes[loader], task, selection["executionProfileId"], selection.get("repository")) + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES + + profile = DIFFUSERS_EXECUTION_PROFILES[selection["executionProfileId"]] + for node in nodes.values(): + seed_standard_operation_defaults(node, profile) + if profile.id in {"flux-schnell:modular", "flux2-klein:modular", "flux2-klein-kv:t2i-modular"}: + field = nodes["diffusion.denoise"]["params"].get("guidance_scale") + if field: + field["hidden"] = True # the exact distilled transformer does not consume this control + if profile.id == "flux-schnell:modular": + text_encoder = nodes["diffusion.encode_prompt"] + text_encoder["params"]["max_sequence_length"].update(value=256, max=256) + text_encoder["values"]["max_sequence_length"] = 256 + if profile.id == "sdxl-turbo:modular": + nodes["diffusion.guidance"]["params"]["guidance_scale"]["min"] = 0.0 + if profile.id == "sdxl-pag:modular": + from copy import deepcopy + from modules.ModularDiffusers.guiders import GUIDER_CONFIGS + + guidance = nodes.get("diffusion.guidance") + if guidance is None: + raise ValueError("The native PAG recipe requires its declared guidance operation.") + guidance["params"].update(deepcopy(GUIDER_CONFIGS["PerturbedAttentionGuidance"])) + for key, value in { + "guider": "PerturbedAttentionGuidance", "guidance_scale": 5.0, + "perturbed_guidance_scale": 3.0, "perturbed_guidance_start": 0.0, + "perturbed_guidance_stop": 1.0, + }.items(): + guidance["params"][key]["value"] = value + guidance["values"][key] = value + layers = nodes["diffusion.guidance_layers"] + block = "mid_block.attentions.0.transformer_blocks" + layers["params"]["blocks_select"]["value"] = [block] + layers["values"]["blocks_select"] = [block] + config = {"enabled": True, "indices": ",".join(map(str, range(10))), "dropout": 1.0, + "skip_attention": False, "skip_attention_scores": True, "skip_ff": False} + layers["params"][block] = {"label": block, "display": "layerconfig", "value": config} + layers["values"][block] = deepcopy(config) + workflow_id, upstream, required = None, [], set() + ordered = [loader] + if nodes[loader]["operation"]["decomposition"] == "integrated": + if len(selected) != 1: + raise ValueError("An integrated starter must be one complete model operation.") + elif any(c["decomposition"] == "pipeline" for c in selected): + # The reviewed Transformers actions pass their typed model handle, + # not a Diffusers pipeline. Keep the real registered socket names. + model_handle = "model" if nodes[loader]["module"] == "modules.HuggingFaceTransformers" and nodes[loader]["action"] in { + "LoadImageTextToTextModel", "LoadAnyToAnyModel", + } else "pipeline" + for c in selected: + if c["decomposition"] == "pipeline": + connect(loader, model_handle, c["operationId"], model_handle) + ordered.append(c["operationId"]) + else: + from modiff.modular_action_bindings import MODULAR_ACTION_BINDINGS + from modiff.huggingface_cluster_admission import _REQUIRED_COMPONENT_EDGES, _PIPELINE_ACTION_COMPONENT_EDGES + + workflow_id, adapter = _modular_route(pipeline, task) + upstream = adapter["upstreamBlockSequence"] + required = set(adapter["requiredInputs"]) + by_key = {c["nodeKey"]: c["operationId"] for c in selected} + actions = {a: by_key[MODULAR_ACTION_BINDINGS[a][1]] for a in adapter["actionSequence"]} + ordered.extend(actions.values()) + for action, operation in actions.items(): + # These exact component edges already govern registered admission. + # Auxiliary loaders/guider nodes are deliberately left as inputs. + component_edges = _REQUIRED_COMPONENT_EDGES.get(action, set()) | _PIPELINE_ACTION_COMPONENT_EDGES.get( + (pipeline, action), set() + ) + for source, output, _, input_ in sorted(component_edges): + if source == "models": + connect(loader, output, operation, input_) + for edge in adapter["stateEdges"]: + connect( + actions[edge["producerAction"]], + edge["producerOutput"], + actions[edge["consumerAction"]], + edge["consumerInput"], + ) + # Publish effective output dimensions instead of duplicating decoder + # literals. The generic config declares their semantic names explicitly. + for c in selected: + for output in c["ports"]: + if ( + output["direction"] != "output" + or output["hidden"] + or output["semanticName"] not in {"width", "height"} + ): + continue + for target in selected: + if target["operationId"] != "diffusion.decode_latents": + continue + for input_ in target["ports"]: + if ( + input_["direction"] == "input" + and not input_["hidden"] + and input_["semanticName"] == output["semanticName"] + ): + connect(c["operationId"], output["name"], target["operationId"], input_["name"]) + # Typed ordinary auxiliary operations, e.g. a reference assembler. + for c in selected: + if c["operationId"] in ordered: + continue + ordered.insert(1, c["operationId"]) + for output in c["ports"]: + if output["direction"] != "output" or output["hidden"]: + continue + matches = [ + (target, port) + for target in selected + if target is not c + for port in target["ports"] + if port["direction"] == "input" and not port["hidden"] and port["types"] == output["types"] + ] + if len(matches) == 1 or c["operationId"] == "diffusion.guidance": + for target, port in matches: + connect(c["operationId"], output["name"], target["operationId"], port["name"]) + + # Admission describes minimum reviewed paths. The operation owner also + # declares required model inputs (e.g. SDXL Denoise's VAE). Fill only exact, + # unique component bundles from this loader; never infer from tensor types. + loader_ports = nodes[loader]["operation"]["ports"] + for operation, node in nodes.items(): + if operation == loader: + continue + for input_ in node["operation"]["ports"]: + members = input_["semantics"]["members"] + if ( + input_["direction"] != "input" + or input_["hidden"] + or not input_["required"] + or "component" not in input_["roles"] + or not members + or (operation, input_["name"]) in targets + ): + continue + matches = [ + p + for p in loader_ports + if p["direction"] == "output" + and not p["hidden"] + and "component" in p["roles"] + and p["semantics"]["members"] == members + and p["types"] == input_["types"] + ] + if len(matches) == 1: + connect(loader, matches[0]["name"], operation, input_["name"]) + + # Both native PipelineState continuations and sealed route states carry one + # generator. Expose its shared seed as an authoring relationship; execution + # still receives ordinary values and retains the existing seed/state checks. + state_groups = [{key} for key in nodes] + for edge in edges: + output = next( + p + for p in nodes[edge["source"]]["operation"]["ports"] + if p["name"] == edge["sourceHandle"] and p["direction"] == "output" + ) + if output["semantics"]["kind"] != "state": + continue + joined = [g for g in state_groups if edge["source"] in g or edge["target"] in g] + state_groups = [g for g in state_groups if g not in joined] + state_groups.append(set().union(*joined)) + shared_inputs = [] + # SDXL's encoder resizes source/mask before sampling. Its geometry must + # agree with denoising just like its generator seed; otherwise the visible + # Denoise size can be silently superseded by 1024px encoder defaults. + shared_names = ("seed", "width", "height") if binding["pipelineClass"] == "StableDiffusionXLModularPipeline" else ("seed",) + for group in state_groups: + for shared_name in shared_names: + members = [ + {"operationId": key, "field": p["name"]} + for key in ordered + if key in group + for p in nodes[key]["operation"]["ports"] + if p["direction"] == "input" + and p["semanticName"] == shared_name + and not p["hidden"] + and p["semantics"]["kind"] == "value" + ] + if len(members) > 1: + shared_inputs.append({"name": shared_name, "members": members}) + + # A connected Guider owns guidance. Resolve visibility in the draft itself: + # mounting the frontend must not need a family-default schema refresh to + # hide the unused scalar (whose bounds differ for distilled variants). + for edge in edges: + if edge["targetHandle"] == "guider": + guidance = nodes[edge["target"]]["params"].get("guidance_scale") + if guidance is not None: + guidance["hidden"] = True + + required_inputs = [] + for operation, node in nodes.items(): + declared_required = { + p["name"] for p in node["operation"]["ports"] if p["required"] and p["direction"] == "input" + } + for name, field in node["params"].items(): + if field.get("hidden") or field.get("display") == "output" or (operation, name) in targets: + continue + if field.get("required") or name in required or name in declared_required: + required_inputs.append({"operationId": operation, "field": name}) + return { + "schemaVersion": 1, + **binding, + "workflowId": workflow_id, + "nodes": [nodes[key] for key in ordered], + "edges": edges, + "requiredInputs": required_inputs, + "sharedInputs": shared_inputs, + "upstreamBlocks": upstream, + } diff --git a/modiff/optional_runtimes.py b/modiff/optional_runtimes.py index 57ec1159..5441b526 100644 --- a/modiff/optional_runtimes.py +++ b/modiff/optional_runtimes.py @@ -29,6 +29,7 @@ TRANSFORMERS_MAIN_PEFT_BITSANDBYTES_RUNTIME_PROFILE_ID = ( "huggingface-transformers-main-96fe6dce-peft-0.20.0-bitsandbytes-0.50.0" ) +TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID = "huggingface-transformers-peft-5.17.0-0.20.0" TRANSFORMERS_MAIN_COMMIT = "96fe6dce36cc929a5ffd3e34296554c4cb6b669e" TRANSFORMERS_MAIN_REVIEW_BASE_COMMIT = "a597f974857b3d92939971296bc0deb93d33d780" TRANSFORMERS_MAIN_REVIEWED_DELTA_PATHS = ( @@ -1337,8 +1338,186 @@ def spec_digest(self) -> str: ), ) +# Published Hugging Face wheels, locked separately from historical qualified +# profiles. Availability remains closed until this exact target passes the +# portable qualifier; installing new versions must not rewrite an old identity. +_TRANSFORMERS_517_WHEELS = ( + { + "distribution": "transformers", + "version": "5.17.0", + "filename": "transformers-5.17.0-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", + "sha256": "78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", + "byteSize": 12295140, + "platform": "linux", + "pythonTag": "cp312", + "machine": "x86_64" + }, + { + "distribution": "tokenizers", + "version": "0.23.1", + "filename": "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "url": "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "sha256": "5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", + "byteSize": 3346235, + "platform": "linux", + "pythonTag": "cp312", + "machine": "x86_64" + }, + { + "distribution": "transformers", + "version": "5.17.0", + "filename": "transformers-5.17.0-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", + "sha256": "78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", + "byteSize": 12295140, + "platform": "linux", + "pythonTag": "cp312", + "machine": "arm64" + }, + { + "distribution": "tokenizers", + "version": "0.23.1", + "filename": "tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "url": "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + "sha256": "1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", + "byteSize": 3374081, + "platform": "linux", + "pythonTag": "cp312", + "machine": "arm64" + }, + { + "distribution": "transformers", + "version": "5.17.0", + "filename": "transformers-5.17.0-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", + "sha256": "78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", + "byteSize": 12295140, + "platform": "macos", + "pythonTag": "cp312", + "machine": "x86_64" + }, + { + "distribution": "tokenizers", + "version": "0.23.1", + "filename": "tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", + "url": "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", + "sha256": "e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", + "byteSize": 3146732, + "platform": "macos", + "pythonTag": "cp312", + "machine": "x86_64" + }, + { + "distribution": "transformers", + "version": "5.17.0", + "filename": "transformers-5.17.0-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", + "sha256": "78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", + "byteSize": 12295140, + "platform": "macos", + "pythonTag": "cp312", + "machine": "arm64" + }, + { + "distribution": "tokenizers", + "version": "0.23.1", + "filename": "tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", + "url": "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", + "sha256": "e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", + "byteSize": 3054954, + "platform": "macos", + "pythonTag": "cp312", + "machine": "arm64" + }, + { + "distribution": "transformers", + "version": "5.17.0", + "filename": "transformers-5.17.0-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", + "sha256": "78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", + "byteSize": 12295140, + "platform": "windows", + "pythonTag": "cp312", + "machine": "x86_64" + }, + { + "distribution": "tokenizers", + "version": "0.23.1", + "filename": "tokenizers-0.23.1-cp310-abi3-win_amd64.whl", + "url": "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", + "sha256": "e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", + "byteSize": 2798223, + "platform": "windows", + "pythonTag": "cp312", + "machine": "x86_64" + }, + { + "distribution": "transformers", + "version": "5.17.0", + "filename": "transformers-5.17.0-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/e8/d0/c502b60d684adbd98a8dc7d5bb866842772b816ac4354e4608be240041ae/transformers-5.17.0-py3-none-any.whl", + "sha256": "78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", + "byteSize": 12295140, + "platform": "windows", + "pythonTag": "cp312", + "machine": "arm64" + }, + { + "distribution": "tokenizers", + "version": "0.23.1", + "filename": "tokenizers-0.23.1-cp310-abi3-win_arm64.whl", + "url": "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", + "sha256": "a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", + "byteSize": 2664127, + "platform": "windows", + "pythonTag": "cp312", + "machine": "arm64" + }, +) + +_TRANSFORMERS_517_PEFT_PROFILE = replace( + _TRANSFORMERS_PEFT_PROFILE, + id=TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID, + label="Hugging Face Transformers 5.17 + PEFT (Linux x86-64 qualified)", + packages=tuple( + replace(package, version={"transformers": "5.17.0", "tokenizers": "0.23.1"}[package.distribution]) + if package.distribution in {"transformers", "tokenizers"} else package + for package in _TRANSFORMERS_PEFT_PROFILE.packages + ), + artifact_locks=tuple( + dict(artifact) for artifact in _TRANSFORMERS_PEFT_PROFILE.artifact_locks + if artifact["distribution"] not in {"transformers", "tokenizers"} + ) + _TRANSFORMERS_517_WHEELS, + required_diffusers_symbols=(*_TRANSFORMERS_PEFT_PROFILE.required_diffusers_symbols, + "QwenImage21Pipeline", "QwenImage21Transformer2DModel", "AutoencoderKLQwenImage21"), + contract_state="qualified_platform_scoped", + cutover_ready=False, + install_action_available=False, + activation_available=False, + target_contracts=tuple( + OptionalRuntimeTargetContract( + platform=platform_name, machine=machine, + contract_state="qualified" if (platform_name, machine) == ("linux", "x86_64") else "candidate_unqualified", + cutover_ready=(platform_name, machine) == ("linux", "x86_64"), + install_action_available=(platform_name, machine) == ("linux", "x86_64"), + activation_available=(platform_name, machine) == ("linux", "x86_64"), + ) + for platform_name, _python_tag, machine in _OPTIONAL_RUNTIME_TARGETS + ), + # The verified optional-runtime regression suite exercises the existing + # image/audio adapters on this build. Bind compatibility to exact reviewed + # specs; no quantization or media-package extras are implied by this alias. + satisfies_profiles=( + (TRANSFORMERS_PEFT_RUNTIME_PROFILE_ID, _TRANSFORMERS_PEFT_PROFILE.spec_digest), + (TRANSFORMERS_MAIN_PEFT_RUNTIME_PROFILE_ID, _TRANSFORMERS_MAIN_PEFT_PROFILE.spec_digest), + ), +) + + OPTIONAL_RUNTIME_PROFILES: Mapping[str, OptionalRuntimeProfile] = MappingProxyType( { + _TRANSFORMERS_517_PEFT_PROFILE.id: _TRANSFORMERS_517_PEFT_PROFILE, _TRANSFORMERS_PEFT_PROFILE.id: _TRANSFORMERS_PEFT_PROFILE, _TRANSFORMERS_MAIN_PEFT_PROFILE.id: _TRANSFORMERS_MAIN_PEFT_PROFILE, _TRANSFORMERS_MAIN_PEFT_QUANTO_PROFILE.id: _TRANSFORMERS_MAIN_PEFT_QUANTO_PROFILE, diff --git a/modiff/registered_block_v2_catalog.py b/modiff/registered_block_v2_catalog.py index 5ab56c35..80bf6b85 100644 --- a/modiff/registered_block_v2_catalog.py +++ b/modiff/registered_block_v2_catalog.py @@ -162,3 +162,32 @@ def registered_block_v2_definition_pins() -> dict[str, tuple[str, str]]: entry["compiledDefinitionCanonicalSha256"], ) return result + + +def registered_block_v2_interfaces() -> dict[str, Any]: + """Public sockets for discovery, from the validated compiled definitions only. + + No graph, instance values, field actions or runtime admission are returned. + The client still verifies and inserts the full pinned definition on selection. + """ + return { + "schemaVersion": 1, + "error": False, + "entries": [ + { + "catalogDefinitionId": entry["catalogDefinitionId"], + "catalogDefinitionContentHash": entry["catalogDefinitionContentHash"], + "admissionId": entry["admissionId"], + "compiledDefinitionContentHash": entry["definition"]["contentHash"], + "compiledDefinitionCanonicalSha256": entry["compiledDefinitionCanonicalSha256"], + **{ + direction: [ + {"portId": port["portId"], "valueType": copy.deepcopy(port["valueType"])} + for port in entry["definition"]["boundary"][direction] + ] + for direction in ("inputs", "outputs") + }, + } + for entry in _catalog().values() + ], + } diff --git a/modiff/registered_block_v2_catalog.v1.json.gz b/modiff/registered_block_v2_catalog.v1.json.gz index 2d9b4a4c..6024b06e 100644 Binary files a/modiff/registered_block_v2_catalog.v1.json.gz and b/modiff/registered_block_v2_catalog.v1.json.gz differ diff --git a/modiff/runtime_overlays.py b/modiff/runtime_overlays.py index ed90103a..07f8f387 100644 --- a/modiff/runtime_overlays.py +++ b/modiff/runtime_overlays.py @@ -40,8 +40,8 @@ PYPI_SIMPLE_INDEX = "https://pypi.org/simple" PINNED_DIFFUSERS_SOURCE_URL = "https://github.com/huggingface/diffusers.git" -PINNED_DIFFUSERS_COMMIT = "2f7e0154a9db246e95c9ede43edba7db5b130805" -PINNED_DIFFUSERS_VERSION = "0.40.0.dev0" +PINNED_DIFFUSERS_COMMIT = "fbf49e7f35857f76bc57b177e26f12b03687c668" +PINNED_DIFFUSERS_VERSION = "0.41.0.dev0" _DIGEST_PREFIX = "sha256:" MANAGED_ROOT = Path( os.environ.get("MODIFF_MANAGED_ROOT") or Path(__file__).resolve().parents[1] / ".modiff" diff --git a/modiff/server.py b/modiff/server.py index 5c7f2f6c..026b7cdf 100644 --- a/modiff/server.py +++ b/modiff/server.py @@ -13,7 +13,7 @@ logging.getLogger("asyncio").setLevel(logging.WARNING) from functools import partial -from importlib import import_module, metadata, invalidate_caches +from importlib import import_module, metadata import os import platform import base64 @@ -22,6 +22,7 @@ import html import ipaddress import io +import inspect import json import nanoid import random @@ -30,6 +31,7 @@ import stat import subprocess import threading +from concurrent.futures import ThreadPoolExecutor from utils.paths import list_files from pathlib import Path import sys @@ -46,6 +48,7 @@ bind_generation_input_origins, ) from modiff.studio_persistence_lock import STUDIO_PERSISTENCE_LOCK +from modiff.field_metadata import is_metadata_field_action, metadata_field_callback from modiff.path_identifiers import ( data_path_identifier, is_data_path_identifier, @@ -455,7 +458,7 @@ def byte_range_response(request, body, *, content_type, charset=None, filename=N from modiff.hardware import format_hardware_summary, get_hardware_snapshot, legacy_torch_status from modiff.runtime_telemetry import active_accelerator_snapshot, accelerator_cuda_diagnostic from modiff.huggingface_node_library import reviewed_huggingface_node_library -from modiff.registered_block_v2_catalog import registered_block_v2_catalog_entry +from modiff.registered_block_v2_catalog import registered_block_v2_catalog_entry, registered_block_v2_interfaces from modiff.huggingface_cluster_runtime import ( qualify_huggingface_cluster_auto_authority, qualify_huggingface_cluster_expert_runtime, @@ -532,6 +535,7 @@ def byte_range_response(request, body, *, content_type, charset=None, filename=N WAN_T2V_1_3B_DIFFUSERS_FILES, WAN_VACE_1_3B_DIFFUSERS_FILES, Z_IMAGE_DIFFUSERS_FILES, + reviewed_repository_download_files, assert_studio_execution_graph, studio_capability_definitions, studio_execution_spec_for_pair, @@ -555,7 +559,9 @@ def byte_range_response(request, body, *, content_type, charset=None, filename=N build_task_template_contracts, ) from modiff.modelstore import modelstore -from modules import MODULE_MAP, parse_module_map +from modules import MODULE_MAP +from modiff.custom_extension_api import CustomExtensionAPI +from modiff.service_api import ServiceAPI from utils.huggingface import ( cleanup_interrupted_hub_download_files, delete_model, @@ -1247,10 +1253,10 @@ def studio_download_files_for_repo(repo_id): normalized = {tuple(sorted(set(selection))) for selection in matches} if len(normalized) > 1: raise RuntimeError(f"Conflicting reviewed Studio download selections for {repo_id}.") - return list(next(iter(normalized), ())) + return list(next(iter(normalized), ())) or reviewed_repository_download_files(repo_id) -class WebServer: +class WebServer(CustomExtensionAPI, ServiceAPI): def __init__( self, modules: dict = {}, @@ -1271,6 +1277,7 @@ def __init__( self.modules = modules self.ws_sessions = {} self.pending_ws_requests = {} + self.pending_ws_request_sessions = {} self.interrupt_flag = False self._forced_restart_timer = None @@ -1283,6 +1290,7 @@ def __init__( self._supervisor_queue_state_lock = threading.RLock() if supervisor_queue_state else None self._supervisor_queue_last_write = 0.0 self.node_cache = {} + self._model_executor = None self._active_graph_node_ids = set() self._last_auto_model_family = None self._last_auto_resource_signature = None @@ -1334,6 +1342,7 @@ def __init__( # those two callers, so protect the shared history file with a small # process-local lock as well. self.studio_history_file_lock = threading.RLock() + self._studio_history_read_cache = None self.hf_download_semaphore = asyncio.Semaphore(2) self.download_reservation_lock = asyncio.Lock() self.hf_cache_mutation_lock = asyncio.Lock() @@ -1380,6 +1389,9 @@ def __init__( # Model destructors can take minutes. Serialize cache ownership without # holding the HTTP event loop or racing a same-id loader replacement. self._node_cache_lock = asyncio.Lock() + # Reviewed presentation callbacks never borrow cached model owners. + # Retain ordering and drain cancelled threads separately from inference. + self._field_metadata_lock = asyncio.Lock() self._node_cache_teardown_active = False self.main_worker_task = None @@ -1428,6 +1440,9 @@ def __init__( web.get("/ws", self.websocket), web.get(r"/nodes{id:/?([\w\d_-]+/[\w\d_-]+)?}", self.nodes), web.post("/fields/action", self.field_action), + web.post("/operations/resolve", self.resolve_operation), + web.post("/operations/starter", self.resolve_operation_starter), + web.post("/operations/task-starter", self.resolve_task_starter), web.get("/cache/{node}/{field}", self.cache), web.get("/cache/{node}/{field}/{index}", self.cache), web.delete("/cache", self.delete_cache), @@ -1445,6 +1460,7 @@ def __init__( web.get("/media/preview", self.media_preview), web.get("/preview", self.preview), web.post("/graph", self.graph), + web.post("/service_package", self.service_package), web.get("/queue", self.get_queue), web.get("/runs/{task_id}", self.get_run), web.delete("/queue/{task_id}", self.delete_task), @@ -1490,6 +1506,7 @@ def __init__( web.get("/media_assets", self.media_assets_list), web.delete("/media_assets", self.media_assets_cleanup), web.get("/huggingface/node-library", self.huggingface_node_library), + web.get("/huggingface/registered-block-interfaces", self.huggingface_registered_block_interfaces), web.get( "/huggingface/registered-block-v2", self.huggingface_registered_block_v2, @@ -1525,7 +1542,11 @@ def __init__( web.get("/custom_modules", self.custom_modules_list), web.post("/custom_modules/refresh", self.custom_modules_refresh), web.post("/custom_modules/install", self.custom_modules_install), + web.post("/custom_modules/add", self.custom_modules_add), + web.post("/custom_modules/resolve", self.custom_modules_resolve), web.post("/custom_modules/{name}/update", self.custom_modules_update), + web.post("/custom_modules/{name}/inspect", self.custom_modules_inspect), + web.post("/custom_modules/{name}/reload", self.custom_modules_reload), web.post("/custom_modules/{name}/disable", self.custom_modules_disable), web.post("/custom_modules/{name}/enable", self.custom_modules_enable), web.get("/studio_outputs", self.studio_outputs_get), @@ -1683,6 +1704,9 @@ async def cleanup(self): # Cleanup the runner. if self.runner: await self.runner.cleanup() + if self._model_executor is not None: + self._model_executor.shutdown(wait=False, cancel_futures=True) + self._model_executor = None """ ╭───────────────╮ @@ -2082,6 +2106,29 @@ async def get_queue(self, _): } ) + @staticmethod + def _studio_output_identity_values(output, key, provenance_key): + values = set() + + def add(value): + if value is None: + return + normalized = str(value).strip() + if normalized: + values.add(normalized) + + add(output.get(key)) + for container_key in ("provenance", "backendProvenance"): + container = output.get(container_key) + if isinstance(container, dict): + add(container.get(provenance_key)) + media_items = output.get("mediaItems") + if isinstance(media_items, list): + for item in media_items: + if isinstance(item, dict): + add(item.get(key)) + return values + def _studio_outputs_for_run(self, task_id, client_run_id=None): """Return persisted outputs whose recorded run identity matches exactly.""" normalized_task_id = str(task_id or "").strip() @@ -2089,39 +2136,17 @@ def _studio_outputs_for_run(self, task_id, client_run_id=None): if not normalized_task_id: return [] - def identity_values(output, key, provenance_key): - values = set() - - def add(value): - if value is None: - return - normalized = str(value).strip() - if normalized: - values.add(normalized) - - add(output.get(key)) - for container_key in ("provenance", "backendProvenance"): - container = output.get(container_key) - if isinstance(container, dict): - add(container.get(provenance_key)) - media_items = output.get("mediaItems") - if isinstance(media_items, list): - for item in media_items: - if isinstance(item, dict): - add(item.get(key)) - return values - matches = [] with self.studio_history_file_lock: - outputs = self._read_studio_outputs() + outputs = self._read_studio_outputs(task_id=normalized_task_id) for output in outputs: if not isinstance(output, dict): continue - task_ids = identity_values(output, "taskId", "backendExecutionId") + task_ids = self._studio_output_identity_values(output, "taskId", "backendExecutionId") if task_ids != {normalized_task_id}: continue if normalized_client_run_id: - client_run_ids = identity_values(output, "clientRunId", "clientRunId") + client_run_ids = self._studio_output_identity_values(output, "clientRunId", "clientRunId") # Exact task identity is sufficient for legacy records that # predate client-run IDs. When present, however, every recorded # client identity must agree with the originating run. @@ -2284,6 +2309,7 @@ async def _main_worker(self): await asyncio.sleep(0.05) terminal_status = "completed" failure_payload = None + failure = None try: if isinstance(args, tuple): callback = partial(task, *args) @@ -2406,8 +2432,12 @@ def run_unless_cancelled(): # inside third-party model loading, so teardown runs # immediately after that call returns and before the # worker advances the queue. + def release_failed_attempt(): + self._release_exception_frames(failure) + return self._release_runtime_caches_for_retry() + runtime_cleanup = await self._with_node_cache_lease( - lambda: asyncio.to_thread(self._release_runtime_caches_for_retry) + lambda: self._run_executor_callback(release_failed_attempt) ) self._last_auto_model_family = None self._last_auto_resource_signature = None @@ -2471,7 +2501,10 @@ def run_unless_cancelled(): logger.debug("Main worker shutting down") async def _with_node_cache_lease(self, operation): - async with self._node_cache_lock: + return await self._with_action_lease(self._node_cache_lock, operation) + + async def _with_action_lease(self, lock, operation): + async with lock: task = asyncio.ensure_future(operation()) cancelled = False while True: @@ -2488,8 +2521,15 @@ async def _with_node_cache_lease(self, operation): raise asyncio.CancelledError return result - async def _run_executor_callback(self, callback, *, serialize_model_io=False, on_start=None): + async def _run_executor_callback(self, callback, *, serialize_model_io=False, on_start=None, model_work=True): loop = self.loop or asyncio.get_running_loop() + # Accelerator libraries retain workspaces per thread. The shared HTTP + # pool rotates workers between runs, accumulating one workspace per + # worker. Keep model calls/teardown on one thread, separate from control + # requests, while retaining the existing graph and ownership leases. + if model_work and self._model_executor is None: + self._model_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="modiff-model") + executor = self._model_executor if model_work else None def run_after_resource_probe(): # A probe that began while idle must finish before model allocator @@ -2503,10 +2543,10 @@ def run_after_resource_probe(): async with self.model_io_lock: if on_start is not None: on_start() - return await loop.run_in_executor(None, run_after_resource_probe) + return await loop.run_in_executor(executor, run_after_resource_probe) if on_start is not None: on_start() - return await loop.run_in_executor(None, run_after_resource_probe) + return await loop.run_in_executor(executor, run_after_resource_probe) async def _background_worker(self): try: @@ -2563,13 +2603,12 @@ async def favicon(self, _): async def user_assets(self, request): module = request.match_info.get("module") file = request.match_info.get("file") - fileName = f"custom/{module}/web/{file}" - - if not Path(fileName).exists(): - return web.HTTPNotFound(text="File not found") - - response = web.FileResponse(fileName) - # response.headers["Content-Type"] = "application/javascript" + try: + content = await asyncio.to_thread(self._extension_store().asset, module, file) + except (ValueError, OSError): + return web.HTTPNotFound(text="Approved custom asset not found") + import mimetypes + response = web.Response(body=content, content_type=mimetypes.guess_type(file)[0] or 'application/octet-stream') response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "0" @@ -2776,6 +2815,23 @@ async def runtime_options(self, _request): } ) + def _describe_registered_node(self, module, action, values): + return { + "module": module, + "action": action, + "type": values.get("type", "custom"), + "label": values.get("label", f"{module}: {action}"), + "category": values.get("category", "default"), + "description": values.get("description", ""), + "resizable": values.get("resizable", False), + "skipParamsCheck": values.get("skipParamsCheck", False), + "style": values.get("style", ""), + "params": self.describe_node_params(values.get("params", {})), + "time": [0, 0, 0], + "memory": [0, 0, 0], + "cache": False, + } + async def nodes(self, request): id = request.match_info.get("id", "").strip("/") modules = self.modules @@ -2801,23 +2857,7 @@ async def nodes(self, request): for action, values in actions.items(): if values.get("hidden", False) and not id: continue - params = self.describe_node_params(values.get("params", {})) - - output[f"{module}.{action}"] = { - "module": module, - "action": action, - "type": values.get("type", "custom"), - "label": values.get("label", f"{module}: {action}"), - "category": values.get("category", "default"), - "description": values.get("description", ""), - "resizable": values.get("resizable", False), - "skipParamsCheck": values.get("skipParamsCheck", False), - "style": values.get("style", ""), - "params": params, - "time": [0, 0, 0], - "memory": [0, 0, 0], - "cache": False, - } + output[f"{module}.{action}"] = self._describe_registered_node(module, action, values) return web.json_response({"instance": self.instance, "nodes": output}) @@ -2842,6 +2882,7 @@ def _execute_field_action( values, ref, ): + self._require_custom_module_enabled(module) assert_optional_runtime_ready( field_action_optional_runtime_requirement( module, @@ -2890,6 +2931,7 @@ def _authorize_field_action(self, *, module, action, field_key, method_name): if not all(isinstance(value, str) and value for value in (module, action, field_key, method_name)): raise ValueError("Field actions require non-empty module, action, fieldKey, and fn strings.") + self._require_custom_module_enabled(module) module_definition = self.modules.get(module) if not isinstance(module_definition, dict): raise ValueError(f"Unknown field-action module {module!r}.") @@ -2907,10 +2949,14 @@ def _authorize_field_action(self, *, module, action, field_key, method_name): ) async def field_action(self, request): - return await self._with_node_cache_lease(lambda: self._field_action(request)) - - async def _field_action(self, request): data = await request.json() + metadata_only = is_metadata_field_action(data) + lock = self._field_metadata_lock if metadata_only else self._node_cache_lock + return await self._with_action_lease( + lock, lambda: self._field_action(data, metadata_only=metadata_only) + ) + + async def _field_action(self, data, *, metadata_only=False): if not isinstance(data, dict): return web.json_response( {"error": True, "message": "Field action payload must be a JSON object."}, @@ -2978,28 +3024,29 @@ async def _field_action(self, request): if sid: message_identity["sid"] = sid - if node not in self.node_cache: + if metadata_only: + callback = metadata_field_callback(action, method_name, node_id=node, sid=sid, module=module) + elif node not in self.node_cache: work_module = import_module(f"{module}.main") work_action = getattr(work_module, action) work_action = work_action(node_id=node) self.node_cache[node] = work_action - cached_node = self.node_cache[node] - if ( - getattr(cached_node, "module_name", None) != module - or getattr(cached_node, "class_name", None) != action - ): - return web.json_response( - { - "error": True, - "message": "The cached node does not match the requested field-action module and action.", - }, - status=409, - ) - - cached_node._sid = sid # always update the sid as it may change over time - - callback = getattr(cached_node, method_name, None) + if not metadata_only: + cached_node = self.node_cache[node] + if ( + getattr(cached_node, "module_name", None) != module + or getattr(cached_node, "class_name", None) != action + ): + return web.json_response( + { + "error": True, + "message": "The cached node does not match the requested field-action module and action.", + }, + status=409, + ) + cached_node._sid = sid # always update the sid as it may change over time + callback = getattr(cached_node, method_name, None) if not callable(callback): return web.json_response( {"error": True, "message": "The authorized backend field action is not callable."}, @@ -3294,10 +3341,39 @@ async def cache(self, request): filename=filename, ) + def _release_cached_nodes(self, targets): + """Release selected owners and dependents while holding the model/cache lease.""" + targets = self._dependent_cache_nodes(targets) + released_components = self._release_node_modular_components(targets) + # The standard manager also has shared owners. Destruction must + # neither offload discarded weights nor remove a surviving owner's + # model. Clear node destructor ownership only after pruning entries. + surviving_models = {model_id for key, node in self.node_cache.items() if key not in targets + for model_id in getattr(node, "_mm_models", ())} + for key in targets: + cached = self.node_cache.get(key) + for model_id in getattr(cached, "_mm_models", ()): + if model_id not in surviving_models: + memory_manager.cache.pop(model_id, None) + if cached is not None and hasattr(cached, "_mm_models"): + cached._mm_models = [] + cached = None + for node in targets: + self.node_cache.pop(node, None) + if targets or released_components: + gc.collect() + self._best_effort_device_cache_clear() + self._best_effort_allocator_trim() + logger.debug(f"Removed {len(targets)} nodes from cache.") + return targets + async def delete_cache(self, request): data = await request.json() if not isinstance(data, dict): return web.json_response({"error": True, "message": "Cache deletion requires an object."}, status=400) + scope = data.get("scope", "all") + if scope not in ("all", "outputs"): + return web.json_response({"error": True, "message": "Cache scope must be all or outputs."}, status=400) nodes = data.get("nodes", []) if isinstance(nodes, str): nodes = nodes if nodes == "*" else [nodes] @@ -3311,15 +3387,25 @@ def release(): # Drop its exact length-prefixed runtime namespace as well. prefixes = tuple(f"block-v2-node:{len(node.encode('utf-16-le')) // 2}:{node}:" for node in targets) targets = list(dict.fromkeys([*targets, *(key for key in self.node_cache if key.startswith(prefixes))])) - released_components = self._release_node_modular_components(targets) - for node in targets: - self.node_cache.pop(node, None) - if released_components: - gc.collect() - self._best_effort_device_cache_clear() - self._best_effort_allocator_trim() - logger.debug(f"Removed {len(targets)} nodes from cache.") - return targets + if scope == "outputs": + # Preserve component lifetime, model identity tokens and offload + # hooks. Re-execution invalidates connected descendants through + # the ordinary executor on the next Run. Published media stays + # available until its producer successfully replaces it. + manager = getattr(sys.modules.get("modules.ModularDiffusers"), "components", None) + collections = getattr(manager, "collections", {}) + invalidated, retained = [], [] + for node_id in targets: + cached = self.node_cache.get(node_id) + if cached is None: + continue + if collections.get(node_id) or getattr(cached, "_mm_models", ()): + retained.append(node_id) + elif callable(getattr(cached, "invalidate_cache", None)): + cached.invalidate_cache() + invalidated.append(node_id) + return {"nodes": invalidated, "retainedModelNodes": retained} + return self._release_cached_nodes(targets) async def release_in_background(): self._node_cache_teardown_active = True @@ -3331,6 +3417,8 @@ async def release_in_background(): self._node_cache_teardown_active = False removed = await self._with_node_cache_lease(release_in_background) + if scope == "outputs": + return web.json_response({"error": False, "scope": scope, **removed}) return web.json_response({"error": False, "nodes": removed}) """ @@ -4370,18 +4458,58 @@ def _legacy_studio_preview_slots(self, outputs): } return slots - def _read_studio_output_state(self): - history_file = self._studio_history_file() - if not history_file.exists(): - return {"revision": 0, "previewSlots": {}, "outputs": []} + @staticmethod + def _studio_history_signature(path, info=None): + info = path.stat() if info is None else info + return (str(path), info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns, info.st_ctime_ns) + + def _cache_studio_output_state(self, signature, state, encoded_outputs=None): + # Keep compact immutable records, not a second retained Python object + # graph. Decode each record separately so large histories never monopolize + # the GIL in one native JSON decoder call. Readers own their copies. + task_indices = {} + for index, output in enumerate(state["outputs"]): + for task_id in self._studio_output_identity_values(output, "taskId", "backendExecutionId"): + task_indices.setdefault(task_id, []).append(index) + self._studio_history_read_cache = { + "taskIndices": {task_id: tuple(indices) for task_id, indices in task_indices.items()}, + "signature": signature, + "revision": state["revision"], + "previewSlots": deepcopy(state["previewSlots"]), + "outputs": tuple(encoded_outputs) if encoded_outputs is not None else tuple( + json.dumps(output, ensure_ascii=False).encode("utf-8") for output in state["outputs"] + ), + } - try: - with open(history_file, "r", encoding="utf-8") as f: - payload = json.load(f) - except (json.JSONDecodeError, UnicodeDecodeError, OSError) as e: - logger.error(f"Error reading Studio output history: {e}") - return {"revision": 0, "previewSlots": {}, "outputs": []} + def _read_studio_output_state(self): + with self.studio_history_file_lock: + history_file = self._studio_history_file() + if not history_file.exists(): + self._studio_history_read_cache = None + return {"revision": 0, "previewSlots": {}, "outputs": []} + try: + signature = self._studio_history_signature(history_file) + cached = self._studio_history_read_cache + if cached is not None and cached["signature"] == signature: + return { + "revision": cached["revision"], + "previewSlots": deepcopy(cached["previewSlots"]), + "outputs": [json.loads(record) for record in cached["outputs"]], + } + self._studio_history_read_cache = None + with open(history_file, "r", encoding="utf-8") as f: + signature = self._studio_history_signature(history_file, os.fstat(f.fileno())) + payload = json.load(f) + except (json.JSONDecodeError, UnicodeDecodeError, OSError) as e: + logger.error(f"Error reading Studio output history: {e}") + return {"revision": 0, "previewSlots": {}, "outputs": []} + + state = self._normalize_studio_output_state(payload) + self._cache_studio_output_state(signature, state) + return state + + def _normalize_studio_output_state(self, payload): if isinstance(payload, list): outputs = payload version = 1 @@ -4410,10 +4538,31 @@ def _read_studio_output_state(self): slots = self._legacy_studio_preview_slots(outputs) return {"revision": revision, "previewSlots": slots, "outputs": outputs} - def _read_studio_outputs(self): - return self._read_studio_output_state()["outputs"] + def _read_studio_outputs(self, *, task_id=None): + if task_id is None: + return self._read_studio_output_state()["outputs"] + with self.studio_history_file_lock: + cached = self._studio_history_read_cache + try: + signature = self._studio_history_signature(self._studio_history_file()) + except OSError: + signature = None + if cached is not None and cached["signature"] == signature: + # The index selects candidates, not trusted identities. The run + # reader still rejects conflicting task/client provenance. + return [json.loads(cached["outputs"][index]) + for index in cached["taskIndices"].get(task_id, ())] + # Cold reads and external replacements use the same normalization, + # invalidation and error handling as every other history reader. + outputs = self._read_studio_output_state()["outputs"] + return [output for output in outputs + if task_id in self._studio_output_identity_values(output, "taskId", "backendExecutionId")] def _write_studio_output_state(self, outputs, preview_slots, *, revision=None): + with self.studio_history_file_lock: + return self._write_studio_output_state_locked(outputs, preview_slots, revision=revision) + + def _write_studio_output_state_locked(self, outputs, preview_slots, *, revision=None): history_file = self._studio_history_file() history_file.parent.mkdir(parents=True, exist_ok=True) current_ids = { @@ -4436,10 +4585,41 @@ def _write_studio_output_state(self, outputs, preview_slots, *, revision=None): "previewSlots": preview_slots, "outputs": bounded_outputs, } + encoded_outputs = [] temp_file = history_file.with_suffix(".tmp") with open(temp_file, "w", encoding="utf-8") as f: - json.dump(payload, f, ensure_ascii=False) + # Encode each output with the accelerated encoder. json.dump walks + # nested workflow snapshots token by token in Python while holding + # the history lock. Encoding the entire history at once would instead + # allocate a second history-sized string and hold the GIL throughout. + f.write("{") + for index, (key, value) in enumerate(payload.items()): + if index: + f.write(", ") + f.write(json.dumps(key) + ": ") + if key == "outputs": + f.write("[") + for output_index, output in enumerate(value): + if output_index: + f.write(", ") + encoded = json.dumps(output, ensure_ascii=False) + f.write(encoded) + encoded_outputs.append(encoded.encode("utf-8")) + f.write("]") + else: + f.write(json.dumps(value, ensure_ascii=False)) + f.write("}") + written = temp_file.stat() temp_file.replace(history_file) + signature = self._studio_history_signature(history_file) + if signature[1:5] == (written.st_dev, written.st_ino, written.st_size, written.st_mtime_ns): + self._cache_studio_output_state( + signature, self._normalize_studio_output_state(payload), encoded_outputs, + ) + else: + # An external atomic replacement won the race; never cache our + # old content under the replacement's filesystem identity. + self._studio_history_read_cache = None return bounded_outputs def _write_studio_outputs(self, outputs): @@ -5092,16 +5272,33 @@ def _studio_outputs_response_bytes(self, limit): if output_id in current_ids and output_id not in response_ids: response_outputs.append(output) response_ids.add(output_id) - return self._json_response_bytes( - { - "error": False, - "count": len(outputs), - "outputs": response_outputs, - "previewSlots": list(state["previewSlots"].values()), - "revision": state["revision"], - "path": str(self._studio_history_file()), - } - ) + payload = { + "error": False, + "count": len(outputs), + "outputs": response_outputs, + "previewSlots": list(state["previewSlots"].values()), + "revision": state["revision"], + "path": str(self._studio_history_file()), + } + # Current previews can extend past the history limit. Encode their + # records individually, just as persistence does, so one native encoder + # call cannot hold the GIL across the entire retained collection. + parts = [b"{"] + for index, (key, value) in enumerate(payload.items()): + if index: + parts.append(b",") + parts.extend((self._json_response_bytes(key), b":")) + if key == "outputs": + parts.append(b"[") + for output_index, output in enumerate(value): + if output_index: + parts.append(b",") + parts.append(self._json_response_bytes(output)) + parts.append(b"]") + else: + parts.append(self._json_response_bytes(value)) + parts.append(b"}") + return b"".join(parts) async def _run_studio_history_mutation(self, builder, *args): """Serialize whole file transactions without blocking the HTTP loop. @@ -5264,15 +5461,19 @@ def _studio_outputs_delete(self, output_id): async def studio_blocks_get(self, request): limit = min(max(int(request.query.get("limit", 200)), 1), 500) - blocks = self._list_studio_blocks() - return web.json_response( - { - "error": False, - "count": len(blocks), - "blocks": blocks[:limit], - "path": str(self._studio_blocks_dir()), - } + body = await self._coalesced_control_response( + ("studio_blocks", limit), lambda: self._studio_blocks_response_bytes(limit) ) + return web.Response(body=body, content_type="application/json") + + def _studio_blocks_response_bytes(self, limit): + blocks = self._list_studio_blocks() + return self._json_response_bytes({ + "error": False, + "count": len(blocks), + "blocks": blocks[:limit], + "path": str(self._studio_blocks_dir()), + }) async def composite_migration_preview(self, request): from modiff.composite_migration import scan_composite_migration_preview @@ -6488,6 +6689,9 @@ def _runtime_fingerprint(self, *, hardware_snapshot=None): resource_identity = { **returned_payload, "torch": resource_torch_identity, + # Earlier graph receipts could retain only the final node's peak. + # Keep those historical proofs, but never reuse their resource key. + "peakMeasurementVersion": 2, } fingerprint = hashlib.sha256( json.dumps(execution_identity, sort_keys=True, default=str).encode("utf-8") @@ -8796,6 +9000,8 @@ def _record_auto_resource_failure(self, error, classification=None): def _reset_runtime_measurement(self): """Reset accelerator peak counters immediately before one graph attempt.""" + self._runtime_memory_peaks = {} + self._runtime_memory_peak_error = None try: torch = import_module("torch") if bool(torch.cuda.is_available()): @@ -8812,10 +9018,40 @@ def _reset_runtime_measurement(self): if callable(reset): reset(index) except Exception as exc: + self._runtime_memory_peak_error = str(exc) logger.debug(f"Could not reset runtime memory counters: {exc}") + def _reset_node_runtime_measurement(self): + """Keep graph high-water marks before resetting the next node's counters. + + Called only on the serial model worker, never by active-run telemetry. + Per-node UI measurements and graph-attempt resource proof share Torch's + counters; a small final Preview must not erase an earlier denoiser peak. + """ + peaks = getattr(self, "_runtime_memory_peaks", None) + if isinstance(peaks, dict): + try: + torch = import_module("torch") + for kind in ("cuda", "xpu"): + runtime = getattr(torch, kind, None) + if not runtime or not bool(getattr(runtime, "is_available", lambda: False)()): + continue + for index in range(int(runtime.device_count())): + device = peaks.setdefault(f"{kind}:{index}", {}) + for key, method in ( + ("peakAllocatedBytes", "max_memory_allocated"), + ("peakReservedBytes", "max_memory_reserved"), + ): + read_peak = getattr(runtime, method, None) + if callable(read_peak): + device[key] = max(device.get(key, 0), int(read_peak(index))) + except Exception as exc: + self._runtime_memory_peak_error = str(exc) + logger.debug(f"Could not preserve graph memory counters: {exc}") + reset_memory_stats() + def _runtime_measurement(self, *, elapsed_seconds): - measurement = {"elapsedSeconds": max(0.0, float(elapsed_seconds))} + measurement = {"elapsedSeconds": max(0.0, float(elapsed_seconds)), "peakMeasurementVersion": 2} try: torch = import_module("torch") if bool(torch.cuda.is_available()): @@ -8857,6 +9093,18 @@ def _runtime_measurement(self, *, elapsed_seconds): measurement.update({"backend": "cpu", "device": "cpu:0"}) except Exception as exc: measurement["acceleratorMeasurementError"] = str(exc) + peaks = getattr(self, "_runtime_memory_peaks", {}) + device_peaks = peaks.get(measurement.get("device"), {}) if isinstance(peaks, dict) else {} + for key in ("peakAllocatedBytes", "peakReservedBytes"): + if key in device_peaks: + measurement[key] = max(measurement.get(key, 0), device_peaks[key]) + peak_error = getattr(self, "_runtime_memory_peak_error", None) or measurement.get("acceleratorMeasurementError") + if peak_error: + # Missing a boundary makes the graph high-water mark unknown, not + # safely equal to the lower final-node observation. + measurement.pop("peakAllocatedBytes", None) + measurement.pop("peakReservedBytes", None) + measurement["acceleratorMeasurementError"] = peak_error try: import psutil @@ -8865,6 +9113,38 @@ def _runtime_measurement(self, *, elapsed_seconds): pass return measurement + @staticmethod + def _release_exception_frames(error): + """Keep diagnostics/types but release tensors held by completed frames. + + Futures and exception chains can outlive a failed attempt. Empty model + registries alone cannot release locals retained by their tracebacks. + Call on the model worker after formatting the diagnostic traceback. + """ + pending, seen = [error], set() + while pending: + current = pending.pop() + if current is None or id(current) in seen: + continue + seen.add(id(current)) + pending.extend((current.__cause__, current.__context__)) + if isinstance(current, BaseExceptionGroup): + pending.extend(current.exceptions) + if current.__traceback__ is not None: + frame_trace = current.__traceback__ + while frame_trace is not None: + frame = frame_trace.tb_frame + # The async queue worker is suspended awaiting this very + # cleanup. Clearing a suspended coroutine closes it; only + # finished ordinary model-call frames may be cleared here. + if not frame.f_code.co_flags & (inspect.CO_COROUTINE | inspect.CO_ASYNC_GENERATOR | inspect.CO_GENERATOR): + try: + frame.clear() + except RuntimeError: # A synchronous retry's caller is still executing. + pass + frame_trace = frame_trace.tb_next + current.__traceback__ = None + def _release_runtime_caches_for_retry(self): errors = [] released = { @@ -8955,6 +9235,7 @@ def _auto_candidate_cache_signature(runtime_hints): # Those resident objects are not interchangeable. "loaderContract": runtime_hints.get("loaderContract"), "dtype": candidate.get("dtype") or runtime_hints.get("dtype"), + "modelRevision": candidate.get("revision") or runtime_hints.get("modelRevision"), "quantizationMode": (candidate.get("quantizationMode") or runtime_hints.get("quantizationMode")), "quantizedComponents": sorted( str(item) @@ -9020,7 +9301,16 @@ def _runtime_cleanup_hints_for_graph(self, nodes, runtime_hints): for node in nodes.values() if isinstance(node, dict) and node.get("action") in loader_actions ] - if len(loaders) != 1: + if not loaders: + return hints or None + if len(loaders) > 1: + # Identical independent owners do not change the resident recipe. + # Keep their common identity when one is later removed; otherwise + # a multi-owner run records an empty identity and forces a needless + # process-wide reload of the surviving owner on the next run. + derived = [self._runtime_cleanup_hints_for_graph({"loader": loader}, hints) for loader in loaders] + if len({self._auto_candidate_cache_signature(item) for item in derived}) == 1: + return derived[0] return hints or None loader = loaders[0] params = loader.get("params") if isinstance(loader.get("params"), dict) else {} @@ -9039,6 +9329,7 @@ def _runtime_cleanup_hints_for_graph(self, nodes, runtime_hints): "pretrained_model_name_or_path", ) dtype = self._graph_loader_param_value(params, "dtype", "torch_dtype") + revision = self._graph_loader_param_value(params, "revision") offload_mode = self._graph_loader_param_value(params, "offload_mode") device_map = self._graph_loader_param_value(params, "device_map") if not hints.get("resourceMode"): @@ -9055,6 +9346,8 @@ def _runtime_cleanup_hints_for_graph(self, nodes, runtime_hints): hints["modelRepo"] = str(artifact) if dtype and not hints.get("dtype"): hints["dtype"] = str(dtype) + if revision and not hints.get("modelRevision"): + hints["modelRevision"] = str(revision) if offload_mode and not hints.get("offloadMode"): hints["offloadMode"] = str(offload_mode) if device_map and not hints.get("deviceMap"): @@ -9753,6 +10046,8 @@ def execute_graph(self, graph): self._restore_execution_process_state(process_state, graph) def _execute_graph(self, graph): + if "servicePackage" in graph: + self._validate_service_graph(graph) sid = graph["sid"] nodes = graph["nodes"] # API paths share ancestor prefixes. Visit each outer graph node once @@ -10165,6 +10460,7 @@ def _execute_graph(self, graph): } ) self.queue_message(cleanup_progress) + self._release_exception_frames(e) cleanup = self._release_runtime_caches_for_retry() self.queue_message( { @@ -10525,6 +10821,13 @@ def _adopt_reusable_loader_node(self, node_id, module, action): prepare_for_reuse = getattr(cached_node, "prepare_for_workflow_reuse", None) if callable(prepare_for_reuse): prepare_for_reuse() + rebind_owner = getattr(cached_node, "rebind_cache_owner", None) + if callable(rebind_owner): + rebind_owner(node_id) + for dependent in self.node_cache.values(): + sources = getattr(dependent, "_cache_input_sources", ()) + if cached_id in sources: + dependent._cache_input_sources = frozenset(node_id if source == cached_id else source for source in sources) self.node_cache.pop(cached_id, None) cached_node.node_id = node_id self.node_cache[node_id] = cached_node @@ -10542,6 +10845,8 @@ def execute_node(self, id, node, sid, quiet=False, param_overrides=None): if action not in self.modules[module]: raise ValueError(f"Invalid action: {action}") + self._require_custom_module_enabled(module) + # get the arguments values args = {} ui_fields = {} @@ -10616,7 +10921,7 @@ def execute_node(self, id, node, sid, quiet=False, param_overrides=None): ) if not quiet: - reset_memory_stats() + self._reset_node_runtime_measurement() start_time = time.time() phase = node_execution_phase(module, action) @@ -10643,9 +10948,11 @@ def execute_node(self, id, node, sid, quiet=False, param_overrides=None): # through a cached instance whose executable identity no longer # matches the current graph. cached_node = self.node_cache.get(id) + loaded_action = getattr(sys.modules.get(f"{module}.main"), action, None) if cached_node is not None and ( getattr(cached_node, "module_name", None) != module or getattr(cached_node, "class_name", None) != action + or (isinstance(loaded_action, type) and type(cached_node) is not loaded_action) ): self.node_cache.pop(id, None) @@ -10677,6 +10984,10 @@ def execute_node(self, id, node, sid, quiet=False, param_overrides=None): # set the session id, it can be used to send messages from the node back to the client self.node_cache[id]._sid = sid + self.node_cache[id]._cache_input_sources = frozenset( + field["sourceId"] for key, field in params.items() + if field.get("sourceId") and key in args and key not in (param_overrides or {}) + ) if upstream_changed: invalidate_cache = getattr(self.node_cache[id], "invalidate_cache", None) if callable(invalidate_cache): @@ -10848,6 +11159,14 @@ def publish_node_heartbeat(): "progress": 100, "current_node": id, "hasChanged": self.node_cache[id]._has_changed, + "message": { + "empty": "Computed: no reusable output was available.", + "invalidated": "Recomputed: output was invalidated by an upstream change or recompute request.", + "inputs_changed": "Recomputed: node inputs changed.", + "implementation_changed": "Recomputed: loaded node implementation changed.", + "usage_changed": "Resident object reused; changed usage settings invalidate dependent outputs.", + "unchanged_inputs": "Cached result reused: node inputs are unchanged.", + }.get(getattr(self.node_cache[id], "_cache_reason", None), "Node completed."), "executionTime": self.node_cache[id]._execution_time, "memoryUsage": self.node_cache[id]._memory_usage, } @@ -11505,6 +11824,7 @@ def _reserve_worker_runtime_gate(self, kind, identifier): or self.current_task or self.queued_tasks or self._active_nonruntime_mutations + or self._field_metadata_lock.locked() or self.hf_download_tasks or (self.template_gallery_install_task is not None and not self.template_gallery_install_task.done()) ): @@ -11524,15 +11844,15 @@ def _release_worker_runtime_gate(self, token): if isinstance(gate, dict) and gate.get("token") == token: self._runtime_mutation_gate = None - async def _strict_runtime_control_json(self, request, *, allowed, required=(), allow_empty=False): + async def _strict_runtime_control_json(self, request, *, allowed, required=(), allow_empty=False, max_bytes=4096): content_length = getattr(request, "content_length", None) if content_length is not None and ( not isinstance(content_length, int) or isinstance(content_length, bool) or content_length < 0 - or content_length > 4096 + or content_length > max_bytes ): - raise ValueError("Runtime control request exceeds 4096 bytes.") + raise ValueError(f"Runtime control request exceeds {max_bytes} bytes.") def reject_duplicates(pairs): value = {} @@ -11547,18 +11867,18 @@ def reject_duplicates(pairs): chunks = [] total = 0 while not content.at_eof(): - chunk = await content.read(min(4097 - total, 4097)) + chunk = await content.read(min(max_bytes + 1 - total, 65536)) if not chunk: break chunks.append(chunk) total += len(chunk) - if total > 4096: - raise ValueError("Runtime control request exceeds 4096 bytes.") + if total > max_bytes: + raise ValueError(f"Runtime control request exceeds {max_bytes} bytes.") raw = b"".join(chunks) elif hasattr(request, "read"): raw = await request.read() - if len(raw) > 4096: - raise ValueError("Runtime control request exceeds 4096 bytes.") + if len(raw) > max_bytes: + raise ValueError(f"Runtime control request exceeds {max_bytes} bytes.") else: raw = None if raw is not None: @@ -13175,6 +13495,16 @@ def _best_effort_allocator_trim(self): logger.debug("malloc_trim failed during accelerator cleanup", exc_info=True) return False, [f"malloc_trim: {e}"] + def _dependent_cache_nodes(self, node_ids): + """Include cached consumers retaining pipelines, tensors or adapter state.""" + targets = set(node_ids) + while True: + added = {key for key, node in self.node_cache.items() + if key not in targets and targets.intersection(getattr(node, "_cache_input_sources", ())) } + if not added: + return list(dict.fromkeys([*node_ids, *(key for key in self.node_cache if key in targets)])) + targets.update(added) + def _release_node_modular_components(self, node_ids): """Destroy unshared ownership without offloading soon-to-be-dead models. @@ -13303,7 +13633,7 @@ async def runtime_gpu_cleanup(self, request): status=409, ) - return await self._with_node_cache_lease(lambda: asyncio.to_thread(self._runtime_gpu_cleanup_idle)) + return await self._with_node_cache_lease(lambda: self._run_executor_callback(self._runtime_gpu_cleanup_idle)) def _runtime_gpu_cleanup_idle(self): before = self._cuda_memory_snapshot() @@ -13378,6 +13708,14 @@ def _runtime_gpu_cleanup_idle(self): async def huggingface_node_library(self, _request): return web.json_response(await asyncio.to_thread(reviewed_huggingface_node_library)) + async def huggingface_registered_block_interfaces(self, _request): + try: + interfaces = await asyncio.to_thread(registered_block_v2_interfaces) + except ValueError as exc: + logger.exception("Could not validate the registered Block interfaces") + return web.json_response({"error": True, "message": str(exc)}, status=500) + return web.json_response(interfaces) + async def huggingface_registered_block_v2(self, request): definition_id = request.query.get("definition_id", "") admission_id = request.query.get("admission_id", "") @@ -13480,6 +13818,8 @@ async def huggingface_cluster_auto_authority(self, request): def _build_model_capabilities_payload(self, query=""): from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES + from modiff.operation_contracts import OPERATION_CONTRACT_SCHEMA_VERSION + from modiff.operation_catalog import build_operation_catalog from modiff.optional_runtime_execution import optional_runtime_requirement_for_profiles optional_runtime_catalog_snapshot = None @@ -13615,6 +13955,9 @@ def request_optional_runtime_catalog(): specification["mode"] for specification in capability["studioExecutionSpecs"] ) capabilities.append(capability) + operation_contracts, pipeline_support = build_operation_catalog( + self.modules, published_execution_profiles, catalog_resolver=request_optional_runtime_catalog, + ) task_template_contracts = build_task_template_contracts(capabilities, execution_specs) task_contracts_by_model = {} for contract in task_template_contracts: @@ -13635,6 +13978,17 @@ def request_optional_runtime_catalog(): or query in capability.get("defaultRepo", "").lower() ] returned_models = {capability["modelType"] for capability in capabilities} + returned_pipelines = returned_models | { + pipeline for capability in capabilities for pipeline in capability["pipelineClasses"] + } + operation_contracts = [ + contract for contract in operation_contracts + if contract["pipelineClass"] in returned_pipelines or query in contract["pipelineClass"].lower() + ] + pipeline_support = [ + item for item in pipeline_support + if item["pipelineClass"] in returned_pipelines or query in item["pipelineClass"].lower() + ] task_template_contracts = [ contract for contract in task_template_contracts if contract["modelType"] in returned_models ] @@ -13646,6 +14000,10 @@ def request_optional_runtime_catalog(): "capabilities": capabilities, "taskTemplateContractSchemaVersion": TASK_TEMPLATE_CONTRACT_SCHEMA_VERSION, "taskTemplateContracts": task_template_contracts, + "operationContractSchemaVersion": OPERATION_CONTRACT_SCHEMA_VERSION, + "operationContracts": operation_contracts, + "pipelineSupportSchemaVersion": 1, + "pipelineSupport": pipeline_support, "diffusersExecutionProfiles": published_execution_profiles, "studioExecutionSpecs": execution_specs, "optionalRuntimeProfiles": public_optional_runtime_profiles(), @@ -13690,6 +14048,98 @@ async def model_capabilities(self, request): body = await self._model_capabilities_response(query) return web.Response(body=body, content_type="application/json") + async def resolve_operation(self, request): + """Read-only authoring schema over the same ordinary registered actions.""" + from modiff.operation_catalog import resolve_operation + + try: + selection = await request.json() + catalog_bytes = await self._model_capabilities_response("") + + def describe(): + contracts = json.loads(catalog_bytes)["operationContracts"] + resolved = resolve_operation(self.modules, contracts, selection) + return { + "schemaVersion": 1, + "operation": resolved["operation"], + "node": self._describe_registered_node(resolved["module"], resolved["action"], resolved), + } + + payload = await asyncio.to_thread(describe) + except ValueError as exc: + return web.json_response({"error": str(exc)}, status=400) + return web.json_response(payload) + + async def resolve_operation_starter(self, request): + """Describe ordinary nodes and reviewed wires; never execute or install.""" + from modiff.operation_starters import resolve_operation_starter + + try: + selection = await request.json() + catalog_bytes = await self._model_capabilities_response("") + + def describe(): + contracts = json.loads(catalog_bytes)["operationContracts"] + payload = resolve_operation_starter(self.modules, contracts, selection) + payload["nodes"] = [ + {"operation": node["operation"], + "node": self._describe_registered_node(node["module"], node["action"], node)} + for node in payload["nodes"] + ] + return payload + + payload = await asyncio.to_thread(describe) + except ValueError as exc: + return web.json_response({"error": str(exc)}, status=400) + return web.json_response(payload) + + async def resolve_task_starter(self, request): + """Task-first authoring; only inspect existing artifacts and memory recipes.""" + from modiff.task_authoring import resolve_task_starter + from modiff.auto_resource import artifact_revision_cache_status + from modiff.model_artifact_catalog import require_catalog_revision + from modiff.workflow_auto_resource import build_workflow_auto_plan + + try: + selection = await request.json() + catalog_bytes = await self._model_capabilities_response("") + + def describe(): + local_models = get_local_models() + fingerprint = self._auto_planning_runtime_fingerprint() + artifacts = {} + + def installed(profile): + repo = profile["default_repo"] + try: + revision = require_catalog_revision(repo, model_type=profile["model_type"]) + except ValueError: + return False + key = (repo, revision) + if key not in artifacts: + artifacts[key] = artifact_revision_cache_status(repo, revision, local_models) + return artifacts[key].get("complete") is True + + def inspect(graph): + return build_workflow_auto_plan( + graph, runtime_fingerprint=fingerprint, local_models=local_models, data_dir=self.data_dir, + ) + + result = resolve_task_starter( + self.modules, json.loads(catalog_bytes), selection, + installed=installed, inspect_resources=inspect, + ) + result["starter"]["nodes"] = [ + {"operation": node["operation"], + "node": self._describe_registered_node(node["module"], node["action"], node)} + for node in result["starter"]["nodes"] + ] + return result + + return web.json_response(await asyncio.to_thread(describe)) + except ValueError as exc: + return web.json_response({"error": str(exc)}, status=400) + def _auto_resource_runtime_block(self): cached = ( self._last_runtime_fingerprint.get("hardware") @@ -13761,6 +14211,7 @@ def _auto_planning_runtime_fingerprint(self): remains unavailable, so real external pressure still selects a safer plan. """ + cached_identity = isinstance(self._last_runtime_fingerprint, dict) fingerprint = self._runtime_fingerprint_for_control_request() # During an active model call the cached fingerprint was captured # immediately before execution and already describes the capacity that @@ -13769,6 +14220,27 @@ def _auto_planning_runtime_fingerprint(self): # weights, and a refresh-time planning request must remain responsive. if self.current_task: return fingerprint + if cached_identity and isinstance(fingerprint.get("hardware"), dict): + # Runtime identity is cached, but host capacity changes after model + # release and with external pressure. An old RAM sample can reject + # every subsequent Run even after enough memory becomes available. + # Refresh only the inexpensive OS sample; never probe accelerators + # here or rewrite the fingerprint used by execution receipts. + from modiff.hardware import system_memory_snapshot + + try: + memory = system_memory_snapshot() + except Exception: + memory = {} + system = fingerprint["hardware"].setdefault("system", {}) + if isinstance(system, dict): + for field, source in ( + ("ram_total", "total_bytes"), + ("ram_free", "free_bytes"), + ("ram_available", "available_bytes"), + ): + value = memory.get(source) + system[field] = value if type(value) is int and value >= 0 else None if not (self.node_cache or memory_manager.cache): return fingerprint @@ -14219,319 +14691,6 @@ async def model_cache_diagnostics(self, request): raise RuntimeError("Model cache diagnostics did not return a JSON object.") return web.json_response(diagnostics) - def _custom_modules_root(self): - root = Path("custom").resolve() - root.mkdir(parents=True, exist_ok=True) - return root - - def _disabled_custom_modules_root(self): - root = (self._custom_modules_root() / ".disabled").resolve() - root.mkdir(parents=True, exist_ok=True) - return root - - def _safe_custom_module_name(self, value): - name = str(value or "").strip() - if not name: - raise ValueError("Module name is required.") - if not re.match(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$", name): - raise ValueError( - "Module name may only contain letters, numbers, dot, underscore, and dash, and must not start with a dot." - ) - return name - - def _derive_custom_module_name(self, source): - source_text = str(source or "").strip().rstrip("/\\") - if not source_text: - raise ValueError("Module source is required.") - source_text = source_text[:-4] if source_text.endswith(".git") else source_text - name = re.split(r"[/\\:]", source_text)[-1] - name = re.sub(r"[^A-Za-z0-9_.-]+", "-", name).strip(".-") - return self._safe_custom_module_name(name) - - def _custom_module_path(self, name, disabled=False): - safe_name = self._safe_custom_module_name(name) - root = self._disabled_custom_modules_root() if disabled else self._custom_modules_root() - target = (root / safe_name).resolve() - if target.parent != root: - raise ValueError("Resolved custom module path escaped the custom module directory.") - return target - - def _is_git_source(self, source): - source = str(source or "").strip().lower() - return source.startswith(("https://", "http://", "ssh://", "git@")) or source.endswith(".git") - - def _run_git(self, args, cwd=None, timeout=300): - git_bin = shutil.which("git") - if not git_bin: - raise RuntimeError("git is not available in the MoDiff backend environment.") - - completed = subprocess.run( - [git_bin, *args], - cwd=str(cwd) if cwd else None, - capture_output=True, - text=True, - timeout=timeout, - shell=False, - ) - result = { - "returncode": completed.returncode, - "stdout": completed.stdout.strip(), - "stderr": completed.stderr.strip(), - } - if completed.returncode != 0: - message = result["stderr"] or result["stdout"] or f"git exited with {completed.returncode}" - raise RuntimeError(message) - return result - - def _git_value(self, module_path, args): - try: - return self._run_git(args, cwd=module_path, timeout=10).get("stdout", "") - except Exception: - return "" - - def _custom_module_git_info(self, module_path): - is_git = bool(self._git_value(module_path, ["rev-parse", "--is-inside-work-tree"])) - if not is_git: - return { - "hasGit": False, - "canUpdate": False, - } - return { - "hasGit": True, - "canUpdate": True, - "remote": self._git_value(module_path, ["config", "--get", "remote.origin.url"]), - "branch": self._git_value(module_path, ["rev-parse", "--abbrev-ref", "HEAD"]), - "commit": self._git_value(module_path, ["rev-parse", "--short", "HEAD"]), - } - - def _custom_module_info(self, name, module_path, enabled=True): - module_key = f"custom.{name}" - node_actions = sorted((self.modules.get(module_key) or {}).keys()) if enabled else [] - git_info = self._custom_module_git_info(module_path) - return { - "name": name, - "moduleKey": module_key, - "source": "custom", - "enabled": enabled, - "status": "enabled" if enabled else "disabled", - "path": str(module_path), - "hasInit": (module_path / "__init__.py").exists(), - "hasMain": (module_path / "main.py").exists(), - "nodeCount": len(node_actions), - "nodes": node_actions, - "canDisable": enabled, - "canEnable": not enabled, - **git_info, - } - - def _list_custom_modules(self): - root = self._custom_modules_root() - disabled_root = self._disabled_custom_modules_root() - modules = [] - - for entry in sorted(root.iterdir(), key=lambda item: item.name.lower()): - if not entry.is_dir() or entry.name.startswith(".") or entry.name == "__pycache__": - continue - modules.append(self._custom_module_info(entry.name, entry, enabled=True)) - - for entry in sorted(disabled_root.iterdir(), key=lambda item: item.name.lower()): - if not entry.is_dir() or entry.name.startswith(".") or entry.name == "__pycache__": - continue - modules.append(self._custom_module_info(entry.name, entry, enabled=False)) - - return modules - - def _refresh_custom_module_registry(self): - for key in list(MODULE_MAP.keys()): - if key.startswith("custom."): - MODULE_MAP.pop(key, None) - - for key in list(sys.modules.keys()): - if key == "custom" or key.startswith("custom."): - sys.modules.pop(key, None) - - invalidate_caches() - custom_root = self._custom_modules_root() - if custom_root.exists(): - parse_module_map("custom") - - self.modules = MODULE_MAP - self.instance = nanoid.generate(size=10) - return self._list_custom_modules() - - def _prune_custom_node_cache(self, module_key=None): - removed = [] - for node_id, cached_node in list(self.node_cache.items()): - cached_module = getattr(cached_node, "module_name", "") - if module_key is None: - should_remove = str(cached_module).startswith("custom.") - else: - should_remove = cached_module == module_key - if should_remove: - self.node_cache.pop(node_id, None) - removed.append(node_id) - return removed - - def _custom_modules_payload(self): - modules = self._list_custom_modules() - return { - "error": False, - "root": str(self._custom_modules_root()), - "disabledRoot": str(self._disabled_custom_modules_root()), - "count": len(modules), - "modules": modules, - } - - async def custom_modules_list(self, request): - return web.json_response(self._custom_modules_payload()) - - async def custom_modules_refresh(self, request): - self._prune_custom_node_cache() - modules = self._refresh_custom_module_registry() - return web.json_response( - { - "error": False, - "message": "Custom module registry refreshed.", - "instance": self.instance, - "count": len(modules), - "modules": modules, - } - ) - - async def custom_modules_install(self, request): - try: - data = await request.json() - source = str(data.get("source") or data.get("url") or "").strip() - name = self._safe_custom_module_name(data.get("name") or self._derive_custom_module_name(source)) - target = self._custom_module_path(name) - disabled_target = self._custom_module_path(name, disabled=True) - - if target.exists() or disabled_target.exists(): - return web.json_response( - {"error": True, "message": f"Custom module `{name}` already exists."}, status=409 - ) - - if self._is_git_source(source): - self._run_git(["clone", source, str(target)], timeout=900) - else: - source_path = Path(source).expanduser().resolve() - if not source_path.is_dir(): - return web.json_response( - {"error": True, "message": "Source must be a Git URL or an existing local directory."}, - status=400, - ) - shutil.copytree( - source_path, target, ignore=shutil.ignore_patterns("__pycache__", ".pytest_cache", ".mypy_cache") - ) - - modules = self._refresh_custom_module_registry() - return web.json_response( - { - "error": False, - "message": f"Custom module `{name}` installed.", - "module": next((item for item in modules if item["name"] == name), None), - "modules": modules, - "instance": self.instance, - } - ) - except Exception as e: - logger.error(f"Error installing custom module: {e}", exc_info=True) - return web.json_response({"error": True, "message": str(e)}, status=500) - - async def custom_modules_update(self, request): - try: - name = self._safe_custom_module_name(request.match_info.get("name")) - enabled_path = self._custom_module_path(name) - disabled_path = self._custom_module_path(name, disabled=True) - module_path = enabled_path if enabled_path.exists() else disabled_path - if not module_path.exists(): - return web.json_response( - {"error": True, "message": f"Custom module `{name}` was not found."}, status=404 - ) - - if not (module_path / ".git").exists(): - return web.json_response( - {"error": True, "message": f"Custom module `{name}` is not a Git checkout."}, status=400 - ) - - git_result = self._run_git(["pull", "--ff-only"], cwd=module_path, timeout=900) - removed_cache_nodes = self._prune_custom_node_cache(f"custom.{name}") - modules = self._refresh_custom_module_registry() if enabled_path.exists() else self._list_custom_modules() - return web.json_response( - { - "error": False, - "message": f"Custom module `{name}` updated.", - "git": git_result, - "removedCacheNodes": removed_cache_nodes, - "module": next((item for item in modules if item["name"] == name), None), - "modules": modules, - "instance": self.instance, - } - ) - except Exception as e: - logger.error(f"Error updating custom module: {e}", exc_info=True) - return web.json_response({"error": True, "message": str(e)}, status=500) - - async def custom_modules_disable(self, request): - try: - name = self._safe_custom_module_name(request.match_info.get("name")) - source = self._custom_module_path(name) - target = self._custom_module_path(name, disabled=True) - if not source.exists(): - return web.json_response( - {"error": True, "message": f"Custom module `{name}` is not enabled."}, status=404 - ) - if target.exists(): - return web.json_response( - {"error": True, "message": f"Disabled custom module `{name}` already exists."}, status=409 - ) - - shutil.move(str(source), str(target)) - removed_cache_nodes = self._prune_custom_node_cache(f"custom.{name}") - modules = self._refresh_custom_module_registry() - return web.json_response( - { - "error": False, - "message": f"Custom module `{name}` disabled.", - "removedCacheNodes": removed_cache_nodes, - "module": next((item for item in modules if item["name"] == name), None), - "modules": modules, - "instance": self.instance, - } - ) - except Exception as e: - logger.error(f"Error disabling custom module: {e}", exc_info=True) - return web.json_response({"error": True, "message": str(e)}, status=500) - - async def custom_modules_enable(self, request): - try: - name = self._safe_custom_module_name(request.match_info.get("name")) - source = self._custom_module_path(name, disabled=True) - target = self._custom_module_path(name) - if not source.exists(): - return web.json_response( - {"error": True, "message": f"Custom module `{name}` is not disabled."}, status=404 - ) - if target.exists(): - return web.json_response( - {"error": True, "message": f"Enabled custom module `{name}` already exists."}, status=409 - ) - - shutil.move(str(source), str(target)) - modules = self._refresh_custom_module_registry() - return web.json_response( - { - "error": False, - "message": f"Custom module `{name}` enabled.", - "module": next((item for item in modules if item["name"] == name), None), - "modules": modules, - "instance": self.instance, - } - ) - except Exception as e: - logger.error(f"Error enabling custom module: {e}", exc_info=True) - return web.json_response({"error": True, "message": str(e)}, status=500) - @staticmethod def _template_gallery_error_response(error, *, plan=None): status = 409 @@ -15298,6 +15457,7 @@ async def _run_reserved_hf_download(self, repo_id, entry, progress_cb): entry.get("revision"), ), serialize_model_io=True, + model_work=False, ) if result: await self._refresh_model_indexes() @@ -15602,12 +15762,23 @@ async def websocket(self, request): except Exception as e: logger.error(f"[Websocket] Error: {e}") finally: - if sid in self.ws_sessions: - del self.ws_sessions[sid] + if self.ws_sessions.get(sid) is ws: + self.ws_sessions.pop(sid, None) + self._cancel_session_signal_requests(sid) logger.debug(f"Websocket connection closed: {sid}") return ws + def _cancel_session_signal_requests(self, sid): + """Release schema callbacks when their browser leaves; never release another owner.""" + for request_id, owner in list(self.pending_ws_request_sessions.items()): + if owner != sid: + continue + self.pending_ws_request_sessions.pop(request_id, None) + future = self.pending_ws_requests.pop(request_id, None) + if future is not None and not future.done(): + future.set_result({"__MODIFF_ERROR": "websocket_closed"}) + async def broadcast(self, message: dict | bytes, sid: list[str] | str = None, exclude: list[str] | str = None): sessions = [] @@ -15627,6 +15798,7 @@ async def send_to_session(session): if websocket.closed: if self.ws_sessions.get(session) is websocket: self.ws_sessions.pop(session, None) + self._cancel_session_signal_requests(session) return try: if isinstance(message, dict): @@ -15642,6 +15814,7 @@ async def send_to_session(session): # finally block gets scheduled. if self.ws_sessions.get(session) is websocket: self.ws_sessions.pop(session, None) + self._cancel_session_signal_requests(session) if websocket.closed or "closing transport" in str(e).lower(): logger.debug(f"[Websocket] Dropped closing session {session}: {e}") else: @@ -15693,6 +15866,7 @@ async def request_signal_value(): request_id = nanoid.generate(size=12) future = self.loop.create_future() self.pending_ws_requests[request_id] = future + self.pending_ws_request_sessions[request_id] = sid try: await self.broadcast( { @@ -15704,9 +15878,15 @@ async def request_signal_value(): }, sid, ) + # Closing between registration and send must also resolve the + # lookup rather than occupy the field-action lease until timeout. + session = self.ws_sessions.get(sid) + if session is None or session.closed: + self._cancel_session_signal_requests(sid) return await asyncio.wait_for(future, timeout=timeout) finally: self.pending_ws_requests.pop(request_id, None) + self.pending_ws_request_sessions.pop(request_id, None) # The graph executor runs outside the HTTP event-loop thread. Run # the complete request there and synchronously await its bounded diff --git a/modiff/service.py b/modiff/service.py new file mode 100644 index 00000000..96b6e61c --- /dev/null +++ b/modiff/service.py @@ -0,0 +1,130 @@ +"""Local service client. Uses the ordinary graph queue and persisted run outputs.""" + +from __future__ import annotations + +import argparse +import http.client +import ipaddress +import json +from pathlib import Path +import re +import sys +import time +from urllib.parse import urlsplit +import uuid + + +def request(server, path, body=None): + from modiff.service_package import load_json + from modiff.service_package import LIMIT, canonical + + url = urlsplit(server) + try: + local = ipaddress.ip_address(url.hostname or "").is_loopback + except ValueError: + local = False + if ( + not local + or url.scheme != "http" + or url.path not in {"", "/"} + or url.query + or url.fragment + or url.username + or url.password + ): + raise ValueError("Use a loopback HTTP origin, such as http://127.0.0.1:8088.") + connection = http.client.HTTPConnection(url.hostname, url.port or 80, timeout=30) + try: + connection.request( + "POST" if body is not None else "GET", + path, + body=canonical(body) if body is not None else None, + headers={"Content-Type": "application/json"}, + ) + response = connection.getresponse() + raw = response.read(LIMIT + 1) + value = load_json(raw) + if response.status != 200 or not isinstance(value, dict) or value.get("error"): + raise ValueError( + value.get("message", f"HTTP {response.status}") + if isinstance(value, dict) + else "Invalid service response." + ) + return value + finally: + connection.close() + + +def run(server, package, values, *, timeout=300): + from modiff.service_package import service_outputs + + if not 0 < timeout <= 86400: + raise ValueError("Timeout must be between 0 and 86400 seconds.") + prepared = request( + server, + "/service_package", + {"operation": "prepare", "package": package, "values": values, "sid": "service_" + uuid.uuid4().hex}, + ) + submitted = request(server, "/graph", prepared["graph"]) + task_id = submitted.get("task_id") + if not isinstance(task_id, str) or not re.fullmatch(r"[\w-]{1,128}", task_id): + raise ValueError("Graph submission returned no valid task ID; do not automatically resubmit.") + print(f"Submitted task {task_id}", file=sys.stderr) + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + queue = request(server, "/queue") + task = next((item for item in queue.get("recent", []) if item.get("task_id") == task_id), None) + if task is not None: + if task.get("status") != "completed": + raise ValueError(f"Run {task_id} ended with {task.get('status')}. Inspect /runs/{task_id}.") + return service_outputs(package, request(server, f"/runs/{task_id}"), task_id) + time.sleep(0.25) + raise ValueError( + f"Timed out waiting for {task_id}. The run remains queued/running; inspect /runs/{task_id} before retrying." + ) + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("command", choices=("inspect", "build", "run")) + parser.add_argument("file", type=Path, help="API graph for inspect/build; service package for run") + parser.add_argument("--interface", type=Path, help="Named bindings JSON for build") + parser.add_argument("--inputs", type=Path, help="Named input values JSON for run") + parser.add_argument("--server", default="http://127.0.0.1:8088") + parser.add_argument("--output", type=Path, help="Write JSON to a new file, without overwriting existing files") + parser.add_argument("--timeout", type=float, default=300) + args = parser.parse_args(argv) + try: + from modiff.service_package import load_json + from modiff.service_package import LIMIT + + def read(path): + with path.open("rb") as handle: + return load_json(handle.read(LIMIT + 1)) + + document = read(args.file) + if args.command == "run": + result = run(args.server, document, read(args.inputs) if args.inputs else {}, timeout=args.timeout) + else: + body = {"operation": args.command, "graph": document} + if args.command == "build": + if not args.interface: + raise ValueError("build requires --interface with named inputs/outputs.") + body["interface"] = read(args.interface) + result = request(args.server, "/service_package", body) + if args.command == "build": + result = result["package"] + encoded = json.dumps(result, indent=2, allow_nan=False) + "\n" + if args.output: + with args.output.open("x", encoding="utf-8") as handle: + handle.write(encoded) + else: + print(encoded, end="") + return 0 + except (ValueError, OSError, KeyError, http.client.HTTPException) as error: + print(str(error), file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/modiff/service_api.py b/modiff/service_api.py new file mode 100644 index 00000000..c5b7d6b2 --- /dev/null +++ b/modiff/service_api.py @@ -0,0 +1,85 @@ +"""Service export/validation only. Submission still uses POST /graph.""" + +import asyncio + +from aiohttp import web +from modiff import service_package as service + + +load_json = service.load_json + + +class ServiceAPI: + def _service_contract(self, graph): + from modiff.backend_source_identity import backend_source_identity + + if backend_source_identity()["fingerprint"] != self.backend_source_identity["fingerprint"]: + raise ValueError("Backend source changed since startup. Restart before service export/execution.") + return service.runtime_contract(graph, self.backend_source_identity, self._extension_store()) + + def _validate_service_graph(self, graph): + envelope = graph.get("servicePackage") + if "servicePackage" not in graph: + return + if not isinstance(envelope, dict) or set(envelope) != {"package", "values"}: + raise ValueError("Invalid service package execution envelope.") + expected = service.prepare_package( + envelope["package"], + envelope["values"], + registry=self.modules, + contract=self._service_contract(graph), + sid=graph.get("sid"), + ) + if ( + service.graph_material(expected) != service.graph_material(graph) + or expected.get("deterministicMode") != graph.get("deterministicMode") + or self._coerce_runtime_hints(expected.get("runtimeHints")) + != self._coerce_runtime_hints(graph.get("runtimeHints")) + ): + raise ValueError("Prepared service graph was modified. Prepare the package again.") + + async def service_package(self, request): + try: + data = bytearray() + async for chunk in request.content.iter_chunked(65536): + data.extend(chunk) + if len(data) > service.LIMIT: + raise ValueError("Service request exceeds 8 MiB.") + body = load_json(data) + if not isinstance(body, dict): + raise ValueError("Service request must be an object.") + operation = body.get("operation") + keys = { + "inspect": {"operation", "graph"}, + "build": {"operation", "graph", "interface"}, + "prepare": {"operation", "package", "values", "sid"}, + } + if not isinstance(operation, str) or operation not in keys or set(body) != keys[operation]: + raise ValueError("Use inspect, build or prepare with their declared fields.") + + def perform(): + package = body.get("package") + graph = ( + body.get("graph") + if operation != "prepare" + else package.get("graph") + if isinstance(package, dict) + else None + ) + if not isinstance(graph, dict): + raise ValueError("Service request needs an API graph.") + candidates = service.inspect_graph(graph, self.modules) + if operation == "inspect": + return {"error": False, **candidates} + contract = self._service_contract(graph) + if operation == "build": + package = service.build_package(graph, body["interface"], registry=self.modules, contract=contract) + return {"error": False, "package": package} + graph = service.prepare_package( + body["package"], body["values"], registry=self.modules, contract=contract, sid=body["sid"] + ) + return {"error": False, "graph": graph} + + return web.json_response(await asyncio.to_thread(perform)) + except (ValueError, TypeError, KeyError, OSError) as error: + return web.json_response({"error": True, "message": str(error)[:2048]}, status=400) diff --git a/modiff/service_package.py b/modiff/service_package.py new file mode 100644 index 00000000..c0460ec0 --- /dev/null +++ b/modiff/service_package.py @@ -0,0 +1,457 @@ +"""Portable interface around an existing API graph; never an executor or installer.""" + +from __future__ import annotations + +from copy import deepcopy +import hashlib +from importlib import metadata +import json +from pathlib import Path +import re +import sys + + +def graph_material(graph): + from modiff.workflow_auto_resource import graph_material as validate + + return validate(graph) + + +def workflow_graph_hash(graph): + from modiff.workflow_auto_resource import workflow_graph_hash as identity + + return identity(graph) + + +SCHEMA = "modiff-service-v1" +LIMIT = 8 * 1024 * 1024 +NAME = re.compile(r"[A-Za-z][A-Za-z0-9_]{0,63}\Z") +SHA = re.compile(r"[a-f0-9]{40}\Z") +SECRET_KEY = re.compile( + r"(?:^token$|password|secret|api[_-]?key|access[_-]?token|hf[_-]?token|authorization|credential)", re.I +) +AUTHORITY = re.compile( + r"revision|repo|model|pipeline|class|identity|code|token|secret|password|adapter|lora|reviewed_variant|execution_profile|workflow_id|block_path|subfolder|^variant$", + re.I, +) +MODEL_KEY = re.compile(r"(?:repo_id|model_id|model_name|model_path|pretrained_model_name_or_path)\Z") +PRIVATE_TEXT = re.compile( + r"(?:^|\s)(?:/(?:home|Users|tmp|mnt|opt|var|etc|usr)/|[A-Za-z]:[\\/]|\\\\|~/)|hf_[A-Za-z0-9]{20,}|(?:https?://)[^\s/]+:[^\s/]+@", + re.I, +) +SCALARS = {"str", "string", "int", "integer", "float", "number", "bool", "boolean"} +MAX_SERVICE_FILES = 128 +PREVIEWS = {"ui_text", "ui_image", "ui_video", "ui_audio"} +# These are observations/correlation only. Preserve execution hints, including +# Studio specification receipts. Auto's graph-bound receipt is issued afresh. +TRANSIENT_HINTS = { + "clientRunId", + "runInputHash", + "workflowTabId", + "workflowCanvasEpoch", + "workflowFormEpoch", + "workflowTitle", + "workflowSnapshot", + "nodeId", + "workflowAutoPlan", +} + + +def canonical(value): + try: + raw = json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode() + except (ValueError, TypeError, RecursionError) as error: + raise ValueError("Service data must be finite JSON.") from error + if len(raw) > LIMIT: + raise ValueError("Service package exceeds 8 MiB.") + return raw + + +def digest(value): + return "sha256:" + hashlib.sha256(canonical(value)).hexdigest() + + +def portable(value, location="package", depth=0): + if depth > 64: + raise ValueError("Service data exceeds 64 nested levels.") + if isinstance(value, dict): + if value.get("source") == "local": + raise ValueError(f"{location}: select a pinned Hub model; local model selections are not portable.") + for key, item in value.items(): + if SECRET_KEY.search(key) and item not in (None, "", False): + raise ValueError(f"{location}.{key}: credentials cannot be exported.") + portable(item, f"{location}.{key}", depth + 1) + elif isinstance(value, list): + for index, item in enumerate(value): + portable(item, f"{location}[{index}]", depth + 1) + elif isinstance(value, str): + if PRIVATE_TEXT.search(value) or value.startswith(("/", "\\", "file:", "@data/", "@work/", "../", "./")): + raise ValueError(f"{location}: remove the credential/local path or expose a required service input.") + + +def node_fields(registry, node): + definition = registry.get(node["module"], {}).get(node["action"]) + if not isinstance(definition, dict) or not isinstance(definition.get("params"), dict): + raise ValueError("A service node is unavailable in the current registry. Enable/review its source first.") + return definition["params"] + + +def _is_preview_observation(spec, param): + display = spec.get("display") + return ( + isinstance(display, str) + and display in PREVIEWS + and bool(spec.get("dataSource")) + and isinstance(param, dict) + and param.get("display") == display + and param.get("sourceKey") == spec["dataSource"] + and not param.get("sourceId") + ) + + +def _modular_service_fields(graph): + """Resolve dynamic stage fields from one concrete reviewed model owner. + + Imported field types and authoring labels are not schema authority. Reuse + the resource-owner traversal and the same metadata used by dynamic nodes; + ambiguous, unbound and contract-only custom paths remain undiscoverable. + """ + nodes = graph["nodes"] + loaders = { + key for key, node in nodes.items() + if (node["module"], node["action"]) == ("modules.ModularDiffusers", "ModelsLoader") + } + if not loaders: + return {} + from modiff.operation_contracts import MODULAR_STAGE_OPERATIONS + from modiff.workflow_task_identity import resource_consumers + from modules.ModularDiffusers.modular_utils import get_model_type_metadata + + owners = {} + for loader in loaders: + for consumer in resource_consumers(nodes, loader, loaders): + owners.setdefault(consumer, []).append(loader) + actions = {operation.action: kind for kind, operation in MODULAR_STAGE_OPERATIONS.items()} + metadata = {} + result = {} + for node_id, node in nodes.items(): + if (node["module"] != "modules.ModularDiffusers" or node["action"] not in actions + or len(owners.get(node_id, [])) != 1): + continue + owner = owners[node_id][0] + if owner not in metadata: + selector = nodes[owner]["params"].get("model_type", {}) + value = selector.get("value") + metadata[owner] = ( + get_model_type_metadata(value) + if isinstance(value, str) and not selector.get("sourceId") else None + ) + definition = metadata[owner] + if not definition or definition.get("execution_status") == "contract_only": + continue + stage = definition["node_params"].get(actions[node["action"]]) + if stage: + result[node_id] = stage["params"] + return result + + +def inspect_graph(graph, registry): + graph_material(graph) + inputs, outputs = [], [] + modular_fields = _modular_service_fields(graph) + for node_id, node in graph["nodes"].items(): + fields = {**node_fields(registry, node), **modular_fields.get(node_id, {})} + for field, spec in fields.items(): + if not isinstance(spec, dict): + continue + display = spec.get("display") + types = spec.get("type") + # Textarea prompts use the registry's "text" alias in ordinary + # pipelines and "string" in Modular pipelines. Export a string + # contract for both; this does not admit list/opaque inputs. + if types == "text": + types = "string" + # File widgets may carry a scalar or a list despite a legacy + # registry type of str. Only the trusted widget declaration can + # grant this contract; imported parameter metadata cannot. + if ( + isinstance(types, str) and types in {"str", "string"} + and display == "filebrowser" + and isinstance(spec.get("fieldOptions"), dict) + and spec["fieldOptions"].get("multiple") is True + ): + types = "files" + param = node["params"].get(field) + if ( + _is_preview_observation(spec, param) + and not spec.get("hidden") + and not (node["module"] == "modules.Audio" and node["action"] == "Load") + ): + outputs.append({"nodeId": node_id, "field": field, "type": display}) + if ( + isinstance(types, str) + and types in SCALARS | {"files"} + and isinstance(param, dict) + and not param.get("sourceId") + and "value" in param + and display not in (*PREVIEWS, "output", "button") + and not AUTHORITY.search(field) + ): + inputs.append({"nodeId": node_id, "field": field, "type": types}) + return {"inputs": inputs, "outputs": outputs} + + +def bindings(interface, candidates): + if not isinstance(interface, dict) or set(interface) != {"inputs", "outputs"}: + raise ValueError("Interface needs named inputs and outputs maps.") + for kind in ("inputs", "outputs"): + selected = interface[kind] + if not isinstance(selected, dict) or len(selected) > 128 or (kind == "outputs" and not selected): + raise ValueError("Use at most 128 inputs/outputs and at least one persisted preview output.") + allowed = {(x["nodeId"], x["field"]): x for x in candidates[kind]} + seen = set() + for name, targets in selected.items(): + if not NAME.fullmatch(name) or not isinstance(targets, list) or not targets or len(targets) > 128: + raise ValueError( + "Names must start with a letter and contain letters, digits or underscores; targets must be a list." + ) + types = set() + for target in targets: + if not isinstance(target, dict) or set(target) != {"nodeId", "field"}: + raise ValueError("A binding target needs exactly nodeId and field.") + if not all(isinstance(v, str) for v in target.values()): + raise ValueError("Binding identities must be strings.") + pair = (target["nodeId"], target["field"]) + if pair not in allowed or pair in seen: + raise ValueError( + "A binding is duplicated, connected, unavailable or not a supported service value." + ) + seen.add(pair) + types.add(allowed[pair]["type"]) + if len(types) != 1: + raise ValueError("Shared input/output targets must have the same type.") + return deepcopy(interface) + + +def model_pins(graph, registry): + from modiff.model_artifact_catalog import catalog_repository_pin + + pins = [] + + def walk(value, field, location, revision=None): + if isinstance(value, dict): + if value.get("source") == "hub": + walk(value.get("value"), "repo_id", location, value.get("revision") or revision) + else: + for key, item in value.items(): + walk(item, key, f"{location}.{key}", value.get("revision")) + elif isinstance(value, list): + for index, item in enumerate(value): + walk(item, field, f"{location}[{index}]", revision) + elif isinstance(value, str) and value and MODEL_KEY.fullmatch(field): + if not re.fullmatch(r"[\w.-]+/[\w.-]+", value): + raise ValueError(f"{location}: only explicit Hub repository identifiers can be packaged.") + pin = catalog_repository_pin(value) + resolved = revision or (pin or {}).get("revision") + if not isinstance(resolved, str) or not SHA.fullmatch(resolved): + raise ValueError(f"{location}: select an immutable 40-character model revision before exporting.") + pins.append({"location": location, "repository": value, "revision": resolved}) + + for node_id, node in graph["nodes"].items(): + fields = node_fields(registry, node) + revision = node["params"].get("revision", {}).get("value") + for field, param in node["params"].items(): + if param.get("sourceId") and ( + MODEL_KEY.fullmatch(field) or fields.get(field, {}).get("display") == "modelselect" + ): + raise ValueError(f"{node_id}.{field}: service export requires a literal pinned model selection.") + if not param.get("sourceId"): + if node["module"] == "modules.Spandrel" and node["action"] == "Upscaler" and field == "model_id": + from modiff.controlled_artifacts import portable_upscaler_selection + + pins.append({"location": f"{node_id}.{field}", **portable_upscaler_selection(param.get("value"))}) + continue + display = fields.get(field, {}).get("display") + key = "repo_id" if display in ("model", "hf_model", "model_select", "modelselect") else field + walk(param.get("value"), key, f"{node_id}.{field}", revision if MODEL_KEY.fullmatch(key) else None) + return pins + + +def runtime_contract(graph, source_identity, extension_store): + from modiff.runtime_profile import read_state, lock_digest, load_manifest, PROJECT_ROOT + from modiff.optional_runtime_execution import graph_optional_runtime_requirement + + state = read_state(Path(sys.prefix)) or {} + profile = state.get("profile") + spec = load_manifest()["profiles"].get(profile) + if not spec: + raise ValueError("Service export requires a managed runtime profile. Use modiff.dev check.") + contract_hash = lock_digest(PROJECT_ROOT / spec["requirements"], profile=profile) + if state.get("lock_digest") != contract_hash: + raise ValueError("Managed dependency contract changed; check/repair the environment before exporting.") + custom = [] + for module in sorted({n["module"] for n in graph["nodes"].values() if n["module"].startswith("custom.")}): + item = extension_store.require_enabled(module.removeprefix("custom.")) + custom.append({key: item[key] for key in ("moduleKey", "codeHash", "revision", "dependencies")}) + packages = {} + for distribution in metadata.distributions(): + name = re.sub(r"[-_.]+", "-", distribution.metadata.get("Name", "")).lower() + if name and name != "modiff": + packages.setdefault(name, distribution.version) + requirement = graph_optional_runtime_requirement(graph) + return { + "backend": {key: source_identity[key] for key in ("gitCommit", "fingerprint")}, + "python": f"{sys.version_info.major}.{sys.version_info.minor}", + "profile": profile, + "contractHash": contract_hash, + "packages": dict(sorted(packages.items())), + "optionalProfiles": requirement.get("profileIds", []), + "customNodes": custom, + } + + +def build_package(graph, interface, *, registry, contract): + canonical(graph) + candidates = inspect_graph(graph, registry) + interface = bindings(interface, candidates) + material = deepcopy(graph_material(graph)) + # Node parameter execution data is retained, including display/spawn flags. + # Completed previews are observations, not inputs to the next execution. + # Verify their registry binding rather than trusting a client display flag. + for node in material["nodes"].values(): + for field, spec in node_fields(registry, node).items(): + param = node["params"].get(field) + if isinstance(spec, dict) and _is_preview_observation(spec, param): + param.pop("value", None) + hints = graph.get("runtimeHints") or {} + if not isinstance(hints, dict): + raise ValueError("runtimeHints must be an object.") + material["runtimeHints"] = {k: deepcopy(v) for k, v in hints.items() if k not in TRANSIENT_HINTS} + if "deterministicMode" in graph: + material["deterministicMode"] = deepcopy(graph["deterministicMode"]) + models = model_pins(material, registry) + for targets in interface["inputs"].values(): + for target in targets: + # Required values are supplied at invocation, never portable defaults. + material["nodes"][target["nodeId"]]["params"][target["field"]]["value"] = None + package = { + "schema": SCHEMA, + "graph": material, + "interface": interface, + "requirements": {**deepcopy(contract), "models": models}, + } + portable(package) + package["contentHash"] = digest(package) + return package + + +def prepare_package(package, values, *, registry, contract, sid): + canonical(package) + if ( + not isinstance(package, dict) + or set(package) != {"schema", "graph", "interface", "requirements", "contentHash"} + or package.get("schema") != SCHEMA + ): + raise ValueError("Unsupported service package.") + unsigned = {key: value for key, value in package.items() if key != "contentHash"} + if digest(unsigned) != package["contentHash"]: + raise ValueError("Service package content hash does not match. Export again.") + # Rebuild validation from the real registry. A hash is integrity, not approval. + rebuilt = build_package(package["graph"], package["interface"], registry=registry, contract=contract) + if rebuilt != package: + raise ValueError( + "Service requirements changed. Match backend, packages, model pins and approved custom source, then export again." + ) + interface = package["interface"]["inputs"] + if not isinstance(values, dict) or set(values) != set(interface): + raise ValueError("Supply exactly the declared named service inputs.") + graph = deepcopy(package["graph"]) + candidates = {(x["nodeId"], x["field"]): x for x in inspect_graph(graph, registry)["inputs"]} + for name, targets in interface.items(): + value = values[name] + for target in targets: + kind = candidates[(target["nodeId"], target["field"])]["type"] + if kind == "files": + valid = (isinstance(value, str) and bool(value)) or ( + isinstance(value, list) and 0 < len(value) <= MAX_SERVICE_FILES + and all(isinstance(item, str) and bool(item) for item in value) + ) + else: + valid = ( + isinstance(value, str) + if kind in {"str", "string"} + else type(value) is bool + if kind in {"bool", "boolean"} + else type(value) is int + if kind in {"int", "integer"} + else type(value) in {int, float} + ) + if not valid: + raise ValueError(f"Input {name} requires {kind}.") + graph["nodes"][target["nodeId"]]["params"][target["field"]]["value"] = deepcopy(value) + if not isinstance(sid, str) or not NAME.fullmatch(sid): + raise ValueError("Service session ID must be a bounded identifier.") + graph["sid"] = sid + if graph["runtimeHints"].get("resourceMode") == "auto": + graph["runtimeHints"]["workflowAutoPlan"] = {"schemaVersion": 1, "graphHash": workflow_graph_hash(graph)} + graph["servicePackage"] = {"package": deepcopy(package), "values": deepcopy(values)} + canonical(graph) + return graph + + +def service_outputs(package, receipt, task_id): + if not isinstance(receipt, dict) or not isinstance(receipt.get("outputs"), list): + raise ValueError("Invalid run receipt.") + result = {} + for name, targets in package["interface"]["outputs"].items(): + selected = [] + for target in targets: + matches = [ + item + for item in receipt["outputs"] + if isinstance(item, dict) + and item.get("taskId") == task_id + and item.get("nodeId") == target["nodeId"] + and item.get("fieldKey") == target["field"] + ] + if not matches: + raise ValueError(f"Run {task_id} did not persist output {name}. Add a preview node and run again.") + for item in matches: + projected = {key: item[key] for key in ("value", "url", "displayType") if key in item} + media = item.get("mediaItems") + if isinstance(media, list): + projected["mediaItems"] = [ + { + key: member[key] + for key in ("index", "url", "displayType", "contentType", "width", "height") + if key in member + } + for member in media + if isinstance(member, dict) and member.get("taskId") == task_id + ] + selected.append(projected) + result[name] = selected + return {"taskId": task_id, "outputs": result} + + +def load_json(raw): + if len(raw) > LIMIT: + raise ValueError("Service request exceeds 8 MiB.") + + def unique(pairs): + result = {} + for key, value in pairs: + if key in result: + raise ValueError("Duplicate JSON key in service request.") + result[key] = value + return result + + def reject_constant(_value): + raise ValueError("Non-finite JSON.") + + try: + value = json.loads(raw, object_pairs_hook=unique, parse_constant=reject_constant) + except (UnicodeError, RecursionError) as error: + raise ValueError("Invalid service JSON.") from error + canonical(value) + return value diff --git a/modiff/studio_execution_specs.py b/modiff/studio_execution_specs.py index 6a9a2c78..8f4ce293 100644 --- a/modiff/studio_execution_specs.py +++ b/modiff/studio_execution_specs.py @@ -4,6 +4,7 @@ import json from typing import Any +from modiff.operation_contracts import MODULAR_STAGE_OPERATIONS from modiff.diffusers_offload_modes import ( OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK, @@ -12,6 +13,7 @@ OFFLOAD_MODE_SEQUENTIAL_CPU, ) from modiff.model_artifact_catalog import require_catalog_revision +from modiff.optional_runtimes import TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID STUDIO_EXECUTION_SPEC_SCHEMA_VERSION = 1 @@ -209,6 +211,22 @@ # Diffusers layout. Keep a separate selection so revisions and capabilities # never alias merely because their current path sets overlap. FLUX2_KLEIN_BASE_DIFFUSERS_FILES = list(FLUX2_KLEIN_DIFFUSERS_FILES) +# The 9B KV artifact has a different license filename and sharded 8B text +# encoder/9B transformer. These are the exact paths at its reviewed commit; +# neither the 4B selection nor the duplicate root checkpoint is appropriate. +FLUX2_KLEIN_KV_DIFFUSERS_FILES = [ + name for name in FLUX2_KLEIN_DIFFUSERS_FILES + if name not in { + "LICENSE.md", "text_encoder/model-00001-of-00002.safetensors", + "text_encoder/model-00002-of-00002.safetensors", + "transformer/diffusion_pytorch_model.safetensors", + } +] + [ + "LICENSE", + *(f"text_encoder/model-{index:05d}-of-00004.safetensors" for index in range(1, 5)), + *(f"transformer/diffusion_pytorch_model-{index:05d}-of-00002.safetensors" for index in range(1, 3)), + "transformer/diffusion_pytorch_model.safetensors.index.json", +] SDXL_BASE_REPO = "stabilityai/stable-diffusion-xl-base-1.0" SDXL_TURBO_REPO = "stabilityai/sdxl-turbo" SDXL_INSTRUCT_PIX2PIX_REPO = "diffusers/sdxl-instructpix2pix-768" @@ -1788,6 +1806,19 @@ "vae/diffusion_pytorch_model.safetensors", ] QWEN_IMAGE_EDIT_REPO = "Qwen/Qwen-Image-Edit" +# Independently reviewed alternative layouts; do not inherit the 2512 chat +# template or nine transformer shards for the original/BNB artifacts. +QWEN_IMAGE_ORIGINAL_DIFFUSERS_FILES = [ + name for name in QWEN_IMAGE_2512_DIFFUSERS_FILES if name != "tokenizer/chat_template.jinja" +] + ["LICENSE"] +QWEN_IMAGE_BNB_DIFFUSERS_FILES = [ + name for name in QWEN_IMAGE_2512_DIFFUSERS_FILES + if name != "tokenizer/chat_template.jinja" + and not (name.startswith("text_encoder/model-") or name.startswith("transformer/diffusion_pytorch_model-")) +] + [ + *(f"text_encoder/model-{index:05d}-of-00002.safetensors" for index in range(1, 3)), + *(f"transformer/diffusion_pytorch_model-{index:05d}-of-00003.safetensors" for index in range(1, 4)), +] QWEN_IMAGE_EDIT_DIFFUSERS_FILES = [ ".gitattributes", "README.md", @@ -3141,6 +3172,7 @@ def _modular_sdxl_conditioned_graph(*, route: str, control: str | None, ip_adapt ("loadIPAdapterImage", "file", "ipAdapterImage"), ("loadIPAdapterImage", "alpha_channel", "alphaMode"), ("guider", "guider", "classifierFreeGuidance"), + ("guider", "model_type", "pipelineClass"), ("guider", "guidance_scale", "guidanceScale"), ("ipAdapter", "adapter_model", "ipAdapterRepo"), ("ipAdapter", "adapter_revision", "ipAdapterRevision"), @@ -4575,6 +4607,55 @@ def _direct_inpaint_bindings(*, outpaint: bool, unsupported_params: frozenset[st ("decode", "block_path", "workflowDecodeBlock"), ) _MODULAR_WHOLE_IMAGE_TEXT_GRAPH_BINDINGS = _MODULAR_WHOLE_IMAGE_COMMON_GRAPH_BINDINGS +_ERNIE_MODULAR_IMAGE_GRAPH_ROLES = ( + ("models", "modules.ModularDiffusers.ModelsLoader", -1100, -80), + ("promptEnhance", "modules.ModularDiffusers.WorkflowErniePromptEnhance", -700, -80), + ("prompt", "modules.ModularDiffusers.WorkflowErnieTextEncode", -300, -80), + ("denoise", "modules.ModularDiffusers.WorkflowErnieImageDenoise", 100, -80), + ("decode", "modules.ModularDiffusers.WorkflowErnieDecodeImage", 500, -80), + ("preview", "modules.Image.Preview", 900, -80), +) +_ERNIE_MODULAR_IMAGE_GRAPH_EDGES = ( + ("models", "pipeline_components", "promptEnhance", "pipeline_components"), + ("models", "pipeline_components", "prompt", "pipeline_components"), + ("models", "pipeline_components", "denoise", "pipeline_components"), + ("models", "pipeline_components", "decode", "pipeline_components"), + ("promptEnhance", "state_out", "prompt", "state_in"), + ("prompt", "state_out", "denoise", "state_in"), + ("denoise", "state_out", "decode", "state_in"), + ("decode", "images", "preview", "image"), +) +_ERNIE_MODULAR_IMAGE_GRAPH_BINDINGS = ( + ("models", "model_type", "pipelineClass"), + ("models", "repo_id", "artifact"), + ("models", "revision", "defaultRevision"), + ("models", "dtype", "dtype"), + ("models", "device", "device"), + ("models", "auto_offload", "autoOffload"), + ("models", "offload_mode", "offloadMode"), + ("models", "trust_remote_code", "false"), + ("models", "workflow_id", "workflowId"), + ("promptEnhance", "pipeline_class", "pipelineClass"), + ("promptEnhance", "workflow_id", "workflowId"), + ("promptEnhance", "block_path", "workflowPromptEnhancerBlock"), + ("promptEnhance", "prompt", "prompt"), + ("promptEnhance", "width", "width"), + ("promptEnhance", "height", "height"), + ("prompt", "pipeline_class", "pipelineClass"), + ("prompt", "workflow_id", "workflowId"), + ("prompt", "block_path", "workflowTextEncoderBlock"), + ("prompt", "negative_prompt", "negativePrompt"), + ("denoise", "pipeline_class", "pipelineClass"), + ("denoise", "workflow_id", "workflowId"), + ("denoise", "block_path", "workflowDenoiseBlock"), + ("denoise", "width", "width"), + ("denoise", "height", "height"), + ("denoise", "seed", "seed"), + ("denoise", "num_inference_steps", "steps"), + ("decode", "pipeline_class", "pipelineClass"), + ("decode", "workflow_id", "workflowId"), + ("decode", "block_path", "workflowDecodeBlock"), +) _MODULAR_WHOLE_IMAGE_EDIT_GRAPH_BINDINGS = _MODULAR_WHOLE_IMAGE_COMMON_GRAPH_BINDINGS + ( ("loadImage", "file", "referenceImages"), ("loadImage", "alpha_channel", "alphaMode"), @@ -5536,6 +5617,7 @@ def _cosmos3_sound_graph_edges(edges): *_WAN_FLF_GRAPH_BINDINGS, *_MODULAR_WHOLE_AUDIO_GRAPH_BINDINGS, *_MODULAR_WHOLE_IMAGE_TEXT_GRAPH_BINDINGS, + *_ERNIE_MODULAR_IMAGE_GRAPH_BINDINGS, *_MODULAR_WHOLE_IMAGE_EDIT_GRAPH_BINDINGS, *_MODULAR_WHOLE_VIDEO_TEXT_GRAPH_BINDINGS, *_MODULAR_WHOLE_VIDEO_IMAGE_GRAPH_BINDINGS, @@ -12701,7 +12783,7 @@ def _sd15_profile(profile_id: str, mode: str, pipeline_class: str) -> dict[str, "recommendedGuidance": 4.0, "notes": [ "Perturbed-attention guidance reuses the immutable Hunyuan-DiT v1.2 distilled safetensors snapshot without an auxiliary artifact.", - "The exact generic recipe is fixed at 1024x1024, at most 25 steps, guidance 4, PAG scale 3, adaptive scale 0, and official transformer layer 14; the reviewed PAG call fixes both encoder lengths internally.", + "The default recipe uses 1024x1024, at most 25 steps, guidance 4, PAG scale 3, adaptive scale 0, and official transformer layer 14. Explicit dimensions use 32-pixel increments within the one-megapixel ceiling without upstream resolution binning; the reviewed PAG call fixes both encoder lengths internally.", "The Tencent community license and acceptable-use obligations require explicit acknowledgement; Auto and Gallery remain disabled pending live review.", ], } @@ -14485,7 +14567,7 @@ def _sd15_profile(profile_id: str, mode: str, pipeline_class: str) -> dict[str, "galleryEligible": False, "notes": [ "The immutable public Apache-2.0 snapshot uses only package-owned Diffusers and Transformers classes and five bfloat16 safetensors weight files.", - "The reviewed Turbo route is fixed to 1024x1024, 8 steps, guidance 1, the repository's optional prompt enhancer, and the tokenizer's 2048-token ceiling.", + "The reviewed Turbo default uses 1024x1024, 8 steps, guidance 1, the repository's optional prompt enhancer, and the tokenizer's 2048-token ceiling. Explicit dimensions keep the declared alignment and pixel ceiling.", "The approximately 31.60 GB weight surface is remote-only; the missing safety checker keeps Auto and Gallery disabled pending live output review.", ], } @@ -14499,6 +14581,58 @@ def _sd15_profile(profile_id: str, mode: str, pipeline_class: str) -> dict[str, "bindings": _SDXL_GRAPH_BINDINGS, } +_ERNIE_IMAGE_TURBO_MODULAR_PROFILE = { + **deepcopy(_ERNIE_IMAGE_TURBO_PROFILE), + "id": "ernie-image-turbo:official-modular-workflow", + "model_type": "ErnieImageModularPipeline", + "loader_module": "modules.ModularDiffusers", + "loader_action": "ModelsLoader", + "execution_path": "modular-diffusers", + "pipeline_class": "ErnieImageModularPipeline", + "supported_offload_modes": ( + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ), + "retry_offload_modes": (OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK), +} +_ERNIE_IMAGE_TURBO_MODULAR_CAPABILITY = deepcopy(_ERNIE_IMAGE_TURBO_CAPABILITY) +_ERNIE_IMAGE_TURBO_MODULAR_CAPABILITY.update( + { + "modelType": "ErnieImageModularPipeline", + "label": "ERNIE Image Turbo (Modular Diffusers)", + "displayName": "ERNIE Image Turbo — Editable Stages", + "offloadSupport": { + "default": OFFLOAD_MODE_MODEL_CPU, + "lowVram": OFFLOAD_MODE_GROUP_CPU, + "emergency": OFFLOAD_MODE_GROUP_DISK, + "modes": [ + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ], + }, + "templateEligible": False, + "notes": [ + "Runs the pinned upstream prompt-enhancer, text-encoder, denoise, and VAE-decoder blocks as distinct stages.", + "Turbo is fixed to one image, at most eight steps, guidance 1, 32-pixel alignment, and at most 1,048,576 output pixels.", + "The standard ERNIE Image pipeline remains available for existing explicitly saved whole-pipeline graphs.", + "Auto and Gallery remain disabled until real execution and manual output review are complete.", + ], + } +) +STUDIO_EXECUTION_SPEC_DEFINITIONS["ernie-image-turbo:modular-text-to-image:v1"] = { + "modelType": "ErnieImageModularPipeline", + "mode": "text_to_image", + "profile": _ERNIE_IMAGE_TURBO_MODULAR_PROFILE, + "capability": _ERNIE_IMAGE_TURBO_MODULAR_CAPABILITY, + "roles": _ERNIE_MODULAR_IMAGE_GRAPH_ROLES, + "edges": _ERNIE_MODULAR_IMAGE_GRAPH_EDGES, + "bindings": _ERNIE_MODULAR_IMAGE_GRAPH_BINDINGS, +} + _GLM_IMAGE_PROFILE = { "id": "glm-image:direct", @@ -14567,7 +14701,7 @@ def _sd15_profile(profile_id: str, mode: str, pipeline_class: str) -> dict[str, "galleryEligible": False, "notes": [ "The immutable public MIT snapshot uses only package-owned Diffusers and Transformers classes and nine safetensors weight files; incorporated X-Omni tokenizer weights retain Apache-2.0 terms.", - "The reviewed text-to-image route is fixed to 1024x1024, 50 steps, guidance 1.5, and at most 2048 prompt tokens; image-to-image remains outside this first admission.", + "The reviewed text-to-image default uses 1024x1024, 50 steps, guidance 1.5, and at most 2048 prompt tokens. Explicit dimensions keep the declared alignment and pixel ceiling; image-to-image remains outside this first admission.", "The approximately 35.77 GB weight surface is remote-only; the missing safety checker keeps Auto and Gallery disabled pending live output review.", ], } @@ -15331,6 +15465,62 @@ def _sd15_profile(profile_id: str, mode: str, pipeline_class: str) -> dict[str, "bindings": _PERCEPTION_GRAPH_BINDINGS, } +# These models share the same generic AutoModel/processor nodes. A profile is +# an immutable artifact identity and recipe, not a separate user-facing node. +for _depth_id, _depth_model, _depth_label, _depth_repo in ( + ("depth-anything-v2-small", "DepthAnythingV2Model", "Depth Anything V2 Small", "depth-anything/Depth-Anything-V2-Small-hf"), + ("depth-anything-v2-metric-outdoor-small", "DepthAnythingV2MetricModel", "Depth Anything V2 Metric Outdoor Small", "depth-anything/Depth-Anything-V2-Metric-Outdoor-Small-hf"), +): + _depth_profile = { + **_MARIGOLD_DEPTH_PROFILE, + "id": f"{_depth_id}:direct", "model_type": _depth_model, + "loader_module": "modules.HuggingFaceTransformers", "loader_action": "LoadDepthEstimationModel", + "execution_path": "direct-huggingface-transformers-depth", "pipeline_class": "AutoModelForDepthEstimation", + "default_repo": _depth_repo, "supported_offload_modes": (OFFLOAD_MODE_NONE,), + "retry_offload_modes": (), "max_low_memory_side": None, "max_low_memory_steps": None, + "live_proof": False, + } + _depth_capability = { + **_MARIGOLD_DEPTH_CAPABILITY, + "modelType": _depth_model, "label": _depth_label, "displayName": _depth_label, + "family": "Depth Anything", "defaultRepo": _depth_repo, + "artifactLabel": "Transformers safetensors repo", + "downloadFiles": ["config.json", "preprocessor_config.json", "model.safetensors"], + "revisionCandidates": [require_catalog_revision(_depth_repo, model_type=_depth_model)], + "defaultSize": {"width": 518, "height": 518, "aspectRatio": "source"}, + "offloadSupport": {"default": OFFLOAD_MODE_NONE, "lowVram": OFFLOAD_MODE_NONE, + "emergency": OFFLOAD_MODE_NONE, "modes": [OFFLOAD_MODE_NONE]}, + "lowVram": {"dtype": "float32", "autoOffload": False, "offloadMode": OFFLOAD_MODE_NONE, + "steps": 1, "width": 518, "height": 518}, + "modeRequirements": {"depth_estimation": { + "requiredImages": ["referenceImages"], + "note": "Requires one source image. Native depth values and normalized relative preview are separate outputs.", + }}, + "notes": ["Uses the official AutoModelForDepthEstimation and bounded DPT image processor.", + "The prediction-map preview is normalized near=0/far=1; native depth retains the model's numeric scale.", + "Auto and Gallery remain disabled pending live qualification."], + } + STUDIO_EXECUTION_SPEC_DEFINITIONS[f"{_depth_id}:depth-estimation:v1"] = { + "modelType": _depth_model, "mode": "depth_estimation", "profile": _depth_profile, + "capability": _depth_capability, + "roles": (("depthModel", "modules.HuggingFaceTransformers.LoadDepthEstimationModel", -520, -80), + ("loadImage", "modules.Image.Load", -520, 300), + ("predictDepth", "modules.HuggingFaceTransformers.PredictDepth", -120, -80), + ("preview", "modules.Image.Preview", 500, -80)), + "edges": (("depthModel", "pipeline", "predictDepth", "pipeline"), + ("loadImage", "image", "predictDepth", "image"), + ("predictDepth", "preview_images", "preview", "image")), + "bindings": (("depthModel", "model_id", "artifact"), + ("depthModel", "revision", "defaultRevision"), + ("depthModel", "pipeline_class", "pipelineClass"), + ("depthModel", "execution_profile_id", "executionProfileId"), + ("depthModel", "dtype", "dtype"), ("depthModel", "device", "device"), + ("loadImage", "file", "referenceImages"), + ("predictDepth", "processing_resolution", "processingResolution"), + ("predictDepth", "match_input_resolution", "matchInputResolution")), + } + + _SMOLLM2_135M_INSTRUCT_PROFILE = { "id": "smollm2-135m-instruct:direct", "model_type": "HuggingFaceTextGenerationModel", @@ -15959,6 +16149,87 @@ def _direct_image_promotion_capability( } +QWEN_IMAGE_21_REPO = "Qwen/Qwen-Image-2.1" +QWEN_IMAGE_21_DIFFUSERS_FILES = [ + ".gitattributes", + "LICENSE", + "README.md", + "model_index.json", + "processor/added_tokens.json", + "processor/chat_template.jinja", + "processor/merges.txt", + "processor/preprocessor_config.json", + "processor/special_tokens_map.json", + "processor/tokenizer.json", + "processor/tokenizer_config.json", + "processor/video_preprocessor_config.json", + "processor/vocab.json", + "scheduler/scheduler_config.json", + "text_encoder/config.json", + "text_encoder/generation_config.json", + "text_encoder/model-00001-of-00004.safetensors", + "text_encoder/model-00002-of-00004.safetensors", + "text_encoder/model-00003-of-00004.safetensors", + "text_encoder/model-00004-of-00004.safetensors", + "text_encoder/model.safetensors.index.json", + "transformer/config.json", + "transformer/diffusion_pytorch_model-00001-of-00002.safetensors", + "transformer/diffusion_pytorch_model-00002-of-00002.safetensors", + "transformer/diffusion_pytorch_model.safetensors.index.json", + "vae/config.json", + "vae/diffusion_pytorch_model.safetensors" +] +_QWEN_IMAGE_21_MODES = ("text_to_image", "edit_image", "multi_image_reference_edit") +_QWEN_IMAGE_21_PROFILE = { + **_direct_image_promotion_profile( + profile_id="qwen-image-21:direct", model_type="QwenImage21Pipeline", + modes=_QWEN_IMAGE_21_MODES, pipeline_class="QwenImage21Pipeline", repository=QWEN_IMAGE_21_REPO, + quantizable_components=("transformer", "text_encoder"), default_quantized_components=(), + supported_offload_modes=_DIRECT_OFFLOAD_MODES, + retry_offload_modes=(OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK), + max_low_memory_side=1024, max_low_memory_steps=40, + ), + "optional_runtime_profiles": (TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID,), + # No target may silently fall back to an older base Transformers build. + "optional_runtime_platform_deliveries": (), +} +_QWEN_IMAGE_21_CAPABILITY = { + **_direct_image_promotion_capability( + model_type="QwenImage21Pipeline", label="Qwen Image 2.1", display_name="Qwen-Image-2.1", + family="Qwen Image", repository=QWEN_IMAGE_21_REPO, download_files=QWEN_IMAGE_21_DIFFUSERS_FILES, + modes=_QWEN_IMAGE_21_MODES, + mode_requirements={ + "text_to_image": {"note": "Unified RGB/RGBA generation from text."}, + "edit_image": {"requiredImages": ["referenceImages"], "note": "One source image and an edit instruction."}, + "multi_image_reference_edit": {"requiredImages": ["referenceImages"], "note": "One to ten reference images and an instruction."}, + }, + supports_negative_prompt=True, supports_mask=False, supports_multi_image=True, + recommended_steps=40, recommended_guidance=1.0, low_vram_side=1024, low_vram_steps=40, + supported_offload_modes=_DIRECT_OFFLOAD_MODES, low_vram_offload_mode=OFFLOAD_MODE_MODEL_CPU, + notes=[ + "Qwen Research License: non-commercial research/evaluation; commercial use requires separate permission.", + "Native attention-context reuse is per generation, not a cache shared between requests.", + "Standard Diffusers integration; upstream does not provide Qwen 2.1 Modular blocks at the reviewed revision.", + "Execution and hardware qualification remain pending; no Gallery assets are published.", + ], + ), + "defaultSize": {"width": 2048, "height": 2048, "aspectRatio": "1:1"}, + "artifactLabel": "Official Qwen Research License safetensors repository", + "license": "qwen-research", +} +for _qwen21_mode in _QWEN_IMAGE_21_MODES: + _qwen21_edit = _qwen21_mode != "text_to_image" + _qwen21_bindings = _SDXL_EDIT_GRAPH_BINDINGS if _qwen21_edit else _SDXL_GRAPH_BINDINGS + STUDIO_EXECUTION_SPEC_DEFINITIONS[f"qwen-image-21:{_qwen21_mode.replace('_', '-')}:v1"] = { + "modelType": "QwenImage21Pipeline", "mode": _qwen21_mode, + "profile": _QWEN_IMAGE_21_PROFILE, "capability": _QWEN_IMAGE_21_CAPABILITY, + "roles": _EDIT_GRAPH_ROLES if _qwen21_edit else _GRAPH_ROLES, + "edges": _EDIT_GRAPH_EDGES if _qwen21_edit else _GRAPH_EDGES, + "bindings": tuple(binding for binding in _qwen21_bindings + if binding[1] not in {"strength", "reference_strength", "max_sequence_length"}), + } + + _QWEN_IMAGE_EDIT_DIRECT_PROFILE = _direct_image_promotion_profile( profile_id="qwen-image-edit:direct", model_type="QwenImageEditPipeline", @@ -17576,14 +17847,6 @@ def _with_exact_video_revision(bindings: tuple) -> tuple: ) _EQUIVALENT_STANDARD_STUDIO_SPECS = { - "ernie-image:equivalent-standard-text-to-image:v1": { - "source": "ernie-image-turbo:text-to-image:v1", - "modelType": "ErnieImageModularPipeline", - "mode": "text_to_image", - "profileId": "ernie-image:equivalent-standard", - "modes": ("text_to_image",), - "label": "ERNIE Image Modular — Equivalent Standard Execution", - }, "ltx:equivalent-standard-text-to-video:v1": { "source": "ltx-video-0.9.8-13b-distilled:text-to-video:v1", "modelType": "LTXModularPipeline", @@ -17797,6 +18060,7 @@ def _with_exact_video_revision(bindings: tuple) -> tuple: "templateEligible": True, "galleryEligible": False, "liveProof": False, "qualifiedModes": [], "qualificationStatus": "graph-qualified-execution-pending", "revisionCandidates": [require_catalog_revision(_FLUX_KLEIN_KV_REPO)], + "downloadFiles": FLUX2_KLEIN_KV_DIFFUSERS_FILES, "notes": ["Ordinary upstream KV pipeline; no artificial Modular hierarchy.", "Step-distilled four-step model; upstream has no guidance control.", "Reference KV state is per generation, not reused between calls.", @@ -17863,6 +18127,28 @@ def studio_execution_profile_definitions() -> dict[str, dict[str, Any]]: definition["profile"]["id"]: deepcopy(definition["profile"]) for definition in STUDIO_EXECUTION_SPEC_DEFINITIONS.values() } + # A recipe identity is not a new upstream pipeline class. Keep ordinary + # SDXL loaders and persisted standard PAG profiles unchanged; this explicit + # selection adds the official native guider to a new operation starter. + profiles["sdxl-pag:modular"] = { + **deepcopy(_MODULAR_SDXL_PROFILE), + "id": "sdxl-pag:modular", + "model_type": "StableDiffusionXLPAGPipeline", + "modes": ("text_to_image", "image_to_image", "inpaint", "control_image", "control_edit_image"), + } + for identity, model_type, repository, base in ( + ("flux-schnell:modular", "FluxSchnellPipeline", FLUX_SCHNELL_REPO, _MODULAR_FLUX_PROFILE), + ("flux-krea:modular", "FluxKreaPipeline", FLUX_KREA_REPO, _MODULAR_FLUX_PROFILE), + ("sdxl-turbo:modular", "StableDiffusionXLTurboPipeline", SDXL_TURBO_REPO, _MODULAR_SDXL_PROFILE), + ("flux2-klein-kv:t2i-modular", "Flux2KleinKVPipeline", _FLUX_KLEIN_KV_REPO, _MODULAR_FLUX2_KLEIN_PROFILE), + ): + profiles[identity] = { + **deepcopy(base), "id": identity, "model_type": model_type, + "modes": ("text_to_image",), "default_repo": repository, "compatible_repos": (), + } + if identity == "flux2-klein-kv:t2i-modular": + # Do not inherit 4B quantization qualification for the exact 9B artifact. + profiles[identity]["expert_quantization_modes"] = () # Persisted pre-native FLUX.2 graphs explicitly select this loader profile. # Retire the catalog admission, not their executable loader identity. Keep # the original standard pipeline and resource policy; never auto-convert a @@ -17875,6 +18161,14 @@ def studio_execution_profile_definitions() -> dict[str, dict[str, Any]]: ) legacy_flux2["public"] = False profiles[legacy_flux2["id"]] = legacy_flux2 + legacy_ernie = _equivalent_standard_profile( + "ernie-image-turbo:text-to-image:v1", + profile_id="ernie-image:equivalent-standard", + model_type="ErnieImageModularPipeline", + modes=("text_to_image",), + ) + legacy_ernie["public"] = False + profiles[legacy_ernie["id"]] = legacy_ernie return profiles @@ -17909,6 +18203,37 @@ def studio_expert_resource_requirements( return deepcopy(matches[0]) if len(matches) == 1 else None +_REVIEWED_REPOSITORY_DOWNLOAD_FILES = { + "Qwen/Qwen-Image": QWEN_IMAGE_ORIGINAL_DIFFUSERS_FILES, + "Qwen/Qwen-Image-2512": QWEN_IMAGE_2512_DIFFUSERS_FILES, + "Qwen/Qwen-Image-Edit": QWEN_IMAGE_EDIT_DIFFUSERS_FILES, + "Qwen/Qwen-Image-Edit-2511": QWEN_IMAGE_EDIT_2511_DIFFUSERS_FILES, + "Qwen/Qwen-Image-Layered": QWEN_IMAGE_LAYERED_DIFFUSERS_FILES, + "unsloth/Qwen-Image-2512-unsloth-bnb-4bit": QWEN_IMAGE_BNB_DIFFUSERS_FILES, + FLUX_CANNY_VERIFIED_REPAIR_REPO: FLUX_CONTROL_DIFFUSERS_FILES, + FLUX_FILL_REPO: FLUX_FILL_DIFFUSERS_FILES, + FLUX_KONTEXT_REPO: FLUX_KONTEXT_DIFFUSERS_FILES, + Z_IMAGE_REPO: Z_IMAGE_DIFFUSERS_FILES, +} + + +def reviewed_repository_download_files(repo: str) -> list[str]: + """Data-only selections for legacy capabilities and exact alternatives. + + This supplies paths, not task/runtime permission. Single root FP8/NVFP4 + checkpoints are deliberately not guessed into full pipeline layouts. + """ + return list(_REVIEWED_REPOSITORY_DOWNLOAD_FILES.get(repo, ())) + + +def studio_capability_definition(model_type: str) -> dict[str, Any]: + """Copy only one model's defaults, with the same precedence as the catalog.""" + for definition in reversed(STUDIO_EXECUTION_SPEC_DEFINITIONS.values()): + if definition["modelType"] == model_type and "capability" in definition: + return deepcopy(definition["capability"]) + return {} + + def studio_capability_definitions() -> dict[str, dict[str, Any]]: return { definition["modelType"]: deepcopy(definition["capability"]) @@ -17936,13 +18261,8 @@ def _param_types(param: dict[str, Any]) -> set[str]: _MODULAR_NODE_TYPES = { - "modules.ModularDiffusers.EncodePrompt": "text_encoder", - "modules.ModularDiffusers.ImageEmbeddings": "image_encoder", - "modules.ModularDiffusers.ImageEncode": "vae_encoder", - "modules.ModularDiffusers.Denoise": "denoise", - "modules.ModularDiffusers.DecodeLatents": "decoder", - "modules.ModularDiffusers.Controlnet": "controlnet", - "modules.ModularDiffusers.IPAdapter": "ip_adapter", + f"modules.ModularDiffusers.{operation.action}": stage + for stage, operation in MODULAR_STAGE_OPERATIONS.items() } diff --git a/modiff/task_authoring.py b/modiff/task_authoring.py new file mode 100644 index 00000000..89dbeeca --- /dev/null +++ b/modiff/task_authoring.py @@ -0,0 +1,143 @@ +"""Task-first authoring over existing starters and resource inspection. + +No node construction, downloads, runtime activation or execution. The returned +graph remains an ordinary operation starter; Run independently validates it. +""" + +from copy import deepcopy + +from modiff.operation_contracts import _identifier, operation_owns_model +from modiff.operation_starters import resolve_operation_starter + + +def starter_api_graph(starter): + """Project declared inputs for the existing read-only memory planner.""" + nodes = { + node["operation"]["operationId"]: { + "module": node["module"], "action": node["action"], + "params": { + key: {"value": deepcopy(field.get("value", field.get("default")))} + for key, field in node["params"].items() if field.get("display") != "output" + }, + } + for node in starter["nodes"] + } + for edge in starter["edges"]: + nodes[edge["target"]]["params"][edge["targetHandle"]] = { + "sourceId": edge["source"], "sourceKey": edge["sourceHandle"], + } + # Some auxiliary operations precede their consumer without preceding the + # loader in presentation order. Derive execution order from real edges. + order = [] + while len(order) < len(nodes): + ready = [key for key in nodes if key not in order and all( + not field.get("sourceId") or field["sourceId"] in order + for field in nodes[key]["params"].values() + )] + if not ready: + raise ValueError("The task starter contains a dependency cycle.") + order.extend(ready) + return {"nodes": nodes, "paths": [order]} + + +def unbind_starter(starter): + """Keep a useful task shape, but require explicit model binding before Run.""" + result = deepcopy(starter) + loader = next(node for node in result["nodes"] if operation_owns_model(node["operation"])) + found = False + for key, field in loader["params"].items(): + if field.get("display") == "model" or key in {"repo_id", "model_id", "model", "vae_model"}: + value = {"source": "hub", "value": ""} if isinstance(field.get("value"), dict) else "" + field.update(value=value, default=deepcopy(value), required=True) + loader["values"][key] = value + required = {"operationId": loader["operation"]["operationId"], "field": key} + if required not in result["requiredInputs"]: + result["requiredInputs"].append(required) + found = True + elif key in {"revision", "execution_profile_id", "reviewed_variant"}: + field["value"] = "" + loader["values"][key] = "" + if not found: + raise ValueError("This task does not yet declare an editable model input.") + return result + + +def resolve_task_starter(modules, catalog, selection, *, installed, inspect_resources): + """Choose only published compatible profiles; browser preferences grant no authority. + + `installed(profile)` checks the exact reviewed artifact. `inspect_resources` + uses the existing workflow planner and never changes starter defaults. + Callbacks make no-download contract tests independent of machine state. + """ + if not isinstance(selection, dict) or not {"task"} <= set(selection) <= { + "task", "preferredProfileId", "resourceMode", + }: + raise ValueError("Select a task and an optional remembered model.") + task = _identifier(selection["task"]) + preferred = selection.get("preferredProfileId") + if preferred is not None and (not isinstance(preferred, str) or not 0 < len(preferred) <= 256): + raise ValueError("Invalid remembered model identity.") + mode = selection.get("resourceMode", "auto") + if mode not in ("auto", "expert"): + raise ValueError("Invalid authoring memory policy.") + profiles = { + profile["id"]: profile for profile in catalog["diffusersExecutionProfiles"] + if profile.get("public", True) + } + candidates = [] + declared = [] + for pipeline in catalog["pipelineSupport"]: + for support in pipeline["tasks"]: + if support["task"] != task or not support["operationIds"]: + continue + declared.append((pipeline["pipelineClass"], support)) + for identity in support["executionProfileIds"]: + profile = profiles.get(identity) + if not profile or profile["pipeline_class"] != pipeline["pipelineClass"]: + continue + available = installed(profile) + candidates.append((profile, support, available)) + if not candidates: + if not declared: + raise ValueError("No published model route is available for this task.") + declared.sort(key=lambda row: (row[1]["decomposition"] != "stages", row[0])) + starter = resolve_operation_starter(modules, catalog["operationContracts"], + {"pipelineClass": declared[0][0], "task": task}) + return {"schemaVersion": 1, "starter": unbind_starter(starter), "profileId": None, "unbound": True, + "message": "This task has an authoring contract but no published model choice. Bind a supported model before running."} + candidates.sort(key=lambda row: ( + row[0]["id"] != preferred, + not row[2], row[1]["dependencies"] != "ready", + row[1]["decomposition"] != "stages", row[0]["default_repo"], row[0]["id"], + )) + fallback = None + suitable = [] + for profile, support, available in candidates: + binding = {"pipelineClass": profile["pipeline_class"], "task": task, "executionProfileId": profile["id"]} + # Resolve even an unavailable route once for its unbound authoring shape. + if fallback is not None and (not available or support["dependencies"] != "ready"): + continue + starter = resolve_operation_starter(modules, catalog["operationContracts"], binding) + if fallback is None: + fallback = starter + if not available or support["dependencies"] != "ready": + continue + cost = float("inf") + if mode == "auto": + inspection = inspect_resources(starter_api_graph(starter)) + if inspection.get("canAutoRun") is not True: + continue + requirements = inspection.get("requirements", {}) + memory = [requirements.get(key) for key in ("systemRamBytes", "vramBytes")] + if all(isinstance(value, (int, float)) and value >= 0 for value in memory): + cost = sum(memory) + suitable.append((support["decomposition"] != "stages", cost, profile["id"], starter)) + if profile["id"] == preferred: + suitable = [suitable[-1]] + break + if suitable: + _, _, identity, starter = min(suitable, key=lambda row: row[:3]) + return {"schemaVersion": 1, "starter": starter, "profileId": identity, "unbound": False, + "message": "Using a compatible installed model. Run rechecks inputs and memory."} + return {"schemaVersion": 1, "starter": unbind_starter(fallback), "profileId": None, "unbound": True, + "message": "Choose a model on the loader. No installed route with a ready runtime and suitable memory recipe was selected."} diff --git a/modiff/template_gallery.py b/modiff/template_gallery.py index f303cb3b..090afd28 100644 --- a/modiff/template_gallery.py +++ b/modiff/template_gallery.py @@ -71,6 +71,8 @@ def _safe_gallery_path(value: Any) -> str: def load_template_gallery_source(path: Path = TEMPLATE_GALLERY_SOURCE_PATH) -> dict[str, Any]: + """Validate the pinned Dataset identity in remote and installer-built local bundles.""" + source = _read_bounded_json(path, maximum_bytes=TEMPLATE_GALLERY_SOURCE_MAX_BYTES, label="Template Gallery source") unavailable = source.get("unavailableAssets") try: @@ -79,7 +81,7 @@ def load_template_gallery_source(path: Path = TEMPLATE_GALLERY_SOURCE_PATH) -> d raise TemplateGalleryError("template_gallery_source_invalid", "Template Gallery source repository is invalid.") from error if ( source.get("schemaVersion") != 1 - or source.get("mode") != "huggingface" + or source.get("mode") not in {"huggingface", "local"} or source.get("repoType") != "dataset" or source.get("localBasePath") != "/template-gallery" or source.get("pathPrefix") != "template-gallery" diff --git a/modiff/upstream_coverage.py b/modiff/upstream_coverage.py index 8bf6829c..d99ade18 100644 --- a/modiff/upstream_coverage.py +++ b/modiff/upstream_coverage.py @@ -82,6 +82,9 @@ } _INTENTIONALLY_EXCLUDED_PIPELINES = { + "LTX2DFRPipeline": "Video diffusion-frame refinement is deferred from the current image/audio release scope.", + "LTX2DFRTemporalRefinePipeline": "Video temporal refinement is deferred from the current image/audio release scope.", + "Wan22VaceModularPipeline": "This newly exported video composition is deferred from the current image/audio release scope.", "DiffusionPipeline": "Generic factory/base class; it is not an exact task adapter.", "ModularPipeline": "Generic composition base class; reviewed subclasses are inventoried separately.", "OnnxStableDiffusionImg2ImgPipeline": "ONNX is outside MoDiff's reviewed local execution dependency boundary.", @@ -361,6 +364,27 @@ "pipelines/automatic_speech_recognition.py": ["AutomaticSpeechRecognitionPipeline"], }, }, + { + "id": "bounded-depth-estimation", + "label": "Bounded image depth estimation", + "status": "executable", + "reason": "Generic AutoModel nodes use bounded DPT preprocessing and separate native depth from normalized previews.", + "qualification": "source-implemented-mocked-contract-qualified", + "modes": ["depth_estimation"], + "nodeKeys": [ + "modules.HuggingFaceTransformers.LoadDepthEstimationModel", + "modules.HuggingFaceTransformers.PredictDepth", + ], + "canonicalWorkflowIds": [], + "mainEvidence": { + "models/auto/modeling_auto.py": ["AutoModelForDepthEstimation"], + "models/dpt/image_processing_dpt.py": ["DPTImageProcessor", "post_process_depth_estimation"], + }, + "productionEvidence": { + "models/auto/modeling_auto.py": ["AutoModelForDepthEstimation"], + "models/dpt/image_processing_dpt.py": ["DPTImageProcessor", "post_process_depth_estimation"], + }, + }, { "id": "bounded-causal-text-generation", "label": "Bounded causal text generation", diff --git a/modiff/workflow_auto_resource.py b/modiff/workflow_auto_resource.py index 628279a8..11a2762a 100644 --- a/modiff/workflow_auto_resource.py +++ b/modiff/workflow_auto_resource.py @@ -17,8 +17,8 @@ from modiff.auto_resource import READY_PROOF_STATUSES, build_auto_resource_plan, _hardware_snapshot from modiff.diffusers_profiles import resolve_execution_profiles_for_loader from modiff.huggingface_cluster_admission import REVIEWED_CLUSTER_EXECUTION_CANDIDATES +from modiff.workflow_task_identity import resource_consumers as _consumers -_RESOURCE_LINK = re.compile(r"pipeline|component|state|model|encoder|unet|vae|loop_member", re.I) _WORKLOAD = { "width": "width", "height": "height", "num_inference_steps": "steps", "steps": "steps", "guidance_scale": "guidanceScale", "num_frames": "numFrames", "batch_size": "batchSize", @@ -100,20 +100,6 @@ def _repository(value: Any) -> str: return value if isinstance(value, str) else "" -def _consumers(nodes: dict, loader_id: str, loader_ids: set[str]) -> list[str]: - found = {loader_id} - while True: - additions = {node_id for node_id, node in nodes.items() if node_id not in found | loader_ids and any( - p.get("sourceId") in found and _RESOURCE_LINK.search(str(p.get("sourceKey", "")) + " " + key) - for key, p in node["params"].items() - )} - additions.update(p["sourceId"] for node_id in found for key, p in nodes[node_id]["params"].items() - if p.get("sourceId") and "loop_member" in str(p.get("sourceKey", "")) and p["sourceId"] not in found) - if not additions: - return sorted(found - {loader_id}) - found.update(additions) - - def _number(value: Any) -> float | None: if isinstance(value, bool) or value is None: return None @@ -190,9 +176,24 @@ def _build_workflow_auto_plan( issues: list[str] = [] loaders: dict[str, Any] = {} data_nodes: set[str] = set() + custom_nodes: set[str] = set() preparation_nodes: set[str] = set() deferred_fields: list[dict] = [] for node_id, node in nodes.items(): + if node['module'].startswith('custom.'): + from modiff.custom_extensions import ExtensionStore + try: + extension = ExtensionStore().require_enabled(node['module'].removeprefix('custom.')) + if extension['preview']['kind'] == 'modular' and node['action'] == 'LoadModels': + raise ValueError('loading additional custom block models requires Custom memory policy; this supplier is not Auto-qualified.') + if extension['runtimeRole'] == 'manual': + raise ValueError('this custom source declares manual resource management; use Expert or review its data/connected-components contract.') + custom_nodes.add(node_id) + if extension['runtimeRole'] == 'data': + data_nodes.add(node_id) + except (ValueError, OSError, SyntaxError) as error: + issues.append(f'{node_id}: {error}') + continue if (node["module"], node["action"]) in _DATA_ACTIONS: data_nodes.add(node_id) continue @@ -248,7 +249,25 @@ def _build_workflow_auto_plan( modes = {item["studioMode"] for item in REVIEWED_CLUSTER_EXECUTION_CANDIDATES if item["pipelineClass"] == profile.model_type and item["workflowId"] == workflow and item["studioMode"] in profile.modes} if modes == {"edit_image", "multi_image_reference_edit"}: modes = {"multi_image_reference_edit"} - mode = next(iter(modes)) if len(modes) == 1 else profile.modes[0] if len(profile.modes) == 1 else None + if not modes and profile.execution_path == "modular-diffusers": + from modiff.workflow_task_identity import modular_graph_tasks + + modes = modular_graph_tasks(nodes, loader_id, consumers, profile.model_type) + if len(modes) == 1 and not modes.issubset(profile.modes): + from modiff.modular_workflow_contracts import PINNED_MODULAR_WORKFLOW_TRUTH + + # Operation tasks and historical resource modes can name the + # same reviewed upstream workflow differently. Use its existing + # mapping rather than a frontend/model-specific alias table. + truth = PINNED_MODULAR_WORKFLOW_TRUTH.get(profile.model_type) + route = truth.mode(next(iter(modes))) if truth else None + if route: + aliases = {item["studioMode"] for item in REVIEWED_CLUSTER_EXECUTION_CANDIDATES + if item["pipelineClass"] == profile.model_type + and item["workflowId"] == (route.upstream_workflow or "default") + and item["studioMode"] in profile.modes} + modes = aliases or modes + mode = next(iter(modes)) if len(modes) == 1 else profile.modes[0] if not modes and len(profile.modes) == 1 else None if mode not in profile.modes: raise ValueError("The model's task is ambiguous; select an explicit mode on its loader or consumer.") repo = _repository(values.get("repo_id") or values.get("model_id")) @@ -351,10 +370,17 @@ def _build_workflow_auto_plan( retained_total = dict(total) from modiff.workflow_auto_lifecycle import plan_owner_lifetimes schedule = plan_owner_lifetimes(material, planned, adapters) - # Independent owners use their lower live envelope even when the retained - # estimate happens to fit. Dispatch-time recipe/history selection must not - # silently discard a release plan and keep two large pipelines resident. - use_schedule = len(planned) > 1 and bool(schedule["releases"]) and any( + # Retain independent owners when their combined envelope fits. A release + # schedule is needed only under pressure; dispatch rechecks this same + # envelope against current capacity before allocating any model. + retained_demand = {**total, "systemRamBytes": total["systemRamBytes"] + (total["vramBytes"] if shared else 0)} + retention_fits = all( + not required or required <= (_number(available[key]) or 0) + for key, required in retained_demand.items() + ) + # Custom Python can retain references outside the graph. Its approval grants + # execution, not a proof that early model eviction is safe. + use_schedule = not custom_nodes and not retention_fits and len(planned) > 1 and bool(schedule["releases"]) and any( schedule["peak"][key] < total[key] for key in ("systemRamBytes", "vramBytes") ) if use_schedule: diff --git a/modiff/workflow_auto_values.py b/modiff/workflow_auto_values.py index e9ec7c70..d95cdef2 100644 --- a/modiff/workflow_auto_values.py +++ b/modiff/workflow_auto_values.py @@ -1,6 +1,7 @@ """Bounded inspection of built-in data controls; runtime preparation markers.""" from __future__ import annotations from contextvars import ContextVar +import math RUNTIME_VALUES = ContextVar('workflow_auto_values', default={}) DATA_MODULES = {'modules.Primitive', 'modules.Text', 'modules.Image', 'modules.ImageOperations', 'modules.Audio', 'modules.Video'} @@ -36,6 +37,29 @@ def inspect_resource_value(nodes, node_id, field, visited=frozenset()): source = nodes[source_id] def value(name): return inspect_resource_value(nodes, source_id, name, visited | {key}) + if ( + (nodes[node_id]['module'], nodes[node_id]['action']) == ('modules.ModularDiffusers', 'DecodeLatents') + and (source['module'], source['action']) == ('modules.ModularDiffusers', 'Denoise') + and source_field in {'out_width', 'out_height'} + and field == source_field.removeprefix('out_') + and nodes[node_id]['params'].get('latents', {}).get('sourceId') == source_id + and nodes[node_id]['params'].get('latents', {}).get('sourceKey') == 'latents' + ): + # These split-decoder ports carry the originating denoiser's geometry. + # Forecast only its explicitly inspected integer dimension. The normal + # executor compares the actual connected output to this captured value + # before decoder allocation, so changed/normalized geometry cannot + # silently reuse this envelope. Never execute a model during planning. + dimension = value(field) + if isinstance(dimension, bool): + raise ValueError(f'{source_id}.{field} must declare a positive integer dimension.') + try: + number = float(dimension) + except (TypeError, ValueError) as error: + raise ValueError(f'{source_id}.{field} must declare a positive integer dimension.') from error + if not math.isfinite(number) or number <= 0 or not number.is_integer(): + raise ValueError(f'{source_id}.{field} must declare a positive integer dimension.') + return int(number) if source['module'] == 'modules.Primitive': if source['action'] in {'String', 'Integer', 'Float', 'Boolean'}: return value('value') diff --git a/modiff/workflow_store.py b/modiff/workflow_store.py index 3c8bd82c..d5056732 100644 --- a/modiff/workflow_store.py +++ b/modiff/workflow_store.py @@ -15,7 +15,7 @@ _LOCK = STUDIO_PERSISTENCE_LOCK _ID = re.compile(r"^[A-Za-z0-9_-]{1,96}$") -_SUMMARY_FIELDS = ("id", "title", "source", "sourceLabel", "createdAt", "updatedAt", "revision", "clientId") +_SUMMARY_FIELDS = ("id", "title", "source", "sourceLabel", "intent", "createdAt", "updatedAt", "revision", "clientId") # Metadata only, never graph snapshots. File identity detects writes made by # migration/rollback or another local process without a separate invalidation API. _SUMMARY_CACHE_LIMIT = 8192 @@ -41,6 +41,10 @@ def _read(path: Path) -> dict[str, Any]: value = json.loads(path.read_text(encoding="utf-8")) if not isinstance(value, dict) or not isinstance(value.get("snapshot"), dict): raise ValueError(f"Saved workflow {path.name} is invalid.") + # Read-time compatibility, not a destructive migration. Unknown legacy + # documents remain saved even when their title resembles an autosave. + if value.get("intent") not in ("draft", "saved"): + value["intent"] = "saved" return value @@ -98,13 +102,21 @@ def get_workflow(data_dir: str | Path, workflow_id: Any) -> dict[str, Any] | Non def save_workflow(data_dir: str | Path, workflow_id: Any, payload: Any) -> dict[str, Any]: if not isinstance(payload, dict) or not isinstance(payload.get("snapshot"), dict): raise ValueError("Saved workflow payload requires a snapshot object.") + intent = payload.get("intent", "saved") + if not isinstance(intent, str) or intent not in {"draft", "saved"}: + raise ValueError("Workflow intent must be draft or saved.") path = _path(data_dir, workflow_id) now = int(time.time() * 1000) with _LOCK: existing = _read(path) if path.is_file() else None + # Explicit Save is monotonic. An older queued autosave must never move + # a saved document back into recovery, including from another client. + if existing and existing["intent"] == "saved": + intent = "saved" record = { "id": _workflow_id(workflow_id), "title": str(payload.get("title") or "Workflow").strip()[:160] or "Workflow", + "intent": intent, "snapshot": payload["snapshot"], "source": payload.get("source") if isinstance(payload.get("source"), str) else "manual", "sourceLabel": payload.get("sourceLabel") if isinstance(payload.get("sourceLabel"), str) else None, diff --git a/modiff/workflow_task_identity.py b/modiff/workflow_task_identity.py new file mode 100644 index 00000000..192494b0 --- /dev/null +++ b/modiff/workflow_task_identity.py @@ -0,0 +1,208 @@ +"""Read-only task recognition from existing reviewed Modular graph contracts. + +Authoring labels are not execution authority. Match concrete node identities and +state wires; unknown, partial and competing compositions remain unresolved. +This does not qualify a model or a resource recipe, or modify the submitted graph. +""" + +from collections import Counter +import json +import re + +from modiff.modular_action_bindings import MODULAR_ACTION_BINDINGS, MODULAR_AUXILIARY_OPERATION_BINDINGS +from modiff.modular_task_adapters import modular_task_adapters +from modiff.modular_workflow_discovery import load_reviewed_modular_workflow_snapshot + + +def _composition_tasks(stages, pipeline_class, workflows): + """Recognize the pinned selected workflow of a validated explicit composition. + + Placement validation is the runtime's read-only validator. It grants neither + executable-code trust nor a resource recipe. Partial non-media scopes stay + unlabeled, as do mixed workflow owners or invalid imported selectors. + """ + from modiff.modular_composition import validate_modular_composition_recipe + from modiff.huggingface_node_library import reviewed_huggingface_node_library + from modiff.modular_conditional_contracts import reviewed_modular_conditional_snapshot + from modules.ModularDiffusers.reviewed_blocks import _reviewed_placement + + selected = set() + media = False + library = reviewed_huggingface_node_library() + snapshot = None + compositions = {} + for node in stages.values(): + if node['action'] != 'ReviewedModularWorkflowStep': + return set() + values = {k: p.get('value') for k, p in node.get('params', {}).items() if isinstance(p, dict)} + if values.get('pipeline_class') != pipeline_class: + return set() + try: + recipe = values.get('composition_recipe') + composition = None + if recipe is not None: + key = json.dumps(recipe, sort_keys=True, allow_nan=False) + if key not in compositions: + compositions[key] = validate_modular_composition_recipe(recipe, library=library) + composition = compositions[key] + if snapshot is None and (composition is not None or values.get('execution_scope') == 'unpruned_pipeline' + or values.get('workflow_id') == 'default'): + snapshot = reviewed_modular_conditional_snapshot() + _, block = _reviewed_placement( + pipeline_class=pipeline_class, workflow_id=values.get('workflow_id'), + execution_scope=values.get('execution_scope') or 'selected_workflow', + placement_path=tuple(values.get('placement_path') or ()), + block_definition_id=values.get('block_definition_id'), block_class=values.get('block_class'), + block_hash=values.get('block_contract_hash'), composition=composition, library=library, snapshot=snapshot, + ) + except (ValueError, TypeError, KeyError): + return set() + selected.add(values.get('workflow_id')) + media |= any(p['name'] in {'images', 'videos', 'audio', 'audios', 'sound'} for p in block['outputs']) + if len(selected) != 1 or not media: + return set() + return {task for workflow in workflows if workflow['id'] in selected + for task, _ in modular_task_adapters(pipeline_class, workflow)} + + +def modular_graph_tasks(nodes, loader_id, consumer_ids, pipeline_class): + workflows = next((p['workflows'] for p in load_reviewed_modular_workflow_snapshot()['contracts'] + if p['pipelineClass'] == pipeline_class), ()) + if not workflows: + return set() + helpers = {binding[1] for binding in MODULAR_AUXILIARY_OPERATION_BINDINGS.values()} + helpers.update({"modules.ModularDiffusers.Guider", "modules.ModularDiffusers.Layers"}) + stages = { + key: nodes[key] + for key in consumer_ids + if key != loader_id and key in nodes and nodes[key].get("module") == "modules.ModularDiffusers" + and f"{nodes[key]['module']}.{nodes[key]['action']}" not in helpers + } + if any(n['action'] == 'ReviewedModularWorkflowStep' for n in stages.values()): + return _composition_tasks(stages, pipeline_class, workflows) + identities = Counter(f"{n['module']}.{n['action']}" for n in stages.values()) + candidates = [] + routes = [(task, adapter, workflow['id']) for workflow in workflows + for task, adapter in modular_task_adapters(pipeline_class, workflow)] + for task, route, workflow_id in routes: + names = { + action: MODULAR_ACTION_BINDINGS[action][1] + for action in route["actionSequence"] + if action in MODULAR_ACTION_BINDINGS + } + if len(names) != len(route["actionSequence"]) or Counter(names.values()) != identities: + continue + if any(count != 1 for count in identities.values()): + continue # Several branches cannot be labeled as one operation chain. + ids = { + action: next(key for key, node in stages.items() if f"{node['module']}.{node['action']}" == name) + for action, name in names.items() + } + # Whole-workflow stages bind exact executable selectors. A partial or + # conflicting selection is not rescued by advisory authoring metadata. + if any(node.get('params', {}).get('pipeline_class', {}).get('value') != pipeline_class + or node.get('params', {}).get('workflow_id', {}).get('value') != workflow_id + for node in stages.values() if node['action'].startswith('Workflow')): + continue + selected_workflow = nodes.get(loader_id, {}).get('params', {}).get('workflow_id', {}).get('value') + if selected_workflow and selected_workflow != workflow_id: + continue + expected = { + (ids[e['producerAction']], e['producerOutput'], ids[e['consumerAction']], e['consumerInput']) + for e in route['stateEdges'] + } + actual = { + (param["sourceId"], param.get("sourceKey"), key, field) + for key, node in stages.items() + for field, param in node.get("params", {}).items() + if isinstance(param, dict) and param.get("sourceId") in stages + } + # Effective dimensions are ordinary values emitted by Denoise, not + # task-selecting state links. They are explicitly wired by starters. + actual = { + edge for edge in actual if not (edge[1], edge[3]) in {("out_width", "width"), ("out_height", "height")} + } + if actual == expected: + candidates.append((task, route)) + # Some tasks share actions/wires but differ in optional conditioning inputs. + # Only discriminate the fields declared by those exact candidate contracts. + discriminators = set().union(*(set(r["requiredInputs"]) for _, r in candidates)) if candidates else set() + common = set.intersection(*(set(r["requiredInputs"]) for _, r in candidates)) if candidates else set() + discriminators -= common + # Numeric defaults (for example a control selector at zero) do not prove + # which optional branch a generic node selected. Retain that ambiguity. + discriminators -= { + field for node in [nodes.get(loader_id, {}), *stages.values()] + for field, param in node.get("params", {}).items() + if isinstance(param, dict) and not param.get("sourceId") + and isinstance(param.get("value"), (int, float, bool)) + } + supplied = { + field + for node in [nodes.get(loader_id, {}), *stages.values()] + for field, param in node.get("params", {}).items() + if field in discriminators + and isinstance(param, dict) + and (param.get("sourceId") or (not isinstance(param.get("value"), (int, float, bool)) + and param.get("value") not in (None, "", [], {}))) + } + return {task for task, route in candidates if set(route["requiredInputs"]) & discriminators == supplied} + + +_RESOURCE_LINK = re.compile(r"pipeline|component|state|model|encoder|unet|vae|loop_member", re.I) + + +def resource_consumers(nodes: dict, loader_id: str, loader_ids: set[str]) -> list[str]: + found = {loader_id} + while True: + additions = { + node_id + for node_id, node in nodes.items() + if node_id not in found | loader_ids + and any( + p.get("sourceId") in found and _RESOURCE_LINK.search(str(p.get("sourceKey", "")) + " " + key) + for key, p in node["params"].items() + ) + } + additions.update( + p["sourceId"] + for node_id in found + for key, p in nodes[node_id]["params"].items() + if p.get("sourceId") in nodes and p["sourceId"] not in found | loader_ids + # Follow upstream component/state suppliers, not images produced by + # a completed model owner. Crossing a media edge would merge owners + # and prevent the existing sequential resource-release schedule. + and nodes[p["sourceId"]].get("module") == "modules.ModularDiffusers" + and ( + re.search(r"pipeline|component|state|model|encoder|unet|vae|loop_member|controlnet|adapter", + str(p.get("sourceKey", "")) + " " + key, re.I) + # Native guidance helpers supply configuration upstream of the + # denoiser. They own no weights but belong to this model scope. + or (nodes[p["sourceId"]].get("action"), p.get("sourceKey"), key) in { + ("Guider", "guider_out", "guider"), + ("Layers", "layers_config", "layers_config"), + } + ) + ) + if not additions: + return sorted(found - {loader_id}) + found.update(additions) + + +def graph_task_receipts(nodes, records): + """Recognize only the output ancestry with captured Modular loader identity.""" + loaders = { + key + for key, node in nodes.items() + if (node.get("module"), node.get("action")) == ("modules.ModularDiffusers", "ModelsLoader") + } + result = [] + for key in sorted(loaders): + model = records.get(key, {}).get("fields", {}).get("model_type", {}).get("value") + if not isinstance(model, str): + return [] # Incomplete owner evidence must not select another owner's task. + tasks = modular_graph_tasks(nodes, key, resource_consumers(nodes, key, loaders), model) + result.append( + {"loaderId": key, "pipelineClass": model, "task": next(iter(tasks)) if len(tasks) == 1 else None} + ) + return result diff --git a/modules/DiffusersAudio/main.py b/modules/DiffusersAudio/main.py index e34414f6..53b204a0 100644 --- a/modules/DiffusersAudio/main.py +++ b/modules/DiffusersAudio/main.py @@ -22,7 +22,7 @@ from modiff.config import CONFIG from modiff.model_artifact_catalog import IMMUTABLE_HUB_REVISION, catalog_revision from modiff.path_identifiers import resolve_managed_path_identifier, resolve_runtime_input_path -from utils.huggingface import local_files_only, validate_hf_repo_id +from utils.huggingface import exact_cached_snapshot_path, local_files_only, validate_hf_repo_id from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype logger = logging.getLogger("modiff") @@ -141,12 +141,17 @@ def field_param_overlay(self) -> dict[str, dict[str, Any]]: return overlay def signal_value(self, pipeline_class: str, repository: str) -> dict[str, Any]: + actions = {"Generate": [self.mode]} + if pipeline_class == "AceStepPipeline": + for action in ("LoadAdapter", "SetAdapters", "FuseAdapters"): + actions[action] = [self.mode] return { "schemaVersion": 1, "library": "diffusers", "mediaKind": "audio", "pipelineClass": pipeline_class, "mode": self.mode, + "actions": actions, "repository": repository, "taskType": self.task_type, "upstreamTaskType": self.upstream_task_type, @@ -468,6 +473,24 @@ def _require_lowercase_safetensors_filename(value: str, *, label: str) -> str: return value +def get_audio_operation_contracts(modules) -> list[dict]: + from modiff.operation_contracts import build_pipeline_operation_contract + + result = [] + for pipeline_class, adapter in sorted(AUDIO_PIPELINE_ADAPTERS.items()): + for contract in adapter.mode_contracts: + for action, operation in (("LoadPipeline", "diffusion.load_models"), ("Generate", "diffusion.generate_audio")): + record = build_pipeline_operation_contract( + modules, pipeline_class=pipeline_class, task=contract.mode, operation_id=operation, + node_key=f"modules.DiffusersAudio.{action}", + field_overrides=contract.field_param_overlay() if action == "Generate" else None, + loader=action == "LoadPipeline", + ) + if record is not None: + result.append(record) + return result + + def _audio_contract_signal(adapter: AudioPipelineAdapter, mode: str, model_selection: Any) -> dict[str, Any]: # Static registry construction must not inspect the process working # directory. Runtime/action callers normalize the selection first. @@ -1139,8 +1162,9 @@ def execute(self, **kwargs): load_kwargs["use_safetensors"] = True self.progress(-1, phase="loading", message=f"Loading {pipeline_class_name}") + load_target = exact_cached_snapshot_path(model_id, revision) if revision else model_id with self.diffusers_loading_progress(): - pipeline = pipeline_class.from_pretrained(model_id, **load_kwargs) + pipeline = pipeline_class.from_pretrained(load_target, **load_kwargs) _ensure_language_model_generation_api(pipeline, adapter.pipeline_class) self._tag_pipeline(pipeline, adapter, mode, model_id, revision) if recipe: @@ -1150,17 +1174,36 @@ def execute(self, **kwargs): for method_name in ("enable_tiling", "enable_vae_tiling"): method = getattr(vae or pipeline, method_name, None) if callable(method): - method() + try: + method() + except NotImplementedError: + # Diffusers' AutoencoderMixin exposes this hook even + # when the concrete VAE has no tiling implementation. + logger.info("VAE tiling is unsupported for %s; using ordinary decoding", pipeline_class_name) break self.progress(99, phase="component_placement", message=f"Applying {offload_mode} offload") + offload_options = {} + if pipeline_class_name == "AceStepPipeline": + # Upstream calls text_encoder.get_input_embeddings() after forward, + # and reads condition_encoder.silence_latent/null_condition_emb + # outside forward. Keep that smaller component resident, and hook + # the text encoder's actual embedding entry point at leaf level. + # Oobleck's legacy weight_norm pre-hooks run before leaf transfer + # hooks; keep its small VAE resident too, preserving native weights. + offload_options = { + "component_names": tuple(getattr(pipeline, "components", {})), + "leaf_level_components": ("text_encoder",), + "resident_components": ("condition_encoder", "vae"), + } apply_pipeline_offload( pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope="diffusers-audio", - prefer_pipeline_group=True, + prefer_pipeline_group=not offload_options, + **offload_options, ) self.mm_add(pipeline, priority=2) return {"pipeline": pipeline, "resolved_artifact": model_id} @@ -1173,7 +1216,14 @@ class LoadAdapter(NodeBase): category = "Diffusers Audio" resizable = True params = { - "pipeline": {"label": "Pipeline", "display": "input", "type": "audio_diffusion_pipeline", "required": True}, + "pipeline": { + "label": "Pipeline", + "display": "input", + "type": "audio_diffusion_pipeline", + "required": True, + "onSignal": {"action": "signal", "target": "output"}, + "signalCompatibility": {"required": True, "action": "$node"}, + }, "adapter_path": { "label": "LoRA", "display": "modelselect", @@ -1210,7 +1260,12 @@ class LoadAdapter(NodeBase): "max": 2, "step": 0.05, }, - "output": {"label": "Pipeline", "display": "output", "type": "audio_diffusion_pipeline"}, + "output": { + "label": "Pipeline", + "display": "output", + "type": "audio_diffusion_pipeline", + "signal": {"direction": "output", "origin": "pipeline", "value": ""}, + }, } @staticmethod @@ -1387,10 +1442,17 @@ class SetAdapters(NodeBase): "display": "input", "type": "audio_diffusion_pipeline", "required": True, + "onSignal": {"action": "signal", "target": "output"}, + "signalCompatibility": {"required": True, "action": "$node"}, }, "adapter_names": {"label": "Adapter names", "type": "string", "default": "audio_style"}, "adapter_weights": {"label": "Weights", "type": "string", "default": "0.7"}, - "output": {"label": "Pipeline", "display": "output", "type": "audio_diffusion_pipeline"}, + "output": { + "label": "Pipeline", + "display": "output", + "type": "audio_diffusion_pipeline", + "signal": {"direction": "output", "origin": "pipeline", "value": ""}, + }, } def execute(self, **kwargs): @@ -1432,10 +1494,17 @@ class FuseAdapters(NodeBase): "display": "input", "type": "audio_diffusion_pipeline", "required": True, + "onSignal": {"action": "signal", "target": "output"}, + "signalCompatibility": {"required": True, "action": "$node"}, }, "enabled": {"label": "Fuse", "type": "bool", "default": True}, "safe_fusing": {"label": "Safe fusing", "type": "bool", "default": True}, - "output": {"label": "Pipeline", "display": "output", "type": "audio_diffusion_pipeline"}, + "output": { + "label": "Pipeline", + "display": "output", + "type": "audio_diffusion_pipeline", + "signal": {"direction": "output", "origin": "pipeline", "value": ""}, + }, } def execute(self, **kwargs): @@ -1761,6 +1830,7 @@ class Generate(NodeBase): {"action": "value", "target": "audio_contract"}, {"action": "exec", "data": "update_audio_contract"}, ], + "signalCompatibility": {"required": True, "action": "$node"}, }, "audio_contract": { "label": "Audio Contract", @@ -2099,6 +2169,7 @@ def execute(self, **kwargs): current_step=0, total_steps=call_kwargs["num_inference_steps"], ) + self._record_audio_call_inputs(call_kwargs, invocation) result = pipeline(**call_kwargs) audio = output_to_audio_object(result, sample_rate=sample_rate) if task_type == "continuation" and kwargs.get("return_continuation_tail", True): @@ -2117,6 +2188,21 @@ def execute(self, **kwargs): "duration_seconds": float(audio.get("duration_seconds") or 0.0), } + def _record_audio_call_inputs(self, call_kwargs, invocation: AudioInvocation): + # Record normalized controls at dispatch. Inactive ACE controls remain + # in the reusable node schema but do not describe standard audio calls. + self.record_generation_inputs({ + **call_kwargs, + "seed": int(invocation.controls["seed"]), + "audio_duration": invocation.duration_seconds, + "sample_rate": int(invocation.controls["sample_rate"]), + }) + if invocation.adapter.generation_kind != "ace_step": + self._execution_input_source_fields = { + "num_inference_steps": "stable_audio_steps", + "guidance_scale": "stable_audio_guidance", + } + def _execute_standard_diffusers_audio(self, pipeline, kwargs, invocation: AudioInvocation): import torch @@ -2140,6 +2226,7 @@ def callback(step, timestep, latents): "output_type": "pt", "return_dict": True, } + self._record_audio_call_inputs(common_kwargs, invocation) if invocation.adapter.generation_kind == "stable_audio": result = pipeline( **common_kwargs, diff --git a/modules/DiffusersImage/README.md b/modules/DiffusersImage/README.md index 5e7d7717..7d68a038 100644 --- a/modules/DiffusersImage/README.md +++ b/modules/DiffusersImage/README.md @@ -5,6 +5,25 @@ pipelines. An ordinary catalog Block contains these real nodes and media loaders preview. It is not an upstream Modular Diffusers hierarchy. Native Modular routes remain in `modules.ModularDiffusers`. +The Pre-quantized Transformer loader socket is an optional override. Leave it +disconnected for ordinary pipeline loading; graph repair must not supply an +unrelated value for it. Connected overrides still require the reviewed component +contract and exact base-pipeline identity. + +## Unconditional image starters + +New canonical DDPM, DDIM and Consistency Model loaders use the existing reviewed +float32, resident recipe with Auto offload disabled. New unconditioned operations +also clear the generic loader's unused ControlNet selection, so readiness does +not request an unrelated model. Existing saved loaders and generic node defaults +are preserved; change their settings explicitly when needed. + +The pinned upstream DDPM pipeline cannot use model CPU offload on CUDA because +its scheduler receives tensors on different devices. DDPM and DDIM also convert +their final tensors directly to NumPy, which rejects bfloat16. Use the reviewed +float32 recipe. These starter defaults do not grant Auto resource eligibility or +qualify every hardware/model combination. + ## FLUX adapter boundaries - `FluxControlNetPipeline`, `FluxControlNetImg2ImgPipeline` and @@ -20,6 +39,10 @@ remain in `modules.ModularDiffusers`. mapping. A separate explicit override controls distilled guidance. Disabling or omitting the override preserves old calls. ControlNet T2I instead exposes distilled guidance primarily and true CFG secondarily. Do not infer equivalence from labels. + Newly resolved ordinary operations and connected workflows explicitly enable + the distilled-guidance override with the selected model's recommendation and + initialize True CFG to 1 (disabled). This authoring default does not rewrite + saved nodes or run during dynamic field updates. All model/adapter files use the existing Hugging Face Hub resolver and immutable catalog revisions. Optional-runtime, resource, trust and local-file boundaries diff --git a/modules/DiffusersImage/call_inputs.py b/modules/DiffusersImage/call_inputs.py index 5da88dfa..68805713 100644 --- a/modules/DiffusersImage/call_inputs.py +++ b/modules/DiffusersImage/call_inputs.py @@ -16,6 +16,7 @@ _NEGATIVE = ('negative_prompt_2', 'negative_prompt_embeds', 'negative_pooled_prompt_embeds') _FLUX2 = (*_COMMON, 'text_encoder_out_layers', 'attention_kwargs') PIPELINE_CALL_INPUTS = { + 'QwenImage21Pipeline': ('num_images_per_prompt', 'sigmas', 'generator', 'latents', 'use_kv_cache'), 'FluxPipeline': (*_FLUX1, *_NEGATIVE, *IP_ADAPTER_INPUTS), 'FluxImg2ImgPipeline': (*_FLUX1, *_NEGATIVE, *IP_ADAPTER_INPUTS), 'FluxInpaintPipeline': (*_FLUX1, *_NEGATIVE, 'masked_image_latents', *IP_ADAPTER_INPUTS), @@ -36,6 +37,10 @@ 'prompt_2', 'prompt_embeds', 'pooled_prompt_embeds'), } +# Materialize reviewed native defaults at dispatch so receipts describe the +# actual choice even when an older workflow has no corresponding input field. +CALL_INPUT_DEFAULTS = {'QwenImage21Pipeline': {'use_kv_cache': True}} + # Append-only presentation revisions allow old saved contracts without admitting # arbitrary partial field maps or changing any historical execution values. CALL_INPUT_ADDITIONS = ( @@ -44,6 +49,7 @@ frozenset({'output_type', 'latents_out'}), frozenset(IP_ADAPTER_INPUTS), frozenset({'control_mode'}), + frozenset({'use_kv_cache'}), ) _TENSORS = { @@ -61,6 +67,7 @@ 'ip_adapter_image': 'image', 'negative_ip_adapter_image': 'image', 'ip_adapter_image_embeds': 'tensor', 'negative_ip_adapter_image_embeds': 'tensor', 'control_mode': 'int', + 'use_kv_cache': 'bool', } CALL_INPUT_PARAMS = { name: { @@ -70,6 +77,12 @@ } for name, kind in _TYPES.items() } +CALL_INPUT_PARAMS['use_kv_cache'] = { + 'label': 'Reuse attention context', 'type': 'bool', 'hidden': True, + 'description': 'Prefill fixed prompt and reference context once, then reuse it during denoising. ' + 'Uses extra memory. Changing this setting can change reduced-precision outputs. ' + 'The cache belongs to one generation and is never shared with another request.', +} def _number(value: Any, field: str, lower: float, upper: float, *, integer: bool = False): @@ -86,7 +99,10 @@ def _number(value: Any, field: str, lower: float, upper: float, *, integer: bool def normalize_call_inputs(pipeline_class: str, values: dict[str, Any]) -> dict[str, Any]: allowed = PIPELINE_CALL_INPUTS.get(pipeline_class, ()) - selected = {key: values[key] for key in CALL_INPUT_PARAMS if values.get(key) is not None} + selected = { + **CALL_INPUT_DEFAULTS.get(pipeline_class, {}), + **{key: values[key] for key in CALL_INPUT_PARAMS if values.get(key) is not None}, + } unsupported = selected.keys() - set(allowed) if unsupported: raise ValueError(f'{pipeline_class} does not support optional input(s): {", ".join(sorted(unsupported))}. Disconnect these inputs or choose a compatible pipeline.') @@ -139,9 +155,9 @@ def normalize_call_inputs(pipeline_class: str, values: dict[str, Any]) -> dict[s result[key] = _number(value, key, 4096, 16 * 1024 * 1024, integer=True) elif key == 'caption_upsample_temperature': result[key] = _number(value, key, 0, 10) - elif key == '_auto_resize': + elif key in ('_auto_resize', 'use_kv_cache'): if type(value) is not bool: - raise ValueError('_auto_resize must be a boolean.') + raise ValueError(f'{key} must be a boolean.') result[key] = value elif key in _TENSORS or key == 'generator': import torch diff --git a/modules/DiffusersImage/main.py b/modules/DiffusersImage/main.py index 185f1c4e..6a3e4648 100644 --- a/modules/DiffusersImage/main.py +++ b/modules/DiffusersImage/main.py @@ -14,7 +14,7 @@ from modiff.NodeBase import NodeBase from modules.DiffusersImage.call_inputs import ( - CALL_INPUT_ADDITIONS, CALL_INPUT_PARAMS, PIPELINE_CALL_INPUTS, apply_call_inputs, normalize_call_inputs, + CALL_INPUT_ADDITIONS, CALL_INPUT_DEFAULTS, CALL_INPUT_PARAMS, PIPELINE_CALL_INPUTS, apply_call_inputs, normalize_call_inputs, record_image_call_inputs, ) from modules.DiffusersImage.image_prompt_adapter import ( @@ -148,6 +148,7 @@ class ImagePipelineAdapter: prompt_embedding_encoder_dtype: str | None = None prompt_embedding_mask_modes: frozenset[str] = frozenset() max_inference_steps: int = 100 + two_step_intermediate_timestep: bool = False min_output_side: int = 16 max_output_side: int = 2048 output_side_step: int = 16 @@ -176,6 +177,7 @@ class ImagePipelineAdapter: max_reference_aspect_ratio: float | None = None enable_prompt_rewrite: bool | None = None clean_caption: bool | None = None + use_resolution_binning: bool | None = None cfg_trunc_ratio: float | None = None cfg_normalization: bool | None = None unconditional_optional_fields: tuple[str, ...] = () @@ -197,15 +199,17 @@ def __post_init__(self) -> None: raise ValueError("An upstream image pipeline class cannot be blank.") if not 1 <= self.max_inference_steps <= 100: raise ValueError("Image adapters must bound inference steps between 1 and 100.") - if not 16 <= self.min_output_side <= self.max_output_side <= 2048: - raise ValueError("Image adapters must bound output sides between 16 and 2048.") + if type(self.two_step_intermediate_timestep) is not bool: + raise ValueError("Intermediate-timestep policy must be an exact boolean.") + if not 16 <= self.min_output_side <= self.max_output_side <= 4096: + raise ValueError("Image adapters must bound output sides between 16 and 4096.") if self.output_side_step not in {16, 32, 64}: raise ValueError("Image adapter output-side increments must be 16, 32, or 64 pixels.") if self.min_output_side % self.output_side_step or ( self.max_output_side - self.min_output_side ) % self.output_side_step: raise ValueError("Image adapter output-side bounds must align to their declared increment.") - if not self.min_output_side**2 <= self.max_output_pixels <= _MAX_IMAGE_OUTPUT_PIXELS: + if not self.min_output_side**2 <= self.max_output_pixels <= 5 * 1024 * 1024: raise ValueError("Image adapters must declare a bounded output-pixel ceiling covering the minimum size.") if not 0.0 <= self.maximum_guidance_scale <= 50.0: raise ValueError("The maximum text guidance scale must be between 0 and 50.") @@ -287,6 +291,8 @@ def __post_init__(self) -> None: raise ValueError("Image reference aspect-ratio bounds must be positive and ordered.") if self.enable_prompt_rewrite is not None and type(self.enable_prompt_rewrite) is not bool: raise ValueError("Image prompt rewriting must be an exact boolean when configured.") + if self.use_resolution_binning is not None and type(self.use_resolution_binning) is not bool: + raise ValueError("Image resolution binning must be an exact boolean when configured.") if self.clean_caption is not None and type(self.clean_caption) is not bool: raise ValueError("Image caption cleaning must be an exact boolean when configured.") if self.cfg_trunc_ratio is not None and not 0.0 <= self.cfg_trunc_ratio <= 1.0: @@ -369,6 +375,11 @@ def mode_options(self) -> tuple[str, ...]: return tuple(mode for mode in _IMAGE_MODE_ORDER if mode in self.modes) def apply_generation_parameters(self, pipeline: Any, values: dict[str, Any], target: dict[str, Any]) -> None: + if self.two_step_intermediate_timestep and values["num_inference_steps"] != 2: + # SCM's upstream default is a specialized two-step schedule. Other + # declared step counts use its native evenly spaced schedule; never + # silently replace the user's requested number of inference steps. + target["intermediate_timesteps"] = None aliases = { "negative_prompt": "negative_prompt", "width": "width", @@ -419,6 +430,10 @@ def apply_generation_parameters(self, pipeline: Any, values: dict[str, Any], tar target[self.conditioning_scale_parameter] = values["conditioning_scale"] if self.enable_prompt_rewrite is not None and supports_arg(pipeline, "enable_prompt_rewrite"): target["enable_prompt_rewrite"] = self.enable_prompt_rewrite + if self.use_resolution_binning is not None: + if not supports_arg(pipeline, "use_resolution_binning"): + raise ValueError(f"{self.pipeline_class} does not expose its reviewed resolution-binning control.") + target["use_resolution_binning"] = self.use_resolution_binning if self.clean_caption is not None and supports_arg(pipeline, "clean_caption"): target["clean_caption"] = self.clean_caption if self.cfg_trunc_ratio is not None and supports_arg(pipeline, "cfg_trunc_ratio"): @@ -575,6 +590,21 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe unconditional_optional_fields=("class_label",), safe_serialization_required=True, ), + "QwenImage21Pipeline": ImagePipelineAdapter( + "QwenImage21Pipeline", + frozenset({"text_to_image", "edit_image", "multi_image_reference_edit"}), + "Qwen/Qwen-Image-2.1", + guidance_parameter="true_cfg_scale", + image_guidance_parameter=None, + safe_serialization_required=True, + max_reference_images=10, + min_output_side=32, + output_side_step=32, + # The publisher's 2K aspect presets include 2752x1536. Keep the + # cumulative allocation bounded; existing adapters retain their limits. + max_output_side=4096, + max_output_pixels=5 * 1024 * 1024, + ), "QwenImagePipeline": ImagePipelineAdapter( "QwenImagePipeline", frozenset({"text_to_image"}), @@ -708,10 +738,12 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe HUNYUAN_DIT_DISTILLED_REPO, safe_serialization_required=True, max_inference_steps=25, - min_output_side=1024, - max_output_side=1024, + min_output_side=512, + max_output_side=2048, output_side_step=32, max_output_pixels=1024 * 1024, + # Honor explicit graph dimensions instead of upstream's nearest preset. + use_resolution_binning=False, max_sequence_length=256, ), "HunyuanDiTPAGPipeline": ImagePipelineAdapter( @@ -721,10 +753,12 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe artifact_pipeline_classes=("HunyuanDiTPipeline",), safe_serialization_required=True, max_inference_steps=25, - min_output_side=1024, - max_output_side=1024, + min_output_side=512, + max_output_side=2048, output_side_step=32, max_output_pixels=1024 * 1024, + # Honor explicit graph dimensions instead of upstream's nearest preset. + use_resolution_binning=False, max_sequence_length=256, pag_applied_layers=("blocks.14",), ), @@ -735,10 +769,12 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe artifact_pipeline_classes=("HunyuanDiTPipeline",), safe_serialization_required=True, max_inference_steps=50, - min_output_side=1024, - max_output_side=1024, + min_output_side=512, + max_output_side=2048, output_side_step=32, max_output_pixels=1024 * 1024, + # Honor explicit graph dimensions instead of upstream's nearest preset. + use_resolution_binning=False, max_sequence_length=256, conditioning_kind="controlnet", default_conditioning_repo=HUNYUAN_DIT_CONTROLNET_CANNY_REPO, @@ -839,6 +875,7 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe SANA_SPRINT_REPO, safe_serialization_required=True, max_inference_steps=4, + two_step_intermediate_timestep=True, max_sequence_length=300, ), "SanaSprintImg2ImgPipeline": ImagePipelineAdapter( @@ -848,6 +885,7 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe artifact_pipeline_classes=("SanaSprintPipeline",), safe_serialization_required=True, max_inference_steps=4, + two_step_intermediate_timestep=True, max_sequence_length=300, ), "PixArtSigmaPipeline": ImagePipelineAdapter( @@ -875,8 +913,8 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe safe_serialization_required=True, weight_variant="fp16", max_inference_steps=50, - min_output_side=1024, - max_output_side=1024, + min_output_side=512, + max_output_side=2048, output_side_step=64, max_output_pixels=1024 * 1024, max_sequence_length=128, @@ -889,8 +927,8 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe safe_serialization_required=True, weight_variant="fp16", max_inference_steps=50, - min_output_side=1024, - max_output_side=1024, + min_output_side=512, + max_output_side=2048, output_side_step=64, max_output_pixels=1024 * 1024, max_sequence_length=128, @@ -1080,8 +1118,8 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe ERNIE_IMAGE_TURBO_REPO, safe_serialization_required=True, max_inference_steps=8, - min_output_side=1024, - max_output_side=1024, + min_output_side=512, + max_output_side=2048, output_side_step=32, max_output_pixels=1024 * 1024, fixed_guidance_scale=1.0, @@ -1102,8 +1140,8 @@ def prepare_prompt_embeddings(self, pipeline: Any, values: dict[str, Any], targe prompt_embedding_dtype_component="transformer", native_prompt_encoding=True, max_inference_steps=50, - min_output_side=1024, - max_output_side=1024, + min_output_side=512, + max_output_side=2048, output_side_step=32, max_output_pixels=1024 * 1024, max_sequence_length=2048, @@ -1667,6 +1705,10 @@ def _image_field_contract(*visible_fields: str) -> ImageModeFieldContract: ) IMAGE_MODE_FIELD_CONTRACTS = { + "QwenImage21Pipeline": { + mode: _image_field_contract("negative_prompt", "width", "height", "guidance_scale") + for mode in ("text_to_image", "edit_image", "multi_image_reference_edit") + }, "DDPMPipeline": { "unconditional_image": _image_field_contract(), }, @@ -2360,6 +2402,18 @@ def image_model_field_options(adapter: ImagePipelineAdapter) -> dict[str, Any]: } +def image_operation_loader_defaults(adapter: ImagePipelineAdapter) -> dict[str, Any]: + """Seed new canonical operations with the reviewed task's loading recipe. + + Some unconditional samplers lack execution-device-aware offload or + convert tensors directly to NumPy. Their existing creator recipe is + resident float32. Do not rewrite saved loaders or their generic schema. + """ + if adapter.modes == frozenset({"unconditional_image"}): + return {"dtype": "float32", "auto_offload": False, "offload_mode": OFFLOAD_MODE_NONE} + return {} + + def image_loader_field_params(adapter: ImagePipelineAdapter) -> dict[str, dict[str, Any]]: """Selected loader presentation shared by ordinary fields and compiled Blocks. @@ -2382,7 +2436,15 @@ def image_loader_field_params(adapter: ImagePipelineAdapter) -> dict[str, dict[s } -_LATENT_OUTPUT_PIPELINES = frozenset(PIPELINE_CALL_INPUTS) - {'FluxReduxPipeline'} +# An optional call input does not establish a latent layout or a decoder. +# Admit exact producers only after their matching decode path is reviewed. +_LATENT_OUTPUT_PIPELINES = frozenset({ + 'FluxPipeline', 'FluxImg2ImgPipeline', 'FluxInpaintPipeline', 'FluxKontextPipeline', + 'FluxKontextInpaintPipeline', 'FluxFillPipeline', 'FluxControlPipeline', + 'FluxControlImg2ImgPipeline', 'FluxControlInpaintPipeline', 'FluxControlNetPipeline', + 'FluxControlNetImg2ImgPipeline', 'FluxControlNetInpaintPipeline', 'Flux2Pipeline', + 'Flux2KleinPipeline', 'Flux2KleinInpaintPipeline', 'Flux2KleinKVPipeline', 'QwenImage21Pipeline', +}) def _image_output_options(adapter: ImagePipelineAdapter, action: str) -> list[str]: @@ -2413,6 +2475,9 @@ def image_pipeline_contract(adapter: ImagePipelineAdapter, mode: str) -> dict[st field_params = field_contract.field_param_overlay() for key in PIPELINE_CALL_INPUTS.get(adapter.load_pipeline_class, ()): field_params[key] = {"hidden": False} + defaults = CALL_INPUT_DEFAULTS.get(adapter.load_pipeline_class, {}) + if key in defaults: + field_params[key]["default"] = defaults[key] if adapter.secondary_guidance_parameter is not None: field_params["guidance_scale"] = {**field_params["guidance_scale"], "label": adapter.guidance_label} field_params["use_guidance_scale_2"] = { @@ -2462,8 +2527,13 @@ def image_pipeline_contract(adapter: ImagePipelineAdapter, mode: str) -> dict[st action: [candidate for candidate in adapter.mode_options if candidate in accepted_modes] for action, accepted_modes in IMAGE_ACTION_MODES.items() } + connection_actions = None if adapter.load_pipeline_class in _LATENT_OUTPUT_PIPELINES: action = next(action for action, modes in actions.items() if mode in modes) + connection_actions = { + **{candidate: modes for candidate, modes in actions.items() if modes}, + "DecodeLatents": [mode], + } field_params['output_type'] = {'options': _image_output_options(adapter, action)} field_params['latents_out'] = {'hidden': False} contract = { @@ -2476,6 +2546,8 @@ def image_pipeline_contract(adapter: ImagePipelineAdapter, mode: str) -> dict[st "actions": {action: modes for action, modes in actions.items() if modes}, "fieldParams": field_params, } + if connection_actions is not None: + contract["connectionActions"] = connection_actions if adapter.max_output_pixels != _MAX_IMAGE_OUTPUT_PIXELS: contract["maxOutputPixels"] = adapter.max_output_pixels if mode == "unconditional_image": @@ -2498,6 +2570,52 @@ def image_pipeline_contract(adapter: ImagePipelineAdapter, mode: str) -> dict[st return contract +def image_action_field_params(contract: dict[str, Any], action: str) -> dict: + """The same task overlay for dynamic nodes and read-only operation discovery.""" + if action == "UnconditionalGenerate": + return contract.get("actionFieldParams", {}) + if action == "PredictMap": + return {} + fields = { + field: {"hidden": True} + for field in ("use_guidance_scale_2", "guidance_scale_2", "latents_out", *CALL_INPUT_PARAMS) + } + adapter = get_image_pipeline_adapter(contract["pipelineClass"]) + return { + **fields, + **contract["fieldParams"], + # Publish the same bound as preflight for each selected model. Keep it + # out of the persisted signal identity and do not change authored values. + "num_inference_steps": {"min": 1, "max": adapter.max_inference_steps}, + } + + +def get_image_operation_contracts(modules) -> list[dict]: + from modiff.operation_contracts import build_pipeline_operation_contract + + result = [] + for pipeline_class, adapter in sorted(IMAGE_PIPELINE_ADAPTERS.items()): + for mode in adapter.mode_options: + contract = image_pipeline_contract(adapter, mode) + actions = [("LoadPipeline", "diffusion.load_models", image_loader_field_params(adapter))] + for action, modes in contract["actions"].items(): + if mode in modes: + operation = { + "PredictMap": "diffusion.predict_map", + "LayerDecompose": "diffusion.decompose_layers", + }.get(action, "diffusion.generate_image") + actions.append((action, operation, image_action_field_params(contract, action))) + for action, operation, fields in actions: + record = build_pipeline_operation_contract( + modules, pipeline_class=pipeline_class, task=mode, operation_id=operation, + node_key=f"modules.DiffusersImage.{action}", field_overrides=fields, + loader=action == "LoadPipeline", + ) + if record is not None: + result.append(record) + return result + + def _compatible_image_contract(signal: Any, expected: dict[str, Any]) -> bool: """Accept only the exact pre-secondary-guidance form, never arbitrary drift. @@ -3709,6 +3827,11 @@ def build_qwen_pipeline_quantization_config( def add_progress_callback(node: NodeBase, pipeline: Any, call_kwargs: dict[str, Any], steps: int): + if not supports_arg(pipeline, "callback_on_step_end"): + # Some upstream pipelines expose only a console progress bar. Use the + # existing indeterminate convention, never a fabricated step or ETA. + node.progress(-1, phase="denoising", message="Generating image") + return node.progress( 0, phase="denoising", @@ -3720,8 +3843,6 @@ def add_progress_callback(node: NodeBase, pipeline: Any, call_kwargs: dict[str, # ErnieImage and similar pipelines invoke callback_on_step_end before they # assign this Diffusers progress field. NodeBase.pipe_callback reads it. pipeline._num_timesteps = steps - if not supports_arg(pipeline, "callback_on_step_end"): - return def callback(pipe, step_index, timestep, callback_kwargs): # NodeBase owns the common cancellation contract. Propagating it here @@ -3742,27 +3863,9 @@ def prepare_reference_images(image: Any, adapter: ImagePipelineAdapter) -> Any: return image[0] if isinstance(image, (list, tuple)) and image else image if adapter.multi_image_strategy != "stitch_horizontal": return image if isinstance(image, list) else list(image) + from modules.ImageOperations.main import stitch_reference_images - from PIL import Image - - if not all(isinstance(item, Image.Image) for item in image): - raise ValueError("Horizontal multi-reference stitching currently requires PIL image inputs.") - converted = [item.convert("RGB") for item in image] - target_height = max(item.height for item in converted) - resized = [ - item - if item.height == target_height - else item.resize( - (max(1, round(item.width * target_height / item.height)), target_height), Image.Resampling.LANCZOS - ) - for item in converted - ] - canvas = Image.new("RGB", (sum(item.width for item in resized), target_height)) - left = 0 - for item in resized: - canvas.paste(item, (left, 0)) - left += item.width - return canvas + return stitch_reference_images(image) def prepare_reference_prompt(prompt: Any, image: Any, adapter: ImagePipelineAdapter) -> Any: @@ -3950,8 +4053,19 @@ def load_cached_image_component(factory, model_id: str, **load_kwargs): """Keep missing-cache recovery actionable without enabling inference downloads.""" from huggingface_hub.errors import LocalEntryNotFoundError + target = model_id + if ( + load_kwargs.get("local_files_only") is True + and load_kwargs.get("revision") + and getattr(factory, "config_name", None) == "model_index.json" + ): + from utils.huggingface import exact_cached_snapshot_path + + # Model Manager installs the pipeline's runtime files, not unrelated + # weight folders. Preserve its exact commit and managed-cache boundary. + target = exact_cached_snapshot_path(model_id, load_kwargs["revision"]) try: - return factory.from_pretrained(model_id, **load_kwargs) + return factory.from_pretrained(target, **load_kwargs) except LocalEntryNotFoundError as error: revision = load_kwargs.get("revision") selected = f"{model_id}@{revision}" if revision else model_id @@ -4083,7 +4197,8 @@ class LoadPipeline(NodeBase): "label": "Pre-quantized Transformer", "display": "input", "type": "any", - "description": "Reviewed single-file transformer component for exact base-pipeline assembly.", + "required": False, + "description": "Optional reviewed single-file transformer component for exact base-pipeline assembly.", }, "image_prompt_adapter": {"label": "Image Prompt Adapter", "display": "input", "type": "diffusers_image_prompt_adapter", @@ -4508,6 +4623,7 @@ class UnconditionalGenerate(NodeBase): {"action": "value", "target": "image_contract"}, {"action": "exec", "data": "update_image_contract"}, ], + "signalCompatibility": {"required": True, "action": "$node"}, }, "image_contract": { "label": "Image Contract", @@ -4565,7 +4681,7 @@ def update_image_contract(self, values, ref): raise ValueError("The connected image pipeline published a stale or mismatched task contract.") if mode not in expected_signal["actions"].get(self.class_name, ()): raise ValueError("The connected image pipeline does not support unconditional image generation.") - for field, params in expected_signal.get("actionFieldParams", {}).items(): + for field, params in image_action_field_params(expected_signal, self.class_name).items(): if field in self.__class__.params: self.set_field_params(field, params) @@ -4632,6 +4748,7 @@ class PredictMap(NodeBase): {"action": "value", "target": "image_contract"}, {"action": "exec", "data": "update_image_contract"}, ], + "signalCompatibility": {"required": True, "action": "$node"}, }, "image_contract": { "label": "Image Contract", @@ -4759,6 +4876,7 @@ class Generate(NodeBase): {"action": "value", "target": "image_contract"}, {"action": "exec", "data": "update_image_contract"}, ], + "signalCompatibility": {"required": True, "action": "$node"}, }, "image_contract": { "label": "Image Contract", @@ -4942,12 +5060,9 @@ def update_image_contract(self, values, ref): if mode not in expected_signal["actions"].get(self.class_name, ()): raise ValueError("The connected image pipeline does not support this generic image action.") - for field in ("use_guidance_scale_2", "guidance_scale_2", 'latents_out', *CALL_INPUT_PARAMS): - if field not in expected_signal["fieldParams"]: - self.set_field_params(field, {"hidden": True}) if 'output_type' not in expected_signal['fieldParams']: self.set_field_params('output_type', {'options': _image_output_options(adapter, self.class_name)}) - for field, params in expected_signal["fieldParams"].items(): + for field, params in image_action_field_params(expected_signal, self.class_name).items(): if field in self.__class__.params: self.set_field_params(field, params) @@ -5261,7 +5376,13 @@ class DecodeLatents(NodeBase): label = 'Decode Image Latents' category = 'Diffusers Image' params = { - 'pipeline': {'label': 'Pipeline', 'type': 'image_diffusion_pipeline', 'display': 'input', 'required': True}, + 'pipeline': { + 'label': 'Pipeline', + 'type': 'image_diffusion_pipeline', + 'display': 'input', + 'required': True, + 'signalCompatibility': {'required': True, 'action': '$node'}, + }, 'latents': {'label': 'Latents', 'type': 'tensor', 'display': 'input', 'required': True}, 'width': {'label': 'Width', 'type': 'int', 'default': 1024, 'min': 64, 'max': 4096}, 'height': {'label': 'Height', 'type': 'int', 'default': 1024, 'min': 64, 'max': 4096}, @@ -5275,18 +5396,26 @@ def execute(self, pipeline, latents, width=1024, height=1024, output_type='pil') import torch adapter = _image_pipeline_adapter(pipeline) if adapter.load_pipeline_class not in _LATENT_OUTPUT_PIPELINES: - raise ValueError('Decode Image Latents requires a reviewed FLUX pipeline with explicit latent output.') + raise ValueError('Decode Image Latents requires a reviewed pipeline with a matching explicit latent output.') + width = _bounded_image_int(width, field='width', default=1024, minimum=adapter.min_output_side, + maximum=adapter.max_output_side, step=adapter.output_side_step) + height = _bounded_image_int(height, field='height', default=1024, minimum=adapter.min_output_side, + maximum=adapter.max_output_side, step=adapter.output_side_step) + if output_type not in ('pil', 'np', 'pt'): + raise ValueError('Decode Image Latents output_type must be pil, np, or pt.') if not isinstance(latents, torch.Tensor) or not latents.is_floating_point() or latents.device.type == 'meta': raise ValueError('Latents must be a materialized floating-point Tensor from the matching pipeline.') vae = pipeline.vae - channels = vae.config.latent_channels + qwen21 = adapter.load_pipeline_class == 'QwenImage21Pipeline' + channels = vae.config.z_dim if qwen21 else vae.config.latent_channels # FLUX.1 returns packed normalized tokens. FLUX.2 returns already # unpatchified, denormalized VAE latents. Neither conversion is implicit # at an arbitrary tensor connection; this explicit consumer owns decode. flux2 = adapter.load_pipeline_class.startswith('Flux2') - scale = 2 ** (len(vae.config.block_out_channels) - 1) + scale = pipeline.vae_scale_factor if qwen21 else 2 ** (len(vae.config.block_out_channels) - 1) latent_h, latent_w = height // scale, width // scale - expected = (channels, latent_h, latent_w) if flux2 else ((latent_h // 2) * (latent_w // 2), channels * 4) + expected = ((latent_h * latent_w, channels) if qwen21 else + (channels, latent_h, latent_w) if flux2 else ((latent_h // 2) * (latent_w // 2), channels * 4)) if (tuple(latents.shape[1:]) != expected or not 1 <= latents.shape[0] <= 8 or width % (scale * 2) or height % (scale * 2) or latents.shape[0] * width * height > 16 * 1024 * 1024): @@ -5294,10 +5423,17 @@ def execute(self, pipeline, latents, width=1024, height=1024, output_type='pil') 'Use its matching latent output and set Width/Height to the original generation dimensions.') with torch.inference_mode(): value = latents.to(device=pipeline._execution_device, dtype=vae.dtype) - if not flux2: + if qwen21: + value = pipeline._unpack_latents(value, height, width, pipeline.vae_scale_factor) + mean = torch.as_tensor(vae.config.latents_mean, device=value.device, dtype=value.dtype).view(1, channels, 1, 1, 1) + std = torch.as_tensor(vae.config.latents_std, device=value.device, dtype=value.dtype).view(1, channels, 1, 1, 1) + value = value * std + mean + elif not flux2: value = pipeline._unpack_latents(value, height, width, pipeline.vae_scale_factor) value = value / vae.config.scaling_factor + (getattr(vae.config, 'shift_factor', 0) or 0) decoded = vae.decode(value, return_dict=False)[0] + if qwen21: + decoded = decoded[:, :, 0] images = pipeline.image_processor.postprocess(decoded, output_type=output_type) pipeline.maybe_free_model_hooks() actual_width, actual_height = output_image_dimensions(images, output_type) @@ -5311,7 +5447,13 @@ class LoadAdapter(NodeBase): category = "Diffusers Image" resizable = True params = { - "pipeline": {"label": "Pipeline", "display": "input", "type": "image_diffusion_pipeline", "required": True}, + "pipeline": { + "label": "Pipeline", + "display": "input", + "type": "image_diffusion_pipeline", + "required": True, + "onSignal": {"action": "signal", "target": "output"}, + }, "adapter_path": { "label": "Adapter", "display": "modelselect", @@ -5347,7 +5489,12 @@ class LoadAdapter(NodeBase): "max": 2, "step": 0.01, }, - "output": {"label": "Pipeline", "display": "output", "type": "image_diffusion_pipeline"}, + "output": { + "label": "Pipeline", + "display": "output", + "type": "image_diffusion_pipeline", + "signal": {"direction": "output", "origin": "pipeline", "value": ""}, + }, } @staticmethod diff --git a/modules/DiffusersRuntime/main.py b/modules/DiffusersRuntime/main.py index cda8e7d6..f1d14692 100644 --- a/modules/DiffusersRuntime/main.py +++ b/modules/DiffusersRuntime/main.py @@ -417,7 +417,12 @@ def configure(enabled: bool, enable_names: tuple[str, ...], disable_names: tuple if method is None: unsupported.append(label) return - method() + try: + method() + except NotImplementedError: + # An inherited optional hook is not a concrete implementation. + unsupported.append(label) + return applied.append({"feature": label, "enabled": enabled}) configure(slicing, ("enable_slicing", "enable_vae_slicing"), ("disable_slicing", "disable_vae_slicing"), "slicing") @@ -1020,9 +1025,34 @@ def assert_runtime_quantization_full_residency( return {"source_weight_bytes": source_bytes, "required_bytes": required, "free_bytes": free_bytes} +def configure_rocm_vision_attention(pipeline: Any, *, torch_module: Any = None) -> list[str]: + """Keep ROCm vision SDPA failures out of multimodal prompt embeddings. + + Use Transformers' public subconfig setter, leaving text and Diffusers + denoiser attention alone. Only the default SDPA vision implementation is + replaced; explicit alternate implementations and non-ROCm hosts are retained. + """ + if torch_module is None: + import torch as torch_module + if not getattr(getattr(torch_module, "version", None), "hip", None): + return [] + applied = [] + for name, component in getattr(pipeline, "components", {}).items(): + vision = getattr(getattr(component, "config", None), "vision_config", None) + setter = getattr(component, "set_attn_implementation", None) + if getattr(vision, "_attn_implementation", None) != "sdpa" or not callable(setter): + continue + setter({"vision_config": "eager"}) + if vision._attn_implementation != "eager": + raise RuntimeError(f"Could not apply stable ROCm vision attention to {name}.") + applied.append(name) + return applied + + def apply_execution_recipe_to_pipeline(pipeline: Any, recipe: dict[str, Any]) -> dict[str, Any]: + vision_attention = configure_rocm_vision_attention(pipeline) if not recipe: - return {"attention": None, "vae": None} + return {"attention": None, "vae": None, **({"rocmVisionAttention": vision_attention} if vision_attention else {})} vae = configure_vae_memory( pipeline, slicing=bool(recipe.get("vae_slicing", True)), @@ -1062,6 +1092,7 @@ def apply_execution_recipe_to_pipeline(pipeline: Any, recipe: dict[str, Any]) -> ) return { "attention": attention, + **({"rocmVisionAttention": vision_attention} if vision_attention else {}), "vae": vae, "layerwiseCasting": layerwise, "channelsLast": channels_last, diff --git a/modules/DiffusersThreeD/main.py b/modules/DiffusersThreeD/main.py index 888ac70d..c907782b 100644 --- a/modules/DiffusersThreeD/main.py +++ b/modules/DiffusersThreeD/main.py @@ -61,6 +61,7 @@ def signal_value(self) -> dict[str, Any]: "mediaKind": "three_d", "pipelineClass": self.pipeline_class, "mode": self.mode, + "actions": {"GenerateRenderedArtifact": [self.mode]}, "repository": self.default_repo, "outputContract": "rendered_orbit", "inputContract": ( @@ -126,6 +127,26 @@ def signal_value(self) -> dict[str, Any]: DEFAULT_THREE_D_CONTRACT = SHAP_E_ADAPTER.signal_value() +def get_three_d_operation_contracts(modules) -> list[dict]: + from modiff.operation_contracts import build_pipeline_operation_contract + + result = [] + for pipeline_class, adapter in sorted(THREE_D_PIPELINE_ADAPTERS.items()): + for action, operation in ( + ("LoadPipeline", "diffusion.load_models"), + ("GenerateRenderedArtifact", "diffusion.render_3d"), + ): + record = build_pipeline_operation_contract( + modules, pipeline_class=pipeline_class, task=adapter.mode, operation_id=operation, + node_key=f"modules.DiffusersThreeD.{action}", + field_overrides=adapter.signal_value()["fieldParams"] if action != "LoadPipeline" else None, + loader=action == "LoadPipeline", + ) + if record is not None: + result.append(record) + return result + + def _require_adapter(pipeline_class: Any, mode: Any) -> ThreeDPipelineAdapter: adapter = THREE_D_PIPELINE_ADAPTERS.get(pipeline_class) if adapter is None or mode != adapter.mode: @@ -472,6 +493,7 @@ class GenerateRenderedArtifact(NodeBase): {"action": "value", "target": "three_d_contract"}, {"action": "exec", "data": "update_three_d_contract"}, ], + "signalCompatibility": {"required": True, "action": "$node"}, }, "three_d_contract": { "label": "3D Contract", diff --git a/modules/DiffusersVideo/main.py b/modules/DiffusersVideo/main.py index 3a46fd0f..08f66b00 100644 --- a/modules/DiffusersVideo/main.py +++ b/modules/DiffusersVideo/main.py @@ -1034,13 +1034,45 @@ def _pipeline_adapter(pipeline: Any) -> VideoPipelineAdapter: ) +def get_video_operation_contracts(modules) -> list[dict]: + from modiff.operation_contracts import build_pipeline_operation_contract + + result = [] + for pipeline_class, adapter in sorted(VIDEO_PIPELINE_ADAPTERS.items()): + for mode in adapter.modes: + fields = get_video_mode_field_contract(adapter, mode).field_param_overlay() + actions = [("LoadPipeline", "diffusion.load_models"), ("Generate", "diffusion.generate_video")] + if "audio" in adapter.output_media: + actions.append(("GenerateVideoAudio", "diffusion.generate_video_audio")) + for action, operation in actions: + record = build_pipeline_operation_contract( + modules, pipeline_class=pipeline_class, task=mode, operation_id=operation, + node_key=f"modules.DiffusersVideo.{action}", + field_overrides=fields if action != "LoadPipeline" else None, + loader=action == "LoadPipeline", + ) + if record is not None: + result.append(record) + return result + + def _adapter_signal(adapter: VideoPipelineAdapter) -> dict[str, Any]: + actions = { + "Generate": list(adapter.modes), + "GenerateShotJob": list(adapter.modes), + } + if "audio" in adapter.output_media: + actions["GenerateVideoAudio"] = list(adapter.modes) + actions["GenerateLTX2"] = list(adapter.modes) + if "text_to_video" in adapter.modes: + actions["GenerateSequence"] = ["text_to_video"] return { "schemaVersion": 1, "library": "diffusers", "mediaKind": "video", "pipelineClass": adapter.pipeline_class, "modes": list(adapter.modes), + "actions": actions, } @@ -2564,6 +2596,7 @@ class Generate(WanVACEGenerate): {"action": "value", "target": "video_contract"}, {"action": "exec", "data": "update_adapter_modes"}, ], + "signalCompatibility": {"required": True, "action": "$node"}, }, "video_contract": { "label": "Video Contract", @@ -4572,6 +4605,11 @@ class GenerateVideoAudio(NodeBase): "duration_seconds": {"label": "Audio Duration", "display": "output", "type": "float"}, } + def update_adapter_modes(self, values, ref): + # The shared pipeline socket and mode control declare this callback. + # Keep the same adapter-owned fields as video-only generation. + return Generate.update_adapter_modes(self, values, ref) + def __call__(self, **kwargs): values = dict(kwargs) values["mode"] = _require_video_mode(values.get("mode")) @@ -4755,6 +4793,7 @@ class GenerateShotJob(NodeBase): "display": "input", "type": "video_diffusion_pipeline", "required": True, + "signalCompatibility": {"required": True, "action": "$node"}, }, "job": {"label": "Shot Job", "display": "input", "type": "any", "required": True}, "previous_video": { @@ -4842,6 +4881,9 @@ class GenerateSequence(NodeBase): "total_frames": {"label": "Total frames", "display": "output", "type": "int"}, } + def update_adapter_modes(self, values, ref): + return Generate.update_adapter_modes(self, values, ref) + def execute(self, **kwargs): import json diff --git a/modules/HuggingFaceTransformers/README.md b/modules/HuggingFaceTransformers/README.md new file mode 100644 index 00000000..0cf907f0 --- /dev/null +++ b/modules/HuggingFaceTransformers/README.md @@ -0,0 +1,62 @@ +# Transformers depth workflows + +In Developer → Workflows → Depth estimation, select an installed depth model +and preview the connected workflow. Load Models and Predict Map use the same +ordinary graph, image input, output preview and Saved Block controls as other +workflows. The underlying task nodes are Load Depth Estimation Model and Predict +Depth; the two reviewed Depth Anything V2 variants share these nodes. + +The loader uses the official Hugging Face Transformers AutoImageProcessor and +AutoModelForDepthEstimation APIs. It requires the existing reviewed optional +Transformers runtime, an immutable Hub commit or an explicitly selected local +model, local files, native classes and safetensors. Browsing or opening a workflow +does not install a runtime, download weights or authorize repository Python. +The execution profiles declare the same optional-runtime boundary as the existing +Transformers text and vision nodes. Models remain in the existing graph cache. + +## Inputs and outputs + +Predict Depth takes one image. Processing Resolution `0` preserves the selected +processor's configured resize target; a positive value overrides that target. +The reviewed DPT processor preserves its configured aspect-ratio and patch-size +rules. The backend checks projected geometry before preprocessing, then verifies +actual tensor dimensions before model execution. Images with excessive aspect +ratios can require a connected Resize node. Match Input Resolution uses the +processor's official depth postprocessing to return the source dimensions. + +- **Prediction Map** follows MoDiff's existing float32 NHWC relative-depth + contract, with near `0` and far `1`. +- **Depth Preview** is an RGB grayscale visualization of that normalized map. +- **Native Depth** retains the model's floating-point values after optional + spatial interpolation, as a CPU float32 HW tensor. It is not min/max normalized. +- **Depth Details** records the immutable model receipt, processed/output shapes, + native range, depth polarity and normalization semantics. A constant map is + explicitly identified. + +Normalized preview intensity is not distance in metres. Relative inverse-depth +and model-declared metric depth use different polarities. The reviewed Depth +Anything configuration supplies the default; other models must specify their +documented polarity explicitly. A declaration of metric depth does not establish +measurement accuracy. Negative values introduced by upstream bicubic spatial +interpolation are retained in the native output rather than silently clipped. + +## Reviewed adapter scope + +The nodes are task-generic. The initial bounded preprocessing adapter uses the +native DPTImageProcessor contract; another AutoImageProcessor needs a reviewed +geometry/postprocessing adapter before execution. Selecting an arbitrary AutoModel +class alone cannot establish processor geometry, depth polarity or output units. +The two curated profiles pin the relative Small and Metric Outdoor Small models. +Auto resource qualification and Gallery eligibility remain separate from this +integration and require their own execution evidence. + +The source contracts are the official +[Transformers depth documentation](https://huggingface.co/docs/transformers/model_doc/depth_anything) +and the reviewed runtime's `models/dpt/image_processing_dpt.py`, +`models/depth_anything/modeling_depth_anything.py` and +`models/auto/modeling_auto.py`. The executable package provenance and versions +remain in `modiff/optional_runtimes.py`; no additional model library is introduced. + +When connecting the preview to a control model, use that model's documented +intensity convention. A compatible Image socket alone does not establish depth +polarity; an ordinary Invert processor can reverse the normalized preview. diff --git a/modules/HuggingFaceTransformers/depth.py b/modules/HuggingFaceTransformers/depth.py new file mode 100644 index 00000000..2968f3a9 --- /dev/null +++ b/modules/HuggingFaceTransformers/depth.py @@ -0,0 +1,120 @@ +"""Bounded adapter for the official DPT image-processor depth contract. + +AutoModel handles model selection. Processor geometry and depth polarity are +separate backend contracts; normalized previews never claim metric distances. +""" +from math import isfinite + + +def depth_geometry(processor, image, resolution): + """Bound the reviewed processor's resize before it allocates pixel tensors.""" + if type(processor).__name__ != "DPTImageProcessor": + raise ValueError("This depth processor has no reviewed bounded preprocessing contract.") + size = processor.size if resolution == 0 else {"height": resolution, "width": resolution} + if not isinstance(size, dict): + from transformers.image_utils import SizeDict + + if not isinstance(size, SizeDict): + raise ValueError("Depth preprocessing requires the native processor size contract.") + size = dict(size) + if set(size) != {"height", "width"}: + raise ValueError("Depth preprocessing requires explicit height and width.") + if any(type(v) is not int or not 1 <= v <= 2048 for v in size.values()): + raise ValueError("Depth preprocessing size must be between 1 and 2048 pixels.") + multiple = processor.ensure_multiple_of + if type(multiple) is not int or not 1 <= multiple <= 128: + raise ValueError("Depth preprocessing has an invalid patch multiple.") + if processor.do_pad or not processor.do_resize: + raise ValueError("Depth preprocessing requires the reviewed resize without padding.") + width, height = image.size + scale_h, scale_w = size["height"] / height, size["width"] / width + if processor.keep_aspect_ratio: + scale_h = scale_w if abs(1 - scale_w) < abs(1 - scale_h) else scale_h + scale_w = scale_h + processed_h = round(scale_h * height / multiple) * multiple + processed_w = round(scale_w * width / multiple) * multiple + if not (1 <= processed_h <= 4096 and 1 <= processed_w <= 4096 and processed_h * processed_w <= 4_194_304): + raise ValueError("Depth preprocessing exceeds the bounded image geometry; resize the source image first.") + return size, (1, 3, processed_h, processed_w) + + +def depth_convention(model, selected): + if selected in {"near_is_larger", "far_is_larger"}: + return selected, "operator_selected" + if selected != "model_default": + raise ValueError("Depth convention must be model_default, near_is_larger, or far_is_larger.") + config = getattr(model, "config", None) + # The AutoModel contract does not define a universal depth polarity. Infer + # only the reviewed native configuration; other models can state it explicitly. + if getattr(config, "model_type", None) == "depth_anything": + kind = getattr(config, "depth_estimation_type", None) + if kind == "relative": + return "near_is_larger", "relative_inverse_depth" + if kind == "metric": + maximum = getattr(config, "max_depth", None) + if type(maximum) in (int, float) and isfinite(maximum) and maximum > 0: + return "far_is_larger", "model_declared_metric_depth" + raise ValueError("This model does not declare a reviewed depth convention; choose its documented depth polarity.") + + +def predict_depth(model, processor, image, *, receipt, resolution, match_input, convention): + import torch + from modules.DiffusersImage.main import normalize_prediction_map + + size, expected_shape = depth_geometry(processor, image, resolution) + polarity, semantics = depth_convention(model, convention) + batch = processor(images=[image], size=size, return_tensors="pt") + if not hasattr(batch, "items") or set(batch) != {"pixel_values"}: + raise RuntimeError("Depth preprocessing returned an unexpected tensor schema.") + pixels = batch["pixel_values"] + if not isinstance(pixels, torch.Tensor) or tuple(pixels.shape) != expected_shape or not pixels.is_floating_point(): + raise RuntimeError("Depth preprocessing returned an unexpected pixel tensor.") + if not bool(torch.isfinite(pixels).all()): + raise RuntimeError("Depth preprocessing returned nonfinite pixel values.") + runtime = receipt["runtime"] + dtype = {"float32": torch.float32, "float16": torch.float16, "bfloat16": torch.bfloat16}[runtime["dtype"]] + with torch.inference_mode(): + outputs = model(pixel_values=pixels.to(device=runtime["device"], dtype=dtype)) + depth = getattr(outputs, "predicted_depth", None) + if not isinstance(depth, torch.Tensor) or tuple(depth.shape) != (1, *expected_shape[-2:]): + raise RuntimeError("Depth model returned an unexpected prediction shape.") + if not depth.is_floating_point() or not bool(torch.isfinite(depth).all()): + raise RuntimeError("Depth model returned nonfinite or nonnumeric predictions.") + # The processor's official postprocessing owns spatial interpolation. + processed = processor.post_process_depth_estimation( + outputs, target_sizes=[(image.height, image.width)] if match_input else None + ) + if not isinstance(processed, list) or len(processed) != 1 or set(processed[0]) != {"predicted_depth"}: + raise RuntimeError("Depth postprocessing returned an unexpected result schema.") + native = processed[0]["predicted_depth"] + expected_output = (image.height, image.width) if match_input else expected_shape[-2:] + if not isinstance(native, torch.Tensor) or tuple(native.shape) != expected_output or not native.is_floating_point(): + raise RuntimeError("Depth postprocessing returned an unexpected output shape.") + native = native.detach().to(device="cpu", dtype=torch.float32).contiguous() + if not bool(torch.isfinite(native).all()): + raise RuntimeError("Depth postprocessing returned nonfinite values.") + minimum, maximum = float(native.min()), float(native.max()) + span = maximum - minimum + normalized = (native - minimum) / span if span > 0 else torch.zeros_like(native) + if polarity == "near_is_larger" and span > 0: + normalized = 1 - normalized + prediction_map, previews = normalize_prediction_map(normalized, kind="depth") + return { + "prediction_map": prediction_map, + "preview_images": previews, + "native_depth": native, + "width_out": expected_output[1], + "height_out": expected_output[0], + "result": { + "schemaVersion": 1, + "task": "depth-estimation", + "modelReceipt": receipt, + "processedShape": list(expected_shape), + "nativeDepth": { + "layout": "HW", "dtype": "float32", "shape": list(expected_output), + "semantics": semantics, "polarity": polarity, "valueRange": [minimum, maximum], + "resizedToInput": match_input, + }, + "preview": {"semantics": "relative_depth", "nearValue": 0.0, "farValue": 1.0, "constant": span == 0}, + }, + } diff --git a/modules/HuggingFaceTransformers/main.py b/modules/HuggingFaceTransformers/main.py index bd5cc685..042769df 100644 --- a/modules/HuggingFaceTransformers/main.py +++ b/modules/HuggingFaceTransformers/main.py @@ -358,6 +358,12 @@ def _load_model( preprocessor = transformers.AutoProcessor.from_pretrained(source, **common) auto_model = transformers.AutoModelForImageTextToText handle_kind = "transformers-image-text-to-text" + elif task == "depth-estimation": + preprocessor_auto_class = "AutoImageProcessor" + model_auto_class = "AutoModelForDepthEstimation" + preprocessor = transformers.AutoImageProcessor.from_pretrained(source, **common) + auto_model = transformers.AutoModelForDepthEstimation + handle_kind = "transformers-depth-estimation" else: # pragma: no cover - internal programming error raise RuntimeError("Unsupported internal Transformers task.") model_kwargs = { @@ -637,10 +643,12 @@ def _validated_handle(value: Any, *, task: str) -> tuple[Any, Any, dict[str, Any expected_kind = { "text-generation": "transformers-causal-lm", "image-video-to-text": "transformers-image-text-to-text", + "depth-estimation": "transformers-depth-estimation", }[task] expected_loader = { "text-generation": ("AutoTokenizer", "AutoModelForCausalLM"), "image-video-to-text": ("AutoProcessor", "AutoModelForImageTextToText"), + "depth-estimation": ("AutoImageProcessor", "AutoModelForDepthEstimation"), }[task] if not isinstance(value, dict) or set(value) != MODEL_HANDLE_KEYS: raise ValueError("Generation requires an intact Transformers model handle.") @@ -648,7 +656,8 @@ def _validated_handle(value: Any, *, task: str) -> tuple[Any, Any, dict[str, Any raise ValueError("Generation received the wrong Transformers model handle type.") model = value.get("model") preprocessor = value.get("preprocessor") - if model is None or preprocessor is None or not callable(getattr(model, "generate", None)): + method = "forward" if task == "depth-estimation" else "generate" + if model is None or preprocessor is None or not callable(getattr(model, method, None)): raise ValueError("Generation requires a loaded Transformers model and preprocessor.") receipt = value.get("receipt") try: @@ -1519,3 +1528,143 @@ def execute(self, **kwargs): receipt=receipt, kwargs=kwargs, ) + + +class LoadDepthEstimationModel(NodeBase): + """Load an immutable local-only depth model through the official Auto classes.""" + + label = "Load Depth Estimation Model" + category = "Hugging Face Transformers" + resizable = True + params = { + "pipeline": {"label": "Model", "display": "output", "type": "transformers_depth_estimation"}, + "pipeline_class": {"label": "Execution Class", "type": "string", "default": "AutoModelForDepthEstimation", "hidden": True}, + "execution_profile_id": {"label": "Execution Profile", "type": "string", "default": "", "hidden": True}, + "model_id": { + "label": "Model", "display": "modelselect", "type": "string", + "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}, + }, + "revision": {"label": "Exact Hub Commit", "type": "string", "default": ""}, + "dtype": {"label": "DType", "type": "string", "options": ["float32", "float16", "bfloat16"], "default": "float32"}, + "device": {"label": "Device", "type": "string", "options": DEVICE_LIST, "default": DEFAULT_DEVICE}, + "receipt": {"label": "Model Receipt", "display": "output", "type": "object"}, + } + + def execute(self, **kwargs): + if kwargs.get("pipeline_class", "AutoModelForDepthEstimation") != "AutoModelForDepthEstimation": + raise ValueError("Depth estimation requires the AutoModelForDepthEstimation execution contract.") + model, receipt = _load_model( + selection_value=kwargs.get("model_id"), revision_value=kwargs.get("revision"), + dtype_value=kwargs.get("dtype"), device_value=kwargs.get("device"), task="depth-estimation", + ) + return {"pipeline": model, "receipt": receipt} + + +class PredictDepth(NodeBase): + """Predict a bounded depth map with native values and a separate normalized preview.""" + + label = "Predict Depth" + category = "Hugging Face Transformers" + resizable = True + params = { + "pipeline": {"label": "Model", "display": "input", "type": "transformers_depth_estimation", "required": True}, + "image": {"label": "Source Image", "display": "input", "type": "image", "required": True}, + "processing_resolution": { + "label": "Processing Resolution", "type": "int", "default": 0, "min": 0, "max": 2048, + "description": "Zero uses the model's image processor size; positive values set its resize target.", + }, + "match_input_resolution": {"label": "Match Input Resolution", "type": "bool", "default": True}, + "depth_convention": { + "label": "Depth Convention", "type": "string", "default": "model_default", + "options": ["model_default", "near_is_larger", "far_is_larger"], + "description": "Controls conversion to the shared near=0, far=1 preview. Native depth values remain unchanged.", + }, + "prediction_map": {"label": "Prediction Map", "display": "output", "type": "prediction_map"}, + "preview_images": {"label": "Depth Preview", "display": "output", "type": "image"}, + "native_depth": {"label": "Native Depth", "display": "output", "type": "tensor"}, + "width_out": {"label": "Width", "display": "output", "type": "int"}, + "height_out": {"label": "Height", "display": "output", "type": "int"}, + "result": {"label": "Depth Details", "display": "output", "type": "object"}, + } + + def execute(self, **kwargs): + from .depth import predict_depth + + model, processor, receipt = _validated_handle(kwargs.get("pipeline"), task="depth-estimation") + images = _media_items(kwargs.get("image"), field="image", maximum=1) + if len(images) != 1: + raise ValueError("Depth estimation requires exactly one source image.") + image, _pixels = _normalized_media_item(images[0], field="image") + resolution = _bounded_int(kwargs.get("processing_resolution"), field="processing_resolution", default=0, minimum=0, maximum=2048) + match_input = _bounded_bool(kwargs.get("match_input_resolution"), field="match_input_resolution", default=True) + return predict_depth( + model, processor, image, receipt=receipt, resolution=resolution, match_input=match_input, + convention=kwargs.get("depth_convention", "model_default"), + ) + + +def get_depth_operation_contracts(modules): + """Expose the same task operations as Diffusers perception without an executor fork.""" + from modiff.operation_contracts import build_pipeline_operation_contract + + contracts = [] + for operation, action, loader in ( + ("diffusion.load_models", "LoadDepthEstimationModel", True), + ("diffusion.predict_map", "PredictDepth", False), + ): + contract = build_pipeline_operation_contract( + modules, pipeline_class="AutoModelForDepthEstimation", task="depth_estimation", + operation_id=operation, node_key=f"modules.HuggingFaceTransformers.{action}", loader=loader, + ) + if contract is not None: + contracts.append(contract) + return contracts + + +def image_text_operation_schema(pipeline, task, action): + """Authoring-only projection of the existing finite image/text actions.""" + routes = { + ("AutoModelForImageTextToText", "image_to_text"): ("LoadImageTextToTextModel", "GenerateImageVideoText"), + ("JanusForConditionalGeneration", "image_to_text"): ("LoadAnyToAnyModel", "GenerateAnyToAny"), + ("JanusForConditionalGeneration", "text_to_image"): ("LoadAnyToAnyModel", "GenerateAnyToAny"), + } + loader, generator = routes.get((pipeline, task), (None, None)) + if action == loader: + return {}, {"pipeline_class": pipeline} + if action != generator: + raise ValueError("No reviewed image/text operation binding.") + fields = {"images": {"required": task == "image_to_text", "hidden": task != "image_to_text"}} + values = {} + if generator == "GenerateImageVideoText": + fields["video"] = {"hidden": True, "required": False} + else: + mode = "image" if task == "text_to_image" else "text" + values["generation_mode"] = mode + fields["generation_mode"] = {"options": [mode]} + fields["text"] = {"hidden": mode == "image"} + fields["image"] = {"hidden": mode != "image"} + if mode == "image": + values["do_sample"] = True + return fields, values + + +def get_image_text_operation_contracts(modules): + from modiff.operation_contracts import build_pipeline_operation_contract + + contracts = [] + for pipeline, task, loader, generator in ( + ("AutoModelForImageTextToText", "image_to_text", "LoadImageTextToTextModel", "GenerateImageVideoText"), + ("JanusForConditionalGeneration", "image_to_text", "LoadAnyToAnyModel", "GenerateAnyToAny"), + ("JanusForConditionalGeneration", "text_to_image", "LoadAnyToAnyModel", "GenerateAnyToAny"), + ): + for action in (loader, generator): + fields, _ = image_text_operation_schema(pipeline, task, action) + contract = build_pipeline_operation_contract( + modules, pipeline_class=pipeline, task=task, + operation_id="model.load" if action == loader else "image.caption" if task == "image_to_text" else "image.generate", + node_key=f"modules.HuggingFaceTransformers.{action}", + field_overrides=fields, loader=action == loader, + ) + if contract is not None: + contracts.append(contract) + return contracts diff --git a/modules/ImageOperations/main.py b/modules/ImageOperations/main.py index e2828314..d63a464b 100644 --- a/modules/ImageOperations/main.py +++ b/modules/ImageOperations/main.py @@ -481,6 +481,27 @@ def execute(self, **kwargs): return {"tiles": tiles, "count": len(tiles), "layout": layout} +def stitch_reference_images(images): + """Bounded, ordered equal-height RGB canvas used by reference-edit recipes.""" + images, _ = _images(images, name="reference images") + if len(images) > 8: + raise ValueError("Horizontal reference composition supports at most eight images.") + height = max(image.height for image in images) + widths = [max(1, round(image.width * height / image.height)) for image in images] + width = sum(widths) + if width > 8192 or height > 8192 or width * height > MAX_TOTAL_PIXELS: + raise ValueError("Reference composition exceeds the 8192-side or total-pixel execution limit.") + canvas = Image.new("RGB", (width, height)) + left = 0 + for image, target_width in zip(images, widths, strict=True): + image = image.convert("RGB") + if image.size != (target_width, height): + image = image.resize((target_width, height), Image.Resampling.LANCZOS) + canvas.paste(image, (left, 0)) + left += target_width + return canvas + + class StitchImages(NodeBase): """Join a bounded image collection into a deterministic row-major grid.""" @@ -488,6 +509,8 @@ class StitchImages(NodeBase): category = "Image Operations" params = { "image": {"label": "Images", "display": "input", "type": "image"}, + "layout": {"label": "Layout", "type": "string", "default": "grid", + "options": ["grid", "horizontal_reference"]}, "columns": {"label": "Columns", "type": "int", "default": 2, "min": 1, "max": 8}, "spacing": {"label": "Spacing", "type": "int", "default": 0, "min": 0, "max": 1024}, "background": { @@ -504,6 +527,11 @@ class StitchImages(NodeBase): def execute(self, **kwargs): images, singular = _images(kwargs.get("image"), name="images") + layout = kwargs.get("layout", "grid") + if layout == "horizontal_reference": + return {"output": stitch_reference_images(images), "rows": 1, "count": len(images)} + if layout != "grid": + raise ValueError("Unsupported stitch layout.") if singular or len(images) < 2: raise ValueError("Stitch Images requires between 2 and 64 source images.") try: diff --git a/modules/ModularDiffusers/__init__.py b/modules/ModularDiffusers/__init__.py index a9c164af..2c399bbb 100644 --- a/modules/ModularDiffusers/__init__.py +++ b/modules/ModularDiffusers/__init__.py @@ -16,7 +16,9 @@ ModiffPipelineRegistry, get_modular_guider_options, get_modular_layer_block_options, + get_modular_node_action_options, get_modular_scheduler_options, + get_all_model_types, ) @@ -48,6 +50,15 @@ MODULAR_LAYER_BLOCK_OPTIONS = get_modular_layer_block_options() MODULAR_GUIDER_OPTIONS = get_modular_guider_options() MODULAR_SCHEDULER_OPTIONS = get_modular_scheduler_options() +MODULAR_NODE_ACTION_OPTIONS = get_modular_node_action_options() +MODULAR_TEXT_ENCODER_OPTIONS = MODULAR_NODE_ACTION_OPTIONS.get("text_encoder", {}) +MODULAR_IMAGE_ENCODER_OPTIONS = MODULAR_NODE_ACTION_OPTIONS.get("image_encoder", {}) +MODULAR_DENOISE_OPTIONS = MODULAR_NODE_ACTION_OPTIONS.get("denoise", {}) +MODULAR_IP_ADAPTER_OPTIONS = MODULAR_NODE_ACTION_OPTIONS.get("ip_adapter", {}) +MODULAR_DECODER_OPTIONS = MODULAR_NODE_ACTION_OPTIONS.get("decoder", {}) +MODULAR_VAE_ENCODER_OPTIONS = MODULAR_NODE_ACTION_OPTIONS.get("vae_encoder", {}) +MODULAR_CONTROLNET_OPTIONS = MODULAR_NODE_ACTION_OPTIONS.get("controlnet", {}) +MODULAR_MODEL_TYPE_OPTIONS = get_all_model_types(include_contract_only=True) # The static node-registry parser resolves schema constants against this # package object. Export reviewed dynamic options so the public /nodes diff --git a/modules/ModularDiffusers/controlnet.py b/modules/ModularDiffusers/controlnet.py index b7a0d540..770d7655 100644 --- a/modules/ModularDiffusers/controlnet.py +++ b/modules/ModularDiffusers/controlnet.py @@ -3,7 +3,7 @@ from modiff.NodeBase import NodeBase -from . import components +from . import MODULAR_CONTROLNET_OPTIONS, components from .modular_utils import ( modular_generator_from_seed, normalize_modular_runtime_params, @@ -197,6 +197,9 @@ class Controlnet(NodeBase): {"action": "value", "target": "model_type"}, {"action": "exec", "data": "update_node"}, ], + "signalCompatibility": { + "values": MODULAR_CONTROLNET_OPTIONS, + }, }, } @@ -221,6 +224,9 @@ def update_node(self, values, ref): {"action": "value", "target": "model_type"}, {"action": "exec", "data": "update_node"}, ], + "signalCompatibility": { + "values": MODULAR_CONTROLNET_OPTIONS, + }, }, } diff --git a/modules/ModularDiffusers/denoise.py b/modules/ModularDiffusers/denoise.py index 6ce34e7f..c34ad55c 100644 --- a/modules/ModularDiffusers/denoise.py +++ b/modules/ModularDiffusers/denoise.py @@ -12,7 +12,7 @@ from modiff.NodeBase import NodeBase -from . import MESSAGE_DURATION, components +from . import MESSAGE_DURATION, MODULAR_DENOISE_OPTIONS, components from .modular_utils import ( get_model_type_metadata, normalize_modular_runtime_params, @@ -198,6 +198,11 @@ class Denoise(NodeBase): {"action": "signal", "target": "guider"}, {"action": "signal", "target": "controlnet_bundle"}, ], + "signalCompatibility": { + "required": True, + "role": "denoiser", + "values": MODULAR_DENOISE_OPTIONS, + }, }, } @@ -925,6 +930,9 @@ def preview_callback(_latents, step_index: int, scheduler_order: int): f"{type(self._pipeline).__name__} does not expose a 'guider' component, " "so the connected Diffusers guider cannot be installed." ) + metadata = get_model_type_metadata(self._model_type) + if metadata is not None and type(explicit_guider).__name__ not in metadata["guider_options"]: + raise ValueError("Connected guider is not allowed by the actual denoiser pipeline contract.") if model_ids: managed_components = components.get_components_by_ids(ids=model_ids, return_dict_with_names=True) diff --git a/modules/ModularDiffusers/dynamic_node.py b/modules/ModularDiffusers/dynamic_node.py index c8745b7e..939c8977 100644 --- a/modules/ModularDiffusers/dynamic_node.py +++ b/modules/ModularDiffusers/dynamic_node.py @@ -277,7 +277,7 @@ def send_node_definition_with_meta(self, params, label=None, header_color=None): message["label"] = label if header_color: message["style"] = {"headerColor": header_color} - current_server.queue_message(message, self._sid) + self._queue_dynamic_node_message(message) params = { "repo_id": { diff --git a/modules/ModularDiffusers/embeddings.py b/modules/ModularDiffusers/embeddings.py index fd52f8bd..8ec34db0 100644 --- a/modules/ModularDiffusers/embeddings.py +++ b/modules/ModularDiffusers/embeddings.py @@ -5,7 +5,7 @@ from modiff.NodeBase import NodeBase -from . import MESSAGE_DURATION, components +from . import MESSAGE_DURATION, MODULAR_IMAGE_ENCODER_OPTIONS, MODULAR_TEXT_ENCODER_OPTIONS, components from .modular_utils import ( normalize_modular_runtime_params, pipeline_class_from_model_type, @@ -89,6 +89,17 @@ class EncodePrompt(NodeBase): "type": "diffusers_auto_models", "display": "input", "onSignal": "update_node", + "signalCompatibility": { + "required": True, + "role": "text_encoders", + "values": MODULAR_TEXT_ENCODER_OPTIONS, + }, + }, + "prompt_input": { + "label": "Prompt Input", + "type": "string", + "display": "input", + "description": "Optional connected prompt. When connected, this replaces the inline Prompt value.", }, } @@ -142,6 +153,9 @@ def __init__(self, node_id=None): def execute(self, **kwargs): kwargs = dict(kwargs) + prompt_input = kwargs.pop("prompt_input", None) + if prompt_input is not None: + kwargs["prompt"] = prompt_input self._pipeline_class = pipeline_class_from_runtime_inputs(self._pipeline_class, kwargs) # 1. Get node config blocks, node_config = require_modiff_node_contract(self._pipeline_class, self.node_type) @@ -162,6 +176,11 @@ def execute(self, **kwargs): # Enforce the backend-issued action schema before initializing blocks. kwargs = normalize_modular_runtime_params(kwargs, node_config) + if repo_id == "black-forest-labs/FLUX.1-schnell": + length = kwargs.get("max_sequence_length", 256) + if type(length) is not int or not 1 <= length <= 256: + raise ValueError("FLUX.1 schnell requires a maximum sequence length between 1 and 256.") + kwargs["max_sequence_length"] = length # Components came from the reviewed ModelsLoader contract. Re-reading # repository config here would let a later cache mutation choose fresh @@ -212,6 +231,13 @@ def execute(self, **kwargs): f"Blocks: {blocks}" ) + # Snapshot what the encoder actually consumes, not the unused inline + # prompt captured before the connected-input override. Preserve the + # original socket identity for the graph ancestry receipt on cache hits. + self.record_generation_inputs(node_kwargs) + if prompt_input is not None: + self._execution_input_source_fields = {"prompt": "prompt_input"} + # 5. run the pipeline, try: node_output_state = self._pipeline(**node_kwargs) @@ -253,6 +279,11 @@ class ImageEmbeddings(NodeBase): "display": "input", "type": "diffusers_auto_model", "onSignal": "update_node", + "signalCompatibility": { + "required": True, + "role": "image_encoder", + "values": MODULAR_IMAGE_ENCODER_OPTIONS, + }, }, } diff --git a/modules/ModularDiffusers/field_metadata.py b/modules/ModularDiffusers/field_metadata.py new file mode 100644 index 00000000..18a0bc7d --- /dev/null +++ b/modules/ModularDiffusers/field_metadata.py @@ -0,0 +1,69 @@ +"""Presentation-only contexts for the reviewed generic Modular field actions. + +Reuse the existing callback implementations and NodeBase message protocol without +constructing an executable node. In particular, ModelsLoader's destructor removes +components by node ID even when that particular instance never loaded weights. +This context has no model cache, component collection, executor or destructor. +Keep its explicitly borrowed methods under review when changing those callbacks. +""" + +import threading +from types import MethodType + +from modiff.field_metadata_context import FieldMessageContext +from .denoise import Denoise +from .embeddings import EncodePrompt, ImageEmbeddings +from .latents import DecodeLatents, ImageEncode +from .loaders import ModelsLoader, AutoModelLoader +from .controlnet import Controlnet +from .ip_adapter import IPAdapter +from .schedulers import Scheduler +from .guiders import Guider, Layers +from .dynamic_node import DynamicBlockNode + + +class _FieldContext(FieldMessageContext): + _selected_scheduler = Scheduler._selected_scheduler + _selected_guider = Guider._selected_guider + _selected_blocks = Layers._selected_blocks + send_node_definition_with_meta = DynamicBlockNode.send_node_definition_with_meta + + # These helpers read declarations and publish UI messages only. Do not bind + # the loader constructor, execution, cache or component-release methods here. + _begin_pipeline_identity_refresh = ModelsLoader._begin_pipeline_identity_refresh + _publish_pipeline_identity = ModelsLoader._publish_pipeline_identity + _selected_repository = staticmethod(ModelsLoader._selected_repository) + _reviewed_workflow_variants = staticmethod(ModelsLoader._reviewed_workflow_variants) + refresh_pipeline_identity = ModelsLoader.refresh_pipeline_identity + + def __init__(self, *, node_id, sid, node_type): + self.node_id = node_id + self._sid = sid + self.node_type = node_type + # No schema has been published by this request, including the empty + # selection. A string/None sentinel would suppress disconnect updates. + self._model_type = object() + self._pipeline_class = None + self.model_types_loaded = False + self._pipeline_identity_generation = 0 + self._pipeline_identity_lock = threading.Lock() + + +def metadata_field_callback(action, method, *, node_id, sid): + node_class = { + "ModelsLoader": ModelsLoader, + "AutoModelLoader": AutoModelLoader, + "EncodePrompt": EncodePrompt, + "Denoise": Denoise, + "DecodeLatents": DecodeLatents, + "ImageEncode": ImageEncode, + "ImageEmbeddings": ImageEmbeddings, + "IPAdapter": IPAdapter, + "Controlnet": Controlnet, + "Scheduler": Scheduler, + "Guider": Guider, + "Layers": Layers, + "DynamicBlockNode": DynamicBlockNode, + }[action] + context = _FieldContext(node_id=node_id, sid=sid, node_type=getattr(node_class, "node_type", None)) + return MethodType(getattr(node_class, method), context) diff --git a/modules/ModularDiffusers/guiders.py b/modules/ModularDiffusers/guiders.py index 1db54eb6..5eea16da 100644 --- a/modules/ModularDiffusers/guiders.py +++ b/modules/ModularDiffusers/guiders.py @@ -180,6 +180,7 @@ class Guider(NodeBase): resizable = True skipParamsCheck = True params = { + "model_type": {"type": "string", "default": "", "hidden": True}, "guider": { "label": "Guider", "fieldOptions": {"loading": True}, @@ -247,6 +248,7 @@ class Guider(NodeBase): "display": "output", "type": "custom_guider", "onSignal": [ + {"action": "value", "target": "model_type"}, { "action": "value", "target": "guider", @@ -255,12 +257,16 @@ class Guider(NodeBase): }, {"action": "signal", "target": "layers_config"}, ], + "signalCompatibility": { + "values": MODULAR_GUIDER_OPTIONS, + }, }, "layers_config": {"label": "Layers", "type": "layers_config", "display": "input"}, } - def _selected_guider(self, guider): - model_type = self.get_signal_value("guider_out") + def _selected_guider(self, guider, model_type=None): + if model_type in (None, ""): + model_type = self.get_signal_value("guider_out") allowed = MODULAR_GUIDER_OPTIONS.get(model_type) if isinstance(model_type, str) else None if ( not isinstance(guider, str) @@ -273,12 +279,12 @@ def _selected_guider(self, guider): return guider def updateNode(self, values, ref): - value = self._selected_guider(values.get("guider")) + value = self._selected_guider(values.get("guider"), values.get("model_type")) params = GUIDER_CONFIGS.get(value, {}) self.send_node_definition(params) - def execute(self, guider, layers_config=None, **kwargs): + def execute(self, guider, layers_config=None, model_type=None, **kwargs): logger.debug(f" Guider ({self.node_id}) received parameters:") logger.debug(f" - guider: {guider}") logger.debug(f" - kwargs: {kwargs}") @@ -300,7 +306,7 @@ def execute(self, guider, layers_config=None, **kwargs): logger.debug(f" - guider options: {guider_options}") - guider = self._selected_guider(guider) + guider = self._selected_guider(guider, model_type) guider_cls = getattr(diffusers_guiders, guider) @@ -424,6 +430,7 @@ class Layers(NodeBase): resizable = True skipParamsCheck = True params = { + "model_type": {"type": "string", "default": "", "hidden": True}, "blocks_select": { "label": "Blocks", "type": "string", @@ -436,11 +443,14 @@ class Layers(NodeBase): "label": "Layers", "display": "output", "type": "layers_config", - "onSignal": { + "onSignal": [{"action": "value", "target": "model_type"}, { "action": "value", "target": "blocks_select", "prop": "options", "data": MODULAR_LAYER_BLOCK_OPTIONS, + }], + "signalCompatibility": { + "values": MODULAR_LAYER_BLOCK_OPTIONS, }, }, } @@ -459,7 +469,9 @@ def _selected_blocks(self, values): if not blocks_select: return () - model_type = self.get_signal_value("layers_config") + model_type = values.get("model_type") + if model_type in (None, ""): + model_type = self.get_signal_value("layers_config") allowed_blocks = MODULAR_LAYER_BLOCK_OPTIONS.get(model_type) if isinstance(model_type, str) else None if not isinstance(allowed_blocks, list) or any(block not in allowed_blocks for block in blocks_select): raise ValueError("Layers requires block names allowed by the connected reviewed Modular pipeline.") @@ -481,7 +493,7 @@ def set_blocks(self, values, ref): def execute(self, **kwargs): layer_configs = [] blocks_select = self._selected_blocks(kwargs) - supplied_blocks = {block for block in kwargs if block != "blocks_select"} + supplied_blocks = {block for block in kwargs if block not in {"blocks_select", "model_type"}} if supplied_blocks != set(blocks_select): raise ValueError("Layers inputs must exactly match the reviewed selected block names.") diff --git a/modules/ModularDiffusers/ip_adapter.py b/modules/ModularDiffusers/ip_adapter.py index db7c78a8..67fdd5e3 100644 --- a/modules/ModularDiffusers/ip_adapter.py +++ b/modules/ModularDiffusers/ip_adapter.py @@ -6,7 +6,7 @@ from modiff.NodeBase import NodeBase from modiff.auxiliary_ip_adapter import resolve_reviewed_sdxl_ip_adapter -from . import components +from . import MODULAR_IP_ADAPTER_OPTIONS, components from .modular_utils import ( normalize_modular_runtime_params, pipeline_class_from_model_type, @@ -41,6 +41,11 @@ class IPAdapter(NodeBase): "type": "diffusers_auto_model", "required": True, "onSignal": "update_node", + "signalCompatibility": { + "required": True, + "role": "denoiser", + "values": MODULAR_IP_ADAPTER_OPTIONS, + }, }, } diff --git a/modules/ModularDiffusers/latents.py b/modules/ModularDiffusers/latents.py index 59c19e12..3152a185 100644 --- a/modules/ModularDiffusers/latents.py +++ b/modules/ModularDiffusers/latents.py @@ -9,7 +9,7 @@ from modiff.NodeBase import NodeBase -from . import MESSAGE_DURATION, components +from . import MESSAGE_DURATION, MODULAR_DECODER_OPTIONS, MODULAR_VAE_ENCODER_OPTIONS, components from .modular_utils import ( modular_generator_from_seed, normalize_modular_runtime_params, @@ -121,6 +121,9 @@ def prepare_image_for_vae_pipeline(image, pipeline_class): """ pipeline_name = getattr(pipeline_class, "__name__", "") + if pipeline_name in {"Flux2ModularPipeline", "Flux2KleinModularPipeline"}: + if isinstance(image, (list, tuple)) and not 1 <= len(image) <= 8: + raise ValueError("FLUX.2 reference editing requires between one and eight ordered images.") if pipeline_name not in {"QwenImageLayeredModularPipeline", "QwenImageLayeredPipeline"}: return image if isinstance(image, Image.Image): @@ -176,6 +179,11 @@ class DecodeLatents(NodeBase): "display": "input", "type": "diffusers_auto_model", "onSignal": "update_node", + "signalCompatibility": { + "required": True, + "role": "vae", + "values": MODULAR_DECODER_OPTIONS, + }, }, } @@ -596,7 +604,17 @@ class ImageEncode(NodeBase): skipParamsCheck = True node_type = "vae_encoder" params = { - "vae": {"label": "VAE *", "display": "input", "type": "diffusers_auto_model", "onSignal": "update_node"}, + "vae": { + "label": "VAE *", + "display": "input", + "type": "diffusers_auto_model", + "onSignal": "update_node", + "signalCompatibility": { + "required": True, + "role": "vae", + "values": MODULAR_VAE_ENCODER_OPTIONS, + }, + }, "encode_summary_data": { "label": "Encode summary", "display": "output", diff --git a/modules/ModularDiffusers/loaders.py b/modules/ModularDiffusers/loaders.py index a71704af..1576c0c3 100644 --- a/modules/ModularDiffusers/loaders.py +++ b/modules/ModularDiffusers/loaders.py @@ -16,6 +16,7 @@ from diffusers import ComponentSpec, ModularPipeline from diffusers.utils import logging as diffusers_logging from huggingface_hub import get_hf_file_metadata, hf_hub_download, hf_hub_url +from huggingface_hub import constants as hub_constants from huggingface_hub.utils import EntryNotFoundError, HfHubHTTPError, LocalEntryNotFoundError from modiff.NodeBase import NodeBase @@ -50,8 +51,9 @@ reviewed_modular_weight_variant, ) from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype +from utils.huggingface import exact_cached_snapshot_path -from . import MESSAGE_DURATION, components +from . import MESSAGE_DURATION, MODULAR_MODEL_TYPE_OPTIONS, components from .custom_pipeline import ( CUSTOM_PIPELINE_IDENTITY_FIELD, CUSTOM_PIPELINE_MODEL_TYPE, @@ -80,6 +82,15 @@ logger = logging.getLogger("modiff") logger.setLevel(logging.DEBUG) + +def _loader_text_encoder_component_names(loader): + """Resolve only the selected text stage, including flattened workflow leaves.""" + from .workflow_blocks import _official_stage_block + + block = _official_stage_block(loader.blocks, "text_encoder") + return list(block.init_pipeline().pretrained_component_names) if block is not None else [] + + QWEN_LOW_VRAM_COMPONENT = "qwen_low_vram" GROUP_OFFLOAD_COMPONENTS = set(DEFAULT_GROUP_COMPONENTS) REQUIRED_REGIONAL_COMPILE_MODEL_TYPES = frozenset( @@ -154,6 +165,7 @@ "FluxKontextPipeline": "flux-kontext", "Flux2Pipeline": "flux2", "Flux2KleinPipeline": "flux2-klein", + "ErnieImagePipeline": "ernie-image", "ZImagePipeline": "z-image", "HunyuanVideo15Pipeline": "hunyuan-video-1.5", "HunyuanVideo15ImageToVideoPipeline": "hunyuan-video-1.5", @@ -693,13 +705,8 @@ def _load_reviewed_pipeline_index(repository, revision): failures = [] for filename in _REVIEWED_PIPELINE_INDEX_FILENAMES: try: - index_path = hf_hub_download( - repository, - filename=filename, - revision=revision, - local_files_only=True, - ) - except (EntryNotFoundError, LocalEntryNotFoundError, HfHubHTTPError, ValueError) as error: + index_path = exact_cached_snapshot_path(repository, revision, filename) / filename + except (FileNotFoundError, ValueError) as error: failures.append(error) continue return filename, _read_reviewed_pipeline_index( @@ -713,6 +720,18 @@ def _load_reviewed_pipeline_index(repository, revision): ) from (failures[-1] if failures else None) +def _primary_component_cache_dirs(pipeline, repository, revision, index_filename): + """Keep primary component loads in the same managed cache as the reviewed index.""" + + snapshot = exact_cached_snapshot_path(repository, revision, index_filename) + cache_root = str(snapshot.parent.parent.parent) + return { + name: cache_root + for name, spec in pipeline._component_specs.items() + if str(getattr(spec, "pretrained_model_name_or_path", "")).lower() == repository.lower() + } + + def _validate_reviewed_pipeline_index(model_type, repository, revision): """Bind repo metadata to the installed registered pipeline component contract.""" @@ -827,6 +846,16 @@ def _validate_reviewed_pipeline_index(model_type, repository, revision): f"The cached reviewed pipeline index has a malformed {component_name!r} component contract." ) observed_type_hint = raw_component + reviewed_concrete_type = PINNED_MODULAR_REPOSITORY_COMPONENT_TYPES.get(repository, {}).get(component_name) + if ( + filename != ModularPipeline.config_name + and observed_type_hint == [None, None] + and reviewed_concrete_type == (None, None) + ): + # Only a reviewed absent optional component in an exact standard + # checkpoint may use this sentinel. Required/null or malformed + # declarations elsewhere still fail validation below. + continue if ( not isinstance(observed_type_hint, list) or len(observed_type_hint) != 2 @@ -836,7 +865,6 @@ def _validate_reviewed_pipeline_index(model_type, repository, revision): f"The cached reviewed pipeline index has an invalid {component_name!r} component type hint." ) expected_type_hint = list(_fetch_class_library_tuple(component_spec.type_hint)) - reviewed_concrete_type = PINNED_MODULAR_REPOSITORY_COMPONENT_TYPES.get(repository, {}).get(component_name) if observed_type_hint != expected_type_hint and tuple(observed_type_hint) != reviewed_concrete_type: raise ValueError( f"The cached reviewed pipeline index maps component {component_name!r} to " @@ -999,6 +1027,10 @@ def component_load_kwargs_for(name, kwargs): component_load_kwargs[key] = value[name] elif "default" in value: component_load_kwargs[key] = value["default"] + # Diffusers' sharded loader queries model_info unless this flag is explicit, + # even when every pinned shard is cached and Hub offline mode is enabled. + if hub_constants.HF_HUB_OFFLINE: + component_load_kwargs["local_files_only"] = True return component_load_kwargs @@ -1900,7 +1932,7 @@ def execute( message=f"Loading {model_type} weights from {real_model_id}", ) with self.diffusers_loading_progress(): - model = spec.load(torch_dtype=dtype) + model = spec.load(**component_load_kwargs_for(model_type, {"torch_dtype": dtype})) self.progress( 99, phase="component_placement", @@ -1951,12 +1983,14 @@ class ModelsLoader(NodeBase): skipParamsCheck = True params = { "model_type": { - "label": "Model Type", + "label": "Pipeline Type", "type": "string", - "options": { - "": "", - }, + "options": MODULAR_MODEL_TYPE_OPTIONS, "onChange": "set_filters", + "description": ( + "Selects the Modular Diffusers pipeline class. This filters compatible checkpoints and publishes " + "the component contract used by connected nodes; it is not the model checkpoint itself." + ), }, "repo_id": { "label": "Repository ID", @@ -2034,11 +2068,41 @@ class ModelsLoader(NodeBase): "vae": {"label": "VAE", "display": "input", "type": "diffusers_auto_model"}, "controlnet": {"label": "ControlNet", "display": "input", "type": "diffusers_auto_model"}, "lora_list": {"label": "Lora", "display": "input", "type": "custom_lora"}, - "text_encoders": {"label": "Text Encoders", "display": "output", "type": "diffusers_auto_models"}, - "unet_out": {"label": "Denoise Model", "display": "output", "type": "diffusers_auto_model"}, - "vae_out": {"label": "VAE", "display": "output", "type": "diffusers_auto_model"}, - "scheduler": {"label": "Scheduler", "display": "output", "type": "diffusers_auto_model"}, - "image_encoder": {"label": "Image Encoder", "display": "output", "type": "diffusers_auto_model"}, + "text_encoders": { + "label": "Text Encoders", + "display": "output", + "type": "diffusers_auto_models", + "signal": {"direction": "output", "origin": "model_type", "value": ""}, + "connectionRole": "text_encoders", + }, + "unet_out": { + "label": "Denoise Model", + "display": "output", + "type": "diffusers_auto_model", + "signal": {"direction": "output", "origin": "model_type", "value": ""}, + "connectionRole": "denoiser", + }, + "vae_out": { + "label": "VAE", + "display": "output", + "type": "diffusers_auto_model", + "signal": {"direction": "output", "origin": "model_type", "value": ""}, + "connectionRole": "vae", + }, + "scheduler": { + "label": "Scheduler", + "display": "output", + "type": "diffusers_auto_model", + "signal": {"direction": "output", "origin": "model_type", "value": ""}, + "connectionRole": "scheduler", + }, + "image_encoder": { + "label": "Image Encoder", + "display": "output", + "type": "diffusers_auto_model", + "signal": {"direction": "output", "origin": "model_type", "value": ""}, + "connectionRole": "image_encoder", + }, "pipeline_components": { "label": "Pipeline Components", "display": "output", @@ -2121,6 +2185,26 @@ def prepare_for_workflow_reuse(self): with self._pipeline_identity_lock: self._pipeline_identity_generation += 1 + def rebind_cache_owner(self, node_id): + """Transfer live ownership without removing hooks, weights or load files.""" + if node_id == self.node_id: + return + if components.collections.get(node_id): + raise ValueError("Cannot adopt a loader into an occupied component collection.") + owned = components.collections.get(self.node_id, ()) + disk_ids = {key for key in owned + if getattr(components.components[key], "_modiff_offload_node_id", None) == str(self.node_id)} + if any(disk_ids.intersection(ids) for owner, ids in components.collections.items() if owner != self.node_id): + raise ValueError("Cannot transfer a disk-offload owner with shared component ownership.") + for key in disk_ids: + components.components[key]._modiff_offload_node_id = str(node_id) + owned = components.collections.pop(self.node_id, None) + if owned is not None: + components.collections[node_id] = owned + if self.loader is not None: + self.loader._collection = node_id + self.node_id = node_id + def __del__(self): node_comp_ids = components._lookup_ids(collection=self.node_id) for comp_id in node_comp_ids: @@ -2139,6 +2223,7 @@ def _publish_pipeline_identity( *, persisted_identity, signal_value, + signal_origin, show_refresh, dtype=None, update_persisted_identity=True, @@ -2165,7 +2250,7 @@ def _publish_pipeline_identity( { "signal": { "direction": "output", - "origin": CUSTOM_PIPELINE_IDENTITY_FIELD, + "origin": signal_origin, "value": deepcopy(signal_value), } }, @@ -2300,6 +2385,7 @@ def refresh_pipeline_identity(self, values, ref): generation, persisted_identity=None, signal_value="", + signal_origin="model_type", show_refresh=False, ) return None @@ -2322,6 +2408,7 @@ def refresh_pipeline_identity(self, values, ref): generation, persisted_identity=None, signal_value="", + signal_origin="model_type", show_refresh=False, clear_revision=clear_revision, ) @@ -2330,6 +2417,7 @@ def refresh_pipeline_identity(self, values, ref): generation, persisted_identity=None, signal_value="" if trust_remote_code or contract_only else model_type, + signal_origin="model_type", show_refresh=False, clear_revision=clear_revision, ) @@ -2358,6 +2446,7 @@ def refresh_pipeline_identity(self, values, ref): generation, persisted_identity=None, signal_value="", + signal_origin=CUSTOM_PIPELINE_IDENTITY_FIELD, show_refresh=True, clear_revision=clear_revision, ) @@ -2390,6 +2479,7 @@ def refresh_pipeline_identity(self, values, ref): generation, persisted_identity=identity_value, signal_value=identity_value, + signal_origin=CUSTOM_PIPELINE_IDENTITY_FIELD, show_refresh=True, dtype=config.default_dtype, clear_revision=clear_revision, @@ -2402,6 +2492,7 @@ def refresh_pipeline_identity(self, values, ref): generation, persisted_identity=None, signal_value="", + signal_origin=CUSTOM_PIPELINE_IDENTITY_FIELD, show_refresh=True, update_persisted_identity=type(values.get("trust_remote_code", False)) is not bool or values.get("trust_remote_code") is True, @@ -2716,12 +2807,7 @@ def execute( or model_type in REVIEWED_EXPANDED_WORKFLOW_MODEL_TYPES or reviewed_workflow_id is not None ) - text_encoder_block = self.loader.blocks.sub_blocks.get("text_encoder") - text_encoder_names = ( - text_encoder_block.init_pipeline().pretrained_component_names - if text_encoder_block is not None - else [] - ) + text_encoder_names = _loader_text_encoder_component_names(self.loader) components_to_load = [c for c in ALL_COMPONENTS if c not in components_to_update] components_to_reload = [] @@ -2781,6 +2867,9 @@ def execute( diagnostics=self._loader_diagnostics, component_load_kwargs={ "torch_dtype": dtype, + **({"cache_dir": _primary_component_cache_dirs( + self.loader, real_repo_id, revision, reviewed_index_filename, + )} if custom_binding is None else {}), **({"variant": weight_variant} if weight_variant is not None else {}), "trust_remote_code": trust_remote_code, "quantization_config": quant_config, @@ -2904,11 +2993,14 @@ def execute( manager=components, name="scheduler", ) - if whole_workflow_components: - loaded_components["pipeline_components"] = { - name: node_get_component_info(node_id=self.node_id, manager=components, name=name) - for name in ALL_COMPONENTS - } + # Every selected pipeline can supply an approved custom block. + # Publish the models already loaded by the existing policy; this + # must neither require inactive optional components nor load them. + loaded_components["pipeline_components"] = { + name: node_get_component_info(node_id=self.node_id, manager=components, name=name) + for name in ALL_COMPONENTS + if getattr(self.loader, name, None) is not None + } loaded_components.update( { diff --git a/modules/ModularDiffusers/modular_utils.py b/modules/ModularDiffusers/modular_utils.py index 89e8a3b9..d4339f0d 100644 --- a/modules/ModularDiffusers/modular_utils.py +++ b/modules/ModularDiffusers/modular_utils.py @@ -11,6 +11,7 @@ import torch from diffusers import Flux2KleinModularPipeline from modiff.model_artifact_catalog import resolve_model_revision +from modiff.operation_contracts import MODULAR_STAGE_OPERATIONS, build_modular_operation_contracts from modiff.modular_contract_only_registry import ( CURRENT_PIN_CONTRACT_ONLY_MODULAR_BY_NAME, CURRENT_PIN_CONTRACT_ONLY_MODULAR_PIPELINES, @@ -373,6 +374,8 @@ def pin_modular_component_revisions(pipeline, primary_repo, primary_revision): PipelineParam.mask_image(), PipelineParam.padding_mask_crop(), PipelineParam.seed(), + PipelineParam.width(), + PipelineParam.height(), ], "model_inputs": [ PipelineParam.vae(), @@ -469,8 +472,8 @@ def _qwen_image_max_sequence_length_param(): PipelineParam.controlnet_conditioning_scale(), PipelineParam.control_guidance_start(), PipelineParam.control_guidance_end(), - PipelineParam.height(), - PipelineParam.width(), + PipelineParam.height(step=16), + PipelineParam.width(step=16), PipelineParam.seed(), PipelineParam.route_state_in(), ], @@ -490,8 +493,8 @@ def _qwen_image_max_sequence_length_param(): "denoise": { "inputs": [ PipelineParam.embeddings(display="input"), - PipelineParam.width(), - PipelineParam.height(), + PipelineParam.width(step=16), + PipelineParam.height(step=16), PipelineParam.seed(), PipelineParam.num_inference_steps(50), PipelineParam.guidance_scale(4.5), @@ -520,8 +523,8 @@ def _qwen_image_max_sequence_length_param(): PipelineParam.image(), PipelineParam.mask_image(), PipelineParam.padding_mask_crop(), - PipelineParam.height(), - PipelineParam.width(), + PipelineParam.height(step=16), + PipelineParam.width(step=16), PipelineParam.seed(), ], "model_inputs": [ @@ -590,8 +593,8 @@ def _qwen_image_max_sequence_length_param(): "denoise": { "inputs": [ PipelineParam.embeddings(display="input"), - PipelineParam.width(), - PipelineParam.height(), + PipelineParam.width(step=16), + PipelineParam.height(step=16), PipelineParam.seed(), PipelineParam.num_inference_steps(40), PipelineParam.guidance_scale(4.0), @@ -904,8 +907,8 @@ def _qwen_image_layered_max_sequence_length_param(): "denoise": { "inputs": [ PipelineParam.embeddings(display="input"), - PipelineParam.width(), - PipelineParam.height(), + PipelineParam.width(step=16), + PipelineParam.height(step=16), PipelineParam.seed(), PipelineParam.num_inference_steps(28), PipelineParam.guidance_scale(3.5), @@ -936,8 +939,8 @@ def _qwen_image_layered_max_sequence_length_param(): # while preprocessing the source image. Omitting these fields in # a split graph makes it fall back to the pipeline's 1024px # defaults even when the saved workflow requests another size. - PipelineParam.height(), - PipelineParam.width(), + PipelineParam.height(step=16), + PipelineParam.width(step=16), PipelineParam.seed(), ], "model_inputs": [ @@ -955,6 +958,11 @@ def _qwen_image_layered_max_sequence_length_param(): "inputs": [ PipelineParam.prompt(), # No negative_prompt - pipeline does not support this + PipelineParam( + name="max_sequence_length", label="Maximum Sequence Length", type="int", + default=512, min=1, max=512, step=1, + fieldOptions={"controlTier": "advanced"}, + ), ], "model_inputs": [ PipelineParam.text_encoders(), @@ -1007,8 +1015,8 @@ def _qwen_image_layered_max_sequence_length_param(): "denoise": { "inputs": [ PipelineParam.embeddings(display="input"), - PipelineParam.width(), - PipelineParam.height(), + PipelineParam.width(step=16), + PipelineParam.height(step=16), PipelineParam.seed(), PipelineParam.num_inference_steps(28), PipelineParam.guidance_scale(2.5), @@ -1106,8 +1114,8 @@ def _qwen_image_layered_max_sequence_length_param(): "denoise": { "inputs": [ PipelineParam.embeddings(display="input"), - PipelineParam.width(), - PipelineParam.height(), + PipelineParam.width(step=32), + PipelineParam.height(step=32), PipelineParam.seed(), PipelineParam.num_inference_steps(4), PipelineParam.guidance_scale(1.0), @@ -1187,8 +1195,8 @@ def _qwen_image_layered_max_sequence_length_param(): **FLUX_2_KLEIN_DISTILLED_NODE_SPECS["denoise"], "inputs": [ PipelineParam.embeddings(display="input"), - PipelineParam.width(), - PipelineParam.height(), + PipelineParam.width(step=32), + PipelineParam.height(step=32), PipelineParam.seed(), PipelineParam.num_inference_steps(50), PipelineParam.guidance_scale(4.0), @@ -1240,8 +1248,10 @@ def _qwen_image_layered_max_sequence_length_param(): "denoise": { "inputs": [ PipelineParam.embeddings(display="input"), - PipelineParam.width(), - PipelineParam.height(), + # The pinned packed latent preparation requires a spatial multiple + # of 16, not the generic VAE widget's 8-pixel increment. + PipelineParam.width(step=16), + PipelineParam.height(step=16), PipelineParam.seed(), PipelineParam.num_inference_steps(9), PipelineParam.guidance_scale(1.0), @@ -1264,8 +1274,8 @@ def _qwen_image_layered_max_sequence_length_param(): "vae_encoder": { "inputs": [ PipelineParam.image(), - PipelineParam.height(), - PipelineParam.width(), + PipelineParam.height(step=16), + PipelineParam.width(step=16), PipelineParam.seed(), ], "model_inputs": [ @@ -1353,6 +1363,15 @@ def _qwen_image_layered_max_sequence_length_param(): default_dtype="bfloat16", ) +ERNIE_IMAGE_PIPELINE_CONFIG = PipelineConfig( + # ERNIE Image uses its reviewed package-owned prompt-enhancer, text, + # denoise, and decode blocks. Generic block compatibility is not inferred. + node_specs={}, + label="ERNIE Image Turbo", + default_repo="baidu/ERNIE-Image-Turbo", + default_dtype="bfloat16", +) + HELIOS_PIPELINE_CONFIG = PipelineConfig( node_specs={}, label="Helios", @@ -1918,6 +1937,13 @@ def _initialize_registry(registry: ModiffPipelineRegistry): except Exception as e: logger.warning(f"Failed to register AnimaModularPipeline: {e}") + try: + from diffusers import ErnieImageModularPipeline + + registry.register(ErnieImageModularPipeline, ERNIE_IMAGE_PIPELINE_CONFIG) + except Exception as e: + logger.warning(f"Failed to register ErnieImageModularPipeline: {e}") + try: from diffusers import HeliosModularPipeline @@ -2090,6 +2116,25 @@ def get_modular_scheduler_options() -> Dict[str, list[str]]: } +def get_modular_node_action_options() -> Dict[str, Dict[str, list[str]]]: + """Return the reviewed pipeline classes that implement each split-node action. + + Modular component sockets intentionally share transport types such as + ``diffusers_auto_model``. This mapping is the executable semantic contract + behind those sockets: a class is included only when its registered pipeline + config declares the corresponding node action. The list-shaped values keep + this compatible with the existing signal-driven option-map contract. + """ + + result: Dict[str, Dict[str, list[str]]] = {} + for pipeline_cls, config in _get_registry_instance().get_all().items(): + for action, action_config in config.node_params.items(): + if not isinstance(action, str) or not isinstance(action_config, dict): + continue + result.setdefault(action, {})[pipeline_cls.__name__] = [action] + return result + + def pipeline_class_to_modiff_node_config(pipeline_class, node_type=None, *, resolve_blocks=True): """Get the block and MoDiff node parameters for a pipeline class and node type.""" if isinstance(pipeline_class, CustomPipelineBinding): @@ -2115,15 +2160,13 @@ def pipeline_class_to_modiff_node_config(pipeline_class, node_type=None, *, reso return node_type_blocks, node_params -_MODIFF_NODE_ACTION_LABELS = { - "text_encoder": "Encode Prompt", - "image_encoder": "Image Embeddings", - "vae_encoder": "Encode Image", - "denoise": "Denoise", - "decoder": "Decode Latents", - "controlnet": "ControlNet", - "ip_adapter": "IP-Adapter Embeddings", -} +_MODIFF_NODE_ACTION_LABELS = {stage: operation.label for stage, operation in MODULAR_STAGE_OPERATIONS.items()} + + +def get_modular_operation_contracts(modules): + """Describe registered generic stages without resolving executable blocks.""" + configs = {pipeline.__name__: config for pipeline, config in _get_registry_instance().get_all().items()} + return build_modular_operation_contracts(configs, modules) def require_modiff_node_contract(pipeline_class, node_type, *, require_blocks=True, resolve_blocks=True): diff --git a/modules/ModularDiffusers/operation_contracts.py b/modules/ModularDiffusers/operation_contracts.py new file mode 100644 index 00000000..4caab5f1 --- /dev/null +++ b/modules/ModularDiffusers/operation_contracts.py @@ -0,0 +1,249 @@ +"""Operation bindings projected from the existing reviewed Modular owners.""" + +from copy import deepcopy + +from modiff.modular_action_bindings import MODULAR_ACTION_BINDINGS, MODULAR_AUXILIARY_OPERATION_BINDINGS +from modiff.modular_block_contracts import load_reviewed_modular_block_snapshot +from modiff.modular_whole_workflow_contracts import reviewed_whole_workflow_graph_adapter +from modiff.modular_task_adapters import modular_task_adapters as _task_adapters +from modiff.modular_workflow_discovery import load_reviewed_modular_workflow_snapshot +from modiff.operation_contracts import ( + WORKFLOW_STAGE_OPERATIONS, + build_pipeline_operation_contract, + with_operation_semantics, +) + +from .modular_utils import get_model_type_metadata, get_modular_operation_contracts + + +def _members(definitions, group): + result = {} + for definition in definitions: + for field in definition.get(group, []): + item = {"name": field["name"], "type": field["type"]} + previous = result.get(item["name"]) + # Several sequential steps may deliberately refine an opaque type. + # Keep differing representations explicit instead of picking one. + if previous and previous["type"] != item["type"]: + item["type"] = "opaque" + result[item["name"]] = item + return sorted(result.values(), key=lambda item: item["name"]) + + +def _stage_definitions(blocks, definitions, block_name): + return [ + definitions[p["blockDefinitionId"]] + for p in blocks["placements"] + if p["legacyPath"] == block_name or p["legacyPath"].startswith(block_name + ".") + ] + + +def _loader_members(root, blocks, definitions, pipeline_class): + components = _members([root], "components") + by_name = {item["name"]: item for item in components} + denoiser = next((name for name in ("unet", "transformer") if name in by_name), None) + text_components = _members(_stage_definitions(blocks, definitions, "text_encoder"), "components") + metadata = get_model_type_metadata(pipeline_class) or {} + + def selected(names): + return [by_name[name] for name in names if name in by_name] + + return { + "pipeline_components": components, + "text_encoders": text_components, + "unet_out": selected([denoiser]), + "unet": selected([denoiser]), + "vae_out": selected(["vae"]), + "vae": selected(["vae"]), + "scheduler": selected(["scheduler"]), + "image_encoder": selected(metadata.get("loader_component_outputs", [])), + "controlnet": selected(["controlnet"]), + } + + +def get_modular_task_operation_contracts(modules) -> list[dict]: + """Read schemas only: no node/pipeline construction, installation or weights.""" + if not modules.get("modules.ModularDiffusers"): + return [] + generic = {(c["pipelineClass"], c["nodeKey"]): c for c in get_modular_operation_contracts(modules)} + snapshot = load_reviewed_modular_workflow_snapshot() + blocks_snapshot = load_reviewed_modular_block_snapshot() + definitions = {d["id"]: d for d in blocks_snapshot["blockDefinitions"]} + workflows = {(w["pipelineClass"], w["workflowId"]): w for w in blocks_snapshot["workflows"]} + result = [] + for pipeline in snapshot["contracts"]: + pipeline_class = pipeline["pipelineClass"] + for workflow in pipeline["workflows"]: + workflow_id = workflow["id"] + blocks = workflows[pipeline_class, workflow_id] + root = definitions[blocks["rootBlockDefinitionId"]] + for task, adapter in _task_adapters(pipeline_class, workflow): + whole = reviewed_whole_workflow_graph_adapter(pipeline_class, workflow_id) + # A partial module registry must not advertise an executable + # workflow whose required stage action is absent. + node_keys = [MODULAR_ACTION_BINDINGS[key][1] for key in adapter["actionSequence"]] + if any( + key.rsplit(".", 1)[1] not in modules.get(key.rsplit(".", 1)[0], {}) + or (not whole and (pipeline_class, key) not in generic) + for key in node_keys + ): + continue + loader = build_pipeline_operation_contract( + modules, + pipeline_class=pipeline_class, + task=task, + operation_id="diffusion.load_models", + node_key="modules.ModularDiffusers.ModelsLoader", + loader=True, + ) + members = _loader_members(root, blocks, definitions, pipeline_class) + if loader: + for port in loader["ports"]: + if port["direction"] == "output" or port["name"] in {"unet", "vae", "controlnet"}: + port["roles"] = ["component"] + loader = with_operation_semantics( + loader, + workflow_id=workflow_id, + values={ + "model_type": pipeline_class, + }, + ) + # Only the loader's existing allowlist can select workflow + # pruning. Other loaders retain their current unscoped path. + from .loaders import REVIEWED_BUILTIN_WORKFLOWS + + if workflow_id in REVIEWED_BUILTIN_WORKFLOWS.get(pipeline_class, ()): + loader["binding"]["values"]["workflow_id"] = workflow_id + for port in loader["ports"]: + if port["name"] in members: + port["semantics"]["members"] = members[port["name"]] + port["hidden"] = not bool(members[port["name"]]) + result.append(loader) + helper_types = set() + if pipeline_class == "FluxKontextModularPipeline" and task == "multi_image_reference_edit": + helper = build_pipeline_operation_contract( + modules, pipeline_class=pipeline_class, task=task, + operation_id="diffusion.compose_references", node_key="modules.ImageOperations.StitchImages", + field_overrides={"image": {"required": True}}, + ) + if helper is None: + raise ValueError("Native Kontext multi-reference requires its image composition operation.") + helper.update(nodeType="reference_assembly", decomposition="bundle") + result.append(with_operation_semantics(helper, workflow_id=workflow_id, + values={"layout": "horizontal_reference"})) + actions = adapter["actionSequence"] + whole = reviewed_whole_workflow_graph_adapter(pipeline_class, workflow_id) + for index, action_key in enumerate(actions): + role, node_key = MODULAR_ACTION_BINDINGS[action_key] + if whole: + stage = whole["upstreamBlockSequence"][index] + operation_id = WORKFLOW_STAGE_OPERATIONS[stage] + if role == "videoEncode" and stage == "vae_encoder": + operation_id = "diffusion.encode_video" + record = build_pipeline_operation_contract( + modules, + pipeline_class=pipeline_class, + task=task, + operation_id=operation_id, + node_key=node_key, + ) + if record is None: + continue + record.update(nodeType=stage, blockName=stage, decomposition="block") + for port in record["ports"]: + if port["name"] == "pipeline_components": + port["roles"] = ["component"] + record = with_operation_semantics( + record, + workflow_id=workflow_id, + values={ + "pipeline_class": pipeline_class, + "workflow_id": workflow_id, + "block_path": stage, + }, + ) + stage_definitions = _stage_definitions(blocks, definitions, stage) + for port in record["ports"]: + semantics = port["semantics"] + if semantics["kind"] == "state": + semantics["state"] = ( + stage + if port["direction"] == "output" + else (whole["upstreamBlockSequence"][index - 1] if index else None) + ) + semantics["members"] = _members( + stage_definitions, "outputs" if port["direction"] == "output" else "inputs" + ) + elif port["name"] == "pipeline_components": + semantics["members"] = _members(stage_definitions, "components") + else: + record = generic.get((pipeline_class, node_key)) + if record is None: + continue + record = deepcopy(record) + record["task"] = task + record = with_operation_semantics(record, workflow_id=workflow_id) + stage_definitions = _stage_definitions( + blocks, definitions, record["blockName"] or record["nodeType"] + ) + for port in record["ports"]: + if "component" in port["roles"]: + components = _members(stage_definitions, "components") + exact = [item for item in components if item["name"] == port["semanticName"]] + if not exact: + # Generic sockets (e.g. unet) can bind a + # differently named upstream component. Use + # the loader's declared component projection, + # not every dependency of the whole block. + names = {item["name"] for item in members.get(port["name"], [])} + exact = [item for item in components if item["name"] in names] + # The node wrapper can require a component not + # listed on the selected upstream block (e.g. a + # VAE for inpaint route validation). Preserve its + # exact loader projection instead of claiming all + # denoiser dependencies for that single socket. + port["semantics"]["members"] = exact or members.get(port["name"]) or components + # Generic node schemas keep optional media sockets for other + # tasks. A selected workflow can require those same sockets. + # Publish that requirement on the operation itself as well + # as the connected starter, including individually inserted nodes. + for port in record["ports"]: + if (port["direction"] == "input" and port["semantics"]["kind"] == "media" + and port["name"] in adapter["requiredInputs"]): + port["required"] = True + result.append(record) + helper_types.update(t for p in record["ports"] if p["direction"] == "input" for t in p["types"]) + for type_name in sorted(helper_types & MODULAR_AUXILIARY_OPERATION_BINDINGS.keys()): + operation_id, node_key = MODULAR_AUXILIARY_OPERATION_BINDINGS[type_name] + helper = build_pipeline_operation_contract( + modules, pipeline_class=pipeline_class, task=task, operation_id=operation_id, node_key=node_key + ) + if helper: + helper.update(nodeType="reference_assembly", decomposition="bundle") + result.append(with_operation_semantics(helper, workflow_id=workflow_id)) + if pipeline_class == "StableDiffusionXLModularPipeline" and "custom_guider" in helper_types: + helper = build_pipeline_operation_contract( + modules, pipeline_class=pipeline_class, task=task, + operation_id="diffusion.guidance", node_key="modules.ModularDiffusers.Guider", + ) + if helper: + helper.update(nodeType="guidance", decomposition="bundle") + for port in helper["ports"]: + if port["name"] == "guider_out": + port["roles"] = ["component"] + result.append(with_operation_semantics( + helper, workflow_id=workflow_id, values={"model_type": pipeline_class}, + )) + layers = build_pipeline_operation_contract( + modules, pipeline_class=pipeline_class, task=task, + operation_id="diffusion.guidance_layers", node_key="modules.ModularDiffusers.Layers", + ) + if layers: + layers.update(nodeType="guidance_layers", decomposition="bundle") + result.append(with_operation_semantics( + layers, workflow_id=workflow_id, values={"model_type": pipeline_class}, + )) + identities = [(c["pipelineClass"], c["task"], c["operationId"]) for c in result] + if len(identities) != len(set(identities)): + raise ValueError("Ambiguous Modular operation bindings.") + return result diff --git a/modules/ModularDiffusers/pipeline_schema.py b/modules/ModularDiffusers/pipeline_schema.py index 059846b6..3eef16cb 100644 --- a/modules/ModularDiffusers/pipeline_schema.py +++ b/modules/ModularDiffusers/pipeline_schema.py @@ -847,25 +847,44 @@ def _name_to_label(name: str) -> str: # Video "videos": {"label": "Videos", "type": "video", "display": "output", "required_block_params": ["videos"]}, # Models - "vae": {"label": "VAE", "type": "diffusers_auto_model", "display": "input", "required_block_params": ["vae"]}, + "vae": { + "label": "VAE", + "type": "diffusers_auto_model", + "display": "input", + "signalCompatibility": {"role": "vae"}, + "required_block_params": ["vae"], + }, "image_encoder": { "label": "Image Encoder", "type": "diffusers_auto_model", "display": "input", + "signalCompatibility": {"role": "image_encoder"}, "required_block_params": ["image_encoder"], }, - "unet": {"label": "Denoise Model", "type": "diffusers_auto_model", "display": "input"}, - "scheduler": {"label": "Scheduler", "type": "diffusers_auto_model", "display": "input"}, + "unet": { + "label": "Denoise Model", + "type": "diffusers_auto_model", + "display": "input", + "signalCompatibility": {"role": "denoiser"}, + }, + "scheduler": { + "label": "Scheduler", + "type": "diffusers_auto_model", + "display": "input", + "signalCompatibility": {"role": "scheduler"}, + }, "controlnet": { "label": "ControlNet Model", "type": "diffusers_auto_model", "display": "input", + "signalCompatibility": {"role": "controlnet_component"}, "required_block_params": ["controlnet"], }, "text_encoders": { "label": "Text Encoders", "type": "diffusers_auto_models", "display": "input", + "signalCompatibility": {"role": "text_encoders"}, "required_block_params": ["text_encoder"], }, # Bundles/Custom @@ -953,6 +972,8 @@ class MoDiffParam(metaclass=MoDiffParamMeta): fieldOptions: dict[str, Any] | None = None onChange: Any = None onSignal: Any = None + connectionRole: str | None = None + signalCompatibility: dict[str, Any] | None = None required_block_params: str | list[str] | None = None def to_dict(self) -> dict[str, Any]: @@ -1585,7 +1606,13 @@ def to_json_file(self, json_file_path: str | os.PathLike): writer.write(self.to_json_string()) @classmethod - def from_json_bytes(cls, raw_bytes: bytes, *, source_label: str = "") -> "MoDiffPipelineConfig": + def from_json_bytes( + cls, + raw_bytes: bytes, + *, + source_label: str = "", + allow_omitted_custom_model_inputs: bool = False, + ) -> "MoDiffPipelineConfig": """Load one bounded, duplicate-free JSON object from an exact byte sequence.""" if len(raw_bytes) > MAX_MODIFF_PIPELINE_CONFIG_BYTES: @@ -1594,6 +1621,14 @@ def from_json_bytes(cls, raw_bytes: bytes, *, source_label: str = "") -> f"{MAX_MODIFF_PIPELINE_CONFIG_BYTES}-byte limit." ) data = _decode_pipeline_config_bytes(raw_bytes, source_label=source_label) + if allow_omitted_custom_model_inputs: + # Published Mellon custom sidecars can omit this optional list. + # Normalize only the parsed review contract, never source bytes or + # an explicitly invalid value. Ordinary pipeline loading stays strict. + actions = data.get("node_params") + custom = actions.get("custom") if isinstance(actions, dict) else None + if isinstance(custom, dict): + custom.setdefault("model_input_names", []) _validate_pipeline_config_document(data, source_label=source_label) return cls.from_dict(data) diff --git a/modules/ModularDiffusers/reviewed_blocks.py b/modules/ModularDiffusers/reviewed_blocks.py index 9064c373..4453733f 100644 --- a/modules/ModularDiffusers/reviewed_blocks.py +++ b/modules/ModularDiffusers/reviewed_blocks.py @@ -445,10 +445,10 @@ def _reviewed_resolved_dimensions(pipeline_class, block_class, state, values): return {**values, "height": state.get("height"), "width": state.get("width")} -def _reviewed_definition(pipeline_class, workflow_id): +def _reviewed_definition(pipeline_class, workflow_id, library=None): matches = [ definition - for definition in reviewed_huggingface_node_library()["definitions"] + for definition in (library if library is not None else reviewed_huggingface_node_library())["definitions"] if definition.get("provider") == "diffusers" and definition.get("pipelineClass") == pipeline_class and definition.get("workflowId") == workflow_id @@ -460,7 +460,7 @@ def _reviewed_definition(pipeline_class, workflow_id): def _reviewed_placement( *, pipeline_class, workflow_id, execution_scope, placement_path, block_definition_id, block_class, block_hash, - composition=None, + composition=None, library=None, snapshot=None, ): if composition is not None: if ( @@ -473,7 +473,7 @@ def _reviewed_placement( (item for item in composition["composedPlacements"] if tuple(item["path"]) == placement_path), None, ) block = next( - (item for item in reviewed_modular_conditional_snapshot()["blockDefinitions"] + (item for item in (snapshot if snapshot is not None else reviewed_modular_conditional_snapshot())["blockDefinitions"] if item.get("id") == block_definition_id), None, ) if ( @@ -490,7 +490,7 @@ def _reviewed_placement( # snapshot, exactly as an explicitly unpruned library block does. fixed_default_tree = execution_scope == "selected_workflow" and workflow_id == "default" if execution_scope == "unpruned_pipeline" or fixed_default_tree: - snapshot = reviewed_modular_conditional_snapshot() + snapshot = snapshot if snapshot is not None else reviewed_modular_conditional_snapshot() pipeline = next( (item for item in snapshot["pipelines"] if item.get("pipelineClass") == pipeline_class), None, @@ -518,8 +518,8 @@ def _reviewed_placement( return pipeline, block if execution_scope != "selected_workflow": raise ValueError("The reviewed Modular execution scope is unsupported.") - definition = _reviewed_definition(pipeline_class, workflow_id) - library = reviewed_huggingface_node_library() + library = library if library is not None else reviewed_huggingface_node_library() + definition = _reviewed_definition(pipeline_class, workflow_id, library) placement = next( (item for item in definition["blockPlacements"] if tuple(item["path"]) == placement_path), None, @@ -619,7 +619,18 @@ def _continued_runtime(value, *, bundle, pipeline_class, workflow_id, execution_ raise ValueError("The connected block state belongs to another Modular workflow.") if getattr(value._pipeline, "_modiff_composition_hash", None) != composition_hash: raise ValueError("The connected Pipeline State belongs to a different edited Modular composition. Re-run its upstream nodes.") - return token, value._pipeline, value._state + # A cached step owns a snapshot. Later branches/retries must not advance its + # scheduler, guider, generator or tensors. Share only resident neural weights + # and the manager; deepcopy also preserves aliases within this continuation. + pipeline = value._pipeline + manager = getattr(pipeline, "_components_manager", None) + memo = {id(manager): manager} if manager is not None else {} + for name in getattr(pipeline, "pretrained_component_names", ()): + component = getattr(pipeline, name, None) + if isinstance(component, torch.nn.Module): + memo[id(component)] = component + forked_pipeline, forked_state = deepcopy((pipeline, value._state), memo) + return token, forked_pipeline, forked_state def _loop_member_descriptor(kwargs, path, block): diff --git a/modules/ModularDiffusers/schedulers.py b/modules/ModularDiffusers/schedulers.py index 79a8fc89..f38fe080 100644 --- a/modules/ModularDiffusers/schedulers.py +++ b/modules/ModularDiffusers/schedulers.py @@ -300,11 +300,19 @@ class Scheduler(NodeBase): "label": "Scheduler", "display": "input", "type": "diffusers_auto_model", - "onSignal": { - "action": "value", - "target": "scheduler", - "prop": "options", - "data": MODULAR_SCHEDULER_OPTIONS, + "onSignal": [ + { + "action": "value", + "target": "scheduler", + "prop": "options", + "data": MODULAR_SCHEDULER_OPTIONS, + }, + {"action": "signal", "target": "scheduler_out"}, + ], + "signalCompatibility": { + "required": True, + "role": "scheduler", + "values": MODULAR_SCHEDULER_OPTIONS, }, }, "scheduler": { @@ -331,7 +339,13 @@ class Scheduler(NodeBase): "value": "EulerDiscreteScheduler", "onChange": "updateNode", }, - "scheduler_out": {"label": "Scheduler", "display": "output", "type": "diffusers_auto_model"}, + "scheduler_out": { + "label": "Scheduler", + "display": "output", + "type": "diffusers_auto_model", + "signal": {"direction": "output", "origin": "scheduler_in", "value": ""}, + "connectionRole": "scheduler", + }, "prediction_type": { "label": "Prediction Type", "type": "string", @@ -343,8 +357,9 @@ class Scheduler(NodeBase): }, } - def _selected_scheduler(self, scheduler): - model_type = self.get_signal_value("scheduler_in") + def _selected_scheduler(self, scheduler, model_type=None): + if model_type in (None, ""): + model_type = self.get_signal_value("scheduler_in") allowed = MODULAR_SCHEDULER_OPTIONS.get(model_type) if isinstance(model_type, str) else None if ( not isinstance(scheduler, str) @@ -368,7 +383,7 @@ def execute(self, scheduler_in, scheduler, **kwargs): logger.debug(f" - scheduler: {scheduler}") logger.debug(f" - kwargs: {kwargs}") - scheduler = self._selected_scheduler(scheduler) + scheduler = self._selected_scheduler(scheduler, scheduler_in.get("model_type")) scheduler_component = components.get_one(scheduler_in["model_id"]) scheduler_cls = getattr(__import__("diffusers", fromlist=[scheduler]), scheduler) current_scheduler_cls = type(scheduler_component) diff --git a/modules/ModularDiffusers/workflow_blocks.py b/modules/ModularDiffusers/workflow_blocks.py index da257828..70f8ce53 100644 --- a/modules/ModularDiffusers/workflow_blocks.py +++ b/modules/ModularDiffusers/workflow_blocks.py @@ -25,7 +25,7 @@ from modiff.NodeBase import NodeBase from . import components -from .modular_utils import modular_generator_from_seed, pipeline_class_from_model_type +from .modular_utils import modular_generator_from_seed, normalize_modular_runtime_params, pipeline_class_from_model_type from .route_state import require_component_binding from .utils import collect_model_ids from .workflow_runtime import continuation_generator_from_seed @@ -38,6 +38,7 @@ _MINIMAX_PIPELINE = "MiniMaxMusic3ModularPipeline" _MINIMAX_WORKFLOW = "default" _ANIMA_PIPELINE = "AnimaModularPipeline" +_ERNIE_IMAGE_PIPELINE = "ErnieImageModularPipeline" _HELIOS_BASE_PIPELINE = "HeliosModularPipeline" _HELIOS_PYRAMID_PIPELINE = "HeliosPyramidModularPipeline" _HELIOS_DISTILLED_PIPELINE = "HeliosPyramidDistilledModularPipeline" @@ -198,6 +199,58 @@ def _reviewed_preceding_stage(pipeline_class, workflow_id, action_key): return contract["upstreamBlockSequence"][action_index - 1] +def _official_stage_block(blocks, block_path): + """Recover one selected stage from upstream's flattened workflow view. + + get_workflow/get_execution_blocks return dotted *leaf* keys. Recompose + only this stage's selected leaves, retaining their order and loop wrappers; + selecting the unpruned original stage would reintroduce inactive branches. + """ + block = blocks.sub_blocks.get(block_path) + if block is not None: + return block + prefix = block_path + "." + selected = { + name[len(prefix):]: child for name, child in blocks.sub_blocks.items() + if name.startswith(prefix) + } + if not selected: + return None + from diffusers.modular_pipelines import SequentialPipelineBlocks + return SequentialPipelineBlocks.from_blocks_dict(selected) + + +def _official_stage_component_dependencies(block, *, pipeline_class, block_path, definition): + """Retain reviewed cross-stage component geometry without copying model math. + + The pinned Anima stage specs omit components used by pipeline properties: + denoise reads vae_scale_factor; VAE encode reads num_channels_latents. + Use the full official definition's specs and the same bound loader objects. + """ + required = { + (_ANIMA_PIPELINE, "denoise"): ("vae",), + (_ANIMA_PIPELINE, "vae_encoder"): ("transformer",), + }.get((pipeline_class, block_path), ()) + if not required: + return block + from diffusers.modular_pipelines import SequentialPipelineBlocks + + specs = {spec.name: spec for spec in definition.blocks.expected_components} + if any(name not in specs for name in required): + raise ValueError("The official Anima definition is missing a reviewed geometry component.") + + class StageWithGeometryComponents(SequentialPipelineBlocks): + @property + def expected_components(self): + original = super().expected_components + names = {spec.name for spec in original} + return original + [spec for spec in self.geometry_component_specs if spec.name not in names] + + wrapped = StageWithGeometryComponents.from_blocks_dict({block_path: block}) + wrapped.geometry_component_specs = [specs[name] for name in required] + return wrapped + + class _OfficialWorkflowBlockMixin: stage = "" action_key = "" @@ -221,13 +274,18 @@ def _prepare_pipeline(self, *, pipeline_components, pipeline_class, workflow_id, pipeline_type = pipeline_class_from_model_type(pipeline_class) definition = pipeline_type() blocks = definition.blocks - if workflow_id != "default": + if pipeline_class == _ERNIE_IMAGE_PIPELINE and workflow_id == "text2image": + blocks = blocks.get_execution_blocks(use_pe=True) + elif workflow_id != "default": blocks = blocks.get_workflow(workflow_id) - block = blocks.sub_blocks.get(block_path) + block = _official_stage_block(blocks, block_path) if block is None: raise ValueError( f"The reviewed Modular workflow {pipeline_class}/{workflow_id} has no block {block_path!r}." ) + block = _official_stage_component_dependencies( + block, pipeline_class=pipeline_class, block_path=block_path, definition=definition, + ) pipeline = block.init_pipeline(components_manager=components) expected_components = tuple(pipeline.pretrained_component_names) model_ids = collect_model_ids( @@ -247,6 +305,339 @@ def _prepare_pipeline(self, *, pipeline_components, pipeline_class, workflow_id, return token, pipeline +def _anima_image_dimensions(width, height): + # Pinned Anima's VAE factor 8 and patch size 2 require a 16-pixel grid. + for label, value in (("width", width), ("height", height)): + if isinstance(value, bool) or not isinstance(value, int) or not 512 <= value <= 1536 or value % 16: + raise ValueError(f"Anima {label} must be a multiple of 16 from 512 through 1536.") + + +def _ernie_image_dimensions(width, height): + for label, value in (("width", width), ("height", height)): + if isinstance(value, bool) or not isinstance(value, int) or not 512 <= value <= 2048 or value % 32: + raise ValueError(f"ERNIE Image Turbo {label} must be a multiple of 32 from 512 through 2048.") + if width * height > 1024 * 1024: + raise ValueError("ERNIE Image Turbo width times height must not exceed 1,048,576 pixels.") + + +class WorkflowErniePromptEnhance(_OfficialWorkflowBlockMixin, NodeBase): + """Run ERNIE Image Turbo's official conditional prompt-enhancer block.""" + + label = "ERNIE Image Prompt Enhance" + category = "Modular Diffusers" + resizable = True + skipParamsCheck = True + stage = "prompt_enhancer" + action_key = "workflow_ernie_prompt_enhancer" + params = { + "pipeline_components": { + "label": "Pipeline Components", + "display": "input", + "type": "diffusers_modular_pipeline_components", + "required": True, + }, + "pipeline_class": {"type": "string", "default": "ErnieImageModularPipeline", "hidden": True}, + "workflow_id": {"type": "string", "default": "text2image", "hidden": True}, + "block_path": {"type": "string", "default": "prompt_enhancer", "hidden": True}, + "prompt": {"label": "Prompt", "display": "textarea", "type": "text", "default": ""}, + "width": {"label": "Width", "type": "int", "default": 1024, "min": 512, "max": 2048, "step": 32}, + "height": { + "label": "Height", + "type": "int", + "default": 1024, + "min": 512, + "max": 2048, + "step": 32, + }, + "pe_system_prompt": { + "label": "Enhancer System Prompt", + "display": "textarea", + "type": "text", + "default": "", + "optional": True, + }, + "pe_temperature": { + "label": "Enhancer Temperature", + "type": "float", + "default": 0.6, + "min": 0.01, + "max": 2.0, + "step": 0.01, + }, + "pe_top_p": { + "label": "Enhancer Top P", + "type": "float", + "default": 0.95, + "min": 0.01, + "max": 1.0, + "step": 0.01, + }, + "state_out": {"label": "Workflow State", "display": "output", "type": "modular_workflow_state"}, + } + + def execute(self, **kwargs): + kwargs = normalize_modular_runtime_params(kwargs, {"params": type(self).params}) + pipeline_class = _require_exact_string( + kwargs.get("pipeline_class"), label="Pipeline class", expected=_ERNIE_IMAGE_PIPELINE + ) + workflow_id = _require_exact_string(kwargs.get("workflow_id"), label="Workflow", expected="text2image") + token, pipeline = self._prepare_pipeline( + pipeline_components=kwargs.get("pipeline_components"), + pipeline_class=pipeline_class, + workflow_id=workflow_id, + block_path=kwargs.get("block_path"), + ) + prompt = kwargs.get("prompt") + if type(prompt) is not str or not prompt.strip(): + raise ValueError("ERNIE Image Turbo requires a nonblank prompt.") + width = kwargs.get("width", 1024) + height = kwargs.get("height", 1024) + _ernie_image_dimensions(width, height) + system_prompt = kwargs.get("pe_system_prompt") or None + if system_prompt is not None and type(system_prompt) is not str: + raise ValueError("ERNIE Image Turbo enhancer system prompt must be text.") + temperature = float(kwargs.get("pe_temperature", 0.6)) + top_p = float(kwargs.get("pe_top_p", 0.95)) + if not math.isfinite(temperature) or not 0.0 < temperature <= 2.0: + raise ValueError("ERNIE Image Turbo enhancer temperature must be greater than 0 and at most 2.") + if not math.isfinite(top_p) or not 0.0 < top_p <= 1.0: + raise ValueError("ERNIE Image Turbo enhancer top-p must be greater than 0 and at most 1.") + state = pipeline( + prompt=prompt, + width=width, + height=height, + use_pe=True, + pe_system_prompt=system_prompt, + pe_temperature=temperature, + pe_top_p=top_p, + ) + return { + "state_out": _issue_workflow_state( + token=token, + pipeline_class=pipeline_class, + workflow_id=workflow_id, + completed_stage=self.stage, + state=state, + ) + } + + +class WorkflowErnieTextEncode(_OfficialWorkflowBlockMixin, NodeBase): + """Run ERNIE Image's official variable-length text-encoder block.""" + + label = "ERNIE Image Text Encode" + category = "Modular Diffusers" + resizable = True + skipParamsCheck = True + stage = "text_encoder" + action_key = "workflow_ernie_text_encoder" + params = { + "pipeline_components": { + "label": "Pipeline Components", + "display": "input", + "type": "diffusers_modular_pipeline_components", + "required": True, + }, + "state_in": { + "label": "Workflow State", + "display": "input", + "type": "modular_workflow_state", + "required": True, + }, + "pipeline_class": {"type": "string", "default": "ErnieImageModularPipeline", "hidden": True}, + "workflow_id": {"type": "string", "default": "text2image", "hidden": True}, + "block_path": {"type": "string", "default": "text_encoder", "hidden": True}, + "negative_prompt": { + "label": "Negative Prompt", + "display": "textarea", + "type": "text", + "default": "", + "hidden": True, + }, + "state_out": {"label": "Workflow State", "display": "output", "type": "modular_workflow_state"}, + } + + def execute(self, **kwargs): + pipeline_class = kwargs.get("pipeline_class") + workflow_id = kwargs.get("workflow_id") + token, pipeline = self._prepare_pipeline( + pipeline_components=kwargs.get("pipeline_components"), + pipeline_class=pipeline_class, + workflow_id=workflow_id, + block_path=kwargs.get("block_path"), + ) + state = _require_workflow_state( + kwargs.get("state_in"), + token=token, + pipeline_class=pipeline_class, + workflow_id=workflow_id, + completed_stage=_reviewed_preceding_stage(pipeline_class, workflow_id, self.action_key), + ) + negative_prompt = kwargs.get("negative_prompt", "") + if type(negative_prompt) is not str: + raise ValueError("ERNIE Image Turbo negative prompt must be text.") + state = pipeline(state=state, negative_prompt=negative_prompt) + return { + "state_out": _issue_workflow_state( + token=token, + pipeline_class=pipeline_class, + workflow_id=workflow_id, + completed_stage=self.stage, + state=state, + ) + } + + +class WorkflowErnieImageDenoise(_OfficialWorkflowBlockMixin, NodeBase): + """Run ERNIE Image Turbo's official denoise block with its exact bounds.""" + + label = "ERNIE Image Denoise" + category = "Modular Diffusers" + resizable = True + skipParamsCheck = True + stage = "denoise" + action_key = "workflow_ernie_image_denoise" + params = { + "pipeline_components": { + "label": "Pipeline Components", + "display": "input", + "type": "diffusers_modular_pipeline_components", + "required": True, + }, + "state_in": { + "label": "Workflow State", + "display": "input", + "type": "modular_workflow_state", + "required": True, + }, + "pipeline_class": {"type": "string", "default": "ErnieImageModularPipeline", "hidden": True}, + "workflow_id": {"type": "string", "default": "text2image", "hidden": True}, + "block_path": {"type": "string", "default": "denoise", "hidden": True}, + "width": {"label": "Width", "type": "int", "default": 1024, "min": 512, "max": 2048, "step": 32}, + "height": { + "label": "Height", + "type": "int", + "default": 1024, + "min": 512, + "max": 2048, + "step": 32, + }, + "num_inference_steps": { + "label": "Steps", + "display": "slider", + "type": "int", + "default": 8, + "min": 1, + "max": 8, + }, + "seed": { + "label": "Seed", + "display": "random", + "type": "int", + "default": 0, + "min": 0, + "max": 4294967295, + }, + "state_out": {"label": "Workflow State", "display": "output", "type": "modular_workflow_state"}, + } + + def execute(self, **kwargs): + kwargs = normalize_modular_runtime_params(kwargs, {"params": type(self).params}) + pipeline_class = kwargs.get("pipeline_class") + workflow_id = kwargs.get("workflow_id") + token, pipeline = self._prepare_pipeline( + pipeline_components=kwargs.get("pipeline_components"), + pipeline_class=pipeline_class, + workflow_id=workflow_id, + block_path=kwargs.get("block_path"), + ) + state = _require_workflow_state( + kwargs.get("state_in"), + token=token, + pipeline_class=pipeline_class, + workflow_id=workflow_id, + completed_stage=_reviewed_preceding_stage(pipeline_class, workflow_id, self.action_key), + ) + width = kwargs.get("width", 1024) + height = kwargs.get("height", 1024) + _ernie_image_dimensions(width, height) + steps = kwargs.get("num_inference_steps", 8) + if isinstance(steps, bool) or not isinstance(steps, int) or not 1 <= steps <= 8: + raise ValueError("ERNIE Image Turbo denoise steps must be an integer from 1 through 8.") + if getattr(pipeline, "guider", None) is None or not callable(getattr(pipeline.guider, "new", None)): + raise ValueError("The official ERNIE Image denoise block is missing its guidance component.") + pipeline.update_components(guider=pipeline.guider.new(guidance_scale=1.0)) + generator = modular_generator_from_seed(kwargs.get("seed", 0), pipeline) + state = pipeline( + state=state, + width=width, + height=height, + num_images_per_prompt=1, + num_inference_steps=steps, + generator=generator, + ) + return { + "state_out": _issue_workflow_state( + token=token, + pipeline_class=pipeline_class, + workflow_id=workflow_id, + completed_stage=self.stage, + state=state, + ) + } + + +class WorkflowErnieDecodeImage(_OfficialWorkflowBlockMixin, NodeBase): + """Run ERNIE Image's official VAE decoder block.""" + + label = "ERNIE Image Decode" + category = "Modular Diffusers" + resizable = True + skipParamsCheck = True + stage = "decode" + action_key = "workflow_ernie_image_decoder" + params = { + "pipeline_components": { + "label": "Pipeline Components", + "display": "input", + "type": "diffusers_modular_pipeline_components", + "required": True, + }, + "state_in": { + "label": "Workflow State", + "display": "input", + "type": "modular_workflow_state", + "required": True, + }, + "pipeline_class": {"type": "string", "default": "ErnieImageModularPipeline", "hidden": True}, + "workflow_id": {"type": "string", "default": "text2image", "hidden": True}, + "block_path": {"type": "string", "default": "decode", "hidden": True}, + "images": {"label": "Images", "display": "output", "type": "image"}, + } + + def execute(self, **kwargs): + pipeline_class = kwargs.get("pipeline_class") + workflow_id = kwargs.get("workflow_id") + token, pipeline = self._prepare_pipeline( + pipeline_components=kwargs.get("pipeline_components"), + pipeline_class=pipeline_class, + workflow_id=workflow_id, + block_path=kwargs.get("block_path"), + ) + state = _require_workflow_state( + kwargs.get("state_in"), + token=token, + pipeline_class=pipeline_class, + workflow_id=workflow_id, + completed_stage=_reviewed_preceding_stage(pipeline_class, workflow_id, self.action_key), + ) + state = pipeline(state=state, output_type="pil") + images = state.get("images") + if not isinstance(images, list) or len(images) != 1 or not isinstance(images[0], PILImage.Image): + raise ValueError("The official ERNIE Image decoder did not return exactly one PIL image.") + return {"images": images} + + class WorkflowSemanticGeneration(_OfficialWorkflowBlockMixin, NodeBase): """Run MiniMax Music 3's official semantic-generation block.""" @@ -549,14 +940,14 @@ class WorkflowImageEncode(_OfficialWorkflowBlockMixin, NodeBase): "workflow_id": {"type": "string", "default": "img2img", "hidden": True}, "block_path": {"type": "string", "default": "vae_encoder", "hidden": True}, "image": {"label": "Image", "display": "input", "type": "image", "required": True}, - "width": {"label": "Width", "type": "int", "default": 1024, "min": 512, "max": 1536, "step": 8}, + "width": {"label": "Width", "type": "int", "default": 1024, "min": 512, "max": 1536, "step": 16}, "height": { "label": "Height", "type": "int", "default": 1024, "min": 512, "max": 1536, - "step": 8, + "step": 16, }, "seed": { "label": "Seed", @@ -583,9 +974,7 @@ def execute(self, **kwargs): raise ValueError("Anima image-to-image requires one exact PIL image.") width = kwargs.get("width", 1024) height = kwargs.get("height", 1024) - for label, value in (("width", width), ("height", height)): - if isinstance(value, bool) or not isinstance(value, int) or not 512 <= value <= 1536 or value % 8: - raise ValueError(f"Anima {label} must be a multiple of 8 from 512 through 1536.") + _anima_image_dimensions(width, height) previous_stage = _reviewed_preceding_stage(pipeline_class, workflow_id, self.action_key) state = _require_workflow_state( kwargs.get("state_in"), @@ -638,14 +1027,14 @@ class WorkflowImageDenoise(_OfficialWorkflowBlockMixin, NodeBase): "pipeline_class": {"type": "string", "default": "AnimaModularPipeline", "hidden": True}, "workflow_id": {"type": "string", "default": "text2image", "hidden": True}, "block_path": {"type": "string", "default": "denoise", "hidden": True}, - "width": {"label": "Width", "type": "int", "default": 1024, "min": 512, "max": 1536, "step": 8}, + "width": {"label": "Width", "type": "int", "default": 1024, "min": 512, "max": 1536, "step": 16}, "height": { "label": "Height", "type": "int", "default": 1024, "min": 512, "max": 1536, - "step": 8, + "step": 16, }, "num_images_per_prompt": { "label": "Images per Prompt", @@ -710,9 +1099,7 @@ def execute(self, **kwargs): ) width = kwargs.get("width", 1024) height = kwargs.get("height", 1024) - for label, value in (("width", width), ("height", height)): - if isinstance(value, bool) or not isinstance(value, int) or not 512 <= value <= 1536 or value % 8: - raise ValueError(f"Anima {label} must be a multiple of 8 from 512 through 1536.") + _anima_image_dimensions(width, height) steps = kwargs.get("num_inference_steps", 50) images_per_prompt = kwargs.get("num_images_per_prompt", 1) if isinstance(steps, bool) or not isinstance(steps, int) or not 1 <= steps <= 100: diff --git a/modules/ModularDiffusers/workflow_runtime.py b/modules/ModularDiffusers/workflow_runtime.py index f1a9a67f..cb8cb804 100644 --- a/modules/ModularDiffusers/workflow_runtime.py +++ b/modules/ModularDiffusers/workflow_runtime.py @@ -1,8 +1,8 @@ """Runtime helpers shared by split official Modular Diffusers workflows. The upstream ``ModularPipeline`` receives one ``torch.Generator`` object for a -whole call. A visual split workflow must therefore continue that same object -from its sealed process-local block state. Recreating a generator from the +whole call. A visual split workflow must continue that random stream from its +sealed process-local block state. Recreating a generator from the same seed at every stage resets the random stream and is not equivalent to the upstream pipeline. """ @@ -26,7 +26,7 @@ def _workflow_state_value(state, name): def continuation_generator_from_seed(seed, pipeline, state=None): - """Create the workflow generator once, then reuse it from block state. + """Continue from the preceding stage without advancing its cached snapshot. ``state`` is issued by MoDiff's process-local workflow-state authority, so accepting its generator does not expose a graph-supplied Python object. @@ -53,4 +53,8 @@ def continuation_generator_from_seed(seed, pipeline, state=None): ) if torch.device(existing.device) != torch.device(execution_device): raise ValueError("The Modular workflow generator belongs to a different execution device.") - return existing + # ModularPipeline deep-copies ``state``, but then applies keyword arguments + # without copying them. Passing the cached Generator here would advance the + # preceding stage's snapshot, changing a retry or a downstream-only edit. + # Clone the *current* stream position, not just its initial seed. + return existing.clone_state() diff --git a/modules/Spandrel/__init__.py b/modules/Spandrel/__init__.py index 7c76689b..4c09998c 100644 --- a/modules/Spandrel/__init__.py +++ b/modules/Spandrel/__init__.py @@ -18,6 +18,7 @@ "display": "modelselect", "type": "string", "default": real_esrgan_x2_model_selection(), + "onChange": "update_model_selection", "fieldOptions": { "noValidation": True, "sources": ['hub', 'local'], diff --git a/modules/Spandrel/main.py b/modules/Spandrel/main.py index 1f7db6d7..08bb2b98 100644 --- a/modules/Spandrel/main.py +++ b/modules/Spandrel/main.py @@ -13,8 +13,19 @@ from utils.torch_utils import ImageToTensor, TensorToImage from utils.image import resize from modiff.config import CONFIG +from . import MODULE_MAP class Upscaler(NodeBase): + params = MODULE_MAP["Upscaler"]["params"] + + def update_model_selection(self, values, ref): + from modiff.controlled_artifacts import pin_upscaler_model_selection + + selection = values.get("model_id") + if not selection or isinstance(selection, dict) and not selection.get("value"): + return + self.set_field_value({"model_id": pin_upscaler_model_selection(selection)}) + def __init__(self, node_id=None): super().__init__(node_id) diff --git a/modules/Video/main.py b/modules/Video/main.py index c8674371..f432f657 100644 --- a/modules/Video/main.py +++ b/modules/Video/main.py @@ -1629,6 +1629,7 @@ class UpscaleVideo(NodeBase): "display": "modelselect", "type": "string", "default": VIDEO_UPSCALE_MODEL_SELECTION, + "onChange": "update_model_selection", "fieldOptions": { "noValidation": True, "sources": ["hub", "local"], @@ -1665,6 +1666,14 @@ class UpscaleVideo(NodeBase): "frames": {"display": "output", "type": "int"}, } + def update_model_selection(self, values, ref): + from modiff.controlled_artifacts import pin_upscaler_model_selection + + selection = values.get("model_id") + if not selection or isinstance(selection, dict) and not selection.get("value"): + return + self.set_field_value({"model_id": pin_upscaler_model_selection(selection)}) + def execute(self, **kwargs): import imageio import numpy as np diff --git a/modules/__init__.py b/modules/__init__.py index ee71337b..27fde99a 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -209,6 +209,11 @@ def parse_module_map(base_path: str) -> None: logger.warning(f"Module '{module_name}' could not be parsed or has no NodeBase classes.") parse_module_map("modules") -parse_module_map("custom") +from modiff.custom_extensions import ExtensionStore # noqa: E402 +try: + ExtensionStore().load_enabled(MODULE_MAP) +except (ValueError, OSError) as error: + logger.error('Custom extension discovery is unavailable: %s', error) +total_nodes = sum(len(nodes) for nodes in MODULE_MAP.values()) logger.info(f"Loaded {total_nodes} nodes from {len(MODULE_MAP)} modules.") diff --git a/pyproject.toml b/pyproject.toml index bb6ba3e4..8e75ebc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,9 +22,9 @@ dependencies = [ "torchsde>=0.2.6", "torchvision>=0.21.0", "transformers>=4.49.0; sys_platform == 'darwin' or platform_machine == 'aarch64' or platform_machine == 'arm64' or platform_machine == 'ARM64'", - "diffusers @ git+https://github.com/huggingface/diffusers.git@2f7e0154a9db246e95c9ede43edba7db5b130805", + "diffusers @ git+https://github.com/huggingface/diffusers.git@fbf49e7f35857f76bc57b177e26f12b03687c668", "ftfy>=6.3.1", - "huggingface-hub>=1.23.0,<2.0", + "huggingface-hub>=1.31.0,<2.0", "imageio>=2.37.2", "imageio-ffmpeg>=0.6.0", "spandrel>=0.4.2", diff --git a/scripts/generate_image_prototyping_readiness.py b/scripts/generate_image_prototyping_readiness.py new file mode 100644 index 00000000..6fa9816f --- /dev/null +++ b/scripts/generate_image_prototyping_readiness.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""Generate or verify the deterministic image prototyping route ledger.""" + +from __future__ import annotations + +import argparse +from pathlib import Path +import sys + + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from modiff.image_prototyping_readiness import ( # noqa: E402 + IMAGE_PROTOTYPING_READINESS_PATH, + build_image_prototyping_readiness, + render_image_prototyping_readiness, +) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true", help="Fail unless the checked-in ledger is current.") + parser.add_argument("--output", type=Path, default=IMAGE_PROTOTYPING_READINESS_PATH) + args = parser.parse_args() + rendered = render_image_prototyping_readiness(build_image_prototyping_readiness(ROOT)) + if args.check: + try: + current = args.output.read_text(encoding="utf-8") + except OSError: + current = "" + if current != rendered: + raise SystemExit(f"{args.output} is stale; regenerate it with {Path(__file__).name}.") + return 0 + args.output.write_text(rendered, encoding="utf-8", newline="\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/generate_operation_inventory.py b/scripts/generate_operation_inventory.py new file mode 100644 index 00000000..4a526132 --- /dev/null +++ b/scripts/generate_operation_inventory.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""Rebuild the pinned Diffusers operation inventory without model downloads.""" + +import argparse +import json +from pathlib import Path +import sys + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from modiff.operation_inventory import OPERATION_INVENTORY_PATH, build_operation_inventory # noqa: E402 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + rendered = json.dumps(build_operation_inventory(ROOT), indent=2, sort_keys=True, ensure_ascii=False) + "\n" + if args.check: + if OPERATION_INVENTORY_PATH.read_text() != rendered: + parser.error("The pinned operation inventory is stale; regenerate it.") + else: + OPERATION_INVENTORY_PATH.write_text(rendered) + + +if __name__ == "__main__": + main() diff --git a/scripts/run_node_ux_backend.py b/scripts/run_node_ux_backend.py new file mode 100644 index 00000000..8b93d408 --- /dev/null +++ b/scripts/run_node_ux_backend.py @@ -0,0 +1,84 @@ +"""Isolated real backend for custom-node browser acceptance (no mocked registry). + +The explicit test root owns custom sources, state and outputs. Existing application +custom sources are never loaded. Optional HF Git credentials stay in the child +environment and are never written to configuration or logs. +""" +import argparse +import asyncio +import os +from pathlib import Path +import signal +import sys + +PROJECT = Path(__file__).resolve().parents[1] + + +def install_stop_handlers(loop, stopped): + for number in (signal.SIGINT, signal.SIGTERM): + try: + loop.add_signal_handler(number, stopped.set) + except NotImplementedError: + # Windows loops do not implement Unix signal callbacks. asyncio.run + # still cancels the main task on Ctrl+C, reaching server cleanup. + pass + + +async def serve(args): + sys.path.insert(0, str(PROJECT)) + from modiff.backend_source_identity import capture_process_backend_source_identity + capture_process_backend_source_identity() + from modiff.config import CONFIG + # Match the real worker before importing any Hugging Face consumer. + if CONFIG.hf.get("cache_dir"): + os.environ["HF_HUB_CACHE"] = str(CONFIG.hf["cache_dir"]) + from modiff.custom_extensions import ExtensionStore + original_init = ExtensionStore.__init__ + root = Path(args.root).resolve() + root.mkdir(parents=True, exist_ok=True) + custom = root / "custom" + custom.mkdir(exist_ok=True) + (root / "data").mkdir(exist_ok=True) + # Bind the normal lifecycle to an isolated disk root before module discovery. + def isolated_init(self, path=None): + original_init(self, path or custom) + ExtensionStore.__init__ = isolated_init + CONFIG.server.update(host="127.0.0.1", port=args.port, secure=False, cors=False) + CONFIG.paths["work_dir"] = str(PROJECT) + CONFIG.paths["data"] = str(root / "data") + for name in ("images", "videos", "audio", "models", "temp"): + CONFIG.paths[name] = str(root / "data" / name) + CONFIG.paths["upscalers"] = str(root / "data" / "models" / "upscalers") + if args.hf_git: + os.environ["GIT_CONFIG_COUNT"] = "1" + os.environ["GIT_CONFIG_KEY_0"] = "credential.helper" + os.environ["GIT_CONFIG_VALUE_0"] = f'!"{sys.executable}" "{Path(__file__).resolve()}" --credential-helper' + from modiff.optimization_packages import activate_runtime_overlay + activate_runtime_overlay() + # NodeBase publishes dynamic fields and previews through this singleton. + # Serving a second WebServer loses those messages on the unstarted instance. + from modiff.server import server + stopped = asyncio.Event() + install_stop_handlers(asyncio.get_running_loop(), stopped) + await server.run() + print(f"Node UX test backend ready on {args.port}; root={root}", flush=True) + try: + await stopped.wait() + finally: + await server.cleanup() + + +if __name__ == "__main__": + if "--credential-helper" in sys.argv: + fields = dict(line.rstrip("\n").split("=", 1) for line in sys.stdin if "=" in line) + if sys.argv[-1] == "get" and fields.get("host") == "huggingface.co" and fields.get("protocol") == "https": + from huggingface_hub.utils import get_token + token = get_token() + if token: + print("username=hf_user\npassword=" + token) + else: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", required=True) + parser.add_argument("--port", type=int, default=8093) + parser.add_argument("--hf-git", action="store_true") + asyncio.run(serve(parser.parse_args())) diff --git a/scripts/smoke_service_package.py b/scripts/smoke_service_package.py new file mode 100644 index 00000000..8845ab7f --- /dev/null +++ b/scripts/smoke_service_package.py @@ -0,0 +1,120 @@ +"""No-model HTTP smoke in temporary storage; suitable for clean CPU CI installs.""" + +import asyncio +import json +import os +from pathlib import Path +import sys +import tempfile + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) + + +async def smoke(): + # Redirect every path before importing the singleton/queue reconciliation. + with tempfile.TemporaryDirectory(prefix="modiff-service-smoke-") as temporary: + from modiff.config import CONFIG + + for key in CONFIG.paths: + if key != "app_root": + directory = Path(temporary) / key + directory.mkdir(parents=True, exist_ok=True) + CONFIG.paths[key] = str(directory) + CONFIG.server["host"] = "127.0.0.1" + CONFIG.server["port"] = 0 + os.environ["HF_HUB_OFFLINE"] = "1" + from modiff.server import server + from modiff.service import request, run + + from modiff.custom_extensions import ExtensionStore + + store = ExtensionStore(Path(temporary) / "custom") + server._extension_store = lambda: store + await server.run() + origin = f"http://127.0.0.1:{server.site._server.sockets[0].getsockname()[1]}" + + async def http(path, body=None): + return await asyncio.to_thread(request, origin, path, body) + + try: + health = await http("/health") + assert health.get("error") is not True + graph = json.loads((ROOT / "examples/service/text-api.json").read_text()) + interface = json.loads((ROOT / "examples/service/interface.json").read_text()) + values = json.loads((ROOT / "examples/service/inputs.json").read_text()) + direct = await http("/graph", graph) + for _ in range(240): + receipt = await http(f"/runs/{direct['task_id']}") + if receipt["task"].get("status") in {"completed", "failed", "cancelled"}: + break + await asyncio.sleep(0.25) + assert receipt["task"]["status"] == "completed", receipt["task"] + candidates = await http("/service_package", {"operation": "inspect", "graph": graph}) + assert any(item["nodeId"] == "prompt" for item in candidates["inputs"]) + package = (await http("/service_package", {"operation": "build", "graph": graph, "interface": interface}))[ + "package" + ] + from modiff.service_package import service_outputs + + baseline = service_outputs(package, receipt, direct["task_id"])["outputs"] + for mode in ("manual", "auto"): + graph["runtimeHints"] = {"resourceMode": mode} + package = ( + await http("/service_package", {"operation": "build", "graph": graph, "interface": interface}) + )["package"] + result = await asyncio.to_thread(run, origin, package, values, timeout=60) + assert result["outputs"]["text"][0]["value"] == baseline["text"][0]["value"] + staged = await http( + "/custom_modules/install", + { + "kind": "local", + "source": str(ROOT / "examples/custom_nodes/PromptTools"), + "name": "ServiceExample", + }, + ) + await http( + "/custom_modules/ServiceExample/enable", {"codeHash": staged["module"]["codeHash"], "consent": True} + ) + graph["nodes"]["prefix"] = { + "module": "custom.ServiceExample", + "action": "PromptPrefix", + "params": {"text": {"sourceId": "prompt", "sourceKey": "output"}, "prefix": {"value": "Service:"}}, + } + graph["nodes"]["preview"]["params"]["value"] = {"sourceId": "prefix", "sourceKey": "result"} + graph["paths"] = [["prompt", "prefix", "preview"]] + graph["runtimeHints"] = {"resourceMode": "manual"} + package = (await http("/service_package", {"operation": "build", "graph": graph, "interface": interface}))[ + "package" + ] + assert package["requirements"]["customNodes"][0]["codeHash"] == staged["module"]["codeHash"] + result = await asyncio.to_thread(run, origin, package, values, timeout=60) + assert result["outputs"]["text"][0]["value"] == ["Service: " + values["prompt"]] + source = store.path("ServiceExample") / "main.py" + source.write_text(source.read_text() + "\n# source changed\n") + try: + await asyncio.to_thread(run, origin, package, values, timeout=60) + except ValueError as error: + assert "changed" in str(error) + else: + raise AssertionError("Changed custom code was accepted.") + print( + json.dumps( + { + "health": "passed", + "savedGraph": "passed", + "serviceManual": "passed", + "serviceAuto": "passed", + "customServiceAndDrift": "passed", + "modelDownloads": 0, + } + ) + ) + finally: + queue = await http("/queue") + assert queue["current"] is None and not queue["queued"], "Owned smoke queue did not drain." + await server.cleanup() + + +if __name__ == "__main__": + asyncio.run(smoke()) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..bc491b10 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,22 @@ +"""Keep test registry discovery away from the operator's installed extensions.""" + +from tempfile import TemporaryDirectory + +import pytest + + +def pytest_configure(config): + # This runs before collection: several test modules import the node registry + # at module scope, before any autouse fixture could isolate discovery. + from modiff.custom_extensions import ExtensionStore + + directory = TemporaryDirectory(prefix="modiff-test-extensions-") + original_init = ExtensionStore.__init__ + + def isolated_init(self, root=None): + original_init(self, root if root is not None else directory.name) + + patch = pytest.MonkeyPatch() + patch.setattr(ExtensionStore, "__init__", isolated_init) + config.add_cleanup(directory.cleanup) + config.add_cleanup(patch.undo) diff --git a/tests/fixtures/block_removed_controls_v1.json b/tests/fixtures/block_removed_controls_v1.json new file mode 100644 index 00000000..49914b11 --- /dev/null +++ b/tests/fixtures/block_removed_controls_v1.json @@ -0,0 +1,17 @@ +{ + "valid": [ + [], + [{ "nodeId": "guider", "fieldId": "skip_layer_guidance_scale" }], + [{ "nodeId": "temporarily-absent", "fieldId": "blocks.0.scale" }] + ], + "invalid": [ + null, + {}, + [null], + [{ "nodeId": "guider" }], + [{ "nodeId": "guider", "fieldId": "" }], + [{ "nodeId": "bad node", "fieldId": "scale" }], + [{ "nodeId": "guider", "fieldId": "scale", "enabled": false }], + [{ "nodeId": "guider", "fieldId": "scale" }, { "nodeId": "guider", "fieldId": "scale" }] + ] +} diff --git a/tests/test_anima_stage_geometry.py b/tests/test_anima_stage_geometry.py new file mode 100644 index 00000000..254e618b --- /dev/null +++ b/tests/test_anima_stage_geometry.py @@ -0,0 +1,92 @@ +"""Exercise the real pinned stage constructor without weights or inference.""" +from types import SimpleNamespace +import importlib.util + +import pytest +import torch +from diffusers import AnimaModularPipeline + +from modules.ModularDiffusers.workflow_blocks import ( + WorkflowImageDenoise, + WorkflowImageEncode, + _anima_image_dimensions, + _official_stage_block, + _official_stage_component_dependencies, +) + +requires_transformers = pytest.mark.skipif( + importlib.util.find_spec("transformers") is None, + reason="requires the staged optional Transformers runtime", +) + + +@pytest.mark.parametrize("workflow,stage", [("text2image", "denoise"), ("img2img", "denoise"), ("img2img", "vae_encoder")]) +@requires_transformers +def test_anima_stage_retains_cross_stage_geometry_specs(workflow, stage): + definition = AnimaModularPipeline() + block = _official_stage_block(definition.blocks.get_workflow(workflow), stage) + original_names = {spec.name for spec in block.expected_components} + dependency = "vae" if stage == "denoise" else "transformer" + wrapped = _official_stage_component_dependencies( + block, pipeline_class="AnimaModularPipeline", block_path=stage, definition=definition, + ) + assert wrapped.sub_blocks[stage] is block + assert {spec.name for spec in wrapped.expected_components} == original_names | {dependency} + assert wrapped.inputs == block.inputs + pipeline = wrapped.init_pipeline() + assert {"vae", "transformer"} <= set(pipeline.pretrained_component_names) + # Real upstream properties must follow bound component geometry, not copied + # constants. These metadata-only sentinels deliberately differ from defaults. + pipeline.vae = SimpleNamespace(temperal_downsample=(True, True)) + pipeline.transformer = SimpleNamespace(config=SimpleNamespace(in_channels=32)) + assert pipeline.vae_scale_factor == 4 + assert pipeline.num_channels_latents == 32 + # Wrapping must not mutate the global official definition or the stage. + assert {spec.name for spec in block.expected_components} == original_names + + +@requires_transformers +def test_unrelated_stages_are_not_wrapped_and_missing_specs_fail_closed(): + definition = AnimaModularPipeline() + block = _official_stage_block(definition.blocks.get_workflow("text2image"), "text_encoder") + assert _official_stage_component_dependencies( + block, pipeline_class="AnimaModularPipeline", block_path="text_encoder", definition=definition, + ) is block + with pytest.raises(ValueError, match="geometry component"): + _official_stage_component_dependencies( + block, pipeline_class="AnimaModularPipeline", block_path="denoise", + definition=SimpleNamespace(blocks=SimpleNamespace(expected_components=[])), + ) + + +def test_anima_controls_and_validation_use_the_upstream_patch_grid(): + for cls in (WorkflowImageDenoise, WorkflowImageEncode): + for name in ("width", "height"): + assert cls.params[name]["step"] == 16 + for width, height in ((512, 512), (528, 512), (1536, 1024)): + _anima_image_dimensions(width, height) + for value in (520, 504, 1544, True, 512.0): + with pytest.raises(ValueError, match="multiple of 16"): + _anima_image_dimensions(value, 512) + with pytest.raises(ValueError, match="multiple of 16"): + _anima_image_dimensions(512, value) + + +@requires_transformers +def test_real_anima_latent_preparation_uses_bound_vae_geometry(): + from diffusers.modular_pipelines.anima.before_denoise import AnimaPrepareLatentsStep + + definition = AnimaModularPipeline() + wrapped = _official_stage_component_dependencies( + AnimaPrepareLatentsStep(), pipeline_class="AnimaModularPipeline", + block_path="denoise", definition=definition, + ) + pipeline = wrapped.init_pipeline() + pipeline.vae = SimpleNamespace(temperal_downsample=(True, True, True)) + pipeline.transformer = SimpleNamespace(config=SimpleNamespace(in_channels=16)) + state = pipeline( + width=528, height=512, batch_size=1, num_images_per_prompt=1, + dtype=torch.float32, generator=torch.Generator(device="cpu").manual_seed(42), + ) + assert state.get("latents").shape == (1, 16, 1, 64, 66) + assert state.get("padding_mask").shape == (1, 1, 512, 528) diff --git a/tests/test_audio_envelope_example.py b/tests/test_audio_envelope_example.py new file mode 100644 index 00000000..17706b11 --- /dev/null +++ b/tests/test_audio_envelope_example.py @@ -0,0 +1,27 @@ +import importlib.util +from pathlib import Path + +import numpy as np +import pytest + + +def test_audio_envelope_example_preserves_source_and_declares_exact_layout(): + path = Path(__file__).resolve().parents[1] / "examples/custom_nodes/AudioEnvelope.py" + spec = importlib.util.spec_from_file_location("audio_envelope_example", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + source = np.full((48000, 2), 0.5, dtype=np.float32) + audio = {"samples": source, "sample_rate": 48000, "sample_layout": "frames_first", "channels": 2} + execute = module.AudioEnvelope.execute + result = execute(None, audio, 0.2, 0.5, -6, 0.1)["output"] + assert result["samples"].shape == (24000, 2) + assert result["sample_layout"] == "frames_first" + assert result["sample_rate"] == 48000 + assert result["duration_seconds"] == 0.5 + assert np.max(result["samples"]) == pytest.approx(0.5 * 10 ** (-6 / 20)) + assert np.all(result["samples"][[0, -1]] == 0) + assert np.all(source == 0.5) + with pytest.raises(ValueError, match="beyond"): + execute(None, audio, 2, 0.5, 0, 0) + with pytest.raises(ValueError, match="gain_db"): + execute(None, audio, 0, 0.5, float("nan"), 0) diff --git a/tests/test_audio_input_provenance.py b/tests/test_audio_input_provenance.py new file mode 100644 index 00000000..e474094f --- /dev/null +++ b/tests/test_audio_input_provenance.py @@ -0,0 +1,61 @@ +"""Audio receipts must describe the exact active adapter controls.""" + +from types import SimpleNamespace + +import numpy as np +import pytest + +from modiff.execution_input_provenance import bind_generation_input_origins +from modules.DiffusersAudio.main import Generate + + +@pytest.mark.parametrize("pipeline_class,steps,guidance", [ + ("AudioLDM2Pipeline", 200, 3.5), + ("LongCatAudioDiTPipeline", 16, 4), + ("StableAudioPipeline", 100, 7), + ("AceStepPipeline", 8, 1), +]) +def test_audio_call_receipt_uses_consumed_controls_and_connected_origins(pipeline_class, steps, guidance): + node = Generate("audio-receipt-test") + ace = pipeline_class == "AceStepPipeline" + + class Pipeline: + _modiff_audio_pipeline_class = pipeline_class + _modiff_audio_mode = "text_to_audio" + device = "cpu" + sample_rate = 16000 + vocoder = SimpleNamespace(config={"sampling_rate": 16000}) + vae = SimpleNamespace(config={"sampling_rate": 16000}) + + def __call__(self, **kwargs): + self.call_kwargs = kwargs + # Inspect at the actual consumer boundary, before a result exists. + record = node._execution_input_record + assert record["fields"]["num_inference_steps"]["value"] == kwargs["num_inference_steps"] == steps + assert record["fields"]["guidance_scale"]["value"] == kwargs["guidance_scale"] == guidance + assert record["fields"]["seed"]["value"] == kwargs["generator"].initial_seed() == 91 + assert record["fields"]["audio_duration"]["value"] == 1 + assert record["fields"]["sample_rate"]["value"] == 16000 + assert record["fields"]["prompt"]["value"] == "Rain" + assert "pipeline" not in record["fields"] + return SimpleNamespace(audios=np.zeros((1, 16000), dtype=np.float32)) + + pipeline = Pipeline() + node.execute( + pipeline=pipeline, prompt="Rain", audio_duration=1, sample_rate=16000, seed=91, + num_inference_steps=8, guidance_scale=1, + stable_audio_steps=200 if ace else steps, + stable_audio_guidance=3.5 if ace else guidance, + ) + step_source = "num_inference_steps" if ace else "stable_audio_steps" + guidance_source = "guidance_scale" if ace else "stable_audio_guidance" + graph_node = {"module": "modules.DiffusersAudio", "action": "Generate", "params": { + step_source: {"sourceId": "steps", "sourceKey": "value"}, + guidance_source: {"sourceId": "guidance", "sourceKey": "value"}, + }} + bound = bind_generation_input_origins( + "generate", graph_node, node._execution_input_record, + source_fields=node._execution_input_source_fields, + ) + assert bound["fields"]["num_inference_steps"]["sourceNodeId"] == "steps" + assert bound["fields"]["guidance_scale"]["sourceNodeId"] == "guidance" diff --git a/tests/test_audio_offload_entrypoints.py b/tests/test_audio_offload_entrypoints.py new file mode 100644 index 00000000..d49b7b66 --- /dev/null +++ b/tests/test_audio_offload_entrypoints.py @@ -0,0 +1,80 @@ +"""Real upstream hooks must cover direct embeddings and tensor reads (no weights downloaded).""" +from types import SimpleNamespace + +import pytest +import torch + +from modiff.diffusers_offload import apply_pipeline_offload + + +class TextEncoder(torch.nn.Module): + def __init__(self): + super().__init__() + self.embedding = torch.nn.Embedding(8, 4) + self.layers = torch.nn.ModuleList([torch.nn.Linear(4, 4)]) + + def forward(self, ids): + return self.layers[0](self.embedding(ids)) + + def get_input_embeddings(self): + return self.embedding + + +@pytest.mark.skipif(not torch.cuda.is_available(), reason="requires CUDA for actual transfer hooks") +@pytest.mark.parametrize("mode", ["group_cpu", "group_disk"]) +def test_direct_embedding_and_condition_tensors_survive_repeated_offload(mode, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + encoder = TextEncoder() + expected = encoder.embedding.weight.detach().clone() + condition = torch.nn.Linear(4, 4) + condition.register_buffer("silence_latent", torch.ones(1, 3, 4)) + components = {"text_encoder": encoder, "condition_encoder": condition} + pipeline = SimpleNamespace(**components, components=components) + result = apply_pipeline_offload( + pipeline, mode=mode, device="cuda:0", node_id="audio", + component_names=tuple(components), prefer_pipeline_group=False, + leaf_level_components=("text_encoder",), resident_components=("condition_encoder",), + ) + assert result.components == ["text_encoder"] + ids = torch.tensor([[1, 2]], device="cuda:0") + for _ in range(2): + encoder(ids) + lyrics = encoder.get_input_embeddings()(ids) + torch.testing.assert_close(lyrics.cpu(), expected[torch.tensor([[1, 2]])]) + assert condition.silence_latent.device.type == "cuda" + assert torch.all(condition.silence_latent == 1) + + +def test_sequential_preserves_existing_exclusions_without_changing_the_class(): + class Pipeline: + _exclude_from_cpu_offload = ["existing"] + + def enable_sequential_cpu_offload(self, **kwargs): + assert self._exclude_from_cpu_offload == ["existing", "condition_encoder"] + + pipeline = Pipeline() + apply_pipeline_offload(pipeline, mode="sequential_cpu", device="cuda:0", node_id="audio", + resident_components=("condition_encoder",)) + assert Pipeline._exclude_from_cpu_offload == ["existing"] + + +@pytest.mark.skipif(not torch.cuda.is_available(), reason="requires CUDA for actual transfer hooks") +@pytest.mark.parametrize("mode", ["group_cpu", "group_disk"]) +def test_resident_weight_normalized_audio_vae_preserves_direct_encode(mode, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + # Oobleck uses legacy weight_norm forward pre-hooks. They execute before + # Diffusers' wrapped leaf forward, so leaf offload rebuilds weight on CPU. + vae = torch.nn.Sequential(torch.nn.utils.weight_norm(torch.nn.Conv1d(2, 2, 3))) + signal = torch.randn(1, 2, 12) + expected = vae(signal).detach() + encoder = TextEncoder() + components = {"text_encoder": encoder, "vae": vae} + pipeline = SimpleNamespace(**components, components=components) + result = apply_pipeline_offload( + pipeline, mode=mode, device="cuda:0", node_id="audio-vae", + component_names=tuple(components), prefer_pipeline_group=False, + leaf_level_components=("text_encoder",), resident_components=("vae",), + ) + assert result.components == ["text_encoder"] + for _ in range(2): + torch.testing.assert_close(vae(signal.cuda()).cpu(), expected) diff --git a/tests/test_authoring_examples.py b/tests/test_authoring_examples.py new file mode 100644 index 00000000..4be7d031 --- /dev/null +++ b/tests/test_authoring_examples.py @@ -0,0 +1,53 @@ +from modiff.authoring_examples import seed_operation_example + + +def draft(task="text_to_image", pipeline="FuturePipeline", **field): + return { + "operation": {"task": task, "pipelineClass": pipeline}, + "params": {"prompt": {"type": "text", "default": "", **field}}, + "values": {}, + } + + +def test_new_text_and_edit_operations_have_different_meaningful_examples(): + text, edit = draft(), draft("edit_image") + seed_operation_example(text) + seed_operation_example(edit) + assert text["values"]["prompt"] + assert edit["values"]["prompt"] != text["values"]["prompt"] + assert text["params"]["prompt"]["fieldOptions"]["exampleAttribution"] == "MoDiff task example" + + +def test_qwen21_example_retains_creator_source_without_changing_numeric_defaults(): + node = draft(pipeline="QwenImage21Pipeline") + node["params"]["num_inference_steps"] = {"value": 40} + seed_operation_example(node) + assert node["params"]["prompt"]["fieldOptions"]["exampleSource"] == "https://github.com/QwenLM/Qwen-Image-2.1" + assert node["params"]["num_inference_steps"]["value"] == 40 + + +def test_existing_hidden_output_and_unrelated_prompts_are_preserved(): + for fields in ({"value": "Authored prompt"}, {"hidden": True}, {"display": "output"}): + node = draft(**fields) + seed_operation_example(node) + assert not node["values"] + node = draft("text_generation") + seed_operation_example(node) + assert not node["values"] + + +def test_profile_examples_replace_only_generated_defaults_and_keep_accurate_attribution(): + node = draft() + seed_operation_example(node) + generic = node["values"]["prompt"] + seed_operation_example(node, "black-forest-labs/FLUX.1-schnell") + assert node["values"]["prompt"] != generic + assert node["params"]["prompt"]["fieldOptions"]["exampleSource"].endswith("FLUX.1-schnell") + seed_operation_example(node) + assert "cat" in node["values"]["prompt"] + seed_operation_example(node, "example/other-model") + assert node["values"]["prompt"] == generic + assert "exampleSource" not in node["params"]["prompt"]["fieldOptions"] + reference = draft("multi_image_reference_edit", "QwenImage21Pipeline") + seed_operation_example(reference) + assert reference["params"]["prompt"]["fieldOptions"]["exampleAttribution"] == "MoDiff task example" diff --git a/tests/test_auto_resource.py b/tests/test_auto_resource.py index 806b6b73..1239fcd7 100644 --- a/tests/test_auto_resource.py +++ b/tests/test_auto_resource.py @@ -1936,7 +1936,8 @@ def test_every_effective_auto_specification_has_one_canonical_target(self): profiles = [ profile for profile in DIFFUSERS_EXECUTION_PROFILES.values() - if profile.model_type == model_type and mode in profile.modes + if not profile.operation_recipe + and profile.model_type == model_type and mode in profile.modes ] self.assertEqual(len(profiles), 1) profile = profiles[0] diff --git a/tests/test_block_route_selection_v1.py b/tests/test_block_route_selection_v1.py index 957a2b95..49dc9641 100644 --- a/tests/test_block_route_selection_v1.py +++ b/tests/test_block_route_selection_v1.py @@ -46,6 +46,19 @@ def route_selection_fixture(): class BlockRouteSelectionV1Tests(unittest.TestCase): + def test_definition_switch_preserves_user_draft_only_in_explicit_switch_contract(self): + instance = route_selection_fixture() + draft = instance['routeSelection']['inactiveDrafts']['qwen-archived'] + definition = draft['definitionSnapshot'] + definition['source'] = {'kind': 'user'} + definition['ownership'] = {'kind': 'user', 'definitionMutable': True} + definition['contentHash'] = block_definition_content_hash_v2(definition) + draft['definitionRef']['contentHash'] = definition['contentHash'] + with self.assertRaisesRegex(ValueError, 'immutable registered'): + validate_block_instance_v2(instance) + instance['routeSelection']['routeSetId'] = 'diffusers.definition-switch:v1' + self.assertEqual(validate_block_instance_v2(instance), instance) + def test_instance_type_declares_optional_route_selection(self): self.assertEqual( get_type_hints(BlockInstanceV2, include_extras=True)["routeSelection"], diff --git a/tests/test_chroma_ltx2_exact_closure.py b/tests/test_chroma_ltx2_exact_closure.py index 461d09fc..84d1e271 100644 --- a/tests/test_chroma_ltx2_exact_closure.py +++ b/tests/test_chroma_ltx2_exact_closure.py @@ -40,8 +40,8 @@ ) -CHROMA_IMG2IMG_SOURCE_SHA256 = "4dfa9751d317efb5cbae8faadc4a77b53312a98a9920675afb44bab01aae0a09" -CHROMA_INPAINT_SOURCE_SHA256 = "b149fa04c9ae0b4165d761aa380b484e7a8d57f62589c7139fd41040f41e78ae" +CHROMA_IMG2IMG_SOURCE_SHA256 = "bbc16648f48afd78a96ad182ecbeece21ac2e140fa114394921abfb9d88c514e" +CHROMA_INPAINT_SOURCE_SHA256 = "3c94ca463088fa648711adc5110f3babf31194ea94ae9c24d61bf13c0d667112" LTX2_SOURCE_SHA256 = "39aa535e809492eaa49a5e8161c05d82674c54b8ab14e77a8899c848a37562e8" CHROMA_REVISION = "0e0c60ece1e82b17cb7f77342d765ba5024c40c0" LTX2_REPO = "Lightricks/LTX-2" @@ -237,7 +237,7 @@ class ChromaLTX2ExactClosureTests(unittest.TestCase): def test_exact_pinned_sources_and_call_surfaces_are_preserved(self): import diffusers - self.assertEqual(PINNED_DIFFUSERS_REVISION, "2f7e0154a9db246e95c9ede43edba7db5b130805") + self.assertEqual(PINNED_DIFFUSERS_REVISION, "fbf49e7f35857f76bc57b177e26f12b03687c668") root = Path(diffusers.__file__).resolve().parent / "pipelines" cases = ( ( @@ -341,7 +341,7 @@ def test_exact_adapters_reuse_only_the_admitted_artifacts(self): ) def test_exact_hidden_candidate_specs_reuse_artifacts_and_generic_contracts(self): - self.assertEqual(len(validate_studio_execution_specs(module_registry.MODULE_MAP)), 273) + self.assertEqual(len(validate_studio_execution_specs(module_registry.MODULE_MAP)), 278) capabilities = studio_capability_definitions() cases = { ("ChromaImg2ImgPipeline", "edit_image"): ( diff --git a/tests/test_custom_extension_source.py b/tests/test_custom_extension_source.py new file mode 100644 index 00000000..be402cb0 --- /dev/null +++ b/tests/test_custom_extension_source.py @@ -0,0 +1,103 @@ +"""Hub source resolution is metadata inspection, not staging or approval.""" + +from types import SimpleNamespace + +import pytest + + +def test_hub_url_resolves_without_staging_or_import(monkeypatch): + from modiff.custom_extension_source import resolve_hub_extension + + calls = [] + + def info(_self, repo_id, **kwargs): + calls.append((repo_id, kwargs)) + return SimpleNamespace(sha="a" * 40) + + monkeypatch.setattr("huggingface_hub.HfApi.model_info", info) + monkeypatch.setattr("huggingface_hub.snapshot_download", lambda *a, **k: pytest.fail("No source download")) + result = resolve_hub_extension("https://huggingface.co/example/block/tree/reviewed") + assert result == {"kind": "hub", "source": "example/block", "requestedRevision": "reviewed", "revision": "a" * 40} + assert calls == [("example/block", {"revision": "reviewed", "timeout": 20})] + + +@pytest.mark.parametrize( + "source,revision", + [ + ("https://huggingface.co.evil.test/owner/repo", None), + ("https://user:secret@huggingface.co/owner/repo", None), + ("https://huggingface.co/owner/repo?token=secret", None), + ("http://huggingface.co/owner/repo", None), + ("https://huggingface.co:443/owner/repo", None), + ("https://huggingface.co/datasets/owner/repo", None), + ("https://huggingface.co/owner/repo/blob/main/block.py", None), + ("https://huggingface.co/owner/repo/tree/main/extra", None), + ("https://huggingface.co/owner/repo/tree/main", "different"), + ("../owner/repo", None), + ("owner/repo", "bad\nrevision"), + ("owner/repo", 42), + ], +) +def test_invalid_sources_fail_before_network(monkeypatch, source, revision): + from modiff.custom_extension_source import resolve_hub_extension + + monkeypatch.setattr("huggingface_hub.HfApi.model_info", lambda *a, **k: pytest.fail("No network")) + with pytest.raises(ValueError): + resolve_hub_extension(source, revision) + + +def test_branches_are_pinned_and_exact_requests_cannot_be_retargeted(monkeypatch): + from modiff.custom_extension_source import resolve_hub_extension + + monkeypatch.setattr("huggingface_hub.HfApi.model_info", lambda *a, **k: SimpleNamespace(sha="b" * 40)) + assert resolve_hub_extension("owner/repo")["requestedRevision"] == "main" + assert resolve_hub_extension("owner/repo", "release")["revision"] == "b" * 40 + with pytest.raises(ValueError, match="different revision"): + resolve_hub_extension("owner/repo", "a" * 40) + monkeypatch.setattr("huggingface_hub.HfApi.model_info", lambda *a, **k: SimpleNamespace(sha="main")) + with pytest.raises(ValueError, match="40-character"): + resolve_hub_extension("owner/repo") + + +def test_resolution_errors_do_not_expose_remote_details(monkeypatch): + from modiff.custom_extension_source import resolve_hub_extension + + def fail(*args, **kwargs): + raise RuntimeError("https://user:secret@example.test/private") + + monkeypatch.setattr("huggingface_hub.HfApi.model_info", fail) + with pytest.raises(ValueError, match="Check the repository") as error: + resolve_hub_extension("owner/repo") + assert "secret" not in str(error.value) + + +@pytest.mark.parametrize("ref,rows,expected", [ + (None, [("a", "HEAD")], "a"), + ("main", [("b", "refs/heads/main")], "b"), + ("release", [("c", "refs/tags/release"), ("d", "refs/tags/release^{}")], "d"), + ("release", [("a", "refs/heads/release"), ("c", "refs/tags/release"), ("d", "refs/tags/release^{}")], None), +]) +def test_git_resolution_is_pinned_bounded_and_rejects_ambiguity(monkeypatch, ref, rows, expected): + from modiff.custom_extension_source import resolve_git_extension + def run(args, **kwargs): + assert args[:4] == ["git", "-c", "credential.interactive=false", "ls-remote"] + assert kwargs["timeout"] == 30 + assert kwargs["env"]["GIT_TERMINAL_PROMPT"] == "0" + return SimpleNamespace(returncode=0, stdout="".join(f"{sha * 40}\t{name}\n" for sha, name in rows).encode()) + monkeypatch.setattr("subprocess.run", run) + if expected: + assert resolve_git_extension("https://example.com/nodes.git", ref)["revision"] == expected * 40 + else: + with pytest.raises(ValueError, match="ambiguous"): + resolve_git_extension("https://example.com/nodes.git", ref) + + +@pytest.mark.parametrize("source,revision", [ + ("file:///tmp/nodes", None), ("https://user:pass@example.com/nodes", None), + ("https://example.com/nodes?token=secret", None), ("https://example.com/nodes", "--upload-pack=evil"), +]) +def test_git_invalid_source_does_not_launch_git(monkeypatch, source, revision): + from modiff.custom_extension_source import resolve_git_extension + monkeypatch.setattr("subprocess.run", lambda *a, **k: pytest.fail("No Git invocation")) + with pytest.raises(ValueError): + resolve_git_extension(source, revision) diff --git a/tests/test_custom_extensions.py b/tests/test_custom_extensions.py new file mode 100644 index 00000000..f4ff8925 --- /dev/null +++ b/tests/test_custom_extensions.py @@ -0,0 +1,1109 @@ +"""Extension discovery must not import code or silently grant execution authority.""" + +import json +import sys +from pathlib import Path + +import pytest + +from modiff.custom_extensions import ExtensionStore, ExtensionError + + +def source_node(root, *, expression="text.upper()"): + root.mkdir(parents=True, exist_ok=True) + (root / "__init__.py").write_text("from .main import Echo\n") + (root / "main.py").write_text( + "from modiff.NodeBase import NodeBase\n" + "class Echo(NodeBase):\n" + ' label = "Custom Echo"\n' + ' category = "Text"\n' + ' params = {"text": {"type": "string", "default": "hello"}, ' + '"out": {"type": "string", "display": "output"}}\n' + f' def execute(self, text): return {{"out": {expression}}}\n' + ) + return root + + +@pytest.fixture +def store(tmp_path): + store = ExtensionStore(tmp_path / "custom") + yield store + store.unload("Example") + + +def test_staging_discovery_and_declining_review_never_import(store, tmp_path): + source = source_node(tmp_path / "source") + marker = tmp_path / "executed" + with (source / "__init__.py").open("a") as f: + f.write(f'open({str(marker)!r}, "w").write("executed")\n') + item = store.stage(kind="local", source=str(source), name="Example") + assert item["status"] == "disabled" + assert item["preview"]["nodes"]["Echo"]["params"]["out"]["type"] == "string" + assert store.list()[0]["codeHash"] == item["codeHash"] + assert not marker.exists() + with pytest.raises(ExtensionError, match="consent"): + store.enable("Example", code_hash=item["codeHash"], consent=False) + assert not marker.exists() + + +def test_exact_code_enable_reload_and_change_rejection(store, tmp_path, monkeypatch): + item = store.stage(kind="local", source=str(source_node(tmp_path / "source")), name="Example") + registry = store.enable("Example", code_hash=item["codeHash"], consent=True) + from modules import MODULE_MAP + + monkeypatch.setitem(MODULE_MAP, "custom.Example", registry) + assert registry["Echo"]["params"]["out"]["type"] == "string" + action = sys.modules["custom.Example.main"].Echo + assert action("test").execute("Hello") == {"out": "HELLO"} + path = Path(store.inspect("Example")["path"]) / "main.py" + path.write_text(path.read_text().replace("text.upper()", "text.lower()")) + with pytest.raises(ExtensionError, match="changed"): + store.require_enabled("Example") + with pytest.raises(ExtensionError, match="changed"): + store.enable("Example", code_hash=item["codeHash"], consent=True) + changed = store.inspect("Example") + store.enable("Example", code_hash=changed["codeHash"], consent=True) + assert sys.modules["custom.Example.main"].Echo("test").execute("Hello") == {"out": "hello"} + assert sys.modules["custom.Example.main"].Echo is not action + + +def test_path_escape_and_missing_dependencies_do_not_execute(store, tmp_path): + source = source_node(tmp_path / "source") + (source / "escape.py").symlink_to(tmp_path / "private.py") + with pytest.raises(ExtensionError, match="link"): + store.stage(kind="local", source=str(source), name="Example") + (source / "escape.py").unlink() + (source / "requirements.txt").write_text("modiff-nonexistent-test-package==1.0\n") + item = store.stage(kind="local", source=str(source), name="Example") + assert item["dependencies"][0]["status"] == "missing" + with pytest.raises(ExtensionError, match="dependencies"): + store.enable("Example", code_hash=item["codeHash"], consent=True) + assert "custom.Example.main" not in sys.modules + + +def test_import_failure_is_recorded_and_does_not_enable(store, tmp_path): + source = source_node(tmp_path / "source") + (source / "__init__.py").write_text('raise RuntimeError("broken import")\n') + item = store.stage(kind="local", source=str(source), name="Example") + with pytest.raises(ExtensionError, match="broken import"): + store.enable("Example", code_hash=item["codeHash"], consent=True) + failed = store.inspect("Example") + assert not failed["enabled"] + assert "broken import" in failed["diagnostic"] + assert "custom.Example" not in sys.modules + + +def test_git_requires_an_immutable_revision_before_clone(store): + with pytest.raises(ExtensionError, match="40-character"): + store.stage(kind="git", source="https://example.com/example.git", name="Example", revision="main") + + +def test_standalone_discovery_never_imports_and_reload_reads_original_file(store, tmp_path, monkeypatch): + source = source_node(tmp_path / "source") + store.root.mkdir() + code = (source / "main.py").read_text(encoding="utf-8") + path = store.root / "Example.py" + path.write_text('MODIFF_RUNTIME_ROLE = "data"\n' + code, encoding="utf-8") + item = store.list()[0] + assert item["path"] == str(path) and item["runtimeRole"] == "data" + assert not item["enabled"] and "custom.Example.main" not in sys.modules + registry = store.enable("Example", code_hash=item["codeHash"], consent=True) + from modules import MODULE_MAP + monkeypatch.setitem(MODULE_MAP, "custom.Example", registry) + assert sys.modules["custom.Example.main"].Echo("single").execute("Hello") == {"out": "HELLO"} + path.write_text(code.replace("text.upper()", "text.lower()"), encoding="utf-8") + assert store.inspect("Example")["status"] == "changed" + changed = store.inspect("Example") + store.enable("Example", code_hash=changed["codeHash"], consent=True) + assert sys.modules["custom.Example.main"].Echo("single").execute("Hello") == {"out": "hello"} + + +@pytest.mark.parametrize("content,reason", [ + (b"print('not a node')", "No NodeBase"), + (b"class broken:", "Invalid Python"), + (b"MODIFF_RUNTIME_ROLE = compute()", "literal"), + (b"MODIFF_REQUIREMENTS = ['package\\n--index-url=x']", "requirement strings"), +]) +def test_invalid_standalone_node_is_rejected_without_import(content, reason): + from modiff.custom_extensions import python_node_files + with pytest.raises(ExtensionError, match=reason): + python_node_files(content) + + +def test_one_action_add_file_executes_through_graph_dispatch(backend, store, tmp_path): + import asyncio + code = (source_node(tmp_path / "source") / "main.py").read_text(encoding="utf-8") + async def scenario(): + denied = await backend.custom_modules_add(request({"kind": "file", "name": "Example", "content": code, "consent": False})) + assert denied.status == 400 and not store.path("Example").exists() + added = await backend.custom_modules_add(request({"kind": "file", "name": "Example", "content": code, "consent": True})) + assert added.status == 200, added.text + item = json.loads(added.text)["module"] + assert item["enabled"] and item["nodes"] == ["Echo"] + backend.execute_node("file-node", {"module": "custom.Example", "action": "Echo", "params": {"text": {"value": "works"}}}, "test", quiet=True) + assert backend.node_cache["file-node"].output == {"out": "WORKS"} + duplicate = await backend.custom_modules_add(request({"kind": "file", "name": "Example", "content": code, "consent": True})) + assert duplicate.status == 400 + assert store.inspect("Example")["enabled"] + asyncio.run(scenario()) + + +def test_add_source_failure_keeps_canvas_registry_and_queue_isolation(backend, store, tmp_path): + import asyncio + code = (source_node(tmp_path / "source") / "main.py").read_text(encoding="utf-8") + async def scenario(): + body = {"kind": "file", "name": "Example", "content": code, "consent": True} + backend.current_task = {"task_id": "unrelated"} + busy = await backend.custom_modules_add(request(body)) + assert busy.status == 409 and not store.path("Example").exists() + backend.current_task = None + body["content"] += '\nraise RuntimeError("import failed")\n' + failed = await backend.custom_modules_add(request(body)) + assert failed.status == 400 + assert not store.inspect("Example")["enabled"] + assert "custom.Example" not in backend.modules + asyncio.run(scenario()) + + +def test_file_add_allows_real_node_size_without_relaxing_other_control_limits(backend, store, tmp_path): + import asyncio + from types import SimpleNamespace + code = (source_node(tmp_path / "source") / "main.py").read_text() + "\n#" + "x" * 8192 + raw = json.dumps({"kind": "file", "name": "Example", "content": code, "consent": True}).encode() + async def read(): + return raw + incoming = SimpleNamespace(content_length=len(raw), read=read) + async def scenario(): + with pytest.raises(ValueError, match="4096"): + await backend._strict_runtime_control_json(incoming, allowed={"kind", "name", "content", "consent"}) + response = await backend.custom_modules_add(incoming) + assert response.status == 200, response.text + assert store.inspect("Example")["enabled"] + asyncio.run(scenario()) + + +def test_unapproved_directory_is_not_loaded_at_startup(store, tmp_path): + source_node(store.root / "Example") + registry = {} + store.load_enabled(registry) + assert registry == {} + assert "custom.Example.main" not in sys.modules + assert store.inspect("Example")["status"] == "disabled" + + +def test_approval_is_outside_source_and_hash_includes_helper_and_dependencies(store, tmp_path): + source = source_node(tmp_path / "source") + (source / "helper.py").write_text("VALUE = 1\n") + (source / ".extensions.json").write_text(json.dumps({"Example": {"enabled": True}})) + item = store.stage(kind="local", source=str(source), name="Example") + assert not item["enabled"] + helper = Path(item["path"]) / "helper.py" + helper.write_text("VALUE = 2\n") + assert store.inspect("Example")["codeHash"] != item["codeHash"] + + +def test_pinned_hub_modular_block_executes_through_native_upstream_and_reloads(store, tmp_path, monkeypatch): + import shutil + from modules import MODULE_MAP + + fixture = Path(__file__).resolve().parents[1] / "examples/custom_nodes/ModularPrompt" + revision = "a" * 40 + snapshot = tmp_path / "models--fixture--prompt" / "snapshots" / revision + shutil.copytree(fixture, snapshot) + calls = [] + + def downloaded(repo, **kwargs): + calls.append((repo, kwargs)) + return str(snapshot) + + monkeypatch.setattr("huggingface_hub.snapshot_download", downloaded) + item = store.stage(kind="hub", source="fixture/prompt", name="Example", revision=revision) + assert calls[0][1]["revision"] == revision + assert "*.safetensors" not in calls[0][1]["allow_patterns"] + assert item["preview"]["nodes"]["Block"]["params"]["out_result"]["type"] == "string" + registry = store.enable("Example", code_hash=item["codeHash"], consent=True) + monkeypatch.setitem(MODULE_MAP, "custom.Example", registry) + klass = sys.modules["custom.Example.main"].Block + assert klass("custom-block").execute(text="test") == {"out_result": "test — modular"} + code = Path(item["path"]) / "block.py" + code.write_text(code.read_text(encoding="utf-8").replace(" — modular", " — changed"), encoding="utf-8") + changed = store.inspect("Example") + store.enable("Example", code_hash=changed["codeHash"], consent=True) + assert sys.modules["custom.Example.main"].Block("custom-block").execute(text="test") == { + "out_result": "test — changed" + } + + +def test_mellon_omitted_model_inputs_are_reviewable_without_changing_approved_bytes(store, tmp_path, monkeypatch): + import shutil + from modules import MODULE_MAP + from modules.ModularDiffusers.pipeline_schema import MoDiffPipelineConfig + + source = tmp_path / "mellon-source" + shutil.copytree(Path(__file__).resolve().parents[1] / "examples/custom_nodes/ModularPrompt", source) + sidecar = source / "mellon_pipeline_config.json" + metadata = json.loads(sidecar.read_bytes()) + del metadata["node_params"]["custom"]["model_input_names"] + raw = json.dumps(metadata).encode() + sidecar.write_bytes(raw) + # The historical declarative pipeline loader retains its strict contract. + with pytest.raises(OSError, match="model_input_names"): + MoDiffPipelineConfig.from_json_bytes(raw) + item = store.stage(kind="local", source=str(source), name="Example") + assert item["preview"]["contract"]["model_input_names"] == [] + assert (Path(item["path"]) / sidecar.name).read_bytes() == raw + assert not item["enabled"] and "custom.Example.main" not in sys.modules + registry = store.enable("Example", code_hash=item["codeHash"], consent=True) + monkeypatch.setitem(MODULE_MAP, "custom.Example", registry) + assert sys.modules["custom.Example.main"].Block("mellon").execute(text="test") == {"out_result": "test — modular"} + + +@pytest.mark.parametrize("value", [None, "", {}, [None], [""]]) +def test_mellon_model_input_defaults_do_not_accept_invalid_declared_values(store, tmp_path, value): + import shutil + + source = tmp_path / "mellon-source" + shutil.copytree(Path(__file__).resolve().parents[1] / "examples/custom_nodes/ModularPrompt", source) + sidecar = source / "mellon_pipeline_config.json" + metadata = json.loads(sidecar.read_bytes()) + metadata["node_params"]["custom"]["model_input_names"] = value + sidecar.write_text(json.dumps(metadata)) + with pytest.raises(OSError, match="model_input_names"): + store.stage(kind="local", source=str(source), name="Example") + assert not store.path("Example").exists() + + +@pytest.fixture +def backend(store, tmp_path, monkeypatch): + from modiff.config import CONFIG + + for key in CONFIG.paths: + if key != "app_root": + directory = tmp_path / "runtime" / key + directory.mkdir(parents=True, exist_ok=True) + monkeypatch.setitem(CONFIG.paths, key, str(directory)) + from modiff.server import WebServer + from modules import MODULE_MAP + + server = WebServer(modules=dict(MODULE_MAP), work_dir=str(tmp_path), data_dir=str(tmp_path / "data")) + monkeypatch.setattr(server, "_extension_store", lambda: store) + monkeypatch.setattr("modiff.NodeBase._server", lambda: server) + yield server + MODULE_MAP.pop("custom.Example", None) + if server._model_executor: + server._model_executor.shutdown(wait=True) + + +def request(body=None, name="Example"): + from types import SimpleNamespace + from unittest.mock import AsyncMock + + return SimpleNamespace(json=AsyncMock(return_value=body or {}), match_info={"name": name}) + + +def test_api_reload_invalidates_only_affected_nodes_and_rejects_busy_or_unapproved(backend, store, tmp_path): + import asyncio + from types import SimpleNamespace + + async def scenario(): + installed = await backend.custom_modules_install( + request({"kind": "local", "source": str(source_node(tmp_path / "source")), "name": "Example"}) + ) + item = json.loads(installed.text)["module"] + assert "custom.Example" not in backend.modules + denied = await backend.custom_modules_enable(request({"codeHash": item["codeHash"], "consent": False})) + assert denied.status == 400 + approved = await backend.custom_modules_enable(request({"codeHash": item["codeHash"], "consent": True})) + assert approved.status == 200, approved.text + node = {"module": "custom.Example", "action": "Echo", "params": {"text": {"value": "Hello"}}} + backend.execute_node("echo", node, "test", quiet=True) + assert backend.node_cache["echo"].output == {"out": "HELLO"} + backend.execute_node("echo", node, "test", quiet=True) + assert not backend.node_cache["echo"]._has_changed + backend.node_cache["dependent"] = SimpleNamespace(_cache_input_sources={"echo"}) + backend.node_cache["unrelated"] = SimpleNamespace(_cache_input_sources=set()) + code = Path(item["path"]) / "main.py" + code.write_text(code.read_text().replace("text.upper()", "text.lower()")) + with pytest.raises(ExtensionError, match="changed"): + backend.execute_node("echo", node, "test", quiet=True) + changed = store.inspect("Example") + backend.current_task = {"task_id": "running"} + busy = await backend.custom_modules_reload(request({"codeHash": changed["codeHash"], "consent": True})) + assert busy.status == 409 + assert "echo" in backend.node_cache + backend.current_task = None + refreshed = await backend.custom_modules_refresh(request()) + assert refreshed.status == 200 + assert "echo" in backend.node_cache + reloaded = await backend.custom_modules_reload(request({"codeHash": changed["codeHash"], "consent": True})) + assert set(json.loads(reloaded.text)["removedCacheNodes"]) == {"echo", "dependent"} + assert set(backend.node_cache) == {"unrelated"} + backend.execute_node("echo", node, "test", quiet=True) + assert backend.node_cache["echo"].output == {"out": "hello"} + + asyncio.run(scenario()) + + +def test_documented_prompt_tools_example_stages_and_executes_through_the_graph(backend, store, monkeypatch): + """Keep the public walkthrough tied to the checked-in executable example.""" + from modiff.config import CONFIG + from modules import MODULE_MAP + + source = Path(__file__).resolve().parents[1] / "examples/custom_nodes/PromptTools" + item = store.stage(kind="local", source=str(source), name="PromptTools") + assert not item["enabled"] + definition = item["preview"]["nodes"]["PromptPrefix"] + assert definition["params"]["text"]["display"] == "textarea" + assert definition["params"]["prefix"]["display"] == "textarea" + assert definition["params"]["prompt_input"]["display"] == "input" + assert definition["params"]["prompt_input"]["required"] is False + assert definition["params"]["result"]["display"] == "output" + + registry = store.enable("PromptTools", code_hash=item["codeHash"], consent=True) + assert registry["PromptPrefix"]["resizable"] is True + monkeypatch.setitem(MODULE_MAP, "custom.PromptTools", registry) + backend.modules["custom.PromptTools"] = registry + try: + backend.execute_node( + "documented-text-value", + { + "module": "modules.Primitive", + "action": "TextValue", + "params": {"text": {"value": "a lighthouse at night"}}, + }, + "test", + quiet=True, + ) + prefix_node = { + "module": "custom.PromptTools", + "action": "PromptPrefix", + "params": { + "text": {"value": "this inline value is replaced by the connected prompt"}, + "prompt_input": {"sourceId": "documented-text-value", "sourceKey": "output"}, + "prefix": {"value": "Watercolor:"}, + }, + } + backend.execute_node("documented-prompt-prefix", prefix_node, "test", quiet=True) + assert backend.node_cache["documented-prompt-prefix"].output == { + "result": "Watercolor: a lighthouse at night" + } + destination = Path(CONFIG.paths["data"]) / "exports" / "PromptPrefix_test.txt" + backend.execute_node( + "documented-export", + { + "module": "modules.Primitive", + "action": "ExportData", + "params": { + "value": {"sourceId": "documented-prompt-prefix", "sourceKey": "result"}, + "filename": {"value": str(destination)}, + "format": {"value": "text"}, + }, + }, + "test", + quiet=True, + ) + assert destination.read_text(encoding="utf-8") == "Watercolor: a lighthouse at night\n" + assert backend.node_cache["documented-export"].output["output"] == "Watercolor: a lighthouse at night" + finally: + backend.modules.pop("custom.PromptTools", None) + store.unload("PromptTools") + + +def test_reload_client_cancellation_keeps_the_existing_execution_lease(backend, store, tmp_path, monkeypatch): + import asyncio + import threading + + item = store.stage(kind="local", source=str(source_node(tmp_path / "source")), name="Example") + entered, release = threading.Event(), threading.Event() + original = store.enable + + def pause(*args, **kwargs): + entered.set() + assert release.wait(5) + return original(*args, **kwargs) + + monkeypatch.setattr(store, "enable", pause) + + async def scenario(): + task = asyncio.create_task( + backend.custom_modules_enable(request({"codeHash": item["codeHash"], "consent": True})) + ) + try: + while not entered.is_set(): + await asyncio.sleep(0.01) + task.cancel() + await asyncio.sleep(0.01) + assert backend._node_cache_lock.locked() + assert not task.done() + finally: + release.set() + with pytest.raises(asyncio.CancelledError): + await task + assert not backend._node_cache_lock.locked() + assert store.inspect("Example")["enabled"] + + asyncio.run(scenario()) + + +@pytest.mark.parametrize("busy", ["running", "queued", "lease"]) +def test_source_resolution_and_inspection_stay_available_during_execution(backend, store, tmp_path, monkeypatch, busy): + import asyncio + import threading + + item = store.stage(kind="local", source=str(source_node(tmp_path / "source")), name="Example") + entered, release = threading.Event(), threading.Event() + identity = {"kind": "hub", "source": "example/block", "requestedRevision": "main", "revision": "a" * 40} + + def resolve(**kwargs): + assert kwargs == {"source": "example/block"} + entered.set() + assert release.wait(5) + return identity + + monkeypatch.setattr("modiff.custom_extension_source.resolve_hub_extension", resolve) + + async def scenario(): + if busy == "running": + backend.current_task = {"task_id": "running"} + elif busy == "queued": + await backend.main_queue.put({"task_id": "waiting"}) + else: + await backend._node_cache_lock.acquire() + lookup = asyncio.create_task(backend.custom_modules_resolve(request({"source": "example/block"}))) + try: + async with asyncio.timeout(3): + while not entered.is_set(): + await asyncio.sleep(0.01) + inspected = await backend.custom_modules_inspect(request()) + assert json.loads(inspected.text)["module"]["codeHash"] == item["codeHash"] + listed = await backend.custom_modules_list(request()) + assert listed.status == 200 + denied = await backend.custom_modules_enable(request({"codeHash": item["codeHash"], "consent": True})) + assert denied.status == 409 + assert "running and queued work" in json.loads(denied.text)["message"] + assert "custom.Example.main" not in sys.modules + finally: + release.set() + response = await lookup + backend.current_task = None + if busy == "queued": + backend.main_queue.get_nowait() + if busy == "lease": + backend._node_cache_lock.release() + assert response.status == 200 + assert json.loads(response.text)["source"] == identity + assert not store.inspect("Example")["enabled"] + + asyncio.run(scenario()) + + +def test_source_resolution_cancellation_does_not_stage_or_acquire_import_lease(backend, store, monkeypatch): + import asyncio + import threading + + entered, release, finished = threading.Event(), threading.Event(), threading.Event() + + def resolve(**kwargs): + entered.set() + assert release.wait(5) + finished.set() + return {"kind": "hub", "source": "example/block", "requestedRevision": "main", "revision": "a" * 40} + + monkeypatch.setattr("modiff.custom_extension_source.resolve_hub_extension", resolve) + + async def scenario(): + lookup = asyncio.create_task(backend.custom_modules_resolve(request({"source": "example/block"}))) + try: + async with asyncio.timeout(3): + while not entered.is_set(): + await asyncio.sleep(0.01) + lookup.cancel() + with pytest.raises(asyncio.CancelledError): + await lookup + assert not backend._node_cache_lock.locked() + assert not backend._node_cache_teardown_active + assert store.list() == [] + finally: + release.set() + async with asyncio.timeout(3): + while not finished.is_set(): + await asyncio.sleep(0.01) + assert store.list() == [] + + asyncio.run(scenario()) + + +@pytest.mark.parametrize("body", [{}, {"source": "example/block", "consent": True}, {"source": 123}]) +def test_source_resolution_api_rejects_invalid_requests(backend, body): + import asyncio + + response = asyncio.run(backend.custom_modules_resolve(request(body))) + assert response.status == 400 + assert json.loads(response.text)["error"] is True + + +@pytest.mark.parametrize("entry_name", ["block", "main"]) +def test_two_modular_packages_keep_relative_helpers_isolated(store, tmp_path, monkeypatch, entry_name): + import shutil + from modules import MODULE_MAP + + fixture = Path(__file__).resolve().parents[1] / "examples/custom_nodes/ModularPrompt" + try: + for name, suffix in [("Example", " first"), ("Second", " second")]: + source = tmp_path / name + shutil.copytree(fixture, source) + if entry_name != "block": + (source / "block.py").rename(source / f"{entry_name}.py") + config = source / "modular_config.json" + config.write_text(config.read_text().replace("block.PromptSuffix", f"{entry_name}.PromptSuffix")) + code = source / f"{entry_name}.py" + code.write_text( + code.read_text(encoding="utf-8").replace( + " state.set(", + f" from .{entry_name} import PromptSuffix\n assert isinstance(self, PromptSuffix)\n state.set(", + ), encoding="utf-8" + ) + code.write_text("from .helper import SUFFIX\n" + code.read_text(encoding="utf-8").replace('" — modular"', "SUFFIX"), encoding="utf-8") + (source / "helper.py").write_text(f"SUFFIX = {suffix!r}\n") + item = store.stage(kind="local", source=str(source), name=name) + registry = store.enable(name, code_hash=item["codeHash"], consent=True) + monkeypatch.setitem(MODULE_MAP, f"custom.{name}", registry) + assert sys.modules["custom.Example.main"].Block("first").execute(text="test") == {"out_result": "test first"} + assert sys.modules["custom.Second.main"].Block("second").execute(text="test") == {"out_result": "test second"} + helper = store.path("Example") / "helper.py" + helper.write_text("SUFFIX = ' fresh'\n") + changed = store.inspect("Example") + store.enable("Example", code_hash=changed["codeHash"], consent=True) + assert sys.modules["custom.Example.main"].Block("first").execute(text="test") == {"out_result": "test fresh"} + assert sys.modules["custom.Second.main"].Block("second").execute(text="test") == {"out_result": "test second"} + finally: + store.unload("Second") + assert not any(getattr(finder, "prefix", "").startswith("custom.Second") for finder in sys.meta_path) + + +def test_lazy_import_uses_only_approved_bytes_even_if_source_changes(store, tmp_path, monkeypatch): + from modules import MODULE_MAP + + source = source_node(tmp_path / "source") + code = source / "main.py" + code.write_text( + code.read_text().replace( + 'return {"out": text.upper()}', 'from .helper import transform; return {"out": transform(text)}' + ) + ) + (source / "helper.py").write_text("def transform(text): return text.upper()\n") + item = store.stage(kind="local", source=str(source), name="Example") + registry = store.enable("Example", code_hash=item["codeHash"], consent=True) + monkeypatch.setitem(MODULE_MAP, "custom.Example", registry) + (store.path("Example") / "helper.py").write_text('raise RuntimeError("unapproved edit")\n') + # Direct Python call demonstrates the import snapshot. Normal graph dispatch + # additionally rejects filesystem drift before executing even approved bytes. + assert sys.modules["custom.Example.main"].Echo("lazy").execute("Hello") == {"out": "HELLO"} + + +def test_custom_browser_assets_require_the_same_approval_and_reject_traversal(store, tmp_path): + source = source_node(tmp_path / "source") + (source / "web").mkdir() + (source / "web/field.js").write_text('export const label = "Field";\n') + item = store.stage(kind="local", source=str(source), name="Example") + with pytest.raises(ExtensionError, match="approval"): + store.asset("Example", "field.js") + store.enable("Example", code_hash=item["codeHash"], consent=True) + assert store.asset("Example", "field.js").startswith(b"export") + with pytest.raises(ExtensionError, match="path"): + store.asset("Example", "../main.py") + store.disable("Example") + with pytest.raises(ExtensionError, match="approval"): + store.asset("Example", "field.js") + + +def test_auto_accepts_approved_resource_roles_without_importing_or_granting_trust(store, tmp_path, monkeypatch): + from modiff.workflow_auto_resource import build_workflow_auto_plan + + monkeypatch.setattr("modiff.custom_extensions.ExtensionStore", lambda: store) + source = source_node(tmp_path / "source") + (source / "modiff_extension.json").write_text('{"runtimeRole":"data"}') + item = store.stage(kind="local", source=str(source), name="Example") + graph = { + "nodes": {"custom": {"module": "custom.Example", "action": "Echo", "params": {"text": {"value": "Hello"}}}}, + "paths": [["custom"]], + } + + def plan(): + return build_workflow_auto_plan( + graph, + runtime_fingerprint={}, + local_models=[], + data_dir=str(tmp_path), + hardware={"accelerator": {}, "systemMemory": {}, "offloadDisk": {}}, + ) + + assert not plan()["canAutoRun"] + store.enable("Example", code_hash=item["codeHash"], consent=True) + monkeypatch.setattr(store, "_load", lambda *_: pytest.fail("planning imported code")) + assert plan()["canAutoRun"] + assert plan()["schedule"] is None + policy = store.path("Example") / "modiff_extension.json" + policy.write_text('{"runtimeRole":"manual"}') + assert not plan()["canAutoRun"] + + +def test_staging_never_copies_model_weights_or_source_approval(store, tmp_path): + source = source_node(tmp_path / "source") + weights = source / "model.safetensors" + weights.write_bytes(b"preserved model") + item = store.stage(kind="local", source=str(source), name="Example") + assert "model.safetensors" not in {file["name"] for file in item["files"]} + assert weights.read_bytes() == b"preserved model" + assert not (Path(item["path"]) / "model.safetensors").exists() + + +@pytest.mark.parametrize("unsafe_path", [None, "C:/outside.py", "C:outside.py"]) +def test_git_stages_exact_commit_without_checking_out_weights(store, tmp_path, monkeypatch, unsafe_path): + import subprocess + + source = source_node(tmp_path / "git-source") + (source / "model.safetensors").write_bytes(b"preserved fixture weights") + + def git(*args): + return subprocess.check_output(["git", "-C", str(source), *args], stderr=subprocess.DEVNULL).decode().strip() + + git("init") + git("add", ".") + git("-c", "user.name=Extension Test", "-c", "user.email=test@example.invalid", "commit", "-m", "Source fixture") + revision = git("rev-parse", "HEAD") + original = subprocess.run + calls = [] + + def transport(args, **kwargs): + # Exercise real Git using a local fixture transport after production URL + # validation, without relying on an external host or downloading models. + args = [str(source) if arg == "https://example.invalid/fixture.git" else arg for arg in args] + calls.append(args) + result = original(args, **kwargs) + if unsafe_path and "ls-tree" in args: + result.stdout += b"100644 blob " + b"0" * 40 + b"\t" + unsafe_path.encode() + b"\0" + return result + + monkeypatch.setattr("modiff.custom_extensions.subprocess.run", transport) + if unsafe_path: + with pytest.raises(ExtensionError, match="Invalid Git source path"): + store.stage(kind="git", source="https://example.invalid/fixture.git", name="Example", revision=revision) + assert not store.path("Example").exists() + return + item = store.stage(kind="git", source="https://example.invalid/fixture.git", name="Example", revision=revision) + assert item["revision"] == revision and not item["enabled"] + assert not any("checkout" in args for args in calls) + assert not (store.path("Example") / "model.safetensors").exists() + assert (source / "model.safetensors").read_bytes() == b"preserved fixture weights" + # Git staging owns committed blob bytes, not the checkout's CRLF conversion. + assert (store.path("Example") / "main.py").read_bytes() == subprocess.check_output( + ["git", "-C", str(source), "show", f"{revision}:main.py"] + ) + + +def test_global_remote_code_disable_still_blocks_modular_activation(store, monkeypatch): + fixture = Path(__file__).resolve().parents[1] / "examples/custom_nodes/ModularPrompt" + item = store.stage(kind="local", source=str(fixture), name="Example") + monkeypatch.setattr("diffusers.utils.dynamic_modules_utils.DIFFUSERS_DISABLE_REMOTE_CODE", True) + with pytest.raises(ExtensionError, match="disabled globally"): + store.enable("Example", code_hash=item["codeHash"], consent=True) + assert not store.inspect("Example")["enabled"] + + +@pytest.mark.parametrize( + "filename,content", + [ + ("modular_config.json", "[]"), + ("modular_config.json", '{"requirements":[{}]}'), + ("modiff_extension.json", '{"runtimeRole":"fake"}'), + ("modiff_extension.json", '{"runtimeRole":{}}'), + ("pyproject.toml", '[project]\ndependencies="not an array"'), + ], +) +def test_invalid_dependency_and_resource_metadata_never_imports(store, tmp_path, filename, content): + source = source_node(tmp_path / "source") + (source / filename).write_text(content) + with pytest.raises(ExtensionError): + store.stage(kind="local", source=str(source), name="Example") + assert "custom.Example.main" not in sys.modules + + +def test_unchanged_approval_survives_startup_but_changed_source_does_not(store, tmp_path): + item = store.stage(kind="local", source=str(source_node(tmp_path / "source")), name="Example") + store.enable("Example", code_hash=item["codeHash"], consent=True) + store.unload("Example") + registry = {} + store.load_enabled(registry) + assert "custom.Example" in registry + code = store.path("Example") / "main.py" + code.write_text(code.read_text() + "\n# changed source\n") + store.unload("Example") + registry = {} + store.load_enabled(registry) + assert registry == {} + assert "custom.Example.main" not in sys.modules + + +@pytest.mark.parametrize("declared_ports", [True, False, "collision"]) +def test_modular_connected_weights_reuse_native_manager_and_survive_custom_release( + backend, store, tmp_path, monkeypatch, declared_ports +): + import asyncio + import shutil + from types import SimpleNamespace + import torch + from diffusers import ComponentsManager + from modules import MODULE_MAP + + fixture = Path(__file__).resolve().parents[1] / "examples/custom_nodes/ModularPrompt" + source = tmp_path / "component-block" + shutil.copytree(fixture, source) + code = source / "block.py" + code.write_text( + "import torch\nfrom diffusers.modular_pipelines import ComponentSpec\n" + + code.read_text(encoding="utf-8") + .replace( + " @property\n def inputs(self):", + ' @property\n def expected_components(self):\n return [ComponentSpec(name="weights", type_hint=torch.nn.Linear)]\n\n @property\n def inputs(self):', + ) + .replace('state.get("text") + " — modular"', 'state.get("text") + str(int(pipeline.weights.weight[0, 0]))'), + encoding="utf-8", + ) + sidecar = source / "mellon_pipeline_config.json" + data = json.loads(sidecar.read_text()) + contract = data["node_params"]["custom"] + explicit = declared_ports is True + if explicit: + contract["model_input_names"] = ["weights"] + contract["params"]["weights"] = {"type": "diffusers_auto_model", "display": "input"} + else: + del contract["model_input_names"] + if declared_ports == "collision": + contract["params"]["pipeline_components"] = {"type": "string", "default": "author value"} + sidecar.write_text(json.dumps(data)) + item = store.stage(kind="local", source=str(source), name="Example") + registry = store.enable("Example", code_hash=item["codeHash"], consent=True) + port = "weights" if explicit else "modiff_pipeline_components" if declared_ports == "collision" else "pipeline_components" + assert registry["Block"]["params"][port]["display"] == "input" + if not explicit: + assert registry["Block"]["params"][port]["type"] == "diffusers_modular_pipeline_components" + assert port not in item["preview"]["nodes"]["Block"]["params"] + assert store.inspect("Example")["codeHash"] == item["codeHash"] + if declared_ports == "collision": + assert registry["Block"]["params"]["pipeline_components"] == contract["params"]["pipeline_components"] + monkeypatch.setitem(MODULE_MAP, "custom.Example", registry) + backend.modules["custom.Example"] = registry + manager = ComponentsManager() + monkeypatch.setattr("modules.ModularDiffusers.components", manager) + weights = torch.nn.Linear(1, 1, bias=False) + with torch.no_grad(): + weights.weight.fill_(3) + comp_id = manager.add("weights", weights, collection="loader") + value = {"model_id": comp_id} if explicit else {"weights": {"model_id": comp_id}} + backend.node_cache["loader"] = SimpleNamespace(output={"weights": value}, _has_changed=False) + node = { + "module": "custom.Example", + "action": "Block", + "params": {"text": {"value": "value="}, port: {"sourceId": "loader", "sourceKey": "weights"}}, + } + backend.execute_node("block", node, "test", quiet=True) + assert backend.node_cache["block"].output == {"out_result": "value=3"} + + backend.execute_node("block", node, "test", quiet=True) + assert not backend.node_cache["block"]._has_changed + assert manager.components[comp_id] is weights + asyncio.run(backend.delete_cache(request({"nodes": ["block"]}))) + assert manager.components[comp_id] is weights + assert comp_id in manager.collections["loader"] + backend.execute_node("block", node, "test", quiet=True) + assert backend.node_cache["block"].output == {"out_result": "value=3"} + + # A real upstream call must reject missing/incompatible connected weights, + # before the custom body tries to read them. Neither path loads defaults. + from diffusers import ModularPipeline + monkeypatch.setattr(ModularPipeline, "load_components", lambda *a, **k: pytest.fail("implicit model loading")) + action = sys.modules["custom.Example.main"].Block + with pytest.raises(ExtensionError, match="Connect loaded components.*weights"): + action("missing").execute(text="value=") + other = torch.nn.ReLU() + other_id = manager.add("weights", other, collection="other-loader") + invalid = {"model_id": other_id} if explicit else {"weights": {"model_id": other_id}} + with pytest.raises(ValueError, match="expected torch.nn.modules.linear.Linear"): + action("incompatible").execute(text="value=", **{port: invalid}) + assert manager.components[comp_id] is weights + + # Source reload retains the derived socket identity and reuses the loader's + # real manager component, without rewriting its immutable sidecar. + code = Path(item["path"]) / "block.py" + code.write_text(code.read_text().replace('state.get("text") + str(', 'state.get("text") + "reloaded=" + str(')) + changed = store.inspect("Example") + updated = store.enable("Example", code_hash=changed["codeHash"], consent=True) + monkeypatch.setitem(MODULE_MAP, "custom.Example", updated) + assert updated["Block"]["params"][port] == registry["Block"]["params"][port] + assert sys.modules["custom.Example.main"].Block("reloaded").execute(text="value=", **{port: value}) == { + "out_result": "value=reloaded=3" + } + + +@pytest.mark.parametrize("model_type,denoiser", [ + ("FluxModularPipeline", "transformer"), + ("StableDiffusionXLModularPipeline", "unet"), +]) +def test_models_loader_publishes_managed_bundle_for_unscoped_pipeline(model_type, denoiser, monkeypatch): + """Transport/ownership contract using tiny modules, not model qualification.""" + import torch + from diffusers import ComponentsManager, ModularPipelineBlocks, ModelMixin + from diffusers.modular_pipelines import ComponentSpec + from modules.ModularDiffusers import loaders + from modules.ModularDiffusers.route_state import require_component_binding + + class TinyModel(ModelMixin): + def __init__(self): + super().__init__() + self.projection = torch.nn.Linear(1, 1) + + class TinyBlocks(ModularPipelineBlocks): + @property + def expected_components(self): + return [ComponentSpec(name=name, type_hint=TinyModel) for name in + (denoiser, "vae", "scheduler", "controlnet")] + + def __call__(self, pipeline, state): + raise AssertionError("Loader must not execute model blocks") + + manager = ComponentsManager() + pipeline = TinyBlocks().init_pipeline(components_manager=manager, collection="bundle-loader") + monkeypatch.setattr(loaders, "components", manager) + monkeypatch.setattr(loaders.ModelsLoader, "_preflight_reviewed_builtin_selection", + lambda *a, **k: ("hub", "fixture/tiny", "a" * 40, "model_index.json", {})) + monkeypatch.setattr(loaders, "_instantiate_reviewed_builtin_pipeline", lambda *a, **k: pipeline) + cache_dirs = {denoiser: "fixture-cache"} + monkeypatch.setattr(loaders, "_primary_component_cache_dirs", lambda *_args: cache_dirs) + loaded = [] + + def load(pipeline, names, **kwargs): + assert set(kwargs["required_names"]) == {denoiser, "vae", "scheduler"} + assert kwargs["component_load_kwargs"]["cache_dir"] == cache_dirs + for name in names: + if name == "controlnet": + continue # An inactive optional component must stay unloaded. + loaded.append(name) + pipeline.update_components(**{name: TinyModel()}) + + monkeypatch.setattr(loaders, "load_components_strict", load) + node = loaders.ModelsLoader("bundle-loader") + output = node.execute(model_type=model_type, repo_id={"source": "hub", "value": "fixture/tiny"}, + device="cpu", dtype=torch.float32, auto_offload=False, offload_mode="none") + bundle = output["pipeline_components"] + assert set(loaded) == {denoiser, "vae", "scheduler"} + assert "controlnet" not in bundle + for name in loaded: + assert manager.get_one(component_id=bundle[name]["model_id"]) is getattr(pipeline, name) + assert bundle[name]["model_id"] in manager.collections["bundle-loader"] + assert bundle["vae"]["model_id"] == output["vae_out"]["model_id"] + require_component_binding(bundle, label="custom models", expected_model_type=model_type, + expected_role="pipeline_components") + + +@pytest.mark.parametrize("dtype,force_upcast", [("float32", True), ("float16", True), ("float16", False)]) +def test_modular_image_reconstruction_uses_connected_vae_and_native_dispatch( + backend, store, monkeypatch, dtype, force_upcast +): + import numpy as np + import torch + from PIL import Image + from types import SimpleNamespace + from diffusers import AutoencoderKL, ComponentsManager + from modules import MODULE_MAP + + source = Path(__file__).resolve().parents[1] / "examples/custom_nodes/ModularImageReconstruction" + item = store.stage(kind="local", source=str(source), name="Example") + assert item["runtimeRole"] == "connected_components" + registry = store.enable("Example", code_hash=item["codeHash"], consent=True) + monkeypatch.setitem(MODULE_MAP, "custom.Example", registry) + backend.modules["custom.Example"] = registry + manager = ComponentsManager() + monkeypatch.setattr("modules.ModularDiffusers.components", manager) + original_dtype = getattr(torch, dtype) + vae = AutoencoderKL(block_out_channels=(8,), norm_num_groups=4, latent_channels=4, + force_upcast=force_upcast).eval().to(dtype=original_dtype) + comp_id = manager.add("vae", vae, collection="loader") + original = Image.fromarray(np.arange(16 * 16 * 3, dtype=np.uint8).reshape(16, 16, 3)) + backend.node_cache["loader"] = SimpleNamespace( + output={"models": {"vae": {"model_id": comp_id}}}, _has_changed=False, + ) + node = {"module": "custom.Example", "action": "Block", "params": { + "pipeline_components": {"sourceId": "loader", "sourceKey": "models"}, + "image": {"value": original}, "amount": {"value": "1.0"}, + }} + calls = [] + handle = vae.register_forward_hook(lambda *args: calls.append(True)) + observed = [] + def check_precision(module, args): + expected = torch.float32 if force_upcast and original_dtype == torch.float16 else original_dtype + assert module.dtype == expected and args[0].dtype == expected + observed.append(expected) + pre_handle = vae.register_forward_pre_hook(check_precision) + try: + backend.execute_node("reconstruction", node, "test", quiet=True) + first = backend.node_cache["reconstruction"].output["out_images"][0] + assert first.size == original.size and first.mode == "RGB" + assert first.tobytes() != original.tobytes() + backend.execute_node("reconstruction", node, "test", quiet=True) + assert len(calls) == 1 # Normal NodeBase cache, not a custom executor. + node["params"]["amount"]["value"] = "0.0" + backend.execute_node("reconstruction", node, "test", quiet=True) + restored = backend.node_cache["reconstruction"].output["out_images"][0] + assert restored.tobytes() == original.tobytes() + assert len(calls) == 2 + assert len(observed) == 2 and vae.dtype == original_dtype + assert manager.components[comp_id] is vae + assert comp_id in manager.collections["loader"] + assert all(not parameter.requires_grad or parameter.grad is None for parameter in vae.parameters()) + finally: + handle.remove() + pre_handle.remove() + + def fail_forward(*args): + raise RuntimeError("test forward failure") + failing_handle = vae.register_forward_pre_hook(fail_forward) + try: + node["params"]["amount"]["value"] = "0.25" + with pytest.raises(RuntimeError, match="test forward failure"): + backend.execute_node("reconstruction", node, "test", quiet=True) + assert vae.dtype == original_dtype + assert manager.components[comp_id] is vae + finally: + failing_handle.remove() + + +def test_invalid_approval_records_report_disabled_diagnostics(store, tmp_path): + store.stage(kind="local", source=str(source_node(tmp_path / "source")), name="Example") + (store.root / ".extensions.json").write_text('{"Example": []}') + item = store.list()[0] + assert item["status"] == "error" + assert not item["enabled"] + assert "Invalid extension approval file" in item["diagnostic"] + registry = {} + store.load_enabled(registry) + assert not registry + assert "custom.Example.main" not in sys.modules + + +def test_custom_modular_loader_requires_pins_and_reuses_native_components(backend, store, tmp_path, monkeypatch): + from diffusers import AutoencoderKL, ComponentsManager + from modules import MODULE_MAP + from PIL import Image + import torch + + source = Path(__file__).resolve().parents[1] / 'examples/custom_nodes/ModularImageReconstruction' + item = store.stage(kind='local', source=str(source), name='Example') + assert 'LoadModels' not in item['preview']['nodes'] # No Python imports during inspection. + registry = store.enable('Example', code_hash=item['codeHash'], consent=True) + assert registry['LoadModels']['params']['pipeline_components']['type'] == 'diffusers_modular_pipeline_components' + assert registry['LoadModels']['params']['source__vae__repo']['fieldOptions']['filter'] == { + 'hub': {'className': ['AutoencoderKL']}, + } + monkeypatch.setitem(MODULE_MAP, 'custom.Example', registry) + backend.modules['custom.Example'] = registry + manager = ComponentsManager() + monkeypatch.setattr('modules.ModularDiffusers.components', manager) + weights = tmp_path / 'models--fixture--vae' / 'snapshots' / ('a' * 40) + AutoencoderKL(block_out_channels=(8,), norm_num_groups=4, latent_channels=4).save_pretrained(weights) + lookups = [] + def cached_snapshot(repo_id, *, revision, local_files_only, allow_patterns): + assert repo_id == 'fixture/vae' and revision == 'a' * 40 and local_files_only is True + assert allow_patterns == ['*config.json'] + lookups.append(repo_id) + return str(weights) + monkeypatch.setattr('huggingface_hub.snapshot_download', cached_snapshot) + original_load = AutoencoderKL.from_pretrained + loads = [] + def load_cached(repo, **kwargs): + assert repo == 'fixture/vae' and kwargs['revision'] == 'a' * 40 + assert kwargs['subfolder'] == '' + assert kwargs['local_files_only'] and kwargs['trust_remote_code'] is False + assert kwargs['use_safetensors'] and kwargs['weights_only'] + loads.append(repo) + return original_load(weights, **kwargs) + monkeypatch.setattr(AutoencoderKL, 'from_pretrained', load_cached) + node = {'module': 'custom.Example', 'action': 'LoadModels', 'params': { + 'source__vae__repo': {'value': {'source': 'hub', 'value': 'fixture/vae'}}, + 'source__vae__revision': {'value': 'main'}, + 'device': {'value': 'cpu:0'}, 'dtype': {'value': 'float32'}, 'offload_mode': {'value': 'none'}, + }} + with pytest.raises(ValueError, match='40-character'): + backend.execute_node('custom-loader', node, 'test', quiet=True) + assert not lookups and not manager.components + node['params']['source__vae__revision']['value'] = 'a' * 40 + backend.execute_node('custom-loader', node, 'test', quiet=True) + bundle = backend.node_cache['custom-loader'].output['pipeline_components'] + vae_id = bundle['vae']['model_id'] + vae = manager.get_one(component_id=vae_id) + assert isinstance(vae, AutoencoderKL) and vae.dtype == torch.float32 + backend.execute_node('custom-loader', node, 'test', quiet=True) + assert not backend.node_cache['custom-loader']._has_changed + backend.execute_node('second-loader', node, 'test', quiet=True) + assert backend.node_cache['second-loader'].output['pipeline_components']['vae']['model_id'] == vae_id + assert vae_id in manager.collections['custom-loader'] and vae_id in manager.collections['second-loader'] + assert len(loads) == 1 + consumer = {'module': 'custom.Example', 'action': 'Block', 'params': { + 'pipeline_components': {'sourceId': 'custom-loader', 'sourceKey': 'pipeline_components'}, + 'image': {'value': Image.new('RGB', (16, 16), 'red')}, 'amount': {'value': 0.5}, + }} + backend.execute_node('custom-consumer', consumer, 'test', quiet=True) + assert backend.node_cache['custom-consumer'].output['out_images'][0].size == (16, 16) + # Metadata drift must not hit either the node cache or another owner's old + # component, while the old owner keeps its exact loaded object. + config = json.loads((weights / 'config.json').read_text()) + config['scaling_factor'] = 0.25 + (weights / 'config.json').write_text(json.dumps(config)) + backend.execute_node('custom-loader', node, 'test', quiet=True) + replacement_id = backend.node_cache['custom-loader'].output['pipeline_components']['vae']['model_id'] + assert replacement_id != vae_id and len(loads) == 2 + assert manager.get_one(component_id=vae_id) is vae + assert manager.get_one(component_id=replacement_id).config.scaling_factor == 0.25 + # Validation must precede cached reuse, including selectors a graph cannot authorize. + node['params']['source__vae__repo']['value'] = {'source': 'local', 'value': str(weights)} + with pytest.raises(ValueError, match='Hub'): + backend.execute_node('custom-loader', node, 'test', quiet=True) + assert manager.get_one(component_id=vae_id) is vae + backend._release_node_modular_components(['custom-loader']) + assert replacement_id not in manager.components + assert manager.get_one(component_id=vae_id) is vae + + +def test_custom_model_supplier_requires_manual_policy_even_for_connected_source(store, tmp_path, monkeypatch): + from modiff.workflow_auto_resource import build_workflow_auto_plan + source = Path(__file__).resolve().parents[1] / 'examples/custom_nodes/ModularImageReconstruction' + item = store.stage(kind='local', source=str(source), name='Example') + store.enable('Example', code_hash=item['codeHash'], consent=True) + assert store.inspect('Example')['nodeCount'] == 2 + assert store.inspect('Example')['nodes'] == ['Block', 'LoadModels'] + monkeypatch.setattr('modiff.custom_extensions.ExtensionStore', lambda: store) + monkeypatch.setattr(store, '_load', lambda *_: pytest.fail('planning must not import custom Python')) + graph = {'nodes': {'models': {'module': 'custom.Example', 'action': 'LoadModels', 'params': {}}}, 'paths': [['models']]} + result = build_workflow_auto_plan(graph, runtime_fingerprint={}, local_models=[], data_dir=str(tmp_path), + hardware={'accelerator': {}, 'systemMemory': {}, 'offloadDisk': {}}) + assert not result['canAutoRun'] + assert 'Custom memory policy' in ' '.join(result['issues']) + + +@pytest.mark.parametrize('field,value', [ + ('revision', ''), ('revision', 'main'), ('revision', 'A' * 40), + ('repo', {'source': 'hub', 'value': '../outside'}), + ('subfolder', '../outside'), ('variant', '../weights'), +]) +def test_custom_model_source_validation_precedes_cache_lookup(store, monkeypatch, field, value): + from modules import MODULE_MAP + source = Path(__file__).resolve().parents[1] / 'examples/custom_nodes/ModularImageReconstruction' + item = store.stage(kind='local', source=str(source), name='Example') + registry = store.enable('Example', code_hash=item['codeHash'], consent=True) + monkeypatch.setitem(MODULE_MAP, 'custom.Example', registry) + monkeypatch.setattr('huggingface_hub.snapshot_download', lambda *a, **k: pytest.fail('invalid selector reached cache')) + args = {'source__vae__repo': {'source': 'hub', 'value': 'fixture/vae'}, + 'source__vae__revision': 'a' * 40, 'source__vae__subfolder': '', 'source__vae__variant': ''} + args['source__vae__' + field] = value + with pytest.raises(ValueError): + sys.modules['custom.Example.main'].LoadModels('invalid-selection')(**args) diff --git a/tests/test_custom_modular_identity.py b/tests/test_custom_modular_identity.py index 47d68af6..9ad0bfbc 100644 --- a/tests/test_custom_modular_identity.py +++ b/tests/test_custom_modular_identity.py @@ -1105,6 +1105,29 @@ def test_loader_declares_object_identity_and_refresh_actions(self): "reviewed_official_components", ) + def test_builtin_field_action_keeps_output_signals_bound_to_model_type(self): + node = ModelsLoader("builtin-field-action") + self._capture_node_messages(node) + + node.refresh_pipeline_identity( + { + "model_type": "FluxModularPipeline", + "repo_id": {"source": "hub", "value": "black-forest-labs/FLUX.1-dev"}, + "revision": "", + "trust_remote_code": False, + }, + {"key": "model_type"}, + ) + + signals = [ + call.args[1]["signal"] + for call in node.set_field_params.call_args_list + if "signal" in call.args[1] + ] + self.assertEqual(len(signals), len(MODELS_LOADER_IDENTITY_OUTPUTS)) + self.assertTrue(all(signal["origin"] == "model_type" for signal in signals)) + self.assertTrue(all(signal["value"] == "FluxModularPipeline" for signal in signals)) + def test_field_action_persists_identity_and_publishes_structured_output_signals(self): node = ModelsLoader("identity-field-action") self._capture_node_messages(node) @@ -1424,6 +1447,12 @@ def test_standard_component_load_does_not_force_local_only(self): self.assertEqual(diagnostics["components_loaded"], ["transformer"]) def test_auto_model_trust_false_preserves_existing_download_behavior(self): + self._assert_auto_model_load_kwargs(offline=False) + + def test_auto_model_load_honors_offline_mode(self): + self._assert_auto_model_load_kwargs(offline=True) + + def _assert_auto_model_load_kwargs(self, *, offline): node = AutoModelLoader("auto-model-download-contract") node.diffusers_loading_progress = Mock(return_value=nullcontext()) revision = "a" * 40 @@ -1436,6 +1465,7 @@ class ApprovedTransformer: load=Mock(side_effect=RuntimeError("stop after load kwargs")), ) with ( + patch("huggingface_hub.constants.HF_HUB_OFFLINE", offline), patch("modules.ModularDiffusers.loaders.ComponentSpec", return_value=spec) as component_spec, patch( "modules.ModularDiffusers.loaders._preflight_reviewed_diffusers_component", @@ -1469,7 +1499,10 @@ class ApprovedTransformer: variant=None, revision=revision, ) - spec.load.assert_called_once_with(torch_dtype=torch.float32) + expected_kwargs = {"torch_dtype": torch.float32} + if offline: + expected_kwargs["local_files_only"] = True + spec.load.assert_called_once_with(**expected_kwargs) def test_auto_model_selector_spoofing_fails_before_node_cache(self): node = AutoModelLoader("auto-model-selector-cache-guard") @@ -2253,6 +2286,18 @@ def test_flux1_accepts_only_the_pinned_transformers_v5_t5_tokenizer_alias(self): "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21", "FluxPipeline", ), + ( + "FluxModularPipeline", + "black-forest-labs/FLUX.1-schnell", + "741f7c3ce8b383c54771c7003378a50191e9efe9", + "FluxPipeline", + ), + ( + "FluxModularPipeline", + "black-forest-labs/FLUX.1-Krea-dev", + "8162a9c7b05a641be098422bf2fcf335615c2f28", + "FluxPipeline", + ), ( "FluxKontextModularPipeline", "black-forest-labs/FLUX.1-Kontext-dev", @@ -2283,6 +2328,41 @@ def test_flux1_accepts_only_the_pinned_transformers_v5_t5_tokenizer_alias(self): ): _validate_reviewed_pipeline_index(model_type, repository, revision) + @requires_transformers + def test_sdxl_turbo_pinned_optional_absence_and_scheduler(self): + document = { + "_class_name": "StableDiffusionXLPipeline", + "feature_extractor": [None, None], "image_encoder": [None, None], + "scheduler": ["diffusers", "EulerAncestralDiscreteScheduler"], + "text_encoder": ["transformers", "CLIPTextModel"], + "text_encoder_2": ["transformers", "CLIPTextModelWithProjection"], + "tokenizer": ["transformers", "CLIPTokenizer"], + "tokenizer_2": ["transformers", "CLIPTokenizer"], + "unet": ["diffusers", "UNet2DConditionModel"], + "vae": ["diffusers", "AutoencoderKL"], + } + repository = "stabilityai/sdxl-turbo" + model_type = "StableDiffusionXLModularPipeline" + revision = "71153311d3dbb46851df1931d3ca6e939de83304" + with patch("modules.ModularDiffusers.loaders._load_reviewed_pipeline_index", + return_value=("model_index.json", document)): + filename, validated = _validate_reviewed_pipeline_index(model_type, repository, revision) + pipeline = _instantiate_reviewed_builtin_pipeline( + model_type, repository, index_filename=filename, index_document=validated, + components_manager=None, collection="turbo-index-contract", + ) + self.assertIsNone(pipeline.image_encoder) + for name, value in (("unet", [None, None]), ("image_encoder", [None, "Arbitrary"]), + ("scheduler", ["diffusers", "DDIMScheduler"])): + with self.subTest(name=name), patch( + "modules.ModularDiffusers.loaders._load_reviewed_pipeline_index", + return_value=("model_index.json", {**document, name: value}), + ), self.assertRaises(ValueError): + _validate_reviewed_pipeline_index(model_type, repository, revision) + with patch("modules.ModularDiffusers.loaders._load_reviewed_pipeline_index", + return_value=("model_index.json", document)), self.assertRaisesRegex(ValueError, "image_encoder"): + _validate_reviewed_pipeline_index(model_type, "stabilityai/stable-diffusion-xl-base-1.0", "unreviewed") + @requires_transformers def test_wan_flf_loads_reviewed_image_only_processor_after_index_validation(self): from diffusers.pipelines.pipeline_loading_utils import _fetch_class_library_tuple diff --git a/tests/test_dev_entrypoint.py b/tests/test_dev_entrypoint.py new file mode 100644 index 00000000..527ce620 --- /dev/null +++ b/tests/test_dev_entrypoint.py @@ -0,0 +1,44 @@ +from modiff import dev, install + + +def test_setup_preserves_existing_environment(tmp_path, monkeypatch, capsys): + monkeypatch.setattr(dev, "ROOT", tmp_path) + (tmp_path / ".venv").mkdir() + called = [] + monkeypatch.setattr(install, "main", lambda args: called.append(args) or 0) + assert dev.main(["setup", "--accelerator", "cpu", "--non-interactive"]) == 2 + assert not called + assert "Existing .venv preserved" in capsys.readouterr().err + assert dev.main(["plan", "--accelerator", "cpu"]) == 0 + assert called.pop() == ["--accelerator", "cpu", "--dry-run"] + assert dev.main(["setup", "--repair", "--accelerator", "cpu"]) == 0 + assert called.pop() == ["--repair", "--accelerator", "cpu"] + + +def test_runtime_uses_managed_python_and_profile_environment(tmp_path, monkeypatch): + import json + import os + + monkeypatch.setattr(dev, "ROOT", tmp_path) + venv = tmp_path / ".venv" + python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + python.parent.mkdir(parents=True) + python.touch() + (venv / "modiff-profile.json").write_text(json.dumps({"profile": "amd-rocm-linux"})) + monkeypatch.setattr(install, "_rocm_environment", lambda: {"ROCM_PATH": "/reviewed/rocm"}) + calls = [] + monkeypatch.setattr(dev.subprocess, "call", lambda cmd, **kw: calls.append((cmd, kw)) or 0) + assert dev.main(["check", "--json"]) == 0 + cmd, kw = calls.pop() + assert cmd == [str(python), "-m", "modiff.preflight", "--json"] + assert kw["cwd"] == tmp_path + assert kw["env"]["ROCM_PATH"] == "/reviewed/rocm" + assert kw["env"]["PYTORCH_CUDA_ALLOC_CONF"] == "expandable_segments:True" + assert dev.main(["run"]) == 0 + assert calls.pop()[0] == [str(python), str(tmp_path / "main.py")] + + +def test_missing_runtime_and_unknown_command_do_not_install(tmp_path, monkeypatch): + monkeypatch.setattr(dev, "ROOT", tmp_path) + assert dev.main(["check"]) == 2 + assert dev.main(["sync"]) == 2 diff --git a/tests/test_diffusers_audio.py b/tests/test_diffusers_audio.py index 31c4347e..683e9540 100644 --- a/tests/test_diffusers_audio.py +++ b/tests/test_diffusers_audio.py @@ -561,7 +561,8 @@ def from_pretrained(cls, repository, **kwargs): with ( patch("modules.DiffusersAudio.main.pipeline_class_from_name", return_value=FakePipelineClass), patch("modules.DiffusersAudio.main.local_files_only", return_value=True), - patch("modules.DiffusersAudio.main.apply_pipeline_offload"), + patch("modules.DiffusersAudio.main.exact_cached_snapshot_path", return_value=Path("installed-snapshot")) as snapshot, + patch("modules.DiffusersAudio.main.apply_pipeline_offload") as offload, ): result = executing.execute( pipeline_class="AceStepPipeline", @@ -570,7 +571,9 @@ def from_pretrained(cls, repository, **kwargs): revision=replacement_revision, ) - self.assertEqual(upstream_calls, [(replacement["value"], replacement_revision)]) + snapshot.assert_called_once_with(replacement["value"], replacement_revision) + self.assertEqual(offload.call_args.kwargs["resident_components"], ("condition_encoder", "vae")) + self.assertEqual(upstream_calls, [(Path("installed-snapshot"), replacement_revision)]) self.assertEqual(result["pipeline"]._modiff_audio_repo, replacement["value"]) self.assertEqual(result["pipeline"]._modiff_audio_revision, replacement_revision) @@ -2121,6 +2124,7 @@ def from_pretrained(cls, repo, **kwargs): node.mm_add = lambda *args, **kwargs: None with ( patch("modules.DiffusersAudio.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersAudio.main.exact_cached_snapshot_path", return_value=Path("installed-snapshot")), patch("modules.DiffusersAudio.main.apply_pipeline_offload"), ): node.execute( @@ -2133,7 +2137,7 @@ def from_pretrained(cls, repo, **kwargs): offload_mode="none", ) - self.assertEqual(loaded["repo"], "org/ace-step") + self.assertEqual(loaded["repo"], Path("installed-snapshot")) self.assertEqual(loaded["kwargs"]["device_map"], "cuda") self.assertEqual(loaded["kwargs"]["revision"], "0123456789abcdef0123456789abcdef01234567") self.assertTrue(loaded["kwargs"]["use_safetensors"]) @@ -2154,6 +2158,7 @@ def from_pretrained(cls, repo, **kwargs): with self.subTest(source=source): with ( patch("modules.DiffusersAudio.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersAudio.main.exact_cached_snapshot_path", return_value=Path("installed-snapshot")), patch("modules.DiffusersAudio.main.apply_pipeline_offload"), ): node.execute( @@ -2181,6 +2186,7 @@ def from_pretrained(cls, repo, **kwargs): with self.subTest(pipeline_class=pipeline_class): with ( patch("modules.DiffusersAudio.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersAudio.main.exact_cached_snapshot_path", return_value=Path("installed-snapshot")), patch("modules.DiffusersAudio.main.apply_pipeline_offload"), patch("modules.DiffusersAudio.main._ensure_language_model_generation_api"), ): diff --git a/tests/test_diffusers_direct_image_promotions.py b/tests/test_diffusers_direct_image_promotions.py index 0a900378..71c3233f 100644 --- a/tests/test_diffusers_direct_image_promotions.py +++ b/tests/test_diffusers_direct_image_promotions.py @@ -142,7 +142,7 @@ ( "pipelines/flux/pipeline_flux_kontext_inpaint.py", "FluxKontextInpaintPipeline", - "6f0d50ae6b3931dfe94ecec772aca77b77f0cc0aa7520256ad55a38ef7d731a2", + "993fbe87e9120d9cf6bf0540935b1c68d62473e7be9d57390fa50984f4b2158d", ( "DiffusionPipeline", "FluxLoraLoaderMixin", @@ -203,7 +203,7 @@ ( "pipelines/flux2/pipeline_flux2_klein_inpaint.py", "Flux2KleinInpaintPipeline", - "58c5f93bcbe57276e37833beaaeeb43d80e48132d8842ad0271a39784b628f63", + "6172f3ad4159c355b9fd59455a59dc78aeb428d4bc47eb0a266d09e83476d14c", ("DiffusionPipeline", "Flux2LoraLoaderMixin"), ("self", "scheduler", "vae", "text_encoder", "tokenizer", "transformer", "is_distilled"), ( @@ -318,7 +318,7 @@ def test_exact_pinned_sources_bases_constructors_and_calls_are_preserved(self): self.assertEqual( PINNED_DIFFUSERS_REVISION, - "2f7e0154a9db246e95c9ede43edba7db5b130805", + "fbf49e7f35857f76bc57b177e26f12b03687c668", ) diffusers_root = Path(diffusers.__file__).resolve().parent for relative_path, class_name, digest, bases, init_parameters, call_parameters in PINNED_SOURCES: diff --git a/tests/test_diffusers_image_registry.py b/tests/test_diffusers_image_registry.py index 95ee8c60..089bf617 100644 --- a/tests/test_diffusers_image_registry.py +++ b/tests/test_diffusers_image_registry.py @@ -123,6 +123,13 @@ def tag_test_image_pipeline(pipeline, pipeline_class, mode, *, repo=None, revisi class DiffusersImageRegistryTests(unittest.TestCase): + def test_prequantized_transformer_is_an_optional_loader_input(self): + # Ordinary pipelines load their own transformer. Graph repair must not + # invent a connection to an unrelated `any` output for this override. + param = LoadPipeline.params["prequantized_transformer"] + self.assertEqual(param["display"], "input") + self.assertIs(param.get("required"), False) + def test_missing_pinned_snapshot_explains_model_manager_recovery_without_download(self): from huggingface_hub.errors import LocalEntryNotFoundError from modules.DiffusersImage.main import load_cached_image_component @@ -510,6 +517,16 @@ def test_graph_contract_marks_mode_independent_image_inputs_as_required(self): self.assertTrue(ControlGenerate.params["control_image"]["required"]) self.assertTrue(LoadAdapter.params["pipeline"]["required"]) + def test_latent_decode_is_a_connection_capability_not_a_generation_operation(self): + contract = image_pipeline_contract(IMAGE_PIPELINE_ADAPTERS["FluxPipeline"], "text_to_image") + self.assertEqual(contract["actions"], {"Generate": ["text_to_image"]}) + self.assertEqual( + contract["connectionActions"], + {"Generate": ["text_to_image"], "DecodeLatents": ["text_to_image"]}, + ) + ordinary = image_pipeline_contract(IMAGE_PIPELINE_ADAPTERS["StableDiffusionXLPipeline"], "text_to_image") + self.assertNotIn("connectionActions", ordinary) + def test_registered_classes_can_be_constructed(self): for node_class in (Edit, LayerDecompose, ControlEdit, Inpaint, ControlInpaint, ControlGenerate): with self.subTest(node=node_class.__name__): @@ -1058,7 +1075,7 @@ def test_image_field_contracts_cover_every_adapter_mode_and_selected_values(self for field in ("width", "height"): self.assertEqual( {key: ernie["fieldParams"][field][key] for key in ("min", "max", "step")}, - {"min": 1024, "max": 1024, "step": 32}, + {"min": 512, "max": 2048, "step": 32}, ) self.assertEqual(ernie["maxOutputPixels"], 1024 * 1024) self.assertTrue(ernie["fieldParams"]["negative_prompt"]["hidden"]) @@ -1068,7 +1085,7 @@ def test_image_field_contracts_cover_every_adapter_mode_and_selected_values(self for field in ("width", "height"): self.assertEqual( {key: glm_image["fieldParams"][field][key] for key in ("min", "max", "step")}, - {"min": 1024, "max": 1024, "step": 32}, + {"min": 512, "max": 2048, "step": 32}, ) self.assertEqual(glm_image["maxOutputPixels"], 1024 * 1024) self.assertEqual( @@ -1170,7 +1187,7 @@ def test_pag_text_to_image_adapters_bind_exact_reviewed_sources_and_bounds(self) "revision": "ba991d1546d8c50936c4c16398ed0a87b9b99fb1", "artifact_classes": ("HunyuanDiTPipeline",), "max_steps": 25, - "output_bounds": (1024, 1024, 32, 1024 * 1024), + "output_bounds": (512, 2048, 32, 1024 * 1024), "max_sequence_length": 256, "variant": None, "component_dtypes": (), @@ -1270,6 +1287,7 @@ def __call__( return_dict, pag_scale, pag_adaptive_scale, + use_resolution_binning, ): call_values = dict(locals()) call_values.pop("received", None) @@ -1322,6 +1340,7 @@ class SanaPAGPipeline(PixArtSigmaPAGPipeline): "return_dict", "pag_scale", "pag_adaptive_scale", + "use_resolution_binning", }, "PixArtSigmaPAGPipeline": { "self", @@ -1396,7 +1415,7 @@ def test_pag_text_to_image_family_bounds_fail_before_inference(self): ) invalid = ( - ("HunyuanDiTPAGPipeline", {"width": 992}, "between 1024 and 1024"), + ("HunyuanDiTPAGPipeline", {"width": 480}, "between 512 and 2048"), ("HunyuanDiTPAGPipeline", {"num_inference_steps": 26}, "between 1 and 25"), ("HunyuanDiTPAGPipeline", {"max_sequence_length": 257}, "between 1 and 256"), ("PixArtSigmaPAGPipeline", {"num_inference_steps": 51}, "between 1 and 50"), @@ -1941,6 +1960,9 @@ def encode_prompt(prompt=None, *, dtype=None, **kwargs): expected_keys.add(adapter.image_guidance_parameter) if adapter.max_input_image_size is not None and "max_input_image_size" in upstream_parameters: expected_keys.add("max_input_image_size") + if adapter.use_resolution_binning is not None: + self.assertIn("use_resolution_binning", upstream_parameters) + expected_keys.add("use_resolution_binning") if adapter.conditioning_scale_parameter is not None: expected_keys.add(adapter.conditioning_scale_parameter) for parameter in ("control_guidance_start", "control_guidance_end"): @@ -1960,6 +1982,8 @@ def encode_prompt(prompt=None, *, dtype=None, **kwargs): action_class(f"signature-{pipeline_name}").execute(**values) self.assertEqual(set(received), expected_keys) + if adapter.use_resolution_binning is not None: + self.assertIs(received["use_resolution_binning"], adapter.use_resolution_binning) if adapter.guidance_parameter is not None: self.assertEqual( received[adapter.guidance_parameter], @@ -3302,11 +3326,11 @@ class Kandinsky3Img2ImgPipeline(Kandinsky3Pipeline): self.assertEqual(kwargs["variant"], "fp16") self.assertNotIn("trust_remote_code", kwargs) - with self.assertRaisesRegex(ValueError, "between 1024 and 1024"): + with self.assertRaisesRegex(ValueError, "increments of 64"): Generate("kandinsky3-size-contract").execute( pipeline=loaded_pipelines["text_to_image"], prompt="reviewed fixture", - width=960, + width=992, height=1024, num_inference_steps=25, guidance_scale=3.0, @@ -3723,8 +3747,8 @@ def __call__( self.assertNotIn("max_sequence_length", called) for field, value, message in ( - ("width", 1008, "between 1024 and 1024"), - ("height", 1056, "between 1024 and 1024"), + ("width", 1008, "increments of 32"), + ("height", 1056, "cannot exceed 1048576 pixels"), ("num_inference_steps", 9, "between 1 and 8"), ("guidance_scale", 1.1, "requires guidance_scale=1"), ): @@ -3872,8 +3896,8 @@ def encode_prompt( self.assertEqual(encoded["max_sequence_length"], 2048) for field, value, message in ( - ("width", 992, "between 1024 and 1024"), - ("height", 1056, "between 1024 and 1024"), + ("width", 1008, "increments of 32"), + ("height", 1056, "cannot exceed 1048576 pixels"), ("num_inference_steps", 51, "between 1 and 50"), ("max_sequence_length", 2049, "between 1 and 2048"), ): @@ -4652,6 +4676,7 @@ def __call__( controlnet_conditioning_scale=1.0, width=None, height=None, + use_resolution_binning=True, **kwargs, ): calls["generation"] = { @@ -4660,6 +4685,7 @@ def __call__( "controlnet_conditioning_scale": controlnet_conditioning_scale, "width": width, "height": height, + "use_resolution_binning": use_resolution_binning, "kwargs": kwargs, } return SimpleNamespace(images=[Image.new("RGB", (width, height), "white")]) @@ -4725,12 +4751,13 @@ def __call__( self.assertEqual(generated["height_out"], 1024) self.assertIs(calls["generation"]["control_image"], control_image) self.assertEqual(calls["generation"]["controlnet_conditioning_scale"], 1) - with self.assertRaisesRegex(ValueError, "between 1024 and 1024"): + self.assertFalse(calls["generation"]["use_resolution_binning"]) + with self.assertRaisesRegex(ValueError, "cannot exceed 1048576 pixels"): action.execute( pipeline=result["pipeline"], - prompt="reject an unreviewed size", + prompt="reject a size beyond the resource ceiling", control_image=control_image, - width=768, + width=1152, height=1024, num_inference_steps=50, guidance_scale=6, diff --git a/tests/test_diffusers_offload.py b/tests/test_diffusers_offload.py index 3780493e..f000997b 100644 --- a/tests/test_diffusers_offload.py +++ b/tests/test_diffusers_offload.py @@ -822,6 +822,24 @@ def test_strict_component_loading_forwards_reviewed_weight_variant(self): {"torch_dtype": "float16", "variant": "fp16"}, ) + @patch("huggingface_hub.constants.HF_HUB_OFFLINE", True) + def test_strict_component_loading_honors_offline_mode(self): + for requested in ({}, {"local_files_only": False}, {"local_files_only": {"transformer": False}}): + with self.subTest(requested=requested): + pipeline = FakeStrictPipeline({"transformer": FakeComponentSpec("transformer")}) + load_components_strict( + pipeline, + ["transformer"], + required_names={"transformer"}, + model_id="Qwen/Qwen-Image-2512", + dtype="bfloat16", + offload_mode=OFFLOAD_MODE_NONE, + quant_config=None, + diagnostics={}, + component_load_kwargs=requested, + ) + self.assertIs(pipeline.registered["transformer"]["kwargs"]["local_files_only"], True) + def test_incremental_group_offload_does_not_require_a_quantization_override(self): self.assertTrue( should_incrementally_group_offload( diff --git a/tests/test_diffusers_profiles.py b/tests/test_diffusers_profiles.py index 97bd1807..78f62f7c 100644 --- a/tests/test_diffusers_profiles.py +++ b/tests/test_diffusers_profiles.py @@ -38,6 +38,9 @@ def test_every_profile_declares_one_explicit_loader_and_execution_path(self): "modules.HuggingFaceSpeech", "LoadCTCSpeechRecognitionModel", ), + "direct-huggingface-transformers-depth": ( + "modules.HuggingFaceTransformers", "LoadDepthEstimationModel", + ), "direct-huggingface-transformers-text": ( "modules.HuggingFaceTransformers", "LoadTextGenerationModel", @@ -107,6 +110,7 @@ def test_every_supported_studio_model_has_an_execution_profile(self): "QwenImageLayeredModularPipeline", "QwenImageControlNetPipeline", "QwenImageLayeredPipeline", + "QwenImage21Pipeline", "QwenImageEditPipeline", "QwenImageEditPlusPipeline", "ZImageInpaintPipeline", @@ -212,6 +216,8 @@ def test_every_supported_studio_model_has_an_execution_profile(self): "MarigoldDepthPipeline", "HuggingFaceSpeechRecognitionModel", "HuggingFaceCTCSpeechRecognitionModel", + "DepthAnythingV2Model", + "DepthAnythingV2MetricModel", "HuggingFaceTextGenerationModel", "HuggingFaceImageTextToTextModel", "HuggingFaceAnyToAnyModel", diff --git a/tests/test_diffusers_video_extended_routes.py b/tests/test_diffusers_video_extended_routes.py index d60c6cbd..f3106218 100644 --- a/tests/test_diffusers_video_extended_routes.py +++ b/tests/test_diffusers_video_extended_routes.py @@ -103,7 +103,7 @@ def test_exact_pinned_upstream_sources_and_call_signatures_are_preserved(self): self.assertEqual( PINNED_DIFFUSERS_REVISION, - "2f7e0154a9db246e95c9ede43edba7db5b130805", + "fbf49e7f35857f76bc57b177e26f12b03687c668", ) diffusers_root = Path(diffusers.__file__).resolve().parent for relative_path, class_name, expected_digest, required_parameters in ( diff --git a/tests/test_diffusers_video_registry.py b/tests/test_diffusers_video_registry.py index ab078b0a..e03c404f 100644 --- a/tests/test_diffusers_video_registry.py +++ b/tests/test_diffusers_video_registry.py @@ -52,6 +52,7 @@ VIDEO_PIPELINE_LOAD_HANDLERS, VIDEO_MODE_FIELD_CONTRACTS, WAN_VACE_MODE_MEDIA_CONTRACTS, + _adapter_signal, _pipeline_adapter, _resolve_adapter_model_selection, _resolve_loader_revision, @@ -665,29 +666,14 @@ def test_pipeline_signal_publishes_backend_owned_exact_adapter_modes(self): } ) signal = loader.set_field_params.call_args.args[1]["signal"] - self.assertEqual( - signal["value"], - { - "schemaVersion": 1, - "library": "diffusers", - "mediaKind": "video", - "pipelineClass": "HunyuanVideoFramepackPipeline", - "modes": ["image_to_video"], - }, - ) + self.assertEqual(signal["value"], _adapter_signal(VIDEO_PIPELINE_ADAPTERS["HunyuanVideoFramepackPipeline"])) generator = Generate("generator") generator.set_field_params = MagicMock() generator.update_adapter_modes( { "mode": "text_to_video", - "video_contract": { - "schemaVersion": 1, - "library": "diffusers", - "mediaKind": "video", - "pipelineClass": "HunyuanVideoFramepackPipeline", - "modes": ["image_to_video"], - }, + "video_contract": _adapter_signal(VIDEO_PIPELINE_ADAPTERS["HunyuanVideoFramepackPipeline"]), }, None, ) @@ -720,13 +706,7 @@ def test_pipeline_signal_publishes_backend_owned_exact_adapter_modes(self): ) self.assertEqual( LoadPipeline.params["pipeline"]["signal"]["value"], - { - "schemaVersion": 1, - "library": "diffusers", - "mediaKind": "video", - "pipelineClass": "WanVACEPipeline", - "modes": list(VIDEO_PIPELINE_ADAPTERS["WanVACEPipeline"].modes), - }, + _adapter_signal(VIDEO_PIPELINE_ADAPTERS["WanVACEPipeline"]), ) def test_video_model_action_couples_repository_and_revision_before_real_execution(self): @@ -924,13 +904,7 @@ def test_video_field_contracts_cover_every_adapter_mode_and_update_selected_fiel node.update_adapter_modes( { "mode": mode, - "video_contract": { - "schemaVersion": 1, - "library": "diffusers", - "mediaKind": "video", - "pipelineClass": pipeline_class, - "modes": list(adapter.modes), - }, + "video_contract": _adapter_signal(adapter), }, {"key": "mode"}, ) @@ -2060,6 +2034,14 @@ def __call__(self, **_kwargs): def test_legacy_ltx2_action_uses_generic_video_audio_contract_but_is_hidden(self): self.assertTrue(issubclass(GenerateLTX2, GenerateVideoAudio)) + self.assertIs(GenerateLTX2.execute, GenerateVideoAudio.execute) + self.assertIs(GenerateLTX2.params, GenerateVideoAudio.params) + self.assertIs(GenerateLTX2.update_adapter_modes, GenerateVideoAudio.update_adapter_modes) + # Retiring discovery must preserve the saved action's exact callable + # and field contract, including future fixes on the canonical action. + canonical = module_registry.MODULE_MAP["modules.DiffusersVideo"]["GenerateVideoAudio"] + legacy = module_registry.MODULE_MAP["modules.DiffusersVideo"]["GenerateLTX2"] + self.assertEqual(legacy["params"], canonical["params"]) self.assertTrue(module_registry.MODULE_MAP["modules.DiffusersVideo"]["GenerateLTX2"]["hidden"]) self.assertIn("GenerateVideoAudio", module_registry.MODULE_MAP["modules.DiffusersVideo"]) @@ -2302,7 +2284,13 @@ def test_local_video_model_is_canonical_before_cache_and_drops_hub_revision(self with tempfile.TemporaryDirectory() as temporary: local_model = Path(temporary) / "models" / "local-video" local_model.mkdir(parents=True) - with chdir(temporary), patch("modiff.NodeBase.modelstore.is_local_cached", return_value=True): + # Cache notifications must not initialize the HTTP server while the + # test is deliberately resolving model paths from another cwd. + with ( + chdir(temporary), + patch("modiff.NodeBase.modelstore.is_local_cached", return_value=True), + patch("modiff.NodeBase._server"), + ): first = node( pipeline_class="LTXConditionPipeline", model_id={"source": "local", "value": "models/local-video"}, @@ -2965,6 +2953,7 @@ def test_animatelcm_loader_pins_linear_scheduler_and_named_safe_lora(self): ) node = LoadPipeline("animatelcm-loader") with ( + patch.dict("modules.DiffusersVideo.main.CONFIG.hf", {"cache_dir": "reviewed-test-cache"}), patch("diffusers.MotionAdapter.from_pretrained", return_value=object()) as load_motion, patch("diffusers.AnimateDiffPipeline.from_pretrained", return_value=pipeline), patch("diffusers.LCMScheduler.from_config", return_value=replacement_scheduler) as lcm_scheduler, @@ -2993,6 +2982,7 @@ def test_animatelcm_loader_pins_linear_scheduler_and_named_safe_lora(self): revision=ANIMATELCM_MOTION_REVISION, local_files_only=True, use_safetensors=True, + cache_dir="reviewed-test-cache", ) pipeline.set_adapters.assert_called_once_with( [ANIMATELCM_LORA_ADAPTER_NAME], diff --git a/tests/test_editorial_regions.py b/tests/test_editorial_regions.py new file mode 100644 index 00000000..974ed523 --- /dev/null +++ b/tests/test_editorial_regions.py @@ -0,0 +1,102 @@ +"""Art-direction controls, bounded mask algebra and protected graph dispatch.""" + +import json +import sys +from pathlib import Path + +import numpy as np +import pytest +from PIL import Image + +from modiff.custom_extensions import ExtensionStore + + +@pytest.fixture +def nodes(tmp_path, monkeypatch): + from modules import MODULE_MAP + + store = ExtensionStore(tmp_path / "custom") + item = store.stage(kind="local", source=str(Path(__file__).resolve().parents[1] / + "examples/custom_nodes/EditorialRegions"), name="EditorialTest") + assert not item["enabled"] + assert set(item["preview"]["nodes"]) == {"EditorialRegions", "ProtectedComposite"} + registry = store.enable("EditorialTest", code_hash=item["codeHash"], consent=True) + monkeypatch.setitem(MODULE_MAP, "custom.EditorialTest", registry) + module = sys.modules["custom.EditorialTest.main"] + yield module.EditorialRegions("regions"), module.ProtectedComposite("composite") + store.unload("EditorialTest") + + +def polygon(points, operation="add"): + return {"operation": operation, "points": points} + + +def test_mask_algebra_and_protection(nodes): + director, composite = nodes + source = Image.new("RGB", (100, 100), (60, 80, 100)) + before = source.tobytes() + regions = json.dumps([ + polygon([[0.1, 0.1], [0.9, 0.1], [0.9, 0.9], [0.1, 0.9]]), + polygon([[0.4, 0.4], [0.6, 0.4], [0.6, 0.6], [0.4, 0.6]], "subtract"), + ]) + result = director(image=source, regions=regions, feather=0, exposure=1) + assert result["mask"].getpixel((50, 50)) == 0 + assert result["mask"].getpixel((20, 20)) == 255 + assert result["out_image"].getpixel((50, 50)) == source.getpixel((50, 50)) + assert result["out_image"].getpixel((20, 20)) != source.getpixel((20, 20)) + generated = Image.new("RGB", source.size, "red") + finished = composite(original=source, edited=[generated], mask=result["mask"])["out_image"] + zero = np.asarray(result["mask"]) == 0 + assert np.array_equal(np.asarray(finished)[zero], np.asarray(source)[zero]) + assert finished.getpixel((20, 20)) == (255, 0, 0) + assert source.tobytes() == before + + +def test_intersection_and_feather(nodes): + director, _ = nodes + regions = json.dumps([ + polygon([[0, 0], [1, 0], [1, 1], [0, 1]]), + polygon([[0.2, 0.2], [0.8, 0.2], [0.8, 0.8], [0.2, 0.8]], "intersect"), + ]) + mask = np.asarray(director.execute(Image.new("RGB", (100, 100)), regions, feather=0.03)["mask"]) + assert mask[50, 50] == 255 and mask[0, 0] == 0 + assert ((mask > 0) & (mask < 255)).any() + + +def test_neutral_deterministic_and_changed_controls(nodes): + director, _ = nodes + source = Image.fromarray(np.random.default_rng(7).integers(0, 256, (32, 32, 3), dtype=np.uint8)) + neutral = director(image=[source]) + assert neutral["out_image"].tobytes() == source.tobytes() + first = director(image=source, temperature=0.8)["out_image"] + assert first.tobytes() != source.tobytes() + assert director(image=source, temperature=0.8)["out_image"].tobytes() == first.tobytes() + assert director(image=source, saturation=0)["out_image"].getpixel((16, 16))[0] == director( + image=source, saturation=0)["out_image"].getpixel((16, 16))[1] + + +@pytest.mark.parametrize("kwargs", [ + {"regions": "not json"}, {"regions": "[]"}, {"regions": "{}"}, + {"regions": "x" * 16385}, {"regions": '[{"operation":"add","points":[[0,0],[1,0],[2,1]]}]'}, + {"regions": '[{"operation":"add","points":[[0,0],[1,0],[true,1]]}]'}, + {"regions": '[{"operation":"unknown","points":[[0,0],[1,0],[1,1]]}]'}, + {"regions": json.dumps([polygon([[0, 0], [1, 0], [1, 1]])] * 17)}, + {"exposure": float("nan")}, {"temperature": 2}, {"saturation": -1}, {"feather": True}, +]) +def test_invalid_controls(nodes, kwargs): + with pytest.raises(ValueError): + nodes[0].execute(Image.new("RGB", (16, 16)), **kwargs) + + +def test_invalid_media_and_dimensions(nodes): + director, composite = nodes + for image in ([], [Image.new("RGB", (16, 16))] * 2, "tensor", Image.new("L", (2049, 2048))): + with pytest.raises(ValueError): + director.execute(image) + source = Image.new("RGB", (16, 16)) + with pytest.raises(ValueError, match="dimensions"): + composite.execute(source, Image.new("RGB", (32, 32)), Image.new("L", (16, 16))) + with pytest.raises(ValueError, match="grayscale"): + composite.execute(source, source, source) + transparent = Image.new("RGBA", (16, 16), (255, 0, 0, 0)) + assert director.execute(transparent)["out_image"].getextrema() == ((255, 255),) * 3 diff --git a/tests/test_ernie_index_contract.py b/tests/test_ernie_index_contract.py new file mode 100644 index 00000000..6ffdb77a --- /dev/null +++ b/tests/test_ernie_index_contract.py @@ -0,0 +1,47 @@ +"""Exact ERNIE tokenizer factory/concrete mappings, without model loading.""" + +from copy import deepcopy +import importlib.util +from unittest.mock import patch + +import pytest + +from modules.ModularDiffusers.loaders import _validate_reviewed_pipeline_index + +pytestmark = pytest.mark.skipif( + importlib.util.find_spec("transformers") is None, + reason="requires the staged optional Transformers runtime", +) + + +REPOSITORY = "baidu/ERNIE-Image-Turbo" +REVISION = "bc68c81e2a1730a394d5fc9fae70713dee940140" +DOCUMENT = { + "_class_name": "ErnieImagePipeline", + "_diffusers_version": "0.36.0", + "scheduler": ["diffusers", "FlowMatchEulerDiscreteScheduler"], + "pe": ["transformers", "Ministral3ForCausalLM"], + "pe_tokenizer": ["transformers", "TokenizersBackend"], + "text_encoder": ["transformers", "Mistral3Model"], + "tokenizer": ["transformers", "TokenizersBackend"], + "transformer": ["diffusers", "ErnieImageTransformer2DModel"], + "vae": ["diffusers", "AutoencoderKLFlux2"], +} + + +def test_ernie_pinned_index_accepts_the_two_reviewed_concrete_tokenizers(): + with patch("modules.ModularDiffusers.loaders._load_reviewed_pipeline_index", + return_value=("model_index.json", DOCUMENT)): + assert _validate_reviewed_pipeline_index( + "ErnieImageModularPipeline", REPOSITORY, REVISION, + ) == ("model_index.json", DOCUMENT) + + +@pytest.mark.parametrize("component", ["pe_tokenizer", "tokenizer", "pe", "text_encoder", "transformer", "vae"]) +def test_ernie_index_still_rejects_unreviewed_component_types(component): + document = deepcopy(DOCUMENT) + document[component] = ["unreviewed_package", "UnreviewedModel"] + with patch("modules.ModularDiffusers.loaders._load_reviewed_pipeline_index", + return_value=("model_index.json", document)): + with pytest.raises(ValueError, match=f"component '{component}'"): + _validate_reviewed_pipeline_index("ErnieImageModularPipeline", REPOSITORY, REVISION) diff --git a/tests/test_extension_test_isolation.py b/tests/test_extension_test_isolation.py new file mode 100644 index 00000000..93ad5bcb --- /dev/null +++ b/tests/test_extension_test_isolation.py @@ -0,0 +1,13 @@ +"""Unit test discovery must never import or rewrite operator extensions.""" +from pathlib import Path + +from modiff.custom_extensions import ExtensionStore + + +def test_default_extension_store_is_outside_the_operator_checkout(): + operator_root = Path(__file__).resolve().parents[1] / 'custom' + assert ExtensionStore().root != operator_root + + +def test_explicit_extension_fixture_root_is_preserved(tmp_path): + assert ExtensionStore(tmp_path / 'explicit').root == tmp_path / 'explicit' diff --git a/tests/test_field_metadata.py b/tests/test_field_metadata.py new file mode 100644 index 00000000..9381360d --- /dev/null +++ b/tests/test_field_metadata.py @@ -0,0 +1,251 @@ +"""Authoring callbacks must not borrow a running graph's model owners.""" + +import asyncio +import json +import importlib.util +import tempfile +import threading +import unittest +from types import SimpleNamespace +from unittest.mock import AsyncMock, Mock, patch + +from modiff.server import WebServer +from modiff.field_metadata import is_metadata_field_action, metadata_field_callback +from modiff.runtime_overlays import OverlayInstallBusy + + +class FieldMetadataTests(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + directory = tempfile.TemporaryDirectory() + self.addCleanup(directory.cleanup) + self.server = WebServer( + modules={"modules.ModularDiffusers": {"ModelsLoader": { + "params": {"model_type": {"onChange": "set_filters"}}, + }}}, work_dir=directory.name, data_dir=directory.name, + ) + self.server.loop = asyncio.get_running_loop() + + def request(self, **updates): + data = { + "node": "same-model-owner", "sid": "editing-session", + "module": "modules.ModularDiffusers", "action": "ModelsLoader", + "fieldKey": "model_type", "fn": "set_filters", + "values": {"model_type": ""}, "queue": False, + "workflowTabId": "next-draft", "workflowCanvasEpoch": 3, + "workflowFormEpoch": 7, + } + data.update(updates) + return SimpleNamespace(json=AsyncMock(return_value=data)) + + async def test_loader_fields_complete_while_same_id_model_owner_is_locked(self): + owner = SimpleNamespace( + module_name="modules.ModularDiffusers", class_name="ModelsLoader", + _sid="running-session", set_filters=Mock(side_effect=AssertionError("live owner touched")), + ) + self.server.node_cache["same-model-owner"] = owner + await self.server._node_cache_lock.acquire() + try: + with patch("modiff.NodeBase._server", return_value=self.server), patch( + "modiff.server.field_action_optional_runtime_requirement", return_value=None, + ), patch.object(self.server, "queue_message") as publish: + response = await asyncio.wait_for(self.server.field_action(self.request()), timeout=1) + self.assertEqual(response.status, 200, response.text) + self.assertFalse(json.loads(response.text)["error"]) + self.assertGreater(publish.call_count, 3) + for call in publish.call_args_list: + self.assertEqual(call.args[0]["workflow_tab_id"], "next-draft") + self.assertEqual(call.args[0]["sid"], "editing-session") + self.assertEqual(self.server.node_cache, {"same-model-owner": owner}) + self.assertEqual(owner._sid, "running-session") + owner.set_filters.assert_not_called() + finally: + self.server._node_cache_lock.release() + + def test_only_exact_reviewed_nonqueued_callbacks_are_isolated(self): + base = {"module": "modules.ModularDiffusers", "action": "ModelsLoader", "fn": "set_filters"} + self.assertTrue(is_metadata_field_action(base)) + for updates in ( + {"module": "custom.ModularDiffusers"}, {"action": "CustomLoader"}, + {"fn": "execute"}, {"fn": "__del__"}, {"queue": True}, + {"queue": "false"}, {"action": []}, {"fn": {}}, + ): + with self.subTest(updates=updates): + self.assertFalse(is_metadata_field_action({**base, **updates})) + for payload in (None, [], "ModelsLoader"): + self.assertFalse(is_metadata_field_action(payload)) + with self.assertRaises(ValueError): + metadata_field_callback("ModelsLoader", "execute", node_id="n", sid="s") + + async def test_ordinary_image_fields_do_not_wait_for_model_execution(self): + self.server.modules["modules.DiffusersImage"] = {"LoadPipeline": { + "params": {"pipeline_class": {"onChange": "update_pipeline_contract"}}, + }} + await self.server._node_cache_lock.acquire() + try: + with patch("modiff.NodeBase._server", return_value=self.server), patch( + "modiff.server.field_action_optional_runtime_requirement", return_value=None, + ), patch.object(self.server, "queue_message") as publish: + response = await asyncio.wait_for(self.server.field_action(self.request( + module="modules.DiffusersImage", action="LoadPipeline", + fieldKey="pipeline_class", fn="update_pipeline_contract", + values={"pipeline_class": "FluxPipeline", "mode": "text_to_image"}, + )), timeout=1) + self.assertEqual(response.status, 200, response.text) + self.assertTrue(publish.called) + self.assertEqual(self.server.node_cache, {}) + finally: + self.server._node_cache_lock.release() + + async def test_metadata_still_requires_authoritative_field_and_optional_runtime(self): + with patch("modiff.server.metadata_field_callback") as factory: + response = await self.server.field_action(self.request(fieldKey="undeclared")) + self.assertEqual(response.status, 400) + factory.assert_not_called() + requirement = { + "schemaVersion": 1, "delivery": "optional_overlay", "requiredNow": True, + "profileIds": ["missing-profile"], "state": "missing", "reason": "optional_runtime_missing", + } + with patch("modiff.server.field_action_optional_runtime_requirement", return_value=requirement): + response = await self.server.field_action(self.request()) + self.assertEqual(response.status, 409) + factory.assert_not_called() + self.server._runtime_mutation_gate = {"token": "activation"} + response = await self.server.field_action(self.request()) + self.assertEqual(response.status, 409) + self.assertEqual(json.loads(response.text)["error_code"], "runtime_mutation_busy") + factory.assert_not_called() + + async def test_queued_metadata_and_custom_callbacks_keep_model_ownership(self): + for updates in ({"queue": True}, {"module": "custom.Example"}, {"fn": "execute"}): + with self.subTest(updates=updates): + await self.server._node_cache_lock.acquire() + try: + with patch.object(self.server, "_field_action", new_callable=AsyncMock) as dispatch: + dispatch.return_value = "done" + task = asyncio.create_task(self.server.field_action(self.request(**updates))) + await asyncio.sleep(0.02) + dispatch.assert_not_awaited() + self.server._node_cache_lock.release() + self.assertEqual(await task, "done") + finally: + if self.server._node_cache_lock.locked(): + self.server._node_cache_lock.release() + + async def test_cancellation_drains_metadata_before_next_action_or_runtime_activation(self): + entered, release = threading.Event(), threading.Event() + + def set_filters(values, ref): + entered.set() + if not release.wait(3): + raise AssertionError("test did not release metadata callback") + + with patch("modiff.server.metadata_field_callback", return_value=set_filters), patch( + "modiff.server.field_action_optional_runtime_requirement", return_value=None, + ): + task = asyncio.create_task(self.server.field_action(self.request())) + following = None + try: + self.assertTrue(await asyncio.to_thread(entered.wait, 1)) + task.cancel() + await asyncio.sleep(0.02) + task.cancel() + await asyncio.sleep(0.02) + self.assertFalse(task.done()) + self.assertTrue(self.server._field_metadata_lock.locked()) + self.assertFalse(self.server._node_cache_lock.locked()) + with self.assertRaises(OverlayInstallBusy): + self.server._reserve_worker_runtime_gate("activation", "profile") + mutation = Mock(side_effect=AssertionError("custom code changed during metadata")) + response = await self.server._extension_mutation(mutation) + self.assertEqual(response.status, 409) + mutation.assert_not_called() + following = asyncio.create_task(self.server.field_action(self.request())) + await asyncio.sleep(0.02) + self.assertFalse(following.done()) + finally: + release.set() + with self.assertRaises(asyncio.CancelledError): + await task + if following is not None: + self.assertEqual((await following).status, 200) + token = self.server._reserve_worker_runtime_gate("activation", "profile") + self.server._release_worker_runtime_gate(token) + + async def test_metadata_waits_until_extension_mutation_finishes(self): + entered, release = threading.Event(), threading.Event() + + def operation(): + entered.set() + if not release.wait(3): + raise AssertionError("test did not release extension mutation") + return {"ok": True} + + with patch.object(self.server, "_field_action", new_callable=AsyncMock, return_value="done") as dispatch: + mutation = asyncio.create_task(self.server._extension_mutation(operation)) + action = None + try: + self.assertTrue(await asyncio.to_thread(entered.wait, 1)) + action = asyncio.create_task(self.server.field_action(self.request())) + await asyncio.sleep(0.02) + dispatch.assert_not_awaited() + finally: + release.set() + self.assertEqual((await mutation).status, 200) + if action is not None: + self.assertEqual(await action, "done") + if self.server._model_executor is not None: + self.server._model_executor.shutdown() + + def test_context_never_constructs_or_releases_an_executable_node(self): + from modules.ModularDiffusers import components + from modules.ModularDiffusers.loaders import ModelsLoader + from modiff.NodeBase import NodeBase + + with patch.object(ModelsLoader, "__init__", side_effect=AssertionError("model constructor")), patch.object( + NodeBase, "__init__", side_effect=AssertionError("node constructor"), + ), patch.object(components, "remove_from_collection") as remove: + callback = metadata_field_callback("ModelsLoader", "set_filters", node_id="same-model-owner", sid=None) + callback({"model_type": ""}, {"key": "model_type"}) + self.assertFalse(hasattr(callback.__self__, "execute")) + self.assertFalse(hasattr(callback.__self__, "__del__")) + del callback + remove.assert_not_called() + + +class ModularMetadataContractTests(unittest.TestCase): + @unittest.skipUnless(importlib.util.find_spec("transformers"), "requires the reviewed optional runtime") + def test_all_registered_model_schemas_match_existing_generic_contracts(self): + from modules.ModularDiffusers.modular_utils import ( + get_all_model_types, pipeline_class_from_model_type, require_modiff_node_contract, + ) + from modules.ModularDiffusers.field_metadata import _FieldContext + + checked = 0 + for model_type in get_all_model_types(): + if not model_type or model_type == "DummyCustomPipeline": + continue + for action, connector in ( + ("EncodePrompt", "text_encoders"), ("Denoise", "unet"), + ("DecodeLatents", "vae"), ("ImageEncode", "vae"), + ("ImageEmbeddings", "image_encoder"), + ): + with self.subTest(model_type=model_type, action=action): + callback = metadata_field_callback(action, "update_node", node_id="draft", sid="editing") + try: + pipeline = pipeline_class_from_model_type(model_type) + _, expected = require_modiff_node_contract(pipeline, callback.__self__.node_type, resolve_blocks=False) + except ValueError: + with patch.object(_FieldContext, "get_signal_value", return_value=model_type), patch.object( + _FieldContext, "send_node_definition", + ) as publish, self.assertRaises(ValueError): + callback({}, {}) + publish.assert_called_once_with({}) + continue + expected["params"].pop(connector, None) + with patch.object(_FieldContext, "get_signal_value", return_value=model_type), patch.object( + _FieldContext, "send_node_definition", + ) as publish: + callback({}, {}) + publish.assert_called_once_with(expected["params"]) + checked += 1 + self.assertGreater(checked, 0) diff --git a/tests/test_field_metadata_catalog.py b/tests/test_field_metadata_catalog.py new file mode 100644 index 00000000..bd398b8a --- /dev/null +++ b/tests/test_field_metadata_catalog.py @@ -0,0 +1,155 @@ +"""Presentation metadata for every declared ordinary Diffusers task contract.""" +from importlib import import_module +from unittest.mock import Mock, patch + +import pytest + +from modiff.field_metadata import METADATA_ACTIONS, metadata_field_callback, is_metadata_field_action +from modiff.field_metadata_context import FieldMessageContext + + +def callback(module, action, method, values): + node_class = getattr(import_module(module + ".main"), action) + with patch.object(node_class, "__init__", side_effect=AssertionError("executable constructor")): + fn = metadata_field_callback(action, method, module=module, node_id="draft", sid="session") + assert not hasattr(fn.__self__, "execute") + assert not hasattr(fn.__self__, "__del__") + fn.__self__.set_field_params = Mock() + fn.__self__.set_field_value = Mock() + fn(values, {"key": "pipeline_class"}) + return fn.__self__ + + +def test_all_image_models_and_declared_actions_use_isolated_fields(subtests): + from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS, image_pipeline_contract + for name, adapter in IMAGE_PIPELINE_ADAPTERS.items(): + for mode in adapter.modes: + with subtests.test(pipeline=name, mode=mode): + contract = image_pipeline_contract(adapter, mode) + owner = callback("modules.DiffusersImage", "LoadPipeline", "update_pipeline_contract", + {"pipeline_class": name, "mode": mode}) + signals = [call.args[1]["signal"]["value"] for call in owner.set_field_params.call_args_list + if call.args[0] == "pipeline"] + assert signals == [contract] + for action, modes in contract["actions"].items(): + if mode in modes and action in METADATA_ACTIONS["modules.DiffusersImage"]: + callback("modules.DiffusersImage", action, "update_image_contract", {"image_contract": contract}) + + +def test_all_audio_modes_use_isolated_fields(subtests): + from modules.DiffusersAudio.main import AUDIO_PIPELINE_ADAPTERS + for name, adapter in AUDIO_PIPELINE_ADAPTERS.items(): + for mode in adapter.modes: + with subtests.test(pipeline=name, mode=mode): + owner = callback("modules.DiffusersAudio", "LoadPipeline", "update_audio_contract", + {"pipeline_class": name, "mode": mode}) + signal = next(call.args[1]["signal"]["value"] for call in owner.set_field_params.call_args_list + if call.args[0] == "pipeline") + callback("modules.DiffusersAudio", "Generate", "update_audio_contract", {"audio_contract": signal}) + + +def test_all_video_modes_use_isolated_fields(subtests): + from modules.DiffusersVideo.main import VIDEO_PIPELINE_ADAPTERS + for name, adapter in VIDEO_PIPELINE_ADAPTERS.items(): + with subtests.test(pipeline=name): + owner = callback("modules.DiffusersVideo", "LoadPipeline", "select_adapter", {"pipeline_class": name}) + signal = next(call.args[1]["signal"]["value"] for call in owner.set_field_params.call_args_list + if call.args[0] == "pipeline") + for action in ("Generate", "GenerateVideoAudio", "GenerateLTX2", "GenerateSequence"): + for mode in adapter.modes: + callback("modules.DiffusersVideo", action, "update_adapter_modes", {"video_contract": signal, "mode": mode}) + + +def test_all_rendered_3d_models_use_isolated_fields(subtests): + from modules.DiffusersThreeD.main import THREE_D_PIPELINE_ADAPTERS + for name, adapter in THREE_D_PIPELINE_ADAPTERS.items(): + with subtests.test(pipeline=name): + owner = callback("modules.DiffusersThreeD", "LoadPipeline", "update_three_d_contract", + {"pipeline_class": name, "mode": adapter.mode}) + signal = next(call.args[1]["signal"]["value"] for call in owner.set_field_params.call_args_list + if call.args[0] == "pipeline") + callback("modules.DiffusersThreeD", "GenerateRenderedArtifact", "update_three_d_contract", {"three_d_contract": signal}) + + +@pytest.mark.parametrize("module,action,method", [ + (module, action, method) for module, actions in METADATA_ACTIONS.items() + if module != "modules.ModularDiffusers" + for action, methods in actions.items() for method in methods +]) +def test_each_ordinary_context_copies_declarations_without_inheriting_executable_nodes(module, action, method): + fn = metadata_field_callback(action, method, module=module, node_id="n", sid=None) + original = getattr(import_module(module + ".main"), action) + assert isinstance(fn.__self__, FieldMessageContext) + assert not isinstance(fn.__self__, original) + assert fn.__self__.__class__.params == original.params + assert fn.__self__.__class__.params is not original.params + + +def test_builtin_field_action_audit_keeps_model_construction_on_execution_lease(): + from modules import MODULE_MAP + from modiff.server import WebServer + serialized = set() + for module, actions in MODULE_MAP.items(): + if not module.startswith("modules."): + continue + for action, definition in actions.items(): + for contract in definition.get("params", {}).values(): + for method in WebServer._declared_field_exec_actions(contract): + if not is_metadata_field_action({"module": module, "action": action, "fn": method}): + serialized.add((module, action, method)) + # Loading even empty model layers changes Accelerate construction state. + # It is an explicit button action, not passive field/schema discovery. + assert serialized == {("modules.ModularDiffusers", "QuantizationConfigNode", "update_skip_modules")} + +@pytest.mark.parametrize("action,method,values,signal", [ + ("AutoModelLoader", "set_filters", {"model_type": "transformer"}, None), + ("Scheduler", "updateNode", {"scheduler": "EulerDiscreteScheduler"}, "StableDiffusionXLModularPipeline"), + ("Guider", "updateNode", {"guider": "ClassifierFreeGuidance"}, "StableDiffusionXLModularPipeline"), + ("Layers", "set_blocks", {"blocks_select": []}, None), + ("IPAdapter", "update_node", {}, None), + ("Controlnet", "update_node", {"model_type": ""}, None), + ("DynamicBlockNode", "update_node", {"repo_id": ""}, None), +]) +def test_additional_modular_callbacks_have_no_model_ownership(action, method, values, signal): + fn = metadata_field_callback(action, method, node_id="running-owner", sid="editing") + context = fn.__self__ + assert not hasattr(context, "execute") + assert not hasattr(context, "__del__") + context.get_signal_value = Mock(return_value=signal) + for name in ("send_node_definition", "set_field_value", "set_field_params"): + setattr(context, name, Mock()) + fn(values, {}) + + +def test_legacy_custom_definition_keeps_request_workflow_ownership(): + from modiff.NodeBase import node_message_context + from modules.ModularDiffusers import dynamic_node + fn = metadata_field_callback("DynamicBlockNode", "update_node", node_id="shared-id", sid="editing") + server = Mock() + server.describe_node_params.side_effect = lambda params: params + with patch("modiff.NodeBase._server", return_value=server), patch.object(dynamic_node, "_server", return_value=server): + with node_message_context({"workflow_tab_id": "draft", "workflow_canvas_epoch": 3, "workflow_form_epoch": 7}): + fn.__self__.send_node_definition_with_meta({"prompt": {"type": "string"}}, label="Custom", header_color="orange") + message, sid = server.queue_message.call_args.args + assert sid == "editing" + assert message["workflow_tab_id"] == "draft" + assert message["workflow_canvas_epoch"] == 3 + assert message["workflow_form_epoch"] == 7 + assert message["label"] == "Custom" + assert message["style"] == {"headerColor": "orange"} + +@pytest.mark.parametrize("action,signal", [ + (action, signal) + for action in ("Denoise", "EncodePrompt", "DecodeLatents", "ImageEncode", "ImageEmbeddings", "IPAdapter") + for signal in (None, "") +] + [("Controlnet", "")]) +def test_disconnected_metadata_always_clears_previous_dynamic_schema(action, signal): + fn = metadata_field_callback(action, "update_node", node_id="draft", sid="editing") + fn.__self__.get_signal_value = Mock(return_value=signal) + fn.__self__.send_node_definition = Mock() + fn({"model_type": signal}, {}) + # A fresh metadata context cannot assume the browser already has its empty + # schema. The request may follow disconnecting a previously selected model. + fn.__self__.send_node_definition.assert_called_once() + params = fn.__self__.send_node_definition.call_args.args[0] + assert set(params) == ({"model_type", "controlnet_bundle"} if action == "Controlnet" else set()) diff --git a/tests/test_flux_latent_outputs.py b/tests/test_flux_latent_outputs.py index 6531248b..0acd2870 100644 --- a/tests/test_flux_latent_outputs.py +++ b/tests/test_flux_latent_outputs.py @@ -14,7 +14,7 @@ from modules.DiffusersImage.call_inputs import PIPELINE_CALL_INPUTS -FLUX_CLASSES = tuple(name for name in PIPELINE_CALL_INPUTS if name != 'FluxReduxPipeline') +FLUX_CLASSES = tuple(name for name in PIPELINE_CALL_INPUTS if name.startswith('Flux') and name != 'FluxReduxPipeline') def fixture_pipeline(name): diff --git a/tests/test_flux_modular_decode_matrix.py b/tests/test_flux_modular_decode_matrix.py index 314111c2..20b4d41b 100644 --- a/tests/test_flux_modular_decode_matrix.py +++ b/tests/test_flux_modular_decode_matrix.py @@ -78,7 +78,11 @@ def run(adapter): block_definition_id=contract["id"], block_class=contract["className"], block_contract_hash=contract["contentHash"], execution_kind="step", state_in=issued) issued = result["state_out"] - images = state.get("images") + if adapter: + # Continued execution forks mutable state; the input belongs to the + # preceding node and must remain unchanged for repeat/branch reuse. + assert state.get("images") is None + images = (issued._state if adapter else state).get("images") assert len(images) == 1 and images[0].size == (32, 32) return np.asarray(images[0]) diff --git a/tests/test_flux_public_call_coverage.py b/tests/test_flux_public_call_coverage.py index 36b9dca1..7b9b3f15 100644 --- a/tests/test_flux_public_call_coverage.py +++ b/tests/test_flux_public_call_coverage.py @@ -31,16 +31,17 @@ def test_all_pinned_flux_call_arguments_have_an_explicit_owner(): app_owned = {'return_dict', 'callback_on_step_end', 'callback_on_step_end_tensor_inputs'} primary = {'prompt', 'image', 'mask_image', 'control_image', 'num_inference_steps'} signatures = upstream_signatures() - assert len(PIPELINE_CALL_INPUTS) == 17 + flux_inputs = {name: fields for name, fields in PIPELINE_CALL_INPUTS.items() if name.startswith('Flux')} + assert len(flux_inputs) == 17 reviewed_exports = { 'FluxPriorReduxPipeline' if name == 'FluxReduxPipeline' else name - for name in PIPELINE_CALL_INPUTS + for name in flux_inputs } assert set(signatures) == reviewed_exports, ( f'Pinned FLUX exports changed: missing={sorted(set(signatures) - reviewed_exports)}, ' f'stale={sorted(reviewed_exports - set(signatures))}' ) - for name in PIPELINE_CALL_INPUTS: + for name in flux_inputs: upstream = 'FluxPriorReduxPipeline' if name == 'FluxReduxPipeline' else name adapter = IMAGE_PIPELINE_ADAPTERS[name] fields = set().union(*( diff --git a/tests/test_huggingface_cluster_admission.py b/tests/test_huggingface_cluster_admission.py index 40960b30..db78c8dd 100644 --- a/tests/test_huggingface_cluster_admission.py +++ b/tests/test_huggingface_cluster_admission.py @@ -512,14 +512,39 @@ def test_cosmos3_nano_non_action_routes_seal_guardrails_and_the_official_state_g self.assertEqual(result["sealedBindingValues"][frame_source], frame_value) self.assertNotIn(frame_source, result["executionParameterSources"]) + def test_ernie_uses_the_exact_native_prompt_text_denoise_decode_route(self): + result = self.workflow_result( + "ErnieImageModularPipeline", + "text2image", + "official_top_level_blocks", + ) + self.assertEqual(result["status"], "admitted") + self.assertEqual(result["reasons"], []) + self.assertEqual(result["studioMode"], "text_to_image") + self.assertEqual(result["artifact"]["repo"], "baidu/ERNIE-Image-Turbo") + self.assertEqual( + result["studioExecutionSpec"]["executionProfileId"], + "ernie-image-turbo:official-modular-workflow", + ) + specification = STUDIO_EXECUTION_SPEC_DEFINITIONS[result["studioExecutionSpec"]["id"]] + self.assertEqual( + [(role, node_key) for role, node_key, _x, _y in specification["roles"]], + [ + ("models", "modules.ModularDiffusers.ModelsLoader"), + ("promptEnhance", "modules.ModularDiffusers.WorkflowErniePromptEnhance"), + ("prompt", "modules.ModularDiffusers.WorkflowErnieTextEncode"), + ("denoise", "modules.ModularDiffusers.WorkflowErnieImageDenoise"), + ("decode", "modules.ModularDiffusers.WorkflowErnieDecodeImage"), + ("preview", "modules.Image.Preview"), + ], + ) + self.assertIn(("promptEnhance", "state_out", "prompt", "state_in"), specification["edges"]) + self.assertIn(("prompt", "state_out", "denoise", "state_in"), specification["edges"]) + self.assertIn(("denoise", "state_out", "decode", "state_in"), specification["edges"]) + self.assertIs(result["executable"], False) + def test_equivalent_standard_routes_keep_modular_structure_but_seal_full_pipeline_execution(self): expected = { - ("ErnieImageModularPipeline", "text2image"): ( - "text_to_image", - "ErnieImagePipeline", - "modules.DiffusersImage.LoadPipeline", - "baidu/ERNIE-Image-Turbo", - ), ("LTXModularPipeline", "text2video"): ( "text_to_video", "LTXConditionPipeline", diff --git a/tests/test_huggingface_cluster_promotion_staging.py b/tests/test_huggingface_cluster_promotion_staging.py index e11c20d2..ad93db93 100644 --- a/tests/test_huggingface_cluster_promotion_staging.py +++ b/tests/test_huggingface_cluster_promotion_staging.py @@ -82,7 +82,7 @@ def fixture_files(self, root): { "definitionId": self.qwen_definition["id"], "definitionContentHash": ( - "sha256:98dddb4c03eea94a0a8e9b1d318ecf5aab9fe62113b829263d8d605772ed6033" + "sha256:04426a29429af2b7cfbe65adce9d1d67f342fbbbf5e7397a3ce7dc0ec94378ef" ), "admissionId": QWEN, "previousCompiledDefinitionContentHash": current_pin[0], diff --git a/tests/test_huggingface_cluster_promotions.py b/tests/test_huggingface_cluster_promotions.py index 614a3056..d380facd 100644 --- a/tests/test_huggingface_cluster_promotions.py +++ b/tests/test_huggingface_cluster_promotions.py @@ -123,7 +123,7 @@ def qwen_post_promotion_identity(): ), }, "post_promotion_manifest_content_hash": ( - "sha256:98dddb4c03eea94a0a8e9b1d318ecf5aab9fe62113b829263d8d605772ed6033" + "sha256:04426a29429af2b7cfbe65adce9d1d67f342fbbbf5e7397a3ce7dc0ec94378ef" ), } @@ -333,12 +333,12 @@ def route(library): self.assertFalse(current_admission["publication"]["liveProof"]) self.assertEqual( current_definition["contentHash"], - "sha256:9cbb38204acb409888b94e880a6e343e9bd67ea5703455560bc5c6d713437f68", + "sha256:49cb50671b41cf2c03b22387cfe2cf1b37f16b2996961791505aef830817c393", ) self.assertTrue(candidate_admission["publication"]["liveProof"]) self.assertEqual( candidate_definition["contentHash"], - "sha256:98dddb4c03eea94a0a8e9b1d318ecf5aab9fe62113b829263d8d605772ed6033", + "sha256:04426a29429af2b7cfbe65adce9d1d67f342fbbbf5e7397a3ce7dc0ec94378ef", ) self.assertEqual(reviewed_cluster_promotion_receipts()["receipts"], []) diff --git a/tests/test_huggingface_cluster_runtime.py b/tests/test_huggingface_cluster_runtime.py index f7e59dfa..145d375b 100644 --- a/tests/test_huggingface_cluster_runtime.py +++ b/tests/test_huggingface_cluster_runtime.py @@ -724,7 +724,7 @@ def test_qwen_same_family_expert_receipt_uses_the_selected_exact_artifact(self): }, ) - def test_equivalent_standard_diffusers_receipt_keeps_modular_identity_and_direct_executor(self): + def test_ernie_native_receipt_keeps_modular_stage_executor(self): snapshot = self.cache_dir / "models--baidu--ERNIE-Image-Turbo" / "snapshots" / ERNIE_REVISION snapshot.mkdir(parents=True) (snapshot / "model.safetensors").write_bytes(b"reviewed-ernie-weight-fixture") @@ -734,7 +734,7 @@ def test_equivalent_standard_diffusers_receipt_keeps_modular_identity_and_direct "definitionId": "diffusers.modular:ErnieImageModularPipeline:text2image", "admissionId": ( "diffusers.cluster-admission:ErnieImageModularPipeline:text2image:" - "mode:equivalent_standard_route" + "workflow:official_top_level_blocks" ), "resourceMode": "expert", "recipe": { @@ -766,10 +766,10 @@ def test_equivalent_standard_diffusers_receipt_keeps_modular_identity_and_direct ) self.assertEqual(receipt["modelType"], "ErnieImageModularPipeline") - self.assertEqual(receipt["pipelineClass"], "ErnieImagePipeline") - self.assertEqual(receipt["loaderModule"], "modules.DiffusersImage") - self.assertEqual(receipt["loaderAction"], "LoadPipeline") - self.assertEqual(receipt["executionPath"], "direct-diffusers-image") + self.assertEqual(receipt["pipelineClass"], "ErnieImageModularPipeline") + self.assertEqual(receipt["loaderModule"], "modules.ModularDiffusers") + self.assertEqual(receipt["loaderAction"], "ModelsLoader") + self.assertEqual(receipt["executionPath"], "modular-diffusers") self.assertEqual(receipt["artifactStatus"]["revision"], ERNIE_REVISION) self.assertTrue(receipt["artifactStatus"]["exactRevisionComplete"]) self.assertFalse(receipt["publicationExecutable"]) diff --git a/tests/test_huggingface_node_library.py b/tests/test_huggingface_node_library.py index 9c128188..6b434a41 100644 --- a/tests/test_huggingface_node_library.py +++ b/tests/test_huggingface_node_library.py @@ -55,7 +55,7 @@ def test_cached_sound_generators_keep_exact_native_recipe_contracts(self): def test_current_snapshot_builds_immutable_first_party_definitions(self): self.assertEqual(self.library["schemaVersion"], 6) self.assertEqual(len(self.library["definitions"]), 135) - self.assertEqual(len(self.library["blockDefinitions"]), 559) + self.assertEqual(len(self.library["blockDefinitions"]), 560) self.assertEqual(self.library["providers"], ["diffusers", "transformers"]) diffusers_definitions = [item for item in self.library["definitions"] if item["provider"] == "diffusers"] transformers_definitions = [item for item in self.library["definitions"] if item["provider"] == "transformers"] @@ -504,7 +504,7 @@ def test_statically_closed_contract_only_workflows_have_exact_reusable_block_rol adapters = self.library["blockRoleAdapters"] adapters_by_block = {adapter["blockDefinitionId"]: adapter for adapter in adapters} - self.assertEqual(len(adapters), 321) + self.assertEqual(len(adapters), 322) self.assertEqual( {adapter["role"] for adapter in adapters}, { @@ -548,7 +548,7 @@ def test_statically_closed_contract_only_workflows_have_exact_reusable_block_rol for definition in self.library["definitions"] if definition["provider"] == "diffusers" and definition["integrationStatus"] == "equivalent_standard_route" ] - self.assertEqual(len(equivalent_definitions), 9) + self.assertEqual(len(equivalent_definitions), 8) for definition in equivalent_definitions: referenced = { definition["rootBlockDefinitionId"], diff --git a/tests/test_huggingface_node_library_api.py b/tests/test_huggingface_node_library_api.py index 7b7f574a..5518b9d4 100644 --- a/tests/test_huggingface_node_library_api.py +++ b/tests/test_huggingface_node_library_api.py @@ -31,8 +31,8 @@ async def test_read_only_endpoint_publishes_detached_reviewed_library(self): self.assertEqual(second["schemaVersion"], 6) self.assertEqual(second["providers"], ["diffusers", "transformers"]) self.assertEqual(len(second["definitions"]), 135) - self.assertEqual(len(second["blockDefinitions"]), 559) - self.assertEqual(len(second["blockRoleAdapters"]), 321) + self.assertEqual(len(second["blockDefinitions"]), 560) + self.assertEqual(len(second["blockRoleAdapters"]), 322) self.assertEqual(len(second["containerStateAdapters"]), 9) self.assertNotEqual(second["definitions"][0]["label"], "Changed by caller") self.assertTrue(all(definition["ownership"] == "library" for definition in second["definitions"])) @@ -44,6 +44,40 @@ async def test_read_only_endpoint_publishes_detached_reviewed_library(self): self.assertEqual(sum(admission["status"] == "admitted" for admission in admissions), 122) self.assertTrue(all(admission["executable"] is False for admission in admissions)) + async def test_registered_interfaces_match_every_compiled_boundary_and_are_detached(self): + from modiff.registered_block_v2_catalog import registered_block_v2_catalog_entry + + client = TestClient(TestServer(WebServer({}).app)) + await client.start_server() + try: + response = await client.get("/huggingface/registered-block-interfaces") + self.assertEqual(response.status, 200) + payload = await response.json() + self.assertEqual(payload["schemaVersion"], 1) + self.assertFalse(payload["error"]) + self.assertGreater(len(payload["entries"]), 100) + for row in payload["entries"]: + compiled = registered_block_v2_catalog_entry(row["catalogDefinitionId"], row["admissionId"]) + self.assertEqual(row["compiledDefinitionContentHash"], compiled["definition"]["contentHash"]) + self.assertEqual(row["compiledDefinitionCanonicalSha256"], compiled["compiledDefinitionCanonicalSha256"]) + self.assertEqual(row["catalogDefinitionContentHash"], compiled["catalogDefinitionContentHash"]) + for direction in ("inputs", "outputs"): + self.assertEqual(row[direction], [ + {"portId": port["portId"], "valueType": port["valueType"]} + for port in compiled["definition"]["boundary"][direction] + ]) + self.assertNotIn("graph", row) + self.assertNotIn("values", row) + payload["entries"][0]["inputs"].clear() + repeated = await (await client.get("/huggingface/registered-block-interfaces")).json() + self.assertNotEqual(repeated["entries"][0]["inputs"], []) + with patch("modiff.server.registered_block_v2_interfaces", side_effect=ValueError("Catalog is invalid")): + failed = await client.get("/huggingface/registered-block-interfaces") + self.assertEqual(failed.status, 500) + self.assertTrue((await failed.json())["error"]) + finally: + await client.close() + async def test_registered_block_v2_endpoint_serves_one_detached_hash_pinned_definition(self): server = WebServer({}) client = TestClient(TestServer(server.app)) diff --git a/tests/test_huggingface_transformers_registry.py b/tests/test_huggingface_transformers_registry.py index 88a18cd5..7e4e4b3b 100644 --- a/tests/test_huggingface_transformers_registry.py +++ b/tests/test_huggingface_transformers_registry.py @@ -221,6 +221,8 @@ def test_registry_discovers_exact_generic_actions_and_import_is_lazy(self): "GenerateImageVideoText", "LoadAnyToAnyModel", "GenerateAnyToAny", + "LoadDepthEstimationModel", + "PredictDepth", }, ) source = inspect.getsource(__import__("modules.HuggingFaceTransformers.main", fromlist=["*"])) diff --git a/tests/test_hunyuan_dit_artifact_review.py b/tests/test_hunyuan_dit_artifact_review.py index bd97c3a2..209a2686 100644 --- a/tests/test_hunyuan_dit_artifact_review.py +++ b/tests/test_hunyuan_dit_artifact_review.py @@ -43,13 +43,19 @@ def test_exact_safe_base_inventory_is_cataloged_for_the_standalone_pipeline(self self.assertEqual(pin["revision"], repository["revision"]) self.assertEqual(pin["license"], "tencent-hunyuan-community") - def test_bounded_standalone_adapter_and_execution_spec_share_one_exact_contract(self): + def test_current_adapter_preserves_reviewed_default_with_bounded_explicit_sizes(self): adapter = IMAGE_PIPELINE_ADAPTERS["HunyuanDiTPipeline"] contract = self.review["pipelineContract"] self.assertEqual(adapter.modes, frozenset({"text_to_image"})) self.assertTrue(adapter.safe_serialization_required) self.assertEqual(adapter.max_inference_steps, contract["numInferenceSteps"]) - self.assertEqual((adapter.min_output_side, adapter.max_output_side), (1024, 1024)) + self.assertEqual((adapter.min_output_side, adapter.max_output_side), (512, 2048)) + self.assertEqual(adapter.output_side_step, 32) + self.assertEqual(adapter.max_output_pixels, contract["width"] * contract["height"]) + # Preserve the historical upstream review; current explicit dimensions + # opt out of its default binning without changing the 1024-square recipe. + self.assertTrue(contract["useResolutionBinning"]) + self.assertIs(adapter.use_resolution_binning, False) self.assertEqual(adapter.max_sequence_length, contract["t5TokenLimit"]) definition = STUDIO_EXECUTION_SPEC_DEFINITIONS["hunyuan-dit-v1-2-distilled:text-to-image:v1"] diff --git a/tests/test_image_auxiliary_operation_starters.py b/tests/test_image_auxiliary_operation_starters.py new file mode 100644 index 00000000..529ae612 --- /dev/null +++ b/tests/test_image_auxiliary_operation_starters.py @@ -0,0 +1,65 @@ +"""Non-diffusion image tasks use their real existing actions and typed wires.""" +from copy import deepcopy +from unittest.mock import patch + +import pytest + +from modules import MODULE_MAP +from modiff.diffusers_profiles import public_execution_profiles +from modiff.operation_catalog import build_operation_catalog +from modiff.operation_starters import resolve_operation_starter + + +@pytest.fixture(scope="module") +def catalog(): + return build_operation_catalog(MODULE_MAP, public_execution_profiles(), catalog_resolver=lambda: {})[0] + + +@pytest.mark.parametrize("pipeline,task,profile,mode,dtype", [ + ("AutoModelForImageTextToText", "image_to_text", "smolvlm-256m-instruct:direct", None, "float32"), + ("JanusForConditionalGeneration", "image_to_text", "janus-pro-1b:direct", "text", "bfloat16"), + ("JanusForConditionalGeneration", "text_to_image", "janus-pro-1b:direct", "image", "bfloat16"), +]) +def test_caption_and_image_starters_preserve_real_model_handles(catalog, pipeline, task, profile, mode, dtype): + original = deepcopy(MODULE_MAP["modules.HuggingFaceTransformers"]) + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed")): + draft = resolve_operation_starter(MODULE_MAP, catalog, { + "pipelineClass": pipeline, "task": task, "executionProfileId": profile, + }) + loader, generate = draft["nodes"] + assert loader["params"]["dtype"]["value"] == dtype + assert len(loader["params"]["revision"]["value"]) == 40 + assert draft["edges"] == [{"source": "model.load", "sourceHandle": "model", "target": generate["operation"]["operationId"], "targetHandle": "model"}] + assert draft["upstreamBlocks"] == [] + assert generate["params"]["images"]["required"] == (task == "image_to_text") + if mode: + assert generate["values"]["generation_mode"] == mode + assert generate["params"]["image"]["hidden"] == (mode != "image") + if mode == "image": + assert generate["values"]["do_sample"] is True + assert MODULE_MAP["modules.HuggingFaceTransformers"] == original + + +@pytest.mark.parametrize("task", ["image_adjustment", "image_filter", "image_crop", "image_upscale", "image_stitch", "image_tile", "image_channels", "mask_composite"]) +def test_builtin_starters_have_no_fictitious_weights_or_diffusion_stages(catalog, task): + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed")): + draft = resolve_operation_starter(MODULE_MAP, catalog, { + "pipelineClass": "BuiltinImageOperationV1", "task": task, + "executionProfileId": "builtin-image-operations:direct", + }) + assert len(draft["nodes"]) == 1 + node = draft["nodes"][0] + assert node["values"] == {"pipeline_class": "BuiltinImageOperationV1", "operation": task} + assert node["params"]["width"]["hidden"] == (task != "image_crop") + assert node["params"]["seed"]["hidden"] == (task != "image_filter") + required = {item["field"] for item in draft["requiredInputs"]} + assert required == ({"image", "mask"} if task == "mask_composite" else {"image"}) + assert draft["edges"] == [] and draft["upstreamBlocks"] == [] + + +def test_image_text_binding_rejects_unrelated_profile(catalog): + with pytest.raises(ValueError): + resolve_operation_starter(MODULE_MAP, catalog, { + "pipelineClass": "AutoModelForImageTextToText", "task": "image_to_text", + "executionProfileId": "janus-pro-1b:direct", + }) diff --git a/tests/test_image_download_inventories.py b/tests/test_image_download_inventories.py new file mode 100644 index 00000000..b3ab21a9 --- /dev/null +++ b/tests/test_image_download_inventories.py @@ -0,0 +1,56 @@ +from copy import deepcopy + +import pytest + +from modiff import model_artifact_catalog as catalog +from modiff.studio_execution_specs import reviewed_repository_download_files, studio_capability_definitions + + +def test_klein_kv_uses_its_exact_9b_sharded_layout_not_the_4b_selection(): + capability = studio_capability_definitions()['Flux2KleinKVPipeline'] + files = capability['downloadFiles'] + assert 'LICENSE' in files and 'LICENSE.md' not in files + assert 'transformer/diffusion_pytorch_model.safetensors' not in files + assert 'transformer/diffusion_pytorch_model.safetensors.index.json' in files + assert {name for name in files if name.startswith('text_encoder/model-')} == { + f'text_encoder/model-{index:05d}-of-00004.safetensors' for index in range(1, 5) + } + assert {name for name in files if name.startswith('transformer/diffusion_pytorch_model-')} == { + f'transformer/diffusion_pytorch_model-{index:05d}-of-00002.safetensors' for index in range(1, 3) + } + repo = capability['defaultRepo'] + inventory = catalog.catalog_download_inventory(repo, catalog.catalog_revision(repo), files) + assert inventory and inventory['exactBytes'] > 30_000_000_000 + + +def test_alternate_qwen_download_layouts_remain_distinct_and_revision_bound(): + selections = {repo: {'repo': repo, 'revision': catalog.catalog_revision(repo), + 'downloadFiles': reviewed_repository_download_files(repo)} + for repo in ('Qwen/Qwen-Image', 'unsloth/Qwen-Image-2512-unsloth-bnb-4bit')} + original = selections['Qwen/Qwen-Image'] + bnb = selections['unsloth/Qwen-Image-2512-unsloth-bnb-4bit'] + assert 'tokenizer/chat_template.jinja' not in original['downloadFiles'] + assert 'LICENSE' in original['downloadFiles'] + assert 'text_encoder/model-00001-of-00002.safetensors' in bnb['downloadFiles'] + assert 'transformer/diffusion_pytorch_model-00003-of-00003.safetensors' in bnb['downloadFiles'] + for selection in (original, bnb): + assert catalog.catalog_download_inventory(selection['repo'], selection['revision'], selection['downloadFiles']) + # Single root checkpoints are not falsely offered as full Diffusers layouts. + for repo in ('black-forest-labs/FLUX.1-dev-FP8', 'black-forest-labs/FLUX.1-Kontext-dev-NVFP4'): + assert reviewed_repository_download_files(repo) == [] + + +def test_download_byte_inventory_invalidates_on_changed_selection_or_revision(monkeypatch): + row = deepcopy(catalog.read_model_artifact_catalog()['downloadInventories'][0]) + files = [item['path'] for item in row['files']] + monkeypatch.setattr(catalog, 'read_model_artifact_catalog', lambda: {'downloadInventories': [row]}) + assert catalog.catalog_download_inventory(row['repo'], row['revision'], files)['exactBytes'] > 0 + assert catalog.catalog_download_inventory(row['repo'], '0' * 40, files) is None + assert catalog.catalog_download_inventory(row['repo'], row['revision'], files + ['extra.bin']) is None + row['files'][0]['byteSize'] = True + with pytest.raises(ValueError, match='Invalid download file'): + catalog.catalog_download_inventory(row['repo'], row['revision'], files) + row['files'][0]['byteSize'] = 1 + row['files'].append(deepcopy(row['files'][0])) + with pytest.raises(ValueError, match='Invalid download file'): + catalog.catalog_download_inventory(row['repo'], row['revision'], files) diff --git a/tests/test_image_explicit_dimensions.py b/tests/test_image_explicit_dimensions.py new file mode 100644 index 00000000..965b9d90 --- /dev/null +++ b/tests/test_image_explicit_dimensions.py @@ -0,0 +1,79 @@ +"""Keep spatial defaults separate from the generic image action's valid range.""" + +import importlib.util +import inspect +from types import SimpleNamespace + +import pytest + +from modules.DiffusersImage.main import ( + _tag_image_pipeline, + catalog_revision, + get_image_pipeline_adapter, + image_pipeline_contract, + preflight_image_action, +) + + +@pytest.mark.parametrize( + "name,step", + [ + ("Kandinsky3Pipeline", 64), + ("Kandinsky3Img2ImgPipeline", 64), + ("ErnieImagePipeline", 32), + ("GlmImagePipeline", 32), + ], +) +def test_spatial_contract_allows_non_square_sizes_without_raising_pixel_ceiling(name, step): + adapter = get_image_pipeline_adapter(name) + contract = image_pipeline_contract(adapter, adapter.mode_options[0]) + for key in ("width", "height"): + params = contract["fieldParams"][key] + assert (params["min"], params["max"], params["step"]) == (512, 2048, step) + assert contract["maxOutputPixels"] == 1024 * 1024 + + +@pytest.mark.parametrize("name", ["Kandinsky3Pipeline", "ErnieImagePipeline", "GlmImagePipeline"]) +def test_text_to_image_preflight_preserves_requested_size_and_rejects_oversized_or_unaligned(name): + adapter = get_image_pipeline_adapter(name) + pipeline = type(name, (), {})() + _tag_image_pipeline( + pipeline, adapter, "text_to_image", adapter.default_repo, "hub", catalog_revision(adapter.default_repo) + ) + values = { + "width": 1152, + "height": 896, + "num_inference_steps": adapter.max_inference_steps, + "guidance_scale": adapter.fixed_guidance_scale or 1.5, + } + _, accepted = preflight_image_action(pipeline, "Generate", values) + assert (accepted["width"], accepted["height"]) == (1152, 896) + for size, message in [((1152, 1024), "cannot exceed"), ((1153, 896), "increments"), ((480, 896), "between")]: + with pytest.raises(ValueError, match=message): + preflight_image_action(pipeline, "Generate", {**values, "width": size[0], "height": size[1]}) + + +@pytest.mark.skipif(importlib.util.find_spec("transformers") is None, reason="requires reviewed optional runtime") +@pytest.mark.parametrize("name", ["Kandinsky3Pipeline", "ErnieImagePipeline", "GlmImagePipeline"]) +def test_pinned_upstream_accepts_explicit_dimensions(name): + import diffusers + + factory = getattr(diffusers, name) + parameters = inspect.signature(factory.__call__).parameters + for dimension in ("width", "height"): + assert parameters[dimension].default == (None if name == "GlmImagePipeline" else 1024) + assert parameters[dimension].kind is inspect.Parameter.POSITIONAL_OR_KEYWORD + + if name == "Kandinsky3Pipeline": + from diffusers.pipelines.kandinsky3.pipeline_kandinsky3 import downscale_height_and_width + + latent_height, latent_width = downscale_height_and_width(896, 1152) + assert (latent_height * 8, latent_width * 8) == (896, 1152) + elif name == "GlmImagePipeline": + # Invoke the installed library's validation without loading any weights. + pipeline = SimpleNamespace( + vae_scale_factor=8, transformer=SimpleNamespace(config=SimpleNamespace(patch_size=2)) + ) + factory.check_inputs(pipeline, "fixture", 896, 1152, None) + with pytest.raises(ValueError, match="divisible"): + factory.check_inputs(pipeline, "fixture", 897, 1152, None) diff --git a/tests/test_image_operation_step_bounds.py b/tests/test_image_operation_step_bounds.py new file mode 100644 index 00000000..5d3fd116 --- /dev/null +++ b/tests/test_image_operation_step_bounds.py @@ -0,0 +1,56 @@ +"""Ordinary image forms must publish the step limits enforced at execution.""" +from copy import deepcopy +from unittest.mock import Mock + +import pytest + +from modules.DiffusersImage.main import ( + Generate, IMAGE_PIPELINE_ADAPTERS, IMAGE_ACTION_MODES, + image_pipeline_contract, image_action_field_params, +) + + +def test_prx_form_does_not_offer_steps_its_executor_rejects(): + adapter = IMAGE_PIPELINE_ADAPTERS["PRXPipeline"] + fields = image_action_field_params(image_pipeline_contract(adapter, "text_to_image"), "Generate") + assert fields["num_inference_steps"]["min"] == 1 + assert fields["num_inference_steps"]["max"] == adapter.max_inference_steps == 28 + + +@pytest.mark.parametrize("pipeline", sorted(IMAGE_PIPELINE_ADAPTERS)) +def test_prompt_conditioned_actions_publish_the_selected_adapter_bound(pipeline): + adapter = IMAGE_PIPELINE_ADAPTERS[pipeline] + for mode in adapter.mode_options: + contract = image_pipeline_contract(adapter, mode) + for action, modes in IMAGE_ACTION_MODES.items(): + if mode not in modes or action in {"UnconditionalGenerate", "PredictMap"}: + continue + original = deepcopy(contract) + field = image_action_field_params(contract, action)["num_inference_steps"] + assert field == {"min": 1, "max": adapter.max_inference_steps} + assert contract == original + + +def test_dynamic_model_switch_refreshes_bound_without_rewriting_authored_steps(): + node = object.__new__(Generate) + node.class_name = "Generate" + node.set_field_params = Mock() + for pipeline in ("PRXPipeline", "FluxPipeline", "PRXPipeline"): + adapter = IMAGE_PIPELINE_ADAPTERS[pipeline] + node.set_field_params.reset_mock() + Generate.update_image_contract(node, { + "image_contract": image_pipeline_contract(adapter, "text_to_image"), + "num_inference_steps": 17, + }, None) + node.set_field_params.assert_any_call("num_inference_steps", {"min": 1, "max": adapter.max_inference_steps}) + for args, _ in node.set_field_params.call_args_list: + assert "value" not in args[1] + + +def test_unconditional_and_perception_keep_their_own_step_contracts(): + for pipeline, mode, action in ( + ("DDPMPipeline", "unconditional_image", "UnconditionalGenerate"), + ("MarigoldDepthPipeline", "depth_estimation", "PredictMap"), + ): + fields = image_action_field_params(image_pipeline_contract(IMAGE_PIPELINE_ADAPTERS[pipeline], mode), action) + assert "num_inference_steps" not in fields diff --git a/tests/test_image_optional_call_inputs.py b/tests/test_image_optional_call_inputs.py index 3cf59bb8..0202da8c 100644 --- a/tests/test_image_optional_call_inputs.py +++ b/tests/test_image_optional_call_inputs.py @@ -77,13 +77,13 @@ def test_reviewed_optional_fields_exist_in_each_pinned_upstream_call(): from modules.DiffusersImage.call_inputs import PIPELINE_CALL_INPUTS root = Path(importlib.util.find_spec('diffusers').origin).parent / 'pipelines' signatures = {} - for path in root.glob('flux*/pipeline_flux*.py'): + for path in (*root.glob('flux*/pipeline_flux*.py'), *root.glob('qwenimage21/pipeline_*.py')): for node in ast.parse(path.read_text()).body: if isinstance(node, ast.ClassDef): for method in node.body: if isinstance(method, ast.FunctionDef) and method.name == '__call__': signatures[node.name] = {arg.arg for arg in method.args.args + method.args.kwonlyargs} - assert len(PIPELINE_CALL_INPUTS) == 17 + assert len(PIPELINE_CALL_INPUTS) == 18 for name, fields in PIPELINE_CALL_INPUTS.items(): upstream = 'FluxPriorReduxPipeline' if name == 'FluxReduxPipeline' else name assert set(fields) <= signatures[upstream], name diff --git a/tests/test_image_pipeline_snapshot_selection.py b/tests/test_image_pipeline_snapshot_selection.py new file mode 100644 index 00000000..5e998452 --- /dev/null +++ b/tests/test_image_pipeline_snapshot_selection.py @@ -0,0 +1,98 @@ +"""Image pipelines must load the installed runtime files, not unrelated Hub weights.""" + +import importlib.util +import inspect +from pathlib import Path +from unittest.mock import Mock, patch + +import pytest + +from modules.DiffusersImage.main import load_cached_image_component + +REPO = "example/image-pipeline" +REVISION = "a" * 40 + + +def pipeline_factory(): + return type("Pipeline", (), {"config_name": "model_index.json", "from_pretrained": Mock()}) + + +def test_pinned_pipeline_uses_exact_installed_directory_without_hub_tree_check(tmp_path): + factory = pipeline_factory() + snapshot = tmp_path / REVISION + kwargs = dict(revision=REVISION, local_files_only=True, use_safetensors=True, variant="fp16") + with patch("utils.huggingface.exact_cached_snapshot_path", return_value=snapshot) as resolve: + result = load_cached_image_component(factory, REPO, **kwargs) + resolve.assert_called_once_with(REPO, REVISION) + factory.from_pretrained.assert_called_once_with(snapshot, **kwargs) + assert result is factory.from_pretrained.return_value + + +@pytest.mark.parametrize( + "error", + [FileNotFoundError("Install the exact reviewed snapshot"), ValueError("Snapshot escaped its managed cache")], +) +def test_invalid_or_missing_snapshot_never_falls_back_to_hub_loading(error): + factory = pipeline_factory() + with patch("utils.huggingface.exact_cached_snapshot_path", side_effect=error): + with pytest.raises(type(error), match=str(error)): + load_cached_image_component(factory, REPO, revision=REVISION, local_files_only=True) + factory.from_pretrained.assert_not_called() + + +def test_missing_required_pipeline_component_still_fails_from_local_directory(): + factory = pipeline_factory() + factory.from_pretrained.side_effect = OSError("Required transformer shard is missing") + snapshot = Path("/reviewed/snapshots") / REVISION + with patch("utils.huggingface.exact_cached_snapshot_path", return_value=snapshot): + with pytest.raises(OSError, match="Required transformer shard"): + load_cached_image_component(factory, REPO, revision=REVISION, local_files_only=True) + assert factory.from_pretrained.call_args.args == (snapshot,) + + +def test_local_pipeline_and_individual_component_keep_their_existing_loader_paths(): + pipeline = pipeline_factory() + component = type("Component", (), {"config_name": "config.json", "from_pretrained": Mock()}) + with patch("utils.huggingface.exact_cached_snapshot_path") as resolve: + load_cached_image_component(pipeline, "/operator/local-pipeline", revision=None, local_files_only=True) + load_cached_image_component(component, REPO, revision=REVISION, local_files_only=True) + resolve.assert_not_called() + pipeline.from_pretrained.assert_called_once_with("/operator/local-pipeline", revision=None, local_files_only=True) + component.from_pretrained.assert_called_once_with(REPO, revision=REVISION, local_files_only=True) + + +def test_mutable_revision_cannot_bypass_exact_snapshot_validation(): + factory = pipeline_factory() + with pytest.raises(ValueError, match="40-character commit"): + load_cached_image_component(factory, REPO, revision="main", local_files_only=True) + factory.from_pretrained.assert_not_called() + + +@pytest.mark.skipif(importlib.util.find_spec("transformers") is None, reason="requires reviewed optional runtime") +@pytest.mark.parametrize( + "name", + [ + "OvisImagePipeline", + "LongCatImagePipeline", + "HunyuanDiTPipeline", + "HunyuanDiTPAGPipeline", + "HunyuanDiTControlNetPipeline", + ], +) +def test_pinned_upstream_classes_expose_reviewed_snapshot_and_call_contracts(name, tmp_path): + import diffusers + + factory = getattr(diffusers, name) + assert factory.config_name == "model_index.json" + signature = inspect.signature(factory.__call__).parameters + if name.startswith("HunyuanDiT"): + assert signature["use_resolution_binning"].default is True + if name == "LongCatImagePipeline": + assert "callback_on_step_end" not in signature + snapshot = tmp_path / REVISION + with ( + patch("utils.huggingface.exact_cached_snapshot_path", return_value=snapshot), + patch.object(factory, "from_pretrained") as load, + ): + load_cached_image_component(factory, REPO, revision=REVISION, local_files_only=True) + load.assert_called_once_with(snapshot, revision=REVISION, local_files_only=True) diff --git a/tests/test_image_prototyping_readiness.py b/tests/test_image_prototyping_readiness.py new file mode 100644 index 00000000..3b362922 --- /dev/null +++ b/tests/test_image_prototyping_readiness.py @@ -0,0 +1,180 @@ +import json +from copy import deepcopy +from pathlib import Path + +import pytest + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_image_readiness_ledger_reproduces_without_registry_or_network(monkeypatch): + from modiff.image_prototyping_readiness import ( + build_image_prototyping_readiness, + load_image_prototyping_readiness, + ) + + monkeypatch.setattr("socket.socket.connect", lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError())) + generated = build_image_prototyping_readiness(ROOT) + assert generated == load_image_prototyping_readiness() + assert generated["summary"]["routeCount"] == len(generated["routes"]) + assert generated["summary"]["denominatorFrozen"] is False + assert generated["coverageBoundary"]["clientTemplatesAndTaskChoosers"] == "requires_paired_client_gate" + + +@pytest.mark.parametrize("host", [("linux", "x86_64"), ("windows", "x86_64"), ("macos", "arm64")]) +def test_image_readiness_ledger_is_independent_of_host_runtime_target(monkeypatch, host): + from modiff import diffusers_profiles + from modiff.image_prototyping_readiness import ( + build_image_prototyping_readiness, + load_image_prototyping_readiness, + ) + + resolve_target = diffusers_profiles.optional_runtime_target + monkeypatch.setattr( + diffusers_profiles, + "optional_runtime_target", + lambda *, platform_name=None, machine=None: resolve_target( + platform_name=platform_name or host[0], machine=machine or host[1], + ), + ) + # Live publication must still follow the host, unlike the static inventory. + profile = diffusers_profiles.DIFFUSERS_EXECUTION_PROFILES["z-image:modular"] + assert profile.to_public_dict()["optional_runtime_profiles"] == list( + profile.optional_runtime_profile_ids_for_target(platform_name=host[0], machine=host[1]) + ) + generated = build_image_prototyping_readiness(ROOT) + expected = load_image_prototyping_readiness() + assert generated["sources"] == expected["sources"] + assert generated == expected + + +@pytest.mark.parametrize("target", [{"platform_name": "windows"}, {"machine": "arm64"}]) +def test_explicit_inventory_target_cannot_observe_installed_runtime(target): + from modiff.diffusers_profiles import public_execution_profiles + + with pytest.raises(ValueError, match="Runtime observations cannot use an explicit target"): + public_execution_profiles(observe_optional_runtime=True, **target) + + +def test_image_readiness_distinguishes_native_stages_exceptions_and_non_diffusion_tasks(): + from modiff.image_prototyping_readiness import load_image_prototyping_readiness + + ledger = load_image_prototyping_readiness() + by_id = {route["routeId"]: route for route in ledger["routes"]} + + qwen = by_id["qwen-image:modular/modular_text_to_image"] + assert qwen["disposition"] == "native_integrated" + assert qwen["upstream"]["fullStageChain"] is True + assert {"encode_prompt", "denoise", "decode_latents"}.issubset(qwen["upstream"]["stageRoles"]) + + glm = by_id["glm-image:direct/text_to_image"] + assert glm["disposition"] == "documented_whole_pipeline_exception" + assert glm["exception"]["checkedDiffusersRevision"] == ledger["diffusersRevision"] + + ernie = by_id["ernie-image-turbo:official-modular-workflow/text_to_image"] + assert ernie["disposition"] == "native_integrated" + assert ernie["upstream"]["fullStageChain"] is True + assert {"rewrite_prompt", "encode_prompt", "denoise", "decode_latents"}.issubset( + ernie["upstream"]["stageRoles"] + ) + + depth = next(route for route in ledger["routes"] if route["canonicalTask"] == "depth_estimation") + assert depth["disposition"] == "non_diffusion_image_task" + + +def test_image_readiness_rejects_tampering_even_with_valid_json(tmp_path): + from modiff.image_prototyping_readiness import ( + ImagePrototypingReadinessError, + load_image_prototyping_readiness, + ) + + value = deepcopy(load_image_prototyping_readiness()) + value["routes"][0]["disposition"] = "runnable" + path = tmp_path / "ledger.json" + path.write_text(json.dumps(value), encoding="utf-8") + with pytest.raises(ImagePrototypingReadinessError): + load_image_prototyping_readiness(path) + + +def test_generic_stage_routes_do_not_require_whole_workflow_adapters(): + from modiff.image_prototyping_readiness import load_image_prototyping_readiness + + routes = load_image_prototyping_readiness()["routes"] + for pipeline in ("FluxModularPipeline", "StableDiffusionXLModularPipeline"): + selected = [row for row in routes if row["implementation"]["pipelineClass"] == pipeline] + assert selected + assert all(row["disposition"] == "native_integrated" for row in selected) + + +def test_preserved_standard_alternative_is_not_an_upstream_exception_or_missing_native_route(): + from modiff.image_prototyping_readiness import load_image_prototyping_readiness + + routes = load_image_prototyping_readiness()["routes"] + direct = next(row for row in routes if row["routeId"] == "ernie-image-turbo:direct/text_to_image") + assert direct["disposition"] == "preserved_standard_alternative" + assert direct["integratedNativeAlternativeProfileIds"] == ["ernie-image-turbo:official-modular-workflow"] + assert direct["exception"] is None + assert direct["evidence"]["nativeOutput"] != "passed" + + +def test_missing_action_binding_cannot_be_called_integrated(monkeypatch): + from modiff import image_prototyping_readiness as readiness + + monkeypatch.delitem(readiness.MODULAR_ACTION_BINDINGS, "denoise") + rows = readiness.build_image_prototyping_readiness(ROOT)["routes"] + flux = [row for row in rows if row["implementation"]["pipelineClass"] == "FluxModularPipeline"] + assert flux and all(row["disposition"] == "native_integration_missing" for row in flux) + + +def test_exact_native_variant_recipes_do_not_inherit_family_output_qualification(): + from modiff.image_prototyping_readiness import load_image_prototyping_readiness + rows = {row["routeId"]: row for row in load_image_prototyping_readiness()["routes"]} + for identity in ("flux-schnell:direct/text_to_image", "flux-krea:direct/text_to_image"): + assert rows[identity]["disposition"] == "preserved_standard_alternative" + assert rows[identity]["upstream"]["candidateNativePipelineClass"] == "FluxModularPipeline" + assert rows[identity]["exception"] is None + assert rows[identity]["exactVariantReview"]["status"] == "reviewed" + assert rows[identity]["exactVariantReview"]["liveQualification"] == "not_established_by_review" + + +def test_exact_reviews_retain_quantized_artifact_blockers_and_true_kv_exception(): + from modiff.image_prototyping_readiness import load_image_prototyping_readiness + rows = {row["routeId"]: row for row in load_image_prototyping_readiness()["routes"]} + reviews = [row for row in rows.values() if row.get("exactVariantReview")] + assert len(reviews) == 17 # five PAG integrations plus twelve exact-variant reviews + assert not any(row["dispositionReason"] == "native_family_requires_exact_artifact_variant_task_review" + for row in rows.values()) + for row in reviews: + review = row["exactVariantReview"] + assert review["source"] and review["limitations"] and review["status"] == "reviewed" + if review["decision"] == "artifact_blocked": + assert row["disposition"] == "blocked" and row["exception"] is None + assert not row["integratedNativeAlternativeProfileIds"] + elif review["decision"] == "upstream_exception": + assert row["disposition"] == "documented_whole_pipeline_exception" + assert not row["integratedNativeAlternativeProfileIds"] + else: + assert row["disposition"] == "preserved_standard_alternative" + assert row["integratedNativeAlternativeProfileIds"] == [review["nativeProfileId"]] + + +def test_advertised_artifact_variants_are_distinct_required_rows(): + from modiff.image_prototyping_readiness import build_image_prototyping_readiness + rows = {row["routeId"]: row for row in build_image_prototyping_readiness(ROOT)["routes"]} + base = "flux-dev:modular/text_to_image" + alternate = rows[f"{base}@black-forest-labs/FLUX.1-dev-FP8"] + assert alternate["artifactVariantOf"] == base + assert alternate["artifact"]["repository"] != rows[base]["artifact"]["repository"] + assert alternate["artifact"]["revision"] + assert alternate["evidence"]["nativeOutput"] != "passed" + + +def test_workflow_scoped_native_variants_are_not_omitted_or_leaked_to_other_tasks(): + from modiff.image_prototyping_readiness import build_image_prototyping_readiness + rows = {row["routeId"]: row for row in build_image_prototyping_readiness(ROOT)["routes"]} + repository = "unsloth/Qwen-Image-2512-unsloth-bnb-4bit" + alternate = rows[f"qwen-image:modular/modular_text_to_image@{repository}"] + assert alternate["disposition"] == "native_integrated" + assert f"qwen-image:modular/modular_image_to_image@{repository}" not in rows + assert rows[f"qwen-image:t2i-direct/text_to_image@{repository}"]["disposition"] == "preserved_standard_alternative" diff --git a/tests/test_image_size_and_progress_contracts.py b/tests/test_image_size_and_progress_contracts.py new file mode 100644 index 00000000..a38e28cb --- /dev/null +++ b/tests/test_image_size_and_progress_contracts.py @@ -0,0 +1,90 @@ +"""Native-run regressions exercised without constructing models or a graph.""" + +from dataclasses import replace +from unittest.mock import Mock + +import pytest +from PIL import Image + +from modules.DiffusersImage.main import ( + _tag_image_pipeline, + add_progress_callback, + catalog_revision, + get_image_pipeline_adapter, + image_pipeline_contract, + preflight_image_action, +) + + +@pytest.mark.parametrize("name", ["HunyuanDiTPipeline", "HunyuanDiTPAGPipeline", "HunyuanDiTControlNetPipeline"]) +def test_explicit_size_is_not_fixed_or_silently_binned(name): + adapter = get_image_pipeline_adapter(name) + assert adapter.min_output_side <= 896 < 1152 <= adapter.max_output_side + assert adapter.max_output_pixels == 1024 * 1024 + contract = image_pipeline_contract(adapter, adapter.mode_options[0]) + for field in ("width", "height"): + assert {key: contract["fieldParams"][field][key] for key in ("min", "max", "step")} == { + "min": 512, + "max": 2048, + "step": 32, + } + assert contract["maxOutputPixels"] == 1024 * 1024 + + class Pipeline: + def __call__(self, *, width, height, use_resolution_binning=True): + pass + + values = {"width": 1152, "height": 896, "num_inference_steps": 25} + target = {} + adapter.apply_generation_parameters(Pipeline(), values, target) + assert target == {"width": 1152, "height": 896, "use_resolution_binning": False} + + +def test_pipeline_without_step_callback_has_indeterminate_progress(): + class Pipeline: + def __call__(self, *, prompt): + pass + + node = Mock() + pipeline = Pipeline() + call_kwargs = {"prompt": "still working"} + add_progress_callback(node, pipeline, call_kwargs, 50) + node.progress.assert_called_once_with(-1, phase="denoising", message="Generating image") + assert call_kwargs == {"prompt": "still working"} + assert not hasattr(pipeline, "_num_timesteps") + + +@pytest.mark.parametrize("name", ["HunyuanDiTPipeline", "HunyuanDiTPAGPipeline", "HunyuanDiTControlNetPipeline"]) +def test_explicit_size_keeps_backend_pixel_ceiling_and_alignment(name): + adapter = get_image_pipeline_adapter(name) + pipeline = type(name, (), {})() + mode = adapter.mode_options[0] + action = "ControlGenerate" if mode == "control_image" else "Generate" + _tag_image_pipeline( + pipeline, + adapter, + mode, + adapter.default_repo, + "hub", + catalog_revision(adapter.default_repo), + conditioning_repo=adapter.default_conditioning_repo, + conditioning_revision=catalog_revision(adapter.default_conditioning_repo) + if adapter.default_conditioning_repo + else None, + ) + values = {"width": 1152, "height": 896, "num_inference_steps": 25} + if mode == "control_image": + values["control_image"] = Image.new("RGB", (32, 32), "black") + _, accepted = preflight_image_action(pipeline, action, values) + assert (accepted["width"], accepted["height"]) == (1152, 896) + for size, message in [((1152, 1024), "cannot exceed"), ((1153, 896), "increments"), ((480, 896), "between")]: + with pytest.raises(ValueError, match=message): + preflight_image_action(pipeline, action, {**values, "width": size[0], "height": size[1]}) + + +def test_resolution_binning_policy_rejects_non_boolean_and_missing_upstream_contract(): + adapter = get_image_pipeline_adapter("HunyuanDiTPipeline") + with pytest.raises(ValueError, match="exact boolean"): + replace(adapter, use_resolution_binning=0) + with pytest.raises(ValueError, match="resolution-binning control"): + adapter.apply_generation_parameters(lambda: None, {"num_inference_steps": 25}, {}) diff --git a/tests/test_install_guidance.py b/tests/test_install_guidance.py index 701dd0eb..1b64be28 100644 --- a/tests/test_install_guidance.py +++ b/tests/test_install_guidance.py @@ -179,7 +179,7 @@ def test_executable_project_dependency_pins_the_reviewed_diffusers_commit(self): diffusers = next(item for item in project["project"]["dependencies"] if item.startswith("diffusers")) self.assertEqual( diffusers, - "diffusers @ git+https://github.com/huggingface/diffusers.git@2f7e0154a9db246e95c9ede43edba7db5b130805", + "diffusers @ git+https://github.com/huggingface/diffusers.git@fbf49e7f35857f76bc57b177e26f12b03687c668", ) self.assertNotIn("diffusers", project["tool"]["uv"].get("sources", {})) @@ -187,7 +187,7 @@ def test_direct_hugging_face_hub_import_has_compatible_declared_dependency(self) project = tomllib.loads((Path(__file__).parents[1] / "pyproject.toml").read_text(encoding="utf-8")) dependencies = project["project"]["dependencies"] - self.assertIn("huggingface-hub>=1.23.0,<2.0", dependencies) + self.assertIn("huggingface-hub>=1.31.0,<2.0", dependencies) def test_opencv_is_optional_at_runtime_but_available_to_media_tests(self): root = Path(__file__).parents[1] diff --git a/tests/test_install_source_bytes.py b/tests/test_install_source_bytes.py index 26c3c937..7251f94b 100644 --- a/tests/test_install_source_bytes.py +++ b/tests/test_install_source_bytes.py @@ -31,7 +31,7 @@ def test_reviewed_vcs_install_ignores_previously_cached_translated_wheels(tmp_pa assert "--reinstall-package" in command assert command[-1] == ( "diffusers @ git+https://github.com/huggingface/diffusers.git@" - "2f7e0154a9db246e95c9ede43edba7db5b130805" + "fbf49e7f35857f76bc57b177e26f12b03687c668" ) environment = run.call_args.kwargs["env"] assert environment["GIT_CONFIG_VALUE_" + str(int(environment["GIT_CONFIG_COUNT"]) - 2)] == "false" diff --git a/tests/test_integrated_operation_starters.py b/tests/test_integrated_operation_starters.py new file mode 100644 index 00000000..73d7e120 --- /dev/null +++ b/tests/test_integrated_operation_starters.py @@ -0,0 +1,66 @@ +"""An action that loads and computes must not disappear from Workflows.""" +from copy import deepcopy +from unittest.mock import patch + +import pytest + +from modules import MODULE_MAP +from modiff.diffusers_profiles import public_execution_profiles +from modiff.integrated_operation_contracts import get_integrated_operation_contracts +from modiff.operation_catalog import build_operation_catalog +from modiff.operation_starters import resolve_operation_starter +from modiff.upscaler_contracts import real_esrgan_x2_model_selection + + +SELECTION = { + "pipelineClass": "SpandrelImageUpscaleV1", + "task": "image_upscale", + "executionProfileId": "real-esrgan-x2-image-upscale:direct", +} + + +@pytest.fixture(scope="module") +def catalog(): + return build_operation_catalog(MODULE_MAP, public_execution_profiles(), catalog_resolver=lambda: {}) + + +def test_upscale_discovery_and_single_node_starter_do_not_load_models(catalog): + contracts, support = catalog + selected = next(p for p in support if p["pipelineClass"] == SELECTION["pipelineClass"]) + task = next(t for t in selected["tasks"] if t["task"] == "image_upscale") + assert task["execution"] == "adapter" + assert task["decomposition"] == "pipeline" + assert task["operationIds"] == ["image.upscale"] + assert task["executionProfileIds"] == [SELECTION["executionProfileId"]] + original = deepcopy(MODULE_MAP["modules.Spandrel"]) + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed model node")): + draft = resolve_operation_starter(MODULE_MAP, contracts, SELECTION) + assert len(draft["nodes"]) == 1 + node = draft["nodes"][0] + assert (node["module"], node["action"]) == ("modules.Spandrel", "Upscaler") + assert node["operation"]["decomposition"] == "integrated" + assert node["values"]["model_id"] == real_esrgan_x2_model_selection() + assert "revision" not in node["params"] and "pipeline_class" not in node["params"] + assert draft["edges"] == [] + assert draft["requiredInputs"] == [{"operationId": "image.upscale", "field": "image"}] + assert draft["sharedInputs"] == [] and draft["upstreamBlocks"] == [] + assert MODULE_MAP["modules.Spandrel"] == original + + +@pytest.mark.parametrize("profile", ["sdxl-base:modular", "unknown", "real-esrgan-x2-video-upscale:direct"]) +def test_integrated_starter_rejects_unrelated_profiles(catalog, profile): + with pytest.raises(ValueError): + resolve_operation_starter(MODULE_MAP, catalog[0], {**SELECTION, "executionProfileId": profile}) + + +def test_missing_action_is_not_advertised(): + assert get_integrated_operation_contracts({}) == [] + + +def test_new_starter_retains_old_edits_and_does_not_share_selector_objects(catalog): + first = resolve_operation_starter(MODULE_MAP, catalog[0], SELECTION) + first["nodes"][0]["params"]["model_id"]["value"]["revision"] = "a" * 40 + first["nodes"][0]["params"]["downscale"]["value"] = 0.5 + second = resolve_operation_starter(MODULE_MAP, catalog[0], SELECTION) + assert second["nodes"][0]["params"]["model_id"]["value"] == real_esrgan_x2_model_selection() + assert first["nodes"][0]["params"]["downscale"]["value"] == 0.5 diff --git a/tests/test_light_palette_director.py b/tests/test_light_palette_director.py new file mode 100644 index 00000000..c4cc762e --- /dev/null +++ b/tests/test_light_palette_director.py @@ -0,0 +1,81 @@ +"""The portable custom example must be safe, deterministic and graph-executable.""" + +import sys +from pathlib import Path + +import numpy as np +import pytest +from PIL import Image + +from modiff.custom_extensions import ExtensionStore + + +@pytest.fixture +def director(tmp_path, monkeypatch): + from modules import MODULE_MAP + + store = ExtensionStore(tmp_path / "custom") + source = Path(__file__).resolve().parents[1] / "examples/custom_nodes/LightPaletteDirector" + item = store.stage(kind="local", source=str(source), name="PaletteTest") + assert not item["enabled"] + assert item["preview"]["nodes"]["LightPaletteDirector"]["params"]["mask"]["type"] == "image" + registry = store.enable("PaletteTest", code_hash=item["codeHash"], consent=True) + monkeypatch.setitem(MODULE_MAP, "custom.PaletteTest", registry) + yield sys.modules["custom.PaletteTest.main"].LightPaletteDirector("palette-example") + store.unload("PaletteTest") + + +def gradient(): + return Image.fromarray(np.tile(np.arange(64, dtype=np.uint8) * 4, (48, 1))).convert("RGB") + + +def test_deterministic_image_mask_and_diagnostics(director): + image = gradient() + before = image.tobytes() + result = director.execute(image) + assert result["out_image"].mode == "RGB" + assert result["mask"].mode == "L" + assert result["out_image"].size == result["mask"].size == image.size + assert 0 < result["diagnostics"]["mask_coverage"] < 1 + assert result["mask"].getpixel((0, 0)) == 0 + assert result["mask"].getpixel((32, 24)) == 255 + assert result["out_image"].getpixel((0, 0)) == image.getpixel((0, 0)) + assert result["out_image"].tobytes() != before + assert image.tobytes() == before + assert director.execute(image)["out_image"].tobytes() == result["out_image"].tobytes() + + +def test_zero_strength_and_transparency(director): + image = gradient() + assert director.execute(image, strength=0)["out_image"].tobytes() == image.tobytes() + transparent = Image.new("RGBA", (16, 16), (255, 0, 0, 0)) + result = director.execute(transparent, strength=0) + assert result["out_image"].getextrema() == ((255, 255),) * 3 + + +@pytest.mark.parametrize("key,value", [ + ("strength", float("nan")), ("feather", 0), ("center_x", float("inf")), + ("radius_x", -1), ("light_angle", 181), ("strength", True), + ("shadow", "red"), ("highlight", "#XXXXXX"), +]) +def test_rejects_invalid_controls(director, key, value): + with pytest.raises(ValueError): + director.execute(gradient(), **{key: value}) + + +def test_bounded_batches_and_decoded_media_only(director): + for value in ([], [gradient()] * 5, "latents", np.zeros((3, 16, 16))): + with pytest.raises(ValueError): + director.execute(value) + with pytest.raises(ValueError, match="pixels"): + director.execute(Image.new("L", (2049, 2048))) + batch = director.execute([gradient(), gradient()]) + assert len(batch["out_image"]) == len(batch["mask"]) == len(batch["diagnostics"]) == 2 + + +def test_normal_node_dispatch_and_changed_control_invalidates_cache(director): + image = gradient() + first = director(image=image, strength=0.2) + second = director(image=image, strength=0.9) + assert first["out_image"].tobytes() != second["out_image"].tobytes() + assert second["diagnostics"]["settings"]["strength"] == 0.9 diff --git a/tests/test_loader_text_stage_components.py b/tests/test_loader_text_stage_components.py new file mode 100644 index 00000000..a63dc391 --- /dev/null +++ b/tests/test_loader_text_stage_components.py @@ -0,0 +1,26 @@ +"""The selected upstream workflow, not an unpruned family, owns the bundle.""" +from types import SimpleNamespace +import importlib.util + +import pytest +from diffusers import QwenImageEditModularPipeline + +from modules.ModularDiffusers.loaders import _loader_text_encoder_component_names + + +@pytest.mark.parametrize("workflow", [None, "image_conditioned", "image_conditioned_inpainting"]) +@pytest.mark.skipif(importlib.util.find_spec("transformers") is None, + reason="requires the staged optional Transformers runtime") +def test_qwen_edit_text_bundle_survives_workflow_flattening(workflow): + pipeline = QwenImageEditModularPipeline(workflow=workflow) + original_keys = list(pipeline.blocks.sub_blocks) + if workflow: + assert "text_encoder" not in original_keys + assert "text_encoder.encode" in original_keys + assert set(_loader_text_encoder_component_names(pipeline)) == {"text_encoder", "processor"} + assert list(pipeline.blocks.sub_blocks) == original_keys + + +def test_loader_does_not_invent_a_text_bundle_for_other_stages(): + loader = SimpleNamespace(blocks=SimpleNamespace(sub_blocks={"vae_encoder.encode": object()})) + assert _loader_text_encoder_component_names(loader) == [] diff --git a/tests/test_ltx25_execution_contract.py b/tests/test_ltx25_execution_contract.py index 0a210dee..892571a8 100644 --- a/tests/test_ltx25_execution_contract.py +++ b/tests/test_ltx25_execution_contract.py @@ -138,13 +138,17 @@ def test_split_workflow_continues_one_generator_stream(self): pipeline = _Pipeline() generator = continuation_generator_from_seed(42, pipeline) first = torch.rand(4, generator=generator) + cached_position = generator.get_state().clone() continued = continuation_generator_from_seed(42, pipeline, _State(generator=generator)) second = torch.rand(4, generator=continued) expected = torch.Generator(device="cpu").manual_seed(42) self.assertTrue(torch.equal(first, torch.rand(4, generator=expected))) self.assertTrue(torch.equal(second, torch.rand(4, generator=expected))) - self.assertIs(continued, generator) + self.assertIsNot(continued, generator) + self.assertTrue(torch.equal(generator.get_state(), cached_position)) + retry = continuation_generator_from_seed(42, pipeline, _State(generator=generator)) + self.assertTrue(torch.equal(second, torch.rand(4, generator=retry))) def test_split_workflow_rejects_a_midstream_seed_change(self): pipeline = _Pipeline() diff --git a/tests/test_main_supervisor.py b/tests/test_main_supervisor.py index 01c0e406..5a7fe4e2 100644 --- a/tests/test_main_supervisor.py +++ b/tests/test_main_supervisor.py @@ -288,6 +288,74 @@ def test_unexpected_worker_exit_is_reconciled_and_replaced(self): controller.reconcile_interrupted_worker.assert_any_call(worker_pid=4242, return_code=-9) server_class.return_value.close.assert_called_once_with() + def test_repeated_rapid_crashes_stop_after_a_bounded_backoff_budget(self): + module = load_main_module() + workers = [Mock(pid=5000 + i, wait=Mock(return_value=-9)) for i in range(5)] + with ( + patch.object(module.subprocess, "Popen", side_effect=workers) as popen, + patch.object(module.signal, "signal"), + patch.object(module.time, "monotonic", return_value=10), + patch.object(module.threading, "Event") as event, + patch("modiff.supervisor_control.SupervisorController") as controller_class, + patch("modiff.supervisor_control.SupervisorControlServer") as server_class, + ): + event.return_value.wait.return_value = False + controller = controller_class.return_value + controller.consume_restart_request.return_value = False + self.assertEqual(module.run_supervisor(), -9) + self.assertEqual(popen.call_count, 5) + self.assertEqual([call.args[0] for call in event.return_value.wait.call_args_list], [1, 2, 4, 8]) + self.assertEqual(controller.reconcile_interrupted_worker.call_count, 6) + server_class.return_value.close.assert_called_once_with() + + @unittest.skipIf(os.name == "nt", "POSIX abrupt-exit signal; Windows has separate owned-worker tests") + def test_real_abrupt_worker_death_preserves_queue_and_clean_replacement_starts(self): + module = load_main_module() + marker = Path(self.test_data.name) / "crash-probe.json" + code = ''' +import json, os, signal, sys +from pathlib import Path +signal.alarm(5) +marker = Path(sys.argv[1]) +queue = Path(os.environ["MODIFF_SUPERVISOR_QUEUE_STATE"]) +if not marker.exists(): + marker.write_text("started") + queue.parent.mkdir(parents=True, exist_ok=True) + queue.write_text(json.dumps({"workerPid": os.getpid(), + "current": {"task_id": "crashed", "workflow_snapshot": {"user_value": "preserve"}}, + "queued": {"next": {"task_id": "next"}}, "recent": []})) + os.kill(os.getpid(), signal.SIGKILL) +state = json.loads(queue.read_text()) +records = {task["task_id"]: task for task in state["recent"]} +assert state["current"] is None and state["queued"] == {} +assert records["crashed"]["status"] == "failed" +assert records["crashed"]["workflow_snapshot"]["user_value"] == "preserve" +assert records["next"]["status"] == "cancelled" +marker.write_text(json.dumps({"replacementStarted": True, "previousWorker": state["workerPid"]})) +''' + with patch.object(module, "worker_process_command", return_value=[sys.executable, "-c", code, str(marker)]), \ + patch.object(module.signal, "signal"), \ + patch.dict(os.environ, {"MODIFF_SUPERVISOR_CONTROL_PORT": "0"}): + self.assertEqual(module.run_supervisor(), 0) + self.assertTrue(json.loads(marker.read_text())["replacementStarted"]) + + def test_stable_worker_resets_crash_budget_and_shutdown_interrupts_backoff(self): + module = load_main_module() + workers = [Mock(pid=6000 + i, wait=Mock(return_value=-9)) for i in range(3)] + with ( + patch.object(module.subprocess, "Popen", side_effect=workers) as popen, + patch.object(module.signal, "signal"), + patch.object(module.time, "monotonic", side_effect=[0, 1, 2, 70, 71, 72]), + patch.object(module.threading, "Event") as event, + patch("modiff.supervisor_control.SupervisorController") as controller_class, + patch("modiff.supervisor_control.SupervisorControlServer"), + ): + event.return_value.wait.side_effect = [False, False, True] + controller_class.return_value.consume_restart_request.return_value = False + self.assertEqual(module.run_supervisor(), -9) + self.assertEqual(popen.call_count, 3) + self.assertEqual([call.args[0] for call in event.return_value.wait.call_args_list], [1, 1, 2]) + def test_shutdown_signal_is_forwarded_to_the_active_worker(self): module = load_main_module() worker = Mock() diff --git a/tests/test_memory_pressure_recovery.py b/tests/test_memory_pressure_recovery.py new file mode 100644 index 00000000..3f4a7dcb --- /dev/null +++ b/tests/test_memory_pressure_recovery.py @@ -0,0 +1,150 @@ +"""Fault-injected pressure and ownership tests without exhausting host RAM.""" +from unittest.mock import patch + +import pytest +import torch + +from modiff.NodeBase import NodeBase +from utils.memory_menager import GIB, MemoryManager + + +class Model: + def __init__(self, device="cpu"): + self.device = device + self.moves = [] + + def to(self, device): + self.moves.append(device) + self.device = str(device) + return self + + +@pytest.fixture +def manager(): + with patch("utils.memory_menager.memory_flush"), patch( + "modiff.hardware.system_memory_snapshot", return_value={"available_bytes": 32 * GIB, "total_bytes": 64 * GIB}, + ): + instance = MemoryManager() + instance.policy = "lru" + yield instance + instance.clear() + + +def test_discard_does_not_materialize_a_cpu_copy(manager): + model = Model("cuda:0") + identity = manager.add(model) + assert manager.remove(identity) == identity + assert model.moves == [] and not manager.cache + + +def test_exec_excludes_entire_working_set_before_any_component_load(manager): + first, second, inactive = (manager.add(Model()) for _ in range(3)) + calls = [] + def load(identity, device, exclude): + assert {first, second} <= set(exclude) + assert {first, second} <= set(manager._active_ids) + calls.append(identity) + with patch.object(manager, "load_model", side_effect=load): + assert manager.exec(lambda: "done", "cuda:0", models=[first, second]) == "done" + assert calls == [first, second] and not manager._active_ids + assert inactive in manager.cache + + +def test_pressure_inside_dispatch_cannot_remove_active_cpu_components(manager): + active = manager.add(Model()) + inactive_model = Model() + inactive = manager.add(inactive_model) + readings = [{"available_bytes": GIB, "total_bytes": 64 * GIB}, + {"available_bytes": 32 * GIB, "total_bytes": 64 * GIB}] + def execute(): + with patch("modiff.hardware.system_memory_snapshot", side_effect=readings): + added = manager.add(Model()) + assert active in manager.cache and added in manager.cache and inactive not in manager.cache + assert inactive_model.moves == [] + return added + manager.exec(execute, "cpu", models=[active]) + assert not manager._active_ids + + +def test_proactive_accelerator_pressure_evicts_inactive_not_prior_active_model(manager): + first, second, inactive = Model("cuda:0"), Model(), Model("cuda:0") + first_id, second_id = manager.add(first), manager.add(second) + manager.add(inactive, priority=2) + manager.cache[second_id]["size"] = 8 * GIB + with patch("utils.memory_menager.torch.cuda.mem_get_info", side_effect=[(0, 16 * GIB), (10 * GIB, 16 * GIB)]): + result = manager.exec(lambda: (first.device, second.device), "cuda:0", models=[first_id, second_id]) + assert result == ("cuda:0", "cuda:0") + assert first.moves == [] and inactive.moves == ["cpu"] + + +def test_failed_load_releases_leases_and_cleanup_does_not_hide_original_error(manager): + identity = manager.add(Model()) + manager.policy = "no_cache" + failure = torch.OutOfMemoryError("original allocation failure") + with patch.object(manager, "load_model", side_effect=failure), patch.object( + manager, "unload_model", side_effect=RuntimeError("device lost during cleanup"), + ): + with pytest.raises(torch.OutOfMemoryError) as error: + manager.exec(lambda: pytest.fail("must not invoke"), "cuda:0", models=[identity]) + assert error.value is failure and not manager._active_ids + + +def test_nested_no_cache_dispatch_does_not_offload_outer_active_owner(manager): + identity = manager.add(Model()) + manager.policy = "no_cache" + with patch.object(manager, "unload_model") as unload: + def outer(): + manager.exec(lambda: None, "cpu", models=[identity]) + unload.assert_not_called() + manager.exec(outer, "cpu", models=[identity]) + unload.assert_called_once_with(identity) + assert not manager._active_ids + + +def test_oom_callback_is_not_replayed_with_mutated_generator(manager): + manager.add(Model("cpu")) + generator = torch.Generator().manual_seed(9) + seen = [] + def execute(): + seen.append(torch.rand(1, generator=generator)) + raise torch.OutOfMemoryError("activation allocation failed") + with pytest.raises(torch.OutOfMemoryError): + manager.exec(execute, "cpu") + assert len(seen) == 1 and not manager._active_ids + + +def test_sensor_failure_after_pressure_eviction_is_bounded_and_nonfatal(manager): + first = manager.add(Model()) + second = manager.add(Model()) + with patch("modiff.hardware.system_memory_snapshot", side_effect=[ + {"available_bytes": GIB, "total_bytes": 64 * GIB}, OSError("sensor unavailable"), + ]) as sensor: + assert manager._evict_system_ram_pressure() == [first] + assert sensor.call_count == 2 and second in manager.cache + + +@pytest.mark.parametrize("available", [None, True, -1, "0"]) +def test_invalid_pressure_reading_cannot_evict_models(manager, available): + identity = manager.add(Model()) + with patch("modiff.hardware.system_memory_snapshot", return_value={"available_bytes": available}): + assert manager._evict_system_ram_pressure() == [] + assert identity in manager.cache + + +def test_node_reloads_evicted_handle_once_instead_of_returning_a_stale_cache_hit(manager): + class Loader(NodeBase): + def execute(self, **kwargs): + self.calls += 1 + return {"model": self.mm_add(Model())} + module = ".".join(Loader.__module__.split(".")[:-1]) + definitions = {module: {"Loader": {"skipParamsCheck": True, "params": {}}}} + with patch("modiff.NodeBase._module_map", return_value=definitions), patch("modiff.NodeBase.memory_manager", manager): + loader = Loader("pressure-loader") + loader.calls = 0 + first = loader()["model"] + assert loader()["model"] == first and loader.calls == 1 + manager.remove(first) + second = loader()["model"] + assert second != first and loader.calls == 2 and loader._cache_reason == "models_evicted" + assert loader()["model"] == second and loader.calls == 2 + loader._mm_models = [] diff --git a/tests/test_model_capabilities.py b/tests/test_model_capabilities.py index 3cc66091..2e742f6e 100644 --- a/tests/test_model_capabilities.py +++ b/tests/test_model_capabilities.py @@ -1,8 +1,12 @@ import json import unittest -from unittest.mock import patch +from pathlib import Path +import tempfile +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch import modules as module_registry +from modiff.config import CONFIG from modiff.auto_resource import AUTO_MODEL_REQUIREMENTS from modiff.diffusers_profiles import ( CONTRACT_ONLY_DIFFUSERS_PIPELINES, @@ -21,6 +25,47 @@ class FakeRequest: class ModelCapabilitiesTests(unittest.IsolatedAsyncioTestCase): + def setUp(self): + directory = self.enterContext(tempfile.TemporaryDirectory(prefix="modiff-capabilities-")) + paths = {key: str(Path(directory) / key) for key in CONFIG.paths if key != "app_root"} + for path in paths.values(): + Path(path).mkdir(parents=True, exist_ok=True) + self.enterContext(patch.dict(CONFIG.paths, paths)) + + async def test_operation_query_includes_linked_pipeline_classes_and_direct_class_matches(self): + server = WebServer(module_registry.MODULE_MAP) + response = await server.model_capabilities(SimpleNamespace(query={"q": "schnell"})) + payload = json.loads(response.text) + self.assertIn("FluxSchnellPipeline", {c["modelType"] for c in payload["capabilities"]}) + self.assertIn("FluxPipeline", {c["pipelineClass"] for c in payload["operationContracts"]}) + # New adapter metadata remains discoverable before a Studio catalog row exists. + from dataclasses import replace + adapter = replace(next(iter(AUDIO_PIPELINE_ADAPTERS.values())), pipeline_class="FutureAudioPipeline") + with patch.dict(AUDIO_PIPELINE_ADAPTERS, {"FutureAudioPipeline": adapter}): + response = await server.model_capabilities(SimpleNamespace(query={"q": "futureaudio"})) + payload = json.loads(response.text) + self.assertEqual(payload["capabilities"], []) + self.assertEqual({c["pipelineClass"] for c in payload["operationContracts"]}, {"FutureAudioPipeline"}) + response = await server.model_capabilities(SimpleNamespace(query={"q": "no-such-pipeline"})) + self.assertEqual(json.loads(response.text)["operationContracts"], []) + + async def test_operation_resolution_returns_an_ordinary_schema_without_execution(self): + server = WebServer(module_registry.MODULE_MAP) + selection = {"pipelineClass": "AnimaModularPipeline", "task": "text_to_image", "operationId": "diffusion.denoise"} + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("Constructed node")): + response = await server.resolve_operation(SimpleNamespace(json=AsyncMock(return_value=selection))) + payload = json.loads(response.text) + self.assertEqual(response.status, 200) + self.assertEqual(payload["schemaVersion"], 1) + self.assertEqual(payload["node"]["action"], "WorkflowImageDenoise") + self.assertEqual(payload["node"]["params"]["pipeline_class"]["value"], "AnimaModularPipeline") + self.assertEqual(payload["operation"]["operationId"], selection["operationId"]) + self.assertNotIn("executionSpecId", payload) + invalid = await server.resolve_operation(SimpleNamespace(json=AsyncMock(return_value={**selection, "templateId": "arbitrary"}))) + self.assertEqual(invalid.status, 400) + malformed = await server.resolve_operation(SimpleNamespace(json=AsyncMock(side_effect=ValueError("invalid JSON")))) + self.assertEqual(malformed.status, 400) + async def test_public_runtime_aggregate_excludes_hidden_legacy_execution_profiles(self): response = await WebServer(module_registry.MODULE_MAP).model_capabilities(FakeRequest()) capabilities = json.loads(response.text)["capabilities"] @@ -33,6 +78,42 @@ async def test_public_runtime_aggregate_excludes_hidden_legacy_execution_profile self.assertIn("flux2-modular:equivalent-standard", DIFFUSERS_EXECUTION_PROFILES) self.assertFalse(DIFFUSERS_EXECUTION_PROFILES["flux2-modular:equivalent-standard"].public) + async def test_starter_endpoint_describes_nodes_without_construction_or_receipts(self): + server = WebServer(module_registry.MODULE_MAP) + selection = {"pipelineClass": "AnimaModularPipeline", "task": "text_to_image"} + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("Constructed node")): + response = await server.resolve_operation_starter(SimpleNamespace(json=AsyncMock(return_value=selection))) + payload = json.loads(response.text) + self.assertEqual(response.status, 200) + self.assertEqual(len(payload["nodes"]), 4) + self.assertEqual(len(payload["edges"]), 5) + for entry in payload["nodes"]: + self.assertEqual(entry["node"]["type"], "custom") + self.assertNotIn("values", entry["node"]) + self.assertNotIn("operation", entry["node"]) + invalid = await server.resolve_operation_starter(SimpleNamespace(json=AsyncMock(return_value={**selection, "execute": True}))) + self.assertEqual(invalid.status, 400) + + async def test_task_starter_without_installed_weights_is_an_unbound_ordinary_graph(self): + server = WebServer(module_registry.MODULE_MAP) + with ( + patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("Constructed node")), + patch("modiff.server.get_local_models", return_value=[]), + patch.object(server, "_auto_planning_runtime_fingerprint", return_value={}), + patch("modiff.auto_resource.artifact_revision_cache_status", return_value={"complete": False}), + patch("modiff.workflow_auto_resource.build_workflow_auto_plan", side_effect=AssertionError("No installed model")), + ): + response = await server.resolve_task_starter(SimpleNamespace(json=AsyncMock(return_value={"task": "text_to_image"}))) + self.assertEqual(response.status, 200, response.text) + payload = json.loads(response.text) + self.assertTrue(payload["unbound"]) + self.assertIsNone(payload["profileId"]) + self.assertEqual(payload["starter"]["task"], "text_to_image") + loader = payload["starter"]["nodes"][0]["node"] + self.assertEqual(loader["params"]["repo_id"]["value"], {"source": "hub", "value": ""}) + self.assertTrue(loader["params"]["repo_id"]["required"]) + self.assertNotIn("values", loader) + async def test_capabilities_publish_only_app_delivered_quantization_as_available(self): catalog = { "capabilities": [ @@ -64,6 +145,12 @@ async def test_capabilities_publish_normalized_execution_contract(self): response = await WebServer(module_registry.MODULE_MAP).model_capabilities(FakeRequest()) payload = json.loads(response.text) self.assertEqual(payload["schemaVersion"], 2) + self.assertEqual(payload["operationContractSchemaVersion"], 3) + contracts = payload["operationContracts"] + self.assertGreater(len(contracts), 0) + self.assertTrue(all(contract["support"] == "declared" for contract in contracts)) + self.assertTrue(all("executionSpecId" not in contract for contract in contracts)) + self.assertEqual(len({(c["pipelineClass"], c["operationId"], c["task"]) for c in contracts}), len(contracts)) self.assertEqual(len(CURRENT_PIN_CONTRACT_ONLY_MODULAR_BY_NAME), 5) self.assertEqual( len(payload["experimentalCapabilities"]), @@ -343,7 +430,7 @@ async def test_capabilities_publish_normalized_execution_contract(self): self.assertEqual(capability["qualifiedModes"], []) self.assertNotIn(model_type, experimental) - self.assertEqual(len(payload["studioExecutionSpecs"]), 273) + self.assertEqual(len(payload["studioExecutionSpecs"]), 278) for model_type in ( "FluxSchnellPipeline", "FluxDevPipeline", @@ -565,6 +652,7 @@ async def test_capabilities_publish_normalized_execution_contract(self): [ "StableDiffusionXLControlNetPAGImg2ImgPipeline", "StableDiffusionXLControlNetPAGPipeline", + "StableDiffusionXLModularPipeline", "StableDiffusionXLPAGImg2ImgPipeline", "StableDiffusionXLPAGInpaintPipeline", "StableDiffusionXLPAGPipeline", diff --git a/tests/test_modular_block_contracts.py b/tests/test_modular_block_contracts.py index f642ea4c..b234aa5c 100644 --- a/tests/test_modular_block_contracts.py +++ b/tests/test_modular_block_contracts.py @@ -28,8 +28,8 @@ def test_snapshot_has_exact_reviewed_coverage_and_deduplicated_definitions(self) self.assertEqual(snapshot["diffusersRevision"], PINNED_DIFFUSERS_REVISION) self.assertEqual(len(snapshot["workflows"]), 94) self.assertEqual(len({item["pipelineClass"] for item in snapshot["workflows"]}), 34) - self.assertEqual(len(snapshot["blockDefinitions"]), 483) - self.assertEqual(sum(len(item["placements"]) for item in snapshot["workflows"]), 1237) + self.assertEqual(len(snapshot["blockDefinitions"]), 484) + self.assertEqual(sum(len(item["placements"]) for item in snapshot["workflows"]), 1238) definition_ids = {item["id"] for item in snapshot["blockDefinitions"]} self.assertTrue(all(item["rootBlockDefinitionId"] in definition_ids for item in snapshot["workflows"])) diff --git a/tests/test_modular_contract_only_registry.py b/tests/test_modular_contract_only_registry.py index a4ee52e4..565a33e9 100644 --- a/tests/test_modular_contract_only_registry.py +++ b/tests/test_modular_contract_only_registry.py @@ -22,6 +22,7 @@ ) from modiff.modular_workflow_contracts import PINNED_MODULAR_WORKFLOW_TRUTH from modiff.modular_workflow_discovery import reviewed_modular_workflow_contract +from modiff.upstream_coverage import _INTENTIONALLY_EXCLUDED_PIPELINES from modules.ModularDiffusers.loaders import ModelsLoader from modules.ModularDiffusers.modular_utils import ( _get_registry_instance, @@ -38,6 +39,11 @@ class ContractOnlyModularRegistryTests(unittest.TestCase): + def test_standalone_loader_exposes_model_types_before_any_field_callback(self): + options = ModelsLoader.params["model_type"]["options"] + self.assertEqual(options, get_all_model_types(include_contract_only=True)) + self.assertGreater(len(options), 1) + def test_data_only_registry_does_not_import_diffusers(self): result = subprocess.run( [ @@ -77,7 +83,7 @@ def test_current_pin_batches_cover_exact_exported_classes_and_normalized_schemas ) ) self.assertEqual(len(CURRENT_PIN_CONTRACT_ONLY_MODULAR_BY_NAME), 5) - self.assertEqual(len(CURRENT_PIN_EQUIVALENT_MODULAR_TARGETS), 5) + self.assertEqual(len(CURRENT_PIN_EQUIVALENT_MODULAR_TARGETS), 4) self.assertEqual(len(CURRENT_PIN_EQUIVALENT_MODULAR_WORKFLOW_TARGETS), 4) self.assertEqual(set(CURRENT_PIN_PROMOTED_MODULAR_DISCOVERY), { "Cosmos3DistilledModularPipeline", "MiniMaxH3ModularPipeline", @@ -100,7 +106,8 @@ def test_current_pin_batches_cover_exact_exported_classes_and_normalized_schemas set(PINNED_MODULAR_WORKFLOW_TRUTH) | set(CURRENT_PIN_CONTRACT_ONLY_MODULAR_BY_NAME) | set(CURRENT_PIN_EQUIVALENT_MODULAR_TARGETS) - | set(CURRENT_PIN_PROMOTED_MODULAR_DISCOVERY), + | set(CURRENT_PIN_PROMOTED_MODULAR_DISCOVERY) + | (exported_modular_classes & set(_INTENTIONALLY_EXCLUDED_PIPELINES)), exported_modular_classes, ) diff --git a/tests/test_modular_diffusers_upstream_contract.py b/tests/test_modular_diffusers_upstream_contract.py index 5ab1acad..f7952ae3 100644 --- a/tests/test_modular_diffusers_upstream_contract.py +++ b/tests/test_modular_diffusers_upstream_contract.py @@ -1,6 +1,10 @@ import inspect import importlib.util +import json +import tempfile import unittest +from pathlib import Path +from types import SimpleNamespace from unittest.mock import MagicMock, patch import diffusers @@ -16,6 +20,7 @@ get_all_model_types, get_modular_guider_options, get_modular_layer_block_options, + get_modular_node_action_options, get_modular_scheduler_options, get_model_type_metadata, require_modiff_node_contract, @@ -24,6 +29,7 @@ FLUX_BLOCKS, MODULAR_GUIDER_OPTIONS, MODULAR_LAYER_BLOCK_OPTIONS, + MODULAR_NODE_ACTION_OPTIONS, MODULAR_SCHEDULER_OPTIONS, QWEN_IMAGE_BLOCKS, SDXL_BLOCKS, @@ -41,6 +47,8 @@ QuantizationConfigNode, _REVIEWED_STANDARD_PIPELINE_MODEL_NAMES, _reviewed_loader_component_outputs, + _load_reviewed_pipeline_index, + _primary_component_cache_dirs, ) from modules.ModularDiffusers.pipeline_schema import ( MoDiffParam, @@ -64,9 +72,130 @@ _NO_EXPLICIT_GUIDER = object() +class ReviewedIndexCacheRootsTest(unittest.TestCase): + def test_reviewed_index_uses_original_cache_when_configured_cache_is_empty(self): + revision = "f332072aa78be7aecdf3ee76d5c247082da564a6" + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + original_cache = root / "original" + snapshot = original_cache / "models--Tongyi-MAI--Z-Image-Turbo" / "snapshots" / revision + snapshot.mkdir(parents=True) + (snapshot / "model_index.json").write_text(json.dumps({"_class_name": "ZImagePipeline"})) + with ( + patch( + "utils.huggingface._hf_cache_locations", + return_value=[ + ("configured", str(root / "configured")), + ("original", str(original_cache)), + ], + ), + patch("modules.ModularDiffusers.loaders.hf_hub_download", side_effect=AssertionError("wrong cache")), + ): + filename, document = _load_reviewed_pipeline_index("Tongyi-MAI/Z-Image-Turbo", revision) + self.assertEqual(filename, "model_index.json") + self.assertEqual(document["_class_name"], "ZImagePipeline") + + def test_primary_components_follow_index_cache_without_redirecting_auxiliaries(self): + revision = "f332072aa78be7aecdf3ee76d5c247082da564a6" + repository = "Tongyi-MAI/Z-Image-Turbo" + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + snapshot = root / "models--Tongyi-MAI--Z-Image-Turbo" / "snapshots" / revision + pipeline = SimpleNamespace( + _component_specs={ + "transformer": SimpleNamespace(pretrained_model_name_or_path=repository), + "auxiliary": SimpleNamespace(pretrained_model_name_or_path="owner/other"), + } + ) + with patch("modules.ModularDiffusers.loaders.exact_cached_snapshot_path", return_value=snapshot): + cache_dirs = _primary_component_cache_dirs(pipeline, repository, revision, "model_index.json") + self.assertEqual(cache_dirs, {"transformer": str(root)}) + + class ModularDiffusersUpstreamContractTests(unittest.TestCase): """Hardware-free checks for the experimental upstream API MoDiff consumes.""" + def test_connected_prompt_receipt_matches_encoder_call_and_keeps_wire_origin(self): + from types import SimpleNamespace + from modiff.execution_input_provenance import bind_generation_input_origins + + pipeline = MagicMock(return_value={}) + blocks = SimpleNamespace( + input_names=["prompt"], component_names=[], init_pipeline=lambda **_kwargs: pipeline, + ) + schema = { + "params": {"prompt": {"type": "string"}}, "input_names": ["prompt"], + "model_input_names": ["text_encoders"], "output_names": [], + } + node = EncodePrompt() + with ( + patch("modules.ModularDiffusers.embeddings.pipeline_class_from_runtime_inputs", return_value=object), + patch("modules.ModularDiffusers.embeddings.require_modiff_node_contract", return_value=(blocks, schema)), + patch("modules.ModularDiffusers.embeddings.collect_model_ids", return_value=[]), + ): + node(prompt="unused inline", prompt_input="Watercolor: lighthouse", text_encoders={"repo_id": "owner/model"}) + pipeline.assert_called_once_with(prompt="Watercolor: lighthouse") + self.assertEqual(node._execution_input_record["fields"]["prompt"]["value"], "Watercolor: lighthouse") + bound = bind_generation_input_origins("encode", { + "module": "modules.ModularDiffusers", "action": "EncodePrompt", "params": { + "prompt": {"value": "unused inline"}, + "prompt_input": {"sourceId": "custom-prompt", "sourceKey": "result"}, + }, + }, node._execution_input_record, source_fields=node._execution_input_source_fields) + self.assertEqual(bound["fields"]["prompt"]["source"], "connected") + self.assertEqual(bound["fields"]["prompt"]["sourceNodeId"], "custom-prompt") + self.assertEqual(bound["fields"]["prompt"]["sourcePortId"], "result") + + @requires_transformers + def test_z_image_dimension_increment_obeys_upstream_packed_latent_contract(self): + from types import SimpleNamespace + from diffusers.modular_pipelines.z_image.before_denoise import ZImagePrepareLatentsStep + + upstream = ZImagePrepareLatentsStep() + components = SimpleNamespace(vae_scale_factor_spatial=16) + for role in ("denoise", "vae_encoder"): + _, schema = require_modiff_node_contract( + diffusers.ZImageModularPipeline, role, resolve_blocks=False, + ) + with self.subTest(role=role): + dimensions = schema["params"] + for field in ("width", "height"): + self.assertEqual(dimensions[field]["step"], 16) + upstream.check_inputs(components, SimpleNamespace(height=1024, width=1008)) + with self.assertRaisesRegex(ValueError, "divisible by 16"): + upstream.check_inputs(components, SimpleNamespace(height=1024, width=1016)) + + @requires_transformers + def test_related_packed_latent_dimension_widgets_match_each_native_geometry(self): + from types import SimpleNamespace + from diffusers.modular_pipelines.flux2.before_denoise import Flux2PrepareLatentsStep + from diffusers.modular_pipelines.qwenimage.before_denoise import QwenImagePrepareLatentsStep + + for name, multiple, roles in ( + ("QwenImageModularPipeline", 16, ("denoise", "vae_encoder", "controlnet")), + ("QwenImageEditModularPipeline", 16, ("denoise",)), + ("FluxModularPipeline", 16, ("denoise", "vae_encoder")), + ("FluxKontextModularPipeline", 16, ("denoise",)), + ("Flux2KleinModularPipeline", 32, ("denoise",)), + ("Flux2KleinBaseModularPipeline", 32, ("denoise",)), + ("StableDiffusionXLModularPipeline", 8, ("denoise",)), + ): + for role in roles: + with self.subTest(pipeline=name, role=role): + _, schema = require_modiff_node_contract(getattr(diffusers, name), role, resolve_blocks=False) + for field in ("width", "height"): + self.assertEqual(schema["params"][field]["step"], multiple) + QwenImagePrepareLatentsStep.check_inputs(1024, 1008, 8) + with self.assertRaisesRegex(ValueError, "divisible by 16"): + QwenImagePrepareLatentsStep.check_inputs(1024, 1016, 8) + klein = Flux2PrepareLatentsStep() + klein.check_inputs(SimpleNamespace(vae_scale_factor=16), SimpleNamespace(height=1024, width=992)) + # Flux2 warns and floors invalid geometry instead of rejecting it. The + # widget must still avoid advertising a size that cannot be produced. + with self.assertLogs("diffusers.modular_pipelines.flux2.before_denoise", level="WARNING") as logged: + klein.check_inputs(SimpleNamespace(vae_scale_factor=16), SimpleNamespace(height=1024, width=1008)) + self.assertIn("divisible by 32", logged.output[0]) + def test_ip_adapter_is_exported_through_the_runtime_node_module(self): from modules.ModularDiffusers import main @@ -576,6 +705,7 @@ def test_registered_pipeline_action_matrix_resolves_real_contracts(self): registered_whole_workflow_models, { "AnimaModularPipeline", + "ErnieImageModularPipeline", "HeliosModularPipeline", "HeliosPyramidDistilledModularPipeline", "HeliosPyramidModularPipeline", @@ -821,6 +951,7 @@ def test_advertised_modular_control_modes_have_a_registered_node_contract(self): for profile in public_execution_profiles() if "control_image" in profile["modes"] and profile["backend_path"] == "modules.ModularDiffusers.ModelsLoader" + and not profile.get("operation_recipe") ) self.assertTrue(advertised_control_models) @@ -1273,9 +1404,9 @@ def test_layer_options_identify_module_list_stacks(self): } self.assertEqual(get_modular_layer_block_options(), expected) self.assertEqual(MODULAR_LAYER_BLOCK_OPTIONS, expected) - self.assertEqual(Layers.params["layers_config"]["onSignal"]["data"], expected) + self.assertEqual(next(action["data"] for action in Layers.params["layers_config"]["onSignal"] if "data" in action), expected) self.assertEqual( - MODULE_MAP["modules.ModularDiffusers"]["Layers"]["params"]["layers_config"]["onSignal"]["data"], + next(action["data"] for action in MODULE_MAP["modules.ModularDiffusers"]["Layers"]["params"]["layers_config"]["onSignal"] if "data" in action), expected, ) for model_type in set(get_all_model_types()) - {"", "DummyCustomPipeline"}: @@ -1287,6 +1418,38 @@ def test_layer_options_identify_module_list_stacks(self): self.assertNotIn("QwenImageModularPipeline", layer_source) self.assertNotIn("FluxModularPipeline", layer_source) + def test_every_split_node_publishes_its_registry_derived_signal_compatibility(self): + expected = {} + for model_type in set(get_all_model_types()) - {"", "DummyCustomPipeline"}: + metadata = get_model_type_metadata(model_type) + for action, action_config in metadata["node_params"].items(): + if isinstance(action_config, dict): + expected.setdefault(action, {})[model_type] = [action] + + self.assertEqual(get_modular_node_action_options(), expected) + self.assertEqual(MODULAR_NODE_ACTION_OPTIONS, expected) + + cases = ( + (EncodePrompt, "text_encoders", "text_encoder"), + (ImageEmbeddings, "image_encoder", "image_encoder"), + (Denoise, "unet", "denoise"), + (IPAdapter, "unet", "ip_adapter"), + (DecodeLatents, "vae", "decoder"), + (ImageEncode, "vae", "vae_encoder"), + (Controlnet, "controlnet_bundle", "controlnet"), + ) + public_nodes = MODULE_MAP["modules.ModularDiffusers"] + for node_class, field, action in cases: + with self.subTest(node=node_class.__name__, field=field): + declaration = node_class.params[field]["signalCompatibility"] + self.assertEqual(declaration["values"], expected[action]) + self.assertEqual( + public_nodes[node_class.__name__]["params"][field]["signalCompatibility"], + declaration, + ) + if field != "controlnet_bundle": + self.assertTrue(declaration["required"]) + @requires_transformers def test_guider_options_follow_reviewed_pipeline_components_and_layer_contracts(self): all_options = list(GUIDER_OPTIONS) @@ -1312,9 +1475,9 @@ def test_guider_options_follow_reviewed_pipeline_components_and_layer_contracts( self.assertEqual(get_modular_guider_options(), expected) self.assertEqual(MODULAR_GUIDER_OPTIONS, expected) - self.assertEqual(Guider.params["guider_out"]["onSignal"][0]["data"], expected) + self.assertEqual(next(action["data"] for action in Guider.params["guider_out"]["onSignal"] if "data" in action), expected) self.assertEqual( - MODULE_MAP["modules.ModularDiffusers"]["Guider"]["params"]["guider_out"]["onSignal"][0]["data"], + next(action["data"] for action in MODULE_MAP["modules.ModularDiffusers"]["Guider"]["params"]["guider_out"]["onSignal"] if "data" in action), expected, ) @@ -1347,9 +1510,9 @@ def test_scheduler_options_follow_pinned_upstream_compatibility(self): } self.assertEqual(get_modular_scheduler_options(), expected) self.assertEqual(MODULAR_SCHEDULER_OPTIONS, expected) - self.assertEqual(Scheduler.params["scheduler_in"]["onSignal"]["data"], expected) + self.assertEqual(Scheduler.params["scheduler_in"]["onSignal"][0]["data"], expected) self.assertEqual( - MODULE_MAP["modules.ModularDiffusers"]["Scheduler"]["params"]["scheduler_in"]["onSignal"]["data"], + MODULE_MAP["modules.ModularDiffusers"]["Scheduler"]["params"]["scheduler_in"]["onSignal"][0]["data"], expected, ) for scheduler_name in SCHEDULER_CONFIGS: diff --git a/tests/test_modular_headless_selection.py b/tests/test_modular_headless_selection.py new file mode 100644 index 00000000..e9b0b663 --- /dev/null +++ b/tests/test_modular_headless_selection.py @@ -0,0 +1,58 @@ +"""Persisted component choices must replay without a browser RPC.""" +from unittest.mock import patch + +import pytest +import diffusers + +from modules import MODULE_MAP +from modules.ModularDiffusers.guiders import Guider, Layers +from modules.ModularDiffusers.schedulers import Scheduler +from modiff.operation_catalog import build_operation_catalog, resolve_operation + + +def test_guider_replays_persisted_identity_without_a_websocket(): + node = object.__new__(Guider) + node.node_id = "headless-guider" + with patch.object(Guider, "get_signal_value", side_effect=AssertionError("browser RPC")): + output = node.execute("ClassifierFreeGuidance", model_type="StableDiffusionXLModularPipeline", guidance_scale=5) + assert isinstance(output["guider_out"], diffusers.ClassifierFreeGuidance) + with pytest.raises(ValueError, match="allowed"): + node.execute("PerturbedAttentionGuidance", model_type="Flux2KleinModularPipeline") + with pytest.raises(ValueError, match="allowed"): + node.execute("ClassifierFreeGuidance", model_type="UnreviewedPipeline") + + +def test_layers_replay_persisted_identity_without_a_websocket(): + node = object.__new__(Layers) + with patch.object(Layers, "get_signal_value", side_effect=AssertionError("browser RPC")): + output = node.execute(model_type="QwenImageModularPipeline", blocks_select=["transformer_blocks"], + transformer_blocks={"indices": "0,1"}) + assert output["layers_config"][0]["indices"] == [0, 1] + with pytest.raises(ValueError, match="allowed"): + node.execute(model_type="QwenImageModularPipeline", blocks_select=["single_transformer_blocks"], + single_transformer_blocks={"indices": "0"}) + + +def test_scheduler_selection_can_use_the_connected_runtime_model_identity(): + node = object.__new__(Scheduler) + with patch.object(Scheduler, "get_signal_value", side_effect=AssertionError("browser RPC")): + assert node._selected_scheduler("DDIMScheduler", "StableDiffusionXLModularPipeline") == "DDIMScheduler" + with pytest.raises(ValueError, match="allowed"): + node._selected_scheduler("DDIMScheduler", "UnreviewedPipeline") + + +def test_controlnet_starter_persists_exact_identity_for_recreated_instances(): + from modules.ModularDiffusers.modular_utils import pipeline_class_from_runtime_inputs + + contracts, _ = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {}) + checked = 0 + for contract in contracts: + if contract["nodeKey"] != "modules.ModularDiffusers.Controlnet": + continue + draft = resolve_operation(MODULE_MAP, contracts, { + key: contract[key] for key in ("pipelineClass", "task", "operationId") + }) + assert draft["params"]["model_type"]["value"] == contract["pipelineClass"] + assert pipeline_class_from_runtime_inputs(None, draft["values"]).__name__ == contract["pipelineClass"] + checked += 1 + assert checked >= 2 diff --git a/tests/test_modular_operation_catalog.py b/tests/test_modular_operation_catalog.py new file mode 100644 index 00000000..3f139b92 --- /dev/null +++ b/tests/test_modular_operation_catalog.py @@ -0,0 +1,121 @@ +"""Canonical stage bindings must retain the existing Modular execution boundary.""" + +import unittest +from unittest.mock import patch + +from modules import MODULE_MAP + + +class ModularOperationCatalogTests(unittest.TestCase): + def test_specialized_stages_have_generic_identities_and_exact_state_contracts(self): + from modules.ModularDiffusers.operation_contracts import get_modular_task_operation_contracts + + with patch("socket.socket.connect", side_effect=AssertionError("Network during discovery")): + contracts = get_modular_task_operation_contracts(MODULE_MAP) + stages = { + c["operationId"]: c + for c in contracts + if c["pipelineClass"] == "AnimaModularPipeline" and c["task"] == "text_to_image" + } + self.assertEqual(stages["diffusion.encode_prompt"]["nodeKey"], "modules.ModularDiffusers.WorkflowTextEncode") + denoise = stages["diffusion.denoise"] + self.assertEqual(denoise["binding"]["values"]["pipeline_class"], "AnimaModularPipeline") + self.assertEqual(denoise["binding"]["values"]["workflow_id"], "text2image") + self.assertEqual(denoise["binding"]["values"]["block_path"], "denoise") + state = next(p for p in denoise["ports"] if p["name"] == "state_in") + self.assertEqual(state["semantics"]["kind"], "state") + self.assertEqual(state["semantics"]["state"], "text_encoder") + self.assertEqual(state["semantics"]["scope"], "AnimaModularPipeline:text2image") + self.assertEqual(state["semantics"]["owner"], "same_loader") + + def test_loaders_preserve_component_bundle_and_non_decomposed_models_are_not_invented(self): + from modules.ModularDiffusers.operation_contracts import get_modular_task_operation_contracts + + contracts = get_modular_task_operation_contracts(MODULE_MAP) + loader = next( + c + for c in contracts + if c["pipelineClass"] == "QwenImageModularPipeline" + and c["task"] == "text_to_image" + and c["operationId"] == "diffusion.load_models" + ) + self.assertEqual(loader["nodeKey"], "modules.ModularDiffusers.ModelsLoader") + self.assertEqual(loader["binding"]["values"]["model_type"], "QwenImageModularPipeline") + bundle = next(p for p in loader["ports"] if p["name"] == "pipeline_components") + self.assertEqual(bundle["roles"], ["component"]) + self.assertTrue(bundle["semantics"]["members"]) + ernie = [c for c in contracts if c["pipelineClass"] == "ErnieImageModularPipeline"] + self.assertEqual( + [contract["operationId"] for contract in ernie], + [ + "diffusion.load_models", + "diffusion.rewrite_prompt", + "diffusion.encode_prompt", + "diffusion.denoise", + "diffusion.decode_latents", + ], + ) + self.assertEqual(ernie[1]["nodeKey"], "modules.ModularDiffusers.WorkflowErniePromptEnhance") + self.assertEqual(ernie[-1]["binding"]["values"]["block_path"], "decode") + self.assertEqual(get_modular_task_operation_contracts({}), []) + + def test_loader_hides_absent_components_and_missing_actions_do_not_advertise_partial_routes(self): + from copy import deepcopy + from modules.ModularDiffusers.operation_contracts import get_modular_task_operation_contracts + + contracts = get_modular_task_operation_contracts(MODULE_MAP) + qwen = next( + c + for c in contracts + if c["pipelineClass"] == "QwenImageModularPipeline" + and c["task"] == "text_to_image" + and c["decomposition"] == "loader" + ) + self.assertTrue(next(p for p in qwen["ports"] if p["name"] == "image_encoder")["hidden"]) + self.assertEqual( + next(p for p in qwen["ports"] if p["name"] == "unet_out")["semantics"]["members"][0]["name"], "transformer" + ) + missing = deepcopy(MODULE_MAP) + missing["modules.ModularDiffusers"].pop("WorkflowImageDenoise") + self.assertFalse( + any(c["pipelineClass"] == "AnimaModularPipeline" for c in get_modular_task_operation_contracts(missing)) + ) + + def test_numeric_latent_controls_do_not_claim_tensor_ownership(self): + from modiff.operation_contracts import with_operation_semantics + + record = { + "pipelineClass": "Pipeline", + "ports": [ + {"types": ["int"], "semanticName": "num_latents", "roles": ["value"]}, + {"types": ["tensor"], "semanticName": "latents", "roles": ["value"]}, + ], + } + ports = with_operation_semantics(record)["ports"] + self.assertEqual(ports[0]["semantics"]["kind"], "value") + self.assertIsNone(ports[0]["semantics"]["scope"]) + self.assertEqual(ports[1]["semantics"]["kind"], "latents") + self.assertEqual(ports[1]["semantics"]["owner"], "same_loader") + + def test_media_preparation_and_typed_reference_helpers_retain_separate_operations(self): + from modules.ModularDiffusers.operation_contracts import get_modular_task_operation_contracts + + contracts = get_modular_task_operation_contracts(MODULE_MAP) + media = { + c["operationId"]: c + for c in contracts + if c["pipelineClass"] == "MiniMaxH3ModularPipeline" and c["task"] == "reference_to_video_with_audio" + } + self.assertIn("diffusion.prepare_media", media) + self.assertTrue( + any( + c["pipelineClass"] == "Cosmos3OmniModularPipeline" + and c["operationId"] == "diffusion.postprocess_media" + for c in contracts + ) + ) + helper = media["diffusion.assemble_references"] + self.assertEqual(helper["nodeKey"], "modules.ModularDiffusers.WorkflowMiniMaxH3ReferenceAssembler") + self.assertEqual(helper["decomposition"], "bundle") + self.assertIsNone(helper["blockName"]) + self.assertEqual(next(p for p in helper["ports"] if p["name"] == "references")["semantics"]["kind"], "opaque") diff --git a/tests/test_modular_pipeline_recovery.py b/tests/test_modular_pipeline_recovery.py index 851ac040..5f4b0351 100644 --- a/tests/test_modular_pipeline_recovery.py +++ b/tests/test_modular_pipeline_recovery.py @@ -189,6 +189,39 @@ def init_without_repository(*args, **kwargs): self.assertEqual(outputs, {"embeddings": {"prompt_embeds": "encoded"}}) pipeline.update_components.assert_called_once_with(text_encoder=managed_component) + def test_connected_prompt_input_overrides_the_inline_prompt(self): + self.assertEqual(EncodePrompt.params["prompt_input"]["display"], "input") + + node = EncodePrompt("connected-prompt") + node._pipeline_class = FluxModularPipeline + pipeline = Mock() + state = Mock() + state.get_by_kwargs.return_value = {"prompt_embeds": "encoded"} + pipeline.return_value = state + blocks = Mock() + blocks.component_names = [] + blocks.input_names = ["prompt"] + blocks.init_pipeline.return_value = pipeline + node_config = { + "params": {"prompt": {"type": "string"}}, + "model_input_names": ["text_encoders"], + "input_names": ["prompt"], + "output_names": ["embeddings"], + } + + with patch( + "modules.ModularDiffusers.embeddings.require_modiff_node_contract", + return_value=(blocks, node_config), + ): + outputs = node.execute( + text_encoders={"repo_id": "fixture/model"}, + prompt="inline prompt", + prompt_input="custom-node prompt", + ) + + pipeline.assert_called_once_with(prompt="custom-node prompt") + self.assertEqual(outputs, {"embeddings": {"prompt_embeds": "encoded"}}) + def test_recovers_pipeline_class_from_nested_loader_output(self): runtime_inputs = { "text_encoders": { diff --git a/tests/test_modular_route_state.py b/tests/test_modular_route_state.py index 7fffefb1..6dd52fea 100644 --- a/tests/test_modular_route_state.py +++ b/tests/test_modular_route_state.py @@ -420,6 +420,41 @@ def test_prepare_for_workflow_reuse_preserves_resident_output_binding(self): self.assertIs(require_component_binding(outputs["unet_out"], label="resident model"), token) + def test_loader_adoption_transfers_component_collection_without_reloading(self): + from types import SimpleNamespace + from modules.ModularDiffusers import loaders + node = ModelsLoader("old-owner") + node.loader = SimpleNamespace(_collection='old-owner') + owned = {'component'} + manager = SimpleNamespace(collections={'old-owner': owned, 'shared-owner': {'component'}}, + components={'component': object()}) + with patch.object(loaders, 'components', manager): + node.rebind_cache_owner('new-owner') + self.assertIs(manager.collections['new-owner'], owned) + self.assertNotIn('old-owner', manager.collections) + self.assertEqual(manager.collections['shared-owner'], {'component'}) + self.assertEqual(node.loader._collection, 'new-owner') + self.assertEqual(node.node_id, 'new-owner') + + def test_loader_adoption_preserves_exclusive_disk_hook_storage(self): + from types import SimpleNamespace + from modules.ModularDiffusers import loaders + component = SimpleNamespace(_modiff_offload_node_id='old-owner', hook=object()) + hook = component.hook + node = ModelsLoader('old-owner') + node.loader = SimpleNamespace(_collection='old-owner') + manager = SimpleNamespace(collections={'old-owner': {'disk'}, 'foreign': {'disk'}}, + components={'disk': component}) + with patch.object(loaders, 'components', manager): + with self.assertRaisesRegex(ValueError, 'shared component ownership'): + node.rebind_cache_owner('new-owner') + self.assertEqual(node.node_id, 'old-owner') + manager.collections.pop('foreign') + node.rebind_cache_owner('new-owner') + self.assertEqual(component._modiff_offload_node_id, 'new-owner') + self.assertIs(component.hook, hook) + self.assertEqual(manager.collections, {'new-owner': {'disk'}}) + def test_failed_loader_preflight_cannot_mint_or_publish_a_token(self): node = ModelsLoader() with patch("modules.ModularDiffusers.loaders.issue_pipeline_instance_token") as issuer: diff --git a/tests/test_modular_workflow_blocks.py b/tests/test_modular_workflow_blocks.py index 46e48f08..c18575ee 100644 --- a/tests/test_modular_workflow_blocks.py +++ b/tests/test_modular_workflow_blocks.py @@ -1,4 +1,5 @@ import copy +import importlib.util import json import pickle import sys @@ -25,6 +26,10 @@ WorkflowDecodeImage, WorkflowDecodeVideo, WorkflowDenoise, + WorkflowErnieDecodeImage, + WorkflowErnieImageDenoise, + WorkflowErniePromptEnhance, + WorkflowErnieTextEncode, WorkflowHunyuanVideo15Decode, WorkflowHunyuanVideo15Denoise, WorkflowHunyuanVideo15ImageEncode, @@ -69,6 +74,7 @@ WorkflowWanAnimateVaeEncode, WorkflowWanAnimateVideoEncode, _issue_workflow_state, + _official_stage_block, _require_workflow_state, _validated_minimax_h3_references, ) @@ -78,6 +84,21 @@ WORKFLOW_ID = "default" +@unittest.skipUnless(importlib.util.find_spec("transformers"), "requires the staged optional Transformers runtime") +def test_official_stage_resolves_flattened_selected_denoise_without_other_branches(): + from diffusers.modular_pipelines import SequentialPipelineBlocks + from diffusers.modular_pipelines.ernie_image.modular_blocks_ernie_image import ErnieImageAutoBlocks + + selected = ErnieImageAutoBlocks().get_execution_blocks(use_pe=True) + assert "denoise" not in selected.sub_blocks + denoise = _official_stage_block(selected, "denoise") + assert isinstance(denoise, SequentialPipelineBlocks) + assert list(denoise.sub_blocks) == ["input", "set_timesteps", "prepare_latents", "denoise"] + assert denoise.model_name == "ernie-image" + assert _official_stage_block(selected, "prompt_enhancer") is selected.sub_blocks["prompt_enhancer"] + assert _official_stage_block(selected, "absent") is None + + class FakeState: def __init__(self, **values): self.values = values @@ -158,6 +179,170 @@ def new(self, **kwargs): class ModularWorkflowBlockTests(unittest.TestCase): + def test_ernie_scalar_normalization_uses_schema_not_live_nodebase_values(self): + token = object() + pipeline = RecordingPipeline(FakeState(prompt=["enhanced prompt"])) + enhance = WorkflowErniePromptEnhance("enhance-runtime-boundary") + values = { + "pipeline_components": {}, "pipeline_class": "ErnieImageModularPipeline", + "workflow_id": "text2image", "block_path": "prompt_enhancer", + "prompt": "a small red panda", "width": "1024", "height": "1024", + "pe_temperature": "0.6", "pe_top_p": "0.95", + } + with mock.patch.object(enhance, "_prepare_pipeline", return_value=(token, pipeline)): + output = enhance(**values) + self.assertIn("state_out", output) + self.assertEqual(pipeline.calls[0]["width"], 1024) + self.assertEqual(pipeline.calls[0]["pe_temperature"], 0.6) + self.assertEqual(enhance.params["prompt"], values["prompt"]) + + state = _issue_workflow_state( + token=token, pipeline_class="ErnieImageModularPipeline", workflow_id="text2image", + completed_stage="text_encoder", state=FakeState(prompt_embeds=["encoded"]), + ) + denoise_pipeline = RecordingPipeline(FakeState(latents="latents")) + denoise_pipeline.guider = FakeGuider() + denoise = WorkflowErnieImageDenoise("denoise-runtime-boundary") + with mock.patch.object(denoise, "_prepare_pipeline", return_value=(token, denoise_pipeline)): + denoise( + pipeline_components={}, pipeline_class="ErnieImageModularPipeline", workflow_id="text2image", + block_path="denoise", state_in=state, width="1024", height="1024", num_inference_steps="1", seed="23", + ) + self.assertEqual(denoise_pipeline.calls[0]["num_inference_steps"], 1) + self.assertEqual(denoise_pipeline.calls[0]["generator"].initial_seed(), 23) + + def test_ernie_turbo_runs_exact_official_stage_sequence(self): + token = object() + enhanced_state = FakeState(prompt=["enhanced prompt"]) + enhance_pipeline = RecordingPipeline(enhanced_state) + enhance = WorkflowErniePromptEnhance("enhance") + with mock.patch.object(enhance, "_prepare_pipeline", return_value=(token, enhance_pipeline)): + enhanced = enhance.execute( + pipeline_components={}, + pipeline_class="ErnieImageModularPipeline", + workflow_id="text2image", + block_path="prompt_enhancer", + prompt="a small red panda", + width=1024, + height=1024, + pe_system_prompt="", + pe_temperature=0.6, + pe_top_p=0.95, + ) + self.assertEqual( + enhance_pipeline.calls, + [ + { + "prompt": "a small red panda", + "width": 1024, + "height": 1024, + "use_pe": True, + "pe_system_prompt": None, + "pe_temperature": 0.6, + "pe_top_p": 0.95, + } + ], + ) + + encoded_state = FakeState(prompt_embeds=["encoded"]) + text_pipeline = RecordingPipeline(encoded_state) + text = WorkflowErnieTextEncode("text") + with mock.patch.object(text, "_prepare_pipeline", return_value=(token, text_pipeline)): + encoded = text.execute( + pipeline_components={}, + pipeline_class="ErnieImageModularPipeline", + workflow_id="text2image", + block_path="text_encoder", + state_in=enhanced["state_out"], + negative_prompt="", + ) + self.assertEqual(text_pipeline.calls, [{"state": enhanced_state, "negative_prompt": ""}]) + + denoised_state = FakeState(latents="latents") + denoise_pipeline = RecordingPipeline(denoised_state) + denoise_pipeline.guider = FakeGuider(4.0) + denoise = WorkflowErnieImageDenoise("denoise") + with ( + mock.patch.object(denoise, "_prepare_pipeline", return_value=(token, denoise_pipeline)), + mock.patch( + "modules.ModularDiffusers.workflow_blocks.modular_generator_from_seed", + return_value="generator", + ), + ): + denoised = denoise.execute( + pipeline_components={}, + pipeline_class="ErnieImageModularPipeline", + workflow_id="text2image", + block_path="denoise", + state_in=encoded["state_out"], + width=1024, + height=1024, + num_inference_steps=8, + seed=23, + ) + self.assertEqual(denoise_pipeline.guider.guidance_scale, 1.0) + self.assertEqual( + denoise_pipeline.calls, + [ + { + "state": encoded_state, + "width": 1024, + "height": 1024, + "num_images_per_prompt": 1, + "num_inference_steps": 8, + "generator": "generator", + } + ], + ) + + image = Image.new("RGB", (32, 32), "red") + decode_pipeline = RecordingPipeline(FakeState(images=[image])) + decode = WorkflowErnieDecodeImage("decode") + with mock.patch.object(decode, "_prepare_pipeline", return_value=(token, decode_pipeline)): + output = decode.execute( + pipeline_components={}, + pipeline_class="ErnieImageModularPipeline", + workflow_id="text2image", + block_path="decode", + state_in=denoised["state_out"], + ) + self.assertEqual(decode_pipeline.calls, [{"state": denoised_state, "output_type": "pil"}]) + self.assertEqual(output["images"], [image]) + + def test_ernie_turbo_rejects_unreviewed_dimensions_and_step_counts(self): + token = object() + state = _issue_workflow_state( + token=token, + pipeline_class="ErnieImageModularPipeline", + workflow_id="text2image", + completed_stage="text_encoder", + state=FakeState(prompt_embeds=["encoded"]), + ) + pipeline = RecordingPipeline(FakeState(latents="latents")) + pipeline.guider = FakeGuider() + denoise = WorkflowErnieImageDenoise("denoise") + for overrides, message in ( + ({"width": 1056, "height": 1024}, "1,048,576"), + ({"width": 1024, "height": 1024, "num_inference_steps": 9}, "less than or equal to 8"), + ): + with self.subTest(overrides=overrides), mock.patch.object( + denoise, "_prepare_pipeline", return_value=(token, pipeline) + ), self.assertRaisesRegex(ValueError, message): + denoise.execute( + **{ + "pipeline_components": {}, + "pipeline_class": "ErnieImageModularPipeline", + "workflow_id": "text2image", + "block_path": "denoise", + "state_in": state, + "width": 1024, + "height": 1024, + "num_inference_steps": 8, + "seed": 0, + **overrides, + } + ) + def test_anima_text_to_image_runs_exact_official_stage_sequence(self): token = object() encoded_state = FakeState(prompt_embeds="encoded") @@ -1744,7 +1929,8 @@ def test_ltx25_runs_exact_in_context_diffusion_decoder_route(self): self.assertEqual(reference_pipeline.calls[0]["reference_downscale_factor"], 2) self.assertEqual(reference_pipeline.calls[0]["conditioning_attention_strength"], 0.75) self.assertIs(reference_pipeline.calls[0]["conditioning_attention_mask"], attention_mask) - self.assertIs(reference_pipeline.calls[0]["generator"], generator) + self.assertIsNot(reference_pipeline.calls[0]["generator"], generator) + self.assertTrue(torch.equal(reference_pipeline.calls[0]["generator"].get_state(), generator.get_state())) denoised_state = FakeState(latents="video", audio_latents="audio") denoise_pipeline = RecordingPipeline(denoised_state) @@ -1771,7 +1957,8 @@ def test_ltx25_runs_exact_in_context_diffusion_decoder_route(self): denoise_pipeline.calls[0]["sigmas"], [1.0, 0.99375, 0.9875, 0.98125, 0.975, 0.909375, 0.725, 0.421875], ) - self.assertIs(denoise_pipeline.calls[0]["generator"], generator) + self.assertIsNot(denoise_pipeline.calls[0]["generator"], generator) + self.assertTrue(torch.equal(denoise_pipeline.calls[0]["generator"].get_state(), generator.get_state())) self.assertEqual(denoise_pipeline.guider.guidance_scale, 1.0) self.assertEqual(denoise_pipeline.guider.stg_scale, 0.0) self.assertEqual(denoise_pipeline.guider.modality_scale, 1.0) @@ -1799,7 +1986,8 @@ def test_ltx25_runs_exact_in_context_diffusion_decoder_route(self): seed=89, ) self.assertEqual(decode_pipeline.calls[0]["output_type"], "pil") - self.assertIs(decode_pipeline.calls[0]["generator"], generator) + self.assertIsNot(decode_pipeline.calls[0]["generator"], generator) + self.assertTrue(torch.equal(decode_pipeline.calls[0]["generator"].get_state(), generator.get_state())) configure.assert_called_once_with(decode_pipeline) self.assertEqual(output["video"], frames) self.assertEqual(output["sample_rate"], 48_000) @@ -1891,7 +2079,8 @@ def test_ltx25_image_encode_and_denoise_continue_one_generator(self): seed=97, ) - self.assertIs(denoise_pipeline.calls[0]["generator"], generator) + self.assertIsNot(denoise_pipeline.calls[0]["generator"], generator) + self.assertTrue(torch.equal(denoise_pipeline.calls[0]["generator"].get_state(), generator.get_state())) def test_ltx25_denoise_rejects_step_count_substitution(self): pipeline_class = "LTX25ModularPipeline" @@ -2039,7 +2228,8 @@ def test_ltx25_reference_conditions_accept_only_one_official_batched_video(self) seed=103, ) self.assertIs(reference_pipeline.calls[0]["reference_conditions"][0], reference) - self.assertIs(reference_pipeline.calls[0]["generator"], generator) + self.assertIsNot(reference_pipeline.calls[0]["generator"], generator) + self.assertTrue(torch.equal(reference_pipeline.calls[0]["generator"].get_state(), generator.get_state())) reference_pipeline.calls.clear() reference.frames = torch.zeros((2, 5, 3, 16, 16)) diff --git a/tests/test_modular_workflow_reuse.py b/tests/test_modular_workflow_reuse.py new file mode 100644 index 00000000..693a0bd2 --- /dev/null +++ b/tests/test_modular_workflow_reuse.py @@ -0,0 +1,102 @@ +"""Sampling continuations must not mutate a reusable stage's RNG snapshot.""" +from types import SimpleNamespace + +import pytest +import torch +from diffusers.modular_pipelines import PipelineState + +from modules.ModularDiffusers.workflow_runtime import continuation_generator_from_seed + + +def test_continuations_start_at_the_cached_post_stage_state_without_mutating_it(): + pipeline = SimpleNamespace(_execution_device="cpu") + generator = torch.Generator().manual_seed(42) + torch.randn(7, generator=generator) # VAE sampling already consumed part of the stream. + snapshot = generator.get_state().clone() + state = PipelineState(values={"generator": generator}) + first = continuation_generator_from_seed(42, pipeline, state) + expected = torch.randn(5, generator=first) + assert torch.equal(generator.get_state(), snapshot) + second = continuation_generator_from_seed(42, pipeline, state) + assert first is not second and first is not generator and second is not generator + assert torch.equal(torch.randn(5, generator=second), expected) + # A following stage continues after the previous stage's draws, not at seed 42. + next_state = PipelineState(values={"generator": first}) + following = continuation_generator_from_seed(42, pipeline, next_state) + assert torch.equal(following.get_state(), first.get_state()) + assert not torch.equal(following.get_state(), snapshot) + + +def test_continuation_rejects_seed_and_device_changes_before_cloning(): + state = PipelineState(values={"generator": torch.Generator().manual_seed(42)}) + with pytest.raises(ValueError, match="seed changed"): + continuation_generator_from_seed(43, SimpleNamespace(_execution_device="cpu"), state) + with pytest.raises(ValueError, match="different execution device"): + continuation_generator_from_seed(42, SimpleNamespace(_execution_device="cuda:0"), state) + + +def test_expanded_continuations_isolate_state_scheduler_and_guider_but_share_weights(): + from diffusers import DDIMScheduler + from modules.ModularDiffusers import reviewed_blocks + + weights = torch.nn.Linear(2, 2) + manager = SimpleNamespace(components={'weights': weights}) + scheduler = DDIMScheduler() + scheduler.set_timesteps(3) + pipeline = SimpleNamespace( + pretrained_component_names=['unet', 'scheduler'], unet=weights, + scheduler=scheduler, guider=SimpleNamespace(step=0), + _components_manager=manager, _modiff_composition_hash=None, + ) + state = PipelineState(values={'latents': torch.zeros(2), + 'generator': torch.Generator().manual_seed(42)}) + original_rng = state.get('generator').get_state().clone() + token = object() + issued = reviewed_blocks._issue_state( + token=token, pipeline_class='test', workflow_id='default', + execution_scope='selected_workflow', pipeline=pipeline, state=state, completed_path=('prepare',), + ) + def continue_runtime(): + return reviewed_blocks._continued_runtime( + issued, bundle=None, pipeline_class='test', workflow_id='default', + execution_scope='selected_workflow', + ) + returned_token, first, first_state = continue_runtime() + assert returned_token is token + assert first.unet is weights + assert first._components_manager is manager + assert first.scheduler is not scheduler + first.scheduler.timesteps.add_(1) + first.guider.step = 9 + first_state.get('latents').add_(1) + expected = torch.randn(5, generator=first_state.get('generator')) + _, retry, retry_state = continue_runtime() + assert torch.equal(retry.scheduler.timesteps, scheduler.timesteps) + assert retry.guider.step == 0 + assert torch.equal(retry_state.get('latents'), torch.zeros(2)) + assert torch.equal(torch.randn(5, generator=retry_state.get('generator')), expected) + assert torch.equal(state.get('generator').get_state(), original_rng) + + +def test_expanded_fork_supports_the_actual_pinned_modular_pipeline(): + pytest.importorskip("transformers", reason="SDXL requires the validated optional runtime") + from diffusers import EulerDiscreteScheduler, StableDiffusionXLModularPipeline + from modules.ModularDiffusers import reviewed_blocks + + pipeline = StableDiffusionXLModularPipeline().blocks.get_workflow('text2image').init_pipeline() + pipeline.update_components(scheduler=EulerDiscreteScheduler()) + pipeline.scheduler.set_timesteps(3) + pipeline._modiff_composition_hash = None + state = PipelineState(values={'latents': torch.zeros(2)}) + issued = reviewed_blocks._issue_state( + token=object(), pipeline_class='StableDiffusionXLModularPipeline', workflow_id='text2image', + execution_scope='selected_workflow', pipeline=pipeline, state=state, completed_path=('prepare',), + ) + _, fork, output = reviewed_blocks._continued_runtime( + issued, bundle=None, pipeline_class='StableDiffusionXLModularPipeline', workflow_id='text2image', + execution_scope='selected_workflow', + ) + assert type(fork) is type(pipeline) and fork is not pipeline + assert fork.scheduler is not pipeline.scheduler + assert torch.equal(output.get('latents'), state.get('latents')) + assert output.get('latents') is not state.get('latents') diff --git a/tests/test_modular_workflow_truth.py b/tests/test_modular_workflow_truth.py index 907a9587..6cad8a90 100644 --- a/tests/test_modular_workflow_truth.py +++ b/tests/test_modular_workflow_truth.py @@ -71,7 +71,7 @@ def _advertised_modular_modes(): if runnable_modes: advertised.setdefault(capability["modelType"], set()).update(runnable_modes) for profile in public_execution_profiles(): - if profile["backend_path"] != MODULAR_BACKEND_PATH: + if profile["backend_path"] != MODULAR_BACKEND_PATH or profile.get("operation_recipe"): continue advertised.setdefault(profile["pipeline_class"], set()).update(profile["modes"]) return advertised @@ -86,11 +86,11 @@ def test_reviewed_weight_variants_are_exact_and_repository_scoped(self): ), "fp16", ) - self.assertIsNone( + self.assertEqual( reviewed_modular_weight_variant( "StableDiffusionXLModularPipeline", "stabilityai/sdxl-turbo", - ) + ), "fp16", ) self.assertIsNone( reviewed_modular_weight_variant( @@ -101,7 +101,7 @@ def test_reviewed_weight_variants_are_exact_and_repository_scoped(self): def test_registered_pipelines_have_exact_truth_or_whole_workflow_adapters(self): registered = set(get_all_model_types()) - {"", "DummyCustomPipeline"} - self.assertEqual(len(PINNED_MODULAR_WORKFLOW_TRUTH), 22) + self.assertEqual(len(PINNED_MODULAR_WORKFLOW_TRUTH), 23) whole_workflow_only = registered - set(PINNED_MODULAR_WORKFLOW_TRUTH) self.assertEqual( whole_workflow_only, @@ -121,7 +121,7 @@ def test_registered_pipelines_have_exact_truth_or_whole_workflow_adapters(self): reviewed_whole_workflow_graph_adapter(model_type, candidate["workflowId"]), f"{model_type}:{candidate['workflowId']} lacks its exact reviewed whole-workflow adapter", ) - self.assertEqual(PINNED_DIFFUSERS_REVISION, "2f7e0154a9db246e95c9ede43edba7db5b130805") + self.assertEqual(PINNED_DIFFUSERS_REVISION, "fbf49e7f35857f76bc57b177e26f12b03687c668") dependency_contract = Path("pyproject.toml").read_text(encoding="utf-8") self.assertIn( f"diffusers.git@{PINNED_DIFFUSERS_REVISION}", @@ -165,6 +165,7 @@ def test_pinned_workflow_maps_and_fixed_sequences_are_exact(self): def test_flux_qwen_and_wan_advertised_modular_modes_are_exact(self): expected = { + "ErnieImageModularPipeline": {"text_to_image"}, "AnimaModularPipeline": {"text_to_image", "image_to_image"}, "HeliosModularPipeline": {"text_to_video", "image_to_video", "video_to_video"}, "HeliosPyramidModularPipeline": {"text_to_video", "image_to_video", "video_to_video"}, diff --git a/tests/test_multi_cache_snapshot.py b/tests/test_multi_cache_snapshot.py new file mode 100644 index 00000000..d90449c2 --- /dev/null +++ b/tests/test_multi_cache_snapshot.py @@ -0,0 +1,77 @@ +"""Execution resolves the same installed roots as model discovery, without downloads.""" +import pytest + +from utils import huggingface as hf + +REVISION = "a" * 40 + + +def test_startup_cache_override_keeps_the_user_default_discoverable(tmp_path, monkeypatch): + monkeypatch.setitem(hf.CONFIG.hf, "cache_dir", str(tmp_path / "configured")) + monkeypatch.setattr(hf, "HUGGINGFACE_HUB_CACHE", str(tmp_path / "configured")) + monkeypatch.setattr(hf.Path, "home", classmethod(lambda cls: tmp_path / "user")) + monkeypatch.setattr(hf, "_common_appdata_hf_cache_candidates", lambda: []) + monkeypatch.setattr(hf, "_discovered_appdata_hf_cache_roots", lambda: []) + assert str(tmp_path / "user/.cache/huggingface/hub") in [path for _, path in hf._hf_cache_locations()] + + +@pytest.fixture +def caches(tmp_path, monkeypatch): + primary, secondary = tmp_path / "primary", tmp_path / "secondary" + primary.mkdir() + snapshot = secondary / "models--example--audio" / "snapshots" / REVISION + snapshot.mkdir(parents=True) + (snapshot / "model_index.json").write_text("{}") + monkeypatch.setitem(hf.CONFIG.hf, "cache_dir", str(primary)) + monkeypatch.setattr(hf, "_hf_cache_locations", lambda: [("configured", str(primary)), ("default", str(secondary))]) + return primary, secondary, snapshot + + +def test_exact_installed_secondary_snapshot_is_loadable(caches): + _, _, snapshot = caches + assert hf.exact_cached_snapshot_path("example/audio", REVISION) == snapshot + + +@pytest.mark.parametrize("canonical_alias", [False, True]) +def test_cache_root_alias_accepts_lexical_and_canonical_files(tmp_path, monkeypatch, canonical_alias): + actual = tmp_path / "actual" + actual.mkdir() + configured = tmp_path / "configured" + configured.symlink_to(actual, target_is_directory=True) + weight = actual / "weight.safetensors" + weight.write_bytes(b"cached") + monkeypatch.setattr(hf, "_hf_cache_locations", lambda: [("configured", str(configured))]) + alias = weight if canonical_alias else configured / weight.name + assert hf.resolve_managed_hf_cache_file(alias) == weight.resolve() + + outside = tmp_path / "outside.safetensors" + outside.write_bytes(b"private") + (actual / "escape.safetensors").symlink_to(outside) + with pytest.raises(ValueError, match="outside"): + hf.resolve_managed_hf_cache_file(alias.parent / "escape.safetensors") + + +def test_primary_cache_wins_and_wrong_revision_never_substitutes(caches): + primary, _, _ = caches + snapshot = primary / "models--example--audio" / "snapshots" / REVISION + snapshot.mkdir(parents=True) + (snapshot / "model_index.json").write_text("{}") + assert hf.exact_cached_snapshot_path("example/audio", REVISION) == snapshot + with pytest.raises(FileNotFoundError): + hf.exact_cached_snapshot_path("example/audio", "b" * 40) + + +def test_cross_cache_symlink_does_not_expand_file_authority(caches, tmp_path): + primary, secondary, snapshot = caches + outside = tmp_path / "private.json" + outside.write_text("{}") + link = snapshot / "escape.json" + link.symlink_to(outside) + with pytest.raises(ValueError, match="outside"): + hf.resolve_managed_hf_cache_file(link) + other_cache_file = primary / "other.json" + other_cache_file.write_text("{}") + linked = secondary / "other.json" + linked.symlink_to(other_cache_file) + with pytest.raises(ValueError, match="outside"): + hf.resolve_managed_hf_cache_file(linked) diff --git a/tests/test_native_pag_recipes.py b/tests/test_native_pag_recipes.py new file mode 100644 index 00000000..088816d6 --- /dev/null +++ b/tests/test_native_pag_recipes.py @@ -0,0 +1,177 @@ +"""Native PAG recipes exercise official guidance; no model downloads required.""" +from unittest.mock import patch + +import pytest +import importlib.util +import torch + +from modules import MODULE_MAP +from modules.ModularDiffusers.guiders import Guider, Layers +from modiff.diffusers_profiles import public_execution_profiles, resolve_execution_profiles_for_loader +from modiff.operation_catalog import build_operation_catalog +from modiff.operation_starters import resolve_operation_starter + + +@pytest.fixture(scope="module") +def catalog(): + return build_operation_catalog(MODULE_MAP, public_execution_profiles(), catalog_resolver=lambda: {}) + + +@pytest.mark.parametrize("task", [ + "text_to_image", "image_to_image", "inpaint", "control_image", "control_edit_image", +]) +def test_pag_recipes_have_one_explicit_guider_and_preserve_native_stage_wires(catalog, task): + contracts, support = catalog + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed model")): + result = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": "StableDiffusionXLModularPipeline", "task": task, + "executionProfileId": "sdxl-pag:modular", + }) + nodes = {node["operation"]["operationId"]: node for node in result["nodes"]} + assert {"diffusion.encode_prompt", "diffusion.denoise", "diffusion.decode_latents"} <= nodes.keys() + loader = nodes["diffusion.load_models"] + profiles, reason = resolve_execution_profiles_for_loader(loader["module"], loader["action"], loader["values"]) + assert reason is None and [p.id for p in profiles] == ["sdxl-base:modular"] + assert loader["values"]["model_type"] == "StableDiffusionXLModularPipeline" + guidance = nodes["diffusion.guidance"]["values"] + assert guidance["guider"] == "PerturbedAttentionGuidance" + assert guidance["perturbed_guidance_scale"] == 3 + assert guidance["perturbed_guidance_start"] == 0 and guidance["perturbed_guidance_stop"] == 1 + layers = nodes["diffusion.guidance_layers"]["values"] + assert layers["blocks_select"] == ["mid_block.attentions.0.transformer_blocks"] + assert layers["mid_block.attentions.0.transformer_blocks"]["indices"] == "0,1,2,3,4,5,6,7,8,9" + consumers = {e["target"] for e in result["edges"] if e["source"] == "diffusion.guidance"} + expected = {key for key, node in nodes.items() if "guider" in node["params"] and key != "diffusion.guidance"} + assert consumers == expected + assert not any(item["field"] == "guider" for item in result["requiredInputs"]) + row = next(p for p in support if p["pipelineClass"] == result["pipelineClass"]) + assert "sdxl-pag:modular" in next(t for t in row["tasks"] if t["task"] == task)["executionProfileIds"] + + +def test_pag_profile_does_not_leak_to_unreviewed_tasks_or_change_old_standard_profile(catalog): + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES + contracts, _ = catalog + with pytest.raises(ValueError, match="pipeline/task"): + resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": "StableDiffusionXLModularPipeline", "task": "control_inpaint", + "executionProfileId": "sdxl-pag:modular", + }) + assert DIFFUSERS_EXECUTION_PROFILES["sdxl-pag:direct"].pipeline_class == "StableDiffusionXLPAGPipeline" + + +def _guider(**changes): + node = object.__new__(Guider) + node.node_id = "pag-test" + return node.execute(**{ + "guider": "PerturbedAttentionGuidance", "model_type": "StableDiffusionXLModularPipeline", + "layers_config": [{"fqn": "mid_block.attentions.0.transformer_blocks", "indices": list(range(10))}], + "guidance_scale": 5.0, "perturbed_guidance_scale": 3.0, + "perturbed_guidance_start": 0.0, "perturbed_guidance_stop": 1.0, **changes, + })["guider_out"] + + +def test_pag_recipe_uses_all_mid_layers_and_layers_reject_other_pipeline(): + guider = _guider() + config, = guider.skip_layer_config + assert config.indices == list(range(10)) + assert config.fqn == "mid_block.attentions.0.transformer_blocks" + assert config.skip_attention_scores and not config.skip_attention and not config.skip_ff + node = object.__new__(Layers) + block = "mid_block.attentions.0.transformer_blocks" + with pytest.raises(ValueError, match="allowed"): + node.execute(model_type="QwenImageModularPipeline", blocks_select=[block], **{block: {"indices": "0"}}) + + +def test_actual_upstream_attention_changes_only_during_perturbed_pass_and_restores(): + from diffusers.models.attention import BasicTransformerBlock + from diffusers.modular_pipelines.modular_pipeline import BlockState + + # The real upstream hook traverses precisely the same FQN as SDXL. Tiny + # random CPU layers test attention semantics, not model/image quality. + torch.manual_seed(17) + model = torch.nn.Module() + model.mid_block = torch.nn.Module() + attention = torch.nn.Module() + attention.transformer_blocks = torch.nn.ModuleList([ + BasicTransformerBlock(dim=8, num_attention_heads=2, attention_head_dim=4, cross_attention_dim=8) + for _ in range(10) + ]) + model.mid_block.attentions = torch.nn.ModuleList([attention]) + layer = attention.transformer_blocks[0] + inputs, context = torch.randn(1, 4, 8), torch.randn(1, 3, 8) + baseline = layer(inputs, encoder_hidden_states=context) + guider = _guider() + guider.set_state(step=0, num_inference_steps=2, timestep=torch.tensor(999)) + # At this pin the native guider's start boundary is exclusive. Do not + # misrepresent it as bitwise-equivalent to the standard PAG pipeline. + assert len(guider.prepare_inputs({"context": (context, context)})) == 2 + guider.set_state(step=1, num_inference_steps=2, timestep=torch.tensor(500)) + batches = guider.prepare_inputs({"context": (context, context)}) + assert len(batches) == 3 + predictions = [] + for batch in batches: + guider.prepare_models(model) + try: + batch.noise_pred = layer(inputs, encoder_hidden_states=batch.context) + predictions.append(batch.noise_pred) + finally: + guider.cleanup_models(model) + assert torch.equal(predictions[0], baseline) + assert torch.equal(predictions[1], baseline) + assert not torch.allclose(predictions[2], baseline) + assert torch.equal(layer(inputs, encoder_hidden_states=context), baseline) + combined = guider(batches)[0] + assert torch.allclose(combined, baseline + 3 * (baseline - predictions[2])) + assert isinstance(batches[0], BlockState) + + +@pytest.mark.parametrize("profile_id,pipeline,steps,guidance", [ + ("flux-schnell:modular", "FluxModularPipeline", 4, 0.0), + ("flux-krea:modular", "FluxModularPipeline", 28, 3.5), + ("sdxl-turbo:modular", "StableDiffusionXLModularPipeline", 1, 0.0), + ("flux2-klein-kv:t2i-modular", "Flux2KleinModularPipeline", 4, 0.0), +]) +def test_exact_native_artifact_defaults_and_loader_admission(catalog, profile_id, pipeline, steps, guidance): + from modules.ModularDiffusers.loaders import ModelsLoader + contracts, _ = catalog + result = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": pipeline, "task": "text_to_image", "executionProfileId": profile_id, + }) + nodes = {n["operation"]["operationId"]: n for n in result["nodes"]} + loader = nodes["diffusion.load_models"]["values"] + resolved, reason = resolve_execution_profiles_for_loader("modules.ModularDiffusers", "ModelsLoader", loader) + assert reason is None and [p.id for p in resolved] == [profile_id] + _, repository, revision = ModelsLoader._reviewed_builtin_selection( + model_type=loader["model_type"], repo_id=loader["repo_id"], revision=loader["revision"], + ) + assert repository == loader["repo_id"]["value"] and revision == loader["revision"] + denoise = nodes["diffusion.denoise"]["values"] + assert denoise["num_inference_steps"] == steps and denoise["guidance_scale"] == guidance + if profile_id == "flux-schnell:modular": + assert nodes["diffusion.encode_prompt"]["values"]["max_sequence_length"] == 256 + if profile_id == "sdxl-turbo:modular": + assert denoise["width"] == denoise["height"] == 512 + assert nodes["diffusion.guidance"]["values"]["guidance_scale"] == 0 + + +@pytest.mark.skipif(importlib.util.find_spec("transformers") is None, + reason="requires the staged optional Transformers runtime") +def test_schnell_prompt_limit_is_enforced_before_pipeline_construction(): + import diffusers + from modules.ModularDiffusers.embeddings import EncodePrompt + node = object.__new__(EncodePrompt) + node._pipeline_class = diffusers.FluxModularPipeline + with pytest.raises(ValueError, match="schnell.*256"): + node.execute(text_encoders={"model_type": "FluxModularPipeline", + "repo_id": "black-forest-labs/FLUX.1-schnell"}, + prompt="test", max_sequence_length=300) + + +def test_operation_recipes_do_not_replace_legacy_auto_owners(): + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES, execution_profiles_for_execution + for model_type, expected in (("FluxSchnellPipeline", "flux-schnell:direct"), + ("FluxKreaPipeline", "flux-krea:direct")): + assert [p.id for p in execution_profiles_for_execution(model_type, "text_to_image")] == [expected] + recipe = DIFFUSERS_EXECUTION_PROFILES["sdxl-pag:modular"] + assert recipe.to_public_dict()["operation_recipe"] is True + assert recipe.public and recipe.pipeline_class == "StableDiffusionXLModularPipeline" diff --git a/tests/test_native_reference_recipes.py b/tests/test_native_reference_recipes.py new file mode 100644 index 00000000..88ae338b --- /dev/null +++ b/tests/test_native_reference_recipes.py @@ -0,0 +1,77 @@ +"""Reference ordering/composition must agree with the exact upstream variant.""" +from types import SimpleNamespace + +import pytest +import importlib.util +import torch +from PIL import Image + +from modules import MODULE_MAP +from modiff.operation_catalog import build_operation_catalog +from modiff.operation_starters import resolve_operation_starter +from modules.ImageOperations.main import StitchImages, stitch_reference_images +from modules.ModularDiffusers.latents import prepare_image_for_vae_pipeline + + +@pytest.mark.parametrize("pipeline,profile", [ + ("FluxKontextModularPipeline", "flux-kontext:modular"), + ("Flux2ModularPipeline", "flux2:modular"), + ("Flux2KleinModularPipeline", "flux2-klein:modular"), +]) +def test_native_reference_task_selects_real_stages_and_only_kontext_composes(pipeline, profile): + contracts, _ = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {}) + result = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": pipeline, "task": "multi_image_reference_edit", "executionProfileId": profile, + }) + nodes = {node["operation"]["operationId"]: node for node in result["nodes"]} + assert "diffusion.encode_image" in nodes and "diffusion.denoise" in nodes + if pipeline == "FluxKontextModularPipeline": + assert nodes["diffusion.compose_references"]["values"]["layout"] == "horizontal_reference" + assert {"source": "diffusion.compose_references", "sourceHandle": "output", + "target": "diffusion.encode_image", "targetHandle": "image"} in result["edges"] + assert {"operationId": "diffusion.compose_references", "field": "image"} in result["requiredInputs"] + else: + assert "diffusion.compose_references" not in nodes + + +def test_reference_canvas_matches_standard_adapter_preserves_order_and_bounds(): + from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS, prepare_reference_images + images = [Image.new("RGB", (4, 2), "red"), Image.new("RGB", (2, 4), "blue")] + node = object.__new__(StitchImages) + result = node.execute(image=images, layout="horizontal_reference") + standard = prepare_reference_images(images, IMAGE_PIPELINE_ADAPTERS["FluxKontextPipeline"]) + assert result["output"].size == (10, 4) and result["count"] == 2 + assert result["output"].tobytes() == standard.tobytes() + assert result["output"].getpixel((0, 0)) == (255, 0, 0) + assert result["output"].getpixel((9, 0)) == (0, 0, 255) + with pytest.raises(ValueError, match="eight"): + stitch_reference_images([images[0]] * 9) + with pytest.raises(ValueError, match="execution limit"): + stitch_reference_images([Image.new("RGB", (8192, 1)), Image.new("RGB", (1, 2))]) + + +@pytest.mark.parametrize("pipeline", ["Flux2ModularPipeline", "Flux2KleinModularPipeline"]) +def test_flux2_references_remain_ordered_individual_images_and_are_bounded(pipeline): + images = [Image.new("RGB", (32, 64), "red"), Image.new("RGB", (64, 32), "blue")] + cls = type(pipeline, (), {}) + assert prepare_image_for_vae_pipeline(images, cls) is images + for invalid in ([], images * 5): + with pytest.raises(ValueError, match="eight"): + prepare_image_for_vae_pipeline(invalid, cls) + + +@pytest.mark.skipif(importlib.util.find_spec("transformers") is None, + reason="requires the staged optional Transformers runtime") +def test_actual_upstream_flux2_packs_separate_reference_tokens_and_position_ids(): + from diffusers.modular_pipelines import PipelineState + from diffusers.modular_pipelines.flux2.before_denoise import Flux2PrepareImageLatentsStep + state = PipelineState() + state.set("image_latents", [torch.ones(1, 4, 2, 3), torch.full((1, 4, 3, 2), 2.0)]) + state.set("batch_size", 1) + state.set("num_images_per_prompt", 1) + _, state = Flux2PrepareImageLatentsStep()(SimpleNamespace(_execution_device=torch.device("cpu")), state) + latents = state.get("image_latents") + ids = state.get("image_latent_ids") + assert latents.shape == (1, 12, 4) + assert torch.all(latents[:, :6] == 1) and torch.all(latents[:, 6:] == 2) + assert torch.all(ids[:, :6, 0] == 10) and torch.all(ids[:, 6:, 0] == 20) diff --git a/tests/test_node_base.py b/tests/test_node_base.py index f59ee7a2..80dbcfec 100644 --- a/tests/test_node_base.py +++ b/tests/test_node_base.py @@ -19,6 +19,53 @@ class NodeBaseDeepEqualTests(unittest.TestCase): + def test_snapshot_bounds_branching_cycles_and_detects_nested_edits(self): + from modiff.node_cache_identity import input_snapshot + value = {'items': [1]} + value['left'] = value + value['right'] = value + before = input_snapshot(value) + self.assertEqual(input_snapshot(value), before) + value['items'].append(2) + self.assertNotEqual(input_snapshot(value), before) + + def test_cache_detects_in_place_inputs_and_replaced_implementation(self): + import torch + from PIL import Image + + class Consumer(NodeBase): + def execute(self, **kwargs): + self.calls += 1 + return {"result": self.calls} + + module = '.'.join(Consumer.__module__.split('.')[:-1]) + definition = {module: {'Consumer': {'skipParamsCheck': True, 'params': {}}}} + with patch('modiff.NodeBase._module_map', return_value=definition): + node = Consumer('consumer') + node.calls = 0 + tensor = torch.zeros(2) + array = np.zeros(2) + image = Image.new('RGB', (2, 2)) + value = {'nested': [1], 'tensor': tensor, 'array': array, 'image': image} + node(value=value) + node(value=value) + self.assertEqual(node.calls, 1) + for mutate in ( + lambda: value['nested'].append(2), lambda: tensor.add_(1), + lambda: array.fill(1), lambda: image.putpixel((0, 0), (1, 2, 3)), + ): + before = node.calls + mutate() + node(value=value) + self.assertEqual(node.calls, before + 1) + node(value=value) + self.assertEqual(node.calls, before + 1) + def replacement(self, **kwargs): + self.calls += 1 + return {'result': 'new implementation'} + with patch.object(Consumer, 'execute', replacement): + self.assertEqual(node(value=value)['result'], 'new implementation') + def test_optional_outputs_cache_success_but_not_failed_or_invalid_results(self): class OptionalNode(NodeBase): def execute(self, mode): @@ -287,9 +334,14 @@ def execute(self, value): self.assertEqual(node(value=4), {"result": 8}) self.assertFalse(node._has_changed) self.assertEqual(node.execution_count, 1) + self.assertEqual(node._cache_reason, "unchanged_inputs") + self.assertEqual(node(value=5), {"result": 10}) + self.assertEqual(node._cache_reason, "inputs_changed") + node.invalidate_cache() self.assertEqual(node(value=5), {"result": 10}) + self.assertEqual(node._cache_reason, "invalidated") self.assertTrue(node._has_changed) - self.assertEqual(node.execution_count, 2) + self.assertEqual(node.execution_count, 3) def test_changed_upstream_node_invalidates_consumer_of_same_mutable_object(self): from modiff.server import WebServer @@ -697,6 +749,45 @@ class LoaderNode(NodeBase): ) self.assertEqual(component_call.kwargs["phase"], "component_loading") + def test_fast_weight_loading_bounds_updates_but_keeps_initial_and_terminal_counts(self): + from modiff.NodeBase import _StructuredLoadingProgress + + for iterable in (True, False): + with self.subTest(iterable=iterable): + reports = [] + bar = range(1000) if iterable else SimpleNamespace(n=0, update=lambda _amount: None) + progress = _StructuredLoadingProgress( + bar, + lambda value, message, current, total: reports.append((value, current, total)), + description="Loading weights", + total=1000, + ) + with patch("modiff.NodeBase.time.monotonic", return_value=10.0): + if iterable: + self.assertEqual(list(progress), list(range(1000))) + else: + for _ in range(1000): + progress.update(1) + self.assertEqual(reports[0][1], 0 if iterable else 1) + self.assertEqual(reports[-1], (99, 1000, 1000)) + self.assertLessEqual(len(reports), 3, "Rapid weight updates must not flood the browser.") + + def test_loading_progress_reports_latest_count_after_interval_and_always_finishes(self): + from modiff.NodeBase import _StructuredLoadingProgress + + reports = [] + progress = _StructuredLoadingProgress( + SimpleNamespace(n=0, update=lambda _amount: None), + lambda value, message, current, total: reports.append(current), + description="Loading weights", total=5, + ) + with patch("modiff.NodeBase.time.monotonic", side_effect=[10.0, 10.1, 10.3, 10.31]): + progress.update(1) + progress.update(1) + progress.update(1) + progress.update(2) + self.assertEqual(reports, [1, 3, 5]) + def test_structured_loader_progress_publishes_count_finalized_on_close(self): from modiff.NodeBase import _StructuredLoadingProgress @@ -747,14 +838,27 @@ def test_nested_audio_arrays_compare_without_image_attributes(self): def test_direct_node_base_imports_preserve_complete_module_registry(self): script = """ import json +import tempfile +from modiff.custom_extensions import ExtensionStore + +# This is a cold built-in registry test. It must not execute or disable the +# operator's approved extensions when the child has only the base runtime. +directory = tempfile.TemporaryDirectory(prefix='modiff-registry-test-') +original_init = ExtensionStore.__init__ +def isolated_init(self, root=None): + original_init(self, root if root is not None else directory.name) +ExtensionStore.__init__ = isolated_init + from modiff.NodeBase import NodeBase import modules +assert not any(name.startswith('custom.') for name in modules.MODULE_MAP) print(json.dumps({ "module_count": len(modules.MODULE_MAP), "node_count": modules.total_nodes, "recomputed_node_count": sum(len(nodes) for nodes in modules.MODULE_MAP.values()), "module_names": sorted(modules.MODULE_MAP), })) +directory.cleanup() """ result = subprocess.run( [sys.executable, "-c", script], @@ -772,6 +876,27 @@ def test_direct_node_base_imports_preserve_complete_module_registry(self): {"modules.DiffusersImage", "modules.ModularDiffusers"}.issubset(payload["module_names"]) ) + def test_startup_registry_count_includes_enabled_extensions(self): + script = """ +from modiff.custom_extensions import ExtensionStore +def load_fixture(self, registry): + registry['custom.RegistryCountFixture'] = {'First': {}, 'Second': {}} +ExtensionStore.load_enabled = load_fixture +from modiff.NodeBase import NodeBase +import modules +assert len(modules.MODULE_MAP['custom.RegistryCountFixture']) == 2 +assert modules.total_nodes == sum(len(nodes) for nodes in modules.MODULE_MAP.values()) +""" + result = subprocess.run( + [sys.executable, "-c", script], + cwd=Path(__file__).resolve().parents[1], + capture_output=True, + text=True, + timeout=120, + check=False, + ) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_node_cache_cleanup.py b/tests/test_node_cache_cleanup.py index f754a840..c050a509 100644 --- a/tests/test_node_cache_cleanup.py +++ b/tests/test_node_cache_cleanup.py @@ -19,6 +19,137 @@ def server(self): def request(self, nodes): return SimpleNamespace(json=AsyncMock(return_value={"nodes": nodes})) + async def test_failed_worker_releases_exception_tensor_references_before_next_task(self): + import gc + import weakref + server = self.server() + server.loop = asyncio.get_running_loop() + refs, observations = [], [] + class Model: pass + def allocate_then_fail(): + model = Model() + refs.append(weakref.ref(model)) + try: + raise RuntimeError('HIP out of memory') + except RuntimeError as cause: + raise RuntimeError('Error executing model') from cause + def retry(): + gc.collect() + observations.append(refs[0]() is None) + future = server.loop.create_future() + await server.queue_task(allocate_then_fail, (), future, 'test', name='Failure probe') + await server.queue_task(retry, (), None, 'test', name='Retry probe') + server.main_queue.put_nowait(None) + try: + with patch.object(server, '_best_effort_device_cache_clear', return_value=[]): + await asyncio.wait_for(server._main_worker(), timeout=10) + self.assertEqual(observations, [True]) + error = future.exception() + self.assertEqual(server._classify_exception(error)['category'], 'oom') + self.assertEqual(server.recent_tasks[0]['status'], 'completed') + self.assertEqual(server.recent_tasks[1]['status'], 'failed') + finally: + if future.done(): + future.exception() + await server.cleanup() + + async def test_release_drops_transitive_consumers_but_preserves_unrelated_shared_owner(self): + server = self.server() + node = lambda *sources: SimpleNamespace(_cache_input_sources=frozenset(sources), _mm_models=[]) + server.node_cache = {'load': node(), 'encode': node('load'), 'denoise': node('encode'), + 'other-load': node(), 'other-output': node('other-load')} + shared = object() + manager = SimpleNamespace(collections={'load': {'weights'}, 'other-load': {'weights'}}, + components={'weights': shared}) + with patch.dict(sys.modules, {'modules.ModularDiffusers': SimpleNamespace(components=manager)}): + result = json.loads((await server.delete_cache(self.request(['load']))).text) + self.assertEqual(set(result['nodes']), {'load', 'encode', 'denoise'}) + self.assertEqual(set(server.node_cache), {'other-load', 'other-output'}) + self.assertIs(manager.components['weights'], shared) + + async def test_standard_release_preserves_shared_models_without_cpu_offload(self): + server = self.server() + removed, shared = object(), object() + class Owner: + def __init__(self, ids): + self._mm_models = ids + def __del__(self): + for key in self._mm_models: + manager.remove(key) + manager = SimpleNamespace(cache={'removed': removed, 'shared': shared}, remove=Mock()) + server.node_cache = {'remove': Owner(['removed', 'shared']), 'keep': Owner(['shared'])} + with patch('modiff.server.memory_manager', manager): + await server.delete_cache(self.request(['remove'])) + self.assertEqual(manager.cache, {'shared': shared}) + self.assertEqual(server.node_cache['keep']._mm_models, ['shared']) + manager.remove.assert_not_called() + server.node_cache['keep']._mm_models = [] + + async def test_model_callbacks_keep_one_worker_while_http_pool_remains_available(self): + server = self.server() + try: + ids = [await server._run_executor_callback(threading.get_ident) for _ in range(5)] + self.assertEqual(len(set(ids)), 1) + started, release = threading.Event(), threading.Event() + def block(): + started.set() + release.wait(2) + task = asyncio.create_task(server._run_executor_callback(block)) + while not started.is_set(): + await asyncio.sleep(0.001) + try: + control_id = await asyncio.wait_for( + server._run_executor_callback(threading.get_ident, model_work=False), timeout=0.5, + ) + self.assertNotEqual(control_id, ids[0]) + finally: + release.set() + await task + finally: + await server.cleanup() + + async def test_output_invalidation_retains_component_owners_and_objects(self): + server = self.server() + loader = SimpleNamespace(_mm_models=[], invalidate_cache=Mock()) + standard = SimpleNamespace(_mm_models=['pipeline'], invalidate_cache=Mock()) + encode = SimpleNamespace(_mm_models=[], invalidate_cache=Mock()) + nested = 'block-v2-node:5:block:6:encode' + server.node_cache = {'load': loader, 'standard': standard, nested: encode} + manager = SimpleNamespace(collections={'load': {'weights'}}, components={'weights': object()}) + before = dict(server.node_cache) + request = SimpleNamespace(json=AsyncMock(return_value={'nodes': ['load', 'standard', 'block'], 'scope': 'outputs'})) + with patch.dict(sys.modules, {'modules.ModularDiffusers': SimpleNamespace(components=manager)}): + response = await server.delete_cache(request) + result = json.loads(response.text) + self.assertEqual(result['nodes'], [nested]) + self.assertEqual(result['retainedModelNodes'], ['load', 'standard']) + self.assertEqual(server.node_cache, before) + self.assertEqual(set(manager.components), {'weights'}) + encode.invalidate_cache.assert_called_once_with() + loader.invalidate_cache.assert_not_called() + standard.invalidate_cache.assert_not_called() + + async def test_output_invalidation_waits_for_active_execution(self): + server = self.server() + node = SimpleNamespace(_mm_models=[], invalidate_cache=Mock()) + server.node_cache['encode'] = node + await server._node_cache_lock.acquire() + request = SimpleNamespace(json=AsyncMock(return_value={'nodes': ['encode'], 'scope': 'outputs'})) + action = asyncio.create_task(server.delete_cache(request)) + await asyncio.sleep(0.02) + node.invalidate_cache.assert_not_called() + server._node_cache_lock.release() + self.assertEqual((await action).status, 200) + node.invalidate_cache.assert_called_once_with() + self.assertIs(server.node_cache['encode'], node) + + async def test_unknown_cache_scope_cannot_destroy_model_objects(self): + server = self.server() + server.node_cache['load'] = object() + request = SimpleNamespace(json=AsyncMock(return_value={'nodes': '*', 'scope': 'typo'})) + self.assertEqual((await server.delete_cache(request)).status, 400) + self.assertIn('load', server.node_cache) + def test_selective_component_destruction_preserves_shared_owners_and_hooks(self): server = self.server() a, b, shared = object(), object(), object() @@ -132,7 +263,7 @@ async def test_field_actions_wait_for_cache_ownership(self): server = self.server() server._field_action = AsyncMock(return_value="ok") await server._node_cache_lock.acquire() - action = asyncio.create_task(server.field_action(None)) + action = asyncio.create_task(server.field_action(self.request([]))) await asyncio.sleep(0.02) server._field_action.assert_not_awaited() server._node_cache_lock.release() diff --git a/tests/test_node_signal_compatibility.py b/tests/test_node_signal_compatibility.py new file mode 100644 index 00000000..f02ded51 --- /dev/null +++ b/tests/test_node_signal_compatibility.py @@ -0,0 +1,117 @@ +from collections.abc import Mapping + +from modules import MODULE_MAP + + +def _descriptors(value): + return value if isinstance(value, list) else [value] + + +def test_all_registered_signal_compatibility_declarations_are_resolved_and_bounded(): + declarations = [] + for module_name, nodes in MODULE_MAP.items(): + for action_name, node in nodes.items(): + for field_name, param in node.get("params", {}).items(): + connection_role = param.get("connectionRole") + if connection_role is not None: + assert isinstance(connection_role, str) and connection_role.strip() == connection_role + declaration = param.get("signalCompatibility") + if declaration is None: + continue + path = f"{module_name}.{action_name}.{field_name}" + declarations.append(path) + assert isinstance(declaration, Mapping), path + assert set(declaration).issubset({"required", "values", "action", "role"}), path + assert isinstance(declaration.get("required", False), bool), path + assert not ({"values", "action"} <= set(declaration)), path + assert any(key in declaration for key in ("values", "action", "role")), path + if "role" in declaration: + roles = declaration["role"] + roles = roles if isinstance(roles, list) else [roles] + assert roles and all(isinstance(role, str) and role.strip() == role for role in roles), path + if "values" in declaration: + values = declaration["values"] + assert isinstance(values, Mapping) and len(values) <= 256, path + for signal_value, capabilities in values.items(): + assert isinstance(signal_value, str) and signal_value.strip() == signal_value, path + assert isinstance(capabilities, (list, dict)) and capabilities, path + elif "action" in declaration: + action = declaration["action"] + assert isinstance(action, str) and action.strip() == action and action, path + + assert declarations, "the public registry must expose semantic connector contracts" + + +def test_models_loader_outputs_publish_distinct_component_roles(): + params = MODULE_MAP["modules.ModularDiffusers"]["ModelsLoader"]["params"] + expected = { + "text_encoders": "text_encoders", + "unet_out": "denoiser", + "vae_out": "vae", + "scheduler": "scheduler", + "image_encoder": "image_encoder", + } + assert {field: params[field].get("connectionRole") for field in expected} == expected + assert len(set(expected.values())) == len(expected) + + +def test_all_builtin_dynamic_model_and_pipeline_consumers_declare_compatibility(): + audited = [] + for module_name, nodes in MODULE_MAP.items(): + if not module_name.startswith("modules."): + continue + for action_name, node in nodes.items(): + for field_name, param in node.get("params", {}).items(): + descriptors = _descriptors(param.get("onSignal")) + has_dynamic_exec = any( + descriptor == "update_node" + or ( + isinstance(descriptor, Mapping) + and descriptor.get("action") == "exec" + and descriptor.get("data") in { + "update_node", + "update_audio_contract", + "update_adapter_modes", + "update_image_contract", + "update_three_d_contract", + } + ) + for descriptor in descriptors + ) + param_type = param.get("type") + semantic_transport = isinstance(param_type, str) and param_type in { + "diffusers_auto_model", + "diffusers_auto_models", + "audio_diffusion_pipeline", + "image_diffusion_pipeline", + "three_d_diffusion_pipeline", + "video_diffusion_pipeline", + } + if has_dynamic_exec and semantic_transport: + path = f"{module_name}.{action_name}.{field_name}" + audited.append(path) + assert "signalCompatibility" in param, path + + assert audited + + +def test_all_pipeline_signal_relays_publish_a_declared_output_signal(): + audited = [] + for module_name, nodes in MODULE_MAP.items(): + for action_name, node in nodes.items(): + params = node.get("params", {}) + for field_name, param in params.items(): + for descriptor in _descriptors(param.get("onSignal")): + if not isinstance(descriptor, Mapping) or descriptor.get("action") != "signal": + continue + target = descriptor.get("target") + target_param = params.get(target) + if not isinstance(target_param, Mapping) or target_param.get("display") != "output": + continue + path = f"{module_name}.{action_name}.{field_name}->{target}" + audited.append(path) + signal = target_param.get("signal") + assert isinstance(signal, Mapping), path + assert signal.get("direction") == "output", path + + assert audited diff --git a/tests/test_node_ux_backend.py b/tests/test_node_ux_backend.py new file mode 100644 index 00000000..646af825 --- /dev/null +++ b/tests/test_node_ux_backend.py @@ -0,0 +1,36 @@ +"""The isolated browser backend must start on Windows event loops too.""" + +import asyncio +import importlib.util +from pathlib import Path +from types import SimpleNamespace + + +def test_windows_loop_without_signal_handlers(): + path = Path(__file__).resolve().parents[1] / "scripts/run_node_ux_backend.py" + spec = importlib.util.spec_from_file_location("node_ux_backend", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + calls = [] + + def unsupported(number, callback): + calls.append(number) + raise NotImplementedError + + module.install_stop_handlers(SimpleNamespace(add_signal_handler=unsupported), asyncio.Event()) + assert calls == [module.signal.SIGINT, module.signal.SIGTERM] + + +def test_supported_loop_registers_stop_callbacks(): + path = Path(__file__).resolve().parents[1] / "scripts/run_node_ux_backend.py" + spec = importlib.util.spec_from_file_location("node_ux_backend", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + stopped = asyncio.Event() + callbacks = [] + module.install_stop_handlers( + SimpleNamespace(add_signal_handler=lambda number, callback: callbacks.append(callback)), stopped + ) + assert len(callbacks) == 2 + callbacks[0]() + assert stopped.is_set() diff --git a/tests/test_operation_catalog.py b/tests/test_operation_catalog.py new file mode 100644 index 00000000..c87339c1 --- /dev/null +++ b/tests/test_operation_catalog.py @@ -0,0 +1,200 @@ +import unittest +from unittest.mock import patch + +from modules import MODULE_MAP + + +class OperationCatalogTests(unittest.TestCase): + def test_shared_standard_pipeline_starters_retain_the_selected_public_profile(self): + from modiff.diffusers_profiles import public_execution_profiles, resolve_execution_profiles_for_loader + from modiff.operation_catalog import build_operation_catalog + from modiff.operation_starters import resolve_operation_starter + + contracts, _ = build_operation_catalog(MODULE_MAP, public_execution_profiles(), catalog_resolver=lambda: {}) + for pipeline, expected in ( + ("WanImageToVideoPipeline", "wan-22-image-to-video:direct"), + ("Wan22Image2VideoModularPipeline", "wan22-i2v:equivalent-standard"), + ): + with self.subTest(pipeline=pipeline): + starter = resolve_operation_starter( + MODULE_MAP, contracts, {"pipelineClass": pipeline, "task": "image_to_video"} + ) + loader = next(node for node in starter["nodes"] if node["action"] == "LoadPipeline") + self.assertEqual(loader["values"].get("execution_profile_id"), expected) + profiles, reason = resolve_execution_profiles_for_loader( + loader["module"], loader["action"], loader["values"] + ) + self.assertIsNone(reason) + self.assertEqual([profile.id for profile in profiles], [expected]) + + def test_shared_pipeline_binding_does_not_guess_between_profiles_for_one_identity(self): + from dataclasses import replace + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES, public_execution_profiles + from modiff.operation_catalog import build_operation_catalog, resolve_operation + + duplicate = replace(DIFFUSERS_EXECUTION_PROFILES["wan-22-image-to-video:direct"], id="another-reviewed-route") + with patch.dict(DIFFUSERS_EXECUTION_PROFILES, {duplicate.id: duplicate}): + contracts, _ = build_operation_catalog(MODULE_MAP, public_execution_profiles(), catalog_resolver=lambda: {}) + loader = resolve_operation(MODULE_MAP, contracts, { + "pipelineClass": "WanImageToVideoPipeline", "task": "image_to_video", + "operationId": "diffusion.load_models", + }) + self.assertNotIn("execution_profile_id", loader["values"]) + + def test_support_separates_adapters_dependencies_and_editable_stages(self): + from modiff.operation_catalog import build_operation_catalog + from modiff.diffusers_profiles import public_execution_profiles + + # Exercise missing-overlay support even on hosts whose reviewed profile + # delivers these dependencies in the base environment. + profiles = public_execution_profiles(platform_name="linux", machine="x86_64") + for profile in profiles: + requirement = profile["optionalRuntimeRequirement"] + if requirement["requiredNow"]: + requirement.update(state="missing", reason="optional_runtime_missing") + contracts, support = build_operation_catalog(MODULE_MAP, profiles, catalog_resolver=lambda: {}) + by_class = {p["pipelineClass"]: p for p in support} + self.assertGreaterEqual(len(by_class), 330) + qwen = next(t for t in by_class["QwenImageModularPipeline"]["tasks"] if t["task"] == "text_to_image") + self.assertEqual(qwen["execution"], "adapter") + self.assertEqual(qwen["decomposition"], "stages") + self.assertEqual(qwen["dependencies"], "blocked") + krea = next(t for t in by_class["Krea2ModularPipeline"]["tasks"] if t["task"] == "text_to_image") + self.assertEqual(krea["execution"], "declared") + self.assertEqual(krea["dependencies"], "unknown") + self.assertTrue(all("template" not in str(c["binding"]).lower() for c in contracts)) + self.assertFalse( + any(c["pipelineClass"] == "LTXModularPipeline" and c["task"] == "video_to_video" for c in contracts) + ) + for call in (c for c in contracts if c["decomposition"] == "pipeline"): + self.assertTrue( + any( + c["decomposition"] == "loader" + and c["pipelineClass"] == call["pipelineClass"] + and c["task"] == call["task"] + and c["binding"]["pipelineClass"] == call["binding"]["pipelineClass"] + and c["nodeKey"].rsplit(".", 1)[0] == call["nodeKey"].rsplit(".", 1)[0] + for c in contracts + ) + ) + fallback = next( + c + for c in contracts + if c["pipelineClass"] == "LTXModularPipeline" + and c["task"] == "text_to_video" + and c["decomposition"] == "pipeline" + ) + self.assertEqual(fallback["binding"]["pipelineClass"], "LTXConditionPipeline") + self.assertTrue(fallback["nodeKey"].startswith("modules.DiffusersVideo.")) + + def test_support_respects_each_targets_runtime_delivery(self): + from modiff.diffusers_profiles import public_execution_profiles + from modiff.operation_catalog import build_operation_catalog + + for platform_name, machine, expected in ( + ("linux", "x86_64", "blocked"), + ("windows", "x86_64", "blocked"), + ("macos", "arm64", "ready"), + ): + with self.subTest(platform=platform_name, machine=machine): + profiles = public_execution_profiles(platform_name=platform_name, machine=machine) + for profile in profiles: + requirement = profile["optionalRuntimeRequirement"] + if requirement["requiredNow"]: + requirement.update(state="missing", reason="optional_runtime_missing") + _, support = build_operation_catalog(MODULE_MAP, profiles, catalog_resolver=lambda: {}) + pipeline = next(p for p in support if p["pipelineClass"] == "QwenImageModularPipeline") + task = next(t for t in pipeline["tasks"] if t["task"] == "text_to_image") + self.assertEqual(task["dependencies"], expected) + + def test_semantic_connections_do_not_equate_different_state_stages_or_model_domains(self): + from modiff.operation_catalog import operation_port_compatibility + from modiff.operation_catalog import build_operation_catalog + + contracts, _ = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {}) + + def stage(pipeline, operation): + return next( + c + for c in contracts + if c["pipelineClass"] == pipeline and c["task"] == "text_to_image" and c["operationId"] == operation + ) + + def port(contract, name): + return next(p for p in contract["ports"] if p["name"] == name) + + prompt = stage("AnimaModularPipeline", "diffusion.encode_prompt") + denoise = stage("AnimaModularPipeline", "diffusion.denoise") + decode = stage("AnimaModularPipeline", "diffusion.decode_latents") + self.assertEqual( + operation_port_compatibility(port(prompt, "state_out"), port(denoise, "state_in")), "runtime_validation" + ) + self.assertEqual( + operation_port_compatibility(port(prompt, "state_out"), port(decode, "state_in")), "incompatible" + ) + other = stage("Krea2ModularPipeline", "diffusion.denoise") + self.assertEqual( + operation_port_compatibility(port(prompt, "state_out"), port(other, "state_in")), "incompatible" + ) + + def test_every_binding_resolves_without_models_or_mutating_registry(self): + from copy import deepcopy + from modiff.operation_catalog import build_operation_catalog, resolve_operation + from modiff.diffusers_profiles import public_execution_profiles + + contracts, _ = build_operation_catalog(MODULE_MAP, public_execution_profiles(), catalog_resolver=lambda: {}) + original = deepcopy(MODULE_MAP) + with ( + patch("socket.socket.connect", side_effect=AssertionError("Network during resolution")), + patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("Constructed node")), + ): + for contract in contracts: + with self.subTest( + pipeline=contract["pipelineClass"], task=contract["task"], op=contract["operationId"] + ): + resolved = resolve_operation( + MODULE_MAP, contracts, {k: contract[k] for k in ("pipelineClass", "task", "operationId")} + ) + self.assertEqual(resolved["module"] + "." + resolved["action"], contract["nodeKey"]) + for key, value in resolved["values"].items(): + self.assertEqual(resolved["params"][key]["value"], value) + if resolved["action"] == "ModelsLoader": + from modules.ModularDiffusers.modular_utils import get_model_type_metadata + from modules.ModularDiffusers.loaders import MODELS_LOADER_IDENTITY_OUTPUTS + + metadata = get_model_type_metadata(contract["pipelineClass"]) + expected = ( + "" if metadata.get("execution_status") == "contract_only" else contract["pipelineClass"] + ) + for output in MODELS_LOADER_IDENTITY_OUTPUTS: + self.assertEqual(resolved["params"][output]["signal"]["value"], expected) + if contract["decomposition"] == "loader" and resolved["action"] == "LoadPipeline": + self.assertEqual( + resolved["params"]["pipeline"]["signal"]["value"]["pipelineClass"], + contract["binding"]["pipelineClass"], + ) + self.assertEqual(MODULE_MAP, original) + + def test_binding_resolution_never_constructs_a_node_and_returns_existing_fields(self): + from modiff.operation_catalog import build_operation_catalog, resolve_operation + + contracts, _ = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {}) + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("Constructed node")): + result = resolve_operation( + MODULE_MAP, + contracts, + { + "pipelineClass": "AnimaModularPipeline", + "task": "text_to_image", + "operationId": "diffusion.denoise", + }, + ) + self.assertEqual(result["action"], "WorkflowImageDenoise") + self.assertEqual(result["values"]["pipeline_class"], "AnimaModularPipeline") + self.assertIn("state_in", result["params"]) + with self.assertRaises(ValueError): + resolve_operation( + MODULE_MAP, + contracts, + {"pipelineClass": "constructor", "task": None, "operationId": "diffusion.denoise"}, + ) diff --git a/tests/test_operation_contracts.py b/tests/test_operation_contracts.py new file mode 100644 index 00000000..8fb85687 --- /dev/null +++ b/tests/test_operation_contracts.py @@ -0,0 +1,161 @@ +"""Stage discovery must describe registered adapters without loading a pipeline.""" + +from copy import deepcopy +from types import SimpleNamespace +import unittest + +from modiff.operation_contracts import build_modular_operation_contracts + + +class Param: + def __init__(self, name, type_): + self.name = name + self.type = type_ + + def to_dict(self): + return {"type": self.type} + + +def config(): + spec = { + "inputs": [Param("embeddings", "embeddings"), Param("seed", "int")], + "model_inputs": [Param("transformer", "diffusers_auto_model")], + "outputs": [Param("embeddings", "embeddings"), Param("latents", "latents")], + "required_inputs": ["embeddings"], + "required_model_inputs": ["transformer"], + "block_name": "denoise", + } + return SimpleNamespace( + node_specs={"denoise": spec, "controlnet": None}, + node_params={ + "denoise": { + "input_names": ["embeddings", "seed"], + "model_input_names": ["transformer"], + "output_names": ["out_embeddings", "latents"], + } + }, + ) + + +MODULES = {"modules.ModularDiffusers": {"Denoise": {}}} + + +class OperationContractTests(unittest.TestCase): + def test_connected_prompt_has_explicit_execution_semantics(self): + source = config() + source.node_specs = {"text_encoder": { + "inputs": [Param("prompt", "string")], "model_inputs": [], "outputs": [], + "required_inputs": [], "required_model_inputs": [], "block_name": "text_encoder", + }} + source.node_params = {"text_encoder": { + "input_names": ["prompt"], "model_input_names": [], "output_names": [], + }} + modules = {"modules.ModularDiffusers": {"EncodePrompt": {"params": { + "prompt_input": {"type": "string", "display": "input"}, + }}}} + ports = build_modular_operation_contracts({"Pipeline": source}, modules)[0]["ports"] + connected = next(port for port in ports if port["name"] == "prompt_input") + self.assertEqual(connected["semanticName"], "prompt") + self.assertEqual(connected["direction"], "input") + self.assertFalse(connected["required"]) + + def test_same_operation_preserves_pipeline_scoped_port_meaning_and_saved_action(self): + configs = {"FutureModularPipeline": config(), "AnotherModularPipeline": config()} + contracts = build_modular_operation_contracts(configs, MODULES) + self.assertEqual(len(contracts), 2) + self.assertEqual({item["operationId"] for item in contracts}, {"diffusion.denoise"}) + self.assertEqual({item["nodeKey"] for item in contracts}, {"modules.ModularDiffusers.Denoise"}) + self.assertEqual([item["pipelineClass"] for item in contracts], sorted(configs)) + self.assertTrue(all(item["support"] == "declared" for item in contracts)) + ports = {port["name"]: port for port in contracts[0]["ports"]} + self.assertEqual(ports["out_embeddings"]["semanticName"], "embeddings") + self.assertEqual(ports["out_embeddings"]["direction"], "output") + self.assertFalse(ports["out_embeddings"]["required"]) + self.assertTrue(ports["transformer"]["required"]) + self.assertEqual(ports["transformer"]["roles"], ["component"]) + self.assertFalse(ports["seed"]["required"]) + + def test_unbound_custom_and_missing_actions_do_not_advertise_stage_support(self): + self.assertEqual(build_modular_operation_contracts({"Custom": SimpleNamespace(node_specs=None)}, MODULES), []) + self.assertEqual(build_modular_operation_contracts({"Pipeline": config()}, {}), []) + + def test_metadata_does_not_mutate_registry_or_share_returned_lists(self): + source = {"Pipeline": config()} + before = deepcopy(source["Pipeline"].node_specs["denoise"]["required_inputs"]) + first = build_modular_operation_contracts(source, MODULES) + first[0]["ports"][0]["types"].append("wildcard") + self.assertEqual(source["Pipeline"].node_specs["denoise"]["required_inputs"], before) + self.assertNotIn("wildcard", build_modular_operation_contracts(source, MODULES)[0]["ports"][0]["types"]) + + def test_bundle_is_not_mislabeled_as_an_upstream_block(self): + source = config() + source.node_specs["denoise"]["block_name"] = None + result = build_modular_operation_contracts({"Pipeline": source}, MODULES)[0] + self.assertEqual(result["decomposition"], "bundle") + self.assertIsNone(result["blockName"]) + + def test_invalid_declared_ports_fail_instead_of_advertising_partial_support(self): + for mutation in ("unknown_required", "duplicate", "invalid_type"): + source = config() + spec = source.node_specs["denoise"] + if mutation == "unknown_required": + spec["required_inputs"].append("missing") + elif mutation == "duplicate": + spec["inputs"].append(Param("seed", "int")) + else: + spec["inputs"][0].type = [] + with self.subTest(mutation=mutation), self.assertRaises(ValueError): + build_modular_operation_contracts({"Pipeline": source}, MODULES) + + def test_one_conditioning_socket_can_supply_both_values_and_components(self): + source = config() + source.node_specs["denoise"]["model_inputs"].append(Param("embeddings", "embeddings")) + source.node_params["denoise"]["model_input_names"].append("embeddings") + ports = build_modular_operation_contracts({"Pipeline": source}, MODULES)[0]["ports"] + inputs = [port for port in ports if port["name"] == "embeddings" and port["direction"] == "input"] + self.assertEqual(len(inputs), 1) + self.assertEqual(inputs[0]["roles"], ["value", "component"]) + self.assertTrue(inputs[0]["required"]) + + +class PipelineOperationContractTests(unittest.TestCase): + def test_pipeline_projection_preserves_task_visibility_and_pipeline_role(self): + from modiff.operation_contracts import build_pipeline_operation_contract + + modules = {"modules.Example": {"Generate": {"params": { + "pipeline": {"type": "image_pipeline", "display": "input", "required": True}, + "image": {"type": "image", "display": "input", "hidden": True}, + "width": {"type": "int", "default": 512}, + "images": {"type": "image", "display": "output"}, + "refresh": {"display": "button", "type": "bool"}, + }}}} + before = deepcopy(modules) + result = build_pipeline_operation_contract( + modules, pipeline_class="NewPipeline", task="edit_image", + operation_id="diffusion.generate_image", node_key="modules.Example.Generate", + field_overrides={"image": {"hidden": False, "required": True}, "width": {"hidden": True}}, + ) + self.assertEqual(result["decomposition"], "pipeline") + self.assertEqual(result["task"], "edit_image") + ports = {port["name"]: port for port in result["ports"]} + self.assertEqual(ports["pipeline"]["roles"], ["pipeline"]) + self.assertTrue(ports["image"]["required"]) + self.assertFalse(ports["image"]["hidden"]) + self.assertTrue(ports["width"]["hidden"]) + self.assertNotIn("refresh", ports) + self.assertEqual(modules, before) + + def test_missing_actions_are_not_advertised_and_loader_outputs_keep_pipeline_role(self): + from modiff.operation_contracts import build_pipeline_operation_contract + + arguments = dict(pipeline_class="NewPipeline", task="text_to_image", + operation_id="diffusion.load_models", node_key="modules.Example.Load", loader=True) + self.assertIsNone(build_pipeline_operation_contract({}, **arguments)) + result = build_pipeline_operation_contract( + {"modules.Example": {"Load": {"params": { + "pipeline": {"type": "image_pipeline", "display": "output"}, + }}}}, **arguments, + ) + self.assertEqual(result["decomposition"], "loader") + self.assertEqual(result["ports"][0]["roles"], ["pipeline"]) + self.assertFalse(result["ports"][0]["required"]) diff --git a/tests/test_operation_inventory.py b/tests/test_operation_inventory.py new file mode 100644 index 00000000..de8f2cd7 --- /dev/null +++ b/tests/test_operation_inventory.py @@ -0,0 +1,55 @@ +"""Every pinned export and declared Auto/Modular task stays visible in coverage.""" + +import unittest +from pathlib import Path + + +class OperationInventoryTests(unittest.TestCase): + def test_inventory_reproduces_from_pinned_source_without_weights(self): + from modiff.operation_inventory import build_operation_inventory, load_operation_inventory + + generated = build_operation_inventory(Path(__file__).resolve().parents[1]) + self.assertEqual(generated, load_operation_inventory()) + self.assertEqual(len(generated["pipelines"]), 334) + flux = next(p for p in generated["pipelines"] if p["pipelineClass"] == "FluxPipeline") + self.assertIn({"task": "text_to_image", "source": "auto", "workflowId": None}, flux["upstreamTasks"]) + qwen = next(p for p in generated["pipelines"] if p["pipelineClass"] == "QwenImageModularPipeline") + self.assertTrue(any(t["workflowId"] == "inpainting" for t in qwen["upstreamTasks"])) + self.assertTrue(all(p["upstreamTasks"] for p in generated["pipelines"])) + + def test_conditional_auto_mappings_are_audited_and_new_task_categories_fail_closed(self): + from modiff.operation_inventory import _auto_tasks + + source = """AUTO_TEXT2IMAGE_PIPELINES_MAPPING = OrderedDict([('base', BasePipeline)]) +if optional_dependency_available(): + AUTO_TEXT2IMAGE_PIPELINES_MAPPING['conditional'] = ConditionalPipeline +""" + self.assertEqual( + _auto_tasks(source), {"BasePipeline": {"text_to_image"}, "ConditionalPipeline": {"text_to_image"}} + ) + with self.assertRaisesRegex(ValueError, "Unreviewed"): + _auto_tasks(source.replace("TEXT2IMAGE", "UNREVIEWED")) + + def test_inventory_rejects_tampering_even_with_a_recomputed_content_hash(self): + import json + import tempfile + from copy import deepcopy + from modiff.operation_inventory import _hash, load_operation_inventory + + original = load_operation_inventory() + for mutate in ( + lambda value: value["pipelines"].append(deepcopy(value["pipelines"][0])), + lambda value: value["pipelines"][0].update(pipelineClass="constructor"), + lambda value: value["pipelines"][0].update(coverage="runnable"), + lambda value: value["pipelines"][0].update(upstreamTasks=[]), + lambda value: value.update(schemaVersion=True), + lambda value: value.update(diffusersRevision="a" * 40), + ): + value = deepcopy(original) + mutate(value) + value["contentHash"] = _hash({key: item for key, item in value.items() if key != "contentHash"}) + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "inventory.json" + path.write_text(json.dumps(value)) + with self.assertRaises(ValueError): + load_operation_inventory(path) diff --git a/tests/test_operation_model_defaults.py b/tests/test_operation_model_defaults.py new file mode 100644 index 00000000..4891004c --- /dev/null +++ b/tests/test_operation_model_defaults.py @@ -0,0 +1,314 @@ +"""New generic workflows must retain the selected model's reviewed defaults.""" +from copy import deepcopy +from unittest.mock import patch + +import pytest + +from modules import MODULE_MAP +from modiff.operation_catalog import build_operation_catalog, resolve_operation +from modiff.operation_starters import resolve_operation_starter + + +@pytest.fixture(scope="module") +def contracts(): + return build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {})[0] + + +@pytest.mark.parametrize("profile,pipeline,steps,guidance,size,dtype", [ + ("lcm-dreamshaper-v7:direct", "LatentConsistencyModelPipeline", 4, 8.5, 512, "float32"), + ("flux-schnell:direct", "FluxPipeline", 4, 1.0, 1024, "bfloat16"), + ("flux-krea:direct", "FluxPipeline", 28, 1.0, 1024, "bfloat16"), + ("pixart-sigma-1024:direct", "PixArtSigmaPipeline", 20, 4.5, 1024, "float32"), +]) +def test_profile_starter_uses_reviewed_values_without_constructing_models( + contracts, profile, pipeline, steps, guidance, size, dtype, +): + original = deepcopy(MODULE_MAP["modules.DiffusersImage"]) + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed model node")): + starter = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": pipeline, "task": "text_to_image", "executionProfileId": profile, + }) + loader = next(n for n in starter["nodes"] if n["action"] == "LoadPipeline") + generate = next(n for n in starter["nodes"] if n["action"] == "Generate") + expected = {"num_inference_steps": steps, "guidance_scale": guidance, "width": size, "height": size} + for key, value in expected.items(): + assert generate["params"][key]["value"] == value, key + assert generate["values"][key] == value, key + assert loader["params"]["dtype"]["value"] == dtype + assert loader["values"]["dtype"] == dtype + assert MODULE_MAP["modules.DiffusersImage"] == original + + +def test_individually_resolved_lcm_node_uses_same_reviewed_defaults(contracts): + result = resolve_operation(MODULE_MAP, contracts, { + "pipelineClass": "LatentConsistencyModelPipeline", "task": "text_to_image", + "operationId": "diffusion.generate_image", + }) + assert result["params"]["guidance_scale"]["value"] == 8.5 + assert result["params"]["width"]["value"] == 512 + + +@pytest.mark.parametrize("pipeline,profile,duration,steps,guidance,dtype", [ + ("AudioLDM2Pipeline", "audioldm2-base:direct", 10, 200, 3.5, "float16"), + ("LongCatAudioDiTPipeline", "longcat-audio-dit-1b:direct", 5, 16, 4, "bfloat16"), + ("StableAudioPipeline", "stable-audio:direct", 30, 100, 7, "bfloat16"), + ("AceStepPipeline", "ace-step-audio:direct", 30, 8, 1, "bfloat16"), +]) +def test_new_audio_operations_use_reviewed_defaults_and_pass_runtime_preflight( + contracts, pipeline, profile, duration, steps, guidance, dtype, +): + from types import SimpleNamespace + from modules.DiffusersAudio.main import _preflight_audio_invocation + + registry_before = deepcopy(MODULE_MAP["modules.DiffusersAudio"]) + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed model node")): + starter = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": pipeline, "task": "text_to_audio", "executionProfileId": profile, + }) + loader = next(n for n in starter["nodes"] if n["action"] == "LoadPipeline") + generate = next(n for n in starter["nodes"] if n["action"] == "Generate") + def effective(node, key): + field = node["params"][key] + return field.get("value", field.get("default")) + + assert effective(loader, "dtype") == dtype + assert effective(generate, "audio_duration") == duration + values = {key: field.get("value", field.get("default")) for key, field in generate["params"].items()} + runtime = SimpleNamespace(_modiff_audio_pipeline_class=pipeline, _modiff_audio_mode="text_to_audio") + invocation = _preflight_audio_invocation(runtime, values) + assert invocation.duration_seconds == duration + step_key = "num_inference_steps" if pipeline == "AceStepPipeline" else "stable_audio_steps" + guidance_key = "guidance_scale" if pipeline == "AceStepPipeline" else "stable_audio_guidance" + assert effective(generate, step_key) == steps + assert effective(generate, guidance_key) == guidance + assert MODULE_MAP["modules.DiffusersAudio"] == registry_before + individual = resolve_operation(MODULE_MAP, contracts, { + "pipelineClass": pipeline, "task": "text_to_audio", + "operationId": generate["operation"]["operationId"], + }) + for key in ("audio_duration", step_key, guidance_key): + assert effective(individual, key) == effective(generate, key) + + +def test_new_audio_defaults_do_not_rewrite_edited_drafts_or_dynamic_callbacks(contracts): + from modules.DiffusersAudio.main import Generate + + selected = {"pipelineClass": "AudioLDM2Pipeline", "task": "text_to_audio"} + draft = resolve_operation_starter(MODULE_MAP, contracts, selected) + generate = next(n for n in draft["nodes"] if n["action"] == "Generate") + generate["params"]["audio_duration"]["value"] = 8.5 + generate["params"]["stable_audio_steps"]["value"] = 150 + before = deepcopy(draft) + resolve_operation_starter(MODULE_MAP, contracts, selected) + assert draft == before + updates = [] + node = object.__new__(Generate) + node.set_field_params = lambda field, params: updates.append((field, params)) + node.set_field_value = lambda *args: pytest.fail("Dynamic callback overwrote saved values") + Generate.update_audio_contract(node, generate["values"], None) + assert updates + assert all("value" not in params for field, params in updates if field != "task_type") + + +def test_creating_another_model_does_not_rewrite_prior_draft(contracts): + selected = {"pipelineClass": "FluxPipeline", "task": "text_to_image", "executionProfileId": "flux-schnell:direct"} + draft = resolve_operation_starter(MODULE_MAP, contracts, selected) + generate = next(n for n in draft["nodes"] if n["action"] == "Generate") + generate["params"]["guidance_scale"]["value"] = 1.25 + snapshot = deepcopy(draft) + resolve_operation_starter(MODULE_MAP, contracts, {**selected, "executionProfileId": "flux-krea:direct"}) + assert draft == snapshot + + +def test_all_public_image_starter_defaults_fit_their_declared_fields(contracts): + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES + + checked = 0 + for profile in DIFFUSERS_EXECUTION_PROFILES.values(): + if not profile.public or profile.loader_module != "modules.DiffusersImage": + continue + for task in profile.modes: + starter = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": profile.pipeline_class, "task": task, "executionProfileId": profile.id, + }) + for node in starter["nodes"]: + for key in ("dtype", "width", "height", "num_inference_steps", "guidance_scale"): + field = node["params"].get(key) + if not field or field.get("hidden") or key not in node["values"]: + continue + value = node["values"][key] + identity = (profile.id, task, node["action"], key, value) + if isinstance(value, (int, float)): + if "min" in field: + assert value >= field["min"], identity + if "max" in field: + assert value <= field["max"], identity + if "options" in field: + assert value in field["options"], identity + checked += 1 + assert checked > 300 + + +def test_selected_defaults_do_not_copy_unrelated_model_capabilities(contracts): + class UnrelatedCapability(dict): + def __deepcopy__(self, memo): + raise AssertionError("copied unrelated model defaults") + + target = {"defaultDtype": "float32", "recommendedSteps": 4, "recommendedGuidance": 8.5, + "defaultSize": {"width": 512, "height": 512}} + definitions = { + "old": {"modelType": "LatentConsistencyModelPipeline", "capability": {"recommendedSteps": 1}}, + "other": {"modelType": "UnrelatedModel", "capability": UnrelatedCapability()}, + "selected": {"modelType": "LatentConsistencyModelPipeline", "capability": target}, + } + with patch("modiff.studio_execution_specs.STUDIO_EXECUTION_SPEC_DEFINITIONS", definitions): + node = resolve_operation(MODULE_MAP, contracts, { + "pipelineClass": "LatentConsistencyModelPipeline", "task": "text_to_image", + "operationId": "diffusion.generate_image", + }) + assert node["values"]["num_inference_steps"] == 4 + node["values"]["width"] = 640 + assert target["defaultSize"]["width"] == 512 + + +@pytest.mark.parametrize("profile,pipeline,task,guidance", [ + ("flux-krea:direct", "FluxPipeline", "text_to_image", 3.5), + ("flux-schnell:direct", "FluxPipeline", "text_to_image", 0.0), + ("flux-dev:direct", "FluxPipeline", "text_to_image", 3.5), + ("flux-dev:img2img-direct", "FluxImg2ImgPipeline", "edit_image", 3.5), + ("flux-dev:inpaint-direct", "FluxInpaintPipeline", "inpaint", 3.5), + ("flux-kontext:direct", "FluxKontextPipeline", "edit_image", 2.5), + ("flux-kontext:direct", "FluxKontextPipeline", "multi_image_reference_edit", 2.5), + ("flux-kontext-inpaint:direct", "FluxKontextInpaintPipeline", "inpaint", 3.5), + ("flux-kontext-inpaint:direct", "FluxKontextInpaintPipeline", "outpaint", 3.5), +]) +def test_new_flux_starter_routes_recommendation_to_distilled_guidance( + contracts, profile, pipeline, task, guidance, +): + from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS + + starter = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": pipeline, "task": task, "executionProfileId": profile, + }) + node = next(n for n in starter["nodes"] if n["module"] == "modules.DiffusersImage" and n["action"] != "LoadPipeline") + values = node["values"] + assert values["guidance_scale"] == 1.0 + assert values["use_guidance_scale_2"] is True + assert values["guidance_scale_2"] == guidance + for key in ("guidance_scale", "guidance_scale_2", "use_guidance_scale_2"): + assert node["params"][key]["value"] == values[key] + + class Pipeline: + def __call__(self, true_cfg_scale=1.0, guidance_scale=3.5): + pass + + consumed = {} + IMAGE_PIPELINE_ADAPTERS[pipeline].apply_generation_parameters(Pipeline(), values, consumed) + assert consumed["guidance_scale"] == guidance + assert consumed["true_cfg_scale"] == 1.0 + + +def test_legacy_guidance_invocation_and_omitted_override_are_unchanged(): + from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS + + class Pipeline: + def __call__(self, true_cfg_scale=1.0, guidance_scale=3.5): + pass + + adapter = IMAGE_PIPELINE_ADAPTERS["FluxPipeline"] + for values in ({"guidance_scale": 4.25}, { + "guidance_scale": 4.25, "guidance_scale_2": 9.0, "use_guidance_scale_2": False, + }): + original = deepcopy(values) + consumed = {} + adapter.apply_generation_parameters(Pipeline(), values, consumed) + assert consumed == {"true_cfg_scale": 4.25} + assert values == original + + +def test_new_single_operation_enables_distilled_guidance(contracts): + node = resolve_operation(MODULE_MAP, contracts, { + "pipelineClass": "FluxPipeline", "task": "text_to_image", + "operationId": "diffusion.generate_image", + }) + assert node["values"]["guidance_scale"] == 1.0 + assert node["values"]["guidance_scale_2"] == 0.0 + assert node["values"]["use_guidance_scale_2"] is True + + +def test_distilled_authoring_default_preserves_pinned_true_cfg_default(): + import ast + import importlib.util + from pathlib import Path + from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS + + root = Path(importlib.util.find_spec("diffusers").origin).parent / "pipelines" + reviewed = { + name for name, adapter in IMAGE_PIPELINE_ADAPTERS.items() + if adapter.secondary_guidance_parameter == "guidance_scale" + } + checked = set() + for path in root.glob("flux/pipeline_flux*.py"): + for node in ast.parse(path.read_text()).body: + if not isinstance(node, ast.ClassDef) or node.name not in reviewed: + continue + call = next(m for m in node.body if isinstance(m, ast.FunctionDef) and m.name == "__call__") + args = call.args.args[-len(call.args.defaults):] + defaults = {a.arg: d for a, d in zip(args, call.args.defaults)} + assert ast.literal_eval(defaults["true_cfg_scale"]) == 1.0, node.name + assert "guidance_scale" in defaults, node.name + checked.add(node.name) + assert checked == reviewed + + +@pytest.mark.parametrize("pipeline,profile,dtype,values", [ + ("CogVideoXPipeline", "cogvideox-2b:direct", "float16", + {"width": 720, "height": 480, "num_frames": 25, "num_inference_steps": 25, "guidance_scale": 6}), + ("SanaVideoPipeline", "sana-video-480p:direct", "bfloat16", + {"width": 832, "height": 480, "num_frames": 81, "num_inference_steps": 50, "guidance_scale": 6}), + ("WanTI2VPipeline", "wan-22-ti2v-5b:direct", "bfloat16", + {"width": 1280, "height": 704, "num_frames": 121, "num_inference_steps": 50, "guidance_scale": 5}), + ("ShapEPipeline", "shap-e:direct", "float16", + {"frame_size": 256, "num_inference_steps": 64, "guidance_scale": 15}), +]) +def test_video_and_three_d_operations_initialize_from_reviewed_defaults(contracts, pipeline, profile, dtype, values): + task = "text_to_3d" if pipeline == "ShapEPipeline" else "text_to_video" + before = deepcopy(MODULE_MAP) + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed model node")): + starter = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": pipeline, "task": task, "executionProfileId": profile, + }) + loader = next(n for n in starter["nodes"] if n["action"] == "LoadPipeline") + generate = next(n for n in starter["nodes"] if n["action"] != "LoadPipeline") + assert loader["params"]["dtype"]["value"] == dtype + for key, value in values.items(): + assert generate["params"][key]["value"] == value + individual = resolve_operation(MODULE_MAP, contracts, { + "pipelineClass": pipeline, "task": task, "operationId": generate["operation"]["operationId"], + }) + for key, value in values.items(): + assert individual["params"][key]["value"] == value + generate["params"]["num_inference_steps"]["value"] = 17 + resolve_operation_starter(MODULE_MAP, contracts, {"pipelineClass": pipeline, "task": task}) + assert generate["params"]["num_inference_steps"]["value"] == 17 + assert MODULE_MAP == before + + +@pytest.mark.parametrize("repository,task", [ + ("black-forest-labs/FLUX.2-klein-4B", "text_to_image"), + ("Qwen/Qwen-Image", "text_to_image"), + ("stabilityai/stable-audio-open-1.0", "text_to_audio"), + ("cvssp/audioldm2", "text_to_audio"), +]) +def test_additional_creator_examples_are_attributed_and_do_not_overwrite_authored_prompts(repository, task): + from modiff.authoring_examples import seed_operation_example + + node = {"operation": {"task": task}, "params": {"prompt": {"type": "string", "value": ""}}} + seed_operation_example(node, repository) + field = node["params"]["prompt"] + assert field["value"] + assert field["fieldOptions"]["exampleSource"] == f"https://huggingface.co/{repository}" + assert field["fieldOptions"]["exampleAttribution"] == "Adapted from creator guidance" + authored = {"operation": {"task": task}, "params": {"prompt": {"type": "string", "value": "My own prompt"}}} + seed_operation_example(authored, repository) + assert authored["params"]["prompt"]["value"] == "My own prompt" diff --git a/tests/test_operation_starters.py b/tests/test_operation_starters.py new file mode 100644 index 00000000..7d2c086f --- /dev/null +++ b/tests/test_operation_starters.py @@ -0,0 +1,381 @@ +"""Ordinary stage drafts must use the reviewed runtime's actual wires.""" + +import unittest +from unittest.mock import patch + +from modules import MODULE_MAP +from modiff.operation_catalog import build_operation_catalog + + +class OperationStarterTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.contracts, _ = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {}) + + def resolve(self, pipeline, task): + from modiff.operation_starters import resolve_operation_starter + + return resolve_operation_starter(MODULE_MAP, self.contracts, {"pipelineClass": pipeline, "task": task}) + + def test_exact_profile_selects_shared_pipeline_model_without_constructing_nodes(self): + from modiff.operation_starters import resolve_operation_starter + from modiff.diffusers_profiles import resolve_execution_profiles_for_loader + + for identity, repository in ( + ("flux-schnell:direct", "black-forest-labs/FLUX.1-schnell"), + ("flux-krea:direct", "black-forest-labs/FLUX.1-Krea-dev"), + ): + with ( + self.subTest(profile=identity), + patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed")), + ): + result = resolve_operation_starter( + MODULE_MAP, + self.contracts, + { + "pipelineClass": "FluxPipeline", + "task": "text_to_image", + "executionProfileId": identity, + }, + ) + loader = result["nodes"][0] + self.assertEqual(loader["params"]["model_id"]["value"], {"source": "hub", "value": repository}) + self.assertRegex(loader["params"]["revision"]["value"], r"^[0-9a-f]{40}$") + profiles, reason = resolve_execution_profiles_for_loader( + loader["module"], loader["action"], loader["values"] + ) + self.assertIsNone(reason) + self.assertEqual([p.id for p in profiles], [identity]) + self.assertNotIn("executionProfileId", result, "Keep the existing response envelope compatible") + + def test_connected_guider_hides_unused_denoise_guidance(self): + from modiff.operation_starters import resolve_operation_starter + + for profile in ("sdxl-base:modular", "sdxl-pag:modular", "sdxl-turbo:modular"): + with self.subTest(profile=profile): + result = resolve_operation_starter(MODULE_MAP, self.contracts, { + "pipelineClass": "StableDiffusionXLModularPipeline", + "task": "text_to_image", "executionProfileId": profile, + }) + nodes = {node["operation"]["operationId"]: node for node in result["nodes"]} + edges = [edge for edge in result["edges"] if edge["targetHandle"] == "guider"] + self.assertTrue(edges) + for edge in edges: + self.assertTrue(nodes[edge["target"]]["params"]["guidance_scale"]["hidden"]) + if profile == "sdxl-turbo:modular": + guidance = nodes[edges[0]["source"]]["params"]["guidance_scale"] + self.assertEqual(guidance["value"], 0) + self.assertEqual(guidance["min"], 0) + + def test_profile_selection_rejects_unrelated_unknown_and_invalid_identities(self): + from modiff.operation_starters import resolve_operation_starter + + for identity in ("ace-step-audio:direct", "unknown", [], None, " flux-dev:direct"): + with self.subTest(profile=identity), self.assertRaises(ValueError): + resolve_operation_starter( + MODULE_MAP, + self.contracts, + { + "pipelineClass": "FluxPipeline", + "task": "text_to_image", + "executionProfileId": identity, + }, + ) + + def test_exact_artifact_variant_binds_without_model_construction(self): + from modiff.operation_starters import resolve_operation_starter + selection = { + "pipelineClass": "FluxModularPipeline", "task": "text_to_image", + "executionProfileId": "flux-dev:modular", "repository": "black-forest-labs/FLUX.1-dev-FP8", + } + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed")): + result = resolve_operation_starter(MODULE_MAP, self.contracts, selection) + loader = result["nodes"][0] + self.assertEqual(loader["values"]["repo_id"]["value"], selection["repository"]) + self.assertRegex(loader["values"]["revision"], r"^[0-9a-f]{40}$") + for repository in ("unreviewed/arbitrary", None, [], " black-forest-labs/FLUX.1-dev-FP8"): + with self.subTest(repository=repository), self.assertRaises(ValueError): + resolve_operation_starter(MODULE_MAP, self.contracts, {**selection, "repository": repository}) + + def test_native_starter_uses_reviewed_artifact_precision_and_step_defaults(self): + from modiff.operation_starters import resolve_operation_starter + result = resolve_operation_starter(MODULE_MAP, self.contracts, { + "pipelineClass": "ErnieImageModularPipeline", "task": "text_to_image", + "executionProfileId": "ernie-image-turbo:official-modular-workflow", + }) + self.assertEqual(result["nodes"][0]["params"]["dtype"]["value"], "bfloat16") + denoise = next(node for node in result["nodes"] if node["operation"]["operationId"] == "diffusion.denoise") + self.assertEqual(denoise["params"]["num_inference_steps"]["value"], 8) + self.assertNotIn("guidance_scale", denoise["params"], "Turbo fixes guidance internally, not as an editable field") + + def test_workflow_scoped_qwen_artifact_variant_is_a_real_starter_choice(self): + from modiff.operation_starters import resolve_operation_starter + repository = "unsloth/Qwen-Image-2512-unsloth-bnb-4bit" + selection = { + "pipelineClass": "QwenImageModularPipeline", "task": "text_to_image", + "executionProfileId": "qwen-image:modular", "repository": repository, + } + result = resolve_operation_starter(MODULE_MAP, self.contracts, selection) + loader = result["nodes"][0] + self.assertEqual(loader["values"]["reviewed_variant"], repository) + self.assertEqual(loader["values"]["revision"], "f50b8c24fe21e9265509b15113b7cca82d0a4443") + with self.assertRaisesRegex(ValueError, "exact reviewed artifact"): + resolve_operation_starter(MODULE_MAP, self.contracts, {**selection, "task": "image_to_image"}) + + def test_every_advertised_profile_task_binds_without_constructing_models(self): + from modiff.operation_starters import resolve_operation_starter + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES, public_execution_profiles + + contracts, support = build_operation_catalog( + MODULE_MAP, public_execution_profiles(), catalog_resolver=lambda: {} + ) + checked = 0 + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed")): + for pipeline in support: + for task in pipeline["tasks"]: + if not task["operationIds"]: + continue + for identity in task["executionProfileIds"]: + with self.subTest(pipeline=pipeline["pipelineClass"], task=task["task"], profile=identity): + result = resolve_operation_starter( + MODULE_MAP, + contracts, + { + "pipelineClass": pipeline["pipelineClass"], + "task": task["task"], + "executionProfileId": identity, + }, + ) + loader = result["nodes"][0] + field = "repo_id" if loader["action"] == "ModelsLoader" else "model_id" + if loader["operation"]["decomposition"] == "integrated": + from modiff.integrated_operation_contracts import integrated_operation_values + + self.assertEqual(loader["values"], integrated_operation_values( + loader["operation"], profile=DIFFUSERS_EXECUTION_PROFILES[identity] + )) + if DIFFUSERS_EXECUTION_PROFILES[identity].execution_path != "builtin-image-operation": + self.assertRegex(loader["values"][field]["revision"], r"^[0-9a-f]{40}$") + else: + self.assertEqual( + loader["values"][field]["value"], DIFFUSERS_EXECUTION_PROFILES[identity].default_repo + ) + self.assertRegex(loader["values"]["revision"], r"^[0-9a-f]{40}$") + checked += 1 + self.assertGreater(checked, 150) + + def test_four_stages_are_an_ordinary_draft_with_exact_state_and_component_wires(self): + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("Constructed node")): + result = self.resolve("AnimaModularPipeline", "text_to_image") + self.assertEqual(len(result["nodes"]), 4) + self.assertEqual(len(result["edges"]), 5) + self.assertNotIn("receipt", result) + self.assertTrue(all(n["operation"]["task"] == "text_to_image" for n in result["nodes"])) + self.assertTrue( + any(e["sourceHandle"] == "state_out" and e["targetHandle"] == "state_in" for e in result["edges"]) + ) + + def test_task_switch_uses_upstream_rules_and_exposes_required_image(self): + text = self.resolve("FluxModularPipeline", "text_to_image") + image = self.resolve("FluxModularPipeline", "image_to_image") + edit = self.resolve("FluxKontextModularPipeline", "edit_image") + self.assertEqual(len(text["nodes"]), 4) + self.assertEqual(len(image["nodes"]), 5) + self.assertEqual(image["workflowId"], "image2image") + self.assertEqual(edit["workflowId"], "image_conditioned") + self.assertTrue(any(i["field"] == "image" for i in image["requiredInputs"])) + self.assertTrue(any(e["sourceHandle"] == "out_width" and e["targetHandle"] == "width" for e in image["edges"])) + with self.assertRaises(ValueError): + self.resolve("FluxModularPipeline", "edit_image") + + def test_task_required_media_is_declared_on_each_operation_port(self): + for pipeline, task in sorted({(c['pipelineClass'], c['task']) for c in self.contracts + if c['task'] and c['nodeKey'].startswith('modules.ModularDiffusers.')}): + result = self.resolve(pipeline, task) + nodes = {n['operation']['operationId']: n for n in result['nodes']} + for required in result['requiredInputs']: + ports = nodes[required['operationId']]['operation']['ports'] + port = next(p for p in ports if p['name'] == required['field'] and p['direction'] == 'input') + if port['semantics']['kind'] == 'media': + with self.subTest(pipeline=pipeline, task=task, field=required['field']): + self.assertTrue(port['required']) + image = self.resolve('StableDiffusionXLModularPipeline', 'image_to_image') + encode = next(n for n in image['nodes'] if n['operation']['operationId'] == 'diffusion.encode_image') + self.assertFalse(next(p for p in encode['operation']['ports'] if p['name'] == 'mask_image')['required']) + + def test_required_component_contract_completes_denoise_vae_wiring(self): + # The SDXL runtime requires a managed VAE even for text-to-image. + # Admission's minimal component edges alone do not describe this input. + result = self.resolve("StableDiffusionXLModularPipeline", "text_to_image") + edge = { + "source": "diffusion.load_models", + "sourceHandle": "vae_out", + "target": "diffusion.denoise", + "targetHandle": "vae", + } + self.assertIn(edge, result["edges"]) + for task in ('image_to_image', 'inpaint'): + with self.subTest(task=task): + selected = self.resolve('StableDiffusionXLModularPipeline', task) + self.assertIn(edge, selected['edges']) + denoise = next(n['operation'] for n in selected['nodes'] + if n['operation']['operationId'] == 'diffusion.denoise') + vae = next(p for p in denoise['ports'] if p['name'] == 'vae') + self.assertEqual([m['name'] for m in vae['semantics']['members']], ['vae']) + image = self.resolve("StableDiffusionXLModularPipeline", "image_to_image") + self.assertEqual( + [group for group in image["sharedInputs"] if group["name"] == "seed"], + [ + { + "name": "seed", + "members": [ + {"operationId": "diffusion.encode_image", "field": "seed"}, + {"operationId": "diffusion.denoise", "field": "seed"}, + ], + } + ], + ) + self.assertEqual(result["sharedInputs"], []) + for task in ('image_to_image', 'inpaint'): + selected = self.resolve('StableDiffusionXLModularPipeline', task) + encoder = next(n for n in selected['nodes'] if n['operation']['operationId'] == 'diffusion.encode_image') + for dimension in ('width', 'height'): + self.assertEqual(encoder['params'][dimension]['default'], 1024) + group = next(g for g in selected['sharedInputs'] if g['name'] == dimension) + self.assertEqual(group['members'], [ + {'operationId': 'diffusion.encode_image', 'field': dimension}, + {'operationId': 'diffusion.denoise', 'field': dimension}, + ]) + # These adapters do not declare that denoiser component dependency. + for pipeline in ("FluxModularPipeline", "QwenImageModularPipeline"): + self.assertNotIn(edge, self.resolve(pipeline, "text_to_image")["edges"]) + + def test_standard_fallback_is_a_load_and_call_and_keeps_audio_contract(self): + result = self.resolve("StableAudioPipeline", "text_to_audio") + self.assertEqual(len(result["nodes"]), 2) + self.assertEqual(len(result["edges"]), 1) + self.assertEqual(result["edges"][0]["targetHandle"], "pipeline") + + def test_standard_loader_starters_do_not_require_an_execution_recipe_override(self): + # All ordinary loaders can use their own resource fields. A connected + # starter must not ask Fix to add an optional recipe source. + from modiff.operation_catalog import resolve_operation + + checked = set() + for contract in self.contracts: + if not contract.get("nodeKey", "").endswith(".LoadPipeline"): + continue + node = resolve_operation( + MODULE_MAP, + self.contracts, + { + "pipelineClass": contract["pipelineClass"], + "task": contract["task"], + "operationId": contract["operationId"], + }, + ) + if "execution_recipe" not in node["params"]: + continue + with self.subTest(pipeline=contract["pipelineClass"], task=contract["task"]): + self.assertIs(node["params"]["execution_recipe"].get("required"), False) + checked.add(node["module"]) + self.assertEqual( + checked, + {"modules.DiffusersImage", "modules.DiffusersVideo", "modules.DiffusersAudio", "modules.DiffusersThreeD"}, + ) + + def test_unconditional_starters_keep_the_reviewed_resident_float32_recipe(self): + # These upstream samplers do not share the text-to-image loader's + # bfloat16/model-offload recipe. Check individual insertion as well as + # the connected starter; existing generic schemas must stay unchanged. + from copy import deepcopy + from modiff.operation_catalog import resolve_operation + + original = deepcopy(MODULE_MAP["modules.DiffusersImage"]["LoadPipeline"]) + for pipeline in ("DDPMPipeline", "DDIMPipeline", "ConsistencyModelPipeline"): + with self.subTest(pipeline=pipeline): + draft = self.resolve(pipeline, "unconditional_image") + loader = next(n for n in draft["nodes"] if n["action"] == "LoadPipeline") + single = resolve_operation( + MODULE_MAP, + self.contracts, + { + "pipelineClass": pipeline, + "task": "unconditional_image", + "operationId": loader["operation"]["operationId"], + }, + ) + for node in (loader, single): + for field, expected in {"dtype": "float32", "auto_offload": False, "offload_mode": "none"}.items(): + self.assertEqual(node["values"].get(field), expected) + self.assertEqual(node["params"][field]["value"], expected) + self.assertEqual(node["values"].get("conditioning_model_id"), "") + self.assertEqual(node["values"].get("conditioning_revision"), "") + self.assertEqual(MODULE_MAP["modules.DiffusersImage"]["LoadPipeline"], original) + + def test_loader_defaults_keep_conditioned_models_and_other_task_recipes(self): + from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS + + for pipeline, task in (("FluxPipeline", "text_to_image"), ("FluxControlNetPipeline", "control_image")): + with self.subTest(pipeline=pipeline): + loader = next(n for n in self.resolve(pipeline, task)["nodes"] if n["action"] == "LoadPipeline") + self.assertEqual(loader["values"]["dtype"], "bfloat16") + self.assertEqual(loader["params"]["dtype"]["value"], "bfloat16") + self.assertNotIn("offload_mode", loader["values"]) + auxiliary = IMAGE_PIPELINE_ADAPTERS[pipeline].default_conditioning_repo + self.assertEqual( + loader["values"]["conditioning_model_id"], + {"source": "hub", "value": auxiliary} if auxiliary else "", + ) + if auxiliary: + self.assertEqual(len(loader["values"]["conditioning_revision"]), 40) + + def test_all_task_drafts_have_existing_visible_endpoints_and_one_writer(self): + with ( + patch("socket.socket.connect", side_effect=AssertionError("Network during authoring")), + patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("Constructed node")), + ): + for pipeline, task in sorted({(c["pipelineClass"], c["task"]) for c in self.contracts if c["task"]}): + with self.subTest(pipeline=pipeline, task=task): + result = self.resolve(pipeline, task) + nodes = {n["operation"]["operationId"]: n for n in result["nodes"]} + targets = set() + for edge in result["edges"]: + self.assertIn(edge["sourceHandle"], nodes[edge["source"]]["params"]) + self.assertIn(edge["targetHandle"], nodes[edge["target"]]["params"]) + target = edge["target"], edge["targetHandle"] + self.assertNotIn(target, targets) + targets.add(target) + + def test_invalid_selection_is_rejected_without_mutation(self): + from modiff.operation_starters import resolve_operation_starter + + for selection in ( + None, + {}, + {"pipelineClass": "__proto__", "task": "text_to_image"}, + {"pipelineClass": "FluxModularPipeline", "task": None}, + {"pipelineClass": "FluxModularPipeline", "task": "text_to_image", "code": "x"}, + ): + with self.assertRaises(ValueError): + resolve_operation_starter(MODULE_MAP, self.contracts, selection) + + +def test_every_reviewed_starter_wire_retains_compatible_semantic_direction_and_component_names(): + from modules import MODULE_MAP + from modiff.operation_catalog import build_operation_catalog + from modiff.operation_starters import resolve_operation_starter + + contracts, _ = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {}) + for pipeline, task in sorted({(c['pipelineClass'], c['task']) for c in contracts + if c['task'] and c['nodeKey'].startswith('modules.ModularDiffusers.')}): + starter = resolve_operation_starter(MODULE_MAP, contracts, {'pipelineClass': pipeline, 'task': task}) + nodes = {n['operation']['operationId']: n['operation'] for n in starter['nodes']} + for edge in starter['edges']: + left = next(p for p in nodes[edge['source']]['ports'] if p['name'] == edge['sourceHandle'] and p['direction'] == 'output')['semantics'] + right = next(p for p in nodes[edge['target']]['ports'] if p['name'] == edge['targetHandle'] and p['direction'] == 'input')['semantics'] + assert (left['kind'], left['scope']) == (right['kind'], right['scope']), (pipeline, task, edge) + if right['state'] is not None: + assert left['state'] == right['state'], (pipeline, task, edge) + if left['kind'] == 'component' and left['members'] and right['members']: + assert {m['name'] for m in right['members']} <= {m['name'] for m in left['members']}, (pipeline, task, edge) diff --git a/tests/test_optional_runtime_execution.py b/tests/test_optional_runtime_execution.py index 8ad5e286..3d196649 100644 --- a/tests/test_optional_runtime_execution.py +++ b/tests/test_optional_runtime_execution.py @@ -233,6 +233,14 @@ def test_every_current_profile_has_explicit_platform_scoped_delivery(self): self.assertEqual(targeted, requirement) continue self.assertEqual(profile.optional_runtime_delivery, OPTIONAL_RUNTIME_DELIVERY_OVERLAY) + if profile.id == "qwen-image-21:direct": + self.assertEqual(profile.optional_runtime_platform_deliveries, ()) + for platform_name in ("linux", "windows", "macos"): + targeted = declarative_requirement((profile,), platform_name=platform_name, machine="x86_64") + self.assertEqual(targeted["delivery"], "optional_overlay") + self.assertEqual(targeted["profileIds"], ["huggingface-transformers-peft-5.17.0-0.20.0"]) + self.assertEqual(targeted["state"], "unavailable") + continue self.assertEqual(len(profile.optional_runtime_platform_deliveries), 6) requirement = profile.to_public_dict()["optionalRuntimeRequirement"] self.assertEqual(set(requirement), expected_keys) @@ -333,6 +341,13 @@ def test_base_delivery_never_observes_runtime_catalog(self): def test_exact_pair_registry_has_atomic_optional_delivery(self): pairs = {} for profile in DIFFUSERS_EXECUTION_PROFILES.values(): + if profile.operation_recipe: + # Recipes carry their own atomic dependency declaration, but + # are not additional legacy Auto model/task owners. + requirement = declarative_requirement((profile,)) + self.assertNotEqual(requirement["reason"], "execution_profile_contract_invalid") + self.assertEqual(requirement["executionProfileIds"], [profile.id]) + continue for mode in profile.modes: pairs.setdefault((profile.model_type, mode), []).append(profile) for pair, profiles in pairs.items(): @@ -500,11 +515,8 @@ def test_loader_resolution_honors_a_sealed_exact_execution_profile(self): "LoadPipeline", values, ) - self.assertEqual(reason, "loader_profile_ambiguous") - self.assertEqual( - {profile.id for profile in profiles}, - {"ernie-image-turbo:direct", "ernie-image:equivalent-standard"}, - ) + self.assertIsNone(reason) + self.assertEqual([profile.id for profile in profiles], ["ernie-image-turbo:direct"]) profiles, reason = resolve_execution_profiles_for_loader( "modules.DiffusersImage", diff --git a/tests/test_optional_runtimes.py b/tests/test_optional_runtimes.py index 7b9f1777..d508460a 100644 --- a/tests/test_optional_runtimes.py +++ b/tests/test_optional_runtimes.py @@ -25,6 +25,7 @@ TRANSFORMERS_MAIN_PEFT_QUANTO_RUNTIME_PROFILE_ID, TRANSFORMERS_MAIN_PEFT_RUNTIME_PROFILE_ID, TRANSFORMERS_PEFT_RUNTIME_PROFILE_ID, + TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID, optional_runtime_requirements, public_optional_runtime_profiles, ) @@ -96,6 +97,43 @@ def _cpu_hardware(): class OptionalRuntimeContractTests(unittest.TestCase): + def test_qwen21_runtime_is_artifact_locked_and_platform_scoped(self): + profile = OPTIONAL_RUNTIME_PROFILES[TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID] + versions = {package.distribution: package.version for package in profile.packages} + self.assertEqual(versions["transformers"], "5.17.0") + self.assertEqual(versions["tokenizers"], "0.23.1") + self.assertEqual(versions["peft"], "0.20.0") + self.assertFalse(profile.source_builds) + self.assertIn("QwenImage21Pipeline", profile.required_diffusers_symbols) + self.assertIn("AutoencoderKLQwenImage21", profile.required_diffusers_symbols) + for target in profile.target_contracts: + expected = (target.platform, target.machine) == ("linux", "x86_64") + self.assertEqual(target.install_action_available, expected) + self.assertEqual(target.activation_available, expected) + locks = [item for item in profile.artifact_locks if item["distribution"] == "transformers"] + self.assertEqual(len(locks), 6) + self.assertEqual({item["sha256"] for item in locks}, { + "78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801", + }) + self.assertEqual(profile.satisfies_profiles, tuple( + (profile_id, OPTIONAL_RUNTIME_PROFILES[profile_id].spec_digest) + for profile_id in (TRANSFORMERS_PEFT_RUNTIME_PROFILE_ID, TRANSFORMERS_MAIN_PEFT_RUNTIME_PROFILE_ID) + )) + from modiff.optimization_packages import _environment_spec_satisfies_optional_profile + + spec = {"kind": "optional_runtime", "id": profile.id, "specDigest": profile.spec_digest} + for profile_id, digest in profile.satisfies_profiles: + self.assertTrue(_environment_spec_satisfies_optional_profile( + spec, {"id": profile_id, "specDigest": digest}, + )) + self.assertFalse(_environment_spec_satisfies_optional_profile( + spec, {"id": profile_id, "specDigest": "sha256:" + "0" * 64}, + )) + self.assertFalse(_environment_spec_satisfies_optional_profile( + spec, {"id": TRANSFORMERS_MAIN_PEFT_GGUF_RUNTIME_PROFILE_ID, + "specDigest": OPTIONAL_RUNTIME_PROFILES[TRANSFORMERS_MAIN_PEFT_GGUF_RUNTIME_PROFILE_ID].spec_digest}, + )) + def test_transformers_runtime_declares_both_reviewed_speech_model_boundaries(self): profile = OPTIONAL_RUNTIME_PROFILES[TRANSFORMERS_MAIN_PEFT_RUNTIME_PROFILE_ID] transformers = next(package for package in profile.packages if package.distribution == "transformers") @@ -405,7 +443,21 @@ def test_execution_profiles_reference_the_central_composite(self): (), ) continue + if profile.id == "qwen-image-21:direct": + self.assertEqual(profile.optional_runtime_profiles, (TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID,)) + for platform_name in ("linux", "windows", "macos"): + self.assertEqual(profile.optional_runtime_profile_ids_for_target( + platform_name=platform_name, machine="x86_64"), (TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID,)) + continue self.assertEqual(profile.optional_runtime_profiles, expected) + if profile.operation_recipe: + # Exact operation selection resolves dependencies from the + # selected profile, not the legacy model/task resolver. + self.assertEqual(profile.optional_runtime_profile_ids_for_target( + platform_name="linux", machine="x86_64"), (TRANSFORMERS_MAIN_PEFT_RUNTIME_PROFILE_ID,)) + self.assertEqual(profile.optional_runtime_profile_ids_for_target( + platform_name="windows", machine="AMD64"), expected) + continue for mode in profile.modes: self.assertIn( TRANSFORMERS_MAIN_PEFT_RUNTIME_PROFILE_ID, @@ -486,6 +538,12 @@ def test_cold_clean_base_registry_discovery_does_not_load_optional_packages(self import builtins import importlib.util import sys +from modiff.custom_extensions import ExtensionStore + +# This subprocess represents a clean base installation. Operator-enabled +# extensions in the developer's checkout may intentionally import dependencies; +# their separate approval/import contract is exercised by test_custom_extensions. +ExtensionStore.load_enabled = lambda self, registry: None original_import = builtins.__import__ original_find_spec = importlib.util.find_spec @@ -678,6 +736,7 @@ def forbidden_install(*_args, **_kwargs): {profile["id"] for profile in capabilities["optionalRuntimeProfiles"]}, { GALLERY_MEDIA_RUNTIME_PROFILE_ID, + TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID, TRANSFORMERS_PEFT_RUNTIME_PROFILE_ID, TRANSFORMERS_MAIN_PEFT_BITSANDBYTES_RUNTIME_PROFILE_ID, TRANSFORMERS_MAIN_PEFT_GGUF_RUNTIME_PROFILE_ID, diff --git a/tests/test_pipeline_schema.py b/tests/test_pipeline_schema.py index a3ce0d22..0eaf8515 100644 --- a/tests/test_pipeline_schema.py +++ b/tests/test_pipeline_schema.py @@ -1,3 +1,4 @@ +from contextlib import ExitStack import tempfile import unittest from pathlib import Path @@ -144,5 +145,53 @@ def __init__(self): self.assertEqual(second["params"]["steps"]["default"], 4) +class ModularOperationDiscoveryTests(unittest.TestCase): + def test_registered_stage_contracts_are_offline_and_do_not_construct_pipelines(self): + from modules import MODULE_MAP + from modules.ModularDiffusers import MODULAR_REGISTRY + from modules.ModularDiffusers.modular_utils import get_modular_operation_contracts + + configs = MODULAR_REGISTRY.get_all() + with ExitStack() as stack: + stack.enter_context(patch("socket.socket.connect", side_effect=AssertionError("Discovery used network"))) + for pipeline in configs: + stack.enter_context( + patch.object(pipeline, "__init__", side_effect=AssertionError("Constructed pipeline")) + ) + contracts = get_modular_operation_contracts(MODULE_MAP) + self.assertGreater(len(contracts), 0) + by_class = {pipeline.__name__: config for pipeline, config in configs.items()} + for contract in contracts: + with self.subTest(pipeline=contract["pipelineClass"], stage=contract["nodeType"]): + params = by_class[contract["pipelineClass"]].node_params[contract["nodeType"]] + self.assertEqual(contract["blockName"], params["block_name"]) + wrapper_ports = set() + if contract["nodeKey"] == "modules.ModularDiffusers.EncodePrompt": + connected = next((p for p in contract["ports"] if p["name"] == "prompt_input"), None) + if connected: + self.assertEqual(connected["semanticName"], "prompt") + self.assertIn("prompt", params["input_names"]) + wrapper_ports.add(("input", "prompt_input")) + self.assertEqual( + {(p["direction"], p["name"]) for p in contract["ports"]}, + {("input", name) for name in params["input_names"] + params["model_input_names"]} + | {("output", name) for name in params["output_names"]} | wrapper_ports, + ) + self.assertEqual(contract["support"], "declared") + # Different families expose one operation identity, through one saved action. + denoisers = [ + c + for c in contracts + if c["pipelineClass"] + in {"QwenImageModularPipeline", "FluxModularPipeline", "StableDiffusionXLModularPipeline"} + and c["operationId"] == "diffusion.denoise" + ] + self.assertEqual(len(denoisers), 3) + self.assertEqual({c["nodeKey"] for c in denoisers}, {"modules.ModularDiffusers.Denoise"}) + qwen = next(c for c in denoisers if c["pipelineClass"] == "QwenImageModularPipeline") + bundle = next(p for p in qwen["ports"] if p["name"] == "controlnet_bundle") + self.assertEqual(bundle["roles"], ["value", "component"]) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_qwen21_image_adapter.py b/tests/test_qwen21_image_adapter.py new file mode 100644 index 00000000..5ed6f7ea --- /dev/null +++ b/tests/test_qwen21_image_adapter.py @@ -0,0 +1,104 @@ +"""Generic image contracts for Qwen 2.1; no weights or optional imports required.""" +from types import SimpleNamespace + +import pytest +from PIL import Image + +from modules.DiffusersImage.call_inputs import apply_call_inputs, normalize_call_inputs, record_image_call_inputs +from modules.DiffusersImage.main import ( + IMAGE_PIPELINE_ADAPTERS, _tag_image_pipeline, image_pipeline_contract, + preflight_image_action, prepare_reference_images, +) + + +PIPELINE = "QwenImage21Pipeline" +REPO = "Qwen/Qwen-Image-2.1" +REVISION = "790c92633540aa0cb11d9abf19eb46d861714758" + + +def runtime(mode): + pipeline = type(PIPELINE, (), {})() + _tag_image_pipeline(pipeline, IMAGE_PIPELINE_ADAPTERS[PIPELINE], mode, REPO, "hub", REVISION) + return pipeline + + +@pytest.mark.parametrize("mode,action,count", [ + ("text_to_image", "Generate", 0), + ("edit_image", "Edit", 1), + ("multi_image_reference_edit", "Edit", 10), +]) +def test_same_adapter_handles_generation_and_reference_editing(mode, action, count): + images = [Image.new("RGBA", (32, 32), (10, 20, 30, 80)) for _ in range(count)] + adapter, values = preflight_image_action(runtime(mode), action, { + "prompt": "Keep the translucent blue glass; change the background.", + "image": images or None, "width": 1024, "height": 1024, + }) + contract = image_pipeline_contract(adapter, mode) + assert contract["fieldParams"]["max_sequence_length"]["hidden"] + assert contract["fieldParams"]["strength"]["hidden"] + assert not contract["fieldParams"]["use_kv_cache"]["hidden"] + assert values["use_kv_cache"] is True + if images: + refs = prepare_reference_images(images, adapter) + refs = refs if isinstance(refs, list) else [refs] + assert all(a is b for a, b in zip(images, refs, strict=True)) + assert all(image.mode == "RGBA" for image in refs) + + +@pytest.mark.parametrize("value", [True, False]) +def test_cache_flag_reaches_native_call_and_consumed_receipt(value): + selected = normalize_call_inputs(PIPELINE, {"use_kv_cache": value}) + target = {} + apply_call_inputs(selected, target) + assert target["use_kv_cache"] is value + records = [] + node = SimpleNamespace(record_generation_inputs=records.append) + record_image_call_inputs(node, selected, target, IMAGE_PIPELINE_ADAPTERS[PIPELINE]) + assert records[0]["use_kv_cache"] is value + + +@pytest.mark.parametrize("value", [0, 1, "false", "true", {}, []]) +def test_cache_control_rejects_coercion_and_mutable_cache_objects(value): + with pytest.raises(ValueError, match="use_kv_cache must be a boolean"): + normalize_call_inputs(PIPELINE, {"use_kv_cache": value}) + + +def test_cache_control_does_not_leak_to_other_model_families(): + with pytest.raises(ValueError, match="does not support optional input"): + normalize_call_inputs("FluxPipeline", {"use_kv_cache": True}) + assert "use_kv_cache" not in normalize_call_inputs("FluxPipeline", {}) + + +def test_reference_and_output_limits_remain_bounded(): + with pytest.raises(ValueError, match="at most 10"): + preflight_image_action(runtime("multi_image_reference_edit"), "Edit", { + "image": [Image.new("RGB", (16, 16))] * 11, + }) + preflight_image_action(runtime("text_to_image"), "Generate", {"width": 2752, "height": 1536}) + with pytest.raises(ValueError, match="output cannot exceed"): + preflight_image_action(runtime("text_to_image"), "Generate", {"width": 4096, "height": 4096}) + assert IMAGE_PIPELINE_ADAPTERS["QwenImagePipeline"].max_output_side == 2048 + + +def test_qwen21_starters_share_existing_nodes_and_exact_runtime_requirement(): + from modules import MODULE_MAP + from modiff.operation_catalog import build_operation_catalog + from modiff.operation_starters import resolve_operation_starter + from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES + from modiff.optional_runtimes import TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID + + contracts = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {})[0] + profile = DIFFUSERS_EXECUTION_PROFILES["qwen-image-21:direct"] + assert profile.optional_runtime_profiles == (TRANSFORMERS_517_PEFT_RUNTIME_PROFILE_ID,) + for task in ("text_to_image", "edit_image", "multi_image_reference_edit"): + graph = resolve_operation_starter(MODULE_MAP, contracts, { + "pipelineClass": PIPELINE, "task": task, "executionProfileId": profile.id, + }) + loader = next(n for n in graph["nodes"] if n["action"] == "LoadPipeline") + action = "Generate" if task == "text_to_image" else "Edit" + consumer = next(n for n in graph["nodes"] if n["action"] == action) + assert loader["values"]["revision"] == REVISION + assert consumer["params"]["use_kv_cache"]["default"] is True + assert consumer["params"]["width"]["value"] == 2048 + assert consumer["params"]["guidance_scale"]["value"] == 1 + assert all("Qwen" not in n["action"] for n in graph["nodes"]) diff --git a/tests/test_qwen21_upstream_runtime.py b/tests/test_qwen21_upstream_runtime.py new file mode 100644 index 00000000..8994e775 --- /dev/null +++ b/tests/test_qwen21_upstream_runtime.py @@ -0,0 +1,110 @@ +"""Exercise the pinned native cache with a tiny real CPU denoiser, without weights.""" +import importlib.util +from types import SimpleNamespace + +import pytest + + +@pytest.mark.skipif(importlib.util.find_spec("transformers") is None, + reason="Reviewed optional model runtime not active") +def test_native_context_cache_prefills_once_and_is_private_to_each_invocation(monkeypatch): + import torch + from diffusers import ( + AutoencoderKLQwenImage21, QwenImage21Pipeline, QwenImage21Transformer2DModel, + FlowMatchEulerDiscreteScheduler, + ) + + previous_threads = torch.get_num_threads() + torch.set_num_threads(1) + try: + torch.manual_seed(71) + transformer = QwenImage21Transformer2DModel( + patch_size=1, in_channels=4, out_channels=4, num_layers=2, + attention_head_dim=16, num_attention_heads=2, context_in_dim=8, + mlp_ratio=2, axes_dims_rope=(4, 6, 6), causal_condition=True, + ).eval() + # The real denoiser receives supplied embeddings. Only constructor-time + # template metadata is stubbed; this test makes no text-encoding claim. + class ProcessorStub: + tokenizer = SimpleNamespace(encode=lambda value: [2]) + + def apply_chat_template(self, *args, **kwargs): + return [[0, 1]] + + processor = ProcessorStub() + vae = AutoencoderKLQwenImage21( + base_dim=16, decoder_base_dim=16, z_dim=4, dim_mult=[1, 1, 1, 1, 1], + num_res_blocks=1, latents_mean=[0.1] * 4, latents_std=[0.8] * 4, + ).eval() + pipeline = QwenImage21Pipeline( + transformer=transformer, vae=vae, text_encoder=None, processor=processor, + scheduler=FlowMatchEulerDiscreteScheduler(use_dynamic_shifting=False), + ) + pipeline.set_progress_bar_config(disable=True) + embeddings = torch.randn(1, 4, 8) + events, caches = [], [] + forward = transformer.forward + + def observe(*args, **kwargs): + mode, cache = kwargs.get("kv_cache_mode"), kwargs.get("kv_cache") + events.append(mode) + if mode == "extract": + assert all(layer.k is None and layer.v is None for layer in cache.layer_caches) + caches.append(cache) + result = forward(*args, **kwargs) + if mode == "extract": + for layer in cache.layer_caches: + for tensor in layer.get(): + assert tensor.untyped_storage().nbytes() == tensor.numel() * tensor.element_size() + return result + + monkeypatch.setattr(transformer, "forward", observe) + + def run(prompt_embeds, *, enabled=True, interrupted=False, guidance=False): + events.clear() + + def callback(owner, step, timestep, values): + if interrupted: + owner._interrupt = True + return values + + output = pipeline( + prompt_embeds=prompt_embeds, height=64, width=64, num_inference_steps=3, + true_cfg_scale=2.0 if guidance else 1.0, + negative_prompt_embeds=-prompt_embeds if guidance else None, + generator=torch.Generator().manual_seed(123), use_kv_cache=enabled, + output_type="latent", callback_on_step_end=callback, + ).images + expected = (["extract", "cached", "cached"] if enabled else [None] * 3) + if interrupted: + expected = expected[:1] + if guidance: + expected = [event for event in expected for _ in range(2)] + assert events == expected + assert torch.isfinite(output).all() + return output + + baseline = run(embeddings) + torch.testing.assert_close(run(embeddings), baseline, atol=0, rtol=0) + assert not torch.equal(run(embeddings + 0.5), baseline) + run(embeddings, enabled=False) + run(embeddings, interrupted=True) + torch.testing.assert_close(run(embeddings), baseline, atol=0, rtol=0) + run(embeddings, guidance=True) + assert len(caches) == len({id(cache) for cache in caches}) == 7 + + from modules.DiffusersImage.main import DecodeLatents + + original = baseline.clone() + expected = pipeline( + prompt_embeds=embeddings, height=64, width=64, num_inference_steps=3, + generator=torch.Generator().manual_seed(123), use_kv_cache=True, output_type="pt", + ).images + decoded = object.__new__(DecodeLatents).execute(pipeline, baseline, width=64, height=64, output_type="pt") + assert decoded["images"].shape == (1, 4, 64, 64) + torch.testing.assert_close(decoded["images"], expected, atol=0, rtol=0) + torch.testing.assert_close(baseline, original, atol=0, rtol=0) + with pytest.raises(ValueError, match="incompatible layout"): + object.__new__(DecodeLatents).execute(pipeline, baseline, width=128, height=64) + finally: + torch.set_num_threads(previous_threads) diff --git a/tests/test_qwen_modular_decode_matrix.py b/tests/test_qwen_modular_decode_matrix.py index b43e3f82..98471e7f 100644 --- a/tests/test_qwen_modular_decode_matrix.py +++ b/tests/test_qwen_modular_decode_matrix.py @@ -57,7 +57,10 @@ def test_replaced_unpack_decoder_and_output_blocks_execute_with_real_vae(admissi block_definition_id=metadata["blockDefinitionId"], block_class=metadata["blockClass"], block_contract_hash=metadata["blockContractHash"], execution_kind="step", state_in=issued) issued = result["state_out"] - images = state.get("images") + # Each step returns its own state without mutating the preceding node's + # cached state. Inspect the final output, not the original issued input. + assert state.get("images") is None + images = issued._state.get("images") if layered: assert len(images) == 1 and len(images[0]) == 2 images = images[0] @@ -67,9 +70,8 @@ def test_replaced_unpack_decoder_and_output_blocks_execute_with_real_vae(admissi if not layered: decoder = next(node for node in stages if node["modularDiffusers"]["blockClass"] == "QwenImageDecoderStep") metadata = decoder["modularDiffusers"] - state.set("latents", torch.zeros((1, 2, 3))) with pytest.raises(ValueError, match=r"Modular block .*QwenImageDecoderStep.*4D or 5D.*Input shapes: latents=\(1, 2, 3\)"): reviewed_blocks.ReviewedModularWorkflowStep().execute(pipeline_class=definition["pipelineClass"], workflow_id=definition["workflowId"], execution_scope="unpruned_pipeline", composition_recipe=recipe, placement_path=metadata["placementPath"], block_definition_id=metadata["blockDefinitionId"], block_class=metadata["blockClass"], block_contract_hash=metadata["blockContractHash"], - execution_kind="step", state_in=issued) + execution_kind="step", state_in=issued, latents=torch.zeros((1, 2, 3))) diff --git a/tests/test_qwen_standard_safe_wave.py b/tests/test_qwen_standard_safe_wave.py index 594772fb..b687c42f 100644 --- a/tests/test_qwen_standard_safe_wave.py +++ b/tests/test_qwen_standard_safe_wave.py @@ -123,7 +123,7 @@ class QwenStandardSafeWaveTests(unittest.TestCase): def test_exact_pinned_upstream_sources_and_call_signatures_are_preserved(self): import diffusers - self.assertEqual(PINNED_DIFFUSERS_REVISION, "2f7e0154a9db246e95c9ede43edba7db5b130805") + self.assertEqual(PINNED_DIFFUSERS_REVISION, "fbf49e7f35857f76bc57b177e26f12b03687c668") cases = ( ( "pipeline_qwenimage_layered.py", diff --git a/tests/test_removed_guidance_controls.py b/tests/test_removed_guidance_controls.py new file mode 100644 index 00000000..87b790c7 --- /dev/null +++ b/tests/test_removed_guidance_controls.py @@ -0,0 +1,41 @@ +import copy +import json +from pathlib import Path + +import pytest + +from modiff.block_definition_v2 import ( + block_definition_content_hash_v2, + validate_block_definition_v2, + validate_block_instance_v2, +) +from tests.test_block_route_selection_v1 import route_selection_fixture + + +CASES = json.loads((Path(__file__).parent / "fixtures/block_removed_controls_v1.json").read_text()) + + +def test_removed_controls_round_trip_without_execution_identity_changes(): + original = route_selection_fixture() + for bindings in CASES["valid"]: + instance = copy.deepcopy(original) + instance["presentation"]["removedControlBindings"] = bindings + assert validate_block_instance_v2(instance) == instance + definition = copy.deepcopy(instance["definitionSnapshot"]) + definition["removedControlBindings"] = bindings + assert validate_block_definition_v2(definition) == definition + assert block_definition_content_hash_v2(definition) == definition["contentHash"] + + +def test_removed_controls_reject_malformed_and_unbounded_metadata(): + original = route_selection_fixture() + invalid = CASES["invalid"] + [[{"nodeId": f"node-{i}", "fieldId": "scale"} for i in range(4097)]] + for bindings in invalid: + instance = copy.deepcopy(original) + instance["presentation"]["removedControlBindings"] = bindings + with pytest.raises(ValueError, match="removedControlBindings"): + validate_block_instance_v2(instance) + definition = copy.deepcopy(instance["definitionSnapshot"]) + definition["removedControlBindings"] = bindings + with pytest.raises(ValueError, match="removedControlBindings"): + validate_block_definition_v2(definition) diff --git a/tests/test_reviewed_modular_workflow_step.py b/tests/test_reviewed_modular_workflow_step.py index ba1e15e3..378d14b5 100644 --- a/tests/test_reviewed_modular_workflow_step.py +++ b/tests/test_reviewed_modular_workflow_step.py @@ -177,10 +177,14 @@ def test_composed_state_cannot_cross_to_another_recipe_or_unmodified_execution(s value, bundle=None, pipeline_class=PIPELINE_CLASS, workflow_id=WORKFLOW_ID, execution_scope="unpruned_pipeline", composition_hash=incompatible, ) - self.assertIs(reviewed_blocks._continued_runtime( + token, continued, state = reviewed_blocks._continued_runtime( value, bundle=None, pipeline_class=PIPELINE_CLASS, workflow_id=WORKFLOW_ID, execution_scope="unpruned_pipeline", composition_hash="sha256:edited", - )[1], pipeline) + ) + self.assertIs(token, value._token) + self.assertIsNot(continued, pipeline) + self.assertEqual(continued._modiff_composition_hash, pipeline._modiff_composition_hash) + self.assertIsNot(state, value._state) def test_sound_socket_preserves_actual_sample_rate_without_mutating_pipeline_state(self): state = FakeState() diff --git a/tests/test_rocm_vision_attention.py b/tests/test_rocm_vision_attention.py new file mode 100644 index 00000000..21a35bed --- /dev/null +++ b/tests/test_rocm_vision_attention.py @@ -0,0 +1,58 @@ +"""ROCm multimodal attention fallback preserves unrelated attention choices.""" + +from types import SimpleNamespace + +import pytest + +from modules.DiffusersRuntime.main import configure_rocm_vision_attention + + +def model(implementation="sdpa", *, accepts=True): + vision = SimpleNamespace(_attn_implementation=implementation) + text = SimpleNamespace(_attn_implementation="sdpa") + calls = [] + + def set_attention(value): + calls.append(value) + if accepts: + vision._attn_implementation = value["vision_config"] + + return SimpleNamespace( + config=SimpleNamespace(vision_config=vision, text_config=text), + set_attn_implementation=set_attention, + calls=calls, + ) + + +def test_rocm_only_changes_the_sdpa_vision_subconfig_once(): + encoder = model() + pipeline = SimpleNamespace( + components={"text_encoder": encoder, "alias": encoder, "transformer": object(), "processor": object()} + ) + result = configure_rocm_vision_attention( + pipeline, torch_module=SimpleNamespace(version=SimpleNamespace(hip="7.2")) + ) + assert result == ["text_encoder"] + assert encoder.calls == [{"vision_config": "eager"}] + assert encoder.config.text_config._attn_implementation == "sdpa" + assert ( + configure_rocm_vision_attention(pipeline, torch_module=SimpleNamespace(version=SimpleNamespace(hip="7.2"))) + == [] + ) + + +@pytest.mark.parametrize("hip,implementation", [(None, "sdpa"), ("7.2", "eager"), ("7.2", "flash_attention_2")]) +def test_nvidia_and_explicit_non_sdpa_implementations_remain_unchanged(hip, implementation): + encoder = model(implementation) + pipeline = SimpleNamespace(components={"text_encoder": encoder}) + assert ( + configure_rocm_vision_attention(pipeline, torch_module=SimpleNamespace(version=SimpleNamespace(hip=hip))) == [] + ) + assert not encoder.calls + + +def test_a_rejected_fallback_is_not_reported_as_applied(): + encoder = model(accepts=False) + pipeline = SimpleNamespace(components={"text_encoder": encoder}) + with pytest.raises(RuntimeError, match="vision attention"): + configure_rocm_vision_attention(pipeline, torch_module=SimpleNamespace(version=SimpleNamespace(hip="7.2"))) diff --git a/tests/test_runtime_peak_accounting.py b/tests/test_runtime_peak_accounting.py new file mode 100644 index 00000000..19d292ec --- /dev/null +++ b/tests/test_runtime_peak_accounting.py @@ -0,0 +1,94 @@ +"""Graph high-water marks must survive the existing per-node counter resets.""" + +from types import SimpleNamespace +from unittest.mock import patch + +import pytest + +from modiff.server import WebServer +from modiff.auto_resource import _normalized_measurement + + +def runtime(kind="cuda"): + peaks = {0: [100, 120], 1: [200, 240]} + accelerator = SimpleNamespace( + is_available=lambda: True, + device_count=lambda: 2, + reset_peak_memory_stats=lambda index=0: peaks.__setitem__(index, [10, 12]), + max_memory_allocated=lambda index=0: peaks[index][0], + max_memory_reserved=lambda index=0: peaks[index][1], + memory_allocated=lambda index=0: 10, + memory_reserved=lambda index=0: 12, + ) + torch = SimpleNamespace( + cuda=accelerator if kind == "cuda" else SimpleNamespace(is_available=lambda: False), + xpu=accelerator if kind == "xpu" else None, + version=SimpleNamespace(hip="test" if kind == "cuda" else None), + ) + return torch, accelerator, peaks + + +@pytest.mark.parametrize("kind", ["cuda", "xpu"]) +def test_graph_peak_survives_node_reset_and_attempt_reset_isolated(kind): + server = object.__new__(WebServer) + torch, accelerator, peaks = runtime(kind) + with patch("modiff.server.import_module", return_value=torch), patch( + "modiff.server.reset_memory_stats", side_effect=lambda: accelerator.reset_peak_memory_stats(0) + ): + server._reset_runtime_measurement() + peaks[0] = [300, 400] + peaks[1] = [500, 600] + server._reset_node_runtime_measurement() + assert peaks[0] == [10, 12] + result = server._runtime_measurement(elapsed_seconds=2) + assert result["peakAllocatedBytes"] == 300 + assert result["peakReservedBytes"] == 400 + assert result["allocatedBytes"] == 10 + assert result["peakMeasurementVersion"] == 2 + assert server._runtime_memory_peaks[f"{kind}:1"]["peakAllocatedBytes"] == 500 + server._reset_runtime_measurement() + result = server._runtime_measurement(elapsed_seconds=0) + assert result["peakAllocatedBytes"] == 10 + assert result["peakReservedBytes"] == 12 + + +def test_last_node_peak_is_not_lost_without_a_following_reset(): + server = object.__new__(WebServer) + torch, _, peaks = runtime() + with patch("modiff.server.import_module", return_value=torch): + server._reset_runtime_measurement() + peaks[0] = [700, 800] + result = server._runtime_measurement(elapsed_seconds=1) + assert result["peakAllocatedBytes"] == 700 + assert result["peakReservedBytes"] == 800 + + +def test_failed_checkpoint_cannot_publish_a_misleading_graph_peak(): + server = object.__new__(WebServer) + torch, accelerator, _ = runtime() + with patch("modiff.server.import_module", return_value=torch): + server._reset_runtime_measurement() + with patch.object(accelerator, "max_memory_allocated", side_effect=RuntimeError("allocator unavailable")), patch( + "modiff.server.reset_memory_stats" + ): + server._reset_node_runtime_measurement() + result = server._runtime_measurement(elapsed_seconds=1) + assert "peakAllocatedBytes" not in result + assert "peakReservedBytes" not in result + assert "allocator unavailable" in result["acceleratorMeasurementError"] + + +def test_non_graph_node_reset_does_not_create_a_phantom_attempt(): + server = object.__new__(WebServer) + with patch("modiff.server.import_module", side_effect=AssertionError("no active measurement")), patch( + "modiff.server.reset_memory_stats" + ) as reset: + server._reset_node_runtime_measurement() + reset.assert_called_once_with() + assert not hasattr(server, "_runtime_memory_peaks") + + +def test_resource_history_preserves_the_accounting_version(): + assert _normalized_measurement({"peakMeasurementVersion": 2, "peakAllocatedBytes": 300}) == { + "peakMeasurementVersion": 2, "peakAllocatedBytes": 300, + } diff --git a/tests/test_runtime_status.py b/tests/test_runtime_status.py index fc26ba27..c3db201c 100644 --- a/tests/test_runtime_status.py +++ b/tests/test_runtime_status.py @@ -1,6 +1,7 @@ import copy import asyncio import io +import hashlib import json import mimetypes import os @@ -867,6 +868,8 @@ async def test_cancelled_run_releases_runtime_before_the_queue_advances(self): events = [] async def run_callback(_callback, *, serialize_model_io=False, on_start=None): + if on_start is None: + return _callback() if on_start is not None: on_start() if "first" not in events: @@ -974,6 +977,21 @@ def test_expert_pre_run_cleanup_releases_cross_family_cache_and_emits_generic_ev self.assertEqual(messages[0]["type"], "runtime_resource_cleanup") self.assertEqual(messages[0]["resourceMode"], "expert") + def test_identical_multiple_loaders_keep_the_survivors_cleanup_recipe(self): + loader = {'module': 'modules.ModularDiffusers', 'action': 'ModelsLoader', 'params': { + 'model_type': {'value': 'StableDiffusionXLModularPipeline'}, + 'repo_id': {'value': {'source': 'hub', 'value': 'test/model'}}, + 'revision': {'value': 'a' * 40}, 'dtype': {'value': 'float16'}, + 'offload_mode': {'value': 'model_cpu'}, + }} + single = self.server._runtime_cleanup_hints_for_graph({'a': loader}, {'resourceMode': 'expert'}) + multiple = self.server._runtime_cleanup_hints_for_graph({'a': loader, 'b': loader}, {'resourceMode': 'expert'}) + self.assertEqual(self.server._auto_candidate_cache_signature(single), self.server._auto_candidate_cache_signature(multiple)) + loader['params']['revision']['value'] = 'b' * 40 + changed = self.server._runtime_cleanup_hints_for_graph({'a': loader}, {'resourceMode': 'expert'}) + self.assertNotEqual(self.server._auto_candidate_cache_signature(single), self.server._auto_candidate_cache_signature(changed)) + self.assertNotIn('autoResourcePlan', multiple) + def test_structurally_customized_graph_derives_cleanup_identity_without_route_authority(self): graph_nodes = { "models": { @@ -2696,6 +2714,27 @@ def test_auto_planning_does_not_enter_accelerator_apis_during_active_run(self): self.assertEqual(adjusted, fingerprint) + def test_idle_auto_planning_refreshes_cached_host_memory_without_mutating_identity(self): + fingerprint = {"fingerprint": "cached", "resourceFingerprint": "resource", "hardware": hardware_snapshot()} + fingerprint["hardware"]["system"].update(ram_total=128 * GIB, ram_free=16 * GIB, ram_available=72 * GIB) + self.server._last_runtime_fingerprint = copy.deepcopy(fingerprint) + for available in (84 * GIB, 4 * GIB): + with self.subTest(available=available), patch("modiff.hardware.system_memory_snapshot", return_value={ + "total_bytes": 128 * GIB, "free_bytes": 2 * GIB, "available_bytes": available, + }), patch.object(self.server, "_runtime_fingerprint", side_effect=AssertionError("no full probe")): + adjusted = self.server._auto_planning_runtime_fingerprint() + self.assertEqual(adjusted["hardware"]["system"]["ram_available"], available) + self.assertEqual(adjusted["hardware"]["system"]["ram_free"], 2 * GIB) + self.assertEqual(adjusted["fingerprint"], "cached") + self.assertEqual(adjusted["resourceFingerprint"], "resource") + self.assertEqual(self.server._last_runtime_fingerprint, fingerprint) + + def test_idle_auto_planning_does_not_reuse_stale_ram_when_sampling_fails(self): + self.server._last_runtime_fingerprint = {"hardware": hardware_snapshot()} + with patch("modiff.hardware.system_memory_snapshot", side_effect=OSError("probe failed")): + adjusted = self.server._auto_planning_runtime_fingerprint() + self.assertIsNone(adjusted["hardware"]["system"].get("ram_available")) + async def test_auto_plan_uses_capacity_after_releasing_its_resident_cache(self): snapshot = hardware_snapshot() snapshot["devices"][0]["vram_free"] = 4 * GIB @@ -2862,6 +2901,16 @@ def test_resource_fingerprint_ignores_free_memory_and_deterministic_run_state(se self.assertNotEqual(second["fingerprint"], third["fingerprint"]) self.assertEqual(first["resourceFingerprint"], second["resourceFingerprint"]) self.assertEqual(second["resourceFingerprint"], third["resourceFingerprint"]) + legacy_identity = {key: first[key] for key in ("packages", "work_dir", "data_dir")} + legacy_identity["torch"] = { + key: value for key, value in first["torch"].items() + if key not in {"cuda_memory_free_bytes", "cudnn_deterministic", "cudnn_benchmark", "deterministic_algorithms"} + } + legacy_hash = hashlib.sha256(json.dumps(legacy_identity, sort_keys=True, default=str).encode()).hexdigest() + self.assertNotEqual(first["resourceFingerprint"], f"sha256:{legacy_hash}") + legacy_identity["peakMeasurementVersion"] = 2 + current_hash = hashlib.sha256(json.dumps(legacy_identity, sort_keys=True, default=str).encode()).hexdigest() + self.assertEqual(first["resourceFingerprint"], f"sha256:{current_hash}") class PreflightHardwareTests(unittest.TestCase): diff --git a/tests/test_sana_sprint_schedule.py b/tests/test_sana_sprint_schedule.py new file mode 100644 index 00000000..9680614e --- /dev/null +++ b/tests/test_sana_sprint_schedule.py @@ -0,0 +1,57 @@ +"""Exercise real pinned input/scheduler validation without allocating model weights.""" +import inspect +from types import SimpleNamespace + +import pytest +from PIL import Image + +pytest.importorskip("transformers") + +from diffusers import SCMScheduler, SanaSprintImg2ImgPipeline, SanaSprintPipeline +from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS, Generate, Edit, _tag_image_pipeline +from modiff.model_artifact_catalog import catalog_revision + + +@pytest.mark.parametrize("upstream,action,mode", [ + (SanaSprintPipeline, Generate, "text_to_image"), + (SanaSprintImg2ImgPipeline, Edit, "edit_image"), +]) +@pytest.mark.parametrize("steps", [1, 2, 3, 4]) +def test_generic_dispatch_preserves_requested_sprint_steps(upstream, action, mode, steps): + signature = inspect.signature(upstream.__call__) + received = {} + + def call(self, **kwargs): + received.update(kwargs) + bound = signature.bind(self, **kwargs) + bound.apply_defaults() + checks = {key: bound.arguments[key] for key in inspect.signature(upstream.check_inputs).parameters if key != "self"} + upstream.check_inputs(self, **checks) + scheduler = SCMScheduler() + scheduler.set_timesteps( + bound.arguments["num_inference_steps"], + max_timesteps=bound.arguments["max_timesteps"], + intermediate_timesteps=bound.arguments["intermediate_timesteps"], + ) + assert len(scheduler.timesteps) == steps + 1 + if steps == 2: + assert float(scheduler.timesteps[1]) == pytest.approx(1.3) + return SimpleNamespace(images=[Image.new("RGB", (64, 64), "white")]) + + call.__signature__ = signature + pipeline = type(upstream.__name__, (), { + "__call__": call, "_execution_device": "cpu", "_callback_tensor_inputs": ["latents"], + })() + adapter = IMAGE_PIPELINE_ADAPTERS[upstream.__name__] + _tag_image_pipeline(pipeline, adapter, mode, adapter.default_repo, "hub", catalog_revision(adapter.default_repo)) + values = {"pipeline": pipeline, "prompt": "Brass instrument on blue velvet", "seed": "123", + "num_inference_steps": str(steps), "width": 1024, "height": 1024} + if action is Edit: + values.update(image=Image.new("RGB", (1024, 1024)), strength=1.0) + node = action() + node(**values) + assert received["num_inference_steps"] == steps + if steps == 2: + assert "intermediate_timesteps" not in received + else: + assert received["intermediate_timesteps"] is None diff --git a/tests/test_server_security.py b/tests/test_server_security.py index 95397a01..65676232 100644 --- a/tests/test_server_security.py +++ b/tests/test_server_security.py @@ -879,6 +879,46 @@ def reject_queued_dispatch(*_args, **_kwargs): self.assertEqual(broadcasts[0][1], "signal-session") self.assertEqual(self.server.pending_ws_requests, {}) + async def test_disconnected_browser_releases_its_signal_lookup_without_waiting_for_timeout(self): + self.server.loop = asyncio.get_running_loop() + requested = asyncio.Event() + + class DisconnectingWebSocket(EmptyWebSocket): + async def send_json(inner, message): + inner.messages.append(message) + if message["type"] == "get_signal_value": + requested.set() + + async def __anext__(inner): + await requested.wait() + inner.closed = True + raise StopAsyncIteration + + websocket = DisconnectingWebSocket() + with patch("modiff.server.web.WebSocketResponse", return_value=websocket): + serving = asyncio.create_task(self.server.websocket(WebSocketRequest(sid="leaving-session"))) + while "leaving-session" not in self.server.ws_sessions: + await asyncio.sleep(0) + lookup = asyncio.create_task(asyncio.to_thread( + self.server.get_signal_value, "node", "input", "leaving-session", 0.15, + )) + await serving + result = await lookup + self.assertEqual(result, {"__MODIFF_ERROR": "websocket_closed"}) + self.assertEqual(self.server.pending_ws_requests, {}) + + async def test_signal_disconnect_preserves_other_browser_requests(self): + first = asyncio.get_running_loop().create_future() + other = asyncio.get_running_loop().create_future() + self.server.pending_ws_requests.update({"first": first, "other": other}) + self.server.pending_ws_request_sessions.update({"first": "leaving", "other": "staying"}) + self.server._cancel_session_signal_requests("leaving") + self.assertEqual(first.result(), {"__MODIFF_ERROR": "websocket_closed"}) + self.assertFalse(other.done()) + self.assertEqual(self.server.pending_ws_requests, {"other": other}) + self.assertEqual(self.server.pending_ws_request_sessions, {"other": "staying"}) + other.cancel() + async def test_signal_lookup_timeout_cleans_loop_owned_pending_request(self): self.server.loop = asyncio.get_running_loop() self.server.ws_sessions["signal-session"] = EmptyWebSocket() diff --git a/tests/test_service_modular_fields.py b/tests/test_service_modular_fields.py new file mode 100644 index 00000000..ecca7b4d --- /dev/null +++ b/tests/test_service_modular_fields.py @@ -0,0 +1,89 @@ +"""Service interfaces use reviewed dynamic fields, never client type hints.""" + +from copy import deepcopy +from unittest.mock import patch + +import pytest + +from modiff import service_package as service +from modules import MODULE_MAP + + +def graph(pipeline="ZImageModularPipeline"): + def node(action, params): + return {"module": "modules.ModularDiffusers", "action": action, "params": params} + return {"nodes": { + "load": node("ModelsLoader", {"model_type": {"value": pipeline}}), + "encode": node("EncodePrompt", { + "text_encoders": {"sourceId": "load", "sourceKey": "text_encoders"}, + "prompt": {"value": "Rain", "type": "int"}, + "invented": {"value": "not declared", "type": "string"}, + }), + "denoise": node("Denoise", { + "unet": {"sourceId": "load", "sourceKey": "unet"}, + "seed": {"value": 9}, "num_inference_steps": {"value": 8}, + }), + }, "paths": [["load", "encode", "denoise"]]} + + +@pytest.mark.parametrize("pipeline", ["ZImageModularPipeline", "FluxModularPipeline", "QwenImageModularPipeline"]) +def test_reviewed_modular_fields_are_discovered_without_constructing_nodes(pipeline): + g = graph(pipeline) + before = deepcopy(g) + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed node")): + fields = service.inspect_graph(g, MODULE_MAP)["inputs"] + assert {"nodeId": "encode", "field": "prompt", "type": "string"} in fields + assert {"nodeId": "denoise", "field": "seed", "type": "int"} in fields + assert not any(f["field"] in {"invented", "model_type"} for f in fields) + assert g == before + + +@pytest.mark.parametrize("change", ["disconnected", "unknown", "custom", "connected_identity", "ambiguous"]) +def test_unresolved_modular_ownership_never_trusts_graph_types(change): + g = graph() + if change == "disconnected": + g["nodes"]["encode"]["params"]["text_encoders"] = {"value": None} + elif change == "unknown": + g["nodes"]["load"]["params"]["model_type"]["value"] = "UnknownPipeline" + elif change == "custom": + g["nodes"]["load"]["params"]["model_type"]["value"] = "CustomModularPipeline" + elif change == "connected_identity": + g["nodes"]["selector"] = {"module": "modules.Primitive", "action": "TextValue", "params": {"text": {"value": "ZImageModularPipeline"}}} + g["nodes"]["load"]["params"]["model_type"] = {"value": "ZImageModularPipeline", "sourceId": "selector", "sourceKey": "output"} + g["paths"] = [["selector", "load", "encode", "denoise"]] + else: + g["nodes"]["other"] = deepcopy(g["nodes"]["load"]) + g["nodes"]["encode"]["params"]["extra_model"] = {"sourceId": "other", "sourceKey": "text_encoders"} + g["paths"] = [["load", "other", "encode", "denoise"]] + assert not any(f["nodeId"] == "encode" for f in service.inspect_graph(g, MODULE_MAP)["inputs"]) + + +def test_modular_service_roundtrip_keeps_dynamic_types_and_rejects_wrong_invocation_values(): + g = graph() + g['nodes']['load']['params'].update({ + 'repo_id': {'value': {'source': 'hub', 'value': 'Tongyi-MAI/Z-Image-Turbo'}}, + 'revision': {'value': 'f332072aa78be7aecdf3ee76d5c247082da564a6'}, + }) + g['nodes']['denoise']['params']['embeddings'] = {'sourceId': 'encode', 'sourceKey': 'embeddings'} + g['nodes']['decode'] = {'module': 'modules.ModularDiffusers', 'action': 'DecodeLatents', 'params': { + 'vae': {'sourceId': 'load', 'sourceKey': 'vae'}, + 'latents': {'sourceId': 'denoise', 'sourceKey': 'latents'}, + }} + g['nodes']['preview'] = {'module': 'modules.Image', 'action': 'Preview', 'params': { + 'image': {'sourceId': 'decode', 'sourceKey': 'images'}, + 'preview': {'display': 'ui_image', 'sourceKey': 'output'}, + }} + g['paths'][0].extend(['decode', 'preview']) + interface = {'inputs': {'prompt': [{'nodeId': 'encode', 'field': 'prompt'}], + 'seed': [{'nodeId': 'denoise', 'field': 'seed'}]}, + 'outputs': {'image': [{'nodeId': 'preview', 'field': 'preview'}]}} + contract = {'backend': {}, 'packages': {}, 'customNodes': [], 'optionalProfiles': []} + package = service.build_package(g, interface, registry=MODULE_MAP, contract=contract) + values = {'prompt': 'A glass vessel', 'seed': 42} + prepared = service.prepare_package(package, values, registry=MODULE_MAP, contract=contract, sid='test') + assert prepared['nodes']['encode']['params']['prompt']['value'] == values['prompt'] + assert prepared['nodes']['denoise']['params']['seed']['value'] == 42 + assert package['graph']['nodes']['encode']['params']['prompt']['value'] is None + for invalid in ({'prompt': ['a'], 'seed': 42}, {'prompt': 'a', 'seed': True}): + with pytest.raises(ValueError, match='requires'): + service.prepare_package(package, invalid, registry=MODULE_MAP, contract=contract, sid='test') diff --git a/tests/test_service_package.py b/tests/test_service_package.py new file mode 100644 index 00000000..4ba26e51 --- /dev/null +++ b/tests/test_service_package.py @@ -0,0 +1,527 @@ +from copy import deepcopy +import json + +import pytest + +from modiff import service_package as service + +REGISTRY = { + "modules.Primitive": { + "TextValue": { + "params": { + "text": {"type": "string", "display": "text"}, + "output": {"display": "output", "type": "string"}, + } + }, + "DataViewer": { + "params": { + "value": {"display": "input", "type": "any"}, + "preview": {"display": "ui_text", "dataSource": "output"}, + } + }, + } +} +GRAPH = { + "sid": "browser", + "nodes": { + "prompt": { + "module": "modules.Primitive", + "action": "TextValue", + "params": {"text": {"value": "private prompt"}}, + }, + "preview": { + "module": "modules.Primitive", + "action": "DataViewer", + "params": { + "value": {"sourceId": "prompt", "sourceKey": "output"}, + "preview": {"display": "ui_text", "sourceKey": "output"}, + }, + }, + }, + "paths": [["prompt", "preview"]], +} +INTERFACE = { + "inputs": {"prompt": [{"nodeId": "prompt", "field": "text"}]}, + "outputs": {"text": [{"nodeId": "preview", "field": "preview"}]}, +} +CONTRACT = { + "backend": {"fingerprint": "test"}, + "packages": {"diffusers": "0.test"}, + "customNodes": [], + "optionalProfiles": [], +} + + +def build(graph=None, interface=None): + return service.build_package( + deepcopy(graph or GRAPH), deepcopy(interface or INTERFACE), registry=REGISTRY, contract=CONTRACT + ) + + +def prepare(package, values=None, contract=None): + return service.prepare_package( + package, + values or {"prompt": "public input"}, + registry=REGISTRY, + contract=contract or CONTRACT, + sid="service_test", + ) + + +def test_roundtrip_preserves_graph_and_omits_session_snapshots_and_input_defaults(): + graph = deepcopy(GRAPH) + graph["runtimeHints"] = { + "workflowSnapshot": {"path": "/home/private"}, + "workflowTabId": "private", + "device": "cpu", + } + package = build(graph) + assert "private" not in json.dumps(package) + assert "sid" not in package["graph"] + result = prepare(package) + expected = deepcopy(GRAPH) + expected["sid"] = "service_test" + expected["nodes"]["prompt"]["params"]["text"]["value"] = "public input" + assert service.graph_material(result) == service.graph_material(expected) + assert result["runtimeHints"] == {"device": "cpu"} + assert package["graph"]["nodes"]["prompt"]["params"]["text"]["value"] is None + assert result["servicePackage"]["package"] == package + + +def test_textarea_text_alias_is_a_required_string_service_input(): + # Ordinary Diffusers image/audio prompts use "text", while Modular prompts + # use "string". Both must be callable through the same scalar interface. + registry = deepcopy(REGISTRY) + registry["modules.Primitive"]["TextValue"]["params"]["text"] = { + "type": "text", "display": "textarea" + } + assert service.inspect_graph(GRAPH, registry)["inputs"] == [ + {"nodeId": "prompt", "field": "text", "type": "string"} + ] + package = service.build_package(GRAPH, INTERFACE, registry=registry, contract=CONTRACT) + assert package["graph"]["nodes"]["prompt"]["params"]["text"]["value"] is None + result = service.prepare_package( + package, {"prompt": "A blue teapot"}, registry=registry, contract=CONTRACT, sid="service_text" + ) + assert result["nodes"]["prompt"]["params"]["text"]["value"] == "A blue teapot" + for value in (None, 1, True, ["a", "b"], {"text": "a"}): + with pytest.raises(ValueError, match="requires string"): + service.prepare_package( + package, {"prompt": value}, registry=registry, contract=CONTRACT, sid="service_text" + ) + + +def test_text_alias_does_not_expose_connected_fields_or_model_identity(): + graph = deepcopy(GRAPH) + registry = deepcopy(REGISTRY) + fields = registry["modules.Primitive"]["TextValue"]["params"] + for field in ("model_id", "revision", "code"): + fields[field] = {"type": "text", "display": "textarea"} + graph["nodes"]["prompt"]["params"][field] = {"value": "private"} + registry["modules.Primitive"]["DataViewer"]["params"]["value"]["type"] = "text" + assert service.inspect_graph(graph, registry)["inputs"] == service.inspect_graph(GRAPH, REGISTRY)["inputs"] + + +@pytest.mark.parametrize("display", sorted(service.PREVIEWS)) +def test_export_after_execution_omits_preview_observations_without_mutating_graph(display): + registry = deepcopy(REGISTRY) + graph = deepcopy(GRAPH) + registry["modules.Primitive"]["DataViewer"]["params"]["preview"] = { + "type": "string", "display": display, "dataSource": "output" + } + preview = graph["nodes"]["preview"]["params"]["preview"] + preview.update(display=display, value=["/cache/preview/output/0", "/home/private/result.mp4"]) + original = deepcopy(graph) + candidates = service.inspect_graph(graph, registry) + assert not any(c["field"] == "preview" for c in candidates["inputs"]) + package = service.build_package(graph, INTERFACE, registry=registry, contract=CONTRACT) + assert graph == original + assert package["graph"]["nodes"]["preview"]["params"]["preview"] == { + "display": display, "sourceKey": "output" + } + assert "/home/private" not in json.dumps(package) + result = service.prepare_package( + package, {"prompt": "new input"}, registry=registry, contract=CONTRACT, sid="service_preview" + ) + assert "value" not in result["nodes"]["preview"]["params"]["preview"] + + +@pytest.mark.parametrize("change", ["display", "sourceKey", "registry", "connected"]) +def test_untrusted_preview_claim_cannot_hide_private_execution_values(change): + registry = deepcopy(REGISTRY) + graph = deepcopy(GRAPH) + param = graph["nodes"]["preview"]["params"]["preview"] + param["value"] = "/home/private/input.txt" + if change == "display": + param["display"] = "ui_image" + elif change == "sourceKey": + param["sourceKey"] = "different" + elif change == "registry": + registry["modules.Primitive"]["DataViewer"]["params"]["preview"]["display"] = "text" + else: + param["sourceId"] = "prompt" + # Use a second genuine preview so the portable-value guard is exercised + # even when the forged field is not an eligible named output. + graph["nodes"]["other"] = deepcopy(GRAPH["nodes"]["preview"]) + graph["paths"].append(["prompt", "other"]) + interface = deepcopy(INTERFACE) + interface["outputs"]["text"][0]["nodeId"] = "other" + if change == "registry": + registry["modules.Primitive"]["DataViewer"]["params"]["real_preview"] = { + "display": "ui_text", "dataSource": "output" + } + graph["nodes"]["other"]["params"]["real_preview"] = {"display": "ui_text", "sourceKey": "output"} + interface["outputs"]["text"][0]["field"] = "real_preview" + with pytest.raises(ValueError, match="credential/local path"): + service.build_package(graph, interface, registry=registry, contract=CONTRACT) + + +@pytest.mark.parametrize( + "value", + [ + "/home/alice/models", + "C:\\Users\\Alice\\model", + "hf_" + "a" * 30, + "https://name:password@example.org/a", + "@data/private.png", + ], +) +def test_local_paths_and_known_credentials_never_export(value): + graph = deepcopy(GRAPH) + graph["nodes"]["prompt"]["params"]["text"]["value"] = value + with pytest.raises(ValueError, match="credential/local path"): + build(graph, {**INTERFACE, "inputs": {}}) + assert value not in json.dumps(build(graph)) + + +def test_bound_inputs_cannot_change_model_identity_or_connected_fields(): + interface = deepcopy(INTERFACE) + interface["inputs"]["prompt"] = [{"nodeId": "preview", "field": "value"}] + with pytest.raises(ValueError, match="binding"): + build(interface=interface) + interface = deepcopy(INTERFACE) + interface["inputs"]["duplicate"] = interface["inputs"]["prompt"] + with pytest.raises(ValueError, match="duplicated"): + build(interface=interface) + + +def test_tampering_and_runtime_drift_require_new_export(): + package = build() + package["graph"]["nodes"]["prompt"]["params"]["text"]["value"] = "tampered" + with pytest.raises(ValueError, match="content hash"): + prepare(package) + with pytest.raises(ValueError, match="requirements changed"): + prepare(build(), contract={**CONTRACT, "packages": {"diffusers": "different"}}) + with pytest.raises(ValueError, match="requires string"): + prepare(build(), {"prompt": 12}) + with pytest.raises(ValueError, match="exactly"): + prepare(build(), {"prompt": "x", "extra": True}) + + +def test_auto_receipt_is_rebound_to_invocation_values(): + graph = deepcopy(GRAPH) + graph["runtimeHints"] = {"resourceMode": "auto", "workflowAutoPlan": {"graphHash": "stale"}} + package = build(graph) + result = prepare(package) + assert result["runtimeHints"]["workflowAutoPlan"]["graphHash"] == service.workflow_graph_hash(result) + assert "workflowAutoPlan" not in package["graph"]["runtimeHints"] + + +def test_outputs_are_exact_task_scoped_and_do_not_copy_private_snapshots(): + package = build() + receipt = { + "outputs": [ + {"taskId": "old", "nodeId": "preview", "fieldKey": "preview", "value": "old"}, + { + "taskId": "new", + "nodeId": "preview", + "fieldKey": "preview", + "value": ["text"], + "graphSnapshot": {"secret": True}, + }, + ] + } + assert service.service_outputs(package, receipt, "new") == { + "taskId": "new", + "outputs": {"text": [{"value": ["text"]}]}, + } + with pytest.raises(ValueError, match="did not persist"): + service.service_outputs(package, receipt, "missing") + + +@pytest.mark.parametrize("raw", [b'{"a":1,"a":2}', b'{"a":NaN}', b"[]" * 10, b"{" * 10000]) +def test_json_boundary_rejects_duplicate_nonfinite_and_malformed_data(raw): + with pytest.raises(ValueError): + service.load_json(raw) + + +def test_unknown_model_requires_immutable_revision_and_never_downloads(monkeypatch): + graph = deepcopy(GRAPH) + node = graph["nodes"]["prompt"] + node["params"]["repo_id"] = {"value": {"source": "hub", "value": "example/model"}} + from modiff import model_artifact_catalog + + monkeypatch.setattr(model_artifact_catalog, "catalog_repository_pin", lambda _: None) + with pytest.raises(ValueError, match="immutable"): + build(graph) + node["params"]["revision"] = {"value": "a" * 40} + package = build(graph) + assert package["requirements"]["models"] == [ + {"location": "prompt.repo_id", "repository": "example/model", "revision": "a" * 40} + ] + + +def test_spandrel_service_preserves_exact_single_file_pin_without_loading_or_resolving(monkeypatch): + from modiff import controlled_artifacts + from modiff.upscaler_contracts import real_esrgan_x2_model_selection + + monkeypatch.setattr(controlled_artifacts, "resolve_upscaler_artifact", lambda *_: pytest.fail("resolved bytes")) + monkeypatch.setattr(controlled_artifacts, "resolve_model_revision", lambda *_a, **_k: pytest.fail("resolved ref")) + registry = deepcopy(REGISTRY) + registry["modules.Spandrel"] = {"Upscaler": {"params": { + "model_id": {"type": "string", "display": "modelselect"}, + "output": {"type": "image", "display": "output"}, + }}} + graph = deepcopy(GRAPH) + graph["nodes"]["prompt"] = {"module": "modules.Spandrel", "action": "Upscaler", "params": { + "model_id": {"value": real_esrgan_x2_model_selection()}, + }} + interface = {"inputs": {}, "outputs": INTERFACE["outputs"]} + package = service.build_package(graph, interface, registry=registry, contract=CONTRACT) + pin = package["requirements"]["models"][0] + assert pin["repository"] == "nateraw/real-esrgan" + assert pin["weightName"] == "RealESRGAN_x2plus.pth" + assert pin["sha256"] == real_esrgan_x2_model_selection()["sha256"] + prepared = service.prepare_package(package, {}, registry=registry, contract=CONTRACT, sid="upscale_service") + assert prepared["nodes"]["prompt"]["params"]["model_id"]["value"] == real_esrgan_x2_model_selection() + for changes in ({"revision": "main"}, {"sha256": ""}, {"byteSize": True}, {"byteSize": 0}, + {"source": "local"}, {"value": "nateraw/real-esrgan/../escape.pth"}): + graph["nodes"]["prompt"]["params"]["model_id"]["value"] = {**real_esrgan_x2_model_selection(), **changes} + with pytest.raises(ValueError): + service.build_package(graph, interface, registry=registry, contract=CONTRACT) + + +def test_single_file_selector_is_not_admitted_as_a_generic_pipeline_repository(): + from modiff.upscaler_contracts import real_esrgan_x2_model_selection + + graph = deepcopy(GRAPH) + graph["nodes"]["prompt"]["params"]["repo_id"] = {"value": real_esrgan_x2_model_selection()} + with pytest.raises(ValueError, match="explicit Hub repository"): + build(graph) + + +def test_execution_guard_rechecks_contract_and_rejects_modified_prepared_graph(): + from modiff.service_api import ServiceAPI + + class Server(ServiceAPI): + modules = REGISTRY + contract = CONTRACT + + def _service_contract(self, graph): + return self.contract + + def _coerce_runtime_hints(self, hints): + return hints + + server = Server() + graph = prepare(build()) + server._validate_service_graph(graph) + with pytest.raises(ValueError, match="envelope"): + server._validate_service_graph({**graph, "servicePackage": None}) + graph["nodes"]["prompt"]["params"]["text"]["value"] = "changed after preparation" + with pytest.raises(ValueError, match="modified"): + server._validate_service_graph(graph) + server.contract = {**CONTRACT, "customNodes": [{"codeHash": "changed"}]} + with pytest.raises(ValueError, match="requirements changed"): + server._validate_service_graph(prepare(build())) + + +def test_service_api_rejects_malformed_and_duplicate_keys_without_execution(): + import asyncio + from types import SimpleNamespace + from modiff.service_api import ServiceAPI + + class Content: + def __init__(self, raw): + self.raw = raw + + async def iter_chunked(self, _size): + yield self.raw + + server = ServiceAPI() + server.modules = REGISTRY + server._service_contract = lambda graph: CONTRACT + + async def post(body): + response = await server.service_package(SimpleNamespace(content=Content(body))) + return response.status, json.loads(response.text) + + async def scenario(): + status, body = await post(json.dumps({"operation": "build", "graph": GRAPH, "interface": INTERFACE}).encode()) + assert status == 200 and body["package"] == build() + for raw in (b'{"operation":"inspect","operation":"build"}', b'{"operation":"prepare","package":[]}', b"[]"): + status, body = await post(raw) + assert status == 400 and body["error"] is True + + asyncio.run(scenario()) + + +def test_cli_refuses_remote_redirect_and_credential_origins_without_connecting(monkeypatch): + from modiff.service import request + + monkeypatch.setattr("http.client.HTTPConnection", lambda *a, **k: pytest.fail("unexpected network")) + for origin in ( + "https://127.0.0.1", + "http://example.com", + "http://user:secret@127.0.0.1", + "http://127.0.0.1/private", + "http://localhost", + ): + with pytest.raises(ValueError, match="loopback"): + request(origin, "/queue") + + +def test_cli_timeout_reports_exact_task_without_resubmitting(monkeypatch, capsys): + from modiff import service as client + + calls = [] + + def request(server, path, body=None): + calls.append(path) + return ( + {"graph": GRAPH} + if path == "/service_package" + else {"task_id": "owned_task"} + if path == "/graph" + else {"recent": []} + ) + + times = iter([0, 0, 2]) + monkeypatch.setattr(client, "request", request) + monkeypatch.setattr(client.time, "monotonic", lambda: next(times)) + monkeypatch.setattr(client.time, "sleep", lambda _: None) + with pytest.raises(ValueError, match="owned_task.*remains queued/running"): + client.run("http://127.0.0.1:8088", build(), {"prompt": "x"}, timeout=1) + assert calls.count("/graph") == 1 + assert "owned_task" in capsys.readouterr().err + + +def test_preview_must_be_present_in_export_and_not_hidden(): + graph = deepcopy(GRAPH) + graph["nodes"]["preview"]["params"].pop("preview") + assert service.inspect_graph(graph, REGISTRY)["outputs"] == [] + with pytest.raises(ValueError, match="binding"): + build(graph) + + +def test_media_projection_keeps_all_durable_items_without_private_paths(): + receipt = { + "outputs": [ + { + "taskId": "run", + "nodeId": "preview", + "fieldKey": "preview", + "mediaItems": [ + {"taskId": "run", "index": 0, "url": "/file?file=a", "backendPath": "/home/private/a"}, + {"taskId": "run", "index": 1, "url": "/file?file=b", "graphSnapshot": {"private": True}}, + {"taskId": "other", "url": "/wrong-run"}, + ], + } + ] + } + result = service.service_outputs(build(), receipt, "run") + assert result["outputs"]["text"][0]["mediaItems"] == [ + {"index": 0, "url": "/file?file=a"}, + {"index": 1, "url": "/file?file=b"}, + ] + assert "private" not in json.dumps(result) + + +def test_auxiliary_repositories_never_inherit_the_base_models_revision(monkeypatch): + graph = deepcopy(GRAPH) + graph["nodes"]["prompt"]["params"].update( + { + "model_id": {"value": "example/base"}, + "revision": {"value": "a" * 40}, + "lora": {"value": [{"repo_id": "example/auxiliary"}]}, + } + ) + monkeypatch.setattr("modiff.model_artifact_catalog.catalog_repository_pin", lambda _: None) + with pytest.raises(ValueError, match="immutable"): + build(graph) + graph["nodes"]["prompt"]["params"]["lora"]["value"][0]["revision"] = "b" * 40 + assert [pin["revision"] for pin in build(graph)["requirements"]["models"]] == ["a" * 40, "b" * 40] + + +def test_runtime_manifest_observes_active_overlay_first_and_omits_extension_paths(monkeypatch): + from types import SimpleNamespace + from modiff import runtime_profile + + monkeypatch.setattr(runtime_profile, "read_state", lambda _: {"profile": "cpu", "lock_digest": "reviewed"}) + monkeypatch.setattr( + runtime_profile, + "load_manifest", + lambda: {"profiles": {"cpu": {"requirements": "requirements/profiles/cpu.txt"}}}, + ) + monkeypatch.setattr(runtime_profile, "lock_digest", lambda *args, **kw: "reviewed") + monkeypatch.setattr( + "modiff.optional_runtime_execution.graph_optional_runtime_requirement", + lambda _: {"profileIds": ["example-overlay"]}, + ) + monkeypatch.setattr( + service.metadata, + "distributions", + lambda: [ + SimpleNamespace(metadata={"Name": "Example_Package"}, version="2.0"), + SimpleNamespace(metadata={"Name": "example-package"}, version="1.0"), + ], + ) + graph = deepcopy(GRAPH) + graph["nodes"]["prompt"]["module"] = "custom.Example" + store = SimpleNamespace( + require_enabled=lambda _: { + "moduleKey": "custom.Example", + "codeHash": "reviewed", + "revision": None, + "dependencies": [], + "path": "/home/private/source", + } + ) + manifest = service.runtime_contract(graph, {"gitCommit": "a" * 40, "fingerprint": "source"}, store) + assert manifest["packages"] == {"example-package": "2.0"} + assert manifest["optionalProfiles"] == ["example-overlay"] + assert manifest["customNodes"][0]["codeHash"] == "reviewed" + assert "private" not in json.dumps(manifest) + + +def file_input_contract(*, multiple=True): + registry = deepcopy(REGISTRY) + registry['modules.Primitive']['TextValue']['params']['text'] = { + 'type': 'str', 'display': 'filebrowser', 'fieldOptions': {'multiple': multiple} + } + package = service.build_package(GRAPH, INTERFACE, registry=registry, contract=CONTRACT) + return registry, package + + +@pytest.mark.parametrize('value', ['@data/images/a.webp', ['@data/images/a.webp', '@data/images/b.webp']]) +def test_multiple_file_service_input_preserves_native_single_and_list_values(value): + registry, package = file_input_contract() + assert service.inspect_graph(GRAPH, registry)['inputs'][0]['type'] == 'files' + result = service.prepare_package(package, {'prompt': value}, registry=registry, contract=CONTRACT, sid='files_test') + assert result['nodes']['prompt']['params']['text']['value'] == value + assert package['graph']['nodes']['prompt']['params']['text']['value'] is None + + +@pytest.mark.parametrize('value', [[], ['a', 2], [['a']], {'file': 'a'}, [''], ['a'] * 129, '']) +def test_multiple_file_service_input_rejects_invalid_or_unbounded_lists(value): + registry, package = file_input_contract() + with pytest.raises(ValueError, match='requires files'): + service.prepare_package(package, {'prompt': value}, registry=registry, contract=CONTRACT, sid='files_test') + + +def test_file_list_contract_requires_trusted_registry_multiple_flag(): + registry, package = file_input_contract(multiple=False) + package['graph']['nodes']['prompt']['params']['text']['fieldOptions'] = {'multiple': True} + package['contentHash'] = service.digest({key: value for key, value in package.items() if key != 'contentHash'}) + with pytest.raises(ValueError, match='requires str'): + service.prepare_package(package, {'prompt': ['a', 'b']}, registry=registry, contract=CONTRACT, sid='files_test') diff --git a/tests/test_standard_operation_contracts.py b/tests/test_standard_operation_contracts.py new file mode 100644 index 00000000..2edcf301 --- /dev/null +++ b/tests/test_standard_operation_contracts.py @@ -0,0 +1,112 @@ +"""Standard declarations must retain the exact existing task/node boundary.""" + +from dataclasses import replace +import unittest +from unittest.mock import patch + +from modules import MODULE_MAP +from modules.DiffusersImage import main as image +from modules.DiffusersVideo import main as video +from modules.DiffusersAudio import main as audio +from modules.DiffusersThreeD import main as three_d + + +OWNERS = ( + (image, image.get_image_operation_contracts), + (video, video.get_video_operation_contracts), + (audio, audio.get_audio_operation_contracts), + (three_d, three_d.get_three_d_operation_contracts), +) + + +class StandardOperationDiscoveryTests(unittest.TestCase): + def test_discovery_is_offline_and_does_not_construct_nodes_or_install_packages(self): + with ( + patch("socket.socket.connect", side_effect=AssertionError("Discovery used network")), + patch("subprocess.Popen", side_effect=AssertionError("Discovery spawned process")), + patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("Discovery constructed node")), + ): + contracts = [record for _owner, discover in OWNERS for record in discover(MODULE_MAP)] + self.assertGreater(len(contracts), 100) + self.assertEqual(len({(c['pipelineClass'], c['operationId'], c['task']) for c in contracts}), len(contracts)) + for contract in contracts: + with self.subTest(pipeline=contract['pipelineClass'], task=contract['task']): + self.assertIsNone(contract['blockName']) + self.assertEqual(contract['support'], 'declared') + self.assertIn(contract['decomposition'], ('loader', 'pipeline')) + handles = [port for port in contract['ports'] if port['roles'] == ['pipeline']] + self.assertEqual(len(handles), 1) + self.assertEqual(handles[0]['direction'], 'output' if contract['decomposition'] == 'loader' else 'input') + + def test_task_fields_match_the_existing_dynamic_actions(self): + # Exercise real field actions on uninitialized nodes: no model or node + # runtime is needed to compare their presentation/requiredness contracts. + for owner, discover in OWNERS: + for contract in discover(MODULE_MAP): + if contract['decomposition'] == 'loader': + continue + action = contract['nodeKey'].rsplit('.', 1)[1] + cls = getattr(owner, action) + fields = {} + node = cls.__new__(cls) + node.class_name = action + node.set_field_params = lambda name, params: fields.setdefault(name, {}).update(params) + pipeline, mode = contract['pipelineClass'], contract['task'] + if owner is image: + signal = image.image_pipeline_contract(image.IMAGE_PIPELINE_ADAPTERS[pipeline], mode) + node.update_image_contract({'image_contract': signal}, None) + elif owner is video: + signal = video._adapter_signal(video.VIDEO_PIPELINE_ADAPTERS[pipeline]) + node.update_adapter_modes({'video_contract': signal, 'mode': mode}, None) + elif owner is audio: + adapter = audio.AUDIO_PIPELINE_ADAPTERS[pipeline] + signal = adapter.contract_for_mode(mode).signal_value(pipeline, adapter.default_repo) + node.update_audio_contract({'audio_contract': signal}, None) + else: + signal = three_d.THREE_D_PIPELINE_ADAPTERS[pipeline].signal_value() + node.update_three_d_contract({'three_d_contract': signal}, None) + module = contract['nodeKey'].rsplit('.', 1)[0] + base = MODULE_MAP[module][action]['params'] + with self.subTest(pipeline=pipeline, task=mode, action=action): + for port in contract['ports']: + expected = {**base[port['name']], **fields.get(port['name'], {})} + self.assertEqual(port['hidden'], expected.get('hidden') is True) + self.assertEqual(port['required'], port['direction'] == 'input' and expected.get('required') is True) + + def test_specialized_outputs_and_required_conditioning_are_not_flattened(self): + contracts = [record for _owner, discover in OWNERS for record in discover(MODULE_MAP)] + for contract in contracts: + pipeline, action = contract['pipelineClass'], contract['nodeKey'].rsplit('.', 1)[1] + ports = {p['name']: p for p in contract['ports']} + with self.subTest(pipeline=pipeline, task=contract['task'], action=action): + self.assertNotEqual(action, 'GenerateLTX2') # Retained alias, no duplicate discovery entry. + if action == 'GenerateVideoAudio': + self.assertIn('audio', video.VIDEO_PIPELINE_ADAPTERS[pipeline].output_media) + self.assertEqual(ports['audio']['types'], ['audio']) + if action == 'GenerateRenderedArtifact': + self.assertEqual(ports['video']['types'], ['video']) + self.assertFalse(any('mesh' in p['types'] for p in ports.values())) + if three_d.THREE_D_PIPELINE_ADAPTERS[pipeline].input_kind == 'image': + self.assertTrue(ports['reference_images']['required']) + self.assertFalse(ports['reference_images']['hidden']) + if action in ('Inpaint', 'ControlInpaint'): + self.assertTrue(ports['mask_image']['required']) + if action == 'PredictMap': + self.assertEqual(ports['prediction_map']['types'], ['prediction_map']) + expected_av = { + (name, mode) for name, adapter in video.VIDEO_PIPELINE_ADAPTERS.items() + for mode in adapter.modes if 'audio' in adapter.output_media + } + self.assertEqual( + {(c['pipelineClass'], c['task']) for c in contracts if c['operationId'] == 'diffusion.generate_video_audio'}, + expected_av, + ) + + def test_new_adapter_is_discovered_without_a_family_allowlist_and_missing_actions_are_omitted(self): + adapter = replace(next(iter(audio.AUDIO_PIPELINE_ADAPTERS.values())), pipeline_class='FutureAudioPipeline') + with patch.dict(audio.AUDIO_PIPELINE_ADAPTERS, {'FutureAudioPipeline': adapter}): + records = audio.get_audio_operation_contracts(MODULE_MAP) + self.assertEqual(len([c for c in records if c['pipelineClass'] == 'FutureAudioPipeline']), len(adapter.modes) * 2) + for owner, discover in OWNERS: + with self.subTest(owner=owner.__name__): + self.assertEqual(discover({}), []) diff --git a/tests/test_studio_execution_specs.py b/tests/test_studio_execution_specs.py index 8cd152ae..b1b81f23 100644 --- a/tests/test_studio_execution_specs.py +++ b/tests/test_studio_execution_specs.py @@ -500,6 +500,7 @@ def test_flux_registry_owns_profile_capability_and_auto_contracts(self): ("CogView3PlusPipeline", "text_to_image"), ("CogView4Pipeline", "text_to_image"), ("ErnieImagePipeline", "text_to_image"), + ("ErnieImageModularPipeline", "text_to_image"), ("GlmImagePipeline", "text_to_image"), ("JoyImageEditPipeline", "text_to_image"), ("JoyImageEditPipeline", "edit_image"), @@ -517,6 +518,8 @@ def test_flux_registry_owns_profile_capability_and_auto_contracts(self): ("StableDiffusionPAGPipeline", "control_image"), ("StableDiffusionPAGPipeline", "control_inpaint"), ("MarigoldDepthPipeline", "depth_estimation"), + ("DepthAnythingV2Model", "depth_estimation"), + ("DepthAnythingV2MetricModel", "depth_estimation"), ("HuggingFaceTextGenerationModel", "text_generation"), ("HuggingFaceImageTextToTextModel", "image_to_text"), ("HuggingFaceAnyToAnyModel", "text_generation"), @@ -530,6 +533,9 @@ def test_flux_registry_owns_profile_capability_and_auto_contracts(self): ("FluxDepthPipeline", "control_inpaint"), ("FluxCannyPipeline", "control_edit_image"), ("FluxCannyPipeline", "control_inpaint"), + ("QwenImage21Pipeline", "text_to_image"), + ("QwenImage21Pipeline", "edit_image"), + ("QwenImage21Pipeline", "multi_image_reference_edit"), ("QwenImageEditPipeline", "edit_image"), ("QwenImageEditPlusPipeline", "edit_image"), ("QwenImageEditPlusPipeline", "multi_image_reference_edit"), @@ -569,7 +575,6 @@ def test_flux_registry_owns_profile_capability_and_auto_contracts(self): ("Flux2Pipeline", "multi_image_reference_edit"), ("Flux2ModularPipeline", "text_to_image"), ("Flux2ModularPipeline", "edit_image"), - ("ErnieImageModularPipeline", "text_to_image"), ("LTXModularPipeline", "text_to_video"), ("LTXModularPipeline", "image_to_video"), ("Wan22ModularPipeline", "text_to_video"), diff --git a/tests/test_studio_history_responsiveness.py b/tests/test_studio_history_responsiveness.py index c4ae6acc..55f97829 100644 --- a/tests/test_studio_history_responsiveness.py +++ b/tests/test_studio_history_responsiveness.py @@ -5,6 +5,7 @@ import tempfile import threading import unittest +from contextlib import contextmanager from types import SimpleNamespace from unittest.mock import AsyncMock, patch @@ -31,6 +32,153 @@ def setUp(self): {"id": "one", "createdAt": 1, "prompt": "older"}, ]) + async def test_saved_block_validation_and_encoding_are_off_the_http_thread(self): + main_thread = threading.get_ident() + encoded = self.server._json_response_bytes + + def blocks(): + self.assertNotEqual(threading.get_ident(), main_thread) + return [{"id": "saved", "name": "Saved Block"}] + + def encode(payload): + self.assertNotEqual(threading.get_ident(), main_thread) + return encoded(payload) + + with (patch.object(self.server, "_list_studio_blocks", side_effect=blocks), + patch.object(self.server, "_json_response_bytes", side_effect=encode)): + response = await self.server.studio_blocks_get(Request()) + self.assertEqual(json.loads(response.body)["blocks"], [{"id": "saved", "name": "Saved Block"}]) + + async def test_unchanged_history_avoids_monolithic_decode_and_readers_are_independent(self): + original = self.server._read_studio_output_state() + with patch("modiff.server.json.load", side_effect=AssertionError("decoded unchanged history")): + first = self.server._read_studio_output_state() + first["outputs"][0]["prompt"] = "only this reader" + second = self.server._read_studio_output_state() + self.assertEqual(second, original) + + async def test_run_detail_decodes_only_its_matching_cached_records(self): + outputs = [{"id": str(i), "taskId": f"task-{i}", "graphSnapshot": {"large": "x" * 1000}} + for i in range(100)] + outputs.append({"id": "legacy", "provenance": {"backendExecutionId": "task-42"}}) + self.server._write_studio_outputs(outputs) + decode = json.loads + decoded = [] + + def record_decode(value, *args, **kwargs): + result = decode(value, *args, **kwargs) + decoded.append(result["id"]) + return result + + with patch("modiff.server.json.loads", side_effect=record_decode): + matched = self.server._studio_outputs_for_run("task-42") + self.assertEqual(decoded, ["42", "legacy"], "A run lookup must not deserialize other workflows.") + self.assertEqual([output["id"] for output in matched], decoded) + matched[0]["graphSnapshot"]["large"] = "reader edit" + self.assertEqual(self.server._studio_outputs_for_run("task-42")[0]["graphSnapshot"]["large"], "x" * 1000) + + async def test_indexed_run_detail_invalidates_replaced_deleted_and_corrupt_history(self): + self.server._write_studio_outputs([{"id": "old", "taskId": "task"}]) + self.assertEqual(self.server._studio_outputs_for_run("task")[0]["id"], "old") + target = self.server._studio_history_file() + replacement = target.with_suffix(".external") + replacement.write_text(json.dumps([{"id": "new", "taskId": "new-task"}])) + replacement.replace(target) + self.assertEqual(self.server._studio_outputs_for_run("task"), []) + self.assertEqual(self.server._studio_outputs_for_run("new-task")[0]["id"], "new") + target.write_text("invalid JSON") + self.assertEqual(self.server._studio_outputs_for_run("new-task"), []) + target.write_text(json.dumps([{"id": "repaired", "taskId": "task"}])) + self.assertEqual(self.server._studio_outputs_for_run("task")[0]["id"], "repaired") + target.unlink() + self.assertEqual(self.server._studio_outputs_for_run("task"), []) + + async def test_history_cache_observes_atomic_replacement_removal_and_failed_write(self): + original = self.server._read_studio_output_state() + with self.assertRaises(TypeError): + self.server._write_studio_output_state([{"id": "invalid", "value": object()}], {}, revision=9) + self.assertEqual(self.server._read_studio_output_state(), original) + target = self.server._studio_history_file() + replacement = target.with_suffix(".external") + replacement.write_text(json.dumps({"version": 2, "revision": 41, "outputs": [{"id": "external"}], "previewSlots": {}})) + replacement.replace(target) + self.assertEqual(self.server._read_studio_output_state()["revision"], 41) + target.unlink() + self.assertEqual(self.server._read_studio_output_state()["outputs"], []) + target.write_text(json.dumps([{"id": "legacy", "prompt": "café 雨"}])) + self.assertEqual(self.server._read_studio_output_state()["outputs"][0]["id"], "legacy") + + async def test_history_cache_invalidates_in_place_edits_and_corrupt_content(self): + self.server._read_studio_output_state() + target = self.server._studio_history_file() + target.write_text(json.dumps([{"id": "edited", "prompt": "external edit"}])) + self.assertEqual(self.server._read_studio_outputs()[0]["id"], "edited") + target.write_text("invalid JSON") + self.assertEqual(self.server._read_studio_outputs(), []) + target.write_text(json.dumps([{"id": "repaired"}])) + self.assertEqual(self.server._read_studio_outputs(), [{"id": "repaired"}]) + + async def test_history_response_does_not_encode_the_entire_retained_collection_at_once(self): + # The accelerated encoder holds the GIL. One call per retained record + # bounds that interval without discarding old current previews or graphs. + state = {"revision": 8, "outputs": [ + {"id": "new", "graphSnapshot": {"nodes": [{"prompt": "café 雨"}]}}, + {"id": "retained", "graphSnapshot": {"nodes": [{"prompt": "original"}]}}, + ], "previewSlots": {"slot": {"currentOutputId": "retained"}}} + encoded = self.server._json_response_bytes + + def encode(value): + self.assertFalse(isinstance(value, dict) and "outputs" in value and "previewSlots" in value, + "The whole retained history must not enter one GIL-holding encoder call.") + return encoded(value) + + with (patch.object(self.server, "_read_studio_output_state", return_value=state), + patch.object(self.server, "_json_response_bytes", side_effect=encode)): + response = await self.server.studio_outputs_get(Request()) + payload = json.loads(response.body) + self.assertEqual(payload["outputs"], state["outputs"]) + self.assertEqual(payload["revision"], 8) + self.assertEqual(payload["previewSlots"], list(state["previewSlots"].values())) + + async def test_history_write_batches_nested_records_and_preserves_retained_previews(self): + # Real workflow snapshots contain thousands of nested fields. Streaming + # json.dump writes each punctuation token while holding the history lock. + snapshot = {"nodes": [{"id": str(i), "params": {"prompt": "café 雨", "seed": i}} + for i in range(1000)]} + outputs = [{"id": str(i), "workflowSnapshot": snapshot if i == 0 else {}} + for i in range(202)] + slots = {"retained": {"currentOutputId": "201"}} + writes = [] + + @contextmanager + def counted_open(*args, **kwargs): + with open(*args, **kwargs) as stream: + def write(value): + writes.append(len(value)) + return stream.write(value) + yield SimpleNamespace(write=write) + + with patch("modiff.server.open", side_effect=counted_open): + kept = self.server._write_studio_output_state(outputs, slots, revision=17) + payload = json.loads(self.server._studio_history_file().read_text(encoding="utf-8")) + self.assertEqual(payload["outputs"], outputs[:200] + [outputs[201]]) + self.assertEqual(payload["outputs"], kept) + self.assertEqual(payload["previewSlots"], slots) + self.assertEqual(payload["revision"], 17) + self.assertEqual(payload["version"], 2) + self.assertLess(len(writes), 3 * len(kept) + 20, + "Nested fields must not produce individual locked file writes.") + + async def test_failed_history_encoding_preserves_previous_atomic_document(self): + before = self.server._studio_history_file().read_bytes() + with self.assertRaises(TypeError): + self.server._write_studio_output_state( + [{"id": "new", "invalid": object()}], {}, revision=23 + ) + self.assertEqual(self.server._studio_history_file().read_bytes(), before) + self.server._write_studio_output_state([{"id": "recovered"}], {}, revision=24) + self.assertEqual(self.server._read_studio_output_state()["revision"], 24) + async def test_slow_browser_does_not_hold_up_other_browser_completion(self): stalled = asyncio.Event() delivered = asyncio.Event() diff --git a/tests/test_task_authoring.py b/tests/test_task_authoring.py new file mode 100644 index 00000000..bf9b51dd --- /dev/null +++ b/tests/test_task_authoring.py @@ -0,0 +1,114 @@ +"""Task selection uses reviewed starters without constructing or loading models.""" + +from copy import deepcopy +import unittest +from unittest.mock import patch + +from modules import MODULE_MAP +from modiff.diffusers_profiles import public_execution_profiles +from modiff.operation_catalog import build_operation_catalog +from modiff.task_authoring import resolve_task_starter, starter_api_graph, unbind_starter + + +class TaskAuthoringTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + profiles = public_execution_profiles() + contracts, support = build_operation_catalog(MODULE_MAP, profiles, catalog_resolver=lambda: {}) + # Readiness is an independent runtime probe; keep this selection fixture + # deterministic in both base and activated optional-runtime environments. + for pipeline in support: + for task in pipeline["tasks"]: + task["dependencies"] = "ready" + cls.catalog = {"operationContracts": contracts, "pipelineSupport": support, + "diffusersExecutionProfiles": profiles} + + def resolve(self, selection=None, *, available=(), fits=lambda graph: {"canAutoRun": True}, catalog=None): + with patch("modiff.NodeBase.NodeBase.__init__", side_effect=AssertionError("constructed model")): + return resolve_task_starter( + MODULE_MAP, catalog or self.catalog, selection or {"task": "text_to_image"}, + installed=lambda profile: profile["id"] in available, inspect_resources=fits, + ) + + def test_only_installed_compatible_model_is_bound(self): + result = self.resolve(available=("flux-schnell:direct",)) + self.assertFalse(result["unbound"]) + self.assertEqual(result["profileId"], "flux-schnell:direct") + + def test_remembered_profile_is_advisory_and_memory_is_still_checked(self): + selection = {"task": "text_to_image", "preferredProfileId": "flux-krea:direct"} + installed = ("flux-schnell:direct", "flux-krea:direct") + self.assertEqual(self.resolve(selection, available=installed)["profileId"], "flux-krea:direct") + checked = [] + + def fits(graph): + repo = graph["nodes"]["diffusion.load_models"]["params"]["model_id"]["value"]["value"] + checked.append(repo) + return {"canAutoRun": repo.endswith("schnell")} + + result = self.resolve(selection, available=installed, fits=fits) + self.assertEqual(result["profileId"], "flux-schnell:direct") + self.assertEqual(checked, ["black-forest-labs/FLUX.1-Krea-dev", "black-forest-labs/FLUX.1-schnell"]) + self.assertEqual(self.resolve({**selection, "preferredProfileId": "unknown"}, available=(installed[0],))["profileId"], installed[0]) + + def test_unavailable_or_memory_limited_selection_leaves_required_empty_model(self): + for available in ((), ("flux-schnell:direct",)): + with self.subTest(available=available): + result = self.resolve(available=available, fits=lambda graph: {"canAutoRun": False}) + self.assertTrue(result["unbound"]) + self.assertIsNone(result["profileId"]) + loader = result["starter"]["nodes"][0] + key = "repo_id" if loader["action"] == "ModelsLoader" else "model_id" + self.assertEqual(loader["params"][key]["value"], {"source": "hub", "value": ""}) + self.assertEqual(loader["params"][key]["default"], loader["params"][key]["value"]) + self.assertTrue(loader["params"][key]["required"]) + self.assertIn({"operationId": loader["operation"]["operationId"], "field": key}, result["starter"]["requiredInputs"]) + + def test_custom_memory_policy_skips_auto_recipe_but_not_runtime_readiness(self): + catalog = deepcopy(self.catalog) + for pipeline in catalog["pipelineSupport"]: + if pipeline["pipelineClass"] == "FluxPipeline": + for task in pipeline["tasks"]: + task["dependencies"] = "blocked" + selection = {"task": "text_to_image", "resourceMode": "expert"} + with patch("modiff.task_authoring.starter_api_graph", side_effect=AssertionError("Auto in custom policy")): + self.assertFalse(self.resolve(selection, available=("flux-schnell:direct",))["unbound"]) + self.assertTrue(self.resolve(selection, available=("flux-schnell:direct",), catalog=catalog)["unbound"]) + + def test_invalid_selections_do_not_touch_model_inventory(self): + for selection in ({}, {"task": "text_to_image", "module": "remote"}, {"task": "../bad"}, + {"task": "text_to_image", "preferredProfileId": []}, + {"task": "text_to_image", "resourceMode": "other"}): + with self.subTest(selection=selection), self.assertRaises(ValueError): + resolve_task_starter(MODULE_MAP, self.catalog, selection, + installed=lambda profile: self.fail("unexpected inventory"), inspect_resources=None) + + def test_declared_task_without_published_profiles_still_creates_an_unbound_draft(self): + catalog = deepcopy(self.catalog) + catalog["diffusersExecutionProfiles"] = [] + result = self.resolve(catalog=catalog) + self.assertTrue(result["unbound"]) + self.assertIsNone(result["profileId"]) + self.assertGreater(len(result["starter"]["nodes"]), 1) + + def test_initial_choice_prefers_smaller_known_memory_requirement_over_alphabetical_model(self): + def inspect(graph): + repo = graph["nodes"]["diffusion.load_models"]["params"]["model_id"]["value"]["value"] + return {"canAutoRun": True, "requirements": {"systemRamBytes": 2 if repo.endswith("schnell") else 20, "vramBytes": 1}} + result = self.resolve(available=("flux-krea:direct", "flux-schnell:direct"), fits=inspect) + self.assertEqual(result["profileId"], "flux-schnell:direct") + + def test_native_modular_projection_retains_wires_and_unbinding_does_not_mutate_source(self): + from modiff.operation_starters import resolve_operation_starter + + starter = resolve_operation_starter(MODULE_MAP, self.catalog["operationContracts"], + {"pipelineClass": "ZImageModularPipeline", "task": "text_to_image"}) + original = deepcopy(starter) + unbound = unbind_starter(starter) + self.assertEqual(starter, original) + graph = starter_api_graph(unbound) + self.assertEqual(len(graph["nodes"]), 4) + for edge in unbound["edges"]: + self.assertEqual(graph["nodes"][edge["target"]]["params"][edge["targetHandle"]], + {"sourceId": edge["source"], "sourceKey": edge["sourceHandle"]}) + self.assertLess(graph["paths"][0].index(edge["source"]), graph["paths"][0].index(edge["target"])) diff --git a/tests/test_task_template_contracts.py b/tests/test_task_template_contracts.py index ba16c41a..53a974aa 100644 --- a/tests/test_task_template_contracts.py +++ b/tests/test_task_template_contracts.py @@ -35,7 +35,7 @@ async def asyncSetUp(self): async def test_every_execution_spec_has_one_exact_stable_task_contract(self): self.assertEqual(self.payload["taskTemplateContractSchemaVersion"], 1) - self.assertEqual(len(self.contracts), 273) + self.assertEqual(len(self.contracts), 278) self.assertEqual(set(self.contract_by_pair), set(self.spec_by_pair)) self.assertEqual(self.contracts, sorted(self.contracts, key=lambda item: item["id"])) self.assertEqual(self.contracts, json.loads(json.dumps(self.contracts))) diff --git a/tests/test_template_gallery_install.py b/tests/test_template_gallery_install.py index c38d278e..226a3db7 100644 --- a/tests/test_template_gallery_install.py +++ b/tests/test_template_gallery_install.py @@ -11,6 +11,7 @@ TemplateGalleryError, _path_is_link_or_reparse, install_template_gallery, + load_template_gallery_source, plan_template_gallery_install, validate_template_gallery_manifest, verify_template_gallery_tree, @@ -188,6 +189,27 @@ def test_plan_reserves_download_staging_active_queue_and_safety_space(self): self.assertFalse(plan["installed"]) self.assertFalse(cache.exists(), "A read-only install plan must not create the cache directory.") + def test_local_bundle_uses_pinned_gallery_source_for_install_plan(self): + self.source["mode"] = "local" + source_path, remote_manifest = self._write_contract() + self.assertEqual(load_template_gallery_source(source_path), self.source) + + _source, _manifest, plan = plan_template_gallery_install( + source_path, + self.root / "web/template-gallery", + cache_root=self.root / "cache", + reserve_bytes=0, + download_file=lambda **_kwargs: str(remote_manifest), + ) + self.assertEqual(plan["repoId"], self.source["repoId"]) + self.assertEqual(plan["revision"], self.source["revision"]) + self.assertFalse(plan["installed"]) + + self.source["revision"] = "main" + source_path.write_text(json.dumps(self.source), encoding="utf-8") + with self.assertRaisesRegex(TemplateGalleryError, "source contract is invalid"): + load_template_gallery_source(source_path) + def test_install_stages_and_verifies_before_atomic_promotion(self): from huggingface_hub import constants as hf_constants diff --git a/tests/test_transformers_depth.py b/tests/test_transformers_depth.py new file mode 100644 index 00000000..f310f3d9 --- /dev/null +++ b/tests/test_transformers_depth.py @@ -0,0 +1,173 @@ +"""Depth adapters retain native values and the existing normalized map contract.""" +from types import SimpleNamespace +from unittest.mock import Mock, patch + +import pytest +import torch +from PIL import Image + +from utils.torch_utils import DEFAULT_DEVICE + +from modules.HuggingFaceTransformers.depth import depth_geometry, depth_convention, predict_depth +from modules.HuggingFaceTransformers.main import LoadDepthEstimationModel, PredictDepth, _sealed_receipt, SECURITY_CONTRACT + + +class DPTImageProcessor: + size = {"height": 28, "width": 28} + ensure_multiple_of = 14 + keep_aspect_ratio = True + do_pad = False + do_resize = True + + def __call__(self, *, images, size, return_tensors): + _, shape = depth_geometry(self, images[0], size["height"]) + return {"pixel_values": torch.ones(shape)} + + def post_process_depth_estimation(self, output, target_sizes): + value = output.predicted_depth + if target_sizes is not None: + value = torch.nn.functional.interpolate(value[:, None], size=target_sizes[0], mode="bicubic", align_corners=False)[:, 0] + return [{"predicted_depth": value[0]}] + + +class DepthModel: + def __init__(self, kind="relative"): + self.config = SimpleNamespace(model_type="depth_anything", depth_estimation_type=kind, max_depth=80) + self.inputs = [] + + def forward(self, pixel_values): + self.inputs.append(pixel_values) + h, w = pixel_values.shape[-2:] + return SimpleNamespace(predicted_depth=torch.linspace(2, 22, h * w).reshape(1, h, w)) + + __call__ = forward + + +def handle(model=None, processor=None): + model = model or DepthModel() + processor = processor or DPTImageProcessor() + receipt = _sealed_receipt({ + "schemaVersion": 1, "library": "transformers", "task": "depth-estimation", + "source": {"kind": "hub", "repository": "owner/model", "revision": "a" * 40}, + "security": SECURITY_CONTRACT, + "loader": {"preprocessorAutoClass": "AutoImageProcessor", "modelAutoClass": "AutoModelForDepthEstimation"}, + "runtime": {"dtype": "float32", "device": "cpu"}, + }) + return {"schemaVersion": 1, "kind": "transformers-depth-estimation", "model": model, "preprocessor": processor, "receipt": receipt} + + +@pytest.mark.parametrize("kind,near_is_larger", [("relative", True), ("metric", False)]) +def test_native_depth_is_not_replaced_by_its_normalized_preview(kind, near_is_larger): + selected = handle(DepthModel(kind)) + result = PredictDepth.execute(None, pipeline=selected, image=Image.new("RGB", (28, 28))) + native = result["native_depth"] + assert native.shape == (28, 28) + assert float(native.min()) == 2 and float(native.max()) == 22 + prediction = result["prediction_map"] + assert prediction["shape"] == [1, 28, 28, 1] + assert prediction["nearValue"] == 0 and prediction["farValue"] == 1 + assert float(prediction["prediction"][0, 0, 0, 0]) == (1 if near_is_larger else 0) + assert result["preview_images"][0].size == (28, 28) + assert result["result"]["modelReceipt"] == selected["receipt"] + + +def test_output_resolution_uses_official_postprocessing_and_keeps_model_reusable(): + selected = handle() + for size, match in [((56, 28), True), ((112, 56), False)]: + output = PredictDepth.execute(None, pipeline=selected, image=Image.new("RGB", size), match_input_resolution=match) + assert output["native_depth"].shape == ((size[1], size[0]) if match else (28, 56)) + assert len(selected["model"].inputs) == 2 + + +def test_invalid_geometry_is_rejected_before_preprocessing_or_forward(): + processor = DPTImageProcessor() + model = DepthModel() + with pytest.raises(ValueError, match="geometry"): + predict_depth(model, processor, Image.new("RGB", (4096, 1)), receipt=handle()["receipt"], resolution=2048, match_input=True, convention="model_default") + assert not model.inputs + + +@pytest.mark.parametrize("image", [None, [], [Image.new("RGB", (28, 28))] * 2]) +def test_exactly_one_source_image_is_required(image): + with pytest.raises(ValueError): + PredictDepth.execute(None, pipeline=handle(), image=image) + + +def test_nonfinite_output_is_rejected(): + model = DepthModel() + model.forward = Mock(return_value=SimpleNamespace(predicted_depth=torch.full((1, 28, 28), float("nan")))) + class BadModel(DepthModel): + def __call__(self, **kwargs): + return model.forward(**kwargs) + with pytest.raises(RuntimeError, match="nonfinite"): + PredictDepth.execute(None, pipeline=handle(BadModel()), image=Image.new("RGB", (28, 28))) + + +def test_unreviewed_depth_polarity_requires_an_explicit_choice(): + model = SimpleNamespace(config=SimpleNamespace(model_type="another_model")) + with pytest.raises(ValueError, match="convention"): + depth_convention(model, "model_default") + assert depth_convention(model, "far_is_larger") == ("far_is_larger", "operator_selected") + + +def test_loader_uses_existing_local_only_model_loading_boundary(): + with patch("modules.HuggingFaceTransformers.main._load_model", return_value=("model", "receipt")) as load: + result = LoadDepthEstimationModel.execute(None, model_id="owner/model", revision="a" * 40, dtype="float32", device="cpu") + assert result == {"pipeline": "model", "receipt": "receipt"} + assert load.call_args.kwargs == {"selection_value": "owner/model", "revision_value": "a" * 40, "dtype_value": "float32", "device_value": "cpu", "task": "depth-estimation"} + + +def test_curated_models_share_generic_connected_nodes_and_exact_artifact_pins(): + from modules import MODULE_MAP + from modiff.operation_catalog import build_operation_catalog + from modiff.operation_starters import resolve_operation_starter + contracts = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {})[0] + for profile in ("depth-anything-v2-small:direct", "depth-anything-v2-metric-outdoor-small:direct"): + starter = resolve_operation_starter(MODULE_MAP, contracts, {"pipelineClass": "AutoModelForDepthEstimation", "task": "depth_estimation", "executionProfileId": profile}) + loader = next(n for n in starter["nodes"] if n["action"] == "LoadDepthEstimationModel") + assert len(loader["values"]["revision"]) == 40 + assert any(n["action"] == "PredictDepth" for n in starter["nodes"]) + assert any(e["sourceHandle"] == "pipeline" and e["targetHandle"] == "pipeline" for e in starter["edges"]) + + +def test_official_auto_loader_preserves_immutable_local_only_safetensors_flags(): + model = DepthModel() + model.to = Mock() + model.eval = Mock() + processor = DPTImageProcessor() + runtime = SimpleNamespace( + __version__="5.14.1", + AutoImageProcessor=SimpleNamespace(from_pretrained=Mock(return_value=processor)), + AutoModelForDepthEstimation=SimpleNamespace(from_pretrained=Mock(return_value=model)), + ) + with patch.dict("sys.modules", {"transformers": runtime}): + loaded = LoadDepthEstimationModel.execute(None, model_id="owner/model", revision="a" * 40, dtype="float32", device=DEFAULT_DEVICE) + common = {"revision": "a" * 40, "local_files_only": True, "trust_remote_code": False} + runtime.AutoImageProcessor.from_pretrained.assert_called_once_with("owner/model", **common) + runtime.AutoModelForDepthEstimation.from_pretrained.assert_called_once_with( + "owner/model", **common, dtype=torch.float32, use_safetensors=True, weights_only=True, low_cpu_mem_usage=True, + ) + assert loaded["pipeline"]["model"] is model + assert loaded["receipt"]["task"] == "depth-estimation" + + +@pytest.mark.parametrize("revision", ["main", "", "a" * 39]) +def test_depth_loader_rejects_mutable_identity_before_loading(revision): + runtime = SimpleNamespace(AutoImageProcessor=Mock(), AutoModelForDepthEstimation=Mock()) + with patch.dict("sys.modules", {"transformers": runtime}), pytest.raises(ValueError, match="commit"): + LoadDepthEstimationModel.execute(None, model_id="owner/model", revision=revision, dtype="float32", device="cpu") + runtime.AutoImageProcessor.assert_not_called() + runtime.AutoModelForDepthEstimation.assert_not_called() + + +def test_depth_controls_are_in_the_bounded_consumed_input_receipt(): + from modiff.execution_input_provenance import capture_generation_inputs + record = capture_generation_inputs("depth", {"module": "modules.HuggingFaceTransformers", "action": "PredictDepth"}, { + "processing_resolution": 0, "match_input_resolution": False, "depth_convention": "model_default", + "pipeline": object(), "image": Image.new("RGB", (28, 28)), + }) + assert record["fields"] == { + "processing_resolution": {"source": "literal", "value": 0}, + "match_input_resolution": {"source": "literal", "value": False}, + "depth_convention": {"source": "literal", "value": "model_default"}, + } diff --git a/tests/test_transformers_depth_optional.py b/tests/test_transformers_depth_optional.py new file mode 100644 index 00000000..2dcdbc35 --- /dev/null +++ b/tests/test_transformers_depth_optional.py @@ -0,0 +1,38 @@ +"""No-download checks against the already approved official runtime.""" +import importlib.util +from types import SimpleNamespace + +import pytest +import torch +from PIL import Image + +from modules.HuggingFaceTransformers.depth import depth_geometry, predict_depth + +pytestmark = pytest.mark.skipif(importlib.util.find_spec("transformers") is None, reason="Requires reviewed optional Transformers runtime") + + +@pytest.mark.parametrize("image_size,resolution", [((1024, 1024), 0), ((1328, 1024), 0), ((640, 384), 392)]) +def test_real_dpt_preprocessor_matches_preflight_geometry(image_size, resolution): + from transformers import DPTImageProcessor + processor = DPTImageProcessor(size={"height": 518, "width": 518}, keep_aspect_ratio=True, ensure_multiple_of=14) + image = Image.new("RGB", image_size, color=(30, 70, 100)) + size, shape = depth_geometry(processor, image, resolution) + actual = processor(images=[image], size=size, return_tensors="pt") + assert set(actual) == {"pixel_values"} + assert tuple(actual["pixel_values"].shape) == shape + + +def test_real_native_auto_class_and_postprocessing_contract_without_weights(): + from transformers import AutoImageProcessor, AutoModelForDepthEstimation, DPTImageProcessor + assert callable(AutoImageProcessor.from_pretrained) + assert callable(AutoModelForDepthEstimation.from_pretrained) + processor = DPTImageProcessor(size={"height": 28, "width": 28}, keep_aspect_ratio=True, ensure_multiple_of=14) + class Model: + config = SimpleNamespace(model_type="depth_anything", depth_estimation_type="relative") + def __call__(self, pixel_values): + h, w = pixel_values.shape[-2:] + return SimpleNamespace(predicted_depth=torch.arange(h * w, dtype=torch.float32).reshape(1, h, w)) + result = predict_depth(Model(), processor, Image.new("RGB", (84, 56)), receipt={"runtime": {"dtype": "float32", "device": "cpu"}}, resolution=0, match_input=True, convention="model_default") + assert result["native_depth"].shape == (56, 84) + assert result["preview_images"][0].size == (84, 56) + assert result["result"]["nativeDepth"]["resizedToInput"] is True diff --git a/tests/test_upscaler_selection_authoring.py b/tests/test_upscaler_selection_authoring.py new file mode 100644 index 00000000..4b21dc66 --- /dev/null +++ b/tests/test_upscaler_selection_authoring.py @@ -0,0 +1,112 @@ +"""Native model edits must persist exact installed single-file identities.""" +import hashlib +from importlib import import_module +from unittest.mock import Mock, patch + +import pytest + +from modiff import controlled_artifacts +from modiff.field_metadata import is_metadata_field_action, metadata_field_callback +from modiff.upscaler_contracts import real_esrgan_x2_model_selection +from utils.huggingface import CONFIG + + +@pytest.fixture +def cached_upscaler(tmp_path, monkeypatch): + monkeypatch.setitem(CONFIG.hf, 'cache_dir', str(tmp_path)) + repository = tmp_path / 'models--example--upscaler' + weight = repository / 'snapshots' / ('a' * 40) / 'weights' / 'upscale.pth' + weight.parent.mkdir(parents=True) + weight.write_bytes(b'installed-upscaler') + (repository / 'refs').mkdir() + (repository / 'refs' / 'main').write_text('a' * 40) + return repository, weight + + +def test_cached_file_selection_is_pinned_before_admission(cached_upscaler): + _, weight = cached_upscaler + selection = {'source': 'hub', 'value': 'example/upscaler/weights/upscale.pth'} + with patch('huggingface_hub.hf_hub_download', side_effect=AssertionError('no download')): + pinned = controlled_artifacts.pin_upscaler_model_selection(selection) + resolved = controlled_artifacts.resolve_upscaler_artifact(pinned) + assert pinned == {**selection, 'revision': 'a' * 40, + 'sha256': hashlib.sha256(weight.read_bytes()).hexdigest(), + 'byteSize': weight.stat().st_size} + assert resolved.path == weight + assert 'revision' not in selection + # Once authored, changing the cache's main ref must not change this workflow. + (cached_upscaler[0] / 'refs' / 'main').write_text('b' * 40) + assert controlled_artifacts.resolve_upscaler_artifact(pinned).path == weight + + +def test_complete_reviewed_pin_can_be_authored_without_installed_weights(): + selection = real_esrgan_x2_model_selection() + with patch('utils.huggingface.cached_file_path', side_effect=AssertionError('no cache inspection')): + assert controlled_artifacts.pin_upscaler_model_selection(selection) == selection + + +def test_missing_cache_ref_never_selects_newest_snapshot(cached_upscaler): + repository, _ = cached_upscaler + (repository / 'refs' / 'main').unlink() + with pytest.raises(FileNotFoundError, match='installed'): + controlled_artifacts.pin_upscaler_model_selection( + {'source': 'hub', 'value': 'example/upscaler/weights/upscale.pth'}) + + +def test_explicit_revision_is_honored_and_local_selection_gets_integrity(cached_upscaler): + _, weight = cached_upscaler + selected = {'source': 'hub', 'value': 'example/upscaler/weights/upscale.pth', 'revision': 'a' * 40} + assert controlled_artifacts.pin_upscaler_model_selection(selected)['revision'] == 'a' * 40 + local = controlled_artifacts.pin_upscaler_model_selection({'source': 'local', 'value': str(weight)}) + assert 'revision' not in local + assert local['byteSize'] == weight.stat().st_size + assert controlled_artifacts.resolve_upscaler_artifact(local).path == weight + + +@pytest.mark.parametrize('metadata', [ + {'revision': 'main'}, {'sha256': '0' * 64}, {'byteSize': 1}, {'byteSize': True}, +]) +def test_invalid_or_mismatched_declared_identity_is_not_repaired_silently(cached_upscaler, metadata): + with pytest.raises(ValueError): + controlled_artifacts.pin_upscaler_model_selection( + {'source': 'hub', 'value': 'example/upscaler/weights/upscale.pth', **metadata}) + + +def test_cache_alias_outside_exact_repository_is_rejected(cached_upscaler, tmp_path): + _, weight = cached_upscaler + outside = tmp_path / 'other.pth' + outside.write_bytes(b'outside') + weight.unlink() + weight.symlink_to(outside) + with pytest.raises(ValueError, match='outside'): + controlled_artifacts.pin_upscaler_model_selection( + {'source': 'hub', 'value': 'example/upscaler/weights/upscale.pth'}) + + +def test_newly_resolved_cached_ref_still_checks_predeclared_integrity(cached_upscaler): + _, weight = cached_upscaler + with pytest.raises(ValueError, match='SHA-256'): + controlled_artifacts.pin_upscaler_model_selection({ + 'source': 'hub', 'value': 'example/upscaler/weights/upscale.pth', + 'sha256': '0' * 64, 'byteSize': weight.stat().st_size, + }) + + +@pytest.mark.parametrize('module,action', [('modules.Spandrel', 'Upscaler'), ('modules.Video', 'UpscaleVideo')]) +def test_authoring_callback_has_no_model_owner_and_publishes_pin(cached_upscaler, module, action): + from modules import MODULE_MAP + method = MODULE_MAP[module][action]['params']['model_id'].get('onChange') + assert method == 'update_model_selection' + assert is_metadata_field_action({'module': module, 'action': action, 'fn': method}) + assert not is_metadata_field_action({'module': module, 'action': action, 'fn': method, 'queue': True}) + node_class = getattr(import_module(module + '.main'), action) + with patch.object(node_class, '__init__', side_effect=AssertionError('no executable owner')): + callback = metadata_field_callback(action, method, module=module, node_id='editing', sid='browser') + callback.__self__.set_field_value = Mock() + callback({'model_id': {'source': 'hub', 'value': 'example/upscaler/weights/upscale.pth'}}, {'key': 'model_id'}) + value = callback.__self__.set_field_value.call_args.args[0]['model_id'] + assert value['revision'] == 'a' * 40 + assert not hasattr(callback.__self__, 'execute') + callback.__self__.set_field_value.reset_mock() + callback({'model_id': {'source': 'hub', 'value': ''}}, {'key': 'model_id'}) + callback.__self__.set_field_value.assert_not_called() diff --git a/tests/test_upstream_coverage.py b/tests/test_upstream_coverage.py index a832a4b7..be381dc2 100644 --- a/tests/test_upstream_coverage.py +++ b/tests/test_upstream_coverage.py @@ -64,12 +64,12 @@ def test_checked_in_ledger_has_exact_reviewed_counts(self): "canonicalWorkflowCount": 200, "canonicalWorkflowsWithPublicTemplates": 52, "canonicalWorkflowsWithoutPublicTemplates": 148, - "diffusersPipelineSymbolCount": 330, + "diffusersPipelineSymbolCount": 334, "pipelineStatusCounts": { "contract-only": 5, - "equivalent": 11, - "executable": 142, - "intentionally-excluded": 56, + "equivalent": 10, + "executable": 144, + "intentionally-excluded": 59, "research-blocked": 116, "unreviewed": 0, }, @@ -83,12 +83,12 @@ def test_checked_in_ledger_has_exact_reviewed_counts(self): "research-blocked": 0, "unreviewed": 0, }, - "transformersProductionSupportedSemanticCount": 5, - "transformersSemanticCount": 6, + "transformersProductionSupportedSemanticCount": 6, + "transformersSemanticCount": 7, "transformersSemanticStatusCounts": { "contract-only": 0, "equivalent": 0, - "executable": 4, + "executable": 5, "intentionally-excluded": 0, "research-blocked": 2, "unreviewed": 0, @@ -109,7 +109,7 @@ def test_every_diffusers_export_is_unique_and_explicitly_classified(self): names = [item["name"] for item in items] self.assertEqual(names, sorted(names)) self.assertEqual(len(names), len(set(names))) - self.assertEqual(len(names), 330) + self.assertEqual(len(names), 334) self.assertTrue({item["status"] for item in items}.issubset(UPSTREAM_COVERAGE_STATUSES)) by_name = {item["name"]: item for item in items} @@ -236,7 +236,7 @@ def test_diffusers_scope_is_the_exact_dependency_pin(self): scope = self.ledger["scope"]["diffusers"] self.assertEqual(scope["revision"], PINNED_DIFFUSERS_REVISION) self.assertEqual(scope["verifiedSourceRevision"], PINNED_DIFFUSERS_REVISION) - self.assertEqual(scope["version"], "0.40.0.dev0") + self.assertEqual(scope["version"], "0.41.0.dev0") self.assertRegex(scope["exportModuleSha256"], r"^[0-9a-f]{64}$") def test_transformers_main_and_production_are_not_conflated(self): @@ -268,6 +268,7 @@ def test_transformers_semantic_inventory_is_finite_and_honest(self): set(by_id), { "speech-recognition", + "bounded-depth-estimation", "bounded-causal-text-generation", "bounded-image-video-to-text", "any-to-any-generation", @@ -277,6 +278,7 @@ def test_transformers_semantic_inventory_is_finite_and_honest(self): ) for semantic_id in ( "speech-recognition", + "bounded-depth-estimation", "bounded-causal-text-generation", "bounded-image-video-to-text", "any-to-any-generation", diff --git a/tests/test_upstream_coverage_exact_closure.py b/tests/test_upstream_coverage_exact_closure.py index d5ece6ec..437bc039 100644 --- a/tests/test_upstream_coverage_exact_closure.py +++ b/tests/test_upstream_coverage_exact_closure.py @@ -11,7 +11,7 @@ ROOT = Path(__file__).resolve().parents[1] -PINNED_EXPORT_SHA256 = "a31b3d860c85b22f08976b00c81a00f36cd3986c702715954bb230882803493f" +PINNED_EXPORT_SHA256 = "f9536d3cd5f2f5df8992fa4b4b0fb53a407eea15744fcc288760624ce9111251" PROMOTED_PIPELINES = { "AnimateDiffControlNetPipeline", "AnimateDiffPAGPipeline", @@ -27,6 +27,7 @@ "LTX2InContextPipeline", "LTX2Pipeline", "Cosmos3OmniModularPipeline", + "ErnieImageModularPipeline", "StableDiffusionControlNetImg2ImgPipeline", "StableDiffusionControlNetInpaintPipeline", "StableDiffusionControlNetPAGInpaintPipeline", @@ -37,7 +38,6 @@ "StableDiffusionXLControlNetPAGPipeline", } NEW_EQUIVALENT_PIPELINES = { - "ErnieImageModularPipeline", "LTX2ModularPipeline", "LTXModularPipeline", "LuminaText2ImgPipeline", @@ -49,19 +49,19 @@ class UpstreamCoverageExactClosureTests(unittest.TestCase): def test_exact_pin_closes_with_the_reviewed_finite_partition(self): source = Path(diffusers.__file__).resolve().parent - self.assertEqual(PINNED_DIFFUSERS_REVISION, "2f7e0154a9db246e95c9ede43edba7db5b130805") + self.assertEqual(PINNED_DIFFUSERS_REVISION, "fbf49e7f35857f76bc57b177e26f12b03687c668") self.assertEqual(source_sha256(source / "__init__.py"), PINNED_EXPORT_SHA256) self.assertTrue(PROMOTED_PIPELINES.isdisjoint(_REVIEWED_PIPELINE_DECISIONS)) version, items = _pipeline_coverage(ROOT, source) - self.assertEqual(version, "0.40.0.dev0") - self.assertEqual(len(items), 330) + self.assertEqual(version, "0.41.0.dev0") + self.assertEqual(len(items), 334) self.assertEqual( Counter(item["status"] for item in items), { - "executable": 142, - "equivalent": 11, - "intentionally-excluded": 56, + "executable": 144, + "equivalent": 10, + "intentionally-excluded": 59, "research-blocked": 116, "contract-only": 5, }, diff --git a/tests/test_vae_optional_memory.py b/tests/test_vae_optional_memory.py new file mode 100644 index 00000000..960412a8 --- /dev/null +++ b/tests/test_vae_optional_memory.py @@ -0,0 +1,51 @@ +"""Inherited optional VAE hooks can exist without an implementation.""" + +from types import SimpleNamespace +from unittest.mock import Mock, patch + +import pytest + +from modules.DiffusersAudio.main import LoadPipeline +from modules.DiffusersRuntime.main import configure_vae_memory + + +@pytest.mark.parametrize("enabled", [False, True]) +def test_runtime_reports_unimplemented_vae_hooks_as_unsupported(enabled): + vae = SimpleNamespace(**{ + name: Mock(side_effect=NotImplementedError("Unsupported VAE feature")) + for name in ("enable_tiling", "disable_tiling", "enable_slicing", "disable_slicing") + }) + result = configure_vae_memory(SimpleNamespace(vae=vae), slicing=enabled, tiling=enabled) + assert result == {"applied": [], "unsupported": ["slicing", "tiling"]} + getattr(vae, "enable_tiling" if enabled else "disable_tiling").assert_called_once_with() + + +def test_runtime_does_not_hide_vae_configuration_errors(): + vae = SimpleNamespace(enable_tiling=Mock(side_effect=RuntimeError("broken kernel"))) + with pytest.raises(RuntimeError, match="broken kernel"): + configure_vae_memory(SimpleNamespace(vae=vae), slicing=False, tiling=True) + + +@pytest.mark.parametrize("error", [NotImplementedError("Unsupported tiling"), RuntimeError("broken kernel")]) +def test_audio_loader_skips_only_unimplemented_tiling(error): + vae = SimpleNamespace(enable_tiling=Mock(side_effect=error)) + pipeline = SimpleNamespace(vae=vae) + loader = LoadPipeline("audio-optional-tiling") + loader.mm_add = Mock() + cls = SimpleNamespace(from_pretrained=Mock(return_value=pipeline)) + with ( + patch("modules.DiffusersAudio.main.pipeline_class_from_name", return_value=cls), + patch("modules.DiffusersAudio.main.local_files_only", return_value=True), + patch("modules.DiffusersAudio.main.exact_cached_snapshot_path", return_value="installed-snapshot"), + patch("modules.DiffusersAudio.main.apply_pipeline_offload"), + ): + kwargs = {"pipeline_class": "LongCatAudioDiTPipeline", "mode": "text_to_audio", "enable_vae_tiling": True} + if isinstance(error, NotImplementedError): + result = loader.execute(**kwargs) + assert result["pipeline"] is pipeline + loader.mm_add.assert_called_once_with(pipeline, priority=2) + else: + with pytest.raises(RuntimeError, match="broken kernel"): + loader.execute(**kwargs) + loader.mm_add.assert_not_called() + vae.enable_tiling.assert_called_once_with() diff --git a/tests/test_wan_animate_2_artifact_review.py b/tests/test_wan_animate_2_artifact_review.py index 0bef4e1d..8cfa9ab4 100644 --- a/tests/test_wan_animate_2_artifact_review.py +++ b/tests/test_wan_animate_2_artifact_review.py @@ -10,7 +10,6 @@ import diffusers from modiff.model_artifact_catalog import catalog_repository_pin -from modiff.modular_workflow_contracts import PINNED_DIFFUSERS_REVISION from modiff.modular_workflow_discovery import reviewed_modular_workflow_contract from modules.ModularDiffusers.loaders import ModelsLoader from modules.ModularDiffusers.modular_utils import pin_modular_component_revisions @@ -27,7 +26,9 @@ def setUp(self): self.repositories = {item["role"]: item for item in self.review["repositories"]} def test_family_is_graph_qualified_and_cataloged_without_claiming_live_proof(self): - self.assertEqual(self.review["diffusersRevision"], PINNED_DIFFUSERS_REVISION) + # An archived artifact review records the source it actually inspected; + # upgrading the shared runtime must not relabel it as a new live proof. + self.assertEqual(self.review["diffusersRevision"], "2f7e0154a9db246e95c9ede43edba7db5b130805") self.assertEqual(self.review["format"], "safetensors") admission = self.review["admission"] self.assertEqual(admission["status"], "graph_qualified") diff --git a/tests/test_wan_vace.py b/tests/test_wan_vace.py index bad85383..7911eb84 100644 --- a/tests/test_wan_vace.py +++ b/tests/test_wan_vace.py @@ -23,6 +23,7 @@ def test_loader_uses_the_app_configured_hugging_face_cache(self): node.mm_add = Mock() vae = Mock() pipeline = Mock() + pipeline.components = {"vae": vae} with ( patch.dict(CONFIG.hf, {"cache_dir": "E:/MoDiff/huggingface/hub", "online_status": "Auto"}), diff --git a/tests/test_workflow_auto_resource.py b/tests/test_workflow_auto_resource.py index 4b58d836..30d2b773 100644 --- a/tests/test_workflow_auto_resource.py +++ b/tests/test_workflow_auto_resource.py @@ -51,8 +51,9 @@ def test_multiple_blocks_count_separate_model_owners_and_preserve_graph(setup): assert len(result["loaders"]) == 2 assert len(requests) == 1 # Same recipe inspected once; owners still counted twice. assert result["retainedRequirements"]["systemRamBytes"] == 600 - assert result["requirements"]["systemRamBytes"] == 300 - assert result["strategy"] == "dependency_order_release_owners" + assert result["requirements"]["systemRamBytes"] == 600 + assert result["strategy"] == "dependency_order_retained_owners" + assert result["schedule"] is None assert result["loaders"][0]["consumers"] == ["generate"] assert g == before @@ -319,6 +320,63 @@ def test_composed_image_output_is_retained_across_owner_release(setup): assert result['schedule']['releases'][0]['retainOutputs'] == {'generate': ['images']} +def test_split_decoder_dimension_connections_are_planned_without_executing_denoise(setup): + plan, _, _, requests = setup + g = graph() + g['nodes']['generate']['action'] = 'Denoise' + g['nodes']['generate']['params']['width']['value'] = '1024' + g['nodes']['decode'] = node( + 'DecodeLatents', vae={'sourceId': 'load', 'sourceKey': 'vae_out'}, + latents={'sourceId': 'generate', 'sourceKey': 'latents'}, + width={'sourceId': 'generate', 'sourceKey': 'out_width'}, + height={'sourceId': 'generate', 'sourceKey': 'out_height'}, + ) + g['paths'] = [list(g['nodes'])] + before = deepcopy(g) + result = plan(g) + assert result['canAutoRun'], result['issues'] + assert not result['requiresPreparation'] + # The executor rechecks these integer outputs against its actual connected + # arguments before allocating decoder resources; no cached latent is read. + assert result['resolvedFields']['decode'] == {'width': 1024, 'height': 1024} + assert requests[-1]['form']['width'] == 1024 + assert g == before + + +@pytest.mark.parametrize('change', ['different_latents', 'unknown_output', 'non_integer']) +def test_split_decoder_dimension_projection_does_not_authorize_unknown_geometry(setup, change): + plan, _, _, _ = setup + g = graph() + g['nodes']['generate']['action'] = 'Denoise' + g['nodes']['decode'] = node( + 'DecodeLatents', vae={'sourceId': 'load', 'sourceKey': 'vae_out'}, + latents={'sourceId': 'generate', 'sourceKey': 'latents'}, + width={'sourceId': 'generate', 'sourceKey': 'out_width'}, + ) + if change == 'different_latents': + g['nodes']['decode']['params']['latents'] = {'sourceId': 'load', 'sourceKey': 'latents'} + elif change == 'unknown_output': + g['nodes']['decode']['params']['width']['sourceKey'] = 'estimated_width' + else: + g['nodes']['generate']['params']['width']['value'] = 1.5 + g['paths'] = [list(g['nodes'])] + assert not plan(g)['canAutoRun'] + + +def test_split_decoder_stops_before_allocation_if_actual_geometry_differs(): + from modiff.server import WebServer + + app = object.__new__(WebServer) + app.modules = {'modules.ModularDiffusers': {'DecodeLatents': {}}} + app.node_cache = {'denoise': SimpleNamespace(output={'out_width': 2048})} + app._workflow_auto_resolved_fields = {'decode': {'width': 1024}} + with pytest.raises(RuntimeError, match='decode.width changed after resource planning'): + app.execute_node('decode', node('DecodeLatents', width={ + 'sourceId': 'denoise', 'sourceKey': 'out_width', + }), 'test') + assert 'decode' not in app.node_cache + + def test_nested_data_selection_and_conversion_resolve_without_executing_nodes(setup): plan, _, _, _ = setup g = graph() @@ -547,3 +605,27 @@ def prune(_): _best_effort_allocator_trim=lambda: (True, [])) with pytest.raises(ValueError, match='model references remain alive'): release_owner_caches(app, {'nodeIds': ['expired'], 'ownerIds': ['expired'], 'retainOutputs': {}}, SimpleNamespace(cache={})) + + +@pytest.mark.parametrize("shared, ram, vram, release", [ + (False, 1000, 1000, False), (False, 450, 1000, True), + (False, 1000, 300, True), (True, 1000, 1000, False), + (True, 800, 1000, True), +]) +def test_owner_retention_uses_the_combined_live_memory_envelope(setup, shared, ram, vram, release): + plan, _, hardware, _ = setup + hardware['systemMemory']['availableBytes'] = ram + hardware['accelerator'].update(freeBytes=vram, memoryKind='shared' if shared else 'dedicated') + result = plan(graph(two=True)) + assert result['canAutoRun'] + assert (result['schedule'] is not None) is release + assert result['retainedRequirements']['systemRamBytes'] == 600 + + +def test_owner_retention_normalizes_numeric_hardware_values(setup): + plan, _, hardware, _ = setup + hardware['systemMemory']['availableBytes'] = '1000' + hardware['accelerator']['freeBytes'] = '1000' + result = plan(graph(two=True)) + assert result['canAutoRun'] + assert result['schedule'] is None diff --git a/tests/test_workflow_store.py b/tests/test_workflow_store.py index f4f79db5..7a27c4cc 100644 --- a/tests/test_workflow_store.py +++ b/tests/test_workflow_store.py @@ -54,6 +54,33 @@ def test_workflow_id_cannot_escape_backend_storage(self): with self.assertRaisesRegex(ValueError, "Workflow id"): save_workflow(self.directory.name, "../escape", {"snapshot": {}}) + def test_explicit_save_promotes_draft_and_delayed_autosave_cannot_demote_it(self): + payload = {"snapshot": {"nodes": []}, "intent": "draft"} + draft = save_workflow(self.directory.name, "draft", payload) + self.assertEqual(draft["intent"], "draft") + saved = save_workflow(self.directory.name, "draft", {**payload, "intent": "saved"}) + self.assertEqual(saved["intent"], "saved") + late = save_workflow(self.directory.name, "draft", payload) + self.assertEqual(late["intent"], "saved") + self.assertEqual(list_workflow_summaries(self.directory.name)[0]["intent"], "saved") + + def test_legacy_intent_is_saved_without_rewriting_the_document(self): + path = Path(self.directory.name) / "user-workflows/legacy.json" + path.parent.mkdir() + original = json.dumps({"id": "legacy", "title": "Workflow 1", "snapshot": {}, "revision": 1}) + path.write_text(original) + for _ in range(2): + self.assertEqual(get_workflow(self.directory.name, "legacy")["intent"], "saved") + self.assertEqual(list_workflow_summaries(self.directory.name)[0]["intent"], "saved") + self.assertEqual(path.read_text(), original) + result = save_workflow(self.directory.name, "legacy", {"snapshot": {}, "intent": "draft"}) + self.assertEqual(result["intent"], "saved") + + def test_unknown_document_intent_is_rejected_before_writing(self): + with self.assertRaisesRegex(ValueError, "intent"): + save_workflow(self.directory.name, "bad", {"snapshot": {}, "intent": "discard"}) + self.assertIsNone(get_workflow(self.directory.name, "bad")) + def test_summary_listing_reuses_only_unchanged_metadata_and_detects_external_replacement(self): from modiff import workflow_store @@ -471,11 +498,15 @@ def prepare_for_workflow_reuse(self): raise AssertionError("An undeclared callback was dispatched.") cached_node = CachedModelsLoader() + metadata_node = CachedModelsLoader() server = WebServer(modules=definition, work_dir=self.directory.name, data_dir=self.directory.name) server.loop = asyncio.get_running_loop() server.node_cache["models-loader"] = cached_node with patch( + "modiff.server.metadata_field_callback", + return_value=metadata_node.refresh_pipeline_identity, + ) as create_metadata, patch( "modiff.server.field_action_optional_runtime_requirement", return_value={ "schemaVersion": 1, @@ -510,9 +541,14 @@ def prepare_for_workflow_reuse(self): self.assertEqual(response.status, 200) self.assertFalse(payload["error"]) self.assertEqual(payload["ref"], {"node": "models-loader", "key": "repo_id", "queue": False}) - self.assertEqual(cached_node._sid, "field-session") + self.assertIsNone(cached_node._sid) + self.assertEqual(cached_node.calls, []) + create_metadata.assert_called_once_with( + action_name, "refresh_pipeline_identity", node_id="models-loader", sid="field-session", + module=module_name, + ) self.assertEqual( - cached_node.calls, + metadata_node.calls, [ ( { @@ -572,7 +608,9 @@ def refresh_pipeline_identity(self, _values, _ref): "state": "active", "reason": "optional_runtime_active", } - with patch("modiff.server.field_action_optional_runtime_requirement", return_value=active_requirement): + with patch("modiff.server.field_action_optional_runtime_requirement", return_value=active_requirement), patch( + "modiff.server.metadata_field_callback", return_value=CachedModelsLoader().refresh_pipeline_identity, + ): response = await server.field_action(request) payload = json.loads(response.text) diff --git a/tests/test_workflow_task_identity.py b/tests/test_workflow_task_identity.py new file mode 100644 index 00000000..1d11796b --- /dev/null +++ b/tests/test_workflow_task_identity.py @@ -0,0 +1,374 @@ +from copy import deepcopy + +import pytest + +from modiff.workflow_auto_resource import build_workflow_auto_plan + + +def modular_graph(pipeline, mode): + from modiff.modular_action_bindings import MODULAR_ACTION_BINDINGS + from modiff.modular_workflow_contracts import PINNED_MODULAR_WORKFLOW_TRUTH + + route = PINNED_MODULAR_WORKFLOW_TRUTH[pipeline].mode(mode) + nodes = { + "loader": { + "module": "modules.ModularDiffusers", + "action": "ModelsLoader", + "params": { + "model_type": {"value": pipeline}, + "repo_id": {"value": "test/model"}, + "device": {"value": "cpu"}, + "dtype": {"value": "float32"}, + }, + } + } + for action in route.action_sequence: + module, name = MODULAR_ACTION_BINDINGS[action][1].rsplit(".", 1) + nodes[action] = { + "module": module, + "action": name, + "params": { + "pipeline_components": {"sourceId": "loader", "sourceKey": "pipeline_components"}, + }, + } + for edge in route.state_edges: + nodes[edge.consumer_action]["params"][edge.consumer_input] = { + "sourceId": edge.producer_action, + "sourceKey": edge.producer_output, + } + # Only the task's actual external input set is supplied. + for name in route.required_upstream_inputs: + nodes[route.action_sequence[0]]["params"][name] = {"value": "test-input"} + return {"nodes": nodes, "paths": [list(nodes)]} + + +def test_auto_resolves_sdxl_img2img_from_the_executable_graph(monkeypatch, tmp_path): + from types import SimpleNamespace + from modiff import workflow_auto_resource as planner + + profile = SimpleNamespace( + model_type="StableDiffusionXLModularPipeline", + modes=("text_to_image", "image_to_image"), + loader_module="modules.ModularDiffusers", + loader_action="ModelsLoader", + execution_path="modular-diffusers", + ) + monkeypatch.setattr( + planner, + "resolve_execution_profiles_for_loader", + lambda module, action, values: ((profile,), None) if action == "ModelsLoader" else ((), None), + ) + requests = [] + + def recipe(payload, **kwargs): + requests.append(payload) + return {"candidates": []} + + graph = modular_graph(profile.model_type, "image_to_image") + before = deepcopy(graph) + result = build_workflow_auto_plan( + graph, + runtime_fingerprint={}, + local_models=[], + data_dir=str(tmp_path), + plan_recipe=recipe, + hardware={"systemMemory": {}, "accelerator": {}, "offloadDisk": {}}, + ) + assert requests and requests[0]["form"]["mode"] == "image_to_image" + assert not result["canAutoRun"], "Resolving a task does not grant an unqualified resource recipe." + assert graph == before + + +@pytest.mark.parametrize( + "pipeline,task", + [ + ("StableDiffusionXLModularPipeline", "text_to_image"), + ("StableDiffusionXLModularPipeline", "image_to_image"), + ("StableDiffusionXLModularPipeline", "inpaint"), + ("FluxModularPipeline", "text_to_image"), + ("FluxModularPipeline", "image_to_image"), + ("QwenImageModularPipeline", "text_to_image"), + ], +) +def test_reviewed_tasks_are_identified_without_authoring_hints(pipeline, task): + from modiff.workflow_task_identity import modular_graph_tasks + + graph = modular_graph(pipeline, task) + assert modular_graph_tasks(graph["nodes"], "loader", list(graph["nodes"])[1:], pipeline) == {task} + + +def test_missing_or_wrong_state_wire_and_duplicate_consumers_do_not_guess(): + from modiff.workflow_task_identity import modular_graph_tasks + + pipeline = "StableDiffusionXLModularPipeline" + graph = modular_graph(pipeline, "image_to_image")["nodes"] + + def tasks(nodes): + return modular_graph_tasks(nodes, "loader", list(nodes)[1:], pipeline) + + assert tasks(graph) == {"image_to_image"} + broken = deepcopy(graph) + edge = next(p for p in broken["denoise"]["params"].values() if p.get("sourceId") == "vae_encoder") + edge["sourceKey"] = "unrelated" + assert tasks(broken) == set() + duplicate = deepcopy(graph) + duplicate["other_denoise"] = deepcopy(graph["denoise"]) + assert tasks(duplicate) == set() + assert modular_graph_tasks(graph, "loader", ["denoise"], pipeline) == set() + assert modular_graph_tasks(graph, "loader", list(graph)[1:], "UnknownPipeline") == set() + + +def test_guidance_helpers_belong_to_the_connected_owner_without_crossing_media(): + from modiff.workflow_task_identity import resource_consumers + + nodes = modular_graph("StableDiffusionXLModularPipeline", "image_to_image")["nodes"] + nodes["layers"] = {"module": "modules.ModularDiffusers", "action": "Layers", "params": {}} + nodes["guide"] = {"module": "modules.ModularDiffusers", "action": "Guider", "params": { + "layers_config": {"sourceId": "layers", "sourceKey": "layers_config"}, + }} + nodes["denoise"]["params"]["guider"] = {"sourceId": "guide", "sourceKey": "guider_out"} + nodes["other"] = {"module": "modules.ModularDiffusers", "action": "ModelsLoader", "params": {}} + nodes["image"] = {"module": "modules.ModularDiffusers", "action": "DecodeLatents", "params": { + "vae": {"sourceId": "other", "sourceKey": "vae_out"}, + }} + nodes["denoise"]["params"]["image"] = {"sourceId": "image", "sourceKey": "image"} + found = resource_consumers(nodes, "loader", {"loader", "other"}) + assert {"guide", "layers"}.issubset(found) + assert not {"image", "other"}.intersection(found) + nodes["guide"]["module"] = "custom.Arbitrary" + assert "guide" not in resource_consumers(nodes, "loader", {"loader", "other"}) + + +def test_actual_operation_starter_edges_resolve_the_selected_image_task(): + from modules import MODULE_MAP + from modiff.operation_catalog import build_operation_catalog + from modiff.operation_starters import resolve_operation_starter + from modiff.workflow_task_identity import modular_graph_tasks + + contracts, _ = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {}) + for pipeline, task in [ + ("StableDiffusionXLModularPipeline", "text_to_image"), + ("StableDiffusionXLModularPipeline", "image_to_image"), + ("FluxModularPipeline", "text_to_image"), + ("FluxModularPipeline", "image_to_image"), + ("QwenImageModularPipeline", "text_to_image"), + ]: + starter = resolve_operation_starter(MODULE_MAP, contracts, {"pipelineClass": pipeline, "task": task}) + nodes = { + n["operation"]["operationId"]: { + "module": n["module"], + "action": n["action"], + "params": { + k: {"value": p.get("value", p.get("default"))} + for k, p in n["params"].items() + if p.get("display") != "output" + }, + } + for n in starter["nodes"] + } + for e in starter["edges"]: + nodes[e["target"]]["params"][e["targetHandle"]] = {"sourceId": e["source"], "sourceKey": e["sourceHandle"]} + loader = starter["nodes"][0]["operation"]["operationId"] + assert modular_graph_tasks(nodes, loader, list(nodes)[1:], pipeline) == {task}, (pipeline, task) + + +def test_output_history_uses_recognized_graph_task_without_rewriting_form(): + from modiff.execution_input_provenance import ( + capture_generation_inputs, + build_resolved_execution_inputs, + apply_resolved_execution_inputs, + ) + + graph = modular_graph("StableDiffusionXLModularPipeline", "image_to_image") + records = { + key: capture_generation_inputs( + key, node, {field: p.get("value") for field, p in node["params"].items() if "value" in p} + ) + for key, node in graph["nodes"].items() + } + receipt = build_resolved_execution_inputs(graph, records, task_id="run", attempt_index=0, node_id="decoder") + output = {"taskId": "run", "nodeId": "decoder", "mode": "text_to_image", "formSnapshot": {"mode": "text_to_image"}} + result = apply_resolved_execution_inputs(output, receipt) + assert result["mode"] == "image_to_image" + assert result["formSnapshot"]["mode"] == "text_to_image" + assert receipt["graphTasks"] == [ + {"loaderId": "loader", "pipelineClass": "StableDiffusionXLModularPipeline", "task": "image_to_image"} + ] + + +def test_conflicting_explicit_modes_remain_unresolved_even_for_a_single_mode_profile(monkeypatch, tmp_path): + from types import SimpleNamespace + from modiff import workflow_auto_resource as planner + + profile = SimpleNamespace( + model_type="StableDiffusionXLModularPipeline", + modes=("text_to_image",), + loader_module="modules.ModularDiffusers", + loader_action="ModelsLoader", + execution_path="modular-diffusers", + ) + monkeypatch.setattr( + planner, + "resolve_execution_profiles_for_loader", + lambda module, action, values: ((profile,), None) if action == "ModelsLoader" else ((), None), + ) + graph = modular_graph(profile.model_type, "text_to_image") + graph["nodes"]["loader"]["params"]["mode"] = {"value": "text_to_image"} + graph["nodes"]["denoise"]["params"]["mode"] = {"value": "image_to_image"} + + def recipe(*args, **kwargs): + raise AssertionError("Conflicting tasks must not reach recipe selection") + + result = planner.build_workflow_auto_plan( + graph, + runtime_fingerprint={}, + local_models=[], + data_dir=str(tmp_path), + plan_recipe=recipe, + hardware={"systemMemory": {}, "accelerator": {}, "offloadDisk": {}}, + ) + assert not result["canAutoRun"] and any("ambiguous" in issue for issue in result["issues"]) + + +def test_ambiguous_declared_tasks_missing_owner_capture_and_unrelated_nodes_stay_explicit(monkeypatch): + from modiff import workflow_task_identity as identity + + pipeline = "StableDiffusionXLModularPipeline" + nodes = modular_graph(pipeline, "text_to_image")["nodes"] + adapters = identity.modular_task_adapters + + def ambiguous(pipeline_class, workflow): + result = adapters(pipeline_class, workflow) + return result + [("other_task", adapter) for task, adapter in result if task == "text_to_image"] + + monkeypatch.setattr(identity, "modular_task_adapters", ambiguous) + assert identity.modular_graph_tasks(nodes, "loader", list(nodes)[1:], pipeline) == {"text_to_image", "other_task"} + records = {"loader": {"fields": {"model_type": {"value": pipeline}}}} + assert identity.graph_task_receipts(nodes, records)[0]["task"] is None + assert identity.graph_task_receipts(nodes, {}) == [] + nodes["unrelated"] = {"module": "custom.Developer", "action": "Process", "params": {}} + assert identity.resource_consumers(nodes, "loader", {"loader"}) == ["decoder", "denoise", "text_encoder"] + + +def test_receipts_use_only_the_output_ancestry_and_leave_incomplete_graphs_unlabeled(): + from modiff.execution_input_provenance import capture_generation_inputs, build_resolved_execution_inputs + + graph = modular_graph("StableDiffusionXLModularPipeline", "image_to_image") + graph["nodes"]["other_loader"] = deepcopy(graph["nodes"]["loader"]) + records = { + key: capture_generation_inputs( + key, node, {field: p.get("value") for field, p in node["params"].items() if "value" in p} + ) + for key, node in graph["nodes"].items() + } + receipt = build_resolved_execution_inputs(graph, records, task_id="run", attempt_index=0, node_id="decoder") + assert [row["loaderId"] for row in receipt["graphTasks"]] == ["loader"] + del records["vae_encoder"] + receipt = build_resolved_execution_inputs(graph, records, task_id="run", attempt_index=0, node_id="decoder") + assert "graphTasks" not in receipt + + +@pytest.mark.parametrize( + "pipeline,repository,task,resource_mode", + [ + ( + "StableDiffusionXLModularPipeline", + "stabilityai/stable-diffusion-xl-base-1.0", + "image_to_image", + "edit_image", + ), + ( + "StableDiffusionXLModularPipeline", + "stabilityai/stable-diffusion-xl-base-1.0", + "text_to_image", + "text_to_image", + ), + ("FluxModularPipeline", "black-forest-labs/FLUX.1-dev", "image_to_image", "image_to_image"), + ("QwenImageModularPipeline", "Qwen/Qwen-Image-2512", "text_to_image", "modular_text_to_image"), + ], +) +def test_actual_profiles_use_their_existing_resource_mode_alias(tmp_path, pipeline, repository, task, resource_mode): + graph = modular_graph(pipeline, task) + graph["nodes"]["loader"]["params"]["repo_id"] = {"value": {"source": "hub", "value": repository}} + requests = [] + + def recipe(payload, **kwargs): + requests.append(payload) + return {"candidates": []} + + result = build_workflow_auto_plan( + graph, + runtime_fingerprint={}, + local_models=[], + data_dir=str(tmp_path), + plan_recipe=recipe, + hardware={"systemMemory": {}, "accelerator": {}, "offloadDisk": {}}, + ) + assert requests and requests[0]["form"]["mode"] == resource_mode + assert not result["canAutoRun"], "The existing resource profile still needs a valid recipe." + + +def test_every_published_modular_starter_recognizes_its_task_without_claiming_unique_aliases(): + from modules import MODULE_MAP + from modiff.operation_catalog import build_operation_catalog + from modiff.operation_starters import resolve_operation_starter + from modiff.workflow_task_identity import modular_graph_tasks, resource_consumers + + contracts, _ = build_operation_catalog(MODULE_MAP, [], catalog_resolver=lambda: {}) + selections = sorted({(c['pipelineClass'], c['task']) for c in contracts + if c['task'] and c['nodeKey'].startswith('modules.ModularDiffusers.')}) + failures = [] + for pipeline, task in selections: + starter = resolve_operation_starter(MODULE_MAP, contracts, {'pipelineClass': pipeline, 'task': task}) + nodes = { + n['operation']['operationId']: { + 'module': n['module'], 'action': n['action'], + 'params': {k: {'value': p.get('value', p.get('default'))} + for k, p in n['params'].items() if p.get('display') != 'output'}, + } for n in starter['nodes'] + } + for edge in starter['edges']: + nodes[edge['target']]['params'][edge['targetHandle']] = { + 'sourceId': edge['source'], 'sourceKey': edge['sourceHandle'], + } + for required in starter['requiredInputs']: + nodes[required['operationId']]['params'][required['field']] = { + 'sourceId': 'external', 'sourceKey': required['field'], + } + nodes['external'] = {'module': 'custom.Source', 'action': 'Values', 'params': {}} + loader = starter['nodes'][0]['operation']['operationId'] + before = deepcopy(nodes) + actual = modular_graph_tasks(nodes, loader, resource_consumers(nodes, loader, {loader}), pipeline) + if task not in actual: + failures.append((pipeline, task, actual)) + assert nodes == before + assert not failures, repr(failures) + + +def test_explicit_upstream_composition_uses_validated_placements_and_rejects_partial_or_forged_scope(): + from modiff.huggingface_node_library import reviewed_huggingface_node_library + from modiff.workflow_task_identity import modular_graph_tasks + + library = reviewed_huggingface_node_library() + definition = next(d for d in library['definitions'] if d.get('pipelineClass') == 'QwenImageModularPipeline' + and d.get('workflowId') == 'text2image') + blocks = {b['id']: b for b in library['blockDefinitions']} + nodes = {'loader': {'module': 'modules.ModularDiffusers', 'action': 'ModelsLoader', 'params': {}}} + for i, placement in enumerate(definition['blockPlacements']): + block = blocks[placement['blockDefinitionId']] + if block['kind'] not in ('block', 'loop'): + continue + values = {'pipeline_class': 'QwenImageModularPipeline', 'workflow_id': 'text2image', + 'placement_path': placement['path'], 'block_definition_id': block['id'], + 'block_class': block['className'], 'block_contract_hash': block['contentHash']} + nodes[f'step-{i}'] = {'module': 'modules.ModularDiffusers', 'action': 'ReviewedModularWorkflowStep', + 'params': {k: {'value': v} for k, v in values.items()}} + before = deepcopy(nodes) + assert modular_graph_tasks(nodes, 'loader', list(nodes)[1:], 'QwenImageModularPipeline') == {'text_to_image'} + assert nodes == before + partial = [k for k, n in nodes.items() if k != 'loader' and 'Text' in n['params']['block_class']['value']] + assert partial + assert modular_graph_tasks(nodes, 'loader', partial, 'QwenImageModularPipeline') == set() + first = next(k for k in nodes if k != 'loader') + nodes[first]['params']['block_contract_hash']['value'] = 'sha256:' + '0' * 64 + assert modular_graph_tasks(nodes, 'loader', list(nodes)[1:], 'QwenImageModularPipeline') == set() diff --git a/utils/huggingface.py b/utils/huggingface.py index c61fdaf0..d7db7db4 100644 --- a/utils/huggingface.py +++ b/utils/huggingface.py @@ -231,6 +231,10 @@ def _hf_cache_locations(): candidates.extend(_explicit_hf_cache_candidates()) if default: candidates.append(('Default Hugging Face cache', default)) + # main.py directs new Hub writes to the configured cache before importing + # huggingface_hub. Its constant may therefore no longer name the user's + # original cache, which inventory and exact execution must still discover. + candidates.append(('User default Hugging Face cache', str(Path.home() / '.cache' / 'huggingface' / 'hub'))) for label, path in _common_appdata_hf_cache_candidates(): if Path(path).exists(): @@ -1748,9 +1752,6 @@ def get_local_model_ids(id: Optional[str] = None, class_name: Optional[str] | bo return local_models def cached_file_path(repo_id: str, file: str | None = None, *, revision: str | None = None): - cache_dir = CONFIG.hf['cache_dir'] - file_path = None - if file is None: path = repo_id.split('/') if len(path) < 3: @@ -1758,23 +1759,34 @@ def cached_file_path(repo_id: str, file: str | None = None, *, revision: str | N file = '/'.join(path[2:]) repo_id = '/'.join(path[:2]) - try: - file_path = try_to_load_from_cache( - repo_id=repo_id, - filename=file, - cache_dir=cache_dir, - revision=revision, - ) - except Exception as e: - logger.error(f'Error checking cache for {repo_id}/{file}: {e}') - return None - - if isinstance(file_path, str): - return file_path + for _label, cache_dir in _hf_cache_locations(): + try: + file_path = try_to_load_from_cache( + repo_id=repo_id, filename=file, cache_dir=cache_dir, revision=revision, + ) + except Exception as e: + logger.error(f'Error checking cache for {repo_id}/{file}: {e}') + continue + if isinstance(file_path, str): + return file_path return False +def _containing_hf_cache_root(path: Path) -> Path: + """Use the inventory's roots, retaining the alias's own containment boundary.""" + alias = path.expanduser().absolute() + for _label, location in _hf_cache_locations(): + root = Path(location).expanduser().absolute() + resolved_root = root.resolve(strict=False) + # Callers may already have canonicalized the cache root (Windows short + # names, or a linked root). Keep the file alias unresolved here so an + # escaping snapshot link cannot select a different authorized root. + if alias.is_relative_to(root) or alias.is_relative_to(resolved_root): + return resolved_root + raise ValueError('Installed Hugging Face cache entry is outside the managed cache roots.') + + def resolve_managed_hf_cache_file(path: str | os.PathLike[str]) -> Path: """Resolve one cached Hub file without allowing a cache-root escape. @@ -1788,7 +1800,7 @@ def resolve_managed_hf_cache_file(path: str | os.PathLike[str]) -> Path: resolved = Path(path).expanduser().resolve(strict=True) except (OSError, RuntimeError) as error: raise FileNotFoundError(f'Installed Hugging Face cache entry does not exist: {path}') from error - cache_root = Path(CONFIG.hf['cache_dir'] or str(HUGGINGFACE_HUB_CACHE)).expanduser().resolve(strict=False) + cache_root = _containing_hf_cache_root(Path(path)) try: resolved.relative_to(cache_root) except (OSError, RuntimeError, ValueError) as error: @@ -1846,7 +1858,7 @@ def exact_cached_snapshot_path(repo_id: str, revision: str, marker_file: str = ' resolved_marker = resolve_managed_hf_cache_file(alias) try: snapshot.resolve(strict=True).relative_to( - Path(CONFIG.hf['cache_dir'] or str(HUGGINGFACE_HUB_CACHE)).expanduser().resolve(strict=False) + _containing_hf_cache_root(snapshot) ) resolved_marker.relative_to(repository_cache.resolve(strict=True)) except (OSError, RuntimeError, ValueError) as error: diff --git a/utils/memory_menager.py b/utils/memory_menager.py index e1631d96..dbc399ce 100644 --- a/utils/memory_menager.py +++ b/utils/memory_menager.py @@ -6,6 +6,7 @@ import time import nanoid import os +from collections import Counter from utils.torch_utils import DEFAULT_DEVICE GIB = 1024 ** 3 @@ -99,6 +100,7 @@ def memory_flush(): class MemoryManager: def __init__(self): self.cache = {} + self._active_ids = Counter() self.policy = str(os.environ.get('MODIFF_MODEL_CACHE_POLICY') or 'lru').strip().lower() if self.policy not in {'lru', 'no_cache', 'high_ram'}: self.policy = 'lru' @@ -126,14 +128,10 @@ def remove(self, model): if model_id is None or model_id not in self.cache: return None - try: - self.cache[model_id]['model'] = self.cache[model_id]['model'].to('cpu') - except Exception: - # should prevent errors with quantized models - pass - - self.cache[model_id]['model'] = None - del self.cache[model_id] + # Removing ownership is not CPU offload. Materializing a discarded + # offloaded pipeline here can itself OOM a unified-memory machine. + record = self.cache.pop(model_id) + record['model'] = None memory_flush() return model_id @@ -204,10 +202,7 @@ def load_model(self, model, device, exclude=None): #memory_current = torch.cuda.mem_get_info()[0] if 'cuda' in device else 0 x = x.to(device) - #self.cache[model_id]['model'] = x - #self.cache[model_id]['device'] = device - #if self.cache[model_id]['size'] == 0 and 'cuda' in device: - # self.cache[model_id]['size'] = memory_current - torch.cuda.mem_get_info()[0] + self.cache[model_id]['model'] = x return x except torch.OutOfMemoryError as e: if not cache_priority: @@ -262,46 +257,43 @@ def exec(self, func, device, models=None, exclude=None, args=None, kwargs=None, if k and k in self.cache: exclude_ids.append(k) - # auto load the models, add them to the exclude list + # Reserve the complete working set BEFORE loading the first component. + # Otherwise loading B can evict active A, or an add() inside func can + # discard a CPU component which this same call still needs. active_models = list(models or []) for v in active_models: k = v if isinstance(v, str) else v._mm_id if hasattr(v, '_mm_id') else None if k and k in self.cache: exclude_ids.append(k) - self.load_model(v, device) - - # Get a list of all models on the target device that can be unloaded. - cache_priority = self._get_unload_candidates(device, exclude_ids=exclude_ids) + leased_ids = set(exclude_ids) + self._active_ids.update(leased_ids) args = args or [] kwargs = kwargs or {} try: - while True: - try: - if inference_mode: - with torch.inference_mode(): - return func(*args, **kwargs) - else: - return func(*args, **kwargs) - except torch.cuda.OutOfMemoryError as e: - # If we're out of memory, we need to unload a model. - if not cache_priority: - # If there are no more models to unload, we have failed. - logger.error("OOM during exec. No models left to unload to free memory.") - raise e - - # Unload the lowest-priority model. - k = cache_priority.pop(0)[2] - logger.debug(f"OOM during exec. Unloading model '{k}' to free VRAM.") - self.unload_model(k) - except Exception as e: - logger.error(f"An unexpected error occurred during exec: {e}") - raise e + for model in active_models: + self.load_model(model, device, exclude=exclude_ids) + # An opaque callback may already have consumed a generator or + # mutated pipeline state before raising OOM. Only the graph's + # bounded retry policy can discard that attempt and rebuild it + # from declared inputs/seeds. Never silently replay it here. + if inference_mode: + with torch.inference_mode(): + return func(*args, **kwargs) + return func(*args, **kwargs) finally: + self._active_ids.subtract(leased_ids) + self._active_ids += Counter() # discard zero-count nested leases if self.policy == 'no_cache': for active in active_models: - self.unload_model(active) + key = active if isinstance(active, str) else getattr(active, '_mm_id', None) + if self._active_ids.get(key, 0): + continue + try: + self.unload_model(active) + except Exception: + logger.warning("Could not offload a no-cache model after execution", exc_info=True) self._evict_system_ram_pressure(exclude_ids=exclude_ids) def _get_unload_candidates(self, device, exclude_ids=None): @@ -313,7 +305,8 @@ def _get_unload_candidates(self, device, exclude_ids=None): cache_priority = [] for k, v in self.cache.items(): # Check if the model is on the target device and not in the exclude list - if _model_device(v['model']) == str(device) and k not in (exclude_ids or []): + if (_model_device(v['model']) == str(device) and k not in (exclude_ids or []) + and not self._active_ids.get(k, 0)): cache_priority.append((v['priority'], v['last_used'], k)) # Sort by priority then last_used to find the best unload candidate @@ -329,12 +322,12 @@ def _evict_system_ram_pressure(self, exclude_ids=None): memory = system_memory_snapshot() available = memory.get('available_bytes') total = memory.get('total_bytes') - floor = max(4 * GIB, int(total * 0.1)) if isinstance(total, int) else 4 * GIB - if not isinstance(available, int) or available >= floor: + floor = max(4 * GIB, int(total * 0.1)) if type(total) is int and total > 0 else 4 * GIB + if type(available) is not int or available < 0 or available >= floor: return [] except Exception: return [] - excluded = set(exclude_ids or []) + excluded = set(exclude_ids or []) | set(self._active_ids) candidates = sorted( ( (record['priority'], record['last_used'], model_id) @@ -348,8 +341,15 @@ def _evict_system_ram_pressure(self, exclude_ids=None): model_id = candidates.pop(0)[2] self.remove(model_id) evicted.append(model_id) - memory = system_memory_snapshot() - available = int(memory.get('available_bytes') or 0) + logger.info("Released inactive CPU model %s under system RAM pressure", model_id) + try: + memory = system_memory_snapshot() + available = memory.get('available_bytes') + if type(available) is not int or available < 0: + break + except Exception: + logger.warning("Memory pressure observation failed after eviction; stopping eviction", exc_info=True) + break return evicted memory_manager = MemoryManager() diff --git a/web/assets/AlertDialog.js b/web/assets/AlertDialog.js index 6337a657..36433700 100644 --- a/web/assets/AlertDialog.js +++ b/web/assets/AlertDialog.js @@ -1 +1 @@ -import{ur as e,z as n}from"./graph-vendor.js?v=5bcd315505f5670a";import{Ut as s,Wt as a}from"./studio-templates.js?v=5bcd315505f5670a";var r=e();function t({opener:e,onClose:t}){return e?(0,r.jsx)(a,{open:!0,onClose:t,title:(0,r.jsxs)("span",{className:"flex items-center gap-2",children:[(0,r.jsx)(n,{size:20,className:"shrink-0 text-modiff-warning","aria-hidden":"true"}),(0,r.jsx)("span",{children:e.title||"Confirm action"})]}),panelClassName:"max-w-lg",footer:(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s,{onClick:()=>{e?.onCancel?.(),t()},children:e.cancelText||"Cancel"}),(0,r.jsx)(s,{onClick:()=>{e?.onConfirm(),t()},tone:"primary",children:e.confirmText||"Confirm"})]}),children:(0,r.jsx)("p",{className:"text-sm leading-6 text-modiff-text",children:e.message})}):null}export{t as default}; \ No newline at end of file +import{R as e,cr as n}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{in as s,rn as a}from"./studio-templates.js?v=cc046c26d6fe7aa4";var r=n();function i({opener:n,onClose:i}){return n?(0,r.jsx)(s,{open:!0,onClose:i,title:(0,r.jsxs)("span",{className:"flex items-center gap-2",children:[(0,r.jsx)(e,{size:20,className:"shrink-0 text-modiff-warning","aria-hidden":"true"}),(0,r.jsx)("span",{children:n.title||"Confirm action"})]}),panelClassName:"max-w-lg",footer:(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(a,{onClick:()=>{n?.onCancel?.(),i()},children:n.cancelText||"Cancel"}),(0,r.jsx)(a,{onClick:()=>{n?.onConfirm(),i()},tone:"primary",children:n.confirmText||"Confirm"})]}),children:(0,r.jsx)("p",{className:"text-sm leading-6 text-modiff-text",children:n.message})}):null}export{i as default}; \ No newline at end of file diff --git a/web/assets/BlockInterfaceDialogContentV2.js b/web/assets/BlockInterfaceDialogContentV2.js index c42dd80d..660dbacf 100644 --- a/web/assets/BlockInterfaceDialogContentV2.js +++ b/web/assets/BlockInterfaceDialogContentV2.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{An as t,B as r,Nn as n,ct as o,pr as i,ur as a}from"./graph-vendor.js?v=5bcd315505f5670a";import{Co as s,Gt as l,Oo as d,Xt as c,bo as u,do as p,yo as f}from"./studio-templates.js?v=5bcd315505f5670a";import{u as m}from"./block-composition-tools.js?v=5bcd315505f5670a";var h=e(i(),1),b=a();function v({value:e,options:t,onValueChange:r,placeholder:n,disabled:o,...i}){let[a,s]=(0,h.useState)(null),l=t.find(t=>t.value===e)?.label??"";return(0,b.jsxs)("div",{className:"min-w-0",title:"string"==typeof l?l:void 0,children:[(0,b.jsx)(c,{openOnFocus:!0,wrapOptions:!0,id:i.id,invalid:i.invalid,readOnly:i.readOnly,required:i.required,size:i.size,"aria-describedby":i["aria-describedby"],"aria-label":i["aria-label"]??("string"==typeof n?n:"Search internal fields"),"data-testid":i["data-testid"],disabled:o,value:e||null,query:a??("string"==typeof l?l:""),onQueryChange:s,onFocus:e=>e.currentTarget.select(),onBlur:()=>s(null),onValueChange:e=>{"string"==typeof e&&r(e),s(null)},options:t.map(e=>({...e,label:String(e.label)})),placeholder:"string"==typeof n?n:"Search internal fields",emptyMessage:"No matching internal fields"}),"string"==typeof l&&l?(0,b.jsx)("p",{className:"mt-1 break-words text-xs text-modiff-subtle-text",children:l}):null]})}function g(e,t){if(!t)return new Set(e.effectiveGraph.nodes.map(({nodeId:e})=>e));if(!e.effectiveGraph.nodes.some(e=>e.nodeId===t))throw Error("This internal Block no longer exists. Reopen Configure Interface.");let r=p(e),n=new Set([t]);for(let o of e.effectiveGraph.nodes){let e=r.get(o.nodeId);for(;e;)e===t&&n.add(o.nodeId),e=r.get(e)}return n}function y(e,t){return[e.binding,...e.mirrorBindings??[]].every(({nodeId:e})=>t.has(e))}function x(e,t){if(t){let{boundary:r,controls:n,previews:o}=u(e,t);return structuredClone({boundary:r,controls:n,previews:o??[]})}let r=g(e,t);return structuredClone({boundary:{mode:"explicit",inputs:e.effectiveInterface.boundary.inputs.filter(e=>y(e,r)),outputs:e.effectiveInterface.boundary.outputs.filter(e=>y(e,r))},controls:e.effectiveInterface.controls.filter(e=>y(e,r))})}function w(e,t,r,n){for(let r of["instanceId","definitionRef","effectiveGraph","effectiveInterface","values"])if(JSON.stringify(e[r])!==JSON.stringify(t[r]))throw Error("This Block changed while Configure Interface was open. Reopen it before applying.");let o=g(t,n);if(n){let e=r.previews??u(t,n).previews,{boundary:o,controls:i,previews:a}=d({schemaVersion:1,boundary:r.boundary,controls:r.controls.map(e=>{let t=structuredClone(e);return delete t.defaultValue,t}),...void 0===e?{}:{previews:e}},t.effectiveGraph,n);return{boundary:o,controls:i,...void 0===a?{}:{previews:a}}}if(void 0!==r.previews)throw Error("Root preview bindings belong to the Block definition, not its effective interface.");let i=(e,t,r)=>{let n=new Set(e.filter(e=>!y(e,o)).map(r));if(t.some(e=>!y(e,o)||n.has(r(e))))throw Error("This interface change would affect another branch. Use the owning Block interface editor.");if(new Set(t.map(r)).size!==t.length)throw Error("Interface entries must have unique IDs.");let i=0;return[...e.flatMap(e=>y(e,o)?ie.portId),outputs:i(t.effectiveInterface.boundary.outputs,r.boundary.outputs,e=>e.portId)},controls:i(t.effectiveInterface.controls,r.controls,e=>e.controlId).map((e,t)=>({...e,order:t}))}}function I(e,t,r){return f(e,t,r)}function j(e){return`${e.nodeId}\0${e.outputPortId}\0${e.mediaType}`}function k(e,t){let r=s(e.effectiveGraph,t);return e.effectiveGraph.nodes.flatMap(n=>{if(!r.has(n.nodeId))return[];let o=n.data.params;return!o||"object"!=typeof o||Array.isArray(o)?[]:Object.entries(o).flatMap(([r,o])=>!o||"object"!=typeof o||Array.isArray(o)||"output"!==o.display&&!String(o.display??"").startsWith("ui_")?[]:["image","video","audio","text","file"].flatMap(i=>{let a={nodeId:n.nodeId,outputPortId:r,mediaType:i};try{d({schemaVersion:1,boundary:{mode:"explicit",inputs:[],outputs:[]},controls:[],previews:[a]},e.effectiveGraph,t)}catch{return[]}return[{key:j(a),label:`${String(n.data.label||n.nodeId)} / ${String(o.label||r)} (${i})`,binding:a}]}))})}function C({snapshot:e,subtreeId:i,previews:a,onChange:s}){let d=(0,h.useMemo)(()=>k(e,i),[e,i]),[c,u]=(0,h.useState)(""),p=d.filter(({binding:e})=>!a.some(t=>t.nodeId===e.nodeId&&t.outputPortId===e.outputPortId)),f=new Map(d.map(({key:e,label:t})=>[e,t])),m=e=>f.get(j(e))??`${e.nodeId} / ${e.outputPortId} (${e.mediaType})`,g=(e,t)=>{let r=e+t;if(r<0||r>=a.length)return;let n=[...a];[n[e],n[r]]=[n[r],n[e]],s(n)},y=a.find(e=>e.primary);return(0,b.jsxs)("section",{className:"grid gap-2","data-testid":"block-interface-previews",children:[(0,b.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Collapsed previews"}),(0,b.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Choose outputs from this Block's own nodes. This changes its preview surface only, not connections, generation settings, or the root Block's previews. Existing generated files are not deleted."}),a.map((e,o)=>(0,b.jsxs)("div",{className:"flex min-w-0 items-center gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:[(0,b.jsx)("span",{className:"min-w-0 flex-1 break-words text-sm text-modiff-text",children:m(e)}),(0,b.jsx)(l,{label:`Move preview ${m(e)} up`,size:"compact",disabled:0===o,onClick:()=>g(o,-1),children:(0,b.jsx)(t,{size:14})}),(0,b.jsx)(l,{label:`Move preview ${m(e)} down`,size:"compact",disabled:o===a.length-1,onClick:()=>g(o,1),children:(0,b.jsx)(n,{size:14})}),(0,b.jsx)(l,{label:`Remove preview ${m(e)}`,size:"compact",onClick:()=>s(a.filter((e,t)=>t!==o)),children:(0,b.jsx)(r,{size:14})})]},j(e))),a.length?null:(0,b.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No previews selected for this Block."}),(0,b.jsx)(v,{"aria-label":"Primary collapsed preview",value:y?j(y):"none",options:[{value:"none",label:"No primary preview"},...a.map(e=>({value:j(e),label:m(e)}))],onValueChange:e=>s(a.map(t=>{let r={...t};return delete r.primary,j(r)===e?{...r,primary:!0}:r}))}),(0,b.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2",children:[(0,b.jsx)(v,{"aria-label":"Internal preview source",value:c,onValueChange:u,options:p.map(({key:e,label:t})=>({value:e,label:t})),placeholder:"Choose an internal media or text output",disabled:!p.length}),(0,b.jsx)(l,{label:"Add collapsed preview",size:"compact",disabled:!p.some(({key:e})=>e===c),onClick:()=>{let e=p.find(({key:e})=>e===c);e&&(s([...a,{...e.binding,...a.length?{}:{primary:!0}}]),u(""))},children:(0,b.jsx)(o,{size:14})})]}),d.length?null:(0,b.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No compatible media or text outputs are declared inside this Block."})]})}export{w as a,m as default,g as i,x as n,v as o,I as r,C as t}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{En as t,cr as r,dr as n,kn as i,ot as o,z as a}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$o as s,Hs as l,Ls as d,an as c,bs as u,un as p,ys as f}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{u as m}from"./block-composition-tools.js?v=cc046c26d6fe7aa4";var h=e(n(),1),b=r();function v({value:e,options:t,onValueChange:r,placeholder:n,disabled:i,...o}){let[a,s]=(0,h.useState)(null),l=t.find(t=>t.value===e)?.label??"";return(0,b.jsxs)("div",{className:"min-w-0",title:"string"==typeof l?l:void 0,children:[(0,b.jsx)(p,{openOnFocus:!0,wrapOptions:!0,id:o.id,invalid:o.invalid,readOnly:o.readOnly,required:o.required,size:o.size,"aria-describedby":o["aria-describedby"],"aria-label":o["aria-label"]??("string"==typeof n?n:"Search internal fields"),"data-testid":o["data-testid"],disabled:i,value:e||null,query:a??("string"==typeof l?l:""),onQueryChange:s,onFocus:e=>e.currentTarget.select(),onBlur:()=>s(null),onValueChange:e=>{"string"==typeof e&&r(e),s(null)},options:t.map(e=>({...e,label:String(e.label)})),placeholder:"string"==typeof n?n:"Search internal fields",emptyMessage:"No matching internal fields"}),"string"==typeof l&&l?(0,b.jsx)("p",{className:"mt-1 break-words text-xs text-modiff-subtle-text",children:l}):null]})}function g(e,t){if(!t)return new Set(e.effectiveGraph.nodes.map(({nodeId:e})=>e));if(!e.effectiveGraph.nodes.some(e=>e.nodeId===t))throw Error("This internal Block no longer exists. Reopen Configure Interface.");let r=s(e),n=new Set([t]);for(let i of e.effectiveGraph.nodes){let e=r.get(i.nodeId);for(;e;)e===t&&n.add(i.nodeId),e=r.get(e)}return n}function y(e,t){return[e.binding,...e.mirrorBindings??[]].every(({nodeId:e})=>t.has(e))}function x(e,t){if(t){let{boundary:r,controls:n,previews:i}=u(e,t);return structuredClone({boundary:r,controls:n,previews:i??[]})}let r=g(e,t);return structuredClone({boundary:{mode:"explicit",inputs:e.effectiveInterface.boundary.inputs.filter(e=>y(e,r)),outputs:e.effectiveInterface.boundary.outputs.filter(e=>y(e,r))},controls:e.effectiveInterface.controls.filter(e=>y(e,r))})}function w(e,t,r,n){for(let r of["instanceId","definitionRef","effectiveGraph","effectiveInterface","values"])if(JSON.stringify(e[r])!==JSON.stringify(t[r]))throw Error("This Block changed while Configure Interface was open. Reopen it before applying.");let i=g(t,n);if(n){let e=r.previews??u(t,n).previews,{boundary:i,controls:o,previews:a}=l({schemaVersion:1,boundary:r.boundary,controls:r.controls.map(e=>{let t=structuredClone(e);return delete t.defaultValue,t}),...void 0===e?{}:{previews:e}},t.effectiveGraph,n);return{boundary:i,controls:o,...void 0===a?{}:{previews:a}}}if(void 0!==r.previews)throw Error("Root preview bindings belong to the Block definition, not its effective interface.");let o=(e,t,r)=>{let n=new Set(e.filter(e=>!y(e,i)).map(r));if(t.some(e=>!y(e,i)||n.has(r(e))))throw Error("This interface change would affect another branch. Use the owning Block interface editor.");if(new Set(t.map(r)).size!==t.length)throw Error("Interface entries must have unique IDs.");let o=0;return[...e.flatMap(e=>y(e,i)?oe.portId),outputs:o(t.effectiveInterface.boundary.outputs,r.boundary.outputs,e=>e.portId)},controls:o(t.effectiveInterface.controls,r.controls,e=>e.controlId).map((e,t)=>({...e,order:t}))}}function I(e,t,r){return f(e,t,r)}function j(e){return`${e.nodeId}\0${e.outputPortId}\0${e.mediaType}`}function k(e,t){let r=d(e.effectiveGraph,t);return e.effectiveGraph.nodes.flatMap(n=>{if(!r.has(n.nodeId))return[];let i=n.data.params;return!i||"object"!=typeof i||Array.isArray(i)?[]:Object.entries(i).flatMap(([r,i])=>!i||"object"!=typeof i||Array.isArray(i)||"output"!==i.display&&!String(i.display??"").startsWith("ui_")?[]:["image","video","audio","text","file"].flatMap(o=>{let a={nodeId:n.nodeId,outputPortId:r,mediaType:o};try{l({schemaVersion:1,boundary:{mode:"explicit",inputs:[],outputs:[]},controls:[],previews:[a]},e.effectiveGraph,t)}catch{return[]}return[{key:j(a),label:`${String(n.data.label||n.nodeId)} / ${String(i.label||r)} (${o})`,binding:a}]}))})}function C({snapshot:e,subtreeId:r,previews:n,onChange:s}){let l=(0,h.useMemo)(()=>k(e,r),[e,r]),[d,u]=(0,h.useState)(""),p=l.filter(({binding:e})=>!n.some(t=>t.nodeId===e.nodeId&&t.outputPortId===e.outputPortId)),f=new Map(l.map(({key:e,label:t})=>[e,t])),m=e=>f.get(j(e))??`${e.nodeId} / ${e.outputPortId} (${e.mediaType})`,g=(e,t)=>{let r=e+t;if(r<0||r>=n.length)return;let i=[...n];[i[e],i[r]]=[i[r],i[e]],s(i)},y=n.find(e=>e.primary);return(0,b.jsxs)("section",{className:"grid gap-2","data-testid":"block-interface-previews",children:[(0,b.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Collapsed previews"}),(0,b.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Choose outputs from this Block's own nodes. This changes its preview surface only, not connections, generation settings, or the root Block's previews. Existing generated files are not deleted."}),n.map((e,r)=>(0,b.jsxs)("div",{className:"flex min-w-0 items-center gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:[(0,b.jsx)("span",{className:"min-w-0 flex-1 break-words text-sm text-modiff-text",children:m(e)}),(0,b.jsx)(c,{label:`Move preview ${m(e)} up`,size:"compact",disabled:0===r,onClick:()=>g(r,-1),children:(0,b.jsx)(t,{size:14})}),(0,b.jsx)(c,{label:`Move preview ${m(e)} down`,size:"compact",disabled:r===n.length-1,onClick:()=>g(r,1),children:(0,b.jsx)(i,{size:14})}),(0,b.jsx)(c,{label:`Remove preview ${m(e)}`,size:"compact",onClick:()=>s(n.filter((e,t)=>t!==r)),children:(0,b.jsx)(a,{size:14})})]},j(e))),n.length?null:(0,b.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No previews selected for this Block."}),(0,b.jsx)(v,{"aria-label":"Primary collapsed preview",value:y?j(y):"none",options:[{value:"none",label:"No primary preview"},...n.map(e=>({value:j(e),label:m(e)}))],onValueChange:e=>s(n.map(t=>{let r={...t};return delete r.primary,j(r)===e?{...r,primary:!0}:r}))}),(0,b.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2",children:[(0,b.jsx)(v,{"aria-label":"Internal preview source",value:d,onValueChange:u,options:p.map(({key:e,label:t})=>({value:e,label:t})),placeholder:"Choose an internal media or text output",disabled:!p.length}),(0,b.jsx)(c,{label:"Add collapsed preview",size:"compact",disabled:!p.some(({key:e})=>e===d),onClick:()=>{let e=p.find(({key:e})=>e===d);e&&(s([...n,{...e.binding,...n.length?{}:{primary:!0}}]),u(""))},children:(0,b.jsx)(o,{size:14})})]}),l.length?null:(0,b.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No compatible media or text outputs are declared inside this Block."})]})}export{w as a,m as default,g as i,x as n,v as o,I as r,C as t}; \ No newline at end of file diff --git a/web/assets/BlockModelTaskControls.js b/web/assets/BlockModelTaskControls.js new file mode 100644 index 00000000..b5a62984 --- /dev/null +++ b/web/assets/BlockModelTaskControls.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as a}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ci as i,Ei as n,Eo as r,Hr as s,Kr as l,Ti as o,Vr as d,Xr as c,Ya as u,bn as f,dn as p,fn as h,in as m,is as g,ji as b,la as w,qr as x,rn as k}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as j}from"./operationGraphTransaction.js?v=cc046c26d6fe7aa4";var v=e(a(),1),C=t();function y({node:e}){let t=c(e=>e.library),a=c(e=>e.error),y=b(e=>e.activeWorkflowTabId),[S,I]=(0,v.useState)(""),[R,N]=(0,v.useState)(!1),[E,T]=(0,v.useState)(null),[F,V]=(0,v.useState)(null),O=(0,v.useRef)(null),q=e.data.blockInstanceV2,_=(t?.definitions??[]).flatMap(e=>e.executionAdmissions.filter(t=>"admitted"===t.status&&t.publication.insertable&&"graph_qualified"===t.publication.readiness&&w(e,t)).map(t=>({definition:e,admission:t}))),A=Object.entries(q.routeSelection?.inactiveDrafts??{});return(0,v.useEffect)(()=>(O.current?.abort(),O.current=null,N(!1),T(null),V(null),()=>{O.current?.abort()}),[y,e.id,S]),(0,C.jsxs)(p,{label:"Change model / task",panelClassName:"grid gap-2 py-2",children:[(0,C.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Choose a reviewed workflow. The current composition and custom edits remain available as a saved draft in this workflow; compatible public values carry across."}),(0,C.jsx)(k,{onClick:()=>{c.getState().fetchLibrary()},disabled:R,children:t?"Refresh workflows":"Load workflows"}),a?(0,C.jsx)("p",{role:"alert",children:a}):null,(0,C.jsx)(h,{label:"Workflow",children:(0,C.jsx)(f,{"aria-label":"Replacement Block workflow",value:S,onValueChange:I,options:[{value:"",label:"Select a model and task"},...A.map(([e,t])=>({value:`draft:${e}`,label:`Restore draft · ${t.definitionSnapshot.displayName}`})),..._.filter(e=>e.admission.id!==q.definitionRef.definitionId).map(e=>({value:e.admission.id,label:`${e.definition.label} · ${e.admission.studioMode??e.definition.workflowId}`}))]})}),(0,C.jsx)(k,{disabled:R||!S,onClick:()=>{!async function(){if(O.current||!S)return;let t=new AbortController;O.current=t;let a=n(),c=d.getState().toObject(),f=JSON.stringify(c);N(!0),T(null);try{let n,r=c.nodes.find(t=>t.id===e.id)?.data.blockInstanceV2;if(!r)throw Error("The owning Block is unavailable.");if(S.startsWith("draft:"))n=l(r,S.slice(6));else{let e=_.find(e=>e.admission.id===S);if(!e)throw Error("Reload the catalog and select an available workflow.");let{createHuggingFaceClusterForGraph:a}=await i(async()=>{let{createHuggingFaceClusterForGraph:e}=await import(`./huggingFaceClusterInsertion.js?v=cc046c26d6fe7aa4`).then(e=>e.n);return{createHuggingFaceClusterForGraph:e}},[]),s=u(e.admission.studioMode,e.definition.pipelineClass),l=await a(e.definition,r.presentation.position,s,{insert:!1,signal:t.signal});if(!l.data.blockInstanceV2||l.data.blockInstanceV2.definitionRef.definitionId!==e.admission.id)throw Error("The compiler returned a different workflow. No changes were applied.");n=x(r,l.data.blockInstanceV2)}if(t.signal.aborted)return;if(o(a,{includeForm:!1}),JSON.stringify(d.getState().toObject())!==f)throw Error("The graph changed. Request a fresh preview.");s(r,n,c.edges),V({destination:n,context:a,signature:f})}catch(e){t.signal.aborted||T(r(e,"Could not prepare the model/task change."))}finally{O.current===t&&(O.current=null,N(!1))}}()},children:"Preview model / task change"}),R?(0,C.jsx)(k,{onClick:()=>{O.current?.abort(),O.current=null,N(!1)},children:"Cancel preparation"}):null,E?(0,C.jsx)("p",{role:"alert",className:"text-xs text-modiff-text",children:E}):null,F?(0,C.jsx)(m,{open:!0,title:"Review Block model / task change",onClose:()=>{V(null),T(null)},children:(0,C.jsxs)("div",{className:"grid gap-3 text-sm",children:[(0,C.jsxs)("p",{children:[q.definitionSnapshot.displayName," → ",F.destination.definitionSnapshot.displayName]}),(0,C.jsx)("p",{children:"The current edited graph is retained outside execution. Restore its draft to recover custom nodes, unsupported settings and internal connections. External connections must fit the destination interface. Undo restores the entire change."}),(0,C.jsxs)("p",{children:["Required inputs:"," ",F.destination.effectiveInterface.boundary.inputs.filter(e=>e.required).map(e=>e.label).join(", ")||"No required public media inputs declared."]}),E?(0,C.jsx)("p",{role:"alert",children:E}):null,(0,C.jsx)(k,{onClick:function(){if(F)try{let t=d.getState().toObject(),a=g(F.destination);j({...t,nodes:t.nodes.map(t=>t.id===e.id?{...t,data:{...t.data,...a.data}}:t)},F.context,F.signature,"Change Block model or task"),V(null),I("")}catch(e){T(r(e,"Could not apply the model/task change."))}},children:"Apply graph change"})]})}):null]})}export{y as default}; \ No newline at end of file diff --git a/web/assets/CompositeMigrationCard.js b/web/assets/CompositeMigrationCard.js index e79ecb83..cc0cc3c1 100644 --- a/web/assets/CompositeMigrationCard.js +++ b/web/assets/CompositeMigrationCard.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{K as t,U as i,at as n,on as o,pr as a,tt as s,ur as r,z as c}from"./graph-vendor.js?v=5bcd315505f5670a";import{$a as d,Ao as l,Da as u,Eo as p,Fr as m,Ir as h,Jt as f,Lo as g,Lt as v,No as y,Pr as x,Qt as b,Sn as I,Ua as C,Ur as w,Ut as $,Vi as N,Wt as S,Zt as j,ai as k,cn as H,di as P,en as A,ni as E,ro as R,ti as _,to as M,ui as F,wo as D}from"./studio-templates.js?v=5bcd315505f5670a";import{t as O}from"./huggingFaceClusterInsertion.js?v=5bcd315505f5670a";var T=e(a(),1),B="APPLY_BLOCK_V2_MIGRATION",L="ROLLBACK_BLOCK_V2_MIGRATION",V=/^block-v2-migration-[a-f0-9]{24}$/u,q=/^sha256:[a-f0-9]{64}$/u,z=/^block-definition-v2-[a-f0-9]{8}$/u,K=/^block-graph-v2-[a-f0-9]{8}$/u,G=/^block-interface-v2-[a-f0-9]{8}$/u,J=/^[a-f0-9]{40}$/u,U=/^(?:user-workflows\/[^/]+\.json|studio\/blocks\/[^/]+\.json)$/u,W=/^user-workflows\/[^/]+\.json$/u,Z=new Set(["legacy_registered_cluster_instance","reusable_v1_definition","workflow_v1_instance","workflow_v1_instances","registered_cluster_compiler_output"]),Y=new Set(["reusable_v1_definition","workflow_v1_instances","workflow_composite_instances"]),Q=new Set(["applied","applying","prepared","recovery_required","rolled_back","rolling_back"]),X=new Set(["applied","conflict","interrupted","rolled_back"]),ee=new Set(["applied","conflict","missing","source"]);function te(e,t="is invalid"){throw Error(`Invalid composite migration response: ${e} ${t}.`)}function ie(e,t){return(!e||"object"!=typeof e||Array.isArray(e))&&te(t,"must be an object"),e}function ne(e,t,i,n=[]){let o=new Set([...i,...n]),a=i.filter(t=>!Object.prototype.hasOwnProperty.call(e,t)),s=Object.keys(e).filter(e=>!o.has(e));a.length&&te(t,`is missing ${a.join(", ")}`),s.length&&te(t,`contains unknown keys ${s.join(", ")}`)}function oe(e,t,i,n=4096){return("string"!=typeof e||!e||e.length>n||i&&!i.test(e))&&te(t),e}function ae(e,t){return null===e?null:oe(e,t,void 0,512)}function se(e,t){return"boolean"!=typeof e&&te(t,"must be a boolean"),e}function re(e,t){return(!Number.isSafeInteger(e)||Number(e)<0)&&te(t,"must be a nonnegative integer"),Number(e)}function ce(e,t){return Array.isArray(e)||te(t,"must be an array"),e}function de(e,t){new Set(e).size!==e.length&&te(t,"contains duplicate entries")}function le(e,t,i){return e.summary.hasChanges&&"APPLY_BLOCK_V2_MIGRATION"===t&&(0===e.summary.blockedCandidateCount||i)}function ue(e,t){for(let i=1;i=(e[i]??"")&&te(t,"must be unique and canonically ordered")}function pe(e,t){let i=ce(e,t).map((e,i)=>oe(e,`${t}[${i}]`,void 0,512));return ue(i,t),i}function me(e,t){let i=ie(e,t);return ne(i,t,["id","contentHash","executionProfileId"]),{id:oe(i.id,`${t}.id`,void 0,512),contentHash:oe(i.contentHash,`${t}.contentHash`,void 0,128),executionProfileId:oe(i.executionProfileId,`${t}.executionProfileId`,void 0,512)}}function he(e,t){let i=ie(e,t);ne(i,t,["receiptId","receiptHash","historical","destination","review"]);let n=ie(i.historical,`${t}.historical`);ne(n,`${t}.historical`,["manifestDefinitionId","libraryRevision","manifestContentHash","executionAdmissionId","studioExecutionSpec","executionGraphHash","interfaceHash"]);let o=ie(i.destination,`${t}.destination`);ne(o,`${t}.destination`,["manifestDefinitionId","libraryRevision","manifestContentHash","executionAdmissionId","blockDefinitionId","blockDefinitionContentHash","blockDefinitionCanonicalSha256","executionGraphHash","interfaceHash"]);let a=ie(i.review,`${t}.review`);ne(a,`${t}.review`,["decision","issuer","reviewedAt","notes"]),"semantic_equivalent"!==a.decision&&te(`${t}.review.decision`);let s=oe(a.reviewedAt,`${t}.review.reviewedAt`,void 0,64);return(!/(?:Z|[+-][0-9]{2}:[0-9]{2})$/u.test(s)||Number.isNaN(Date.parse(s)))&&te(`${t}.review.reviewedAt`,"must be an offset ISO-8601 timestamp"),{receiptId:oe(i.receiptId,`${t}.receiptId`,void 0,256),receiptHash:oe(i.receiptHash,`${t}.receiptHash`,q),historical:{manifestDefinitionId:oe(n.manifestDefinitionId,`${t}.historical.manifestDefinitionId`,void 0,512),libraryRevision:oe(n.libraryRevision,`${t}.historical.libraryRevision`,J),manifestContentHash:oe(n.manifestContentHash,`${t}.historical.manifestContentHash`,q),executionAdmissionId:oe(n.executionAdmissionId,`${t}.historical.executionAdmissionId`,void 0,1024),studioExecutionSpec:me(n.studioExecutionSpec,`${t}.historical.studioExecutionSpec`),executionGraphHash:oe(n.executionGraphHash,`${t}.historical.executionGraphHash`,q),interfaceHash:oe(n.interfaceHash,`${t}.historical.interfaceHash`,q)},destination:{manifestDefinitionId:oe(o.manifestDefinitionId,`${t}.destination.manifestDefinitionId`,void 0,512),libraryRevision:oe(o.libraryRevision,`${t}.destination.libraryRevision`,J),manifestContentHash:oe(o.manifestContentHash,`${t}.destination.manifestContentHash`,q),executionAdmissionId:oe(o.executionAdmissionId,`${t}.destination.executionAdmissionId`,void 0,1024),blockDefinitionId:oe(o.blockDefinitionId,`${t}.destination.blockDefinitionId`,void 0,384),blockDefinitionContentHash:oe(o.blockDefinitionContentHash,`${t}.destination.blockDefinitionContentHash`,z),blockDefinitionCanonicalSha256:oe(o.blockDefinitionCanonicalSha256,`${t}.destination.blockDefinitionCanonicalSha256`,q),executionGraphHash:oe(o.executionGraphHash,`${t}.destination.executionGraphHash`,K),interfaceHash:oe(o.interfaceHash,`${t}.destination.interfaceHash`,G)},review:{decision:"semantic_equivalent",issuer:oe(a.issuer,`${t}.review.issuer`,void 0,256),reviewedAt:s,notes:oe(a.notes,`${t}.review.notes`,void 0,4096)}}}function fe(e,t){let i=ie(e,t);ne(i,t,["mappingId","mappingHash","historical","destination","review"]);let n=ie(i.historical,`${t}.historical`);ne(n,`${t}.historical`,["manifestDefinitionId","libraryRevision","manifestContentHash","executionAdmissionId","studioExecutionSpec","archivedDefinitionRecordHash","blockContractHash","rootBlockDefinitionId"]);let o=ie(i.destination,`${t}.destination`);ne(o,`${t}.destination`,["manifestDefinitionId","libraryRevision","manifestContentHash","executionAdmissionId","blockDefinitionId","blockDefinitionContentHash","blockDefinitionCanonicalSha256","executionGraphHash","interfaceHash"]);let a=ie(i.review,`${t}.review`);ne(a,`${t}.review`,["decision","issuer","reviewedAt","notes"]),"historical_compiler_mapping_reviewed"!==a.decision&&te(`${t}.review.decision`);let s=oe(a.reviewedAt,`${t}.review.reviewedAt`,void 0,64);return(!/(?:Z|[+-][0-9]{2}:[0-9]{2})$/u.test(s)||Number.isNaN(Date.parse(s)))&&te(`${t}.review.reviewedAt`,"must be an offset ISO-8601 timestamp"),{mappingId:oe(i.mappingId,`${t}.mappingId`,void 0,256),mappingHash:oe(i.mappingHash,`${t}.mappingHash`,q),historical:{manifestDefinitionId:oe(n.manifestDefinitionId,`${t}.historical.manifestDefinitionId`,void 0,512),libraryRevision:oe(n.libraryRevision,`${t}.historical.libraryRevision`,J),manifestContentHash:oe(n.manifestContentHash,`${t}.historical.manifestContentHash`,q),executionAdmissionId:oe(n.executionAdmissionId,`${t}.historical.executionAdmissionId`,void 0,1024),studioExecutionSpec:me(n.studioExecutionSpec,`${t}.historical.studioExecutionSpec`),archivedDefinitionRecordHash:oe(n.archivedDefinitionRecordHash,`${t}.historical.archivedDefinitionRecordHash`,q),blockContractHash:oe(n.blockContractHash,`${t}.historical.blockContractHash`,q),rootBlockDefinitionId:oe(n.rootBlockDefinitionId,`${t}.historical.rootBlockDefinitionId`,void 0,1024)},destination:{manifestDefinitionId:oe(o.manifestDefinitionId,`${t}.destination.manifestDefinitionId`,void 0,512),libraryRevision:oe(o.libraryRevision,`${t}.destination.libraryRevision`,J),manifestContentHash:oe(o.manifestContentHash,`${t}.destination.manifestContentHash`,q),executionAdmissionId:oe(o.executionAdmissionId,`${t}.destination.executionAdmissionId`,void 0,1024),blockDefinitionId:oe(o.blockDefinitionId,`${t}.destination.blockDefinitionId`,void 0,384),blockDefinitionContentHash:oe(o.blockDefinitionContentHash,`${t}.destination.blockDefinitionContentHash`,z),blockDefinitionCanonicalSha256:oe(o.blockDefinitionCanonicalSha256,`${t}.destination.blockDefinitionCanonicalSha256`,q),executionGraphHash:oe(o.executionGraphHash,`${t}.destination.executionGraphHash`,K),interfaceHash:oe(o.interfaceHash,`${t}.destination.interfaceHash`,G)},review:{decision:"historical_compiler_mapping_reviewed",issuer:oe(a.issuer,`${t}.review.issuer`,void 0,256),reviewedAt:s,notes:oe(a.notes,`${t}.review.notes`,void 0,4096)}}}function ge(e){let t=ie(e,"compiler supplement");ne(t,"compiler supplement",["schemaVersion","kind","compilerOutputs"]),(1!==t.schemaVersion||"registered_cluster_v2_compiler_supplement"!==t.kind)&&te("compiler supplement","uses an unsupported contract");let i=ce(t.compilerOutputs,"compiler supplement.compilerOutputs").map((e,t)=>{let i=`compiler supplement.compilerOutputs[${t}]`,n=ie(e,i);ne(n,i,["sourcePath","sourceSha256","conversions"]);let o=ce(n.conversions,`${i}.conversions`).map((e,t)=>function(e,t){let i=ie(e,t);ne(i,t,["legacyInstanceId","legacyCompositeHash","admissionId","compiledDefinitionContentHash","compiledDefinitionCanonicalSha256","blockInstanceV2","ownedNodeMappings","portMappings","valueMappings","previewMappings","absorbedInternalEdgeIds"],["semanticEquivalenceReceipt","historicalCompilerMapping"]);let n=ce(i.ownedNodeMappings,`${t}.ownedNodeMappings`).map((e,i)=>{let n=`${t}.ownedNodeMappings[${i}]`,o=ie(e,n);return ne(o,n,["legacyNodeId","semanticNodeId"]),{legacyNodeId:oe(o.legacyNodeId,`${n}.legacyNodeId`,void 0,512),semanticNodeId:oe(o.semanticNodeId,`${n}.semanticNodeId`,void 0,512)}});ue(n.map(({legacyNodeId:e,semanticNodeId:t})=>`${e}\0${t}`),`${t}.ownedNodeMappings`);let o=ce(i.portMappings,`${t}.portMappings`).map((e,i)=>{let n=`${t}.portMappings[${i}]`,o=ie(e,n);ne(o,n,["direction","legacyNodeId","legacyPortId","v2PortId"]);let a=oe(o.direction,`${n}.direction`);return"input"!==a&&"output"!==a&&te(`${n}.direction`),{direction:a,legacyNodeId:oe(o.legacyNodeId,`${n}.legacyNodeId`,void 0,512),legacyPortId:oe(o.legacyPortId,`${n}.legacyPortId`,void 0,512),v2PortId:oe(o.v2PortId,`${n}.v2PortId`,void 0,512)}});ue(o.map(({direction:e,legacyNodeId:t,legacyPortId:i,v2PortId:n})=>`${e}\0${t}\0${i}\0${n}`),`${t}.portMappings`);let a=ce(i.valueMappings,`${t}.valueMappings`).map((e,i)=>{let n=`${t}.valueMappings[${i}]`,o=ie(e,n),a=oe(o.targetKind,`${n}.targetKind`),s={sourceKind:oe(o.sourceKind,`${n}.sourceKind`,void 0,512),sourceNodeId:oe(o.sourceNodeId,`${n}.sourceNodeId`,void 0,512),sourceFieldId:oe(o.sourceFieldId,`${n}.sourceFieldId`,void 0,512)};return"instance_value"===a?(ne(o,n,["sourceKind","sourceNodeId","sourceFieldId","targetKind","targetValueId"]),{...s,targetKind:"instance_value",targetValueId:oe(o.targetValueId,`${n}.targetValueId`,void 0,512)}):"graph_param"===a?(ne(o,n,["sourceKind","sourceNodeId","sourceFieldId","targetKind","targetNodeId","targetFieldId"]),{...s,targetKind:"graph_param",targetNodeId:oe(o.targetNodeId,`${n}.targetNodeId`,void 0,512),targetFieldId:oe(o.targetFieldId,`${n}.targetFieldId`,void 0,512)}):void te(`${n}.targetKind`)});ue(a.map(({sourceKind:e,sourceNodeId:t,sourceFieldId:i,targetKind:n})=>`${e}\0${t}\0${i}\0${n}`),`${t}.valueMappings`);let s=ce(i.previewMappings,`${t}.previewMappings`).map((e,i)=>{let n=`${t}.previewMappings[${i}]`,o=ie(e,n);return ne(o,n,["sourceNodeId","sourceFieldId","targetNodeId","targetOutputPortId"]),{sourceNodeId:oe(o.sourceNodeId,`${n}.sourceNodeId`,void 0,512),sourceFieldId:oe(o.sourceFieldId,`${n}.sourceFieldId`,void 0,512),targetNodeId:oe(o.targetNodeId,`${n}.targetNodeId`,void 0,512),targetOutputPortId:oe(o.targetOutputPortId,`${n}.targetOutputPortId`,void 0,512)}});ue(s.map(({sourceNodeId:e,sourceFieldId:t,targetNodeId:i,targetOutputPortId:n})=>`${e}\0${t}\0${i}\0${n}`),`${t}.previewMappings`);let r=void 0===i.semanticEquivalenceReceipt?void 0:(()=>{let e=ie(i.semanticEquivalenceReceipt,`${t}.semanticEquivalenceReceipt`);return ne(e,`${t}.semanticEquivalenceReceipt`,["receiptId","receiptHash"]),{receiptId:oe(e.receiptId,`${t}.semanticEquivalenceReceipt.receiptId`,void 0,256),receiptHash:oe(e.receiptHash,`${t}.semanticEquivalenceReceipt.receiptHash`,q)}})(),c=void 0===i.historicalCompilerMapping?void 0:(()=>{let e=ie(i.historicalCompilerMapping,`${t}.historicalCompilerMapping`);return ne(e,`${t}.historicalCompilerMapping`,["mappingId","mappingHash"]),{mappingId:oe(e.mappingId,`${t}.historicalCompilerMapping.mappingId`,void 0,256),mappingHash:oe(e.mappingHash,`${t}.historicalCompilerMapping.mappingHash`,q)}})();return r&&c&&te(t,"must not combine semantic-equivalence and historical compiler-mapping authority"),{legacyInstanceId:oe(i.legacyInstanceId,`${t}.legacyInstanceId`,void 0,512),legacyCompositeHash:oe(i.legacyCompositeHash,`${t}.legacyCompositeHash`,q),admissionId:oe(i.admissionId,`${t}.admissionId`,void 0,1024),compiledDefinitionContentHash:oe(i.compiledDefinitionContentHash,`${t}.compiledDefinitionContentHash`,z),compiledDefinitionCanonicalSha256:oe(i.compiledDefinitionCanonicalSha256,`${t}.compiledDefinitionCanonicalSha256`,q),blockInstanceV2:l(i.blockInstanceV2),ownedNodeMappings:n,portMappings:o,valueMappings:a,previewMappings:s,absorbedInternalEdgeIds:pe(i.absorbedInternalEdgeIds,`${t}.absorbedInternalEdgeIds`),...r?{semanticEquivalenceReceipt:r}:{},...c?{historicalCompilerMapping:c}:{}}}(e,`${i}.conversions[${t}]`));return o.length||te(`${i}.conversions`,"must not be empty"),ue(o.map(({legacyInstanceId:e})=>e),`${i}.conversions`),{sourcePath:oe(n.sourcePath,`${i}.sourcePath`,W),sourceSha256:oe(n.sourceSha256,`${i}.sourceSha256`,q),conversions:o}});return ue(i.map(({sourcePath:e})=>e),"compiler supplement.compilerOutputs"),{schemaVersion:1,kind:"registered_cluster_v2_compiler_supplement",compilerOutputs:i}}function ve(e,t){let i=ie(e,`preview.targets[${t}]`);ne(i,`preview.targets[${t}]`,["sourcePath","kind","beforeSha256","afterSha256","convertedIds","byteLength"]);let n=oe(i.kind,`preview.targets[${t}].kind`);Y.has(n)||te(`preview.targets[${t}].kind`);let o=ce(i.convertedIds,`preview.targets[${t}].convertedIds`).map((e,i)=>oe(e,`preview.targets[${t}].convertedIds[${i}]`,void 0,512));return o.length||te(`preview.targets[${t}].convertedIds`,"must not be empty"),de(o,`preview.targets[${t}].convertedIds`),{sourcePath:oe(i.sourcePath,`preview.targets[${t}].sourcePath`,U),kind:n,beforeSha256:oe(i.beforeSha256,`preview.targets[${t}].beforeSha256`,q),afterSha256:oe(i.afterSha256,`preview.targets[${t}].afterSha256`,q),convertedIds:o,byteLength:re(i.byteLength,`preview.targets[${t}].byteLength`)}}function ye(e,t){let i=ie(e,t);ne(i,t,["kind","sourcePath","id","status"],["reason","targetAfterSha256","sourceSha256","legacyCompositeHash","compilerReceipt","semanticEquivalenceAuthority","historicalCompilerMappingAuthority"]);let n=oe(i.kind,`${t}.kind`);Z.has(n)||te(`${t}.kind`);let o=oe(i.status,`${t}.status`);"blocked"!==o&&"convertible"!==o&&te(`${t}.status`);let a=void 0===i.reason?void 0:oe(i.reason,`${t}.reason`,void 0,8192),s=void 0===i.targetAfterSha256?void 0:oe(i.targetAfterSha256,`${t}.targetAfterSha256`,q),r=void 0===i.sourceSha256?void 0:oe(i.sourceSha256,`${t}.sourceSha256`,q),c=void 0===i.legacyCompositeHash?void 0:oe(i.legacyCompositeHash,`${t}.legacyCompositeHash`,q),d=void 0===i.compilerReceipt?void 0:function(e,t){let i=ie(e,t);ne(i,t,["instanceId","definitionId","admissionId","compiledDefinitionContentHash","compiledDefinitionCanonicalSha256","legacyCompositeHash","absorbedProjectionNodeIds","absorbedInternalEdgeIds"],["semanticEquivalenceAuthority","historicalCompilerMappingAuthority"]);let n=void 0===i.semanticEquivalenceAuthority?void 0:he(i.semanticEquivalenceAuthority,`${t}.semanticEquivalenceAuthority`),o=void 0===i.historicalCompilerMappingAuthority?void 0:fe(i.historicalCompilerMappingAuthority,`${t}.historicalCompilerMappingAuthority`);return n&&o&&te(t,"must not combine semantic-equivalence and historical compiler-mapping authority"),{instanceId:oe(i.instanceId,`${t}.instanceId`,void 0,512),definitionId:oe(i.definitionId,`${t}.definitionId`,void 0,512),admissionId:oe(i.admissionId,`${t}.admissionId`,void 0,1024),compiledDefinitionContentHash:oe(i.compiledDefinitionContentHash,`${t}.compiledDefinitionContentHash`,z),compiledDefinitionCanonicalSha256:oe(i.compiledDefinitionCanonicalSha256,`${t}.compiledDefinitionCanonicalSha256`,q),legacyCompositeHash:oe(i.legacyCompositeHash,`${t}.legacyCompositeHash`,q),absorbedProjectionNodeIds:pe(i.absorbedProjectionNodeIds,`${t}.absorbedProjectionNodeIds`),absorbedInternalEdgeIds:pe(i.absorbedInternalEdgeIds,`${t}.absorbedInternalEdgeIds`),...n?{semanticEquivalenceAuthority:n}:{},...o?{historicalCompilerMappingAuthority:o}:{}}}(i.compilerReceipt,`${t}.compilerReceipt`),l=void 0===i.semanticEquivalenceAuthority?void 0:he(i.semanticEquivalenceAuthority,`${t}.semanticEquivalenceAuthority`),u=void 0===i.historicalCompilerMappingAuthority?void 0:fe(i.historicalCompilerMappingAuthority,`${t}.historicalCompilerMappingAuthority`);return l&&u&&te(t,"must not combine semantic-equivalence and historical compiler-mapping authority"),("blocked"===o!=!!a||"convertible"===o!=!!s)&&te(t,"has inconsistent status details"),{kind:n,sourcePath:oe(i.sourcePath,`${t}.sourcePath`,U),id:ae(i.id,`${t}.id`),status:o,...a?{reason:a}:{},...s?{targetAfterSha256:s}:{},...r?{sourceSha256:r}:{},...c?{legacyCompositeHash:c}:{},...d?{compilerReceipt:d}:{},...l?{semanticEquivalenceAuthority:l}:{},...u?{historicalCompilerMappingAuthority:u}:{}}}function xe(e,t){let i=`preview.inventoryIssues[${t}]`,n=ie(e,i);ne(n,i,["sourcePath","path","code","severity","message"]);let o=oe(n.severity,`${i}.severity`);return"error"!==o&&"warning"!==o&&te(`${i}.severity`),{sourcePath:oe(n.sourcePath,`${i}.sourcePath`,void 0,4096),path:oe(n.path,`${i}.path`,void 0,4096),code:oe(n.code,`${i}.code`,void 0,512),severity:o,message:oe(n.message,`${i}.message`,void 0,8192)}}function be(e){let t=ie(e,"preview response");ne(t,"preview response",["error","preview"]),!1!==t.error&&te("preview response.error","must be false");let i=ie(t.preview,"preview");ne(i,"preview",["schemaVersion","kind","mode","boundary","inventoryReportHash","sourceSetHash","summary","targets","candidates","blocked","inventoryIssues","migrationId","planHash"],["compilerSupplement"]),(1!==i.schemaVersion||"legacy_composite_to_block_v2_migration"!==i.kind||"read_only_preview"!==i.mode)&&te("preview","uses an unsupported contract");let n=ie(i.boundary,"preview.boundary");ne(n,"preview.boundary",["writesFiles","requiresExplicitApplyAuthority","createsExactBackupsBeforeReplacement","deletesRecords","mergesRecords","legacyClusterConversionRequiresRegisteredCompiler","containsPromptAndParameterValues"],["compilerSupplementRequiredForRegisteredClusters"]),(!1!==n.writesFiles||!0!==n.requiresExplicitApplyAuthority||!0!==n.createsExactBackupsBeforeReplacement||!1!==n.deletesRecords||!1!==n.mergesRecords||!0!==n.legacyClusterConversionRequiresRegisteredCompiler||!1!==n.containsPromptAndParameterValues)&&te("preview.boundary","does not preserve the read-only safety contract");let o=ie(i.summary,"preview.summary");ne(o,"preview.summary",["sourceCount","targetFileCount","convertibleCandidateCount","blockedCandidateCount","legacyClusterBlockedCount","hasChanges"],["registeredClusterConvertibleCount"]);let a=ce(i.targets,"preview.targets").map(ve),s=ce(i.candidates,"preview.candidates").map((e,t)=>ye(e,`preview.candidates[${t}]`)),r=ce(i.blocked,"preview.blocked").map((e,t)=>ye(e,`preview.blocked[${t}]`)),c=ce(i.inventoryIssues,"preview.inventoryIssues").map(xe),d={sourceCount:re(o.sourceCount,"preview.summary.sourceCount"),targetFileCount:re(o.targetFileCount,"preview.summary.targetFileCount"),convertibleCandidateCount:re(o.convertibleCandidateCount,"preview.summary.convertibleCandidateCount"),blockedCandidateCount:re(o.blockedCandidateCount,"preview.summary.blockedCandidateCount"),legacyClusterBlockedCount:re(o.legacyClusterBlockedCount,"preview.summary.legacyClusterBlockedCount"),hasChanges:se(o.hasChanges,"preview.summary.hasChanges"),...void 0===o.registeredClusterConvertibleCount?{}:{registeredClusterConvertibleCount:re(o.registeredClusterConvertibleCount,"preview.summary.registeredClusterConvertibleCount")}},l=void 0===i.compilerSupplement?void 0:(()=>{let e=ie(i.compilerSupplement,"preview.compilerSupplement");ne(e,"preview.compilerSupplement",["provided","contentHash","sourceCount","conversionCount"]);let t=se(e.provided,"preview.compilerSupplement.provided"),n=null===e.contentHash?null:oe(e.contentHash,"preview.compilerSupplement.contentHash",q);return t!==!!n&&te("preview.compilerSupplement","has an inconsistent provided/contentHash pair"),{provided:t,contentHash:n,sourceCount:re(e.sourceCount,"preview.compilerSupplement.sourceCount"),conversionCount:re(e.conversionCount,"preview.compilerSupplement.conversionCount")}})();(!!l!=(!0===n.compilerSupplementRequiredForRegisteredClusters)||!!l!=(void 0!==d.registeredClusterConvertibleCount))&&te("preview.compilerSupplement","is inconsistent with the compiler-preview boundary and summary"),de(a.map(({sourcePath:e})=>e),"preview.targets");let u=s.filter(({status:e})=>"blocked"===e);(d.targetFileCount!==a.length||d.convertibleCandidateCount!==s.filter(({status:e})=>"convertible"===e).length||d.blockedCandidateCount!==u.length||d.blockedCandidateCount!==r.length||d.legacyClusterBlockedCount!==r.filter(({kind:e})=>"legacy_registered_cluster_instance"===e).length||void 0!==d.registeredClusterConvertibleCount&&d.registeredClusterConvertibleCount!==s.filter(({kind:e,status:t})=>"legacy_registered_cluster_instance"===e&&"convertible"===t).length||d.hasChanges!==!!a.length||JSON.stringify(u)!==JSON.stringify(r))&&te("preview.summary","does not match the listed targets and candidates");let p=new Map(a.map(e=>[e.sourcePath,e.afterSha256]));return s.some(e=>"convertible"===e.status&&p.get(e.sourcePath)!==e.targetAfterSha256)&&te("preview.candidates","does not match the target hashes"),{schemaVersion:1,kind:"legacy_composite_to_block_v2_migration",mode:"read_only_preview",boundary:{writesFiles:!1,requiresExplicitApplyAuthority:!0,createsExactBackupsBeforeReplacement:!0,deletesRecords:!1,mergesRecords:!1,legacyClusterConversionRequiresRegisteredCompiler:!0,...l?{compilerSupplementRequiredForRegisteredClusters:!0}:{},containsPromptAndParameterValues:!1},...l?{compilerSupplement:l}:{},inventoryReportHash:oe(i.inventoryReportHash,"preview.inventoryReportHash",q),sourceSetHash:oe(i.sourceSetHash,"preview.sourceSetHash",q),summary:d,targets:a,candidates:s,blocked:r,inventoryIssues:c,migrationId:oe(i.migrationId,"preview.migrationId",V),planHash:oe(i.planHash,"preview.planHash",q)}}function Ie(e,t){let i=`migration status.targets[${t}]`,n=ie(e,i);ne(n,i,["sourcePath","currentSha256","state"]);let o=oe(n.state,`${i}.state`);ee.has(o)||te(`${i}.state`);let a=null===n.currentSha256?null:oe(n.currentSha256,`${i}.currentSha256`,q);return"missing"===o!=(null===a)&&te(i,"has an inconsistent current file hash"),{sourcePath:oe(n.sourcePath,`${i}.sourcePath`,U),currentSha256:a,state:o}}function Ce(e){let t=ie(e,"migration status");ne(t,"migration status",["migrationId","journalState","effectiveState","planHash","inventoryReportHash","targets","rollbackAvailable","resumeAvailable"]);let i=oe(t.journalState,"migration status.journalState"),n=oe(t.effectiveState,"migration status.effectiveState");Q.has(i)||te("migration status.journalState"),X.has(n)||te("migration status.effectiveState");let o=ce(t.targets,"migration status.targets").map(Ie);o.length||te("migration status.targets","must not be empty"),de(o.map(({sourcePath:e})=>e),"migration status.targets");let a=se(t.rollbackAvailable,"migration status.rollbackAvailable"),s=se(t.resumeAvailable,"migration status.resumeAvailable"),r=new Set(o.map(({state:e})=>e));return(n!==(1===r.size&&r.has("applied")?"applied":1===r.size&&r.has("source")?"rolled_back":[...r].every(e=>"source"===e||"applied"===e)?"interrupted":"conflict")||!1!==s||a!==["applied","interrupted","rolled_back"].includes(n))&&te("migration status","has inconsistent recovery actions"),{migrationId:oe(t.migrationId,"migration status.migrationId",V),journalState:i,effectiveState:n,planHash:oe(t.planHash,"migration status.planHash",q),inventoryReportHash:oe(t.inventoryReportHash,"migration status.inventoryReportHash",q),targets:o,rollbackAvailable:a,resumeAvailable:!1}}function we(e){let t=ie(e,"migration list response");ne(t,"migration list response",["error","schemaVersion","migrations"]),(!1!==t.error||1!==t.schemaVersion)&&te("migration list response","uses an unsupported contract");let i=ce(t.migrations,"migration list response.migrations").map(Ce);return de(i.map(({migrationId:e})=>e),"migration list response.migrations"),{schemaVersion:1,migrations:i}}function $e(e){let t=ie(e,"migration status response");return ne(t,"migration status response",["error","status"]),!1!==t.error&&te("migration status response.error","must be false"),Ce(t.status)}function Ne(e,t){let i=ie(e,"migration mutation response");ne(i,"migration mutation response",["error","idempotent","status"],[t]),!1!==i.error&&te("migration mutation response.error","must be false");let n=Ce(i.status),o=void 0===i[t]?void 0:ce(i[t],`migration mutation response.${t}`).map((e,i)=>oe(e,`migration mutation response.${t}[${i}]`,U));if(o){de(o,`migration mutation response.${t}`);let e=new Set(n.targets.map(({sourcePath:e})=>e));o.some(t=>!e.has(t))&&te(`migration mutation response.${t}`,"is not a target")}return{idempotent:se(i.idempotent,"migration mutation response.idempotent"),..."changedPaths"===t&&o?{changedPaths:o}:{},..."restoredPaths"===t&&o?{restoredPaths:o}:{},status:n}}function Se(e){let t=Ne(e,"changedPaths");return"applied"!==t.status.effectiveState&&te("migration apply result.status","did not reach the applied state"),t}function je(e){let t=Ne(e,"restoredPaths");return"rolled_back"!==t.status.effectiveState&&te("migration rollback result.status","did not reach the rolled-back state"),t}function ke(e,t){let i=M(e,t);return e instanceof d&&403===e.status?`Authorization rejected (HTTP 403): ${i}`:e instanceof d&&409===e.status?`Migration state conflict (HTTP 409): ${i}`:i}function He(e){let t=void 0===e?void 0:ge(e);return R(`${g.serverAddress}/studio/composite-migrations/preview`,{...t?{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({compilerSupplement:t})}:{},timeoutMs:3e4,parse:be})}function Pe(){return R(`${g.serverAddress}/studio/composite-migrations`,{timeoutMs:3e4,parse:we})}async function Ae(e){oe(e,"migrationId",V);let t=await R(`${g.serverAddress}/studio/composite-migrations/${encodeURIComponent(e)}`,{timeoutMs:3e4,parse:$e});return t.migrationId!==e&&te("migration status.migrationId","does not match the request"),t}async function Ee(e,t){if("APPLY_BLOCK_V2_MIGRATION"!==t.confirmation)throw Error(`Type ${B} exactly before applying.`);if("boolean"!=typeof t.allowBlockedCandidates)throw Error("Choose explicitly whether blocked candidates may remain before applying.");let i=be({error:!1,preview:e}),n=void 0===t.compilerSupplement?void 0:ge(t.compilerSupplement);if(!!i.compilerSupplement!=!!n)throw Error("Apply must reuse the exact compiler supplement from the reviewed preview.");if(i.compilerSupplement&&n&&(i.compilerSupplement.sourceCount!==n.compilerOutputs.length||i.compilerSupplement.conversionCount!==n.compilerOutputs.reduce((e,t)=>e+t.conversions.length,0)))throw Error("Apply compiler supplement does not match the reviewed preview counts.");if(i.summary.blockedCandidateCount>0&&!t.allowBlockedCandidates)throw Error("Explicitly allow blocked candidates to remain unchanged before applying safe targets.");let o=await R(`${g.serverAddress}/studio/composite-migrations/apply`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({migrationId:i.migrationId,planHash:i.planHash,confirmation:t.confirmation,allowBlockedCandidates:t.allowBlockedCandidates,...n?{compilerSupplement:n}:{}}),timeoutMs:12e4,parse:Se});return(o.status.migrationId!==i.migrationId||o.status.planHash!==i.planHash)&&te("migration apply result","does not match the reviewed preview"),o}async function Re(e,t){if(oe(e,"migrationId",V),"ROLLBACK_BLOCK_V2_MIGRATION"!==t)throw Error(`Type ${L} exactly before rollback.`);let i=await R(`${g.serverAddress}/studio/composite-migrations/${encodeURIComponent(e)}/rollback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({confirmation:t}),timeoutMs:12e4,parse:je});return i.status.migrationId!==e&&te("migration rollback result","does not match the requested journal"),i}var _e=/^sha256:[a-f0-9]{64}$/u,Me=/^[a-f0-9]{40}$/u,Fe=/^studio-spec-v1-[a-f0-9]{8}$/u;function De(e,t="is invalid"){throw Error(`Invalid legacy Cluster recovery-audit response: ${e} ${t}.`)}function Oe(e,t){return(!e||"object"!=typeof e||Array.isArray(e))&&De(t,"must be an object"),e}function Te(e,t,i,n=[]){let o=new Set([...i,...n]),a=i.filter(t=>!Object.prototype.hasOwnProperty.call(e,t)),s=Object.keys(e).filter(e=>!o.has(e));(a.length||s.length)&&De(t,"has missing or unknown fields")}function Be(e,t,i,n=8192){return("string"!=typeof e||!e||e.length>n||i&&!i.test(e))&&De(t),e}function Le(e,t,i,n=8192){return null===e?null:Be(e,t,i,n)}function Ve(e,t){return(!Number.isSafeInteger(e)||Number(e)<0)&&De(t,"must be a nonnegative integer"),Number(e)}function qe(e,t){return"boolean"!=typeof e&&De(t,"must be a boolean"),e}function ze(e,t){return Array.isArray(e)||De(t,"must be an array"),e}function Ke(e,t){let i=ze(e,t).map((e,i)=>Be(e,`${t}[${i}]`,void 0,1024));return new Set(i).size!==i.length&&De(t,"contains duplicates"),i}var Ge=new Set(["identity_malformed","execution_receipt_missing","execution_receipt_incomplete","current_manifest_exact","archived_manifest_exact","semantic_equivalence_reviewed","historical_manifest_evidence_required"]),Je="legacyClusterInstanceCount.definitionIdentityCount.currentManifestExactInstanceCount.currentManifestExactIdentityCount.currentExecutionTupleExactInstanceCount.currentExecutionTupleExactIdentityCount.archivedManifestExactInstanceCount.archivedManifestExactIdentityCount.semanticEquivalenceReviewedInstanceCount.semanticEquivalenceReviewedIdentityCount.compilerMappingEligibleInstanceCount.compilerMappingEligibleIdentityCount.remainingBlockedHistoricalInstanceCount.remainingBlockedHistoricalIdentityCount.historicalEvidenceRequiredInstanceCount.historicalEvidenceRequiredIdentityCount.completeExecutionReceiptInstanceCount.missingExecutionReceiptInstanceCount.missingExecutionReceiptIdentityCount.incompleteExecutionReceiptInstanceCount.incompleteExecutionReceiptIdentityCount.identityReferenceOnlyInstanceCount.embeddedCompleteDefinitionInstanceCount.presentationProjectionInstanceCount.presentationProjectionNodeCount.malformedIdentityInstanceCount.malformedIdentityCount.recoveredStudioSpecBodyCount.recoveredStudioSpecManifestIdentityCount.recoveredStudioSpecExecutionTupleCount.recoveredStudioSpecInstanceCount.partialReviewVerifiedExecutionTupleCount.partialReviewRejectedExecutionTupleCount".split(".");function Ue(e,t){let i=Oe(e,t);return Te(i,t,["id","contentHash","executionProfileId"]),{id:Be(i.id,`${t}.id`,void 0,512),contentHash:Be(i.contentHash,`${t}.contentHash`,Fe),executionProfileId:Be(i.executionProfileId,`${t}.executionProfileId`,void 0,512)}}function We(e,t){return ze(e,t).map((e,i)=>{let n=`${t}[${i}]`,o=Oe(e,n);return Te(o,n,["code","message"]),{code:Be(o.code,`${n}.code`,void 0,512),message:Be(o.message,`${n}.message`)}})}function Ze(e,t){let i=Oe(e,t),n=Be(i.status,`${t}.status`);return"missing"===n?(Te(i,t,["status","authorizesConversion"]),!1!==qe(i.authorizesConversion,`${t}.authorizesConversion`)&&De(t,"grants authority"),{status:n,authorizesConversion:!1}):("self_hash_valid_partial_evidence"!==n&&De(`${t}.status`),Te(i,t,["status","canonicalBodySha256","sourceIds","authorizesConversion"]),!1!==qe(i.authorizesConversion,`${t}.authorizesConversion`)&&De(t,"grants authority"),{status:n,canonicalBodySha256:Be(i.canonicalBodySha256,`${t}.canonicalBodySha256`,_e),sourceIds:Ke(i.sourceIds,`${t}.sourceIds`),authorizesConversion:!1})}function Ye(e,t){let i=Oe(e,t),n=Be(i.status,`${t}.status`);if("evidence_missing"===n||"unreviewed"===n)return Te(i,t,["status","authorizesConversion"]),!1!==qe(i.authorizesConversion,`${t}.authorizesConversion`)&&De(t,"grants authority"),{status:n,authorizesConversion:!1};"verified_partial_evidence"!==n&&"rejected_evidence"!==n&&De(`${t}.status`),Te(i,t,["status","reviewId","reviewer","reviewedAt","notes","reviewHash","authorizesConversion"]),!1!==qe(i.authorizesConversion,`${t}.authorizesConversion`)&&De(t,"grants authority");let o=Be(i.reviewedAt,`${t}.reviewedAt`,void 0,64);return(Number.isNaN(Date.parse(o))||!/(?:Z|[+-][0-9]{2}:[0-9]{2})$/u.test(o))&&De(`${t}.reviewedAt`),{status:n,reviewId:Be(i.reviewId,`${t}.reviewId`,void 0,512),reviewer:Be(i.reviewer,`${t}.reviewer`,void 0,256),reviewedAt:o,notes:Be(i.notes,`${t}.notes`,void 0,4096),reviewHash:Be(i.reviewHash,`${t}.reviewHash`,_e),authorizesConversion:!1}}function Qe(e,t){let i=Oe(e,t);Te(i,t,["status","specificationBodyCount","executionTupleCount","instanceCount","verifiedPartialReviewExecutionTupleCount","rejectedPartialReviewExecutionTupleCount","isManifestDefinitionOrConversionAuthority"]);let n=Be(i.status,`${t}.status`);return"partial_evidence_available"!==n&&"missing"!==n&&De(`${t}.status`),!1!==qe(i.isManifestDefinitionOrConversionAuthority,`${t}.isManifestDefinitionOrConversionAuthority`)&&De(t,"grants authority"),{status:n,specificationBodyCount:Ve(i.specificationBodyCount,`${t}.specificationBodyCount`),executionTupleCount:Ve(i.executionTupleCount,`${t}.executionTupleCount`),instanceCount:Ve(i.instanceCount,`${t}.instanceCount`),verifiedPartialReviewExecutionTupleCount:Ve(i.verifiedPartialReviewExecutionTupleCount,`${t}.verifiedPartialReviewExecutionTupleCount`),rejectedPartialReviewExecutionTupleCount:Ve(i.rejectedPartialReviewExecutionTupleCount,`${t}.rejectedPartialReviewExecutionTupleCount`),isManifestDefinitionOrConversionAuthority:!1}}function Xe(e,t){let i=`audit.identities[${t}]`,n=Oe(e,i);Te(n,i,["definitionId","libraryRevision","manifestContentHash","admissionIds","instanceCount","disposition","sourceEvidence","executionTuples","missingEvidence","safeNextActions"]);let o=Be(n.disposition,`${i}.disposition`);Ge.has(o)||De(`${i}.disposition`);let a=Oe(n.sourceEvidence,`${i}.sourceEvidence`);Te(a,`${i}.sourceEvidence`,["embeddedManifest","executionReceipt","presentationProjection","currentCatalog","registeredArchive","reviewedSemanticEquivalence","recoveredStudioSpecs"]);for(let e of["embeddedManifest","executionReceipt","presentationProjection","currentCatalog","registeredArchive","reviewedSemanticEquivalence"])Oe(a[e],`${i}.sourceEvidence.${e}`);return{definitionId:Le(n.definitionId,`${i}.definitionId`,void 0,512),libraryRevision:Le(n.libraryRevision,`${i}.libraryRevision`,Me),manifestContentHash:Le(n.manifestContentHash,`${i}.manifestContentHash`,_e),admissionIds:Ke(n.admissionIds,`${i}.admissionIds`),instanceCount:Ve(n.instanceCount,`${i}.instanceCount`),disposition:o,recoveredStudioSpecs:Qe(a.recoveredStudioSpecs,`${i}.sourceEvidence.recoveredStudioSpecs`),executionTuples:ze(n.executionTuples,`${i}.executionTuples`).map((e,t)=>function(e,t){let i=Oe(e,t);return Te(i,t,["admissionId","studioExecutionSpec","instanceCount","recoveredStudioSpec","manualReview"]),{admissionId:Le(i.admissionId,`${t}.admissionId`,void 0,1024),studioExecutionSpec:null===i.studioExecutionSpec?null:Ue(i.studioExecutionSpec,`${t}.studioExecutionSpec`),instanceCount:Ve(i.instanceCount,`${t}.instanceCount`),recoveredStudioSpec:Ze(i.recoveredStudioSpec,`${t}.recoveredStudioSpec`),manualReview:Ye(i.manualReview,`${t}.manualReview`)}}(e,`${i}.executionTuples[${t}]`)),missingEvidence:We(n.missingEvidence,`${i}.missingEvidence`),safeNextActions:We(n.safeNextActions,`${i}.safeNextActions`)}}function et(e,t){let i=`audit.partialStudioSpecEvidence.sources[${t}]`,n=Oe(e,i);return Te(n,i,["sourceId","artifactLabel","compressedSha256","compressedBytes","uncompressedSha256","uncompressedBytes","selector","candidateSpecificationCount"]),Be(n.compressedSha256,`${i}.compressedSha256`,_e),Ve(n.compressedBytes,`${i}.compressedBytes`),Be(n.uncompressedSha256,`${i}.uncompressedSha256`,_e),Ve(n.uncompressedBytes,`${i}.uncompressedBytes`),Be(n.selector,`${i}.selector`,void 0,512),Ve(n.candidateSpecificationCount,`${i}.candidateSpecificationCount`),{sourceId:Be(n.sourceId,`${i}.sourceId`,void 0,160),artifactLabel:Be(n.artifactLabel,`${i}.artifactLabel`,void 0,512)}}function tt(e,t){let i=`audit.partialStudioSpecEvidence.specifications[${t}]`,n=Oe(e,i);Te(n,i,["identity","canonicalBodySha256","specification","sourceIds","authorizesConversion"]),!1!==qe(n.authorizesConversion,`${i}.authorizesConversion`)&&De(i,"grants authority");let o=Ue(n.identity,`${i}.identity`),a=Oe(n.specification,`${i}.specification`);Te(a,`${i}.specification`,["schemaVersion","canonicalizationVersion","id","modelType","mode","executionProfileId","loaderModule","loaderAction","executionPath","pipelineClass","defaultRepo","roles","edges","bindings","autoFields","actions","contentHash"],["auxiliaryTerminalRoles"]),(1!==a.schemaVersion||a.id!==o.id||a.contentHash!==o.contentHash||a.executionProfileId!==o.executionProfileId)&&De(`${i}.specification`,"does not match its evidence identity");let s=ze(a.roles,`${i}.specification.roles`),r=ze(a.edges,`${i}.specification.edges`),c=ze(a.bindings,`${i}.specification.bindings`),d=ze(a.actions,`${i}.specification.actions`);return ze(a.autoFields,`${i}.specification.autoFields`),void 0!==a.auxiliaryTerminalRoles&&Ke(a.auxiliaryTerminalRoles,`${i}.specification.auxiliaryTerminalRoles`),{identity:o,canonicalBodySha256:Be(n.canonicalBodySha256,`${i}.canonicalBodySha256`,_e),sourceIds:Ke(n.sourceIds,`${i}.sourceIds`),modelType:Be(a.modelType,`${i}.specification.modelType`,void 0,512),mode:Be(a.mode,`${i}.specification.mode`,void 0,512),pipelineClass:Be(a.pipelineClass,`${i}.specification.pipelineClass`,void 0,512),roleCount:s.length,edgeCount:r.length,bindingCount:c.length,actionCount:d.length}}function it(e){let t=Oe(e,"response");Te(t,"response",["error","audit"]),!1!==t.error&&De("response.error","must be false");let i=Oe(t.audit,"audit");Te(i,"audit",["schemaVersion","kind","boundary","localEvidence","summary","partialStudioSpecEvidence","identities","contentHash"]),(4!==i.schemaVersion||"registered_cluster_manifest_recovery_audit"!==i.kind)&&De("audit","uses an unsupported contract");let n=Oe(i.boundary,"audit.boundary");Te(n,"audit.boundary",["readOnly","containsWorkflowPaths","containsInstanceIds","containsPromptOrParameterValues","historicalHashAloneIsNotExecutionAuthority","presentationProjectionIsNotDefinitionAuthority","recoveredStudioSpecEvidenceDoesNotAuthorizeConversion","compilerMappingPresenceAloneDoesNotAuthorizeConversion"]);let o=qe(n.readOnly,"audit.boundary.readOnly"),a=qe(n.containsWorkflowPaths,"audit.boundary.containsWorkflowPaths"),s=qe(n.containsInstanceIds,"audit.boundary.containsInstanceIds"),r=qe(n.containsPromptOrParameterValues,"audit.boundary.containsPromptOrParameterValues"),c=qe(n.historicalHashAloneIsNotExecutionAuthority,"audit.boundary.historicalHashAloneIsNotExecutionAuthority"),d=qe(n.presentationProjectionIsNotDefinitionAuthority,"audit.boundary.presentationProjectionIsNotDefinitionAuthority"),l=qe(n.recoveredStudioSpecEvidenceDoesNotAuthorizeConversion,"audit.boundary.recoveredStudioSpecEvidenceDoesNotAuthorizeConversion"),u=qe(n.compilerMappingPresenceAloneDoesNotAuthorizeConversion,"audit.boundary.compilerMappingPresenceAloneDoesNotAuthorizeConversion");(!0!==o||!1!==a||!1!==s||!1!==r||!0!==c||!0!==d||!0!==l||!0!==u)&&De("audit.boundary","weakens the read-only evidence boundary"),Oe(i.localEvidence,"audit.localEvidence");let p=Oe(i.summary,"audit.summary");Te(p,"audit.summary",Je);let m=Object.fromEntries(Je.map(e=>[e,Ve(p[e],`audit.summary.${e}`)]));(m.compilerMappingEligibleInstanceCount+m.remainingBlockedHistoricalInstanceCount!==m.legacyClusterInstanceCount-m.currentExecutionTupleExactInstanceCount-m.semanticEquivalenceReviewedInstanceCount||m.compilerMappingEligibleIdentityCount+m.remainingBlockedHistoricalIdentityCount!==m.definitionIdentityCount-m.currentExecutionTupleExactIdentityCount-m.semanticEquivalenceReviewedIdentityCount)&&De("audit.summary","has inconsistent historical compiler-mapping progress");let h=Oe(i.partialStudioSpecEvidence,"audit.partialStudioSpecEvidence");Te(h,"audit.partialStudioSpecEvidence",["status","authorizesConversion","sources","specifications","manualReviews"]);let f=Be(h.status,"audit.partialStudioSpecEvidence.status");"matched"!==f&&"no_matching_evidence"!==f&&De("audit.partialStudioSpecEvidence.status"),!1!==qe(h.authorizesConversion,"audit.partialStudioSpecEvidence.authorizesConversion")&&De("audit.partialStudioSpecEvidence","grants authority");let g=ze(h.sources,"audit.partialStudioSpecEvidence.sources").map(et),v=ze(h.specifications,"audit.partialStudioSpecEvidence.specifications").map(tt);ze(h.manualReviews,"audit.partialStudioSpecEvidence.manualReviews").forEach((e,t)=>Oe(e,`audit.partialStudioSpecEvidence.manualReviews[${t}]`));let y=ze(i.identities,"audit.identities").map(Xe);return m.definitionIdentityCount!==y.length&&De("audit.summary.definitionIdentityCount"),m.recoveredStudioSpecBodyCount!==v.length&&De("audit.summary.recoveredStudioSpecBodyCount"),{schemaVersion:4,kind:"registered_cluster_manifest_recovery_audit",contentHash:Be(i.contentHash,"audit.contentHash",_e),boundary:{readOnly:!0,containsWorkflowPaths:!1,containsInstanceIds:!1,containsPromptOrParameterValues:!1,historicalHashAloneIsNotExecutionAuthority:!0,presentationProjectionIsNotDefinitionAuthority:!0,recoveredStudioSpecEvidenceDoesNotAuthorizeConversion:!0,compilerMappingPresenceAloneDoesNotAuthorizeConversion:!0},summary:m,evidenceStatus:f,evidenceSources:g,specifications:v,identities:y}}function nt(){return R(`${g.serverAddress}/studio/composite-migrations/recovery-audit`,{timeoutMs:6e4,parse:it})}var ot=/^user-workflows\/([A-Za-z0-9_-]{1,96})\.json$/u;function at(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function st(e,t){return et)}function rt(...e){return e.join("\0")}function ct(e,t){return p(e)===p(t)}function dt(e){throw Error(e)}function lt(e){return"number"==typeof e&&Number.isFinite(e)&&e>0}function ut(e,t){return(!e.position||!Number.isFinite(e.position.x)||!Number.isFinite(e.position.y))&&dt(`${t} has no exact finite persisted position.`),{x:e.position.x,y:e.position.y}}function pt(e){let t=ot.exec(e);return t||dt(`Compiler candidate source path ${e} is not a saved workflow path.`),t[1]}async function mt(e,t){let i=pt(e);return R(`${g.serverAddress}/workflows/${encodeURIComponent(i)}`,{timeoutMs:12e4,signal:t,parse:t=>function(e,t){(!at(e)||"string"!=typeof e.id||!at(e.snapshot))&&dt(`Saved workflow ${t} is malformed.`);let i=pt(t);e.id!==i&&dt(`Saved workflow ${t} returned a different workflow id.`);let n=e.snapshot.nodes,o=e.snapshot.edges;return(!Array.isArray(n)||!Array.isArray(o))&&dt(`Saved workflow ${t} has no exact graph snapshot.`),{id:i,sourcePath:t,snapshot:{nodes:structuredClone(n),edges:structuredClone(o)}}}(t,e)})}function ht(e,t){return e.snapshot.nodes.filter(e=>e.id!==t&&(e.parentId===t||e.data?.huggingFaceClusterInstanceId===t)).sort((e,t)=>st(e.id,t.id))}function ft(e,t,i){e.id||dt("Legacy registered Cluster candidate has no instance id."),(!e.sourceSha256||!e.legacyCompositeHash)&&dt("Backend preview did not provide the exact source and legacy composite hashes.");let n=t.snapshot.nodes.filter(t=>t.id===e.id);1!==n.length&&dt(`Legacy Cluster ${e.id} does not resolve to exactly one saved root.`);let o=n[0],a=o.data?.huggingFaceClusterInstance;("cluster"!==o.type||"cluster"!==o.data?.type||"root"!==o.data?.huggingFaceClusterRole||!a||a.instanceId!==o.id)&&dt(`Saved node ${e.id} is not one exact legacy registered Cluster root.`),null!==a.structuralFork&&dt(`Legacy Cluster ${e.id} is structurally forked.`);let s=a.execution;s||dt(`Legacy Cluster ${e.id} has no execution receipt.`);let r=i.filter(({id:e})=>e===a.definition.id),c=r.find(e=>e.libraryRevision===a.definition.libraryRevision&&e.contentHash===a.definition.contentHash),d=e.semanticEquivalenceAuthority??null,l=e.historicalCompilerMappingAuthority??null;d&&l&&dt(`Legacy Cluster ${e.id} has conflicting historical review authorities.`);let u=d??l;if(!c){u||(r.length&&dt(`Legacy Cluster ${e.id} uses historical manifest ${a.definition.contentHash}; no reviewed archived definition or equivalence receipt is registered.`),dt(`Legacy Cluster ${e.id} references an unavailable catalog definition ${a.definition.id}.`));let t=u.historical;(t.manifestDefinitionId!==a.definition.id||t.libraryRevision!==a.definition.libraryRevision||t.manifestContentHash!==a.definition.contentHash||t.executionAdmissionId!==s.admissionId||!ct(t.studioExecutionSpec,s.studioExecutionSpec))&&dt(`Legacy Cluster ${e.id} does not match the reviewed historical compiler authority.`);let n=u.destination,o=i.filter(e=>e.id===n.manifestDefinitionId&&e.libraryRevision===n.libraryRevision&&e.contentHash===n.manifestContentHash);1!==o.length&&dt(`Legacy Cluster ${e.id} historical compiler destination is unavailable or ambiguous.`),c=o[0]}let p=u?u.destination.executionAdmissionId:s.admissionId,m=c.executionAdmissions.find(({id:e})=>e===p);m||dt(`Legacy Cluster ${e.id} references an unavailable execution admission.`),!u&&(m.studioExecutionSpec?.id!==s.studioExecutionSpec.id||m.studioExecutionSpec?.contentHash!==s.studioExecutionSpec.contentHash||m.studioExecutionSpec?.executionProfileId!==s.studioExecutionSpec.executionProfileId)&&dt(`Legacy Cluster ${e.id} has a stale Studio execution specification receipt.`);let h=N(c,m);return h||dt(`Legacy Cluster ${e.id} is not an exact current registered Block V2 route.`),(!lt(o.width)||!lt(o.height))&&dt(`Legacy Cluster ${e.id} has no exact positive persisted width and height.`),ut(o,`Legacy Cluster ${e.id}`),{candidate:e,workflow:t,root:o,children:ht(t,o.id),edges:t.snapshot.edges,instance:a,definition:c,route:h,semanticEquivalenceAuthority:d,historicalCompilerMappingAuthority:l}}function gt(e,t,i){let n=new Map(i.map(e=>[e.legacyNodeId,e.semanticNodeId])),o=new Set;return t.effectiveGraph.nodes.forEach(t=>{if(!t.semanticRole)return;o.has(t.semanticRole)&&dt(`Compiled graph has duplicate semantic role ${t.semanticRole}; legacy endpoint ownership is ambiguous.`),o.add(t.semanticRole);let i=_(e.root.id,`diffusers.cluster-execution:${encodeURIComponent(t.semanticRole)}`),a=n.get(i);a&&a!==t.nodeId&&dt(`Legacy execution endpoint ${i} maps to conflicting semantic nodes.`),n.set(i,t.nodeId)}),n}function vt(e,t,i,n){let o=("input"===t?e.effectiveInterface.boundary.inputs:e.effectiveInterface.boundary.outputs).filter(e=>function(e){return[e.binding,...e.mirrorBindings??[]].map(({nodeId:e,fieldOrPortId:t})=>({nodeId:e,fieldId:t}))}(e).some(e=>e.nodeId===i&&e.fieldId===n));return 1!==o.length&&dt(`Legacy ${t} endpoint ${i}.${n} does not resolve to exactly one V2 public port.`),o[0]}function yt(e,t,i){let n=gt(e,t,i),o=new Set([e.root.id,...e.children.map(({id:e})=>e)]),a=new Map;return Object.entries(e.root.data.params??{}).forEach(([t,i])=>{let n=i.fieldOptions?.huggingFaceClusterPortDirection;if("input"!==n&&"output"!==n)return;let o={direction:n,legacyNodeId:e.root.id,legacyPortId:t};a.set(rt(n,e.root.id,t),o)}),e.edges.forEach(e=>{let t=o.has(e.source),i=o.has(e.target);if(t!==i){if(t){e.sourceHandle||dt(`Crossing edge ${e.id} has no exact source handle.`);let t={direction:"output",legacyNodeId:e.source,legacyPortId:e.sourceHandle};a.set(rt(t.direction,t.legacyNodeId,t.legacyPortId),t)}if(i){e.targetHandle||dt(`Crossing edge ${e.id} has no exact target handle.`);let t={direction:"input",legacyNodeId:e.target,legacyPortId:e.targetHandle};a.set(rt(t.direction,t.legacyNodeId,t.legacyPortId),t)}}}),[...a.values()].map(i=>{let o=function(e,t,i,n,o){if(n!==e.root.id){let e=t.get(n);return e||dt(`Legacy boundary endpoint ${n}.${o} is not an owned projection.`),{semanticNodeId:e,fieldId:o}}let a=e.root.data.params?.[o],s=at(a?.fieldOptions)?a.fieldOptions:{};(s.huggingFaceClusterPortDirection!==i||"string"!=typeof s.huggingFaceClusterPortNodeId||"string"!=typeof s.huggingFaceClusterPortField)&&dt(`Legacy root port ${o} has incomplete ${i} metadata.`);let r=t.get(s.huggingFaceClusterPortNodeId);return r||dt(`Legacy root port ${o} targets a projection that is absent from the saved snapshot.`),{semanticNodeId:r,fieldId:s.huggingFaceClusterPortField}}(e,n,i.direction,i.legacyNodeId,i.legacyPortId);return{...i,v2PortId:vt(t,i.direction,o.semanticNodeId,o.fieldId).portId}}).sort((e,t)=>st(rt(e.direction,e.legacyNodeId,e.legacyPortId,e.v2PortId),rt(t.direction,t.legacyNodeId,t.legacyPortId,t.v2PortId)))}function xt(e,t,i){let n=e.effectiveInterface.controls.filter(e=>[e.binding,...e.mirrorBindings??[]].some(e=>e.nodeId===t&&e.fieldId===i));return 1===n.length?n[0]:null}function bt(e){return null===e?"null":Array.isArray(e)?"array":"object"==typeof e?"object":"number"==typeof e?Number.isInteger(e)?"integer":"number":typeof e}function It(e,t,i={}){if(null===e)return!0!==i.required||null===i.defaultValue;let n=y(t);return!!(Array.isArray(n)?n:[n]).some(t=>{if("string"!=typeof t)return!1;let i=t.trim().toLowerCase();return"string"===i||"text"===i||"str"===i||"dropdown"===i?"string"==typeof e:"bool"===i||"boolean"===i?"boolean"==typeof e:"int"===i||"integer"===i?"number"==typeof e&&Number.isInteger(e):"float"===i||"double"===i||"number"===i?"number"==typeof e&&Number.isFinite(e):"minimax_h3_references"===i||/^(?:list|array|sequence|tuple|set)(?:\[|$)/u.test(i)?Array.isArray(e):/^(?:dict|mapping|object|json)(?:\[|$)/u.test(i)?at(e):!!/^(?:image|video|audio|file|path|uri|url|media)(?:\b|_)/u.test(i)&&("string"==typeof e||at(e))})||void 0!==i.defaultValue&&null!==i.defaultValue&&function(e,t){let i=bt(e),n=bt(t);return i===n||"integer"===i&&"number"===n}(e,i.defaultValue)}function Ct(e,t,i){let n=gt(e,t,i),o=[],a={...t.values},s=new Map;(function(e){let t=[];return[e.root,...e.children].sort((e,t)=>st(e.id,t.id)).forEach(e=>{Object.entries(e.data.params??{}).sort(([e],[t])=>st(e,t)).forEach(([i,n])=>{Object.prototype.hasOwnProperty.call(n,"value")&&"output"!==n.display&&t.push({sourceKind:"node_param",sourceNodeId:e.id,sourceFieldId:i,value:structuredClone(n.value)})})}),Object.entries(e.instance.parameterOverrides).sort(([e],[t])=>st(e,t)).forEach(([i,n])=>t.push({sourceKind:"instance_parameter_override",sourceNodeId:e.root.id,sourceFieldId:i,value:structuredClone(n)})),Object.entries(e.instance.execution?.parameterOverrides??{}).sort(([e],[t])=>st(e,t)).forEach(([i,n])=>t.push({sourceKind:"execution_parameter_override",sourceNodeId:e.root.id,sourceFieldId:i,value:structuredClone(n)})),t})(e).forEach(i=>{let r=function(e,t,i,n){if("instance_parameter_override"===n.sourceKind||"execution_parameter_override"===n.sourceKind)return new Set([...t.effectiveInterface.controls.map(({controlId:e})=>e),...t.effectiveInterface.boundary.inputs.map(({portId:e})=>e)]).has(n.sourceFieldId)||dt(`Legacy override ${n.sourceKind}/${n.sourceFieldId} has no exact V2 input/control.`),{targetKind:"instance_value",targetValueId:n.sourceFieldId};if(n.sourceNodeId===e.root.id){let o=e.root.data.params[n.sourceFieldId]?.fieldOptions;if("input"===o?.huggingFaceClusterPortDirection&&"string"==typeof o.huggingFaceClusterPortNodeId&&"string"==typeof o.huggingFaceClusterPortField){let e=i.get(o.huggingFaceClusterPortNodeId),a=e?xt(t,e,o.huggingFaceClusterPortField):null;return a||dt(`Legacy root value ${n.sourceFieldId} has ambiguous input-port ownership.`),{targetKind:"instance_value",targetValueId:a.controlId}}return new Set([...t.effectiveInterface.controls.map(({controlId:e})=>e),...t.effectiveInterface.boundary.inputs.map(({portId:e})=>e)]).has(n.sourceFieldId)||dt(`Legacy root value ${n.sourceFieldId} has no exact V2 input/control.`),{targetKind:"instance_value",targetValueId:n.sourceFieldId}}let o=i.get(n.sourceNodeId);o||dt(`Legacy value ${n.sourceNodeId}.${n.sourceFieldId} is outside the owned projection.`);let a=function(e){let t=e?.fieldOptions?.huggingFaceClusterBinding;return!at(t)||1!==t.schemaVersion||"string"!=typeof t.source||"instance_input"!==t.persistence&&"execution_parameter"!==t.persistence&&"sealed"!==t.persistence||void 0!==t.input&&"string"!=typeof t.input?null:t}(e.children.find(({id:e})=>e===n.sourceNodeId).data.params[n.sourceFieldId]);if("instance_input"===a?.persistence||"execution_parameter"===a?.persistence){let e=xt(t,o,n.sourceFieldId);return e||dt(`Legacy mutable field ${n.sourceNodeId}.${n.sourceFieldId} has no exact V2 control.`),{targetKind:"instance_value",targetValueId:e.controlId}}let s=t.effectiveGraph.nodes.find(({nodeId:e})=>e===o),r=(at(s?.data.params)?s.data.params:{})[n.sourceFieldId];return(!at(r)||!Object.prototype.hasOwnProperty.call(r,"value"))&&dt(`Legacy static field ${n.sourceNodeId}.${n.sourceFieldId} has no exact V2 graph parameter.`),{targetKind:"graph_param",targetNodeId:o,targetFieldId:n.sourceFieldId}}(e,t,n,i);if("instance_value"===r.targetKind){!function(e,t,i){let n=e.effectiveInterface.controls.filter(({controlId:e})=>e===t),o=e.effectiveInterface.boundary.inputs.filter(({portId:e})=>e===t);n.length+o.length===0&&dt(`Legacy value ${t} does not target a declared V2 input/control.`),n.forEach(e=>{e.sealed&&!ct(i,e.defaultValue)&&dt(`Legacy value ${t} attempts to rewrite a sealed V2 control.`),It(i,e.valueType,{...void 0===e.defaultValue?{}:{defaultValue:e.defaultValue},required:e.required})||dt(`Legacy value ${t} is incompatible with V2 control type ${String(e.valueType)}.`)}),o.forEach(e=>{It(i,e.valueType,{required:e.required})||dt(`Legacy value ${t} is incompatible with V2 input type ${String(e.valueType)}.`)})}(t,r.targetValueId,i.value);let e=s.get(r.targetValueId);void 0!==e&&!ct(e,i.value)&&dt(`Legacy sources disagree for V2 input/control ${r.targetValueId}.`),s.set(r.targetValueId,i.value),a[r.targetValueId]=i.value}else{let e=t.effectiveGraph.nodes.find(({nodeId:e})=>e===r.targetNodeId).data.params[r.targetFieldId];(!at(e)||!ct(e.value,i.value))&&dt(`Legacy static value ${i.sourceNodeId}.${i.sourceFieldId} differs from the pinned definition.`)}o.push({sourceKind:i.sourceKind,sourceNodeId:i.sourceNodeId,sourceFieldId:i.sourceFieldId,...r})}),o.sort((e,t)=>st(rt(e.sourceKind,e.sourceNodeId,e.sourceFieldId,e.targetKind),rt(t.sourceKind,t.sourceNodeId,t.sourceFieldId,t.targetKind)));let r=t.effectiveInterface.boundary.inputs.some(({portId:e})=>Object.prototype.hasOwnProperty.call(a,e))||t.effectiveInterface.controls.some(e=>Object.prototype.hasOwnProperty.call(a,e.controlId)&&!ct(a[e.controlId],e.defaultValue));return{mappings:o,instance:l({...t,values:a,customization:{...t.customization,state:r?"parameters_changed":"unchanged"}})}}function wt(e,t,i){let n=gt(e,t,i),o=structuredClone(t.previewStates),a=[];return function(e){return[e.root,...e.children].flatMap(e=>Object.entries(e.data.params??{}).flatMap(([t,i])=>"output"!==i.display||null===i.value||void 0===i.value?[]:(("string"!=typeof i.value||!i.value)&&dt(`Legacy output ${e.id}.${t} is not a persistable media reference.`),[{node:e,fieldId:t,mediaReference:i.value}])))}(e).forEach(({node:t,fieldId:i,mediaReference:s})=>{let r,c=i;if(t.id===e.root.id){let e=t.data.params[i]?.fieldOptions,o="string"==typeof e?.huggingFaceClusterPreviewSourceNodeId?e.huggingFaceClusterPreviewSourceNodeId:"output"===e?.huggingFaceClusterPortDirection&&"string"==typeof e.huggingFaceClusterPortNodeId?e.huggingFaceClusterPortNodeId:null,a="string"==typeof e?.huggingFaceClusterPreviewSourceField?e.huggingFaceClusterPreviewSourceField:"output"===e?.huggingFaceClusterPortDirection&&"string"==typeof e.huggingFaceClusterPortField?e.huggingFaceClusterPortField:null;(!o||!a)&&dt(`Legacy root output ${i} has no exact preview-source metadata.`),r=n.get(o),c=a}else r=n.get(t.id);r||dt(`Legacy output ${t.id}.${i} targets an absent projection.`);let d=o.map((e,t)=>({state:e,index:t})).filter(({state:e})=>e.binding.nodeId===r&&e.binding.outputPortId===c);1!==d.length&&dt(`Legacy output ${t.id}.${i} does not resolve to exactly one V2 preview binding.`);let l=d[0];l.state.mediaReference&&l.state.mediaReference!==s&&dt(`Legacy outputs conflict for V2 preview ${r}.${c}.`),o[l.index]={...l.state,mediaReference:s,status:"complete"},a.push({sourceNodeId:t.id,sourceFieldId:i,targetNodeId:r,targetOutputPortId:c})}),a.sort((e,t)=>st(rt(e.sourceNodeId,e.sourceFieldId,e.targetNodeId,e.targetOutputPortId),rt(t.sourceNodeId,t.sourceFieldId,t.targetNodeId,t.targetOutputPortId))),{mappings:a,instance:l({...t,previewStates:o})}}function $t(e,t,i){let n={...t.presentation.internalLayout};return i.forEach(({legacyNodeId:t,semanticNodeId:i})=>{let o=e.children.find(({id:e})=>e===t);n[i]={...ut(o,`Legacy projection ${t}`),...lt(o.width)?{width:o.width}:{},...lt(o.height)?{height:o.height}:{}}}),l({...t,presentation:{...t.presentation,internalLayout:n}})}async function Nt(e,t){let i=await O(e.definition,function(e){if(!e.semanticEquivalenceAuthority&&!e.historicalCompilerMappingAuthority)return e.root;let t=e.definition.executionAdmissions.find(({id:t})=>t===e.route.admissionId);t||dt(`Current destination admission ${e.route.admissionId} is unavailable.`);let i={...u(t.studioMode,e.definition.pipelineClass),modelType:e.definition.pipelineClass,mode:t.studioMode},{parameterOverrides:n,executionParameterOverrides:o}=w(e.definition,t,i),a=e.definition.suggestedInputs?.values??{},s=Object.fromEntries(Object.entries(n).filter(([e,t])=>!(""===t&&Object.prototype.hasOwnProperty.call(a,e)))),r=h(e.definition,e.root.id,ut(e.root,`Legacy Cluster ${e.root.id}`),{...a,...s},t.id,o);r.width=e.root.width,r.height=e.root.height;let c=r.data.huggingFaceClusterInstance;return c?.execution||dt(`Current destination compiler root ${e.root.id} has no execution receipt.`),r.data.huggingFaceClusterInstance={...c,presentation:{...c.presentation,expanded:e.instance.presentation.expanded}},r}(e),{timeoutMs:t}),n=[...i.definition.contentHash===e.route.compiledDefinitionContentHash?[]:["definition content hash"],...i.instance.definitionSnapshot.source.executionAdmissionId===e.route.admissionId?[]:["execution admission"],...i.instance.authorities.length?["instance authorities"]:[],...i.instance.effectiveGraph.graphHash===i.definition.graph.graphHash?[]:["effective graph"]];n.length&&dt(`Compiled registered definition for ${e.root.id} is stale or structurally customized (${n.join(", ")}).`);let o=e.semanticEquivalenceAuthority??e.historicalCompilerMappingAuthority;o&&(ct(o.destination,{manifestDefinitionId:e.route.definitionId,libraryRevision:e.route.libraryRevision,manifestContentHash:e.route.definitionContentHash,executionAdmissionId:e.route.admissionId,blockDefinitionId:i.definition.definitionId,blockDefinitionContentHash:i.definition.contentHash,blockDefinitionCanonicalSha256:e.route.compiledDefinitionCanonicalSha256,executionGraphHash:i.definition.graph.graphHash,interfaceHash:D({boundary:i.definition.boundary,controls:i.definition.controls})})||dt(`Reviewed historical compiler authority for ${e.root.id} is stale for the compiled BlockDefinitionV2 graph or interface.`));let a=function(e,t){let i=new Map;t.effectiveGraph.nodes.forEach(e=>{let t=e.semanticRole;t&&i.set(t,[...i.get(t)??[],e.nodeId])});let n=e.children.map(t=>{let n=t.data,o=n?.huggingFaceClusterExecutionRole;("execution"!==n?.huggingFaceClusterRole||n.huggingFaceClusterInstanceId!==e.root.id||n.huggingFaceClusterExecutionAdmissionId!==e.instance.execution?.admissionId||n.huggingFaceClusterExecutionSpecId!==e.instance.execution.studioExecutionSpec.id||!o)&&dt(`Legacy projection ${t.id} has incomplete or stale execution ownership.`);let a=i.get(o)??[];return 1!==a.length&&dt(`Legacy projection ${t.id} cannot be mapped one-to-one to semantic role ${o}.`),{legacyNodeId:t.id,semanticNodeId:a[0]}}),o=n.map(({semanticNodeId:e})=>e);return new Set(o).size!==o.length&&dt(`Legacy Cluster ${e.root.id} would merge multiple projection nodes.`),n.sort((e,t)=>st(rt(e.legacyNodeId,e.semanticNodeId),rt(t.legacyNodeId,t.semanticNodeId)))}(e,i.instance),s=$t(e,i.instance,a),r=Ct(e,s,a);s=r.instance;let c=wt(e,s,a);s=c.instance;let d=new Set([e.root.id,...e.children.map(({id:e})=>e)]),l=e.edges.filter(e=>d.has(e.source)&&d.has(e.target)).map(t=>(t.id||dt(`Legacy Cluster ${e.root.id} has an internal edge without an id.`),t.id)).sort(st);return new Set(l).size!==l.length&&dt(`Legacy Cluster ${e.root.id} has duplicate internal edge ids.`),{legacyInstanceId:e.root.id,legacyCompositeHash:e.candidate.legacyCompositeHash,admissionId:e.route.admissionId,compiledDefinitionContentHash:e.route.compiledDefinitionContentHash,compiledDefinitionCanonicalSha256:e.route.compiledDefinitionCanonicalSha256,blockInstanceV2:s,ownedNodeMappings:a,portMappings:yt(e,s,a),valueMappings:r.mappings,previewMappings:c.mappings,absorbedInternalEdgeIds:l,...e.semanticEquivalenceAuthority?{semanticEquivalenceReceipt:{receiptId:e.semanticEquivalenceAuthority.receiptId,receiptHash:e.semanticEquivalenceAuthority.receiptHash}}:{},...e.historicalCompilerMappingAuthority?{historicalCompilerMapping:{mappingId:e.historicalCompilerMappingAuthority.mappingId,mappingHash:e.historicalCompilerMappingAuthority.mappingHash}}:{}}}function St(e,t){let i=e instanceof Error?e.message:String(e),n=i.includes("historical manifest")?"historical_manifest_unavailable":i.includes("no execution receipt")?"execution_receipt_missing":i.includes("source and legacy composite hashes")?"candidate_receipt_missing":i.includes("saved root")||i.includes("projection")||i.includes("ambiguous")?"legacy_snapshot_ambiguous":i.includes("route")||i.includes("admission")||i.includes("catalog definition")?"registered_route_unavailable":i.includes("workflow")?"workflow_unavailable":"compiler_failed";return{sourcePath:t.sourcePath,instanceId:t.id,code:n,message:i}}async function jt(e){let t=k(),i=e.candidates.filter(({kind:e,status:t})=>"legacy_registered_cluster_instance"===e&&"blocked"===t).sort((e,t)=>st(rt(e.sourcePath,e.id??""),rt(t.sourcePath,t.id??""))),n=await async function(){let e=m.getState(),t=E.getState();await Promise.all([e.loaded?Promise.resolve():e.fetchLibrary(),Object.keys(t.nodesRegistry).length?Promise.resolve():t.fetchNodes(),t.studioModelCapabilities.length?Promise.resolve():t.fetchStudioModelCapabilities()]);let i=m.getState();return i.library||dt(i.error||"The Hugging Face node library is unavailable."),Object.keys(E.getState().nodesRegistry).length||dt("The backend node registry is unavailable."),i.library}();if(n.definitions.some(({provider:e})=>"diffusers"===e)){let e=x.getState();e.loaded||await e.fetchSnapshot()}P(t,{includeForm:!1})||dt("The active workflow changed while compiler dependencies were loading. Generate a fresh supplement.");let o=null,a=[],s=new Map,r=0;for(let c of i){if(e.signal?.aborted)throw new DOMException("Compiler supplement generation was cancelled.","AbortError");P(t,{includeForm:!1})||dt("The active workflow changed during compiler supplement generation. Generate a fresh supplement.");try{o?.sourcePath!==c.sourcePath&&(o=await mt(c.sourcePath,e.signal));let i=o;P(t,{includeForm:!1})||dt("The active workflow changed while a saved workflow was loading. Generate a fresh supplement.");let a=await Nt(ft(c,i,n.definitions),e.timeoutMs);P(t,{includeForm:!1})||dt("The active workflow changed during compiler supplement generation. Generate a fresh supplement."),s.set(c.sourcePath,[...s.get(c.sourcePath)??[],a])}catch(e){if(!P(t,{includeForm:!1}))throw e;a.push(St(e,c))}finally{r+=1,e.onProgress?.({completed:r,total:i.length,sourcePath:c.sourcePath,instanceId:c.id})}}let c=ge({schemaVersion:1,kind:"registered_cluster_v2_compiler_supplement",compilerOutputs:[...s.entries()].sort(([e],[t])=>st(e,t)).map(([e,t])=>({sourcePath:e,sourceSha256:i.find(({sourcePath:t})=>t===e).sourceSha256,conversions:t.sort((e,t)=>st(e.legacyInstanceId,t.legacyInstanceId))}))});return(new TextEncoder).encode(JSON.stringify(c)).byteLength>33554432&&dt("Generated compiler supplement exceeds the reviewed 32 MiB request limit."),{supplement:c,diagnostics:a.sort((e,t)=>st(rt(e.sourcePath,e.instanceId??"",e.code),rt(t.sourcePath,t.instanceId??"",t.code))),candidateCount:i.length,conversionCount:c.compilerOutputs.reduce((e,t)=>e+t.conversions.length,0)}}var kt=r();function Ht(e){return e.replace(/_/g," ")}function Pt(e){return"applied"===e||"rolled_back"===e?"success":"interrupted"===e?"warning":"error"}function At({audit:e,error:t,loading:i,onRefresh:o}){let a=e?.identities.filter(e=>"partial_evidence_available"===e.recoveredStudioSpecs.status)??[];return(0,kt.jsxs)("div",{className:"grid gap-3","data-testid":"legacy-cluster-recovery-evidence-panel",children:[(0,kt.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,kt.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"This read-only audit reports historical recovery evidence and checked-in compiler-mapping coverage. It does not compile, convert, execute, approve, or mutate a Cluster by itself. Mapping-ready instances still require an exact compiler supplement, reviewed preview, and explicit apply confirmation."}),(0,kt.jsx)($,{size:"compact",tone:"secondary",icon:(0,kt.jsx)(n,{size:14}),loading:i,disabled:i,onClick:o,"data-testid":"legacy-cluster-recovery-evidence-refresh",children:"Refresh evidence"})]}),t?(0,kt.jsxs)("div",{className:"flex items-start gap-2 rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/5 p-2 text-xs text-modiff-red",children:[(0,kt.jsx)(c,{size:14,className:"mt-0.5 shrink-0"}),(0,kt.jsx)("span",{className:"break-words",children:t})]}):null,e?(0,kt.jsxs)(kt.Fragment,{children:[(0,kt.jsxs)("div",{className:"flex flex-wrap gap-2","data-testid":"legacy-cluster-recovery-evidence-summary",children:[(0,kt.jsx)(v,{label:`${e.summary.legacyClusterInstanceCount} saved Clusters`,tone:"neutral"}),(0,kt.jsx)(v,{label:`${e.summary.compilerMappingEligibleInstanceCount} mapping-ready instances`,tone:e.summary.compilerMappingEligibleInstanceCount?"success":"neutral"}),(0,kt.jsx)(v,{label:`${e.summary.compilerMappingEligibleIdentityCount} mapping-ready ${1===e.summary.compilerMappingEligibleIdentityCount?"identity":"identities"}`,tone:e.summary.compilerMappingEligibleIdentityCount?"success":"neutral"}),(0,kt.jsx)(v,{label:`${e.summary.remainingBlockedHistoricalInstanceCount} historical still blocked`,tone:e.summary.remainingBlockedHistoricalInstanceCount?"warning":"success"}),(0,kt.jsx)(v,{label:`${e.summary.remainingBlockedHistoricalIdentityCount} blocked ${1===e.summary.remainingBlockedHistoricalIdentityCount?"identity":"identities"}`,tone:e.summary.remainingBlockedHistoricalIdentityCount?"warning":"success"}),(0,kt.jsx)(v,{label:`${e.summary.recoveredStudioSpecBodyCount} recovered bodies`,tone:"neutral"}),(0,kt.jsx)(v,{label:`${e.summary.recoveredStudioSpecManifestIdentityCount} identities`,tone:"neutral"}),(0,kt.jsx)(v,{label:`${e.summary.recoveredStudioSpecExecutionTupleCount} execution tuples`,tone:"neutral"}),(0,kt.jsx)(v,{label:`${e.summary.recoveredStudioSpecInstanceCount} covered instances`,tone:"neutral"}),(0,kt.jsx)(v,{label:`${e.summary.partialReviewVerifiedExecutionTupleCount} partially reviewed`,tone:e.summary.partialReviewVerifiedExecutionTupleCount?"warning":"neutral"})]}),(0,kt.jsx)("div",{className:"rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-2 text-xs leading-5 text-modiff-text",children:"Read-only boundary: no workflow paths, instance IDs, prompts, or parameter values are included. A valid historical Studio-spec self-hash is partial evidence only; it is not manifest, interface, artifact, or execution authority. A checked-in compiler mapping covers only its exact archived tuple and does not, by its presence alone, authorize conversion or workflow mutation."}),(0,kt.jsxs)("section",{className:"grid gap-1.5",children:[(0,kt.jsxs)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:["Recovered Studio bodies (",e.specifications.length,")"]}),e.specifications.length?(0,kt.jsx)("div",{className:"grid max-h-72 gap-2 overflow-auto","data-testid":"legacy-cluster-recovered-specifications",children:e.specifications.map(e=>(0,kt.jsxs)("article",{className:"grid gap-1 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs",children:[(0,kt.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,kt.jsxs)("div",{className:"min-w-0",children:[(0,kt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.identity.id}),(0,kt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.modelType," · ",e.mode," · ",e.identity.executionProfileId]})]}),(0,kt.jsx)(v,{label:`${e.roleCount} roles · ${e.edgeCount} edges`,tone:"neutral"})]}),(0,kt.jsxs)("p",{className:"break-all font-mono text-modiff-subtle-text",children:[e.identity.contentHash," · ",e.canonicalBodySha256]}),(0,kt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.bindingCount," bindings · ",e.actionCount," dynamic actions · sources:"," ",e.sourceIds.join(", ")]})]},e.canonicalBodySha256))}):(0,kt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No exact local capture contains a matching body."})]}),(0,kt.jsxs)("section",{className:"grid gap-1.5",children:[(0,kt.jsxs)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:["Historical identities with partial evidence (",a.length,")"]}),a.length?(0,kt.jsx)("div",{className:"grid max-h-80 gap-2 overflow-auto","data-testid":"legacy-cluster-recovered-identities",children:a.map(e=>(0,kt.jsxs)("article",{className:"grid gap-1.5 rounded-modiff-compact border border-hf-orange/30 bg-modiff-bg p-2 text-xs",children:[(0,kt.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,kt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.definitionId??"Malformed identity"}),(0,kt.jsx)(v,{label:`${e.instanceCount} instances`,tone:"warning"})]}),(0,kt.jsxs)("p",{className:"break-all font-mono text-modiff-subtle-text",children:[e.libraryRevision??"missing revision"," ·"," ",e.manifestContentHash??"missing hash"]}),e.executionTuples.filter(e=>"self_hash_valid_partial_evidence"===e.recoveredStudioSpec.status).map((e,t)=>(0,kt.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border p-2",children:[(0,kt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.studioExecutionSpec?.id??"Missing Studio receipt"}),(0,kt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.instanceCount," instances · review ",Ht(e.manualReview.status)," · conversion authority: none"]})]},`${e.admissionId??"missing"}:${e.studioExecutionSpec?.contentHash??t}`)),(0,kt.jsxs)("p",{className:"text-hf-orange",children:["Still missing: ",e.missingEvidence.map(({code:e})=>Ht(e)).join("; ")]})]},`${e.definitionId}:${e.libraryRevision}:${e.manifestContentHash}`))}):(0,kt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No historical identity has recovered partial evidence."})]}),(0,kt.jsxs)("p",{className:"break-all font-mono text-xs text-modiff-subtle-text",children:["Audit: ",e.contentHash]})]}):i||t?null:(0,kt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No historical recovery audit is available."})]})}function Et({preview:e}){let t=[...new Map(e.candidates.flatMap(e=>{let t=e.compilerReceipt?.semanticEquivalenceAuthority??e.semanticEquivalenceAuthority;return t?[[t.receiptHash,t]]:[]})).values()],i=[...new Map(e.candidates.flatMap(e=>{let t=e.compilerReceipt?.historicalCompilerMappingAuthority??e.historicalCompilerMappingAuthority;return t?[[t.mappingHash,t]]:[]})).values()];return(0,kt.jsxs)("div",{className:"grid gap-3","data-testid":"composite-migration-preview-details",children:[(0,kt.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,kt.jsx)(v,{label:`${e.summary.targetFileCount} target files`,tone:"neutral"}),(0,kt.jsx)(v,{label:`${e.summary.convertibleCandidateCount} convertible`,tone:e.summary.convertibleCandidateCount?"success":"neutral"}),e.compilerSupplement?(0,kt.jsx)(v,{label:`${e.summary.registeredClusterConvertibleCount??0} registered Clusters compiled`,tone:e.compilerSupplement.provided?"success":"neutral"}):null,(0,kt.jsx)(v,{label:`${e.summary.blockedCandidateCount} blocked`,tone:e.summary.blockedCandidateCount?"warning":"success"})]}),(0,kt.jsxs)("div",{className:"grid gap-1 text-xs text-modiff-subtle-text",children:[(0,kt.jsxs)("p",{className:"break-all",children:["Preview: ",(0,kt.jsx)("span",{className:"font-mono text-modiff-text",children:e.migrationId})]}),(0,kt.jsxs)("p",{className:"break-all",children:["Plan hash: ",(0,kt.jsx)("span",{className:"font-mono text-modiff-text",children:e.planHash})]}),e.compilerSupplement?.contentHash?(0,kt.jsxs)("p",{className:"break-all",children:["Compiler supplement:"," ",(0,kt.jsx)("span",{className:"font-mono text-modiff-text",children:e.compilerSupplement.contentHash})]}):null]}),(0,kt.jsxs)("section",{className:"grid gap-1.5",children:[(0,kt.jsx)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Exact files that would change"}),e.targets.length?(0,kt.jsx)("ul",{className:"max-h-44 space-y-1 overflow-auto rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:e.targets.map(e=>(0,kt.jsx)("li",{className:"break-all font-mono text-xs text-modiff-text",children:e.sourcePath},e.sourcePath))}):(0,kt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No safe conversion target is present."})]}),t.length?(0,kt.jsxs)("section",{className:"grid gap-1.5","data-testid":"composite-migration-equivalence-diffs",children:[(0,kt.jsx)("h4",{className:"text-xs font-bold uppercase text-hf-yellow",children:"Reviewed historical equivalence (preview only)"}),(0,kt.jsx)("div",{className:"grid max-h-72 gap-2 overflow-auto",children:t.map(e=>(0,kt.jsxs)("article",{className:"grid gap-1 rounded-modiff-compact border border-hf-yellow/30 bg-hf-yellow/5 p-2 text-xs",children:[(0,kt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.receiptId}),(0,kt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Historical manifest: ",(0,kt.jsx)("span",{className:"font-mono",children:e.historical.manifestContentHash})]}),(0,kt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Historical graph/interface:"," ",(0,kt.jsxs)("span",{className:"font-mono",children:[e.historical.executionGraphHash," · ",e.historical.interfaceHash]})]}),(0,kt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Destination definition:"," ",(0,kt.jsx)("span",{className:"font-mono",children:e.destination.blockDefinitionContentHash})]}),(0,kt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Destination graph/interface:"," ",(0,kt.jsxs)("span",{className:"font-mono",children:[e.destination.executionGraphHash," · ",e.destination.interfaceHash]})]}),(0,kt.jsxs)("p",{className:"text-modiff-subtle-text",children:["Reviewed by ",e.review.issuer," at ",e.review.reviewedAt,": ",e.review.notes]}),(0,kt.jsx)("p",{className:"text-hf-yellow",children:"This checked-in review enables exact compiler validation only; it does not apply or mutate a workflow."})]},e.receiptHash))})]}):null,i.length?(0,kt.jsxs)("section",{className:"grid gap-1.5","data-testid":"composite-migration-archived-compiler-mappings",children:[(0,kt.jsx)("h4",{className:"text-xs font-bold uppercase text-hf-yellow",children:"Reviewed archived compiler mapping (preview only)"}),(0,kt.jsx)("div",{className:"grid max-h-72 gap-2 overflow-auto",children:i.map(e=>(0,kt.jsxs)("article",{className:"grid gap-1 rounded-modiff-compact border border-hf-yellow/30 bg-hf-yellow/5 p-2 text-xs",children:[(0,kt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.mappingId}),(0,kt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Archived manifest: ",(0,kt.jsx)("span",{className:"font-mono",children:e.historical.manifestContentHash})]}),(0,kt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Archive record: ",(0,kt.jsx)("span",{className:"font-mono",children:e.historical.archivedDefinitionRecordHash})]}),(0,kt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Destination definition:"," ",(0,kt.jsx)("span",{className:"font-mono",children:e.destination.blockDefinitionContentHash})]}),(0,kt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Destination graph/interface:"," ",(0,kt.jsxs)("span",{className:"font-mono",children:[e.destination.executionGraphHash," · ",e.destination.interfaceHash]})]}),(0,kt.jsxs)("p",{className:"text-modiff-subtle-text",children:["Reviewed by ",e.review.issuer," at ",e.review.reviewedAt,": ",e.review.notes]}),(0,kt.jsx)("p",{className:"text-hf-yellow",children:"This maps one exact archived body to one exact compiler destination. Per-instance preservation, explicit Apply, byte backup, and rollback are still required."})]},e.mappingHash))})]}):null,e.blocked.length?(0,kt.jsxs)("section",{className:"grid gap-1.5","data-testid":"composite-migration-blockers",children:[(0,kt.jsx)("h4",{className:"text-xs font-bold uppercase text-hf-orange",children:"Blocked candidates remain unchanged"}),(0,kt.jsx)("ul",{className:"max-h-44 space-y-2 overflow-auto rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-2",children:e.blocked.map((e,t)=>(0,kt.jsxs)("li",{className:"text-xs",children:[(0,kt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.sourcePath}),(0,kt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.id?`${e.id}: `:"",e.reason]})]},`${e.sourcePath}:${e.kind}:${e.id??t}`))})]}):null,e.inventoryIssues.length?(0,kt.jsxs)("section",{className:"grid gap-1.5",children:[(0,kt.jsx)("h4",{className:"text-xs font-bold uppercase text-hf-orange",children:"Inventory issues"}),(0,kt.jsx)("ul",{className:"max-h-36 space-y-1 overflow-auto text-xs text-modiff-subtle-text",children:e.inventoryIssues.map((e,t)=>(0,kt.jsxs)("li",{children:[(0,kt.jsx)("span",{className:"break-all font-mono text-modiff-text",children:e.sourcePath}),": ",e.message]},`${e.sourcePath}:${e.path}:${e.code}:${t}`))})]}):null]})}function Rt({allowBlockedCandidates:e,busy:t,confirmation:i,error:n,onAllowBlockedCandidatesChange:o,onApply:a,onCancel:s,onConfirmationChange:r,preview:c}){let d=le(c,i,e);return(0,kt.jsxs)("div",{className:"grid gap-4","data-testid":"composite-migration-apply-review",children:[(0,kt.jsx)("div",{className:"rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-3 text-xs leading-5 text-modiff-text",children:"This action creates exact backups and replaces only the listed safe User Node or workflow files. It never deletes, renames, or merges records. Blocked candidates remain unchanged."}),(0,kt.jsx)(Et,{preview:c}),c.summary.blockedCandidateCount>0?(0,kt.jsx)(f,{checked:e,onCheckedChange:o,label:`Apply only the ${c.summary.targetFileCount} safe target files and leave all ${c.summary.blockedCandidateCount} blocked candidates unchanged.`,"data-testid":"composite-migration-allow-blocked"}):null,(0,kt.jsx)(b,{label:(0,kt.jsxs)("span",{children:["Type ",(0,kt.jsx)("code",{className:"font-mono text-hf-yellow",children:B})]}),required:!0,error:n??void 0,children:(0,kt.jsx)(A,{autoComplete:"off",spellCheck:!1,value:i,onChange:e=>r(e.currentTarget.value),"data-testid":"composite-migration-apply-confirmation"})}),(0,kt.jsxs)("div",{className:"flex flex-wrap justify-end gap-2 border-t border-modiff-border pt-3",children:[(0,kt.jsx)($,{disabled:t,onClick:s,children:"Cancel"}),(0,kt.jsx)($,{tone:"primary",loading:t,disabled:t||!d,onClick:a,"data-testid":"composite-migration-apply",children:"Apply reviewed migration"})]})]})}function _t({busyId:e,migrations:t,onInspectRollback:i}){return t.length?(0,kt.jsx)("div",{className:"grid max-h-72 gap-2 overflow-auto","data-testid":"composite-migration-recovery-list",children:t.map(t=>(0,kt.jsxs)("section",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,kt.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,kt.jsxs)("div",{className:"min-w-0",children:[(0,kt.jsx)("p",{className:"truncate font-mono text-xs text-modiff-text",title:t.migrationId,children:t.migrationId}),(0,kt.jsxs)("p",{className:"text-xs text-modiff-subtle-text",children:[t.targets.length," targets · ",Ht(t.journalState)," journal"]})]}),(0,kt.jsx)(v,{label:Ht(t.effectiveState),tone:Pt(t.effectiveState)})]}),(0,kt.jsx)("ul",{className:"space-y-1",children:t.targets.map(e=>(0,kt.jsxs)("li",{className:"flex min-w-0 items-start justify-between gap-2 text-xs",children:[(0,kt.jsx)("span",{className:"break-all font-mono text-modiff-text",children:e.sourcePath}),(0,kt.jsx)("span",{className:I("shrink-0 font-semibold","conflict"===e.state||"missing"===e.state?"text-modiff-red":"applied"===e.state?"text-modiff-green":"text-modiff-subtle-text"),children:e.state})]},e.sourcePath))}),t.rollbackAvailable?(0,kt.jsx)("div",{children:(0,kt.jsx)($,{size:"compact",tone:"secondary",icon:(0,kt.jsx)(s,{size:14}),loading:e===t.migrationId,disabled:!!e,onClick:()=>i(t),"data-testid":`composite-migration-inspect-${t.migrationId}`,children:"Review rollback"})}):null]},t.migrationId))}):(0,kt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No migration recovery journals are present."})}function Mt({busy:e,confirmation:t,error:i,onCancel:n,onConfirmationChange:o,onRollback:a,status:s}){return(0,kt.jsxs)("div",{className:"grid gap-4","data-testid":"composite-migration-rollback-review",children:[(0,kt.jsx)("div",{className:"rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-3 text-xs leading-5 text-modiff-text",children:"Rollback restores the exact backups for this journal. It refuses missing or independently changed files and has no force option."}),(0,kt.jsxs)("section",{className:"grid gap-1.5",children:[(0,kt.jsx)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Exact files to restore"}),(0,kt.jsx)("ul",{className:"max-h-44 space-y-1 overflow-auto rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:s.targets.map(e=>(0,kt.jsxs)("li",{className:"break-all font-mono text-xs text-modiff-text",children:[e.sourcePath," ",(0,kt.jsxs)("span",{className:"text-modiff-subtle-text",children:["(",e.state,")"]})]},e.sourcePath))})]}),(0,kt.jsx)(b,{label:(0,kt.jsxs)("span",{children:["Type ",(0,kt.jsx)("code",{className:"font-mono text-hf-yellow",children:L})]}),required:!0,error:i??void 0,children:(0,kt.jsx)(A,{autoComplete:"off",spellCheck:!1,value:t,onChange:e=>o(e.currentTarget.value),"data-testid":"composite-migration-rollback-confirmation"})}),(0,kt.jsxs)("div",{className:"flex flex-wrap justify-end gap-2 border-t border-modiff-border pt-3",children:[(0,kt.jsx)($,{disabled:e,onClick:n,children:"Cancel"}),(0,kt.jsx)($,{tone:"danger",loading:e,disabled:e||"ROLLBACK_BLOCK_V2_MIGRATION"!==t,onClick:a,"data-testid":"composite-migration-rollback",children:"Restore exact backups"})]})]})}function Ft(){let e=F(e=>e.workflowTabs.some(({dirty:e})=>e)),[a,s]=(0,T.useState)(null),[r,d]=(0,T.useState)([]),[l,u]=(0,T.useState)(!0),[p,m]=(0,T.useState)(null),[h,f]=(0,T.useState)(null),[g,y]=(0,T.useState)(!0),[x,I]=(0,T.useState)(null),[w,N]=(0,T.useState)(null),[k,P]=(0,T.useState)(null),[A,E]=(0,T.useState)(""),[R,_]=(0,T.useState)(null),[M,D]=(0,T.useState)([]),[O,B]=(0,T.useState)(null),[L,V]=(0,T.useState)(!1),[q,z]=(0,T.useState)(""),[K,G]=(0,T.useState)(!1),[J,U]=(0,T.useState)(null),[W,Z]=(0,T.useState)(""),[Y,Q]=(0,T.useState)(null),X=(0,T.useRef)(0),ee=(0,T.useRef)(0),te=(0,T.useRef)(0),ie=(0,T.useRef)(null),ne=(0,T.useRef)(!1),oe=(0,T.useRef)(null),ae=(0,T.useCallback)(async()=>{let e=++X.current;u(!0);try{let[t,i]=await Promise.all([He(oe.current??void 0),Pe()]);if(e!==X.current)return;s(t),d(i.migrations),m(null)}catch(t){e===X.current&&m(ke(t,"Could not inspect legacy composite migrations."))}finally{e===X.current&&u(!1)}},[]),se=(0,T.useCallback)(async()=>{let e=++ee.current;y(!0);try{let t=await nt();if(e!==ee.current)return;f(t),I(null)}catch(t){e===ee.current&&I(ke(t,"Could not inspect historical Cluster recovery evidence."))}finally{e===ee.current&&y(!1)}},[]);(0,T.useEffect)(()=>(ae(),se(),()=>{X.current+=1,ee.current+=1,te.current+=1,ie.current?.abort()}),[ae,se]);let re=async(e,t)=>{if(!ne.current){ne.current=!0,Q(e),N(null);try{await t()}catch(e){let t=ke(e,"Composite migration action failed.");N(t),m(t),C(t,{variant:"error"})}finally{ne.current=!1,Q(null)}}},ce=a?.summary.blockedCandidateCount??0;return(0,kt.jsxs)("section",{className:"grid gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3","data-testid":"composite-migration-card",children:[(0,kt.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,kt.jsxs)("div",{className:"flex items-center gap-2",children:[(0,kt.jsx)(t,{size:15,className:"text-hf-yellow"}),(0,kt.jsxs)("div",{children:[(0,kt.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Legacy composite migration"}),(0,kt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Read-only preview and exact-backup recovery"})]})]}),(0,kt.jsx)(v,{label:l&&!a?"Checking":ce?`${ce} blocked`:"Reviewed",tone:p?"error":ce?"warning":a?"success":"neutral"})]}),(0,kt.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"Previewing writes nothing. Apply and rollback are available only after reviewing exact paths and typing the required literal confirmation."}),p?(0,kt.jsxs)("div",{className:"flex items-start gap-2 rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/5 p-2 text-xs text-modiff-red",children:[(0,kt.jsx)(c,{size:14,className:"mt-0.5 shrink-0"}),(0,kt.jsx)("span",{className:"break-words",children:p})]}):null,a?(0,kt.jsx)(j,{label:(0,kt.jsxs)("span",{className:"flex items-center gap-2",children:[(0,kt.jsx)(o,{size:14,className:"text-hf-yellow"}),"Current read-only preview"]}),className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",buttonClassName:"p-0",panelClassName:"mt-3","data-testid":"composite-migration-preview-disclosure",children:(0,kt.jsx)(Et,{preview:a})}):l||p?null:(0,kt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No migration preview is available."}),(0,kt.jsx)(j,{label:(0,kt.jsxs)("span",{className:"flex items-center gap-2",children:[(0,kt.jsx)(o,{size:14,className:"text-hf-orange"}),"Historical definition recovery evidence"]}),className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",buttonClassName:"p-0",panelClassName:"mt-3","data-testid":"legacy-cluster-recovery-evidence-disclosure",children:(0,kt.jsx)(At,{audit:h,error:x,loading:g,onRefresh:()=>{se()}})}),(0,kt.jsx)(j,{label:"Registered Cluster compiler supplement",className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",buttonClassName:"p-0",panelClassName:"mt-3","data-testid":"composite-migration-compiler-disclosure",children:(0,kt.jsxs)("div",{className:"grid gap-3",children:[(0,kt.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"Generate exact compiler evidence from saved legacy Clusters, or paste externally produced evidence below. Generation and preview are read-only. The exact parsed supplement is retained only for this reviewed preview and resent unchanged on Apply."}),e?(0,kt.jsxs)("div",{className:"flex items-start gap-2 rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-2 text-xs text-hf-orange","data-testid":"composite-migration-unsaved-warning",children:[(0,kt.jsx)(c,{size:14,className:"mt-0.5 shrink-0"}),(0,kt.jsx)("span",{children:"Generation reads saved backend workflow bytes only. Save dirty workflow tabs first if their current canvas changes should be included."})]}):null,(0,kt.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,kt.jsx)($,{size:"compact",tone:"primary",icon:(0,kt.jsx)(i,{size:14}),loading:"compiler-generate"===Y,disabled:!!Y,onClick:()=>{re("compiler-generate",async()=>{let e=++te.current,t=new AbortController;ie.current?.abort(),ie.current=t,P(null),D([]),B(null);try{let i=await He();if(t.signal.aborted||e!==te.current)return;let n=await jt({candidates:i.candidates,signal:t.signal,onProgress:i=>{!t.signal.aborted&&e===te.current&&B(i)}});if(t.signal.aborted||e!==te.current)return;let o=JSON.stringify(n.supplement,null,2);if(E(o),D(n.diagnostics),!n.conversionCount){oe.current=null,_(null),s(i);let e=n.candidateCount?`No legacy registered Cluster matched an exact current compiler route. Review ${n.diagnostics.length} blocker${1===n.diagnostics.length?"":"s"} below.`:"No legacy registered Cluster candidates are present in saved workflows.";return P(e),void C(e,{variant:"warning"})}let a=ge(n.supplement),r=await He(a);if(t.signal.aborted||e!==te.current)return;oe.current=a,_(a),s(r),m(null),C(`Generated and previewed ${n.conversionCount} exact registered-Cluster conversion${1===n.conversionCount?"":"s"}; no files were changed.`,{variant:"success"})}catch(i){if(t.signal.aborted||e!==te.current)return void C("Compiler supplement generation cancelled; no files were changed.",{variant:"info"});throw i}finally{ie.current===t&&(ie.current=null)}})},"data-testid":"composite-migration-compiler-generate",children:"Generate exact supplement"}),"compiler-generate"===Y?(0,kt.jsx)($,{size:"compact",tone:"secondary",onClick:()=>{te.current+=1,ie.current?.abort(),ie.current=null,B(null)},"data-testid":"composite-migration-compiler-cancel",children:"Cancel generation"}):null,O?(0,kt.jsxs)("span",{className:"text-xs text-modiff-subtle-text","data-testid":"composite-migration-compiler-progress",children:["Reviewed ",O.completed,"/",O.total," saved Cluster candidates"]}):null]}),M.length?(0,kt.jsxs)("section",{className:"grid gap-1.5 rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-2","data-testid":"composite-migration-compiler-diagnostics",children:[(0,kt.jsxs)("h4",{className:"text-xs font-bold uppercase text-hf-orange",children:["Candidates left unchanged (",M.length,")"]}),(0,kt.jsx)("ul",{className:"max-h-40 space-y-2 overflow-auto",children:M.map((e,t)=>(0,kt.jsxs)("li",{className:"text-xs",children:[(0,kt.jsxs)("p",{className:"break-all font-mono text-modiff-text",children:[e.sourcePath,e.instanceId?` · ${e.instanceId}`:""]}),(0,kt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.code,": ",e.message]})]},`${e.sourcePath}:${e.instanceId??""}:${e.code}:${t}`))})]}):null,(0,kt.jsx)(b,{label:"Compiler supplement JSON",error:k??void 0,description:"Presentation children never become execution authority; the backend verifies pinned V2 definitions and every preservation receipt.",children:(0,kt.jsx)(H,{value:A,onChange:e=>{E(e.target.value),P(null),D([]),B(null),oe.current&&(oe.current=null,_(null),s(null),ae())},rows:8,spellCheck:!1,disabled:!!Y,className:"font-mono text-xs",placeholder:'{"schemaVersion":1,"kind":"registered_cluster_v2_compiler_supplement","compilerOutputs":[]}',"data-testid":"composite-migration-compiler-json"})}),(0,kt.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,kt.jsx)($,{size:"compact",tone:"primary",loading:"compiler-preview"===Y,disabled:!!Y||!A.trim(),onClick:()=>{re("compiler-preview",async()=>{let e;try{e=ge(JSON.parse(A))}catch(e){let t=e instanceof Error?e.message:"Compiler supplement JSON is invalid.";throw P(t),Error(t)}let t=await He(e);oe.current=e,_(e),s(t),P(null),m(null),C("Exact registered-Cluster compiler supplement previewed; no files were changed.",{variant:"success"})})},"data-testid":"composite-migration-compiler-preview",children:"Preview exact supplement"}),(0,kt.jsx)($,{size:"compact",tone:"secondary",disabled:!!Y||!R,onClick:()=>{oe.current=null,_(null),E(""),P(null),D([]),B(null),ae()},"data-testid":"composite-migration-compiler-clear",children:"Return to default preview"})]})]})}),(0,kt.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,kt.jsx)($,{size:"compact",tone:"primary",disabled:l||!!Y||!a?.summary.hasChanges||!(!a.compilerSupplement||R),onClick:()=>{!a?.summary.hasChanges||a.compilerSupplement&&!R||(z(""),G(!1),N(null),V(!0))},"data-testid":"composite-migration-review",children:"Review migration"}),(0,kt.jsx)($,{size:"compact",tone:"secondary",icon:(0,kt.jsx)(n,{size:14}),loading:l,disabled:l||!!Y,onClick:()=>{ae()},"data-testid":"composite-migration-refresh",children:"Refresh preview"})]}),(0,kt.jsx)(j,{label:`Recovery journals (${r.length})`,className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",buttonClassName:"p-0",panelClassName:"mt-3","data-testid":"composite-migration-recovery-disclosure",children:(0,kt.jsx)(_t,{migrations:r,busyId:Y,onInspectRollback:e=>{re(e.migrationId,async()=>{let t=await Ae(e.migrationId);U(t),Z(""),N(null)})}})}),(0,kt.jsx)(S,{open:L&&!!a,onClose:()=>{Y||V(!1)},title:"Review legacy composite migration",description:"Only the listed files can change. Exact backups are created before migration.",panelClassName:"max-w-4xl",dismissible:!Y,testId:"composite-migration-review-dialog",children:a?(0,kt.jsx)(Rt,{preview:a,confirmation:q,allowBlockedCandidates:K,busy:"apply"===Y,error:w,onConfirmationChange:z,onAllowBlockedCandidatesChange:G,onCancel:()=>V(!1),onApply:()=>{!a||!le(a,q,K)||re("apply",async()=>{let e=await Ee(a,{confirmation:q,allowBlockedCandidates:K,...R?{compilerSupplement:R}:{}});C(e.idempotent?"This exact migration was already applied.":`Converted ${e.changedPaths?.length??0} reviewed files and created exact backups.`,{variant:"success"}),V(!1),z(""),G(!1),oe.current=null,_(null),E(""),D([]),B(null),await ae()})}}):null}),(0,kt.jsx)(S,{open:!!J,onClose:()=>{Y||U(null)},title:"Review exact-backup rollback",description:"Restore the exact backed-up files. Review conflicts before confirming rollback.",panelClassName:"max-w-3xl",dismissible:!Y,testId:"composite-migration-rollback-dialog",children:J?(0,kt.jsx)(Mt,{status:J,confirmation:W,busy:Y===J.migrationId,error:w,onConfirmationChange:Z,onCancel:()=>U(null),onRollback:()=>{!J||"ROLLBACK_BLOCK_V2_MIGRATION"!==W||re(J.migrationId,async()=>{let e=await Re(J.migrationId,W);C(e.idempotent?"This migration was already rolled back.":`Restored ${e.restoredPaths?.length??0} files from exact backups.`,{variant:"success"}),U(null),Z(""),await ae()})}}):null})]})}export{Ft as CompositeMigrationCard}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{$ as t,G as i,H as n,R as o,cr as a,dr as s,rn as r,rt as c}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Bs as d,Ei as l,Eo as u,In as p,Mi as m,Mo as h,Oo as f,Rs as g,Si as v,Sn as y,Ws as x,Xr as b,Ya as I,Yr as C,Zr as w,Zt as $,cn as N,dn as S,fn as j,in as k,ji as H,la as P,mn as A,oc as E,oi as R,qs as _,rn as M,vi as F,wo as D}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as O}from"./huggingFaceClusterInsertion.js?v=cc046c26d6fe7aa4";var B=e(s(),1),T="APPLY_BLOCK_V2_MIGRATION",V="ROLLBACK_BLOCK_V2_MIGRATION",L=/^block-v2-migration-[a-f0-9]{24}$/u,q=/^sha256:[a-f0-9]{64}$/u,z=/^block-definition-v2-[a-f0-9]{8}$/u,K=/^block-graph-v2-[a-f0-9]{8}$/u,G=/^block-interface-v2-[a-f0-9]{8}$/u,J=/^[a-f0-9]{40}$/u,Z=/^(?:user-workflows\/[^/]+\.json|studio\/blocks\/[^/]+\.json)$/u,W=/^user-workflows\/[^/]+\.json$/u,Y=new Set(["legacy_registered_cluster_instance","reusable_v1_definition","workflow_v1_instance","workflow_v1_instances","registered_cluster_compiler_output"]),U=new Set(["reusable_v1_definition","workflow_v1_instances","workflow_composite_instances"]),X=new Set(["applied","applying","prepared","recovery_required","rolled_back","rolling_back"]),Q=new Set(["applied","conflict","interrupted","rolled_back"]),ee=new Set(["applied","conflict","missing","source"]);function te(e,t="is invalid"){throw Error(`Invalid composite migration response: ${e} ${t}.`)}function ie(e,t){return(!e||"object"!=typeof e||Array.isArray(e))&&te(t,"must be an object"),e}function ne(e,t,i,n=[]){let o=new Set([...i,...n]),a=i.filter(t=>!Object.prototype.hasOwnProperty.call(e,t)),s=Object.keys(e).filter(e=>!o.has(e));a.length&&te(t,`is missing ${a.join(", ")}`),s.length&&te(t,`contains unknown keys ${s.join(", ")}`)}function oe(e,t,i,n=4096){return("string"!=typeof e||!e||e.length>n||i&&!i.test(e))&&te(t),e}function ae(e,t){return null===e?null:oe(e,t,void 0,512)}function se(e,t){return"boolean"!=typeof e&&te(t,"must be a boolean"),e}function re(e,t){return(!Number.isSafeInteger(e)||Number(e)<0)&&te(t,"must be a nonnegative integer"),Number(e)}function ce(e,t){return Array.isArray(e)||te(t,"must be an array"),e}function de(e,t){new Set(e).size!==e.length&&te(t,"contains duplicate entries")}function le(e,t,i){return e.summary.hasChanges&&"APPLY_BLOCK_V2_MIGRATION"===t&&(0===e.summary.blockedCandidateCount||i)}function ue(e,t){for(let i=1;i=(e[i]??"")&&te(t,"must be unique and canonically ordered")}function pe(e,t){let i=ce(e,t).map((e,i)=>oe(e,`${t}[${i}]`,void 0,512));return ue(i,t),i}function me(e,t){let i=ie(e,t);return ne(i,t,["id","contentHash","executionProfileId"]),{id:oe(i.id,`${t}.id`,void 0,512),contentHash:oe(i.contentHash,`${t}.contentHash`,void 0,128),executionProfileId:oe(i.executionProfileId,`${t}.executionProfileId`,void 0,512)}}function he(e,t){let i=ie(e,t);ne(i,t,["receiptId","receiptHash","historical","destination","review"]);let n=ie(i.historical,`${t}.historical`);ne(n,`${t}.historical`,["manifestDefinitionId","libraryRevision","manifestContentHash","executionAdmissionId","studioExecutionSpec","executionGraphHash","interfaceHash"]);let o=ie(i.destination,`${t}.destination`);ne(o,`${t}.destination`,["manifestDefinitionId","libraryRevision","manifestContentHash","executionAdmissionId","blockDefinitionId","blockDefinitionContentHash","blockDefinitionCanonicalSha256","executionGraphHash","interfaceHash"]);let a=ie(i.review,`${t}.review`);ne(a,`${t}.review`,["decision","issuer","reviewedAt","notes"]),"semantic_equivalent"!==a.decision&&te(`${t}.review.decision`);let s=oe(a.reviewedAt,`${t}.review.reviewedAt`,void 0,64);return(!/(?:Z|[+-][0-9]{2}:[0-9]{2})$/u.test(s)||Number.isNaN(Date.parse(s)))&&te(`${t}.review.reviewedAt`,"must be an offset ISO-8601 timestamp"),{receiptId:oe(i.receiptId,`${t}.receiptId`,void 0,256),receiptHash:oe(i.receiptHash,`${t}.receiptHash`,q),historical:{manifestDefinitionId:oe(n.manifestDefinitionId,`${t}.historical.manifestDefinitionId`,void 0,512),libraryRevision:oe(n.libraryRevision,`${t}.historical.libraryRevision`,J),manifestContentHash:oe(n.manifestContentHash,`${t}.historical.manifestContentHash`,q),executionAdmissionId:oe(n.executionAdmissionId,`${t}.historical.executionAdmissionId`,void 0,1024),studioExecutionSpec:me(n.studioExecutionSpec,`${t}.historical.studioExecutionSpec`),executionGraphHash:oe(n.executionGraphHash,`${t}.historical.executionGraphHash`,q),interfaceHash:oe(n.interfaceHash,`${t}.historical.interfaceHash`,q)},destination:{manifestDefinitionId:oe(o.manifestDefinitionId,`${t}.destination.manifestDefinitionId`,void 0,512),libraryRevision:oe(o.libraryRevision,`${t}.destination.libraryRevision`,J),manifestContentHash:oe(o.manifestContentHash,`${t}.destination.manifestContentHash`,q),executionAdmissionId:oe(o.executionAdmissionId,`${t}.destination.executionAdmissionId`,void 0,1024),blockDefinitionId:oe(o.blockDefinitionId,`${t}.destination.blockDefinitionId`,void 0,384),blockDefinitionContentHash:oe(o.blockDefinitionContentHash,`${t}.destination.blockDefinitionContentHash`,z),blockDefinitionCanonicalSha256:oe(o.blockDefinitionCanonicalSha256,`${t}.destination.blockDefinitionCanonicalSha256`,q),executionGraphHash:oe(o.executionGraphHash,`${t}.destination.executionGraphHash`,K),interfaceHash:oe(o.interfaceHash,`${t}.destination.interfaceHash`,G)},review:{decision:"semantic_equivalent",issuer:oe(a.issuer,`${t}.review.issuer`,void 0,256),reviewedAt:s,notes:oe(a.notes,`${t}.review.notes`,void 0,4096)}}}function fe(e,t){let i=ie(e,t);ne(i,t,["mappingId","mappingHash","historical","destination","review"]);let n=ie(i.historical,`${t}.historical`);ne(n,`${t}.historical`,["manifestDefinitionId","libraryRevision","manifestContentHash","executionAdmissionId","studioExecutionSpec","archivedDefinitionRecordHash","blockContractHash","rootBlockDefinitionId"]);let o=ie(i.destination,`${t}.destination`);ne(o,`${t}.destination`,["manifestDefinitionId","libraryRevision","manifestContentHash","executionAdmissionId","blockDefinitionId","blockDefinitionContentHash","blockDefinitionCanonicalSha256","executionGraphHash","interfaceHash"]);let a=ie(i.review,`${t}.review`);ne(a,`${t}.review`,["decision","issuer","reviewedAt","notes"]),"historical_compiler_mapping_reviewed"!==a.decision&&te(`${t}.review.decision`);let s=oe(a.reviewedAt,`${t}.review.reviewedAt`,void 0,64);return(!/(?:Z|[+-][0-9]{2}:[0-9]{2})$/u.test(s)||Number.isNaN(Date.parse(s)))&&te(`${t}.review.reviewedAt`,"must be an offset ISO-8601 timestamp"),{mappingId:oe(i.mappingId,`${t}.mappingId`,void 0,256),mappingHash:oe(i.mappingHash,`${t}.mappingHash`,q),historical:{manifestDefinitionId:oe(n.manifestDefinitionId,`${t}.historical.manifestDefinitionId`,void 0,512),libraryRevision:oe(n.libraryRevision,`${t}.historical.libraryRevision`,J),manifestContentHash:oe(n.manifestContentHash,`${t}.historical.manifestContentHash`,q),executionAdmissionId:oe(n.executionAdmissionId,`${t}.historical.executionAdmissionId`,void 0,1024),studioExecutionSpec:me(n.studioExecutionSpec,`${t}.historical.studioExecutionSpec`),archivedDefinitionRecordHash:oe(n.archivedDefinitionRecordHash,`${t}.historical.archivedDefinitionRecordHash`,q),blockContractHash:oe(n.blockContractHash,`${t}.historical.blockContractHash`,q),rootBlockDefinitionId:oe(n.rootBlockDefinitionId,`${t}.historical.rootBlockDefinitionId`,void 0,1024)},destination:{manifestDefinitionId:oe(o.manifestDefinitionId,`${t}.destination.manifestDefinitionId`,void 0,512),libraryRevision:oe(o.libraryRevision,`${t}.destination.libraryRevision`,J),manifestContentHash:oe(o.manifestContentHash,`${t}.destination.manifestContentHash`,q),executionAdmissionId:oe(o.executionAdmissionId,`${t}.destination.executionAdmissionId`,void 0,1024),blockDefinitionId:oe(o.blockDefinitionId,`${t}.destination.blockDefinitionId`,void 0,384),blockDefinitionContentHash:oe(o.blockDefinitionContentHash,`${t}.destination.blockDefinitionContentHash`,z),blockDefinitionCanonicalSha256:oe(o.blockDefinitionCanonicalSha256,`${t}.destination.blockDefinitionCanonicalSha256`,q),executionGraphHash:oe(o.executionGraphHash,`${t}.destination.executionGraphHash`,K),interfaceHash:oe(o.interfaceHash,`${t}.destination.interfaceHash`,G)},review:{decision:"historical_compiler_mapping_reviewed",issuer:oe(a.issuer,`${t}.review.issuer`,void 0,256),reviewedAt:s,notes:oe(a.notes,`${t}.review.notes`,void 0,4096)}}}function ge(e){let t=ie(e,"compiler supplement");ne(t,"compiler supplement",["schemaVersion","kind","compilerOutputs"]),(1!==t.schemaVersion||"registered_cluster_v2_compiler_supplement"!==t.kind)&&te("compiler supplement","uses an unsupported contract");let i=ce(t.compilerOutputs,"compiler supplement.compilerOutputs").map((e,t)=>{let i=`compiler supplement.compilerOutputs[${t}]`,n=ie(e,i);ne(n,i,["sourcePath","sourceSha256","conversions"]);let o=ce(n.conversions,`${i}.conversions`).map((e,t)=>function(e,t){let i=ie(e,t);ne(i,t,["legacyInstanceId","legacyCompositeHash","admissionId","compiledDefinitionContentHash","compiledDefinitionCanonicalSha256","blockInstanceV2","ownedNodeMappings","portMappings","valueMappings","previewMappings","absorbedInternalEdgeIds"],["semanticEquivalenceReceipt","historicalCompilerMapping"]);let n=ce(i.ownedNodeMappings,`${t}.ownedNodeMappings`).map((e,i)=>{let n=`${t}.ownedNodeMappings[${i}]`,o=ie(e,n);return ne(o,n,["legacyNodeId","semanticNodeId"]),{legacyNodeId:oe(o.legacyNodeId,`${n}.legacyNodeId`,void 0,512),semanticNodeId:oe(o.semanticNodeId,`${n}.semanticNodeId`,void 0,512)}});ue(n.map(({legacyNodeId:e,semanticNodeId:t})=>`${e}\0${t}`),`${t}.ownedNodeMappings`);let o=ce(i.portMappings,`${t}.portMappings`).map((e,i)=>{let n=`${t}.portMappings[${i}]`,o=ie(e,n);ne(o,n,["direction","legacyNodeId","legacyPortId","v2PortId"]);let a=oe(o.direction,`${n}.direction`);return"input"!==a&&"output"!==a&&te(`${n}.direction`),{direction:a,legacyNodeId:oe(o.legacyNodeId,`${n}.legacyNodeId`,void 0,512),legacyPortId:oe(o.legacyPortId,`${n}.legacyPortId`,void 0,512),v2PortId:oe(o.v2PortId,`${n}.v2PortId`,void 0,512)}});ue(o.map(({direction:e,legacyNodeId:t,legacyPortId:i,v2PortId:n})=>`${e}\0${t}\0${i}\0${n}`),`${t}.portMappings`);let a=ce(i.valueMappings,`${t}.valueMappings`).map((e,i)=>{let n=`${t}.valueMappings[${i}]`,o=ie(e,n),a=oe(o.targetKind,`${n}.targetKind`),s={sourceKind:oe(o.sourceKind,`${n}.sourceKind`,void 0,512),sourceNodeId:oe(o.sourceNodeId,`${n}.sourceNodeId`,void 0,512),sourceFieldId:oe(o.sourceFieldId,`${n}.sourceFieldId`,void 0,512)};return"instance_value"===a?(ne(o,n,["sourceKind","sourceNodeId","sourceFieldId","targetKind","targetValueId"]),{...s,targetKind:"instance_value",targetValueId:oe(o.targetValueId,`${n}.targetValueId`,void 0,512)}):"graph_param"===a?(ne(o,n,["sourceKind","sourceNodeId","sourceFieldId","targetKind","targetNodeId","targetFieldId"]),{...s,targetKind:"graph_param",targetNodeId:oe(o.targetNodeId,`${n}.targetNodeId`,void 0,512),targetFieldId:oe(o.targetFieldId,`${n}.targetFieldId`,void 0,512)}):void te(`${n}.targetKind`)});ue(a.map(({sourceKind:e,sourceNodeId:t,sourceFieldId:i,targetKind:n})=>`${e}\0${t}\0${i}\0${n}`),`${t}.valueMappings`);let s=ce(i.previewMappings,`${t}.previewMappings`).map((e,i)=>{let n=`${t}.previewMappings[${i}]`,o=ie(e,n);return ne(o,n,["sourceNodeId","sourceFieldId","targetNodeId","targetOutputPortId"]),{sourceNodeId:oe(o.sourceNodeId,`${n}.sourceNodeId`,void 0,512),sourceFieldId:oe(o.sourceFieldId,`${n}.sourceFieldId`,void 0,512),targetNodeId:oe(o.targetNodeId,`${n}.targetNodeId`,void 0,512),targetOutputPortId:oe(o.targetOutputPortId,`${n}.targetOutputPortId`,void 0,512)}});ue(s.map(({sourceNodeId:e,sourceFieldId:t,targetNodeId:i,targetOutputPortId:n})=>`${e}\0${t}\0${i}\0${n}`),`${t}.previewMappings`);let r=void 0===i.semanticEquivalenceReceipt?void 0:(()=>{let e=ie(i.semanticEquivalenceReceipt,`${t}.semanticEquivalenceReceipt`);return ne(e,`${t}.semanticEquivalenceReceipt`,["receiptId","receiptHash"]),{receiptId:oe(e.receiptId,`${t}.semanticEquivalenceReceipt.receiptId`,void 0,256),receiptHash:oe(e.receiptHash,`${t}.semanticEquivalenceReceipt.receiptHash`,q)}})(),c=void 0===i.historicalCompilerMapping?void 0:(()=>{let e=ie(i.historicalCompilerMapping,`${t}.historicalCompilerMapping`);return ne(e,`${t}.historicalCompilerMapping`,["mappingId","mappingHash"]),{mappingId:oe(e.mappingId,`${t}.historicalCompilerMapping.mappingId`,void 0,256),mappingHash:oe(e.mappingHash,`${t}.historicalCompilerMapping.mappingHash`,q)}})();return r&&c&&te(t,"must not combine semantic-equivalence and historical compiler-mapping authority"),{legacyInstanceId:oe(i.legacyInstanceId,`${t}.legacyInstanceId`,void 0,512),legacyCompositeHash:oe(i.legacyCompositeHash,`${t}.legacyCompositeHash`,q),admissionId:oe(i.admissionId,`${t}.admissionId`,void 0,1024),compiledDefinitionContentHash:oe(i.compiledDefinitionContentHash,`${t}.compiledDefinitionContentHash`,z),compiledDefinitionCanonicalSha256:oe(i.compiledDefinitionCanonicalSha256,`${t}.compiledDefinitionCanonicalSha256`,q),blockInstanceV2:x(i.blockInstanceV2),ownedNodeMappings:n,portMappings:o,valueMappings:a,previewMappings:s,absorbedInternalEdgeIds:pe(i.absorbedInternalEdgeIds,`${t}.absorbedInternalEdgeIds`),...r?{semanticEquivalenceReceipt:r}:{},...c?{historicalCompilerMapping:c}:{}}}(e,`${i}.conversions[${t}]`));return o.length||te(`${i}.conversions`,"must not be empty"),ue(o.map(({legacyInstanceId:e})=>e),`${i}.conversions`),{sourcePath:oe(n.sourcePath,`${i}.sourcePath`,W),sourceSha256:oe(n.sourceSha256,`${i}.sourceSha256`,q),conversions:o}});return ue(i.map(({sourcePath:e})=>e),"compiler supplement.compilerOutputs"),{schemaVersion:1,kind:"registered_cluster_v2_compiler_supplement",compilerOutputs:i}}function ve(e,t){let i=ie(e,`preview.targets[${t}]`);ne(i,`preview.targets[${t}]`,["sourcePath","kind","beforeSha256","afterSha256","convertedIds","byteLength"]);let n=oe(i.kind,`preview.targets[${t}].kind`);U.has(n)||te(`preview.targets[${t}].kind`);let o=ce(i.convertedIds,`preview.targets[${t}].convertedIds`).map((e,i)=>oe(e,`preview.targets[${t}].convertedIds[${i}]`,void 0,512));return o.length||te(`preview.targets[${t}].convertedIds`,"must not be empty"),de(o,`preview.targets[${t}].convertedIds`),{sourcePath:oe(i.sourcePath,`preview.targets[${t}].sourcePath`,Z),kind:n,beforeSha256:oe(i.beforeSha256,`preview.targets[${t}].beforeSha256`,q),afterSha256:oe(i.afterSha256,`preview.targets[${t}].afterSha256`,q),convertedIds:o,byteLength:re(i.byteLength,`preview.targets[${t}].byteLength`)}}function ye(e,t){let i=ie(e,t);ne(i,t,["kind","sourcePath","id","status"],["reason","targetAfterSha256","sourceSha256","legacyCompositeHash","compilerReceipt","semanticEquivalenceAuthority","historicalCompilerMappingAuthority"]);let n=oe(i.kind,`${t}.kind`);Y.has(n)||te(`${t}.kind`);let o=oe(i.status,`${t}.status`);"blocked"!==o&&"convertible"!==o&&te(`${t}.status`);let a=void 0===i.reason?void 0:oe(i.reason,`${t}.reason`,void 0,8192),s=void 0===i.targetAfterSha256?void 0:oe(i.targetAfterSha256,`${t}.targetAfterSha256`,q),r=void 0===i.sourceSha256?void 0:oe(i.sourceSha256,`${t}.sourceSha256`,q),c=void 0===i.legacyCompositeHash?void 0:oe(i.legacyCompositeHash,`${t}.legacyCompositeHash`,q),d=void 0===i.compilerReceipt?void 0:function(e,t){let i=ie(e,t);ne(i,t,["instanceId","definitionId","admissionId","compiledDefinitionContentHash","compiledDefinitionCanonicalSha256","legacyCompositeHash","absorbedProjectionNodeIds","absorbedInternalEdgeIds"],["semanticEquivalenceAuthority","historicalCompilerMappingAuthority"]);let n=void 0===i.semanticEquivalenceAuthority?void 0:he(i.semanticEquivalenceAuthority,`${t}.semanticEquivalenceAuthority`),o=void 0===i.historicalCompilerMappingAuthority?void 0:fe(i.historicalCompilerMappingAuthority,`${t}.historicalCompilerMappingAuthority`);return n&&o&&te(t,"must not combine semantic-equivalence and historical compiler-mapping authority"),{instanceId:oe(i.instanceId,`${t}.instanceId`,void 0,512),definitionId:oe(i.definitionId,`${t}.definitionId`,void 0,512),admissionId:oe(i.admissionId,`${t}.admissionId`,void 0,1024),compiledDefinitionContentHash:oe(i.compiledDefinitionContentHash,`${t}.compiledDefinitionContentHash`,z),compiledDefinitionCanonicalSha256:oe(i.compiledDefinitionCanonicalSha256,`${t}.compiledDefinitionCanonicalSha256`,q),legacyCompositeHash:oe(i.legacyCompositeHash,`${t}.legacyCompositeHash`,q),absorbedProjectionNodeIds:pe(i.absorbedProjectionNodeIds,`${t}.absorbedProjectionNodeIds`),absorbedInternalEdgeIds:pe(i.absorbedInternalEdgeIds,`${t}.absorbedInternalEdgeIds`),...n?{semanticEquivalenceAuthority:n}:{},...o?{historicalCompilerMappingAuthority:o}:{}}}(i.compilerReceipt,`${t}.compilerReceipt`),l=void 0===i.semanticEquivalenceAuthority?void 0:he(i.semanticEquivalenceAuthority,`${t}.semanticEquivalenceAuthority`),u=void 0===i.historicalCompilerMappingAuthority?void 0:fe(i.historicalCompilerMappingAuthority,`${t}.historicalCompilerMappingAuthority`);return l&&u&&te(t,"must not combine semantic-equivalence and historical compiler-mapping authority"),("blocked"===o!=!!a||"convertible"===o!=!!s)&&te(t,"has inconsistent status details"),{kind:n,sourcePath:oe(i.sourcePath,`${t}.sourcePath`,Z),id:ae(i.id,`${t}.id`),status:o,...a?{reason:a}:{},...s?{targetAfterSha256:s}:{},...r?{sourceSha256:r}:{},...c?{legacyCompositeHash:c}:{},...d?{compilerReceipt:d}:{},...l?{semanticEquivalenceAuthority:l}:{},...u?{historicalCompilerMappingAuthority:u}:{}}}function xe(e,t){let i=`preview.inventoryIssues[${t}]`,n=ie(e,i);ne(n,i,["sourcePath","path","code","severity","message"]);let o=oe(n.severity,`${i}.severity`);return"error"!==o&&"warning"!==o&&te(`${i}.severity`),{sourcePath:oe(n.sourcePath,`${i}.sourcePath`,void 0,4096),path:oe(n.path,`${i}.path`,void 0,4096),code:oe(n.code,`${i}.code`,void 0,512),severity:o,message:oe(n.message,`${i}.message`,void 0,8192)}}function be(e){let t=ie(e,"preview response");ne(t,"preview response",["error","preview"]),!1!==t.error&&te("preview response.error","must be false");let i=ie(t.preview,"preview");ne(i,"preview",["schemaVersion","kind","mode","boundary","inventoryReportHash","sourceSetHash","summary","targets","candidates","blocked","inventoryIssues","migrationId","planHash"],["compilerSupplement"]),(1!==i.schemaVersion||"legacy_composite_to_block_v2_migration"!==i.kind||"read_only_preview"!==i.mode)&&te("preview","uses an unsupported contract");let n=ie(i.boundary,"preview.boundary");ne(n,"preview.boundary",["writesFiles","requiresExplicitApplyAuthority","createsExactBackupsBeforeReplacement","deletesRecords","mergesRecords","legacyClusterConversionRequiresRegisteredCompiler","containsPromptAndParameterValues"],["compilerSupplementRequiredForRegisteredClusters"]),(!1!==n.writesFiles||!0!==n.requiresExplicitApplyAuthority||!0!==n.createsExactBackupsBeforeReplacement||!1!==n.deletesRecords||!1!==n.mergesRecords||!0!==n.legacyClusterConversionRequiresRegisteredCompiler||!1!==n.containsPromptAndParameterValues)&&te("preview.boundary","does not preserve the read-only safety contract");let o=ie(i.summary,"preview.summary");ne(o,"preview.summary",["sourceCount","targetFileCount","convertibleCandidateCount","blockedCandidateCount","legacyClusterBlockedCount","hasChanges"],["registeredClusterConvertibleCount"]);let a=ce(i.targets,"preview.targets").map(ve),s=ce(i.candidates,"preview.candidates").map((e,t)=>ye(e,`preview.candidates[${t}]`)),r=ce(i.blocked,"preview.blocked").map((e,t)=>ye(e,`preview.blocked[${t}]`)),c=ce(i.inventoryIssues,"preview.inventoryIssues").map(xe),d={sourceCount:re(o.sourceCount,"preview.summary.sourceCount"),targetFileCount:re(o.targetFileCount,"preview.summary.targetFileCount"),convertibleCandidateCount:re(o.convertibleCandidateCount,"preview.summary.convertibleCandidateCount"),blockedCandidateCount:re(o.blockedCandidateCount,"preview.summary.blockedCandidateCount"),legacyClusterBlockedCount:re(o.legacyClusterBlockedCount,"preview.summary.legacyClusterBlockedCount"),hasChanges:se(o.hasChanges,"preview.summary.hasChanges"),...void 0===o.registeredClusterConvertibleCount?{}:{registeredClusterConvertibleCount:re(o.registeredClusterConvertibleCount,"preview.summary.registeredClusterConvertibleCount")}},l=void 0===i.compilerSupplement?void 0:(()=>{let e=ie(i.compilerSupplement,"preview.compilerSupplement");ne(e,"preview.compilerSupplement",["provided","contentHash","sourceCount","conversionCount"]);let t=se(e.provided,"preview.compilerSupplement.provided"),n=null===e.contentHash?null:oe(e.contentHash,"preview.compilerSupplement.contentHash",q);return t!==!!n&&te("preview.compilerSupplement","has an inconsistent provided/contentHash pair"),{provided:t,contentHash:n,sourceCount:re(e.sourceCount,"preview.compilerSupplement.sourceCount"),conversionCount:re(e.conversionCount,"preview.compilerSupplement.conversionCount")}})();(!!l!=(!0===n.compilerSupplementRequiredForRegisteredClusters)||!!l!=(void 0!==d.registeredClusterConvertibleCount))&&te("preview.compilerSupplement","is inconsistent with the compiler-preview boundary and summary"),de(a.map(({sourcePath:e})=>e),"preview.targets");let u=s.filter(({status:e})=>"blocked"===e);(d.targetFileCount!==a.length||d.convertibleCandidateCount!==s.filter(({status:e})=>"convertible"===e).length||d.blockedCandidateCount!==u.length||d.blockedCandidateCount!==r.length||d.legacyClusterBlockedCount!==r.filter(({kind:e})=>"legacy_registered_cluster_instance"===e).length||void 0!==d.registeredClusterConvertibleCount&&d.registeredClusterConvertibleCount!==s.filter(({kind:e,status:t})=>"legacy_registered_cluster_instance"===e&&"convertible"===t).length||d.hasChanges!==!!a.length||JSON.stringify(u)!==JSON.stringify(r))&&te("preview.summary","does not match the listed targets and candidates");let p=new Map(a.map(e=>[e.sourcePath,e.afterSha256]));return s.some(e=>"convertible"===e.status&&p.get(e.sourcePath)!==e.targetAfterSha256)&&te("preview.candidates","does not match the target hashes"),{schemaVersion:1,kind:"legacy_composite_to_block_v2_migration",mode:"read_only_preview",boundary:{writesFiles:!1,requiresExplicitApplyAuthority:!0,createsExactBackupsBeforeReplacement:!0,deletesRecords:!1,mergesRecords:!1,legacyClusterConversionRequiresRegisteredCompiler:!0,...l?{compilerSupplementRequiredForRegisteredClusters:!0}:{},containsPromptAndParameterValues:!1},...l?{compilerSupplement:l}:{},inventoryReportHash:oe(i.inventoryReportHash,"preview.inventoryReportHash",q),sourceSetHash:oe(i.sourceSetHash,"preview.sourceSetHash",q),summary:d,targets:a,candidates:s,blocked:r,inventoryIssues:c,migrationId:oe(i.migrationId,"preview.migrationId",L),planHash:oe(i.planHash,"preview.planHash",q)}}function Ie(e,t){let i=`migration status.targets[${t}]`,n=ie(e,i);ne(n,i,["sourcePath","currentSha256","state"]);let o=oe(n.state,`${i}.state`);ee.has(o)||te(`${i}.state`);let a=null===n.currentSha256?null:oe(n.currentSha256,`${i}.currentSha256`,q);return"missing"===o!=(null===a)&&te(i,"has an inconsistent current file hash"),{sourcePath:oe(n.sourcePath,`${i}.sourcePath`,Z),currentSha256:a,state:o}}function Ce(e){let t=ie(e,"migration status");ne(t,"migration status",["migrationId","journalState","effectiveState","planHash","inventoryReportHash","targets","rollbackAvailable","resumeAvailable"]);let i=oe(t.journalState,"migration status.journalState"),n=oe(t.effectiveState,"migration status.effectiveState");X.has(i)||te("migration status.journalState"),Q.has(n)||te("migration status.effectiveState");let o=ce(t.targets,"migration status.targets").map(Ie);o.length||te("migration status.targets","must not be empty"),de(o.map(({sourcePath:e})=>e),"migration status.targets");let a=se(t.rollbackAvailable,"migration status.rollbackAvailable"),s=se(t.resumeAvailable,"migration status.resumeAvailable"),r=new Set(o.map(({state:e})=>e));return(n!==(1===r.size&&r.has("applied")?"applied":1===r.size&&r.has("source")?"rolled_back":[...r].every(e=>"source"===e||"applied"===e)?"interrupted":"conflict")||!1!==s||a!==["applied","interrupted","rolled_back"].includes(n))&&te("migration status","has inconsistent recovery actions"),{migrationId:oe(t.migrationId,"migration status.migrationId",L),journalState:i,effectiveState:n,planHash:oe(t.planHash,"migration status.planHash",q),inventoryReportHash:oe(t.inventoryReportHash,"migration status.inventoryReportHash",q),targets:o,rollbackAvailable:a,resumeAvailable:!1}}function we(e){let t=ie(e,"migration list response");ne(t,"migration list response",["error","schemaVersion","migrations"]),(!1!==t.error||1!==t.schemaVersion)&&te("migration list response","uses an unsupported contract");let i=ce(t.migrations,"migration list response.migrations").map(Ce);return de(i.map(({migrationId:e})=>e),"migration list response.migrations"),{schemaVersion:1,migrations:i}}function $e(e){let t=ie(e,"migration status response");return ne(t,"migration status response",["error","status"]),!1!==t.error&&te("migration status response.error","must be false"),Ce(t.status)}function Ne(e,t){let i=ie(e,"migration mutation response");ne(i,"migration mutation response",["error","idempotent","status"],[t]),!1!==i.error&&te("migration mutation response.error","must be false");let n=Ce(i.status),o=void 0===i[t]?void 0:ce(i[t],`migration mutation response.${t}`).map((e,i)=>oe(e,`migration mutation response.${t}[${i}]`,Z));if(o){de(o,`migration mutation response.${t}`);let e=new Set(n.targets.map(({sourcePath:e})=>e));o.some(t=>!e.has(t))&&te(`migration mutation response.${t}`,"is not a target")}return{idempotent:se(i.idempotent,"migration mutation response.idempotent"),..."changedPaths"===t&&o?{changedPaths:o}:{},..."restoredPaths"===t&&o?{restoredPaths:o}:{},status:n}}function Se(e){let t=Ne(e,"changedPaths");return"applied"!==t.status.effectiveState&&te("migration apply result.status","did not reach the applied state"),t}function je(e){let t=Ne(e,"restoredPaths");return"rolled_back"!==t.status.effectiveState&&te("migration rollback result.status","did not reach the rolled-back state"),t}function ke(e,t){let i=u(e,t);return e instanceof D&&403===e.status?`Authorization rejected (HTTP 403): ${i}`:e instanceof D&&409===e.status?`Migration state conflict (HTTP 409): ${i}`:i}function He(e){let t=void 0===e?void 0:ge(e);return f(`${E.serverAddress}/studio/composite-migrations/preview`,{...t?{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({compilerSupplement:t})}:{},timeoutMs:3e4,parse:be})}function Pe(){return f(`${E.serverAddress}/studio/composite-migrations`,{timeoutMs:3e4,parse:we})}async function Ae(e){oe(e,"migrationId",L);let t=await f(`${E.serverAddress}/studio/composite-migrations/${encodeURIComponent(e)}`,{timeoutMs:3e4,parse:$e});return t.migrationId!==e&&te("migration status.migrationId","does not match the request"),t}async function Ee(e,t){if("APPLY_BLOCK_V2_MIGRATION"!==t.confirmation)throw Error(`Type ${T} exactly before applying.`);if("boolean"!=typeof t.allowBlockedCandidates)throw Error("Choose explicitly whether blocked candidates may remain before applying.");let i=be({error:!1,preview:e}),n=void 0===t.compilerSupplement?void 0:ge(t.compilerSupplement);if(!!i.compilerSupplement!=!!n)throw Error("Apply must reuse the exact compiler supplement from the reviewed preview.");if(i.compilerSupplement&&n&&(i.compilerSupplement.sourceCount!==n.compilerOutputs.length||i.compilerSupplement.conversionCount!==n.compilerOutputs.reduce((e,t)=>e+t.conversions.length,0)))throw Error("Apply compiler supplement does not match the reviewed preview counts.");if(i.summary.blockedCandidateCount>0&&!t.allowBlockedCandidates)throw Error("Explicitly allow blocked candidates to remain unchanged before applying safe targets.");let o=await f(`${E.serverAddress}/studio/composite-migrations/apply`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({migrationId:i.migrationId,planHash:i.planHash,confirmation:t.confirmation,allowBlockedCandidates:t.allowBlockedCandidates,...n?{compilerSupplement:n}:{}}),timeoutMs:12e4,parse:Se});return(o.status.migrationId!==i.migrationId||o.status.planHash!==i.planHash)&&te("migration apply result","does not match the reviewed preview"),o}async function Re(e,t){if(oe(e,"migrationId",L),"ROLLBACK_BLOCK_V2_MIGRATION"!==t)throw Error(`Type ${V} exactly before rollback.`);let i=await f(`${E.serverAddress}/studio/composite-migrations/${encodeURIComponent(e)}/rollback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({confirmation:t}),timeoutMs:12e4,parse:je});return i.status.migrationId!==e&&te("migration rollback result","does not match the requested journal"),i}var _e=/^sha256:[a-f0-9]{64}$/u,Me=/^[a-f0-9]{40}$/u,Fe=/^studio-spec-v1-[a-f0-9]{8}$/u;function De(e,t="is invalid"){throw Error(`Invalid legacy Cluster recovery-audit response: ${e} ${t}.`)}function Oe(e,t){return(!e||"object"!=typeof e||Array.isArray(e))&&De(t,"must be an object"),e}function Be(e,t,i,n=[]){let o=new Set([...i,...n]),a=i.filter(t=>!Object.prototype.hasOwnProperty.call(e,t)),s=Object.keys(e).filter(e=>!o.has(e));(a.length||s.length)&&De(t,"has missing or unknown fields")}function Te(e,t,i,n=8192){return("string"!=typeof e||!e||e.length>n||i&&!i.test(e))&&De(t),e}function Ve(e,t,i,n=8192){return null===e?null:Te(e,t,i,n)}function Le(e,t){return(!Number.isSafeInteger(e)||Number(e)<0)&&De(t,"must be a nonnegative integer"),Number(e)}function qe(e,t){return"boolean"!=typeof e&&De(t,"must be a boolean"),e}function ze(e,t){return Array.isArray(e)||De(t,"must be an array"),e}function Ke(e,t){let i=ze(e,t).map((e,i)=>Te(e,`${t}[${i}]`,void 0,1024));return new Set(i).size!==i.length&&De(t,"contains duplicates"),i}var Ge=new Set(["identity_malformed","execution_receipt_missing","execution_receipt_incomplete","current_manifest_exact","archived_manifest_exact","semantic_equivalence_reviewed","historical_manifest_evidence_required"]),Je="legacyClusterInstanceCount.definitionIdentityCount.currentManifestExactInstanceCount.currentManifestExactIdentityCount.currentExecutionTupleExactInstanceCount.currentExecutionTupleExactIdentityCount.archivedManifestExactInstanceCount.archivedManifestExactIdentityCount.semanticEquivalenceReviewedInstanceCount.semanticEquivalenceReviewedIdentityCount.compilerMappingEligibleInstanceCount.compilerMappingEligibleIdentityCount.remainingBlockedHistoricalInstanceCount.remainingBlockedHistoricalIdentityCount.historicalEvidenceRequiredInstanceCount.historicalEvidenceRequiredIdentityCount.completeExecutionReceiptInstanceCount.missingExecutionReceiptInstanceCount.missingExecutionReceiptIdentityCount.incompleteExecutionReceiptInstanceCount.incompleteExecutionReceiptIdentityCount.identityReferenceOnlyInstanceCount.embeddedCompleteDefinitionInstanceCount.presentationProjectionInstanceCount.presentationProjectionNodeCount.malformedIdentityInstanceCount.malformedIdentityCount.recoveredStudioSpecBodyCount.recoveredStudioSpecManifestIdentityCount.recoveredStudioSpecExecutionTupleCount.recoveredStudioSpecInstanceCount.partialReviewVerifiedExecutionTupleCount.partialReviewRejectedExecutionTupleCount".split(".");function Ze(e,t){let i=Oe(e,t);return Be(i,t,["id","contentHash","executionProfileId"]),{id:Te(i.id,`${t}.id`,void 0,512),contentHash:Te(i.contentHash,`${t}.contentHash`,Fe),executionProfileId:Te(i.executionProfileId,`${t}.executionProfileId`,void 0,512)}}function We(e,t){return ze(e,t).map((e,i)=>{let n=`${t}[${i}]`,o=Oe(e,n);return Be(o,n,["code","message"]),{code:Te(o.code,`${n}.code`,void 0,512),message:Te(o.message,`${n}.message`)}})}function Ye(e,t){let i=Oe(e,t),n=Te(i.status,`${t}.status`);return"missing"===n?(Be(i,t,["status","authorizesConversion"]),!1!==qe(i.authorizesConversion,`${t}.authorizesConversion`)&&De(t,"grants authority"),{status:n,authorizesConversion:!1}):("self_hash_valid_partial_evidence"!==n&&De(`${t}.status`),Be(i,t,["status","canonicalBodySha256","sourceIds","authorizesConversion"]),!1!==qe(i.authorizesConversion,`${t}.authorizesConversion`)&&De(t,"grants authority"),{status:n,canonicalBodySha256:Te(i.canonicalBodySha256,`${t}.canonicalBodySha256`,_e),sourceIds:Ke(i.sourceIds,`${t}.sourceIds`),authorizesConversion:!1})}function Ue(e,t){let i=Oe(e,t),n=Te(i.status,`${t}.status`);if("evidence_missing"===n||"unreviewed"===n)return Be(i,t,["status","authorizesConversion"]),!1!==qe(i.authorizesConversion,`${t}.authorizesConversion`)&&De(t,"grants authority"),{status:n,authorizesConversion:!1};"verified_partial_evidence"!==n&&"rejected_evidence"!==n&&De(`${t}.status`),Be(i,t,["status","reviewId","reviewer","reviewedAt","notes","reviewHash","authorizesConversion"]),!1!==qe(i.authorizesConversion,`${t}.authorizesConversion`)&&De(t,"grants authority");let o=Te(i.reviewedAt,`${t}.reviewedAt`,void 0,64);return(Number.isNaN(Date.parse(o))||!/(?:Z|[+-][0-9]{2}:[0-9]{2})$/u.test(o))&&De(`${t}.reviewedAt`),{status:n,reviewId:Te(i.reviewId,`${t}.reviewId`,void 0,512),reviewer:Te(i.reviewer,`${t}.reviewer`,void 0,256),reviewedAt:o,notes:Te(i.notes,`${t}.notes`,void 0,4096),reviewHash:Te(i.reviewHash,`${t}.reviewHash`,_e),authorizesConversion:!1}}function Xe(e,t){let i=Oe(e,t);Be(i,t,["status","specificationBodyCount","executionTupleCount","instanceCount","verifiedPartialReviewExecutionTupleCount","rejectedPartialReviewExecutionTupleCount","isManifestDefinitionOrConversionAuthority"]);let n=Te(i.status,`${t}.status`);return"partial_evidence_available"!==n&&"missing"!==n&&De(`${t}.status`),!1!==qe(i.isManifestDefinitionOrConversionAuthority,`${t}.isManifestDefinitionOrConversionAuthority`)&&De(t,"grants authority"),{status:n,specificationBodyCount:Le(i.specificationBodyCount,`${t}.specificationBodyCount`),executionTupleCount:Le(i.executionTupleCount,`${t}.executionTupleCount`),instanceCount:Le(i.instanceCount,`${t}.instanceCount`),verifiedPartialReviewExecutionTupleCount:Le(i.verifiedPartialReviewExecutionTupleCount,`${t}.verifiedPartialReviewExecutionTupleCount`),rejectedPartialReviewExecutionTupleCount:Le(i.rejectedPartialReviewExecutionTupleCount,`${t}.rejectedPartialReviewExecutionTupleCount`),isManifestDefinitionOrConversionAuthority:!1}}function Qe(e,t){let i=`audit.identities[${t}]`,n=Oe(e,i);Be(n,i,["definitionId","libraryRevision","manifestContentHash","admissionIds","instanceCount","disposition","sourceEvidence","executionTuples","missingEvidence","safeNextActions"]);let o=Te(n.disposition,`${i}.disposition`);Ge.has(o)||De(`${i}.disposition`);let a=Oe(n.sourceEvidence,`${i}.sourceEvidence`);Be(a,`${i}.sourceEvidence`,["embeddedManifest","executionReceipt","presentationProjection","currentCatalog","registeredArchive","reviewedSemanticEquivalence","recoveredStudioSpecs"]);for(let e of["embeddedManifest","executionReceipt","presentationProjection","currentCatalog","registeredArchive","reviewedSemanticEquivalence"])Oe(a[e],`${i}.sourceEvidence.${e}`);return{definitionId:Ve(n.definitionId,`${i}.definitionId`,void 0,512),libraryRevision:Ve(n.libraryRevision,`${i}.libraryRevision`,Me),manifestContentHash:Ve(n.manifestContentHash,`${i}.manifestContentHash`,_e),admissionIds:Ke(n.admissionIds,`${i}.admissionIds`),instanceCount:Le(n.instanceCount,`${i}.instanceCount`),disposition:o,recoveredStudioSpecs:Xe(a.recoveredStudioSpecs,`${i}.sourceEvidence.recoveredStudioSpecs`),executionTuples:ze(n.executionTuples,`${i}.executionTuples`).map((e,t)=>function(e,t){let i=Oe(e,t);return Be(i,t,["admissionId","studioExecutionSpec","instanceCount","recoveredStudioSpec","manualReview"]),{admissionId:Ve(i.admissionId,`${t}.admissionId`,void 0,1024),studioExecutionSpec:null===i.studioExecutionSpec?null:Ze(i.studioExecutionSpec,`${t}.studioExecutionSpec`),instanceCount:Le(i.instanceCount,`${t}.instanceCount`),recoveredStudioSpec:Ye(i.recoveredStudioSpec,`${t}.recoveredStudioSpec`),manualReview:Ue(i.manualReview,`${t}.manualReview`)}}(e,`${i}.executionTuples[${t}]`)),missingEvidence:We(n.missingEvidence,`${i}.missingEvidence`),safeNextActions:We(n.safeNextActions,`${i}.safeNextActions`)}}function et(e,t){let i=`audit.partialStudioSpecEvidence.sources[${t}]`,n=Oe(e,i);return Be(n,i,["sourceId","artifactLabel","compressedSha256","compressedBytes","uncompressedSha256","uncompressedBytes","selector","candidateSpecificationCount"]),Te(n.compressedSha256,`${i}.compressedSha256`,_e),Le(n.compressedBytes,`${i}.compressedBytes`),Te(n.uncompressedSha256,`${i}.uncompressedSha256`,_e),Le(n.uncompressedBytes,`${i}.uncompressedBytes`),Te(n.selector,`${i}.selector`,void 0,512),Le(n.candidateSpecificationCount,`${i}.candidateSpecificationCount`),{sourceId:Te(n.sourceId,`${i}.sourceId`,void 0,160),artifactLabel:Te(n.artifactLabel,`${i}.artifactLabel`,void 0,512)}}function tt(e,t){let i=`audit.partialStudioSpecEvidence.specifications[${t}]`,n=Oe(e,i);Be(n,i,["identity","canonicalBodySha256","specification","sourceIds","authorizesConversion"]),!1!==qe(n.authorizesConversion,`${i}.authorizesConversion`)&&De(i,"grants authority");let o=Ze(n.identity,`${i}.identity`),a=Oe(n.specification,`${i}.specification`);Be(a,`${i}.specification`,["schemaVersion","canonicalizationVersion","id","modelType","mode","executionProfileId","loaderModule","loaderAction","executionPath","pipelineClass","defaultRepo","roles","edges","bindings","autoFields","actions","contentHash"],["auxiliaryTerminalRoles"]),(1!==a.schemaVersion||a.id!==o.id||a.contentHash!==o.contentHash||a.executionProfileId!==o.executionProfileId)&&De(`${i}.specification`,"does not match its evidence identity");let s=ze(a.roles,`${i}.specification.roles`),r=ze(a.edges,`${i}.specification.edges`),c=ze(a.bindings,`${i}.specification.bindings`),d=ze(a.actions,`${i}.specification.actions`);return ze(a.autoFields,`${i}.specification.autoFields`),void 0!==a.auxiliaryTerminalRoles&&Ke(a.auxiliaryTerminalRoles,`${i}.specification.auxiliaryTerminalRoles`),{identity:o,canonicalBodySha256:Te(n.canonicalBodySha256,`${i}.canonicalBodySha256`,_e),sourceIds:Ke(n.sourceIds,`${i}.sourceIds`),modelType:Te(a.modelType,`${i}.specification.modelType`,void 0,512),mode:Te(a.mode,`${i}.specification.mode`,void 0,512),pipelineClass:Te(a.pipelineClass,`${i}.specification.pipelineClass`,void 0,512),roleCount:s.length,edgeCount:r.length,bindingCount:c.length,actionCount:d.length}}function it(e){let t=Oe(e,"response");Be(t,"response",["error","audit"]),!1!==t.error&&De("response.error","must be false");let i=Oe(t.audit,"audit");Be(i,"audit",["schemaVersion","kind","boundary","localEvidence","summary","partialStudioSpecEvidence","identities","contentHash"]),(4!==i.schemaVersion||"registered_cluster_manifest_recovery_audit"!==i.kind)&&De("audit","uses an unsupported contract");let n=Oe(i.boundary,"audit.boundary");Be(n,"audit.boundary",["readOnly","containsWorkflowPaths","containsInstanceIds","containsPromptOrParameterValues","historicalHashAloneIsNotExecutionAuthority","presentationProjectionIsNotDefinitionAuthority","recoveredStudioSpecEvidenceDoesNotAuthorizeConversion","compilerMappingPresenceAloneDoesNotAuthorizeConversion"]);let o=qe(n.readOnly,"audit.boundary.readOnly"),a=qe(n.containsWorkflowPaths,"audit.boundary.containsWorkflowPaths"),s=qe(n.containsInstanceIds,"audit.boundary.containsInstanceIds"),r=qe(n.containsPromptOrParameterValues,"audit.boundary.containsPromptOrParameterValues"),c=qe(n.historicalHashAloneIsNotExecutionAuthority,"audit.boundary.historicalHashAloneIsNotExecutionAuthority"),d=qe(n.presentationProjectionIsNotDefinitionAuthority,"audit.boundary.presentationProjectionIsNotDefinitionAuthority"),l=qe(n.recoveredStudioSpecEvidenceDoesNotAuthorizeConversion,"audit.boundary.recoveredStudioSpecEvidenceDoesNotAuthorizeConversion"),u=qe(n.compilerMappingPresenceAloneDoesNotAuthorizeConversion,"audit.boundary.compilerMappingPresenceAloneDoesNotAuthorizeConversion");(!0!==o||!1!==a||!1!==s||!1!==r||!0!==c||!0!==d||!0!==l||!0!==u)&&De("audit.boundary","weakens the read-only evidence boundary"),Oe(i.localEvidence,"audit.localEvidence");let p=Oe(i.summary,"audit.summary");Be(p,"audit.summary",Je);let m=Object.fromEntries(Je.map(e=>[e,Le(p[e],`audit.summary.${e}`)]));(m.compilerMappingEligibleInstanceCount+m.remainingBlockedHistoricalInstanceCount!==m.legacyClusterInstanceCount-m.currentExecutionTupleExactInstanceCount-m.semanticEquivalenceReviewedInstanceCount||m.compilerMappingEligibleIdentityCount+m.remainingBlockedHistoricalIdentityCount!==m.definitionIdentityCount-m.currentExecutionTupleExactIdentityCount-m.semanticEquivalenceReviewedIdentityCount)&&De("audit.summary","has inconsistent historical compiler-mapping progress");let h=Oe(i.partialStudioSpecEvidence,"audit.partialStudioSpecEvidence");Be(h,"audit.partialStudioSpecEvidence",["status","authorizesConversion","sources","specifications","manualReviews"]);let f=Te(h.status,"audit.partialStudioSpecEvidence.status");"matched"!==f&&"no_matching_evidence"!==f&&De("audit.partialStudioSpecEvidence.status"),!1!==qe(h.authorizesConversion,"audit.partialStudioSpecEvidence.authorizesConversion")&&De("audit.partialStudioSpecEvidence","grants authority");let g=ze(h.sources,"audit.partialStudioSpecEvidence.sources").map(et),v=ze(h.specifications,"audit.partialStudioSpecEvidence.specifications").map(tt);ze(h.manualReviews,"audit.partialStudioSpecEvidence.manualReviews").forEach((e,t)=>Oe(e,`audit.partialStudioSpecEvidence.manualReviews[${t}]`));let y=ze(i.identities,"audit.identities").map(Qe);return m.definitionIdentityCount!==y.length&&De("audit.summary.definitionIdentityCount"),m.recoveredStudioSpecBodyCount!==v.length&&De("audit.summary.recoveredStudioSpecBodyCount"),{schemaVersion:4,kind:"registered_cluster_manifest_recovery_audit",contentHash:Te(i.contentHash,"audit.contentHash",_e),boundary:{readOnly:!0,containsWorkflowPaths:!1,containsInstanceIds:!1,containsPromptOrParameterValues:!1,historicalHashAloneIsNotExecutionAuthority:!0,presentationProjectionIsNotDefinitionAuthority:!0,recoveredStudioSpecEvidenceDoesNotAuthorizeConversion:!0,compilerMappingPresenceAloneDoesNotAuthorizeConversion:!0},summary:m,evidenceStatus:f,evidenceSources:g,specifications:v,identities:y}}function nt(){return f(`${E.serverAddress}/studio/composite-migrations/recovery-audit`,{timeoutMs:6e4,parse:it})}var ot=/^user-workflows\/([A-Za-z0-9_-]{1,96})\.json$/u;function at(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function st(e,t){return et)}function rt(...e){return e.join("\0")}function ct(e,t){return d(e)===d(t)}function dt(e){throw Error(e)}function lt(e){return"number"==typeof e&&Number.isFinite(e)&&e>0}function ut(e,t){return(!e.position||!Number.isFinite(e.position.x)||!Number.isFinite(e.position.y))&&dt(`${t} has no exact finite persisted position.`),{x:e.position.x,y:e.position.y}}function pt(e){let t=ot.exec(e);return t||dt(`Compiler candidate source path ${e} is not a saved workflow path.`),t[1]}async function mt(e,t){let i=pt(e);return f(`${E.serverAddress}/workflows/${encodeURIComponent(i)}`,{timeoutMs:12e4,signal:t,parse:t=>function(e,t){(!at(e)||"string"!=typeof e.id||!at(e.snapshot))&&dt(`Saved workflow ${t} is malformed.`);let i=pt(t);e.id!==i&&dt(`Saved workflow ${t} returned a different workflow id.`);let n=e.snapshot.nodes,o=e.snapshot.edges;return(!Array.isArray(n)||!Array.isArray(o))&&dt(`Saved workflow ${t} has no exact graph snapshot.`),{id:i,sourcePath:t,snapshot:{nodes:structuredClone(n),edges:structuredClone(o)}}}(t,e)})}function ht(e,t){return e.snapshot.nodes.filter(e=>e.id!==t&&(e.parentId===t||e.data?.huggingFaceClusterInstanceId===t)).sort((e,t)=>st(e.id,t.id))}function ft(e,t,i){e.id||dt("Legacy registered Cluster candidate has no instance id."),(!e.sourceSha256||!e.legacyCompositeHash)&&dt("Backend preview did not provide the exact source and legacy composite hashes.");let n=t.snapshot.nodes.filter(t=>t.id===e.id);1!==n.length&&dt(`Legacy Cluster ${e.id} does not resolve to exactly one saved root.`);let o=n[0],a=o.data?.huggingFaceClusterInstance;("cluster"!==o.type||"cluster"!==o.data?.type||"root"!==o.data?.huggingFaceClusterRole||!a||a.instanceId!==o.id)&&dt(`Saved node ${e.id} is not one exact legacy registered Cluster root.`),null!==a.structuralFork&&dt(`Legacy Cluster ${e.id} is structurally forked.`);let s=a.execution;s||dt(`Legacy Cluster ${e.id} has no execution receipt.`);let r=i.filter(({id:e})=>e===a.definition.id),c=r.find(e=>e.libraryRevision===a.definition.libraryRevision&&e.contentHash===a.definition.contentHash),d=e.semanticEquivalenceAuthority??null,l=e.historicalCompilerMappingAuthority??null;d&&l&&dt(`Legacy Cluster ${e.id} has conflicting historical review authorities.`);let u=d??l;if(!c){u||(r.length&&dt(`Legacy Cluster ${e.id} uses historical manifest ${a.definition.contentHash}; no reviewed archived definition or equivalence receipt is registered.`),dt(`Legacy Cluster ${e.id} references an unavailable catalog definition ${a.definition.id}.`));let t=u.historical;(t.manifestDefinitionId!==a.definition.id||t.libraryRevision!==a.definition.libraryRevision||t.manifestContentHash!==a.definition.contentHash||t.executionAdmissionId!==s.admissionId||!ct(t.studioExecutionSpec,s.studioExecutionSpec))&&dt(`Legacy Cluster ${e.id} does not match the reviewed historical compiler authority.`);let n=u.destination,o=i.filter(e=>e.id===n.manifestDefinitionId&&e.libraryRevision===n.libraryRevision&&e.contentHash===n.manifestContentHash);1!==o.length&&dt(`Legacy Cluster ${e.id} historical compiler destination is unavailable or ambiguous.`),c=o[0]}let p=u?u.destination.executionAdmissionId:s.admissionId,m=c.executionAdmissions.find(({id:e})=>e===p);m||dt(`Legacy Cluster ${e.id} references an unavailable execution admission.`),!u&&(m.studioExecutionSpec?.id!==s.studioExecutionSpec.id||m.studioExecutionSpec?.contentHash!==s.studioExecutionSpec.contentHash||m.studioExecutionSpec?.executionProfileId!==s.studioExecutionSpec.executionProfileId)&&dt(`Legacy Cluster ${e.id} has a stale Studio execution specification receipt.`);let h=P(c,m);return h||dt(`Legacy Cluster ${e.id} is not an exact current registered Block V2 route.`),(!lt(o.width)||!lt(o.height))&&dt(`Legacy Cluster ${e.id} has no exact positive persisted width and height.`),ut(o,`Legacy Cluster ${e.id}`),{candidate:e,workflow:t,root:o,children:ht(t,o.id),edges:t.snapshot.edges,instance:a,definition:c,route:h,semanticEquivalenceAuthority:d,historicalCompilerMappingAuthority:l}}function gt(e,t,i){let n=new Map(i.map(e=>[e.legacyNodeId,e.semanticNodeId])),o=new Set;return t.effectiveGraph.nodes.forEach(t=>{if(!t.semanticRole)return;o.has(t.semanticRole)&&dt(`Compiled graph has duplicate semantic role ${t.semanticRole}; legacy endpoint ownership is ambiguous.`),o.add(t.semanticRole);let i=F(e.root.id,`diffusers.cluster-execution:${encodeURIComponent(t.semanticRole)}`),a=n.get(i);a&&a!==t.nodeId&&dt(`Legacy execution endpoint ${i} maps to conflicting semantic nodes.`),n.set(i,t.nodeId)}),n}function vt(e,t,i,n){let o=("input"===t?e.effectiveInterface.boundary.inputs:e.effectiveInterface.boundary.outputs).filter(e=>function(e){return[e.binding,...e.mirrorBindings??[]].map(({nodeId:e,fieldOrPortId:t})=>({nodeId:e,fieldId:t}))}(e).some(e=>e.nodeId===i&&e.fieldId===n));return 1!==o.length&&dt(`Legacy ${t} endpoint ${i}.${n} does not resolve to exactly one V2 public port.`),o[0]}function yt(e,t,i){let n=gt(e,t,i),o=new Set([e.root.id,...e.children.map(({id:e})=>e)]),a=new Map;return Object.entries(e.root.data.params??{}).forEach(([t,i])=>{let n=i.fieldOptions?.huggingFaceClusterPortDirection;if("input"!==n&&"output"!==n)return;let o={direction:n,legacyNodeId:e.root.id,legacyPortId:t};a.set(rt(n,e.root.id,t),o)}),e.edges.forEach(e=>{let t=o.has(e.source),i=o.has(e.target);if(t!==i){if(t){e.sourceHandle||dt(`Crossing edge ${e.id} has no exact source handle.`);let t={direction:"output",legacyNodeId:e.source,legacyPortId:e.sourceHandle};a.set(rt(t.direction,t.legacyNodeId,t.legacyPortId),t)}if(i){e.targetHandle||dt(`Crossing edge ${e.id} has no exact target handle.`);let t={direction:"input",legacyNodeId:e.target,legacyPortId:e.targetHandle};a.set(rt(t.direction,t.legacyNodeId,t.legacyPortId),t)}}}),[...a.values()].map(i=>{let o=function(e,t,i,n,o){if(n!==e.root.id){let e=t.get(n);return e||dt(`Legacy boundary endpoint ${n}.${o} is not an owned projection.`),{semanticNodeId:e,fieldId:o}}let a=e.root.data.params?.[o],s=at(a?.fieldOptions)?a.fieldOptions:{};(s.huggingFaceClusterPortDirection!==i||"string"!=typeof s.huggingFaceClusterPortNodeId||"string"!=typeof s.huggingFaceClusterPortField)&&dt(`Legacy root port ${o} has incomplete ${i} metadata.`);let r=t.get(s.huggingFaceClusterPortNodeId);return r||dt(`Legacy root port ${o} targets a projection that is absent from the saved snapshot.`),{semanticNodeId:r,fieldId:s.huggingFaceClusterPortField}}(e,n,i.direction,i.legacyNodeId,i.legacyPortId);return{...i,v2PortId:vt(t,i.direction,o.semanticNodeId,o.fieldId).portId}}).sort((e,t)=>st(rt(e.direction,e.legacyNodeId,e.legacyPortId,e.v2PortId),rt(t.direction,t.legacyNodeId,t.legacyPortId,t.v2PortId)))}function xt(e,t,i){let n=e.effectiveInterface.controls.filter(e=>[e.binding,...e.mirrorBindings??[]].some(e=>e.nodeId===t&&e.fieldId===i));return 1===n.length?n[0]:null}function bt(e){return null===e?"null":Array.isArray(e)?"array":"object"==typeof e?"object":"number"==typeof e?Number.isInteger(e)?"integer":"number":typeof e}function It(e,t,i={}){if(null===e)return!0!==i.required||null===i.defaultValue;let n=_(t);return!!(Array.isArray(n)?n:[n]).some(t=>{if("string"!=typeof t)return!1;let i=t.trim().toLowerCase();return"string"===i||"text"===i||"str"===i||"dropdown"===i?"string"==typeof e:"bool"===i||"boolean"===i?"boolean"==typeof e:"int"===i||"integer"===i?"number"==typeof e&&Number.isInteger(e):"float"===i||"double"===i||"number"===i?"number"==typeof e&&Number.isFinite(e):"minimax_h3_references"===i||/^(?:list|array|sequence|tuple|set)(?:\[|$)/u.test(i)?Array.isArray(e):/^(?:dict|mapping|object|json)(?:\[|$)/u.test(i)?at(e):!!/^(?:image|video|audio|file|path|uri|url|media)(?:\b|_)/u.test(i)&&("string"==typeof e||at(e))})||void 0!==i.defaultValue&&null!==i.defaultValue&&function(e,t){let i=bt(e),n=bt(t);return i===n||"integer"===i&&"number"===n}(e,i.defaultValue)}async function Ct(e,t){let i=await O(e.definition,function(e){if(!e.semanticEquivalenceAuthority&&!e.historicalCompilerMappingAuthority)return e.root;let t=e.definition.executionAdmissions.find(({id:t})=>t===e.route.admissionId);t||dt(`Current destination admission ${e.route.admissionId} is unavailable.`);let i={...I(t.studioMode,e.definition.pipelineClass),modelType:e.definition.pipelineClass,mode:t.studioMode},{parameterOverrides:n,executionParameterOverrides:o}=R(e.definition,t,i),a=e.definition.suggestedInputs?.values??{},s=Object.fromEntries(Object.entries(n).filter(([e,t])=>!(""===t&&Object.prototype.hasOwnProperty.call(a,e)))),r=w(e.definition,e.root.id,ut(e.root,`Legacy Cluster ${e.root.id}`),{...a,...s},t.id,o);r.width=e.root.width,r.height=e.root.height;let c=r.data.huggingFaceClusterInstance;return c?.execution||dt(`Current destination compiler root ${e.root.id} has no execution receipt.`),r.data.huggingFaceClusterInstance={...c,presentation:{...c.presentation,expanded:e.instance.presentation.expanded}},r}(e),{timeoutMs:t}),n=[...i.definition.contentHash===e.route.compiledDefinitionContentHash?[]:["definition content hash"],...i.instance.definitionSnapshot.source.executionAdmissionId===e.route.admissionId?[]:["execution admission"],...i.instance.authorities.length?["instance authorities"]:[],...i.instance.effectiveGraph.graphHash===i.definition.graph.graphHash?[]:["effective graph"]];n.length&&dt(`Compiled registered definition for ${e.root.id} is stale or structurally customized (${n.join(", ")}).`);let o=e.semanticEquivalenceAuthority??e.historicalCompilerMappingAuthority;o&&(ct(o.destination,{manifestDefinitionId:e.route.definitionId,libraryRevision:e.route.libraryRevision,manifestContentHash:e.route.definitionContentHash,executionAdmissionId:e.route.admissionId,blockDefinitionId:i.definition.definitionId,blockDefinitionContentHash:i.definition.contentHash,blockDefinitionCanonicalSha256:e.route.compiledDefinitionCanonicalSha256,executionGraphHash:i.definition.graph.graphHash,interfaceHash:g({boundary:i.definition.boundary,controls:i.definition.controls})})||dt(`Reviewed historical compiler authority for ${e.root.id} is stale for the compiled BlockDefinitionV2 graph or interface.`));let a=function(e,t){let i=new Map;t.effectiveGraph.nodes.forEach(e=>{let t=e.semanticRole;t&&i.set(t,[...i.get(t)??[],e.nodeId])});let n=e.children.map(t=>{let n=t.data,o=n?.huggingFaceClusterExecutionRole;("execution"!==n?.huggingFaceClusterRole||n.huggingFaceClusterInstanceId!==e.root.id||n.huggingFaceClusterExecutionAdmissionId!==e.instance.execution?.admissionId||n.huggingFaceClusterExecutionSpecId!==e.instance.execution.studioExecutionSpec.id||!o)&&dt(`Legacy projection ${t.id} has incomplete or stale execution ownership.`);let a=i.get(o)??[];return 1!==a.length&&dt(`Legacy projection ${t.id} cannot be mapped one-to-one to semantic role ${o}.`),{legacyNodeId:t.id,semanticNodeId:a[0]}}),o=n.map(({semanticNodeId:e})=>e);return new Set(o).size!==o.length&&dt(`Legacy Cluster ${e.root.id} would merge multiple projection nodes.`),n.sort((e,t)=>st(rt(e.legacyNodeId,e.semanticNodeId),rt(t.legacyNodeId,t.semanticNodeId)))}(e,i.instance),s=function(e,t,i){let n={...t.presentation.internalLayout};return i.forEach(({legacyNodeId:t,semanticNodeId:i})=>{let o=e.children.find(({id:e})=>e===t);n[i]={...ut(o,`Legacy projection ${t}`),...lt(o.width)?{width:o.width}:{},...lt(o.height)?{height:o.height}:{}}}),x({...t,presentation:{...t.presentation,internalLayout:n}})}(e,i.instance,a),r=function(e,t,i){let n=gt(e,t,i),o=[],a={...t.values},s=new Map;(function(e){let t=[];return[e.root,...e.children].sort((e,t)=>st(e.id,t.id)).forEach(e=>{Object.entries(e.data.params??{}).sort(([e],[t])=>st(e,t)).forEach(([i,n])=>{Object.prototype.hasOwnProperty.call(n,"value")&&"output"!==n.display&&t.push({sourceKind:"node_param",sourceNodeId:e.id,sourceFieldId:i,value:structuredClone(n.value)})})}),Object.entries(e.instance.parameterOverrides).sort(([e],[t])=>st(e,t)).forEach(([i,n])=>t.push({sourceKind:"instance_parameter_override",sourceNodeId:e.root.id,sourceFieldId:i,value:structuredClone(n)})),Object.entries(e.instance.execution?.parameterOverrides??{}).sort(([e],[t])=>st(e,t)).forEach(([i,n])=>t.push({sourceKind:"execution_parameter_override",sourceNodeId:e.root.id,sourceFieldId:i,value:structuredClone(n)})),t})(e).forEach(i=>{let r=function(e,t,i,n){if("instance_parameter_override"===n.sourceKind||"execution_parameter_override"===n.sourceKind)return new Set([...t.effectiveInterface.controls.map(({controlId:e})=>e),...t.effectiveInterface.boundary.inputs.map(({portId:e})=>e)]).has(n.sourceFieldId)||dt(`Legacy override ${n.sourceKind}/${n.sourceFieldId} has no exact V2 input/control.`),{targetKind:"instance_value",targetValueId:n.sourceFieldId};if(n.sourceNodeId===e.root.id){let o=e.root.data.params[n.sourceFieldId]?.fieldOptions;if("input"===o?.huggingFaceClusterPortDirection&&"string"==typeof o.huggingFaceClusterPortNodeId&&"string"==typeof o.huggingFaceClusterPortField){let e=i.get(o.huggingFaceClusterPortNodeId),a=e?xt(t,e,o.huggingFaceClusterPortField):null;return a||dt(`Legacy root value ${n.sourceFieldId} has ambiguous input-port ownership.`),{targetKind:"instance_value",targetValueId:a.controlId}}return new Set([...t.effectiveInterface.controls.map(({controlId:e})=>e),...t.effectiveInterface.boundary.inputs.map(({portId:e})=>e)]).has(n.sourceFieldId)||dt(`Legacy root value ${n.sourceFieldId} has no exact V2 input/control.`),{targetKind:"instance_value",targetValueId:n.sourceFieldId}}let o=i.get(n.sourceNodeId);o||dt(`Legacy value ${n.sourceNodeId}.${n.sourceFieldId} is outside the owned projection.`);let a=function(e){let t=e?.fieldOptions?.huggingFaceClusterBinding;return!at(t)||1!==t.schemaVersion||"string"!=typeof t.source||"instance_input"!==t.persistence&&"execution_parameter"!==t.persistence&&"sealed"!==t.persistence||void 0!==t.input&&"string"!=typeof t.input?null:t}(e.children.find(({id:e})=>e===n.sourceNodeId).data.params[n.sourceFieldId]);if("instance_input"===a?.persistence||"execution_parameter"===a?.persistence){let e=xt(t,o,n.sourceFieldId);return e||dt(`Legacy mutable field ${n.sourceNodeId}.${n.sourceFieldId} has no exact V2 control.`),{targetKind:"instance_value",targetValueId:e.controlId}}let s=t.effectiveGraph.nodes.find(({nodeId:e})=>e===o),r=(at(s?.data.params)?s.data.params:{})[n.sourceFieldId];return(!at(r)||!Object.prototype.hasOwnProperty.call(r,"value"))&&dt(`Legacy static field ${n.sourceNodeId}.${n.sourceFieldId} has no exact V2 graph parameter.`),{targetKind:"graph_param",targetNodeId:o,targetFieldId:n.sourceFieldId}}(e,t,n,i);if("instance_value"===r.targetKind){!function(e,t,i){let n=e.effectiveInterface.controls.filter(({controlId:e})=>e===t),o=e.effectiveInterface.boundary.inputs.filter(({portId:e})=>e===t);n.length+o.length===0&&dt(`Legacy value ${t} does not target a declared V2 input/control.`),n.forEach(e=>{e.sealed&&!ct(i,e.defaultValue)&&dt(`Legacy value ${t} attempts to rewrite a sealed V2 control.`),It(i,e.valueType,{...void 0===e.defaultValue?{}:{defaultValue:e.defaultValue},required:e.required})||dt(`Legacy value ${t} is incompatible with V2 control type ${String(e.valueType)}.`)}),o.forEach(e=>{It(i,e.valueType,{required:e.required})||dt(`Legacy value ${t} is incompatible with V2 input type ${String(e.valueType)}.`)})}(t,r.targetValueId,i.value);let e=s.get(r.targetValueId);void 0!==e&&!ct(e,i.value)&&dt(`Legacy sources disagree for V2 input/control ${r.targetValueId}.`),s.set(r.targetValueId,i.value),a[r.targetValueId]=i.value}else{let e=t.effectiveGraph.nodes.find(({nodeId:e})=>e===r.targetNodeId).data.params[r.targetFieldId];(!at(e)||!ct(e.value,i.value))&&dt(`Legacy static value ${i.sourceNodeId}.${i.sourceFieldId} differs from the pinned definition.`)}o.push({sourceKind:i.sourceKind,sourceNodeId:i.sourceNodeId,sourceFieldId:i.sourceFieldId,...r})}),o.sort((e,t)=>st(rt(e.sourceKind,e.sourceNodeId,e.sourceFieldId,e.targetKind),rt(t.sourceKind,t.sourceNodeId,t.sourceFieldId,t.targetKind)));let r=t.effectiveInterface.boundary.inputs.some(({portId:e})=>Object.prototype.hasOwnProperty.call(a,e))||t.effectiveInterface.controls.some(e=>Object.prototype.hasOwnProperty.call(a,e.controlId)&&!ct(a[e.controlId],e.defaultValue));return{mappings:o,instance:x({...t,values:a,customization:{...t.customization,state:r?"parameters_changed":"unchanged"}})}}(e,s,a);s=r.instance;let c=function(e,t,i){let n=gt(e,t,i),o=structuredClone(t.previewStates),a=[];return function(e){return[e.root,...e.children].flatMap(e=>Object.entries(e.data.params??{}).flatMap(([t,i])=>"output"!==i.display||null===i.value||void 0===i.value?[]:(("string"!=typeof i.value||!i.value)&&dt(`Legacy output ${e.id}.${t} is not a persistable media reference.`),[{node:e,fieldId:t,mediaReference:i.value}])))}(e).forEach(({node:t,fieldId:i,mediaReference:s})=>{let r,c=i;if(t.id===e.root.id){let e=t.data.params[i]?.fieldOptions,o="string"==typeof e?.huggingFaceClusterPreviewSourceNodeId?e.huggingFaceClusterPreviewSourceNodeId:"output"===e?.huggingFaceClusterPortDirection&&"string"==typeof e.huggingFaceClusterPortNodeId?e.huggingFaceClusterPortNodeId:null,a="string"==typeof e?.huggingFaceClusterPreviewSourceField?e.huggingFaceClusterPreviewSourceField:"output"===e?.huggingFaceClusterPortDirection&&"string"==typeof e.huggingFaceClusterPortField?e.huggingFaceClusterPortField:null;(!o||!a)&&dt(`Legacy root output ${i} has no exact preview-source metadata.`),r=n.get(o),c=a}else r=n.get(t.id);r||dt(`Legacy output ${t.id}.${i} targets an absent projection.`);let d=o.map((e,t)=>({state:e,index:t})).filter(({state:e})=>e.binding.nodeId===r&&e.binding.outputPortId===c);1!==d.length&&dt(`Legacy output ${t.id}.${i} does not resolve to exactly one V2 preview binding.`);let l=d[0];l.state.mediaReference&&l.state.mediaReference!==s&&dt(`Legacy outputs conflict for V2 preview ${r}.${c}.`),o[l.index]={...l.state,mediaReference:s,status:"complete"},a.push({sourceNodeId:t.id,sourceFieldId:i,targetNodeId:r,targetOutputPortId:c})}),a.sort((e,t)=>st(rt(e.sourceNodeId,e.sourceFieldId,e.targetNodeId,e.targetOutputPortId),rt(t.sourceNodeId,t.sourceFieldId,t.targetNodeId,t.targetOutputPortId))),{mappings:a,instance:x({...t,previewStates:o})}}(e,s,a);s=c.instance;let d=new Set([e.root.id,...e.children.map(({id:e})=>e)]),l=e.edges.filter(e=>d.has(e.source)&&d.has(e.target)).map(t=>(t.id||dt(`Legacy Cluster ${e.root.id} has an internal edge without an id.`),t.id)).sort(st);return new Set(l).size!==l.length&&dt(`Legacy Cluster ${e.root.id} has duplicate internal edge ids.`),{legacyInstanceId:e.root.id,legacyCompositeHash:e.candidate.legacyCompositeHash,admissionId:e.route.admissionId,compiledDefinitionContentHash:e.route.compiledDefinitionContentHash,compiledDefinitionCanonicalSha256:e.route.compiledDefinitionCanonicalSha256,blockInstanceV2:s,ownedNodeMappings:a,portMappings:yt(e,s,a),valueMappings:r.mappings,previewMappings:c.mappings,absorbedInternalEdgeIds:l,...e.semanticEquivalenceAuthority?{semanticEquivalenceReceipt:{receiptId:e.semanticEquivalenceAuthority.receiptId,receiptHash:e.semanticEquivalenceAuthority.receiptHash}}:{},...e.historicalCompilerMappingAuthority?{historicalCompilerMapping:{mappingId:e.historicalCompilerMappingAuthority.mappingId,mappingHash:e.historicalCompilerMappingAuthority.mappingHash}}:{}}}function wt(e,t){let i=e instanceof Error?e.message:String(e),n=i.includes("historical manifest")?"historical_manifest_unavailable":i.includes("no execution receipt")?"execution_receipt_missing":i.includes("source and legacy composite hashes")?"candidate_receipt_missing":i.includes("saved root")||i.includes("projection")||i.includes("ambiguous")?"legacy_snapshot_ambiguous":i.includes("route")||i.includes("admission")||i.includes("catalog definition")?"registered_route_unavailable":i.includes("workflow")?"workflow_unavailable":"compiler_failed";return{sourcePath:t.sourcePath,instanceId:t.id,code:n,message:i}}async function $t(e){let t=l(),i=e.candidates.filter(({kind:e,status:t})=>"legacy_registered_cluster_instance"===e&&"blocked"===t).sort((e,t)=>st(rt(e.sourcePath,e.id??""),rt(t.sourcePath,t.id??""))),n=await async function(){let e=b.getState(),t=v.getState();await Promise.all([e.loaded?Promise.resolve():e.fetchLibrary(),Object.keys(t.nodesRegistry).length?Promise.resolve():t.fetchNodes(),t.studioModelCapabilities.length?Promise.resolve():t.fetchStudioModelCapabilities()]);let i=b.getState();return i.library||dt(i.error||"The Hugging Face node library is unavailable."),Object.keys(v.getState().nodesRegistry).length||dt("The backend node registry is unavailable."),i.library}();if(n.definitions.some(({provider:e})=>"diffusers"===e)){let e=C.getState();e.loaded||await e.fetchSnapshot()}m(t,{includeForm:!1})||dt("The active workflow changed while compiler dependencies were loading. Generate a fresh supplement.");let o=null,a=[],s=new Map,r=0;for(let c of i){if(e.signal?.aborted)throw new DOMException("Compiler supplement generation was cancelled.","AbortError");m(t,{includeForm:!1})||dt("The active workflow changed during compiler supplement generation. Generate a fresh supplement.");try{o?.sourcePath!==c.sourcePath&&(o=await mt(c.sourcePath,e.signal));let i=o;m(t,{includeForm:!1})||dt("The active workflow changed while a saved workflow was loading. Generate a fresh supplement.");let a=await Ct(ft(c,i,n.definitions),e.timeoutMs);m(t,{includeForm:!1})||dt("The active workflow changed during compiler supplement generation. Generate a fresh supplement."),s.set(c.sourcePath,[...s.get(c.sourcePath)??[],a])}catch(e){if(!m(t,{includeForm:!1}))throw e;a.push(wt(e,c))}finally{r+=1,e.onProgress?.({completed:r,total:i.length,sourcePath:c.sourcePath,instanceId:c.id})}}let c=ge({schemaVersion:1,kind:"registered_cluster_v2_compiler_supplement",compilerOutputs:[...s.entries()].sort(([e],[t])=>st(e,t)).map(([e,t])=>({sourcePath:e,sourceSha256:i.find(({sourcePath:t})=>t===e).sourceSha256,conversions:t.sort((e,t)=>st(e.legacyInstanceId,t.legacyInstanceId))}))});return(new TextEncoder).encode(JSON.stringify(c)).byteLength>33554432&&dt("Generated compiler supplement exceeds the reviewed 32 MiB request limit."),{supplement:c,diagnostics:a.sort((e,t)=>st(rt(e.sourcePath,e.instanceId??"",e.code),rt(t.sourcePath,t.instanceId??"",t.code))),candidateCount:i.length,conversionCount:c.compilerOutputs.reduce((e,t)=>e+t.conversions.length,0)}}var Nt=a();function St(e){return e.replace(/_/g," ")}function jt(e){return"applied"===e||"rolled_back"===e?"success":"interrupted"===e?"warning":"error"}function kt({audit:e,error:t,loading:i,onRefresh:n}){let a=e?.identities.filter(e=>"partial_evidence_available"===e.recoveredStudioSpecs.status)??[];return(0,Nt.jsxs)("div",{className:"grid gap-3","data-testid":"legacy-cluster-recovery-evidence-panel",children:[(0,Nt.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Nt.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"This read-only audit reports historical recovery evidence and checked-in compiler-mapping coverage. It does not compile, convert, execute, approve, or mutate a Block by itself. Mapping-ready instances still require an exact compiler supplement, reviewed preview, and explicit apply confirmation."}),(0,Nt.jsx)(M,{size:"compact",tone:"secondary",icon:(0,Nt.jsx)(c,{size:14}),loading:i,disabled:i,onClick:n,"data-testid":"legacy-cluster-recovery-evidence-refresh",children:"Refresh evidence"})]}),t?(0,Nt.jsxs)("div",{className:"flex items-start gap-2 rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/5 p-2 text-xs text-modiff-red",children:[(0,Nt.jsx)(o,{size:14,className:"mt-0.5 shrink-0"}),(0,Nt.jsx)("span",{className:"break-words",children:t})]}):null,e?(0,Nt.jsxs)(Nt.Fragment,{children:[(0,Nt.jsxs)("div",{className:"flex flex-wrap gap-2","data-testid":"legacy-cluster-recovery-evidence-summary",children:[(0,Nt.jsx)($,{label:`${e.summary.legacyClusterInstanceCount} saved Blocks`,tone:"neutral"}),(0,Nt.jsx)($,{label:`${e.summary.compilerMappingEligibleInstanceCount} mapping-ready instances`,tone:e.summary.compilerMappingEligibleInstanceCount?"success":"neutral"}),(0,Nt.jsx)($,{label:`${e.summary.compilerMappingEligibleIdentityCount} mapping-ready ${1===e.summary.compilerMappingEligibleIdentityCount?"identity":"identities"}`,tone:e.summary.compilerMappingEligibleIdentityCount?"success":"neutral"}),(0,Nt.jsx)($,{label:`${e.summary.remainingBlockedHistoricalInstanceCount} historical still blocked`,tone:e.summary.remainingBlockedHistoricalInstanceCount?"warning":"success"}),(0,Nt.jsx)($,{label:`${e.summary.remainingBlockedHistoricalIdentityCount} blocked ${1===e.summary.remainingBlockedHistoricalIdentityCount?"identity":"identities"}`,tone:e.summary.remainingBlockedHistoricalIdentityCount?"warning":"success"}),(0,Nt.jsx)($,{label:`${e.summary.recoveredStudioSpecBodyCount} recovered bodies`,tone:"neutral"}),(0,Nt.jsx)($,{label:`${e.summary.recoveredStudioSpecManifestIdentityCount} identities`,tone:"neutral"}),(0,Nt.jsx)($,{label:`${e.summary.recoveredStudioSpecExecutionTupleCount} execution tuples`,tone:"neutral"}),(0,Nt.jsx)($,{label:`${e.summary.recoveredStudioSpecInstanceCount} covered instances`,tone:"neutral"}),(0,Nt.jsx)($,{label:`${e.summary.partialReviewVerifiedExecutionTupleCount} partially reviewed`,tone:e.summary.partialReviewVerifiedExecutionTupleCount?"warning":"neutral"})]}),(0,Nt.jsx)("div",{className:"rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-2 text-xs leading-5 text-modiff-text",children:"Read-only boundary: no workflow paths, instance IDs, prompts, or parameter values are included. A valid historical Studio-spec self-hash is partial evidence only; it is not manifest, interface, artifact, or execution authority. A checked-in compiler mapping covers only its exact archived tuple and does not, by its presence alone, authorize conversion or workflow mutation."}),(0,Nt.jsxs)("section",{className:"grid gap-1.5",children:[(0,Nt.jsxs)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:["Recovered Studio bodies (",e.specifications.length,")"]}),e.specifications.length?(0,Nt.jsx)("div",{className:"grid max-h-72 gap-2 overflow-auto","data-testid":"legacy-cluster-recovered-specifications",children:e.specifications.map(e=>(0,Nt.jsxs)("article",{className:"grid gap-1 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs",children:[(0,Nt.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,Nt.jsxs)("div",{className:"min-w-0",children:[(0,Nt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.identity.id}),(0,Nt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.modelType," · ",e.mode," · ",e.identity.executionProfileId]})]}),(0,Nt.jsx)($,{label:`${e.roleCount} roles · ${e.edgeCount} edges`,tone:"neutral"})]}),(0,Nt.jsxs)("p",{className:"break-all font-mono text-modiff-subtle-text",children:[e.identity.contentHash," · ",e.canonicalBodySha256]}),(0,Nt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.bindingCount," bindings · ",e.actionCount," dynamic actions · sources:"," ",e.sourceIds.join(", ")]})]},e.canonicalBodySha256))}):(0,Nt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No exact local capture contains a matching body."})]}),(0,Nt.jsxs)("section",{className:"grid gap-1.5",children:[(0,Nt.jsxs)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:["Historical identities with partial evidence (",a.length,")"]}),a.length?(0,Nt.jsx)("div",{className:"grid max-h-80 gap-2 overflow-auto","data-testid":"legacy-cluster-recovered-identities",children:a.map(e=>(0,Nt.jsxs)("article",{className:"grid gap-1.5 rounded-modiff-compact border border-hf-orange/30 bg-modiff-bg p-2 text-xs",children:[(0,Nt.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,Nt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.definitionId??"Malformed identity"}),(0,Nt.jsx)($,{label:`${e.instanceCount} instances`,tone:"warning"})]}),(0,Nt.jsxs)("p",{className:"break-all font-mono text-modiff-subtle-text",children:[e.libraryRevision??"missing revision"," ·"," ",e.manifestContentHash??"missing hash"]}),e.executionTuples.filter(e=>"self_hash_valid_partial_evidence"===e.recoveredStudioSpec.status).map((e,t)=>(0,Nt.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border p-2",children:[(0,Nt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.studioExecutionSpec?.id??"Missing Studio receipt"}),(0,Nt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.instanceCount," instances · review ",St(e.manualReview.status)," · conversion authority: none"]})]},`${e.admissionId??"missing"}:${e.studioExecutionSpec?.contentHash??t}`)),(0,Nt.jsxs)("p",{className:"text-hf-orange",children:["Still missing: ",e.missingEvidence.map(({code:e})=>St(e)).join("; ")]})]},`${e.definitionId}:${e.libraryRevision}:${e.manifestContentHash}`))}):(0,Nt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No historical identity has recovered partial evidence."})]}),(0,Nt.jsxs)("p",{className:"break-all font-mono text-xs text-modiff-subtle-text",children:["Audit: ",e.contentHash]})]}):i||t?null:(0,Nt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No historical recovery audit is available."})]})}function Ht({preview:e}){let t=[...new Map(e.candidates.flatMap(e=>{let t=e.compilerReceipt?.semanticEquivalenceAuthority??e.semanticEquivalenceAuthority;return t?[[t.receiptHash,t]]:[]})).values()],i=[...new Map(e.candidates.flatMap(e=>{let t=e.compilerReceipt?.historicalCompilerMappingAuthority??e.historicalCompilerMappingAuthority;return t?[[t.mappingHash,t]]:[]})).values()];return(0,Nt.jsxs)("div",{className:"grid gap-3","data-testid":"composite-migration-preview-details",children:[(0,Nt.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Nt.jsx)($,{label:`${e.summary.targetFileCount} target files`,tone:"neutral"}),(0,Nt.jsx)($,{label:`${e.summary.convertibleCandidateCount} convertible`,tone:e.summary.convertibleCandidateCount?"success":"neutral"}),e.compilerSupplement?(0,Nt.jsx)($,{label:`${e.summary.registeredClusterConvertibleCount??0} registered Blocks compiled`,tone:e.compilerSupplement.provided?"success":"neutral"}):null,(0,Nt.jsx)($,{label:`${e.summary.blockedCandidateCount} blocked`,tone:e.summary.blockedCandidateCount?"warning":"success"})]}),(0,Nt.jsxs)("div",{className:"grid gap-1 text-xs text-modiff-subtle-text",children:[(0,Nt.jsxs)("p",{className:"break-all",children:["Preview: ",(0,Nt.jsx)("span",{className:"font-mono text-modiff-text",children:e.migrationId})]}),(0,Nt.jsxs)("p",{className:"break-all",children:["Plan hash: ",(0,Nt.jsx)("span",{className:"font-mono text-modiff-text",children:e.planHash})]}),e.compilerSupplement?.contentHash?(0,Nt.jsxs)("p",{className:"break-all",children:["Compiler supplement:"," ",(0,Nt.jsx)("span",{className:"font-mono text-modiff-text",children:e.compilerSupplement.contentHash})]}):null]}),(0,Nt.jsxs)("section",{className:"grid gap-1.5",children:[(0,Nt.jsx)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Exact files that would change"}),e.targets.length?(0,Nt.jsx)("ul",{className:"max-h-44 space-y-1 overflow-auto rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:e.targets.map(e=>(0,Nt.jsx)("li",{className:"break-all font-mono text-xs text-modiff-text",children:e.sourcePath},e.sourcePath))}):(0,Nt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No safe conversion target is present."})]}),t.length?(0,Nt.jsxs)("section",{className:"grid gap-1.5","data-testid":"composite-migration-equivalence-diffs",children:[(0,Nt.jsx)("h4",{className:"text-xs font-bold uppercase text-hf-yellow",children:"Reviewed historical equivalence (preview only)"}),(0,Nt.jsx)("div",{className:"grid max-h-72 gap-2 overflow-auto",children:t.map(e=>(0,Nt.jsxs)("article",{className:"grid gap-1 rounded-modiff-compact border border-hf-yellow/30 bg-hf-yellow/5 p-2 text-xs",children:[(0,Nt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.receiptId}),(0,Nt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Historical manifest: ",(0,Nt.jsx)("span",{className:"font-mono",children:e.historical.manifestContentHash})]}),(0,Nt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Historical graph/interface:"," ",(0,Nt.jsxs)("span",{className:"font-mono",children:[e.historical.executionGraphHash," · ",e.historical.interfaceHash]})]}),(0,Nt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Destination definition:"," ",(0,Nt.jsx)("span",{className:"font-mono",children:e.destination.blockDefinitionContentHash})]}),(0,Nt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Destination graph/interface:"," ",(0,Nt.jsxs)("span",{className:"font-mono",children:[e.destination.executionGraphHash," · ",e.destination.interfaceHash]})]}),(0,Nt.jsxs)("p",{className:"text-modiff-subtle-text",children:["Reviewed by ",e.review.issuer," at ",e.review.reviewedAt,": ",e.review.notes]}),(0,Nt.jsx)("p",{className:"text-hf-yellow",children:"This checked-in review enables exact compiler validation only; it does not apply or mutate a workflow."})]},e.receiptHash))})]}):null,i.length?(0,Nt.jsxs)("section",{className:"grid gap-1.5","data-testid":"composite-migration-archived-compiler-mappings",children:[(0,Nt.jsx)("h4",{className:"text-xs font-bold uppercase text-hf-yellow",children:"Reviewed archived compiler mapping (preview only)"}),(0,Nt.jsx)("div",{className:"grid max-h-72 gap-2 overflow-auto",children:i.map(e=>(0,Nt.jsxs)("article",{className:"grid gap-1 rounded-modiff-compact border border-hf-yellow/30 bg-hf-yellow/5 p-2 text-xs",children:[(0,Nt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.mappingId}),(0,Nt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Archived manifest: ",(0,Nt.jsx)("span",{className:"font-mono",children:e.historical.manifestContentHash})]}),(0,Nt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Archive record: ",(0,Nt.jsx)("span",{className:"font-mono",children:e.historical.archivedDefinitionRecordHash})]}),(0,Nt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Destination definition:"," ",(0,Nt.jsx)("span",{className:"font-mono",children:e.destination.blockDefinitionContentHash})]}),(0,Nt.jsxs)("p",{className:"break-all text-modiff-subtle-text",children:["Destination graph/interface:"," ",(0,Nt.jsxs)("span",{className:"font-mono",children:[e.destination.executionGraphHash," · ",e.destination.interfaceHash]})]}),(0,Nt.jsxs)("p",{className:"text-modiff-subtle-text",children:["Reviewed by ",e.review.issuer," at ",e.review.reviewedAt,": ",e.review.notes]}),(0,Nt.jsx)("p",{className:"text-hf-yellow",children:"This maps one exact archived body to one exact compiler destination. Per-instance preservation, explicit Apply, byte backup, and rollback are still required."})]},e.mappingHash))})]}):null,e.blocked.length?(0,Nt.jsxs)("section",{className:"grid gap-1.5","data-testid":"composite-migration-blockers",children:[(0,Nt.jsx)("h4",{className:"text-xs font-bold uppercase text-hf-orange",children:"Blocked candidates remain unchanged"}),(0,Nt.jsx)("ul",{className:"max-h-44 space-y-2 overflow-auto rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-2",children:e.blocked.map((e,t)=>(0,Nt.jsxs)("li",{className:"text-xs",children:[(0,Nt.jsx)("p",{className:"break-all font-mono text-modiff-text",children:e.sourcePath}),(0,Nt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.id?`${e.id}: `:"",e.reason]})]},`${e.sourcePath}:${e.kind}:${e.id??t}`))})]}):null,e.inventoryIssues.length?(0,Nt.jsxs)("section",{className:"grid gap-1.5",children:[(0,Nt.jsx)("h4",{className:"text-xs font-bold uppercase text-hf-orange",children:"Inventory issues"}),(0,Nt.jsx)("ul",{className:"max-h-36 space-y-1 overflow-auto text-xs text-modiff-subtle-text",children:e.inventoryIssues.map((e,t)=>(0,Nt.jsxs)("li",{children:[(0,Nt.jsx)("span",{className:"break-all font-mono text-modiff-text",children:e.sourcePath}),": ",e.message]},`${e.sourcePath}:${e.path}:${e.code}:${t}`))})]}):null]})}function Pt({allowBlockedCandidates:e,busy:t,confirmation:i,error:n,onAllowBlockedCandidatesChange:o,onApply:a,onCancel:s,onConfirmationChange:r,preview:c}){let d=le(c,i,e);return(0,Nt.jsxs)("div",{className:"grid gap-4","data-testid":"composite-migration-apply-review",children:[(0,Nt.jsx)("div",{className:"rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-3 text-xs leading-5 text-modiff-text",children:"This action creates exact backups and replaces only the listed safe Block or workflow files. It never deletes, renames, or merges records. Blocked candidates remain unchanged."}),(0,Nt.jsx)(Ht,{preview:c}),c.summary.blockedCandidateCount>0?(0,Nt.jsx)(N,{checked:e,onCheckedChange:o,label:`Apply only the ${c.summary.targetFileCount} safe target files and leave all ${c.summary.blockedCandidateCount} blocked candidates unchanged.`,"data-testid":"composite-migration-allow-blocked"}):null,(0,Nt.jsx)(j,{label:(0,Nt.jsxs)("span",{children:["Type ",(0,Nt.jsx)("code",{className:"font-mono text-hf-yellow",children:T})]}),required:!0,error:n??void 0,children:(0,Nt.jsx)(A,{autoComplete:"off",spellCheck:!1,value:i,onChange:e=>r(e.currentTarget.value),"data-testid":"composite-migration-apply-confirmation"})}),(0,Nt.jsxs)("div",{className:"flex flex-wrap justify-end gap-2 border-t border-modiff-border pt-3",children:[(0,Nt.jsx)(M,{disabled:t,onClick:s,children:"Cancel"}),(0,Nt.jsx)(M,{tone:"primary",loading:t,disabled:t||!d,onClick:a,"data-testid":"composite-migration-apply",children:"Apply reviewed migration"})]})]})}function At({busyId:e,migrations:i,onInspectRollback:n}){return i.length?(0,Nt.jsx)("div",{className:"grid max-h-72 gap-2 overflow-auto","data-testid":"composite-migration-recovery-list",children:i.map(i=>(0,Nt.jsxs)("section",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,Nt.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Nt.jsxs)("div",{className:"min-w-0",children:[(0,Nt.jsx)("p",{className:"truncate font-mono text-xs text-modiff-text",title:i.migrationId,children:i.migrationId}),(0,Nt.jsxs)("p",{className:"text-xs text-modiff-subtle-text",children:[i.targets.length," targets · ",St(i.journalState)," journal"]})]}),(0,Nt.jsx)($,{label:St(i.effectiveState),tone:jt(i.effectiveState)})]}),(0,Nt.jsx)("ul",{className:"space-y-1",children:i.targets.map(e=>(0,Nt.jsxs)("li",{className:"flex min-w-0 items-start justify-between gap-2 text-xs",children:[(0,Nt.jsx)("span",{className:"break-all font-mono text-modiff-text",children:e.sourcePath}),(0,Nt.jsx)("span",{className:p("shrink-0 font-semibold","conflict"===e.state||"missing"===e.state?"text-modiff-red":"applied"===e.state?"text-modiff-green":"text-modiff-subtle-text"),children:e.state})]},e.sourcePath))}),i.rollbackAvailable?(0,Nt.jsx)("div",{children:(0,Nt.jsx)(M,{size:"compact",tone:"secondary",icon:(0,Nt.jsx)(t,{size:14}),loading:e===i.migrationId,disabled:!!e,onClick:()=>n(i),"data-testid":`composite-migration-inspect-${i.migrationId}`,children:"Review rollback"})}):null]},i.migrationId))}):(0,Nt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No migration recovery journals are present."})}function Et({busy:e,confirmation:t,error:i,onCancel:n,onConfirmationChange:o,onRollback:a,status:s}){return(0,Nt.jsxs)("div",{className:"grid gap-4","data-testid":"composite-migration-rollback-review",children:[(0,Nt.jsx)("div",{className:"rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-3 text-xs leading-5 text-modiff-text",children:"Rollback restores the exact backups for this journal. It refuses missing or independently changed files and has no force option."}),(0,Nt.jsxs)("section",{className:"grid gap-1.5",children:[(0,Nt.jsx)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Exact files to restore"}),(0,Nt.jsx)("ul",{className:"max-h-44 space-y-1 overflow-auto rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:s.targets.map(e=>(0,Nt.jsxs)("li",{className:"break-all font-mono text-xs text-modiff-text",children:[e.sourcePath," ",(0,Nt.jsxs)("span",{className:"text-modiff-subtle-text",children:["(",e.state,")"]})]},e.sourcePath))})]}),(0,Nt.jsx)(j,{label:(0,Nt.jsxs)("span",{children:["Type ",(0,Nt.jsx)("code",{className:"font-mono text-hf-yellow",children:V})]}),required:!0,error:i??void 0,children:(0,Nt.jsx)(A,{autoComplete:"off",spellCheck:!1,value:t,onChange:e=>o(e.currentTarget.value),"data-testid":"composite-migration-rollback-confirmation"})}),(0,Nt.jsxs)("div",{className:"flex flex-wrap justify-end gap-2 border-t border-modiff-border pt-3",children:[(0,Nt.jsx)(M,{disabled:e,onClick:n,children:"Cancel"}),(0,Nt.jsx)(M,{tone:"danger",loading:e,disabled:e||"ROLLBACK_BLOCK_V2_MIGRATION"!==t,onClick:a,"data-testid":"composite-migration-rollback",children:"Restore exact backups"})]})]})}function Rt(){let e=H(e=>e.workflowTabs.some(({dirty:e})=>e)),[t,a]=(0,B.useState)(null),[s,d]=(0,B.useState)([]),[l,u]=(0,B.useState)(!0),[p,m]=(0,B.useState)(null),[f,g]=(0,B.useState)(null),[v,x]=(0,B.useState)(!0),[b,I]=(0,B.useState)(null),[C,w]=(0,B.useState)(null),[N,P]=(0,B.useState)(null),[A,E]=(0,B.useState)(""),[R,_]=(0,B.useState)(null),[F,D]=(0,B.useState)([]),[O,T]=(0,B.useState)(null),[V,L]=(0,B.useState)(!1),[q,z]=(0,B.useState)(""),[K,G]=(0,B.useState)(!1),[J,Z]=(0,B.useState)(null),[W,Y]=(0,B.useState)(""),[U,X]=(0,B.useState)(null),Q=(0,B.useRef)(0),ee=(0,B.useRef)(0),te=(0,B.useRef)(0),ie=(0,B.useRef)(null),ne=(0,B.useRef)(!1),oe=(0,B.useRef)(null),ae=(0,B.useCallback)(async()=>{let e=++Q.current;u(!0);try{let[t,i]=await Promise.all([He(oe.current??void 0),Pe()]);if(e!==Q.current)return;a(t),d(i.migrations),m(null)}catch(t){e===Q.current&&m(ke(t,"Could not inspect legacy composite migrations."))}finally{e===Q.current&&u(!1)}},[]),se=(0,B.useCallback)(async()=>{let e=++ee.current;x(!0);try{let t=await nt();if(e!==ee.current)return;g(t),I(null)}catch(t){e===ee.current&&I(ke(t,"Could not inspect historical Block recovery evidence."))}finally{e===ee.current&&x(!1)}},[]);(0,B.useEffect)(()=>(ae(),se(),()=>{Q.current+=1,ee.current+=1,te.current+=1,ie.current?.abort()}),[ae,se]);let re=async(e,t)=>{if(!ne.current){ne.current=!0,X(e),w(null);try{await t()}catch(e){let t=ke(e,"Composite migration action failed.");w(t),m(t),h(t,{variant:"error"})}finally{ne.current=!1,X(null)}}},ce=t?.summary.blockedCandidateCount??0;return(0,Nt.jsxs)("section",{className:"grid gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3","data-testid":"composite-migration-card",children:[(0,Nt.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Nt.jsxs)("div",{className:"flex items-center gap-2",children:[(0,Nt.jsx)(i,{size:15,className:"text-hf-yellow"}),(0,Nt.jsxs)("div",{children:[(0,Nt.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Legacy composite migration"}),(0,Nt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Read-only preview and exact-backup recovery"})]})]}),(0,Nt.jsx)($,{label:l&&!t?"Checking":ce?`${ce} blocked`:"Reviewed",tone:p?"error":ce?"warning":t?"success":"neutral"})]}),(0,Nt.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"Previewing writes nothing. Apply and rollback are available only after reviewing exact paths and typing the required literal confirmation."}),p?(0,Nt.jsxs)("div",{className:"flex items-start gap-2 rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/5 p-2 text-xs text-modiff-red",children:[(0,Nt.jsx)(o,{size:14,className:"mt-0.5 shrink-0"}),(0,Nt.jsx)("span",{className:"break-words",children:p})]}):null,t?(0,Nt.jsx)(S,{label:(0,Nt.jsxs)("span",{className:"flex items-center gap-2",children:[(0,Nt.jsx)(r,{size:14,className:"text-hf-yellow"}),"Current read-only preview"]}),className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",buttonClassName:"p-0",panelClassName:"mt-3","data-testid":"composite-migration-preview-disclosure",children:(0,Nt.jsx)(Ht,{preview:t})}):l||p?null:(0,Nt.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No migration preview is available."}),(0,Nt.jsx)(S,{label:(0,Nt.jsxs)("span",{className:"flex items-center gap-2",children:[(0,Nt.jsx)(r,{size:14,className:"text-hf-orange"}),"Historical definition recovery evidence"]}),className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",buttonClassName:"p-0",panelClassName:"mt-3","data-testid":"legacy-cluster-recovery-evidence-disclosure",children:(0,Nt.jsx)(kt,{audit:f,error:b,loading:v,onRefresh:()=>{se()}})}),(0,Nt.jsx)(S,{label:"Registered Block compiler supplement",className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",buttonClassName:"p-0",panelClassName:"mt-3","data-testid":"composite-migration-compiler-disclosure",children:(0,Nt.jsxs)("div",{className:"grid gap-3",children:[(0,Nt.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"Generate exact compiler evidence from saved legacy Blocks, or paste externally produced evidence below. Generation and preview are read-only. The exact parsed supplement is retained only for this reviewed preview and resent unchanged on Apply."}),e?(0,Nt.jsxs)("div",{className:"flex items-start gap-2 rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-2 text-xs text-hf-orange","data-testid":"composite-migration-unsaved-warning",children:[(0,Nt.jsx)(o,{size:14,className:"mt-0.5 shrink-0"}),(0,Nt.jsx)("span",{children:"Generation reads saved backend workflow bytes only. Save dirty workflow tabs first if their current canvas changes should be included."})]}):null,(0,Nt.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,Nt.jsx)(M,{size:"compact",tone:"primary",icon:(0,Nt.jsx)(n,{size:14}),loading:"compiler-generate"===U,disabled:!!U,onClick:()=>{re("compiler-generate",async()=>{let e=++te.current,t=new AbortController;ie.current?.abort(),ie.current=t,P(null),D([]),T(null);try{let i=await He();if(t.signal.aborted||e!==te.current)return;let n=await $t({candidates:i.candidates,signal:t.signal,onProgress:i=>{!t.signal.aborted&&e===te.current&&T(i)}});if(t.signal.aborted||e!==te.current)return;let o=JSON.stringify(n.supplement,null,2);if(E(o),D(n.diagnostics),!n.conversionCount){oe.current=null,_(null),a(i);let e=n.candidateCount?`No legacy registered Block matched an exact current compiler route. Review ${n.diagnostics.length} blocker${1===n.diagnostics.length?"":"s"} below.`:"No legacy registered Block candidates are present in saved workflows.";return P(e),void h(e,{variant:"warning"})}let s=ge(n.supplement),r=await He(s);if(t.signal.aborted||e!==te.current)return;oe.current=s,_(s),a(r),m(null),h(`Generated and previewed ${n.conversionCount} exact registered-Block conversion${1===n.conversionCount?"":"s"}; no files were changed.`,{variant:"success"})}catch(i){if(t.signal.aborted||e!==te.current)return void h("Compiler supplement generation cancelled; no files were changed.",{variant:"info"});throw i}finally{ie.current===t&&(ie.current=null)}})},"data-testid":"composite-migration-compiler-generate",children:"Generate exact supplement"}),"compiler-generate"===U?(0,Nt.jsx)(M,{size:"compact",tone:"secondary",onClick:()=>{te.current+=1,ie.current?.abort(),ie.current=null,T(null)},"data-testid":"composite-migration-compiler-cancel",children:"Cancel generation"}):null,O?(0,Nt.jsxs)("span",{className:"text-xs text-modiff-subtle-text","data-testid":"composite-migration-compiler-progress",children:["Reviewed ",O.completed,"/",O.total," saved Block candidates"]}):null]}),F.length?(0,Nt.jsxs)("section",{className:"grid gap-1.5 rounded-modiff-compact border border-hf-orange/40 bg-hf-orange/5 p-2","data-testid":"composite-migration-compiler-diagnostics",children:[(0,Nt.jsxs)("h4",{className:"text-xs font-bold uppercase text-hf-orange",children:["Candidates left unchanged (",F.length,")"]}),(0,Nt.jsx)("ul",{className:"max-h-40 space-y-2 overflow-auto",children:F.map((e,t)=>(0,Nt.jsxs)("li",{className:"text-xs",children:[(0,Nt.jsxs)("p",{className:"break-all font-mono text-modiff-text",children:[e.sourcePath,e.instanceId?` · ${e.instanceId}`:""]}),(0,Nt.jsxs)("p",{className:"text-modiff-subtle-text",children:[e.code,": ",e.message]})]},`${e.sourcePath}:${e.instanceId??""}:${e.code}:${t}`))})]}):null,(0,Nt.jsx)(j,{label:"Compiler supplement JSON",error:N??void 0,description:"Presentation children never become execution authority; the backend verifies pinned V2 definitions and every preservation receipt.",children:(0,Nt.jsx)(y,{value:A,onChange:e=>{E(e.target.value),P(null),D([]),T(null),oe.current&&(oe.current=null,_(null),a(null),ae())},rows:8,spellCheck:!1,disabled:!!U,className:"font-mono text-xs",placeholder:'{"schemaVersion":1,"kind":"registered_cluster_v2_compiler_supplement","compilerOutputs":[]}',"data-testid":"composite-migration-compiler-json"})}),(0,Nt.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Nt.jsx)(M,{size:"compact",tone:"primary",loading:"compiler-preview"===U,disabled:!!U||!A.trim(),onClick:()=>{re("compiler-preview",async()=>{let e;try{e=ge(JSON.parse(A))}catch(e){let t=e instanceof Error?e.message:"Compiler supplement JSON is invalid.";throw P(t),Error(t)}let t=await He(e);oe.current=e,_(e),a(t),P(null),m(null),h("Exact registered-Block compiler supplement previewed; no files were changed.",{variant:"success"})})},"data-testid":"composite-migration-compiler-preview",children:"Preview exact supplement"}),(0,Nt.jsx)(M,{size:"compact",tone:"secondary",disabled:!!U||!R,onClick:()=>{oe.current=null,_(null),E(""),P(null),D([]),T(null),ae()},"data-testid":"composite-migration-compiler-clear",children:"Return to default preview"})]})]})}),(0,Nt.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Nt.jsx)(M,{size:"compact",tone:"primary",disabled:l||!!U||!t?.summary.hasChanges||!(!t.compilerSupplement||R),onClick:()=>{!t?.summary.hasChanges||t.compilerSupplement&&!R||(z(""),G(!1),w(null),L(!0))},"data-testid":"composite-migration-review",children:"Review migration"}),(0,Nt.jsx)(M,{size:"compact",tone:"secondary",icon:(0,Nt.jsx)(c,{size:14}),loading:l,disabled:l||!!U,onClick:()=>{ae()},"data-testid":"composite-migration-refresh",children:"Refresh preview"})]}),(0,Nt.jsx)(S,{label:`Recovery journals (${s.length})`,className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",buttonClassName:"p-0",panelClassName:"mt-3","data-testid":"composite-migration-recovery-disclosure",children:(0,Nt.jsx)(At,{migrations:s,busyId:U,onInspectRollback:e=>{re(e.migrationId,async()=>{let t=await Ae(e.migrationId);Z(t),Y(""),w(null)})}})}),(0,Nt.jsx)(k,{open:V&&!!t,onClose:()=>{U||L(!1)},title:"Review legacy composite migration",description:"Only the listed files can change. Exact backups are created before migration.",panelClassName:"max-w-4xl",dismissible:!U,testId:"composite-migration-review-dialog",children:t?(0,Nt.jsx)(Pt,{preview:t,confirmation:q,allowBlockedCandidates:K,busy:"apply"===U,error:C,onConfirmationChange:z,onAllowBlockedCandidatesChange:G,onCancel:()=>L(!1),onApply:()=>{!t||!le(t,q,K)||re("apply",async()=>{let e=await Ee(t,{confirmation:q,allowBlockedCandidates:K,...R?{compilerSupplement:R}:{}});h(e.idempotent?"This exact migration was already applied.":`Converted ${e.changedPaths?.length??0} reviewed files and created exact backups.`,{variant:"success"}),L(!1),z(""),G(!1),oe.current=null,_(null),E(""),D([]),T(null),await ae()})}}):null}),(0,Nt.jsx)(k,{open:!!J,onClose:()=>{U||Z(null)},title:"Review exact-backup rollback",description:"Restore the exact backed-up files. Review conflicts before confirming rollback.",panelClassName:"max-w-3xl",dismissible:!U,testId:"composite-migration-rollback-dialog",children:J?(0,Nt.jsx)(Et,{status:J,confirmation:W,busy:U===J.migrationId,error:C,onConfirmationChange:Y,onCancel:()=>Z(null),onRollback:()=>{!J||"ROLLBACK_BLOCK_V2_MIGRATION"!==W||re(J.migrationId,async()=>{let e=await Re(J.migrationId,W);h(e.idempotent?"This migration was already rolled back.":`Restored ${e.restoredPaths?.length??0} files from exact backups.`,{variant:"success"}),Z(null),Y(""),await ae()})}}):null})]})}export{Rt as CompositeMigrationCard}; \ No newline at end of file diff --git a/web/assets/CustomExtensionsDialog.js b/web/assets/CustomExtensionsDialog.js new file mode 100644 index 00000000..635aa185 --- /dev/null +++ b/web/assets/CustomExtensionsDialog.js @@ -0,0 +1 @@ +import{cr as i}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{in as n}from"./studio-templates.js?v=cc046c26d6fe7aa4";import e from"./CustomExtensionsPanel.js?v=cc046c26d6fe7aa4";var o=i();function s({onClose:i,initialKind:s,initialView:t}){return(0,o.jsx)(n,{open:!0,onClose:i,title:"manage"===t?"Manage custom nodes":"Add custom node",panelClassName:"max-w-2xl",testId:"custom-extensions-dialog",children:(0,o.jsx)(e,{initialKind:s,initialView:t})})}export{s as default}; \ No newline at end of file diff --git a/web/assets/CustomExtensionsPanel.js b/web/assets/CustomExtensionsPanel.js new file mode 100644 index 00000000..5d25524c --- /dev/null +++ b/web/assets/CustomExtensionsPanel.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Wt as a,Xt as s,cr as l,dr as t,hn as d}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Eo as n,Si as o,dn as i,do as r,fn as c,fo as u,mn as m,pn as x,po as h,rn as p,sn as f}from"./studio-templates.js?v=cc046c26d6fe7aa4";var b=e(t(),1),g=l(),j=[{value:"local",label:"Local",Icon:s},{value:"hub",label:"Hugging Face",Icon:d},{value:"git",label:"Git",Icon:a}];function y({initialKind:e,busy:a,onAdd:s}){let[l,t]=(0,b.useState)(e),[d,u]=(0,b.useState)(""),[f,y]=(0,b.useState)(""),[v,N]=(0,b.useState)(""),[C,k]=(0,b.useState)(null),w=o(e=>e.customModuleRoot),S=(0,b.useRef)(null),R=f.trim()||r(d);return(0,g.jsxs)("div",{className:"grid gap-4","data-testid":"extension-source-form",children:[(0,g.jsx)("div",{className:"grid grid-cols-3 gap-2",role:"group","aria-label":"Source type",children:j.map(({value:e,label:s,Icon:d})=>(0,g.jsxs)(p,{disabled:a,"aria-pressed":l===e,className:"aria-pressed:border-hf-yellow aria-pressed:bg-hf-yellow/10 aria-pressed:text-hf-yellow",icon:(0,g.jsx)(d,{size:16}),onClick:()=>{t(e),u(""),N(""),k(null)},children:[s,l===e?" ✓":""]},e))}),"local"===l?(0,g.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3",children:[(0,g.jsx)("p",{className:"text-sm",children:"Drop a Python node onto the canvas, or place a node file/package in your custom folder."}),(0,g.jsx)("p",{className:"break-all font-mono text-xs text-modiff-subtle-text",children:w??"custom/ on the backend"}),(0,g.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"New sources appear automatically in Custom nodes. Choose Load to enable them."}),(0,g.jsx)(x,{ref:S,accept:".py","aria-label":"Choose Python node file",className:"hidden",disabled:a,onChange:e=>{let a=e.target.files?.[0];e.target.value="",a&&h(a).then(s).catch(e=>k(n(e,"Node import failed.")))}}),(0,g.jsx)(p,{disabled:a,onClick:()=>S.current?.click(),children:"Choose Python file"})]}):(0,g.jsx)(c,{label:"hub"===l?"Hugging Face repository":"Git repository URL",children:(0,g.jsx)(m,{"aria-label":"Extension source",value:d,disabled:a,placeholder:"hub"===l?"owner/repository":"https://host/owner/repository.git",onChange:e=>u(e.target.value)})}),(0,g.jsxs)(i,{label:"Advanced options",panelClassName:"grid gap-3 pt-2",children:["local"===l?(0,g.jsx)(c,{label:"Copy an existing backend folder or Python file",children:(0,g.jsx)(m,{"aria-label":"Extension source",value:d,disabled:a,onChange:e=>u(e.target.value)})}):(0,g.jsx)(c,{label:"Branch, tag or commit (optional)",children:(0,g.jsx)(m,{"aria-label":"Extension revision",value:v,disabled:a,placeholder:"Repository default",onChange:e=>N(e.target.value)})}),(0,g.jsx)(c,{label:"Package name (optional)",children:(0,g.jsx)(m,{"aria-label":"Extension module name",value:f,placeholder:d?R:"Derived from source",disabled:a,onChange:e=>y(e.target.value)})})]}),C?(0,g.jsxs)("p",{role:"alert",className:"text-sm text-modiff-red",children:["Node import failed",(0,g.jsx)("br",{}),C]}):null,(0,g.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Only add code you trust. Python nodes run with backend permissions. Dependencies are never installed automatically."}),(0,g.jsx)(p,{tone:"primary",fullWidth:!0,loading:a,disabled:a||!d.trim()||!/^[A-Za-z][A-Za-z0-9_]{0,63}$/u.test(R),onClick:()=>{s({kind:l,source:d.trim(),name:R,...v.trim()?{revision:v.trim()}:{}})},children:"Add node"})]})}function v({initialKind:e="local",initialView:a="add"}){let s=o(e=>e.customModules),l=o(e=>e.fetchCustomModules),t=o(e=>e.addCustomModule),d=o(e=>e.setCustomModuleEnabled),[r,c]=(0,b.useState)(a),[m,x]=(0,b.useState)(!1),[h,j]=(0,b.useState)(null),[v,N]=(0,b.useState)(null),C=(0,b.useRef)(!1),k=(0,b.useRef)(!0);async function w(e,a){if(!C.current){C.current=!0,x(!0),j(null),N(null);try{await e(),k.current&&N(a)}catch(e){k.current&&j(n(e,"Could not change this extension."))}finally{C.current=!1,k.current&&x(!1)}}}return(0,b.useEffect)(()=>(k.current=!0,l(),()=>{k.current=!1}),[l]),(0,g.jsxs)("section",{className:"grid gap-4","aria-label":"Custom nodes","data-testid":"custom-extensions-panel",children:[(0,g.jsxs)("div",{className:"flex gap-2",role:"group","aria-label":"Custom node actions",children:[(0,g.jsx)(p,{className:"aria-pressed:border-hf-yellow aria-pressed:text-hf-yellow",disabled:m,"aria-pressed":"add"===r,onClick:()=>c("add"),children:"Add node"}),(0,g.jsx)(p,{className:"aria-pressed:border-hf-yellow aria-pressed:text-hf-yellow",disabled:m,"aria-pressed":"manage"===r,onClick:()=>{c("manage"),l()},children:"Manage nodes"})]}),"add"===r?(0,g.jsx)(y,{initialKind:e,busy:m,onAdd:e=>w(()=>t(e),"Added and enabled. Find your nodes in Custom nodes or connection search.")}):(0,g.jsxs)("div",{className:"grid gap-2",children:[(0,g.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Load or reload code you trust. Wait for running and queued work to finish before changing code."}),(0,g.jsx)(p,{disabled:m,onClick:()=>{l()},children:"Refresh sources"}),0===s.length?(0,g.jsx)("p",{className:"text-sm text-modiff-subtle-text",children:"No custom nodes yet."}):s.map(e=>(0,g.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border p-3",children:[(0,g.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,g.jsx)("span",{className:"font-semibold",children:e.name}),(0,g.jsx)(f,{tone:e.enabled?"success":"warning",children:"disabled"!==e.status||e.approvedHash?e.status:"Not loaded"})]}),e.diagnostic?(0,g.jsx)("p",{className:"text-xs text-modiff-red",children:e.diagnostic}):null,(0,g.jsxs)("div",{className:"flex gap-2",children:[(0,g.jsx)(p,{disabled:m||!e.codeHash,onClick:()=>{w(async()=>{let a=await u(e.name);await d(e.name,!0,a.codeHash??void 0)},e.name+" loaded. Its nodes are available in the library.")},children:e.enabled||"changed"===e.status?"Reload":"Load"}),e.canDisable?(0,g.jsx)(p,{disabled:m,onClick:()=>{w(()=>d(e.name,!1),"Disabled. Files are preserved; import side effects may require a backend restart.")},children:"Disable"}):null]}),(0,g.jsxs)(i,{label:"Source and dependencies",panelClassName:"grid gap-1 text-xs text-modiff-subtle-text",children:[(0,g.jsxs)("p",{className:"break-all",children:["Edit source: ",e.path]}),(0,g.jsx)("p",{className:"break-all",children:e.revision??e.codeHash}),(0,g.jsxs)("p",{children:["Memory declaration: ",e.runtimeRole??"manual"]}),e.dependencies.map(e=>(0,g.jsxs)("p",{children:[e.requirement," · ",e.status," · ",e.installed??"not installed"]},e.requirement))]})]},e.name))]}),h?(0,g.jsxs)("p",{role:"alert",className:"text-sm text-modiff-red",children:["add"===r?"Node import failed":"Could not update node",(0,g.jsx)("br",{}),h]}):null,m||v?(0,g.jsx)("p",{role:"status",className:"text-sm text-modiff-subtle-text",children:m?"Resolving source and loading code…":v}):null]})}export{v as default}; \ No newline at end of file diff --git a/web/assets/DeveloperWorkflowLauncher.js b/web/assets/DeveloperWorkflowLauncher.js new file mode 100644 index 00000000..3cfee398 --- /dev/null +++ b/web/assets/DeveloperWorkflowLauncher.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as a,dr as t}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$ as s,As as o,Bo as i,Ci as l,Ds as r,Ei as n,Eo as d,Es as c,Js as u,Mo as p,Os as m,Sa as f,Si as g,Ti as h,Vr as k,Ys as w,cn as b,in as x,ji as y,js as j,q as C,rn as S,sc as v,yn as N}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as A}from"./operationGraphTransaction.js?v=cc046c26d6fe7aa4";import{n as I}from"./operationStarterRequest.js?v=cc046c26d6fe7aa4";import{t as M}from"./taskModelPreferences.js?v=cc046c26d6fe7aa4";import{t as T}from"./operationAuthoring.js?v=cc046c26d6fe7aa4";var E=e(t(),1),L=[{type:"image",key:"modules.Image.Preview",input:"image"},{type:"audio",key:"modules.Audio.Preview",input:"audio"},{type:"video",key:"modules.Video.ExportAsset",input:"video"},{type:"video_asset",key:"modules.Video.ExportAsset",input:"video"}];function $(e,a){let t=T(e,{x:80,y:120}),s=[],o=[...t.nodes];if(["Image","Audio"].includes(m(e.task))){let i=new Map;for(let l of e.requiredInputs){let e=o.find(e=>e.data.operationAuthoring.operation.operationId===l.operationId),r=e?.data.operationAuthoring.operation.ports.find(e=>"input"===e.direction&&e.name===l.field),n=r?.types.includes("image")?"image":r?.types.includes("audio")?"audio":null;if(!e||!r||!n||r.hidden||t.edges.some(a=>a.target===e.id&&a.targetHandle===r.name))continue;let d=e.data.params[r.name],c="image"===n?"modules.Image.Load":"modules.Audio.Load",u=a[c]?.params[n];if(!d||d.hidden||"input"!==d.display&&!d.isInput||!u||u.hidden||"output"!==u.display||!w(u.type,d.type)){s.push(`Connect a compatible source for ${e.data.label} · ${d?.label||r.name}. The media loader is unavailable or incompatible.`);continue}let p=`${n}:${r.semanticName}`,m=i.get(p);m||(m=C(c,a,{x:80,y:800+600*i.size}),r.semanticName.includes("mask")?m.data.label="Load Mask":["image","images","audio","source_audio"].includes(r.semanticName)||(m.data.label=`Load ${String(d.label||r.semanticName.replace(/_/gu," "))}`),t.nodes.push(m),i.set(p,m)),t.edges.push({id:`edge-${v()}`,source:m.id,sourceHandle:n,target:e.id,targetHandle:r.name,type:"default"})}}let l=0;for(let e of o)for(let s of e.data.operationAuthoring.operation.ports){if("output"!==s.direction||s.hidden||"media"!==s.semantics?.kind||t.edges.some(a=>a.source===e.id&&a.sourceHandle===s.name))continue;let i=u(s.types),r=L.find(e=>i.includes(e.type));if(!r)continue;let n=a[r.key]?.params[r.input];if(!n||n.hidden||"input"!==n.display&&!n.isInput||!w(s.types,n.type))continue;let d=C(r.key,a,{x:80+420*o.length,y:120+340*l});t.nodes.push(d),t.edges.push({id:`edge-${v()}`,source:e.id,sourceHandle:s.name,target:d.id,targetHandle:r.input,type:"default"}),l++}return l||s.push("No compatible output node is registered. Connect a Preview, Save or Export node before running."),{graph:i(t),notices:s}}var O=a(),W=(0,E.lazy)(()=>l(()=>import(`./CustomExtensionsDialog.js?v=cc046c26d6fe7aa4`),[]));function _({embedded:e=!1}){let a=g(e=>e.pipelineSupport),t=g(e=>e.studioModelCapabilities),i=g(e=>e.workflowModelDescriptors),l=g(e=>e.operationContracts),u=g(e=>e.nodesRegistry),m=g(e=>e.hfCache),w=g(e=>e.localModels),C=g(e=>e.modelCacheDiagnostics),v=g(e=>e.discoveryRequests.capabilities),T=y(e=>e.activeWorkflowTabId),L=(0,E.useMemo)(()=>o(a,t,m,w,C,i),[a,t,m,w,C,i]),_=(0,E.useMemo)(()=>r(L),[L]),[H,P]=(0,E.useState)("Image"),[D,R]=(0,E.useState)(""),[V,q]=(0,E.useState)(!1),[J,B]=(0,E.useState)(null),[z,F]=(0,E.useState)(""),[G,K]=(0,E.useState)(null),Y=(0,E.useRef)(null);(0,E.useEffect)(()=>()=>{Y.current?.abort()},[T]);let Q=()=>{Y.current?.abort(),s({revealWorkspace:!1})};async function U(a){if(Y.current)return;let t=new AbortController;Y.current=t;let o=n(),i=JSON.stringify(k.getState().toObject());F(a),B(null);try{let{starter:r,unbound:d,message:c}=await I(a,l,{signal:t.signal,preferredProfileId:M(a),resourceMode:y.getState().form.resourceMode});if(t.signal.aborted)return;if(h(o,{includeForm:!1}),!e&&k.getState().nodes.length||JSON.stringify(k.getState().toObject())!==i)throw Error("The canvas changed. Start a new workflow before choosing a task.");let m=$(r,u);e?(y.getState().createWorkflowTab(j(a)),A(m.graph,n(),JSON.stringify(k.getState().toObject()),"Create workflow")):A(m.graph,o,i,"Create workflow"),y.getState().detachManagedGraph(),s();let f=y.getState().activeWorkflowTabId;f&&y.getState().renameWorkflowTab(f,j(a)),y.getState().saveActiveWorkflowTab(!0),d&&p(c,{variant:"info",autoHideDuration:8e3})}catch(e){t.signal.aborted||B(d(e,"Could not create this workflow."))}finally{Y.current===t&&(Y.current=null,F(""))}}let X=_.filter(e=>("All"===H||e.category===H)&&(!V||e.downloaded)&&`${e.label} ${e.task} ${e.description}`.toLowerCase().includes(D.trim().toLowerCase()));if(G)return(0,O.jsx)(E.Suspense,{fallback:null,children:(0,O.jsx)(W,{initialKind:G,onClose:()=>K(null)})});let Z=(0,O.jsxs)("div",{className:"space-y-4",children:[(0,O.jsx)("p",{className:"text-sm text-modiff-subtle-text",children:"Choose a task to create its connected nodes. Change the model and parameters on the canvas. Models load only when you run."}),e?null:(0,O.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,O.jsx)(S,{onClick:()=>{e&&y.getState().createWorkflowTab(""),s()},disabled:!!z,"data-testid":"launcher-mode-advanced_workflow",children:"Empty workflow"}),(0,O.jsx)(S,{onClick:()=>{Q(),f.getState().setWorkflowLibraryView("saved"),f.getState().setLeftPanelTabIndex(4),f.getState().setLeftPanelOpen(!0)},disabled:!!z,children:"Open workflow"}),(0,O.jsx)(S,{onClick:()=>{Q(),f.getState().setTemplateBrowserOpen(!0)},disabled:!!z,children:"Browse templates"})]}),(0,O.jsx)(N,{"aria-label":"Search workflow tasks",placeholder:"Search workflows and actions",value:D,onChange:e=>R(e.target.value),onClear:()=>R("")}),(0,O.jsx)("div",{className:"flex flex-wrap gap-2","aria-label":"Workflow categories",children:["All",...c].map(e=>(0,O.jsx)(S,{tone:H===e?"primary":"secondary","aria-pressed":H===e,onClick:()=>P(e),children:e},e))}),(0,O.jsx)(b,{checked:V,onCheckedChange:q,label:"With downloaded models"}),J?(0,O.jsx)("p",{role:"alert",children:J}):null,z?(0,O.jsxs)("p",{role:"status",children:["Creating ",j(z).toLowerCase()," workflow…"]}):null,(0,O.jsx)("div",{className:"grid gap-3 sm:grid-cols-2 lg:grid-cols-3",children:X.map(e=>(0,O.jsx)(S,{tone:"secondary",align:"left",className:"h-auto min-h-24 p-4",disabled:!!z,onClick:()=>{U(e.task)},"data-testid":`workflow-task-${e.task}`,children:(0,O.jsxs)("span",{className:"block",children:[(0,O.jsx)("span",{className:"block font-semibold",children:e.label}),(0,O.jsx)("span",{className:"mt-1 block text-sm font-normal text-modiff-subtle-text",children:e.description})]})},e.task))}),X.length?null:(0,O.jsx)("p",{role:"status",children:"loading"===v.status?"Loading workflows…":_.length?"No workflows match these filters.":"No workflow capabilities are available. Check the backend connection."}),!_.length||v.error?(0,O.jsx)(S,{onClick:()=>{g.getState().fetchStudioModelCapabilities()},children:"Retry capabilities"}):null,(0,O.jsxs)("div",{className:"flex flex-wrap items-center gap-2 border-t border-modiff-border pt-3",children:[(0,O.jsx)("span",{className:"text-sm text-modiff-subtle-text",children:"Custom nodes"}),(0,O.jsx)(S,{disabled:!!z,onClick:()=>K("hub"),children:"Add from Hugging Face"}),(0,O.jsx)(S,{disabled:!!z,onClick:()=>K("local"),children:"Add local source"})]})]});return e?(0,O.jsx)("div",{className:"p-3","data-testid":"task-browser",children:Z}):(0,O.jsx)(x,{open:!0,onClose:Q,title:"Workflows",testId:"task-launcher",panelClassName:"max-w-5xl",bodyClassName:"max-h-[78vh]",children:Z})}export{_ as default}; \ No newline at end of file diff --git a/web/assets/EditorPanel.js b/web/assets/EditorPanel.js index c06290ca..a1d3fe86 100644 --- a/web/assets/EditorPanel.js +++ b/web/assets/EditorPanel.js @@ -1 +1 @@ -import{E as e,ur as r}from"./graph-vendor.js?v=5bcd315505f5670a";import{Gt as s}from"./studio-templates.js?v=5bcd315505f5670a";var l=r();function o({title:r,children:o,footer:t,onClose:i,testId:a}){return(0,l.jsxs)("section",{className:"flex h-full min-h-0 flex-col","aria-label":r,"data-testid":a,children:[(0,l.jsxs)("header",{className:"flex shrink-0 items-center justify-between gap-2 border-b border-modiff-border px-3 py-2",children:[(0,l.jsx)("h2",{className:"text-sm font-semibold text-modiff-text",children:r}),(0,l.jsx)(s,{label:"Close Block editor",onClick:i,children:(0,l.jsx)(e,{size:16})})]}),(0,l.jsx)("div",{className:"min-h-0 flex-1 overflow-y-auto p-3",children:o}),t?(0,l.jsx)("footer",{className:"flex shrink-0 flex-wrap justify-end gap-2 border-t border-modiff-border bg-modiff-panel p-3",children:t}):null]})}export{o as t}; \ No newline at end of file +import{E as e,cr as r}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{an as s}from"./studio-templates.js?v=cc046c26d6fe7aa4";var l=r();function o({title:r,children:o,footer:t,onClose:i,testId:a}){return(0,l.jsxs)("section",{className:"flex h-full min-h-0 flex-col","aria-label":r,"data-testid":a,children:[(0,l.jsxs)("header",{className:"flex shrink-0 items-center justify-between gap-2 border-b border-modiff-border px-3 py-2",children:[(0,l.jsx)("h2",{className:"text-sm font-semibold text-modiff-text",children:r}),(0,l.jsx)(s,{label:"Close Block editor",onClick:i,children:(0,l.jsx)(e,{size:16})})]}),(0,l.jsx)("div",{className:"min-h-0 flex-1 overflow-y-auto p-3",children:o}),t?(0,l.jsx)("footer",{className:"flex shrink-0 flex-wrap justify-end gap-2 border-t border-modiff-border bg-modiff-panel p-3",children:t}):null]})}export{o as t}; \ No newline at end of file diff --git a/web/assets/EncodingNode.js b/web/assets/EncodingNode.js new file mode 100644 index 00000000..3e73ea79 --- /dev/null +++ b/web/assets/EncodingNode.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as n,ln as a}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{At as s,Cs as o,Ct as i,Ei as d,Fo as r,Lo as l,Mo as c,Mt as u,Nt as m,Ot as p,Qs as g,Si as f,Ss as h,Uo as x,Vn as j,Vr as I,an as b,dn as v,es as S,et as N,ft as k,in as C,ji as y,lt as w,ns as $,rn as M,tt as P,ut as V,xs as _}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as G,t as T}from"./operationGraphTransaction.js?v=cc046c26d6fe7aa4";import A from"./GuidanceNodeControls.js?v=cc046c26d6fe7aa4";import E from"./NodeInspectionDetails.js?v=cc046c26d6fe7aa4";var B=e(n(),1),F=t();function O({instance:e,updateStore:t}){let n=I(e=>e.nodes),a=I(e=>e.edges),s=l(e,f(e=>e.operationContracts),f(e=>e.pipelineSupport)),o=(0,B.useMemo)(()=>$(e),[e]),d=(0,B.useMemo)(()=>S(e).nodes,[e]),r=n.find(t=>t.id===e.instanceId)?.data.uiState?.encodingSummaries,c=(0,B.useMemo)(()=>{try{let t=x({nodes:n,edges:a},new Set([e.instanceId])).graph;return new Map(d.map(n=>{let a=g(t.nodes,t.edges,`${e.instanceId}/${n.data.blockProjectionNodeId}`,"seed");return[n.data.blockProjectionNodeId,a?.members.filter(t=>t.node.id!==`${e.instanceId}/${n.data.blockProjectionNodeId}`).map(e=>e.node.data.label).filter(Boolean)??[]]}))}catch{return new Map}},[a,e.instanceId,n,d]),u=[...new Set(e.effectiveInterface.controls.map(e=>e.group??"Settings"))],m=d.filter(e=>["vae_encoder","image_encoder"].includes(e.data.operationAuthoring?.operation.nodeType??""));function p(t,s){let o=e.effectiveInterface.boundary.inputs.filter(e=>[e.binding,...e.mirrorBindings??[]].some(e=>e.nodeId===t&&e.fieldOrPortId===s)),i=a.find(t=>t.target===e.instanceId&&o.some(e=>e.portId===t.targetHandle));if(i)return n.find(e=>e.id===i.source)?.data.label??"Connected source";let d=e.effectiveGraph.edges.find(e=>e.targetNodeId===t&&e.targetPortId===s);return d?String(e.effectiveGraph.nodes.find(e=>e.nodeId===d.sourceNodeId)?.data.label??"Internal source"):null}return(m.length||s)&&!u.includes("Image")&&u.push("Image"),(0,F.jsx)("div",{className:"nodrag nowheel grid gap-2","data-encoding-content":!0,"data-testid":`encoding-controls-${e.instanceId}`,children:u.map(n=>{let a=e.effectiveInterface.controls.filter(e=>(e.group??"Settings")===n),d=[...new Set(a.filter(e=>"prompt"===e.binding.fieldId).map(e=>p(e.binding.nodeId,"prompt_input")).filter(Boolean))];return(0,F.jsxs)(v,{label:n,defaultOpen:!0,unmount:!1,panelClassName:"grid gap-2 px-1 pt-2","aria-label":`${n} encoding controls`,children:["Image"===n&&s?(0,F.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Optional — connect the Image socket to use an image input. Text-only generation remains available without it."}):null,d.length?(0,F.jsxs)("p",{className:"text-xs text-modiff-subtle-text",children:["Using Prompt Input from ",d.join(", "),". The local prompt is retained below as an inactive fallback."]}):null,"Image"===n?m.map(t=>{let n=p(t.data.blockProjectionNodeId,"image"),a=t.data.operationAuthoring?.operation.ports.some(e=>"input"===e.direction&&"image"===e.name&&e.required),s=t.data.params.image?.value??t.data.params.image?.default;return(0,F.jsxs)("div",{className:"grid gap-2",children:[!a||n||null!=s&&""!==s?n?(0,F.jsxs)("p",{className:"text-xs text-modiff-subtle-text",children:["Image from ",n]}):null:(0,F.jsx)("p",{role:"status",className:"text-xs text-modiff-invalid",children:"Image input required — connect a separate Load Image node."}),t.data.params.encode_summary||t.data.params.encode_summary_data?(0,F.jsx)(i,{nodeId:e.instanceId,params:r?.[t.id]?.graphHash===e.effectiveGraph.graphHash?{...t.data.params,encode_summary:{...t.data.params.encode_summary,value:r[t.id].value}}:t.data.params,executionStatus:t.data.executionStatus,progressMessage:t.data.progressMessage}):null]},t.id)}):null,a.map(n=>{let a=o.controlParams[n.controlId];if(!a)return null;let s=c.get(n.binding.nodeId)??[];return(0,F.jsxs)("div",{children:[(0,F.jsx)(k,{nodeId:e.instanceId,params:{[n.controlId]:_(n,a,!!p(n.binding.nodeId,"prompt_input"))},updateStore:t,module:o.source.provider??"MoDiff",action:"BlockV2",mode:"controls"}),"seed"===n.binding.fieldId?(0,F.jsxs)("p",{className:"mb-2 text-xs text-modiff-subtle-text",children:[m.some(e=>e.data.blockProjectionNodeId===n.binding.nodeId&&"vae_encoder"===e.data.operationAuthoring?.operation.nodeType)?"Seed for image-latent sampling.":"Seed declared by this encoding stage.",s.length?` Shared with ${s.join(", ")}; editing updates the linked value.`:""]}):null]},n.controlId)})]},n)})})}function q({node:e,inspect:t,save:n,canSave:i}){let[r,l]=(0,B.useState)(!1),g=o(e.data.blockInstanceV2),f=g?"Guidance":"Encode Inputs",h=`Separate ${g?"guidance":"encoding"} stages`;return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsxs)(s,{className:"nodrag nowheel",children:[(0,F.jsx)(m,{children:(0,F.jsx)(b,{label:`${f} options`,size:"compact",children:(0,F.jsx)(a,{size:16})})}),(0,F.jsxs)(u,{layer:"graph",children:[(0,F.jsx)(p,{onClick:t,children:"Inspect implementation"}),(0,F.jsx)(p,{onClick:function(){try{let t=I.getState().toObject(),n=d(),a=x(t,new Set([e.id])).graph;T(a,n,G(t),h),y.getState().saveActiveWorkflowTab(!0)}catch(e){c(e instanceof Error?e.message:"Could not separate stages.",{variant:"error"})}},children:h}),i?(0,F.jsx)(p,{onClick:n,children:"Save as reusable node…"}):null,(0,F.jsx)(p,{onClick:()=>requestAnimationFrame(()=>l(!0)),children:"Documentation"})]})]}),r?(0,F.jsx)(C,{open:!0,title:`${f} documentation`,onClose:()=>l(!1),children:(0,F.jsxs)("div",{className:"grid gap-4",children:[(0,F.jsx)("p",{children:g?"Guidance combines the related Guider and Layers configuration. Schedulers and adapters remain separate nodes.":"Text and image stages both run when present. Collapsing a section only hides its fields. Load media separately and connect its output to the relevant input."}),S(e.data.blockInstanceV2).nodes.map(e=>(0,F.jsxs)("section",{children:[(0,F.jsx)("h3",{className:"mb-2 font-semibold",children:e.data.label}),(0,F.jsx)(E,{node:e,section:"docs"})]},e.id))]})}):null]})}var H=(0,B.memo)(e=>{let t=e.data.blockInstanceV2,n=o(t),a=n?"Guidance":"Encode Inputs",s=n?"guidance":"encoding",i=(0,B.useMemo)(()=>$(t),[t]),[d,l]=(0,B.useState)(!1),[c,u]=(0,B.useState)(!1),[m,p]=(0,B.useState)(!1);f(e=>e.operationContracts),f(e=>e.pipelineSupport);let g=I(j(t=>t.edges.filter(t=>t.source===e.id||t.target===e.id))),x=I(e=>e.setBlockInstanceValueV2),b=I(e=>e.ensureBlockProjectionV2),v=h(Object.fromEntries(Object.entries(r(e)).map(([t,n])=>[t,{...n,isConnected:g.some(n=>n.source===e.id&&n.sourceHandle===t||n.target===e.id&&n.targetHandle===t)}])),t.effectiveInterface.boundary.outputs),y=(0,B.useCallback)((t,n,a)=>{(void 0===a||"value"===a)&&x(e.id,t,n)},[e.id,x]);return(0,B.useLayoutEffect)(()=>{b(e.id)},[b,e.id,t.presentation.expanded]),(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(w,{...e,surface:{testId:`${n?"guidance":"encoding"}-node-${e.id}`,controls:n?(0,F.jsx)(A,{instance:t}):(0,F.jsx)(O,{instance:t,updateStore:y}),actions:(0,F.jsx)(q,{node:{...e,position:i.position},inspect:()=>requestAnimationFrame(()=>l(!0)),save:()=>requestAnimationFrame(()=>p(!0)),canSave:!0}),connectors:(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(k,{nodeId:e.id,params:v,updateStore:y,module:"MoDiff",action:"Encoding",mode:"connectors"}),(0,F.jsx)(N,{nodeId:e.id})]}),setSize:(t,n)=>I.getState().setBlockPresentationV2(e.id,{size:{width:t,height:n}})}}),d?(0,F.jsxs)(C,{open:!0,title:`${a} implementation`,onClose:()=>l(!1),footer:(0,F.jsx)(M,{onClick:()=>{l(!1),u(!0)},children:"Configure inputs, outputs and controls"}),children:[(0,F.jsxs)("p",{className:"mb-4 text-sm text-modiff-subtle-text",children:["These stages execute behind this node. To edit their connections on the canvas, use Separate ",s," ","stages."]}),(0,F.jsx)("div",{className:"grid gap-4",children:S(t).nodes.map(e=>(0,F.jsxs)("section",{children:[(0,F.jsx)("h3",{className:"mb-2 font-semibold",children:e.data.label}),(0,F.jsx)(E,{node:e,section:"implementation"})]},e.id))})]}):null,c?(0,F.jsx)(P,{nodeId:e.id,onClose:()=>u(!1)}):null,m?(0,F.jsx)(V,{nodeId:e.id,onClose:()=>p(!1)}):null]})});H.displayName="EncodingNode";export{H as default}; \ No newline at end of file diff --git a/web/assets/FileBrowserDialog.js b/web/assets/FileBrowserDialog.js index 482e7c72..b67a767d 100644 --- a/web/assets/FileBrowserDialog.js +++ b/web/assets/FileBrowserDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{Ot as t,Vt as s,Zt as a,pr as l,ur as i}from"./graph-vendor.js?v=5bcd315505f5670a";import{Gt as r,Jt as o,Lo as n,Sn as d,Ut as c,Wt as m,an as f,eo as h,kr as p,ro as x,to as u,ui as b}from"./studio-templates.js?v=5bcd315505f5670a";var g=e(l(),1),j=i(),y=h();function w(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function N(e){if(!w(e)||!Array.isArray(e.files)||"string"!=typeof e.path||"string"!=typeof e.abs_path)throw Error("The directory listing response is invalid.");return{files:e.files.map((e,t)=>{if(!w(e)||"boolean"!=typeof e.is_dir||"boolean"!=typeof e.is_hidden||"string"!=typeof e.name||"string"!=typeof e.path||"number"!=typeof e.modified)throw Error(`Directory entry ${t+1} is invalid.`);return e}),path:e.path,abs_path:e.abs_path}}function v(e){return new Intl.DateTimeFormat(void 0,{year:"2-digit",month:"numeric",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1}).format(new Date(1e3*e))}function C(e){if(0===e||null===e)return"0 B";let t=Math.floor(Math.log(e)/Math.log(1024));return`${(e/1024**t).toFixed(2)} ${["B","KB","MB","GB","TB"][t]}`}var k=(0,g.memo)(({file:e,onPathChange:t})=>(0,j.jsxs)("tr",{role:"button",tabIndex:0,className:"cursor-pointer border-b border-modiff-border hover:bg-modiff-surface-hover",onClick:()=>t(e.path),onKeyDown:s=>{s.target!==s.currentTarget||"Enter"!==s.key&&" "!==s.key||(s.preventDefault(),t(e.path))},children:[(0,j.jsx)("td",{className:"w-8 p-0 text-center",children:(0,j.jsx)(a,{size:16,className:"mx-auto text-hf-yellow"})}),(0,j.jsx)("td",{className:"px-2 py-1 text-sm text-modiff-text",children:e.name}),(0,j.jsx)("td",{className:"whitespace-nowrap px-2 py-1 text-right text-sm text-modiff-subtle-text"}),(0,j.jsx)("td",{className:"whitespace-nowrap px-2 py-1 text-right text-sm text-modiff-subtle-text",children:v(e.modified)})]})),S=(0,g.memo)(({file:e,isSelected:t,onFileClick:s,onFileToggle:a})=>(0,j.jsxs)("tr",{role:"button",tabIndex:0,className:d("cursor-pointer border-b border-modiff-border hover:bg-modiff-surface-hover",t&&"bg-modiff-selected-surface"),onClick:t=>s(e,t),onKeyDown:t=>{t.target!==t.currentTarget||"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),s(e,t))},children:[(0,j.jsx)("td",{className:"w-8 p-0 text-center",children:(0,j.jsx)("span",{className:"inline-flex",onClick:e=>e.stopPropagation(),onKeyDown:e=>e.stopPropagation(),children:(0,j.jsx)(o,{checked:t,label:(0,j.jsx)("span",{className:"sr-only",children:`Select ${e.name}`}),onCheckedChange:t=>a(e,t)})})}),(0,j.jsx)("td",{className:"break-all px-2 py-1 text-sm text-modiff-text",children:e.name}),(0,j.jsx)("td",{className:"whitespace-nowrap px-2 py-1 text-right text-sm text-modiff-subtle-text",children:e.size?C(e.size):"-"}),(0,j.jsx)("td",{className:"whitespace-nowrap px-2 py-1 text-right text-sm text-modiff-subtle-text",children:v(e.modified)})]}));function T({opener:e,onClose:a,onSelect:l}){let i=p(e=>e.setParamWithHistory),d=b(e=>e.activeWorkflowTabId),h=b(e=>e.workflowCanvasEpoch),w=!(!e||e.workflowTabId!==d||e.workflowCanvasEpoch!==h),[T,z]=(0,g.useState)("."),[_,E]=(0,g.useState)(null),[F,I]=(0,g.useState)([]),[$,D]=(0,g.useState)(!1),[A,L]=(0,g.useState)(null),[M,B]=(0,g.useState)(""),K=function(e){let[t,s]=(0,g.useState)(e);return(0,g.useEffect)(()=>{let t=setTimeout(()=>{s(e)},250);return()=>{clearTimeout(t)}},[e,250]),t}(M),P=e?.multiple??!1,U=e?.fileTypes.join(",")??"";(0,g.useEffect)(()=>{e&&!w&&a()},[a,e,w]);let V=(0,g.useCallback)(async e=>{let t=y.begin("directory");D(!0),L(null);try{let s=await x(`${n.serverAddress}/listdir?path=${encodeURIComponent(e)}&type=${U}`,{signal:t.signal,parse:N});t.isLatest()&&E(s)}catch(s){t.isLatest()&&(L(u(s,"Could not load this folder.")),E({files:[],path:e,abs_path:e}))}finally{t.isLatest()&&D(!1),t.finish()}},[U]),{directories:R,files:W}=(0,g.useMemo)(()=>{let e=_?.files||[],t=K?e.filter(e=>e.name.toLowerCase().includes(K.toLowerCase())):e;return{directories:t.filter(e=>e.is_dir).sort((e,t)=>e.name.localeCompare(t.name)),files:t.filter(e=>!e.is_dir).sort((e,t)=>e.name.localeCompare(t.name))}},[_,K]),G=(0,g.useCallback)((e,t)=>{let s=t.target,a="INPUT"===s.tagName&&"checkbox"===s.getAttribute("type");I(s=>{let l=s.some(t=>t.path===e.path);return P?a||t.shiftKey?l?s.filter(t=>t.path!==e.path):[...s,e]:[e]:l?[]:[e]})},[P]),H=(0,g.useCallback)((e,t)=>{I(s=>t?P?s.some(t=>t.path===e.path)?s:[...s,e]:[e]:s.filter(t=>t.path!==e.path))},[P]);(0,g.useEffect)(()=>{e&&V(T)},[T,V,e]),(0,g.useEffect)(()=>{if(z(e?.path||"."),B(""),e?.initialValues&&e.initialValues.length>0){let t=e.initialValues.map(e=>({is_dir:!1,is_hidden:!1,name:e.split(/[/\\]/).pop()||e,path:e,modified:0,size:null,ext:null,type:null}));I(t)}else I([])},[e]);let J=F[F.length-1],O=T.split("/"),Z=()=>{a(),z("."),B("")};return(0,j.jsx)(m,{open:!!e,onClose:Z,title:"File browser",description:"Choose files from the backend. Nothing is added until you select.",testId:"file-browser-dialog",panelClassName:"max-w-6xl",bodyClassName:"!p-0",footer:(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)("span",{role:"status",className:"mr-auto self-center text-xs text-modiff-subtle-text",children:$?"Loading folder…":`${F.length} selected`}),(0,j.jsx)(c,{onClick:Z,children:"Cancel"}),(0,j.jsx)(c,{tone:"primary",onClick:()=>{e&&w&&i(e.nodeId,e.fieldKey,F.map(e=>e.path)),w&&l?.(F.map(e=>e.path)),Z()},disabled:0===F.length||$||!!A||!w,children:"Select"})]}),children:(0,j.jsxs)("div",{className:"flex min-h-0 flex-col",children:[A?(0,j.jsxs)("div",{role:"alert",className:"m-4 grid gap-2 rounded-modiff-compact border border-modiff-invalid/40 bg-modiff-invalid/10 p-3 text-sm text-modiff-text",children:[(0,j.jsx)("p",{className:"break-words",children:A}),(0,j.jsx)(c,{onClick:()=>{V(T)},children:"Retry folder"})]}):null,(0,j.jsxs)("nav",{className:"flex flex-wrap items-center gap-1 border-b border-modiff-border bg-modiff-panel px-4 py-3 text-sm text-modiff-text","aria-label":"Current folder",children:[(0,j.jsx)(r,{label:"Home",size:"compact",className:"text-hf-yellow",onClick:()=>{z("."),I([])},children:(0,j.jsx)(s,{size:16})}),O.map((e,t)=>{let s=O.slice(0,t+1).join("/");return t===O.length-1?(0,j.jsx)("span",{className:"break-all px-1 text-modiff-text",children:e},t):(0,j.jsx)(c,{tone:"ghost",size:"compact",className:"h-7 px-1 font-normal",onClick:()=>{z(s),I([])},children:e},t)})]}),(0,j.jsxs)("div",{className:"grid min-h-0 flex-1 gap-4 overflow-hidden p-4 lg:grid-cols-[minmax(0,1fr)_384px]",children:[(0,j.jsxs)("div",{className:"relative min-h-0 min-w-0 select-none overflow-auto",children:[$&&(0,j.jsx)("div",{className:"absolute inset-0 z-[2] grid place-items-center bg-modiff-panel/90",children:(0,j.jsx)(t,{className:"animate-spin text-hf-yellow",size:28})}),(0,j.jsxs)("table",{className:"w-full border-collapse text-left",children:[(0,j.jsx)("thead",{className:"sticky top-0 z-[1] bg-modiff-panel",children:(0,j.jsxs)("tr",{className:"border-b border-modiff-border",children:[(0,j.jsx)("th",{className:"w-8 p-0 text-center",children:P&&(0,j.jsx)(o,{checked:W.length>0&&F.length===W.length,indeterminate:F.length>0&&F.length{I(e?W:[])}})}),(0,j.jsx)("th",{className:"px-2 py-2",children:(0,j.jsxs)("div",{className:"flex w-full items-center gap-2",children:[(0,j.jsx)("span",{className:"text-sm font-semibold text-modiff-text",children:"Name"}),(0,j.jsx)(f,{"aria-label":"Filter files",className:"min-w-0 flex-1",placeholder:"Filter",value:M,onChange:e=>B(e.currentTarget.value),onClear:()=>B("")})]})}),(0,j.jsx)("th",{className:"whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-modiff-text",children:"Size"}),(0,j.jsx)("th",{className:"whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-modiff-text",children:"Modified"})]})}),(0,j.jsxs)("tbody",{children:[R.map(e=>(0,j.jsx)(k,{file:e,onPathChange:e=>{z(e),I([]),B("")}},e.path)),W.map(e=>(0,j.jsx)(S,{file:e,isSelected:F.some(t=>t.path===e.path),onFileClick:G,onFileToggle:H},e.path))]})]}),$||A||0!==R.length||0!==W.length?null:(0,j.jsxs)("div",{className:"p-3 text-sm text-modiff-subtle-text",children:[M?"No files match your search.":"This folder has no matching files.",M?(0,j.jsx)(c,{tone:"ghost",onClick:()=>B(""),children:"Clear search"}):null]})]}),(0,j.jsxs)("aside",{className:"min-h-0 overflow-hidden break-words",children:[J&&(0,j.jsx)("img",{src:`${n.serverAddress}/preview?file=${encodeURIComponent(J.path)}&width=384&height=384`,alt:J.name,className:"w-full rounded-modiff-compact border border-modiff-border object-contain"}),(0,j.jsxs)("div",{className:"mt-2 space-y-1 text-sm text-modiff-subtle-text",children:[(0,j.jsx)("div",{className:"font-bold text-modiff-text",children:J?J.name:""}),(0,j.jsxs)("div",{children:["Size: ",J?.size?C(J.size):"-"]}),(0,j.jsxs)("div",{children:["Modified: ",J?.modified?v(J.modified):"-"]}),(0,j.jsxs)("div",{children:["Type: ",J?.type??""]})]})]})]})]})})}export{T as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Et as t,Jt as s,cr as a,dr as l,zt as i}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Eo as r,In as o,Oo as n,To as d,Vr as c,an as m,cn as f,in as h,ji as p,oc as x,rn as u,yn as b}from"./studio-templates.js?v=cc046c26d6fe7aa4";var g=e(l(),1),j=a(),y=d();function w(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function N(e){if(!w(e)||!Array.isArray(e.files)||"string"!=typeof e.path||"string"!=typeof e.abs_path)throw Error("The directory listing response is invalid.");return{files:e.files.map((e,t)=>{if(!w(e)||"boolean"!=typeof e.is_dir||"boolean"!=typeof e.is_hidden||"string"!=typeof e.name||"string"!=typeof e.path||"number"!=typeof e.modified)throw Error(`Directory entry ${t+1} is invalid.`);return e}),path:e.path,abs_path:e.abs_path}}function v(e){return new Intl.DateTimeFormat(void 0,{year:"2-digit",month:"numeric",day:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1}).format(new Date(1e3*e))}function C(e){if(0===e||null===e)return"0 B";let t=Math.floor(Math.log(e)/Math.log(1024));return`${(e/1024**t).toFixed(2)} ${["B","KB","MB","GB","TB"][t]}`}var k=(0,g.memo)(({file:e,onPathChange:t})=>(0,j.jsxs)("tr",{role:"button",tabIndex:0,className:"cursor-pointer border-b border-modiff-border hover:bg-modiff-surface-hover",onClick:()=>t(e.path),onKeyDown:s=>{s.target!==s.currentTarget||"Enter"!==s.key&&" "!==s.key||(s.preventDefault(),t(e.path))},children:[(0,j.jsx)("td",{className:"w-8 p-0 text-center",children:(0,j.jsx)(s,{size:16,className:"mx-auto text-hf-yellow"})}),(0,j.jsx)("td",{className:"px-2 py-1 text-sm text-modiff-text",children:e.name}),(0,j.jsx)("td",{className:"whitespace-nowrap px-2 py-1 text-right text-sm text-modiff-subtle-text"}),(0,j.jsx)("td",{className:"whitespace-nowrap px-2 py-1 text-right text-sm text-modiff-subtle-text",children:v(e.modified)})]})),T=(0,g.memo)(({file:e,isSelected:t,onFileClick:s,onFileToggle:a})=>(0,j.jsxs)("tr",{role:"button",tabIndex:0,className:o("cursor-pointer border-b border-modiff-border hover:bg-modiff-surface-hover",t&&"bg-modiff-selected-surface"),onClick:t=>s(e,t),onKeyDown:t=>{t.target!==t.currentTarget||"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),s(e,t))},children:[(0,j.jsx)("td",{className:"w-8 p-0 text-center",children:(0,j.jsx)("span",{className:"inline-flex",onClick:e=>e.stopPropagation(),onKeyDown:e=>e.stopPropagation(),children:(0,j.jsx)(f,{checked:t,label:(0,j.jsx)("span",{className:"sr-only",children:`Select ${e.name}`}),onCheckedChange:t=>a(e,t)})})}),(0,j.jsx)("td",{className:"break-all px-2 py-1 text-sm text-modiff-text",children:e.name}),(0,j.jsx)("td",{className:"whitespace-nowrap px-2 py-1 text-right text-sm text-modiff-subtle-text",children:e.size?C(e.size):"-"}),(0,j.jsx)("td",{className:"whitespace-nowrap px-2 py-1 text-right text-sm text-modiff-subtle-text",children:v(e.modified)})]}));function z({opener:e,onClose:s,onSelect:a}){let l=c(e=>e.setParamWithHistory),o=p(e=>e.activeWorkflowTabId),d=p(e=>e.workflowCanvasEpoch),w=!(!e||e.workflowTabId!==o||e.workflowCanvasEpoch!==d),[z,S]=(0,g.useState)("."),[E,I]=(0,g.useState)(null),[_,F]=(0,g.useState)([]),[$,D]=(0,g.useState)(!1),[A,M]=(0,g.useState)(null),[B,K]=(0,g.useState)(""),L=function(e){let[t,s]=(0,g.useState)(e);return(0,g.useEffect)(()=>{let t=setTimeout(()=>{s(e)},250);return()=>{clearTimeout(t)}},[e,250]),t}(B),P=e?.multiple??!1,V=e?.fileTypes.join(",")??"";(0,g.useEffect)(()=>{e&&!w&&s()},[s,e,w]);let R=(0,g.useCallback)(async e=>{let t=y.begin("directory");D(!0),M(null);try{let s=await n(`${x.serverAddress}/listdir?path=${encodeURIComponent(e)}&type=${V}`,{signal:t.signal,parse:N});t.isLatest()&&I(s)}catch(s){t.isLatest()&&(M(r(s,"Could not load this folder.")),I({files:[],path:e,abs_path:e}))}finally{t.isLatest()&&D(!1),t.finish()}},[V]),{directories:U,files:H}=(0,g.useMemo)(()=>{let e=E?.files||[],t=L?e.filter(e=>e.name.toLowerCase().includes(L.toLowerCase())):e;return{directories:t.filter(e=>e.is_dir).sort((e,t)=>e.name.localeCompare(t.name)),files:t.filter(e=>!e.is_dir).sort((e,t)=>e.name.localeCompare(t.name))}},[E,L]),W=(0,g.useCallback)((e,t)=>{let s=t.target,a="INPUT"===s.tagName&&"checkbox"===s.getAttribute("type");F(s=>{let l=s.some(t=>t.path===e.path);return P?a||t.shiftKey?l?s.filter(t=>t.path!==e.path):[...s,e]:[e]:l?[]:[e]})},[P]),G=(0,g.useCallback)((e,t)=>{F(s=>t?P?s.some(t=>t.path===e.path)?s:[...s,e]:[e]:s.filter(t=>t.path!==e.path))},[P]);(0,g.useEffect)(()=>{e&&R(z)},[z,R,e]),(0,g.useEffect)(()=>{if(S(e?.path||"."),K(""),e?.initialValues&&e.initialValues.length>0){let t=e.initialValues.map(e=>({is_dir:!1,is_hidden:!1,name:e.split(/[/\\]/).pop()||e,path:e,modified:0,size:null,ext:null,type:null}));F(t)}else F([])},[e]);let J=_[_.length-1],O=z.split("/"),q=()=>{s(),S("."),K("")};return(0,j.jsx)(h,{open:!!e,onClose:q,title:"File browser",description:"Choose files from the backend. Nothing is added until you select.",testId:"file-browser-dialog",panelClassName:"max-w-6xl",bodyClassName:"!p-0",footer:(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)("span",{role:"status",className:"mr-auto self-center text-xs text-modiff-subtle-text",children:$?"Loading folder…":`${_.length} selected`}),(0,j.jsx)(u,{onClick:q,children:"Cancel"}),(0,j.jsx)(u,{tone:"primary",onClick:()=>{e&&w&&l(e.nodeId,e.fieldKey,_.map(e=>e.path)),w&&a?.(_.map(e=>e.path)),q()},disabled:0===_.length||$||!!A||!w,children:"Select"})]}),children:(0,j.jsxs)("div",{className:"flex min-h-0 flex-col",children:[A?(0,j.jsxs)("div",{role:"alert",className:"m-4 grid gap-2 rounded-modiff-compact border border-modiff-invalid/40 bg-modiff-invalid/10 p-3 text-sm text-modiff-text",children:[(0,j.jsx)("p",{className:"break-words",children:A}),(0,j.jsx)(u,{onClick:()=>{R(z)},children:"Retry folder"})]}):null,(0,j.jsxs)("nav",{className:"flex flex-wrap items-center gap-1 border-b border-modiff-border bg-modiff-panel px-4 py-3 text-sm text-modiff-text","aria-label":"Current folder",children:[(0,j.jsx)(m,{label:"Home",size:"compact",className:"text-hf-yellow",onClick:()=>{S("."),F([])},children:(0,j.jsx)(i,{size:16})}),O.map((e,t)=>{let s=O.slice(0,t+1).join("/");return t===O.length-1?(0,j.jsx)("span",{className:"break-all px-1 text-modiff-text",children:e},t):(0,j.jsx)(u,{tone:"ghost",size:"compact",className:"h-7 px-1 font-normal",onClick:()=>{S(s),F([])},children:e},t)})]}),(0,j.jsxs)("div",{className:"grid min-h-0 flex-1 gap-4 overflow-hidden p-4 lg:grid-cols-[minmax(0,1fr)_384px]",children:[(0,j.jsxs)("div",{className:"relative min-h-0 min-w-0 select-none overflow-auto",children:[$&&(0,j.jsx)("div",{className:"absolute inset-0 z-[2] grid place-items-center bg-modiff-panel/90",children:(0,j.jsx)(t,{className:"animate-spin text-hf-yellow",size:28})}),(0,j.jsxs)("table",{className:"w-full border-collapse text-left",children:[(0,j.jsx)("thead",{className:"sticky top-0 z-[1] bg-modiff-panel",children:(0,j.jsxs)("tr",{className:"border-b border-modiff-border",children:[(0,j.jsx)("th",{className:"w-8 p-0 text-center",children:P&&(0,j.jsx)(f,{checked:H.length>0&&_.length===H.length,indeterminate:_.length>0&&_.length{F(e?H:[])}})}),(0,j.jsx)("th",{className:"px-2 py-2",children:(0,j.jsxs)("div",{className:"flex w-full items-center gap-2",children:[(0,j.jsx)("span",{className:"text-sm font-semibold text-modiff-text",children:"Name"}),(0,j.jsx)(b,{"aria-label":"Filter files",className:"min-w-0 flex-1",placeholder:"Filter",value:B,onChange:e=>K(e.currentTarget.value),onClear:()=>K("")})]})}),(0,j.jsx)("th",{className:"whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-modiff-text",children:"Size"}),(0,j.jsx)("th",{className:"whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-modiff-text",children:"Modified"})]})}),(0,j.jsxs)("tbody",{children:[U.map(e=>(0,j.jsx)(k,{file:e,onPathChange:e=>{S(e),F([]),K("")}},e.path)),H.map(e=>(0,j.jsx)(T,{file:e,isSelected:_.some(t=>t.path===e.path),onFileClick:W,onFileToggle:G},e.path))]})]}),$||A||0!==U.length||0!==H.length?null:(0,j.jsxs)("div",{className:"p-3 text-sm text-modiff-subtle-text",children:[B?"No files match your search.":"This folder has no matching files.",B?(0,j.jsx)(u,{tone:"ghost",onClick:()=>K(""),children:"Clear search"}):null]})]}),(0,j.jsxs)("aside",{className:"min-h-0 overflow-hidden break-words",children:[J&&(0,j.jsx)("img",{src:`${x.serverAddress}/preview?file=${encodeURIComponent(J.path)}&width=384&height=384`,alt:J.name,className:"w-full rounded-modiff-compact border border-modiff-border object-contain"}),(0,j.jsxs)("div",{className:"mt-2 space-y-1 text-sm text-modiff-subtle-text",children:[(0,j.jsx)("div",{className:"font-bold text-modiff-text",children:J?J.name:""}),(0,j.jsxs)("div",{children:["Size: ",J?.size?C(J.size):"-"]}),(0,j.jsxs)("div",{children:["Modified: ",J?.modified?v(J.modified):"-"]}),(0,j.jsxs)("div",{children:["Type: ",J?.type??""]})]})]})]})]})})}export{z as default}; \ No newline at end of file diff --git a/web/assets/GalleryLibraryDialog.js b/web/assets/GalleryLibraryDialog.js index 6f0c0c84..b959909b 100644 --- a/web/assets/GalleryLibraryDialog.js +++ b/web/assets/GalleryLibraryDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{B as t,F as a,Gt as s,Hn as i,It as r,Lt as o,Ut as l,X as d,at as n,ft as c,gn as m,lt as p,pn as u,pr as x,qt as f,tt as h,ur as g}from"./graph-vendor.js?v=5bcd315505f5670a";import{$i as b,$n as j,$t as v,B as k,Gt as y,H as w,In as N,Li as S,Ni as $,Qi as I,Sn as C,Ua as L,Ut as D,V as z,Vn as H,Wt as T,Yt as O,_a as R,ai as G,at as F,ea as M,hn as A,jn as P,qt as V,si as E,ta as q,ui as B,z as U}from"./studio-templates.js?v=5bcd315505f5670a";var W=e(x(),1),_=g();function J(e){k(`modiff-${e.modelType}-${e.id}-workflow.json`,U(e))}async function K(e){await z(`modiff-${e.modelType}-${e.id}-workflow.png`,e.url,U(e))}function Q(e){let t=e.templateLabel?` | ${e.templateLabel}`:"",a=e.mediaItems?.find(e=>e.durationSeconds)?.durationSeconds??(e.resolvedExecutionInputs?void 0:e.formSnapshot.audioDuration),s=b(e),i=M(e,"seed"),r=X(e)?" | "+(a?`${a.toFixed(1)} sec`:"audio"):Y(e)?` | ${S(e)}`:` | ${s||"image"}`;return`${e.modelLabel}${t} | ${R[e.mode]}${r} | ${void 0===i?"Seed not captured":`Seed ${i}`}`}function X(e){return"audio"===e.displayType||/\.(wav|mp3|flac|ogg|m4a|aac)(\?.*)?$/i.test(e.url)}function Y(e){return"video"===e.displayType||/\.(mp4|webm|mov|mkv|m4v)(\?.*)?$/i.test(e.url)}function Z({output:e,aspectRatio:t,maxHeight:a,onImageClick:s}){return X(e)?(0,_.jsx)("div",{className:C("grid min-h-24 w-full place-items-center border border-modiff-border bg-modiff-surface p-3","1 / 1"===t&&"aspect-square"),children:(0,_.jsx)("audio",{src:e.url,controls:!0,className:"w-full"})}):Y(e)?(0,_.jsx)("div",{className:C("border border-transparent bg-modiff-surface","1 / 1"===t&&"aspect-square"),children:(0,_.jsx)("video",{src:e.url,controls:!0,controlsList:"nodownload noremoteplayback",className:C("block h-full w-full bg-modiff-media-backdrop object-contain",!!a&&"max-h-[460px]")})}):e.mediaItems&&e.mediaItems.length>1&&"image_collection"===e.displayType?(0,_.jsx)("div",{className:"grid grid-cols-[repeat(auto-fit,minmax(96px,1fr))] gap-1 bg-modiff-surface",children:e.mediaItems.slice(0,12).map(i=>(0,_.jsx)(A,{src:i.url,alt:i.label||`${e.id} item ${i.index+1}`,onClick:s,aspectRatio:t??"1 / 1",maxHeight:a,bordered:!1},`${i.index}-${i.url}`))}):(0,_.jsx)(A,{src:e.url,alt:e.prompt||e.id,onClick:s,aspectRatio:t,maxHeight:a,bordered:!1})}function ee({modalView:e=!1}){let[o,x]=(0,W.useState)("generated"),[g,b]=(0,W.useState)("all"),[k,y]=(0,W.useState)("grid"),[S,$]=(0,W.useState)([]),I=(0,W.useRef)(null),z=B(e=>e.outputs),T=B(e=>e.importedAssets),O=B(e=>e.galleryBackendStatus),R=B(e=>e.galleryBackendError),M=B(e=>e.fetchBackendOutputs),A=B(e=>e.toggleFavoriteOutput),V=B(e=>e.deleteOutput),U=B(e=>e.deleteImportedAsset),Q=B(e=>e.useOutputAsReference),ee=B(e=>e.useImportedAssetAsReference),te=B(e=>e.restoreWorkflowFromOutput),ce=B(e=>e.createWorkflowTab),me=q(e=>e.setRightPanelOpen),pe=q(e=>e.setRightPanelTab),ue=q(e=>e.setGalleryLibraryOpen),xe=q(e=>e.setLightboxOpener),fe=q(e=>e.setMediaExportOpener),he=P(e=>e.sid),ge=P(e=>e.isConnected),be=(0,W.useMemo)(()=>{let e=new Map;return z.forEach(t=>e.set(t.modelType,t.modelLabel)),Array.from(e.entries())},[z]),je=(0,W.useMemo)(()=>"favorites"===g?z.filter(e=>e.favorite):"all"===g?z:z.filter(e=>e.modelType===g),[g,z]),ve=(0,W.useMemo)(()=>{let e=S.map(e=>je.find(t=>t.id===e)).filter(e=>!!e);return e.length>0?e:je.slice(0,2)},[je,S]),ke=ve[0]??je[0],ye=ve[0],we=ve[1],Ne=(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(v,{ref:I,"aria-label":"Import workflow package",accept:"application/json,.json,image/png,.png",className:"sr-only","data-testid":"gallery-import-workflow-input",onChange:e=>{let t=e.currentTarget.files?.[0];e.currentTarget.value="",(async e=>{if(e)try{let t=await w(e);if(!t)throw Error("No MoDiff workflow metadata was found in that file.");ce(t.title,t.snapshot,"import",t.sourceLabel),pe("studio"),ue(!1),L("Workflow package imported",{variant:"success",autoHideDuration:2200}),window.setTimeout(()=>j({sid:he,isConnected:ge,includeStudio:!0,showDialog:!0}),0)}catch(e){L(String(e),{variant:"error",autoHideDuration:6e3})}})(t)}}),(0,_.jsx)(ie,{title:"Import workflow package","data-testid":"gallery-import-workflow",onClick:()=>I.current?.click(),children:(0,_.jsx)(a,{size:15})})]}),Se="idle"===O?"Backend history":"syncing"===O?"Syncing history":"loading"===O?"Loading history":"History offline",$e="generated"===o&&0===z.length&&T.length>0?"imported":o;return 0===z.length&&0===T.length?(0,_.jsxs)("section",{className:C("p-3",e&&"min-h-[520px]"),"data-testid":"gallery-panel",children:[(0,_.jsxs)("div",{className:"mb-1 flex items-center gap-2",children:[(0,_.jsx)("h2",{className:"text-sm font-bold text-modiff-text",children:"Gallery"}),(0,_.jsx)(se,{active:"error"!==O,error:"error"===O,"data-testid":"gallery-backend-status",children:Se}),(0,_.jsx)(ie,{title:"Refresh history","data-testid":"gallery-refresh-history",onClick:()=>{M()},children:(0,_.jsx)(n,{size:15})}),Ne]}),(0,_.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Generated and imported media appear here."}),R&&(0,_.jsx)("p",{className:"mt-1 text-xs text-modiff-red",children:R})]}):(0,_.jsxs)("section",{className:C("p-3",e&&"min-h-[520px]"),"data-testid":"gallery-panel",children:[(0,_.jsxs)("div",{className:"mb-2 flex flex-wrap items-center gap-2",children:[(0,_.jsx)(se,{active:"error"!==O,error:"error"===O,"data-testid":"gallery-backend-status",children:Se}),(0,_.jsx)(ie,{title:"Refresh history","data-testid":"gallery-refresh-history",onClick:()=>{M()},children:(0,_.jsx)(n,{size:15})}),Ne,(0,_.jsxs)(se,{active:"generated"===$e,onClick:()=>x("generated"),"data-testid":"gallery-kind-generated",children:["Generated ",z.length]}),(0,_.jsxs)(se,{active:"imported"===$e,onClick:()=>x("imported"),"data-testid":"gallery-kind-imported",children:["Imported ",T.length]}),"generated"===$e?(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(se,{active:"all"===g,onClick:()=>b("all"),"data-testid":"gallery-filter-all",children:"All"}),(0,_.jsx)(se,{active:"favorites"===g,onClick:()=>b("favorites"),"data-testid":"gallery-filter-favorites",children:"Favorites"}),be.map(([e,t])=>(0,_.jsx)(se,{active:g===e,onClick:()=>b(e),"data-testid":`gallery-filter-${e}`,children:t},e))]}):null]}),"generated"===$e?(0,_.jsxs)("div",{className:"mb-2 flex flex-wrap items-center gap-2",children:[(0,_.jsx)(se,{active:"grid"===k,onClick:()=>y("grid"),"data-testid":"gallery-view-grid",icon:(0,_.jsx)(s,{size:14}),children:"Grid"}),(0,_.jsx)(se,{active:"inspect"===k,onClick:()=>y("inspect"),"data-testid":"gallery-view-inspect",icon:(0,_.jsx)(r,{size:14}),children:"Inspect"}),(0,_.jsx)(se,{active:"compare"===k,onClick:()=>y("compare"),"data-testid":"gallery-view-compare",icon:(0,_.jsx)(i,{size:14}),children:"Compare"}),(0,_.jsx)(se,{active:"lineage"===k,onClick:()=>y("lineage"),"data-testid":"gallery-view-lineage",icon:(0,_.jsx)(f,{size:14}),children:"Lineage"})]}):null,R&&(0,_.jsx)("p",{className:"mb-2 text-xs text-modiff-red",children:R}),"imported"===$e?(0,_.jsx)(ae,{assets:T,onDelete:U,onDownload:e=>{if("unknown"===e.displayType){let t=document.createElement("a");return t.href=e.url,t.download=e.name,t.rel="noopener noreferrer",document.body.append(t),t.click(),void t.remove()}fe({source:e.url,filename:e.name,kind:e.displayType,defaultFormat:"audio"===e.displayType?"wav":"video"===e.displayType?"mp4":"png",title:`Download ${e.displayType}`})},onUse:e=>{ee(e),pe("studio"),ue(!1)}}):0===je.length?(0,_.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No gallery items match this filter."}):"inspect"===k&&ke?(0,_.jsx)(oe,{output:ke}):"compare"===k?(0,_.jsxs)("div",{"data-testid":"gallery-compare-view",className:"grid gap-2",children:[(0,_.jsx)("div",{className:"grid gap-2 sm:grid-cols-2",children:ve.slice(0,2).map(e=>(0,_.jsx)(de,{output:e},`compare-${e.id}`))}),ye&&we&&(0,_.jsx)(le,{left:ye,right:we})]}):"lineage"===k?(0,_.jsx)("div",{"data-testid":"gallery-lineage-view",className:"grid gap-2",children:z.slice(0,24).map(e=>(0,_.jsx)(ne,{output:e,onClick:()=>{$([e.id]),y("inspect")}},`lineage-full-${e.id}`))}):(0,_.jsx)("div",{className:"grid gap-2",children:je.map((e,a)=>{let s=je[a+1],i=S.includes(e.id);return(0,_.jsxs)("article",{"data-testid":`gallery-output-${a}`,className:C("border bg-modiff-surface",i?"border-hf-yellow":"border-modiff-border"),children:[(0,_.jsx)(Z,{output:e,maxHeight:420,onImageClick:()=>{$([e.id]),y("inspect")}}),(0,_.jsxs)("div",{className:"p-2",children:[(0,_.jsx)(re,{output:e}),(0,_.jsx)("p",{className:"mt-1 max-h-10 overflow-hidden text-sm text-modiff-text",children:e.prompt||"No prompt recorded"}),(0,_.jsxs)("div",{className:"mt-2 flex flex-wrap gap-1",children:[(0,_.jsx)(ie,{title:X(e)?"Inspect audio":Y(e)?"Inspect video":"Open full size","data-testid":`gallery-open-${a}`,onClick:()=>{Y(e)||X(e)?($([e.id]),y("inspect")):xe(F([e.url]))},children:(0,_.jsx)(d,{size:15})}),(0,_.jsx)(ie,{title:"Compare with previous visible item",disabled:!s||Y(e)||X(e)||!!s&&(Y(s)||X(s)),"data-testid":`gallery-compare-${a}`,onClick:()=>s&&xe(F([s.url,e.url],1)),children:(0,_.jsx)(d,{size:15})}),(0,_.jsx)(D,{className:"h-7 px-2 text-xs",tone:i?"primary":"secondary",onClick:()=>(e=>{$(t=>t.includes(e.id)?t.filter(t=>t!==e.id):[e.id,...t].slice(0,2))})(e),children:"Select"}),(0,_.jsx)(ie,{title:"Favorite","data-testid":`gallery-favorite-${a}`,onClick:()=>A(e.id),children:(0,_.jsx)(l,{size:15,className:e.favorite?"fill-hf-yellow text-hf-yellow":void 0})}),(0,_.jsx)(ie,{title:X(e)?"Download audio":Y(e)?"Download video":"Download image","data-testid":`gallery-download-image-${a}`,onClick:()=>{let t=X(e)?"audio":Y(e)?"video":"image";fe({source:e.url,filename:`modiff-${e.modelType}-${e.id}${"audio"===t?".wav":"video"===t?".mp4":".png"}`,kind:t,defaultFormat:"audio"===t?"wav":"video"===t?"mp4":"png",title:`Download ${t}`})},children:(0,_.jsx)(u,{size:15})}),(0,_.jsx)(ie,{title:"Download metadata package","data-testid":`gallery-download-package-${a}`,onClick:()=>J(e),children:(0,_.jsx)(u,{size:15})}),!Y(e)&&!X(e)&&(0,_.jsx)(ie,{title:"Download PNG package","data-testid":`gallery-download-png-package-${a}`,onClick:()=>{K(e).then(()=>L("PNG package exported",{variant:"success",autoHideDuration:1800})).catch(e=>L(String(e),{variant:"error",autoHideDuration:6e3}))},children:(0,_.jsx)(u,{size:15})}),(0,_.jsx)(ie,{title:"Restore workflow","data-testid":`gallery-restore-${a}`,onClick:()=>(e=>{te(e),pe("studio"),ue(!1),L("Workflow restored from gallery item",{variant:"success",autoHideDuration:2200})})(e),children:(0,_.jsx)(h,{size:15})}),(0,_.jsx)(ie,{title:"Rerun this workflow","data-testid":`gallery-rerun-${a}`,onClick:()=>{(async e=>{if(!he||!ge)return void L("Connect to the MoDiff server before rerunning.",{variant:"error",autoHideDuration:4e3});te(e);let t=G();try{if(!await N(t))return;await H({sid:he,studioContext:{},workflowContext:t}),ue(!1),me(!0),pe("queue")}catch(e){if(E(e))return;L(String(e),{variant:"error",autoHideDuration:7e3})}})(e)},children:(0,_.jsx)(p,{size:15})}),(0,_.jsx)(ie,{title:"Send to edit","data-testid":`gallery-send-edit-${a}`,onClick:()=>{Q(e),pe("studio"),ue(!1)},children:(0,_.jsx)(c,{size:15})}),(0,_.jsx)(ie,{title:"Copy metadata","data-testid":`gallery-copy-metadata-${a}`,onClick:()=>function(e){navigator.clipboard.writeText(JSON.stringify(e,null,2)),L("Metadata copied",{variant:"success",autoHideDuration:1800})}(e),children:(0,_.jsx)(m,{size:15})}),(0,_.jsx)(ie,{title:"Delete record","data-testid":`gallery-delete-${a}`,onClick:()=>V(e.id),children:(0,_.jsx)(t,{size:15})})]})]})]},e.id)})}),"generated"===$e?(0,_.jsxs)("div",{className:"mt-4",children:[(0,_.jsx)("h2",{className:"mb-2 text-sm font-bold text-modiff-text",children:"Version tree"}),(0,_.jsx)("div",{className:"grid gap-1",children:z.slice(0,12).map(e=>(0,_.jsx)(ne,{output:e,onClick:()=>{Y(e)||X(e)?($([e.id]),y("inspect")):xe(F([e.url]))}},`lineage-${e.id}`))})]}):null]})}function te({asset:e}){return"audio"===e.displayType?(0,_.jsx)("div",{className:"grid min-h-24 w-full place-items-center border border-modiff-border bg-modiff-surface p-3",children:(0,_.jsx)("audio",{src:e.url,controls:!0,className:"w-full"})}):"video"===e.displayType?(0,_.jsx)("video",{src:e.url,controls:!0,controlsList:"nodownload noremoteplayback",className:"block aspect-video w-full border border-modiff-border bg-modiff-media-backdrop object-contain"}):(0,_.jsx)(A,{src:e.url,alt:e.name,aspectRatio:"1 / 1",maxHeight:320,bordered:!1})}function ae({assets:e,onDelete:a,onDownload:s,onUse:i}){return 0===e.length?(0,_.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-xs text-modiff-subtle-text",children:"No imported media yet."}):(0,_.jsx)("div",{className:"grid gap-2 sm:grid-cols-2 xl:grid-cols-3","data-testid":"gallery-imported-grid",children:e.map((e,r)=>(0,_.jsxs)("article",{className:"overflow-hidden border border-modiff-border bg-modiff-surface","data-testid":`gallery-imported-${r}`,children:[(0,_.jsx)(te,{asset:e}),(0,_.jsxs)("div",{className:"flex items-center justify-between gap-2 p-2",children:[(0,_.jsxs)("div",{className:"min-w-0",children:[(0,_.jsx)("p",{className:"truncate text-sm font-semibold text-modiff-text",children:e.name}),(0,_.jsxs)("p",{className:"flex min-w-0 items-center gap-1 text-xs text-modiff-subtle-text",children:[(0,_.jsx)("span",{children:e.displayType}),(0,_.jsx)(se,{active:"backend"===e.storage,"data-testid":`gallery-imported-storage-${r}`,title:"backend"===e.storage?e.backendPath??"Stored through backend":"Stored in this browser",children:"backend"===e.storage?"Backend":"Local"})]})]}),(0,_.jsxs)("div",{className:"flex shrink-0 gap-1",children:[(0,_.jsx)(ie,{title:"Use as input","data-testid":`gallery-imported-use-${r}`,onClick:()=>i(e),children:(0,_.jsx)(c,{size:15})}),(0,_.jsx)(ie,{title:"Download asset","data-testid":`gallery-imported-download-${r}`,onClick:()=>s(e),children:(0,_.jsx)(u,{size:15})}),(0,_.jsx)(ie,{title:"Remove imported asset","data-testid":`gallery-imported-delete-${r}`,onClick:()=>a(e.id),children:(0,_.jsx)(t,{size:15})})]})]})]},e.id))})}function se({active:e,children:t,error:a=!1,icon:s,onClick:i,...r}){let o="inline-flex items-center gap-1 rounded-modiff-compact px-2 text-xs font-semibold";return i?(0,_.jsxs)(O,{active:e,tone:a?"error":"default",className:C(o,"min-h-7"),onClick:i,...r,children:[s,t]}):(0,_.jsxs)(V,{className:C(o,"min-h-6"),tone:a?"error":e?"success":"default",...r,children:[s,t]})}function ie({children:e,className:t,title:a,...s}){return(0,_.jsx)(y,{label:a,size:"compact",className:C("text-modiff-subtle-text hover:bg-modiff-surface-hover hover:text-hf-yellow",t),...s,children:e})}function re({output:e}){return(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:Q(e)}),(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:[new Date(e.createdAt).toLocaleString()," | Run ",e.runId||"untracked"," | Task"," ",e.taskId||"untracked"]}),e.backendImagePath&&(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Stored: ",e.backendImagePath]}),e.backendMediaPath&&(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Stored: ",e.backendMediaPath]}),e.mediaItems&&e.mediaItems.length>1&&(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Media items: ",e.mediaItems.length," | Hash: ",e.mediaCollectionHash||e.mediaHash||"pending"]}),e.variationGroupId&&(0,_.jsxs)("p",{className:"break-all text-xs text-hf-orange",children:["Sweep: ",e.variationLabel||"Variant"," | ",e.variationGroupId]}),(e.sourceOutputId||e.referenceImages.length>0)&&(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Source: ",e.sourceOutputId||e.referenceImages.join(", ")]})]})}function oe({output:e}){let t=Y(e)?{requestedSize:`${e.formSnapshot.width}x${e.formSnapshot.height}`,requestedNumFrames:e.formSnapshot.numFrames,requestedFps:e.formSnapshot.fps,resolvedSize:e.resolvedExecutionInputs?`${I(e,"width")}x${I(e,"height")}`:void 0,encodedVideo:$(e)}:{size:b(e)??"Not captured",resolvedInputDimensions:{width:I(e,"width"),height:I(e,"height")}};return(0,_.jsxs)("article",{"data-testid":"gallery-inspect-view",className:"border border-modiff-border bg-modiff-surface",children:[(0,_.jsx)(Z,{output:e,maxHeight:460}),(0,_.jsxs)("div",{className:"p-2",children:[(0,_.jsx)("h2",{className:"text-sm font-bold text-modiff-text",children:Q(e)}),(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:[new Date(e.createdAt).toLocaleString()," | Run ",e.runId||"untracked"," | Task"," ",e.taskId||"untracked"]}),(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Repo: ",e.repo," | Node: ",e.nodeId,":",e.fieldKey]}),(0,_.jsx)("p",{className:"mt-2 text-sm text-modiff-text",children:e.prompt||"No prompt recorded"}),(0,_.jsx)("p",{className:"mt-2 text-xs text-modiff-subtle-text","data-testid":"resolved-inputs-status",children:e.resolvedExecutionInputs?"Resolved node inputs captured by the backend. Connected values take precedence over saved field fallbacks. These are call arguments, not inferred library defaults.":"Legacy form/graph settings only: resolved node inputs were not captured for this output."}),(0,_.jsx)("pre",{className:"mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 font-mono text-xs text-modiff-subtle-text",children:JSON.stringify({seed:I(e,"seed"),...t,steps:I(e,"steps"),guidanceScale:I(e,"guidanceScale"),resolvedExecutionInputs:e.resolvedExecutionInputs,templateLockHash:e.templateLockHash,promptSettingsHash:e.promptSettingsHash,exactTemplateCompatible:e.exactTemplateCompatible,mediaHash:e.mediaHash,mediaCollectionHash:e.mediaCollectionHash,mediaItems:e.mediaItems,variationGroupId:e.variationGroupId,variationLabel:e.variationLabel,provenance:e.provenance,sourceOutputId:e.sourceOutputId,referenceImages:e.referenceImages,sourceVideo:e.formSnapshot.sourceVideo,maskVideo:e.formSnapshot.maskVideo,controlVideo:e.formSnapshot.controlVideo,sourceAudio:e.formSnapshot.sourceAudio,referenceAudio:e.formSnapshot.referenceAudio,audioDuration:e.formSnapshot.audioDuration,numFrames:I(e,"numFrames"),fps:I(e,"fps"),backendImagePath:e.backendImagePath,backendMediaPath:e.backendMediaPath},null,2)})]})]})}function le({left:e,right:t}){let a=[["Prompt",e.prompt||"No prompt",t.prompt||"No prompt"],["Seed",String(I(e,"seed")),String(I(t,"seed"))],["Model",e.modelLabel,t.modelLabel],["Steps",String(I(e,"steps")),String(I(t,"steps"))],["Guidance",String(I(e,"guidanceScale")),String(I(t,"guidanceScale"))],["Size",`${I(e,"width")}x${I(e,"height")}`,`${I(t,"width")}x${I(t,"height")}`],["Template",e.templateLabel||"Manual",t.templateLabel||"Manual"],["Source",e.sourceOutputId||e.referenceImages.join(", ")||"None",t.sourceOutputId||t.referenceImages.join(", ")||"None"]];return(0,_.jsxs)("article",{className:"border border-modiff-border bg-modiff-surface p-2",children:[(0,_.jsx)("h3",{className:"mb-2 text-sm font-bold text-modiff-text",children:"Settings diff"}),(0,_.jsx)("div",{className:"grid gap-1",children:a.map(([e,t,a])=>(0,_.jsxs)("div",{className:"grid gap-1 border border-modiff-border bg-modiff-bg p-2 text-xs sm:grid-cols-[90px_1fr_1fr]",children:[(0,_.jsx)("span",{className:t===a?"font-semibold text-modiff-subtle-text":"font-semibold text-hf-orange",children:e}),(0,_.jsx)("span",{className:"min-w-0 break-words text-modiff-subtle-text",children:t}),(0,_.jsx)("span",{className:"min-w-0 break-words text-modiff-subtle-text",children:a})]},e))})]})}function de({output:e}){return(0,_.jsxs)("article",{className:"border border-modiff-border bg-modiff-surface",children:[(0,_.jsx)(Z,{output:e,aspectRatio:"1 / 1"}),(0,_.jsxs)("div",{className:"p-2",children:[(0,_.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:Q(e)}),e.variationGroupId&&(0,_.jsxs)("p",{className:"text-xs text-hf-orange",children:[e.variationLabel||"Sweep variant"," | ",e.variationGroupId]}),(0,_.jsx)("p",{className:"max-h-16 overflow-hidden text-sm text-modiff-text",children:e.prompt})]})]})}function ne({output:e,onClick:t}){return(0,_.jsxs)(D,{tone:"ghost",align:"left",size:"compact",className:"h-auto border border-transparent px-2 py-1 text-xs text-modiff-subtle-text hover:border-modiff-border",onClick:t,children:[e.variationGroupId?"Sweep":e.parentId?"Branch":"Root"," |"," ",e.variationLabel||e.modelLabel," | ",new Date(e.createdAt).toLocaleString()," |"," ",e.prompt||e.id]})}function ce(){let e=q(e=>e.galleryLibraryOpen),t=q(e=>e.setGalleryLibraryOpen);return(0,_.jsx)(T,{open:e,onClose:()=>t(!1),title:(0,_.jsxs)("span",{className:"inline-flex min-w-0 items-center gap-2",children:[(0,_.jsx)(o,{size:17,className:"text-hf-yellow"}),(0,_.jsx)("span",{className:"truncate",children:"Output Gallery"})]}),panelClassName:"max-w-[1180px]",bodyClassName:"max-h-[76vh] p-0",children:(0,_.jsx)(ee,{modalView:!0})})}export{ce as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{$ as t,Ft as a,P as s,Pt as i,Rn as r,Ut as o,Vt as l,Wt as d,Y as n,cr as c,dr as m,pn as p,rt as u,st as x,un as f,ut as h,z as g}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$n as b,Ei as j,G as v,In as k,Jn as y,K as w,Mo as N,Oi as S,Sa as $,U as C,W as I,an as z,ba as D,ea as L,hr as H,ia as T,in as O,ir as R,ji as G,kn as M,ln as F,pn as P,rn as A,sn as V,va as E,vt as q,xa as B,ya as W,za as U}from"./studio-templates.js?v=cc046c26d6fe7aa4";var J=e(m(),1),_=c();function K(e){I(`modiff-${e.modelType}-${e.id}-workflow.json`,C(e))}async function Y(e){await v(`modiff-${e.modelType}-${e.id}-workflow.png`,e.url,C(e))}function Q(e){let t=e.templateLabel?` | ${e.templateLabel}`:"",a=e.mediaItems?.find(e=>e.durationSeconds)?.durationSeconds??(e.resolvedExecutionInputs?void 0:e.formSnapshot.audioDuration),s=W(e),i=B(e,"seed"),r=X(e)?" | "+(a?`${a.toFixed(1)} sec`:"audio"):Z(e)?` | ${T(e)}`:` | ${s||"image"}`;return`${e.modelLabel}${t} | ${U[e.mode]}${r} | ${void 0===i?"Seed not captured":`Seed ${i}`}`}function X(e){return"audio"===e.displayType||/\.(wav|mp3|flac|ogg|m4a|aac)(\?.*)?$/i.test(e.url)}function Z(e){return"video"===e.displayType||/\.(mp4|webm|mov|mkv|m4v)(\?.*)?$/i.test(e.url)}function ee({output:e,aspectRatio:t,maxHeight:a,onImageClick:s}){return X(e)?(0,_.jsx)("div",{className:k("grid min-h-24 w-full place-items-center border border-modiff-border bg-modiff-surface p-3","1 / 1"===t&&"aspect-square"),children:(0,_.jsx)("audio",{src:e.url,controls:!0,className:"w-full"})}):Z(e)?(0,_.jsx)("div",{className:k("border border-transparent bg-modiff-surface","1 / 1"===t&&"aspect-square"),children:(0,_.jsx)("video",{src:e.url,controls:!0,controlsList:"nodownload noremoteplayback",className:k("block h-full w-full bg-modiff-media-backdrop object-contain",!!a&&"max-h-[460px]")})}):e.mediaItems&&e.mediaItems.length>1&&"image_collection"===e.displayType?(0,_.jsx)("div",{className:"grid grid-cols-[repeat(auto-fit,minmax(96px,1fr))] gap-1 bg-modiff-surface",children:e.mediaItems.slice(0,12).map(i=>(0,_.jsx)(M,{src:i.url,alt:i.label||`${e.id} item ${i.index+1}`,onClick:s,aspectRatio:t??"1 / 1",maxHeight:a,bordered:!1},`${i.index}-${i.url}`))}):(0,_.jsx)(M,{src:e.url,alt:e.prompt||e.id,onClick:s,aspectRatio:t,maxHeight:a,bordered:!1})}function te({modalView:e=!1}){let[a,c]=(0,J.useState)("generated"),[m,v]=(0,J.useState)("all"),[C,I]=(0,J.useState)("grid"),[z,L]=(0,J.useState)([]),T=(0,J.useRef)(null),O=G(e=>e.outputs),M=G(e=>e.importedAssets),F=G(e=>e.galleryBackendStatus),V=G(e=>e.galleryBackendError),E=G(e=>e.fetchBackendOutputs),B=G(e=>e.toggleFavoriteOutput),W=G(e=>e.deleteOutput),U=G(e=>e.deleteImportedAsset),Q=G(e=>e.useOutputAsReference),te=G(e=>e.useImportedAssetAsReference),ae=G(e=>e.restoreWorkflowFromOutput),me=G(e=>e.createWorkflowTab),pe=$(e=>e.setRightPanelOpen),ue=$(e=>e.setRightPanelTab),xe=$(e=>e.setGalleryLibraryOpen),fe=$(e=>e.setLightboxOpener),he=$(e=>e.setMediaExportOpener),ge=y(e=>e.sid),be=y(e=>e.isConnected),je=(0,J.useMemo)(()=>{let e=new Map;return O.forEach(t=>e.set(D(t),t.modelLabel)),Array.from(e.entries())},[O]),ve=(0,J.useMemo)(()=>"favorites"===m?O.filter(e=>e.favorite):"all"===m?O:O.filter(e=>`model:${D(e)}`===m),[m,O]),ke=(0,J.useMemo)(()=>{let e=z.map(e=>ve.find(t=>t.id===e)).filter(e=>!!e);return e.length>0?e:ve.slice(0,2)},[ve,z]),ye=ke[0]??ve[0],we=ke[0],Ne=ke[1],Se=(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(P,{ref:T,"aria-label":"Import workflow package",accept:"application/json,.json,image/png,.png",className:"sr-only","data-testid":"gallery-import-workflow-input",onChange:e=>{let t=e.currentTarget.files?.[0];e.currentTarget.value="",(async e=>{if(e)try{let t=await w(e);if(!t)throw Error("No MoDiff workflow metadata was found in that file.");me(t.title,t.snapshot,"import",t.sourceLabel),ue("studio"),xe(!1),N("Workflow package imported",{variant:"success",autoHideDuration:2200}),window.setTimeout(()=>H({sid:ge,isConnected:be,includeStudio:!0,showDialog:!0}),0)}catch(e){N(String(e),{variant:"error",autoHideDuration:6e3})}})(t)}}),(0,_.jsx)(re,{title:"Import workflow package","data-testid":"gallery-import-workflow",onClick:()=>T.current?.click(),children:(0,_.jsx)(s,{size:15})})]}),$e="idle"===F?"Backend history":"syncing"===F?"Syncing history":"loading"===F?"Loading history":"History offline",Ce="generated"===a&&0===O.length&&M.length>0?"imported":a;return 0===O.length&&0===M.length?(0,_.jsxs)("section",{className:k("p-3",e&&"min-h-[520px]"),"data-testid":"gallery-panel",children:[(0,_.jsxs)("div",{className:"mb-1 flex items-center gap-2",children:[(0,_.jsx)("h2",{className:"text-sm font-bold text-modiff-text",children:"Gallery"}),(0,_.jsx)(ie,{active:"error"!==F,error:"error"===F,"data-testid":"gallery-backend-status",children:$e}),(0,_.jsx)(re,{title:"Refresh history","data-testid":"gallery-refresh-history",onClick:()=>{E()},children:(0,_.jsx)(u,{size:15})}),Se]}),(0,_.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Generated and imported media appear here."}),V&&(0,_.jsx)("p",{className:"mt-1 text-xs text-modiff-red",children:V})]}):(0,_.jsxs)("section",{className:k("p-3",e&&"min-h-[520px]"),"data-testid":"gallery-panel",children:[(0,_.jsxs)("div",{className:"mb-2 flex flex-wrap items-center gap-2",children:[(0,_.jsx)(ie,{active:"error"!==F,error:"error"===F,"data-testid":"gallery-backend-status",children:$e}),(0,_.jsx)(re,{title:"Refresh history","data-testid":"gallery-refresh-history",onClick:()=>{E()},children:(0,_.jsx)(u,{size:15})}),Se,(0,_.jsxs)(ie,{active:"generated"===Ce,onClick:()=>c("generated"),"data-testid":"gallery-kind-generated",children:["Generated ",O.length]}),(0,_.jsxs)(ie,{active:"imported"===Ce,onClick:()=>c("imported"),"data-testid":"gallery-kind-imported",children:["Imported ",M.length]}),"generated"===Ce?(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(ie,{active:"all"===m,onClick:()=>v("all"),"data-testid":"gallery-filter-all",children:"All"}),(0,_.jsx)(ie,{active:"favorites"===m,onClick:()=>v("favorites"),"data-testid":"gallery-filter-favorites",children:"Favorites"}),je.map(([e,t])=>(0,_.jsx)(ie,{active:m===`model:${e}`,onClick:()=>v(`model:${e}`),"data-testid":`gallery-filter-${e}`,children:t},e))]}):null]}),"generated"===Ce?(0,_.jsxs)("div",{className:"mb-2 flex flex-wrap items-center gap-2",children:[(0,_.jsx)(ie,{active:"grid"===C,onClick:()=>I("grid"),"data-testid":"gallery-view-grid",icon:(0,_.jsx)(o,{size:14}),children:"Grid"}),(0,_.jsx)(ie,{active:"inspect"===C,onClick:()=>I("inspect"),"data-testid":"gallery-view-inspect",icon:(0,_.jsx)(i,{size:14}),children:"Inspect"}),(0,_.jsx)(ie,{active:"compare"===C,onClick:()=>I("compare"),"data-testid":"gallery-view-compare",icon:(0,_.jsx)(r,{size:14}),children:"Compare"}),(0,_.jsx)(ie,{active:"lineage"===C,onClick:()=>I("lineage"),"data-testid":"gallery-view-lineage",icon:(0,_.jsx)(d,{size:14}),children:"Lineage"})]}):null,V&&(0,_.jsx)("p",{className:"mb-2 text-xs text-modiff-red",children:V}),"imported"===Ce?(0,_.jsx)(se,{assets:M,onDelete:U,onDownload:e=>{if("unknown"===e.displayType){let t=document.createElement("a");return t.href=e.url,t.download=e.name,t.rel="noopener noreferrer",document.body.append(t),t.click(),void t.remove()}he({source:e.url,filename:e.name,kind:e.displayType,defaultFormat:"audio"===e.displayType?"wav":"video"===e.displayType?"mp4":"png",title:`Download ${e.displayType}`})},onUse:e=>{te(e),ue("studio"),xe(!1)}}):0===ve.length?(0,_.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No gallery items match this filter."}):"inspect"===C&&ye?(0,_.jsx)(le,{output:ye}):"compare"===C?(0,_.jsxs)("div",{"data-testid":"gallery-compare-view",className:"grid gap-2",children:[(0,_.jsx)("div",{className:"grid gap-2 sm:grid-cols-2",children:ke.slice(0,2).map(e=>(0,_.jsx)(ne,{output:e},`compare-${e.id}`))}),we&&Ne&&(0,_.jsx)(de,{left:we,right:Ne})]}):"lineage"===C?(0,_.jsx)("div",{"data-testid":"gallery-lineage-view",className:"grid gap-2",children:O.slice(0,24).map(e=>(0,_.jsx)(ce,{output:e,onClick:()=>{L([e.id]),I("inspect")}},`lineage-full-${e.id}`))}):(0,_.jsx)("div",{className:"grid gap-2",children:ve.map((e,a)=>{let s=ve[a+1],i=z.includes(e.id);return(0,_.jsxs)("article",{"data-testid":`gallery-output-${a}`,className:k("border bg-modiff-surface",i?"border-hf-yellow":"border-modiff-border"),children:[(0,_.jsx)(ee,{output:e,maxHeight:420,onImageClick:()=>{L([e.id]),I("inspect")}}),(0,_.jsxs)("div",{className:"p-2",children:[(0,_.jsx)(oe,{output:e}),(0,_.jsx)("p",{className:"mt-1 max-h-10 overflow-hidden text-sm text-modiff-text",children:e.prompt||"No prompt recorded"}),(0,_.jsxs)("div",{className:"mt-2 flex flex-wrap gap-1",children:[(0,_.jsx)(re,{title:X(e)?"Inspect audio":Z(e)?"Inspect video":"Open full size","data-testid":`gallery-open-${a}`,onClick:()=>{Z(e)||X(e)?(L([e.id]),I("inspect")):fe(q([e.url]))},children:(0,_.jsx)(n,{size:15})}),(0,_.jsx)(re,{title:"Compare with previous visible item",disabled:!s||Z(e)||X(e)||!!s&&(Z(s)||X(s)),"data-testid":`gallery-compare-${a}`,onClick:()=>s&&fe(q([s.url,e.url],1)),children:(0,_.jsx)(n,{size:15})}),(0,_.jsx)(A,{className:"h-7 px-2 text-xs",tone:i?"primary":"secondary",onClick:()=>(e=>{L(t=>t.includes(e.id)?t.filter(t=>t!==e.id):[e.id,...t].slice(0,2))})(e),children:"Select"}),(0,_.jsx)(re,{title:"Favorite","data-testid":`gallery-favorite-${a}`,onClick:()=>B(e.id),children:(0,_.jsx)(l,{size:15,className:e.favorite?"fill-hf-yellow text-hf-yellow":void 0})}),(0,_.jsx)(re,{title:X(e)?"Download audio":Z(e)?"Download video":"Download image","data-testid":`gallery-download-image-${a}`,onClick:()=>{let t=X(e)?"audio":Z(e)?"video":"image";he({source:e.url,filename:`modiff-${e.modelType}-${e.id}${"audio"===t?".wav":"video"===t?".mp4":".png"}`,kind:t,defaultFormat:"audio"===t?"wav":"video"===t?"mp4":"png",title:`Download ${t}`})},children:(0,_.jsx)(f,{size:15})}),(0,_.jsx)(re,{title:"Download metadata package","data-testid":`gallery-download-package-${a}`,onClick:()=>K(e),children:(0,_.jsx)(f,{size:15})}),!Z(e)&&!X(e)&&(0,_.jsx)(re,{title:"Download PNG package","data-testid":`gallery-download-png-package-${a}`,onClick:()=>{Y(e).then(()=>N("PNG package exported",{variant:"success",autoHideDuration:1800})).catch(e=>N(String(e),{variant:"error",autoHideDuration:6e3}))},children:(0,_.jsx)(f,{size:15})}),(0,_.jsx)(re,{title:"Restore workflow","data-testid":`gallery-restore-${a}`,onClick:()=>(e=>{ae(e),ue("studio"),xe(!1),N("Workflow restored from gallery item",{variant:"success",autoHideDuration:2200})})(e),children:(0,_.jsx)(t,{size:15})}),(0,_.jsx)(re,{title:"Rerun this workflow","data-testid":`gallery-rerun-${a}`,onClick:()=>{(async e=>{if(!ge||!be)return void N("Connect to the MoDiff server before rerunning.",{variant:"error",autoHideDuration:4e3});ae(e);let t=j();try{if(!await b(t))return;await R({sid:ge,studioContext:{},workflowContext:t}),xe(!1),pe(!0),ue("queue")}catch(e){if(S(e))return;N(String(e),{variant:"error",autoHideDuration:7e3})}})(e)},children:(0,_.jsx)(x,{size:15})}),(0,_.jsx)(re,{title:"Send to edit","data-testid":`gallery-send-edit-${a}`,onClick:()=>{Q(e),ue("studio"),xe(!1)},children:(0,_.jsx)(h,{size:15})}),(0,_.jsx)(re,{title:"Copy metadata","data-testid":`gallery-copy-metadata-${a}`,onClick:()=>function(e){navigator.clipboard.writeText(JSON.stringify(e,null,2)),N("Metadata copied",{variant:"success",autoHideDuration:1800})}(e),children:(0,_.jsx)(p,{size:15})}),(0,_.jsx)(re,{title:"Delete record","data-testid":`gallery-delete-${a}`,onClick:()=>W(e.id),children:(0,_.jsx)(g,{size:15})})]})]})]},e.id)})}),"generated"===Ce?(0,_.jsxs)("div",{className:"mt-4",children:[(0,_.jsx)("h2",{className:"mb-2 text-sm font-bold text-modiff-text",children:"Version tree"}),(0,_.jsx)("div",{className:"grid gap-1",children:O.slice(0,12).map(e=>(0,_.jsx)(ce,{output:e,onClick:()=>{Z(e)||X(e)?(L([e.id]),I("inspect")):fe(q([e.url]))}},`lineage-${e.id}`))})]}):null]})}function ae({asset:e}){return"audio"===e.displayType?(0,_.jsx)("div",{className:"grid min-h-24 w-full place-items-center border border-modiff-border bg-modiff-surface p-3",children:(0,_.jsx)("audio",{src:e.url,controls:!0,className:"w-full"})}):"video"===e.displayType?(0,_.jsx)("video",{src:e.url,controls:!0,controlsList:"nodownload noremoteplayback",className:"block aspect-video w-full border border-modiff-border bg-modiff-media-backdrop object-contain"}):(0,_.jsx)(M,{src:e.url,alt:e.name,aspectRatio:"1 / 1",maxHeight:320,bordered:!1})}function se({assets:e,onDelete:t,onDownload:a,onUse:s}){return 0===e.length?(0,_.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-xs text-modiff-subtle-text",children:"No imported media yet."}):(0,_.jsx)("div",{className:"grid gap-2 sm:grid-cols-2 xl:grid-cols-3","data-testid":"gallery-imported-grid",children:e.map((e,i)=>(0,_.jsxs)("article",{className:"overflow-hidden border border-modiff-border bg-modiff-surface","data-testid":`gallery-imported-${i}`,children:[(0,_.jsx)(ae,{asset:e}),(0,_.jsxs)("div",{className:"flex items-center justify-between gap-2 p-2",children:[(0,_.jsxs)("div",{className:"min-w-0",children:[(0,_.jsx)("p",{className:"truncate text-sm font-semibold text-modiff-text",children:e.name}),(0,_.jsxs)("p",{className:"flex min-w-0 items-center gap-1 text-xs text-modiff-subtle-text",children:[(0,_.jsx)("span",{children:e.displayType}),(0,_.jsx)(ie,{active:"backend"===e.storage,"data-testid":`gallery-imported-storage-${i}`,title:"backend"===e.storage?e.backendPath??"Stored through backend":"Stored in this browser",children:"backend"===e.storage?"Backend":"Local"})]})]}),(0,_.jsxs)("div",{className:"flex shrink-0 gap-1",children:[(0,_.jsx)(re,{title:"Use as input","data-testid":`gallery-imported-use-${i}`,onClick:()=>s(e),children:(0,_.jsx)(h,{size:15})}),(0,_.jsx)(re,{title:"Download asset","data-testid":`gallery-imported-download-${i}`,onClick:()=>a(e),children:(0,_.jsx)(f,{size:15})}),(0,_.jsx)(re,{title:"Remove imported asset","data-testid":`gallery-imported-delete-${i}`,onClick:()=>t(e.id),children:(0,_.jsx)(g,{size:15})})]})]})]},e.id))})}function ie({active:e,children:t,error:a=!1,icon:s,onClick:i,...r}){let o="inline-flex items-center gap-1 rounded-modiff-compact px-2 text-xs font-semibold";return i?(0,_.jsxs)(F,{active:e,tone:a?"error":"default",className:k(o,"min-h-7"),onClick:i,...r,children:[s,t]}):(0,_.jsxs)(V,{className:k(o,"min-h-6"),tone:a?"error":e?"success":"default",...r,children:[s,t]})}function re({children:e,className:t,title:a,...s}){return(0,_.jsx)(z,{label:a,size:"compact",className:k("text-modiff-subtle-text hover:bg-modiff-surface-hover hover:text-hf-yellow",t),...s,children:e})}function oe({output:e}){return(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:Q(e)}),(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:[new Date(e.createdAt).toLocaleString()," | Run ",e.runId||"untracked"," | Task"," ",e.taskId||"untracked"]}),e.backendImagePath&&(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Stored: ",e.backendImagePath]}),e.backendMediaPath&&(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Stored: ",e.backendMediaPath]}),e.mediaItems&&e.mediaItems.length>1&&(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Media items: ",e.mediaItems.length," | Hash: ",e.mediaCollectionHash||e.mediaHash||"pending"]}),e.variationGroupId&&(0,_.jsxs)("p",{className:"break-all text-xs text-hf-orange",children:["Sweep: ",e.variationLabel||"Variant"," | ",e.variationGroupId]}),(e.sourceOutputId||e.referenceImages.length>0)&&(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Source: ",e.sourceOutputId||e.referenceImages.join(", ")]})]})}function le({output:e}){let t=Z(e)?{requestedSize:`${e.formSnapshot.width}x${e.formSnapshot.height}`,requestedNumFrames:e.formSnapshot.numFrames,requestedFps:e.formSnapshot.fps,resolvedSize:e.resolvedExecutionInputs?`${E(e,"width")}x${E(e,"height")}`:void 0,encodedVideo:L(e)}:{size:W(e)??"Not captured",resolvedInputDimensions:{width:E(e,"width"),height:E(e,"height")}};return(0,_.jsxs)("article",{"data-testid":"gallery-inspect-view",className:"border border-modiff-border bg-modiff-surface",children:[(0,_.jsx)(ee,{output:e,maxHeight:460}),(0,_.jsxs)("div",{className:"p-2",children:[(0,_.jsx)("h2",{className:"text-sm font-bold text-modiff-text",children:Q(e)}),(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:[new Date(e.createdAt).toLocaleString()," | Run ",e.runId||"untracked"," | Task"," ",e.taskId||"untracked"]}),(0,_.jsxs)("p",{className:"break-all text-xs text-modiff-subtle-text",children:["Repo: ",e.repo," | Node: ",e.nodeId,":",e.fieldKey]}),(0,_.jsx)("p",{className:"mt-2 text-sm text-modiff-text",children:e.prompt||"No prompt recorded"}),(0,_.jsx)("p",{className:"mt-2 text-xs text-modiff-subtle-text","data-testid":"resolved-inputs-status",children:e.resolvedExecutionInputs?"Resolved node inputs captured by the backend. Connected values take precedence over saved field fallbacks. These are call arguments, not inferred library defaults.":"Legacy form/graph settings only: resolved node inputs were not captured for this output."}),(0,_.jsx)("pre",{className:"mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 font-mono text-xs text-modiff-subtle-text",children:JSON.stringify({seed:E(e,"seed"),...t,steps:E(e,"steps"),guidanceScale:E(e,"guidanceScale"),resolvedExecutionInputs:e.resolvedExecutionInputs,templateLockHash:e.templateLockHash,promptSettingsHash:e.promptSettingsHash,exactTemplateCompatible:e.exactTemplateCompatible,mediaHash:e.mediaHash,mediaCollectionHash:e.mediaCollectionHash,mediaItems:e.mediaItems,variationGroupId:e.variationGroupId,variationLabel:e.variationLabel,provenance:e.provenance,sourceOutputId:e.sourceOutputId,referenceImages:e.referenceImages,sourceVideo:e.formSnapshot.sourceVideo,maskVideo:e.formSnapshot.maskVideo,controlVideo:e.formSnapshot.controlVideo,sourceAudio:e.formSnapshot.sourceAudio,referenceAudio:e.formSnapshot.referenceAudio,audioDuration:e.formSnapshot.audioDuration,numFrames:E(e,"numFrames"),fps:E(e,"fps"),backendImagePath:e.backendImagePath,backendMediaPath:e.backendMediaPath},null,2)})]})]})}function de({left:e,right:t}){let a=[["Prompt",e.prompt||"No prompt",t.prompt||"No prompt"],["Seed",String(E(e,"seed")),String(E(t,"seed"))],["Model",e.modelLabel,t.modelLabel],["Steps",String(E(e,"steps")),String(E(t,"steps"))],["Guidance",String(E(e,"guidanceScale")),String(E(t,"guidanceScale"))],["Size",`${E(e,"width")}x${E(e,"height")}`,`${E(t,"width")}x${E(t,"height")}`],["Template",e.templateLabel||"Manual",t.templateLabel||"Manual"],["Source",e.sourceOutputId||e.referenceImages.join(", ")||"None",t.sourceOutputId||t.referenceImages.join(", ")||"None"]];return(0,_.jsxs)("article",{className:"border border-modiff-border bg-modiff-surface p-2",children:[(0,_.jsx)("h3",{className:"mb-2 text-sm font-bold text-modiff-text",children:"Settings diff"}),(0,_.jsx)("div",{className:"grid gap-1",children:a.map(([e,t,a])=>(0,_.jsxs)("div",{className:"grid gap-1 border border-modiff-border bg-modiff-bg p-2 text-xs sm:grid-cols-[90px_1fr_1fr]",children:[(0,_.jsx)("span",{className:t===a?"font-semibold text-modiff-subtle-text":"font-semibold text-hf-orange",children:e}),(0,_.jsx)("span",{className:"min-w-0 break-words text-modiff-subtle-text",children:t}),(0,_.jsx)("span",{className:"min-w-0 break-words text-modiff-subtle-text",children:a})]},e))})]})}function ne({output:e}){return(0,_.jsxs)("article",{className:"border border-modiff-border bg-modiff-surface",children:[(0,_.jsx)(ee,{output:e,aspectRatio:"1 / 1"}),(0,_.jsxs)("div",{className:"p-2",children:[(0,_.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:Q(e)}),e.variationGroupId&&(0,_.jsxs)("p",{className:"text-xs text-hf-orange",children:[e.variationLabel||"Sweep variant"," | ",e.variationGroupId]}),(0,_.jsx)("p",{className:"max-h-16 overflow-hidden text-sm text-modiff-text",children:e.prompt})]})]})}function ce({output:e,onClick:t}){return(0,_.jsxs)(A,{tone:"ghost",align:"left",size:"compact",className:"h-auto border border-transparent px-2 py-1 text-xs text-modiff-subtle-text hover:border-modiff-border",onClick:t,children:[e.variationGroupId?"Sweep":e.parentId?"Branch":"Root"," |"," ",e.variationLabel||e.modelLabel," | ",new Date(e.createdAt).toLocaleString()," |"," ",e.prompt||e.id]})}function me(){let e=$(e=>e.galleryLibraryOpen),t=$(e=>e.setGalleryLibraryOpen);return(0,_.jsx)(O,{open:e,onClose:()=>t(!1),title:(0,_.jsxs)("span",{className:"inline-flex min-w-0 items-center gap-2",children:[(0,_.jsx)(a,{size:17,className:"text-hf-yellow"}),(0,_.jsx)("span",{className:"truncate",children:"Output Gallery"})]}),panelClassName:"max-w-[1180px]",bodyClassName:"max-h-[76vh] p-0",children:(0,_.jsx)(te,{modalView:!0})})}export{me as default}; \ No newline at end of file diff --git a/web/assets/GraphList.js b/web/assets/GraphList.js index c85eb321..cc1a610e 100644 --- a/web/assets/GraphList.js +++ b/web/assets/GraphList.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{$t as t,B as a,Ot as r,Zt as i,fn as s,ft as l,gn as o,pn as n,pr as d,sn as c,tt as f,ur as m}from"./graph-vendor.js?v=5bcd315505f5670a";import{$a as u,Fn as p,Gt as h,Lo as w,Mn as x,Nn as b,Pn as y,St as g,Ti as v,Ua as k,an as j,bt as N,dt as S,en as D,eo as C,gt as A,mt as $,on as T,ro as z,ta as L,to as M,ui as E,vt as W,xt as R,yt as q}from"./studio-templates.js?v=5bcd315505f5670a";var H=e(d(),1),K=m(),O=C();function B(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function U(e,t){if(!B(e)||"boolean"!=typeof e.isDir||"string"!=typeof e.path||"string"!=typeof e.name)throw Error(`Workflow entry ${t+1} is invalid.`);if(void 0!==e.children&&!Array.isArray(e.children))throw Error(`Workflow entry ${t+1} has invalid children.`);return{isDir:e.isDir,path:e.path,name:e.name,children:e.children?.map(U),modelType:"string"==typeof e.modelType?e.modelType:void 0,mode:"string"==typeof e.mode?e.mode:void 0,mediaKind:"string"==typeof e.mediaKind?e.mediaKind:void 0,supportTier:"string"==typeof e.supportTier?e.supportTier:void 0,qualificationStatus:"string"==typeof e.qualificationStatus?e.qualificationStatus:void 0,requiredArtifacts:Array.isArray(e.requiredArtifacts)?e.requiredArtifacts.filter(e=>"string"==typeof e):void 0}}function I(e){return B(e)&&"string"==typeof e.id?{id:e.id,title:"string"==typeof e.title?e.title:"Workflow",createdAt:"number"==typeof e.createdAt?e.createdAt:Date.now(),updatedAt:"number"==typeof e.updatedAt?e.updatedAt:Date.now(),dirty:!1,source:"string"==typeof e.source?e.source:"manual",sourceLabel:"string"==typeof e.sourceLabel?e.sourceLabel:void 0,backendRevision:"number"==typeof e.revision?e.revision:0}:null}function _(e){if(!Array.isArray(e))throw Error("The workflow list response must be an array.");return e.map(U)}var F=new Set(["modiff","modular_diffusers"]);function P(e){let t=e.filter(e=>e.isDir&&F.has(e.name));if(0===t.length)return e;let a={isDir:!0,path:"modiff_examples",name:"MoDiff Examples",children:t.flatMap(e=>e.children??[])},r=e.findIndex(e=>e.isDir&&F.has(e.name)),i=e.filter(e=>!(e.isDir&&F.has(e.name)));return[...i.slice(0,Math.max(r,0)),a,...i.slice(Math.max(r,0))]}function J(e){return e.replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-|-$/g,"")}function V(){let[e,d]=(0,H.useState)(!1),[m,C]=(0,H.useState)([]),[U,F]=(0,H.useState)([]),[V,Z]=(0,H.useState)(new Set),[G,Q]=(0,H.useState)(""),[X,Y]=(0,H.useState)("all"),[ee,te]=(0,H.useState)("all"),[ae,re]=(0,H.useState)("all"),[ie,se]=(0,H.useState)("all"),[le,oe]=(0,H.useState)("all"),ne=L(e=>e.edgeType),de=L(e=>e.studioViewMode),ce=L(e=>e.setRightPanelOpen),fe=L(e=>e.setRightPanelTab),me=L(e=>e.setAlertOpener),ue=E(e=>e.createWorkflowTab),pe=E(e=>e.workflowTabs),he=E(e=>e.activeWorkflowTabId),we=E(e=>e.switchWorkflowTab),xe=E(e=>e.renameWorkflowTab),be=E(e=>e.mergeBackendWorkflow),[ye,ge]=(0,H.useState)(null),[ve,ke]=(0,H.useState)(""),je=(0,H.useCallback)(async()=>{let e=O.begin("graphs");d(!0);try{let t=await z(`${w.serverAddress}/listgraphs`,{signal:e.signal,parse:_});e.isLatest()&&C(t)}catch(t){e.isLatest()&&console.error("Error fetching graphs:",t)}finally{e.isLatest()&&d(!1),e.finish()}},[]),Ne=(0,H.useCallback)(async()=>{try{let e=await z(`${w.serverAddress}/workflows?view=summary`),t=B(e)&&Array.isArray(e.workflows)?e.workflows:[];F(t.map(I).filter(e=>!!e))}catch(e){}},[]);(0,H.useEffect)(()=>{je(),Ne()},[je,Ne]);let Se=(0,H.useMemo)(()=>"expert"===de?m:P(m),[m,de]),De=(0,H.useMemo)(()=>(e=>{let t=[],a=e=>{e.isDir?e.children&&e.children.forEach(a):t.push(e)};return e.forEach(a),t})(Se),[Se]),Ce=(0,H.useMemo)(()=>({media:[...new Set(De.map(e=>e.mediaKind).filter(Boolean))],models:[...new Set(De.map(e=>e.modelType).filter(Boolean))],modes:[...new Set(De.map(e=>e.mode).filter(Boolean))]}),[De]),Ae=G||"all"!==X||"all"!==ee||"all"!==ae||"all"!==ie||"all"!==le,$e=(0,H.useMemo)(()=>{let e=new Map(U.map(e=>[e.id,e]));return pe.forEach(t=>e.set(t.id,t)),[...e.values()].sort((e,t)=>t.updatedAt-e.updatedAt)},[U,pe]),Te=$e.filter(e=>e.title.toLowerCase().includes(G.trim().toLowerCase())),ze=(0,H.useMemo)(()=>Ae?De.filter(e=>e.name.toLowerCase().includes(G.toLowerCase())&&("all"===X||e.mediaKind===X)&&("all"===ee||e.modelType===ee)&&("all"===ae||e.mode===ae)&&("all"===ie||e.supportTier===ie)&&("all"===le||e.qualificationStatus===le)):Se,[De,Ae,X,ae,ee,le,G,ie,Se]),Le=(0,H.useCallback)(async e=>{if(!e.isDir)try{let t=`${w.serverAddress}/file?file=${encodeURIComponent(e.path)}&t=${Date.now()}`,a=await z(t,{parse:e=>{if(!B(e))throw Error("The workflow file is not a JSON object.");if(e.error)throw new u(String(e.error),{kind:"application",url:t,payload:e});return e}});ue(e.name||"Imported graph",v(a,ne,E.getState().form),"import",e.path),ce(!0),fe("studio"),k("Workflow opened as a new tab",{variant:"success",autoHideDuration:2200})}catch(e){k(M(e,"Could not open the workflow."),{variant:"error",autoHideDuration:6e3})}},[ue,ne,ce,fe]),Me=(0,H.useCallback)(async e=>{if(e.snapshot)return e;let t=x(await z(`${w.serverAddress}/workflows/${encodeURIComponent(e.id)}`));if(!t)throw Error(`MoDiff returned an invalid saved workflow for ${e.title}.`);return t},[]),Ee=(e,a=0)=>{if(e.isDir&&!Ae){let r=V.has(e.path),s=e.children||[];return(0,K.jsxs)("div",{children:[(0,K.jsxs)(N,{"data-testid":`workflow-source-${J(e.name)}`,onClick:()=>(e=>{Z(t=>{let a=new Set(t);return a.has(e)?a.delete(e):a.add(e),a})})(e.path),level:a,open:r,children:[r?(0,K.jsx)(t,{size:16,className:"shrink-0 text-hf-yellow"}):(0,K.jsx)(i,{size:16,className:"shrink-0 text-hf-yellow"}),(0,K.jsx)("span",{className:"min-w-0 truncate",children:e.name})]}),r?(0,K.jsx)(R,{level:a+1,children:s.map(e=>Ee(e,a+1))}):null]},e.path)}return e.isDir?null:(0,K.jsxs)(g,{"data-testid":`workflow-file-${J(e.path)}`,level:Ae?0:a,className:"group cursor-pointer hover:bg-modiff-surface-hover",onDoubleClick:()=>{Le(e)},title:"Double-click to open workflow as a new tab",children:[(0,K.jsx)(c,{size:15,className:"shrink-0 text-modiff-subtle-text group-hover:text-hf-yellow"}),(0,K.jsx)("span",{draggable:!0,className:"min-w-0 flex-1 cursor-grab truncate",onDragStart:t=>{t.dataTransfer.setData("text/plain",e.path),t.dataTransfer.effectAllowed="move"},children:e.name}),e.mediaKind?(0,K.jsx)("span",{className:"text-modiff-tiny rounded bg-modiff-surface-hover/50 px-1.5 py-0.5 uppercase text-modiff-subtle-text",children:e.mediaKind}):null,(0,K.jsx)(h,{size:"compact",className:"text-modiff-subtle-text opacity-0 group-hover:opacity-100 focus-visible:opacity-100",label:`Open ${e.name}`,onClick:()=>{Le(e)},children:(0,K.jsx)(c,{size:14})})]},e.path)};return(0,K.jsxs)("div",{className:"flex h-full min-w-0 flex-col overflow-hidden",children:[(0,K.jsx)("header",{className:"border-b border-modiff-border bg-modiff-surface px-3 py-3",children:(0,K.jsx)("div",{className:"flex items-start justify-between gap-3",children:(0,K.jsxs)("div",{className:"flex min-w-0 items-center gap-2",children:[(0,K.jsx)("span",{className:"grid size-8 shrink-0 place-items-center rounded-modiff-compact border border-modiff-border bg-modiff-bg text-hf-yellow",children:(0,K.jsx)(i,{size:18})}),(0,K.jsxs)("div",{className:"min-w-0",children:[(0,K.jsx)("h2",{className:"truncate text-sm font-semibold text-modiff-text",children:"Workflows"}),(0,K.jsx)("p",{className:"truncate text-xs text-modiff-subtle-text",children:"Saved and example graphs"})]})]})})}),(0,K.jsxs)("div",{className:"flex items-center gap-2 p-2",children:[(0,K.jsx)(j,{"aria-label":"Search workflows",className:"min-w-0 flex-1",placeholder:"Search workflows",value:G,onChange:e=>Q(e.currentTarget.value),onClear:()=>Q("")}),(0,K.jsx)(h,{label:"Reload workflows",disabled:e,className:"text-hf-yellow hover:text-hf-orange",onClick:()=>{je(),Ne(),Z(new Set),Q(""),Y("all"),te("all"),re("all"),se("all"),oe("all")},children:e?(0,K.jsx)(r,{size:18,className:"animate-spin"}):(0,K.jsx)(f,{size:17})})]}),(0,K.jsxs)("section",{className:"min-w-0 overflow-hidden border-b border-modiff-border px-2 pb-2","data-testid":"my-workflows",children:[(0,K.jsx)("h3",{className:"text-modiff-label mb-1 px-1 font-semibold uppercase text-modiff-subtle-text",children:"My workflows"}),(0,K.jsx)("div",{className:"grid gap-1",children:Te.map(e=>(0,K.jsxs)("div",{className:"group flex min-h-8 min-w-0 items-center gap-1 overflow-hidden rounded-modiff-compact px-2 text-xs "+(e.id===he?"bg-hf-yellow/10 text-hf-yellow":"text-modiff-text hover:bg-modiff-surface-hover/50"),"data-testid":`saved-workflow-${e.id}`,children:[(0,K.jsx)(c,{size:14,className:"shrink-0"}),ye===e.id?(0,K.jsx)(D,{autoFocus:!0,value:ve,onChange:e=>ke(e.target.value),onBlur:()=>{(async(e,t)=>{let a=t.trim();if(a&&a!==e.title){if(pe.some(t=>t.id===e.id))return void xe(e.id,a);try{let t=await p({...await Me(e),title:a,dirty:!0});F(e=>e.map(e=>e.id===t.id?t:e))}catch(t){k(M(t,`Could not rename ${e.title}.`),{variant:"error",autoHideDuration:6e3})}}})(e,ve),ge(null)},onKeyDown:e=>{"Enter"===e.key&&e.currentTarget.blur(),"Escape"===e.key&&ge(null)},controlSize:"compact",className:"nodrag nowheel min-w-0 flex-1 border-hf-yellow"}):(0,K.jsxs)(S,{type:"button",className:"min-h-7 min-w-0 flex-1 truncate text-left",onClick:()=>{(async e=>{try{let t=await Me(e);y(t.id),pe.some(e=>e.id===t.id)||be(t),we(t.id)}catch(t){k(M(t,`Could not open ${e.title}.`),{variant:"error",autoHideDuration:6e3})}})(e)},children:[e.dirty?"* ":"",e.title]}),(0,K.jsxs)(A,{className:"shrink-0",children:[(0,K.jsx)(q,{children:(0,K.jsx)(h,{size:"compact",label:`Actions for ${e.title}`,className:"text-modiff-subtle-text hover:text-hf-yellow",children:(0,K.jsx)(s,{size:14})})}),(0,K.jsxs)(W,{anchor:"bottom end",className:"min-w-40",children:[(0,K.jsx)($,{icon:(0,K.jsx)(l,{size:13}),onClick:()=>{ge(e.id),ke(e.title)},children:"Rename"}),(0,K.jsx)($,{icon:(0,K.jsx)(o,{size:13}),onClick:()=>{(async e=>{E.getState().saveActiveWorkflowTab(!0);let t=$e.find(t=>t.id===e);if(t)try{let e=await Me(t);ue(`${e.title} copy`,e.snapshot,"manual",e.sourceLabel),k("Workflow duplicated",{variant:"success",autoHideDuration:2200})}catch(e){k(M(e,`Could not duplicate ${t.title}.`),{variant:"error",autoHideDuration:6e3})}})(e.id)},children:"Duplicate"}),(0,K.jsx)($,{icon:(0,K.jsx)(n,{size:13}),onClick:()=>{(async e=>{E.getState().saveActiveWorkflowTab(!0);let t=$e.find(t=>t.id===e);if(t)try{let e=await Me(t),a=new Blob([JSON.stringify(e.snapshot,null,2)],{type:"application/json"}),r=URL.createObjectURL(a),i=document.createElement("a");i.href=r,i.download=`${e.title.replace(/[^a-z0-9_-]+/gi,"-").replace(/^-|-$/g,"")||"workflow"}.json`,i.click(),URL.revokeObjectURL(r)}catch(e){k(M(e,`Could not export ${t.title}.`),{variant:"error",autoHideDuration:6e3})}})(e.id)},children:"Export"}),(0,K.jsx)($,{tone:"danger",icon:(0,K.jsx)(a,{size:13}),onClick:()=>(e=>{let t=$e.find(t=>t.id===e);t&&me({title:"Delete workflow?",message:`${t.title}${t.dirty?" has unsaved changes.":""} This removes it from My workflows.`,confirmText:"Delete",cancelText:"Cancel",onConfirm:()=>{b(e).then(()=>{F(t=>t.filter(t=>t.id!==e)),k(`Deleted ${t.title}`,{variant:"success",autoHideDuration:2200})}).catch(e=>{k(M(e,`Could not delete ${t.title}.`),{variant:"error",autoHideDuration:6e3})}),me(null)},onCancel:()=>me(null)})})(e.id),children:"Delete"})]})]})]},e.id))})]}),(0,K.jsx)("div",{className:"grid grid-cols-2 gap-1 px-2 pb-2",children:[["Media",X,Y,Ce.media],["Model",ee,te,Ce.models],["Mode",ae,re,Ce.modes],["Tier",ie,se,["supported","experimental"]],["Readiness",le,oe,["graph-qualified","runtime-qualified","unqualified"]]].map(([e,t,a,r])=>(0,K.jsxs)("div",{className:"min-w-0 text-modiff-label uppercase text-modiff-subtle-text",children:[(0,K.jsx)("span",{children:String(e)}),(0,K.jsx)(T,{"aria-label":`Filter workflows by ${String(e).toLowerCase()}`,value:String(t),onValueChange:a,options:[{value:"all",label:"All"},...r.sort().map(e=>({value:e,label:e.replace(/_/g," ")}))],size:"compact",className:"mt-0.5 w-full normal-case"})]},String(e)))}),e?(0,K.jsx)("div",{className:"text-center text-sm text-modiff-subtle-text",children:"Loading..."}):(0,K.jsx)("div",{className:"select-none","data-testid":"workflow-list",children:ze.length>0?ze.map(e=>Ee(e,0)):0===Te.length?(0,K.jsx)("div",{className:"m-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-sm text-modiff-subtle-text",children:"No saved, imported, or example workflows found."}):null})]})}export{V as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{$ as t,Dn as a,Et as r,Jt as s,On as i,Xt as l,cr as o,dr as n,in as d,ln as c,pn as f,un as m,ut as u,z as p}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{At as h,Ci as x,Eo as w,Ft as v,It as y,Ki as b,Mo as g,Mt as j,Nt as k,Oo as N,Ot as S,Pt as C,Qn as A,Sa as D,To as T,Tt as $,Xn as z,Yn as L,Zn as E,an as M,bn as R,ji as W,mn as O,oc as q,rn as H,wo as K,yn as _}from"./studio-templates.js?v=cc046c26d6fe7aa4";var B=e(n(),1),I=o(),U=(0,B.lazy)(()=>x(()=>import(`./DeveloperWorkflowLauncher.js?v=cc046c26d6fe7aa4`),[])),F=T();function P(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function V(e,t){if(!P(e)||"boolean"!=typeof e.isDir||"string"!=typeof e.path||"string"!=typeof e.name)throw Error(`Workflow entry ${t+1} is invalid.`);if(void 0!==e.children&&!Array.isArray(e.children))throw Error(`Workflow entry ${t+1} has invalid children.`);return{isDir:e.isDir,path:e.path,name:e.name,children:e.children?.map(V),modelType:"string"==typeof e.modelType?e.modelType:void 0,mode:"string"==typeof e.mode?e.mode:void 0,mediaKind:"string"==typeof e.mediaKind?e.mediaKind:void 0,supportTier:"string"==typeof e.supportTier?e.supportTier:void 0,qualificationStatus:"string"==typeof e.qualificationStatus?e.qualificationStatus:void 0,requiredArtifacts:Array.isArray(e.requiredArtifacts)?e.requiredArtifacts.filter(e=>"string"==typeof e):void 0}}function J(e){return P(e)&&"string"==typeof e.id?{id:e.id,title:"string"==typeof e.title?e.title:"Workflow",createdAt:"number"==typeof e.createdAt?e.createdAt:Date.now(),updatedAt:"number"==typeof e.updatedAt?e.updatedAt:Date.now(),dirty:!1,intent:"draft"===e.intent?"draft":"saved",source:"string"==typeof e.source?e.source:"manual",sourceLabel:"string"==typeof e.sourceLabel?e.sourceLabel:void 0,backendRevision:"number"==typeof e.revision?e.revision:0}:null}function X(e){if(!Array.isArray(e))throw Error("The workflow list response must be an array.");return e.map(V)}function Z(e){return e.replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-|-$/g,"")}function Q(){let e=D(e=>e.workflowLibraryView),o=D(e=>e.setWorkflowLibraryView),[n,x]=(0,B.useState)(!1),[T,V]=(0,B.useState)([]),[Q,Y]=(0,B.useState)([]),[G,ee]=(0,B.useState)(new Set),[te,ae]=(0,B.useState)(""),[re,se]=(0,B.useState)(0),[ie,le]=(0,B.useState)("all"),[oe,ne]=(0,B.useState)("all"),[de,ce]=(0,B.useState)("all"),[fe,me]=(0,B.useState)("all"),[ue,pe]=(0,B.useState)("all"),he=D(e=>e.edgeType),xe=D(e=>e.setRightPanelOpen),we=D(e=>e.setRightPanelTab),ve=D(e=>e.setAlertOpener),ye=W(e=>e.createWorkflowTab),be=W(e=>e.workflowTabs),ge=W(e=>e.activeWorkflowTabId),je=W(e=>e.switchWorkflowTab),ke=W(e=>e.renameWorkflowTab),Ne=W(e=>e.mergeBackendWorkflow),[Se,Ce]=(0,B.useState)(null),[Ae,De]=(0,B.useState)(""),Te=(0,B.useCallback)(async()=>{let e=F.begin("graphs");x(!0);try{let t=await N(`${q.serverAddress}/listgraphs`,{signal:e.signal,parse:X});e.isLatest()&&V(t)}catch(t){e.isLatest()&&console.error("Error fetching graphs:",t)}finally{e.isLatest()&&x(!1),e.finish()}},[]),$e=(0,B.useCallback)(async()=>{try{let e=await N(`${q.serverAddress}/workflows?view=summary`),t=P(e)&&Array.isArray(e.workflows)?e.workflows:[];Y(t.map(J).filter(e=>!!e))}catch(e){}},[]);(0,B.useEffect)(()=>{Te(),$e()},[Te,$e]);let ze=T,Le=(0,B.useMemo)(()=>(e=>{let t=[],a=e=>{e.isDir?e.children&&e.children.forEach(a):t.push(e)};return e.forEach(a),t})(ze),[ze]),Ee=(0,B.useMemo)(()=>({media:[...new Set(Le.map(e=>e.mediaKind).filter(Boolean))],models:[...new Set(Le.map(e=>e.modelType).filter(Boolean))],modes:[...new Set(Le.map(e=>e.mode).filter(Boolean))]}),[Le]),Me=te||"all"!==ie||"all"!==oe||"all"!==de||"all"!==fe||"all"!==ue,Re=(0,B.useMemo)(()=>{let e=new Map(Q.map(e=>[e.id,e]));return be.forEach(t=>e.set(t.id,t)),[...e.values()].sort((e,t)=>t.updatedAt-e.updatedAt)},[Q,be]),We=Re.filter(t=>("drafts"===e?"draft"===t.intent:"draft"!==t.intent)&&t.title.toLowerCase().includes(te.trim().toLowerCase())),Oe=Math.max(0,Math.ceil(We.length/50)-1),qe=Math.min(re,Oe),He=50*qe,Ke=We.slice(He,He+50),_e=(0,B.useMemo)(()=>Me?Le.filter(e=>e.name.toLowerCase().includes(te.toLowerCase())&&("all"===ie||e.mediaKind===ie)&&("all"===oe||e.modelType===oe)&&("all"===de||e.mode===de)&&("all"===fe||e.supportTier===fe)&&("all"===ue||e.qualificationStatus===ue)):ze,[Le,Me,ie,de,oe,ue,te,fe,ze]),Be=(0,B.useCallback)(async e=>{if(!e.isDir)try{let t=`${q.serverAddress}/file?file=${encodeURIComponent(e.path)}&t=${Date.now()}`,a=await N(t,{parse:e=>{if(!P(e))throw Error("The workflow file is not a JSON object.");if(e.error)throw new K(String(e.error),{kind:"application",url:t,payload:e});return e}});ye(e.name||"Imported graph",b(a,he,W.getState().form),"import",e.path),xe(!0),we("studio"),g("Workflow opened as a new tab",{variant:"success",autoHideDuration:2200})}catch(e){g(w(e,"Could not open the workflow."),{variant:"error",autoHideDuration:6e3})}},[ye,he,xe,we]),Ie=(0,B.useCallback)(async e=>{if(e.snapshot)return e;let t=L(await N(`${q.serverAddress}/workflows/${encodeURIComponent(e.id)}`));if(!t)throw Error(`MoDiff returned an invalid saved workflow for ${e.title}.`);return t},[]),Ue=(e,t=0)=>{if(e.isDir&&!Me){let a=G.has(e.path),r=e.children||[];return(0,I.jsxs)("div",{children:[(0,I.jsxs)(C,{"data-testid":`workflow-source-${Z(e.name)}`,onClick:()=>(e=>{ee(t=>{let a=new Set(t);return a.has(e)?a.delete(e):a.add(e),a})})(e.path),level:t,open:a,children:[a?(0,I.jsx)(l,{size:16,className:"shrink-0 text-hf-yellow"}):(0,I.jsx)(s,{size:16,className:"shrink-0 text-hf-yellow"}),(0,I.jsx)("span",{className:"min-w-0 truncate",children:e.name})]}),a?(0,I.jsx)(v,{level:t+1,children:r.map(e=>Ue(e,t+1))}):null]},e.path)}return e.isDir?null:(0,I.jsxs)(y,{"data-testid":`workflow-file-${Z(e.path)}`,level:Me?0:t,className:"group cursor-pointer hover:bg-modiff-surface-hover",onDoubleClick:()=>{Be(e)},title:"Double-click to open workflow as a new tab",children:[(0,I.jsx)(d,{size:15,className:"shrink-0 text-modiff-subtle-text group-hover:text-hf-yellow"}),(0,I.jsx)("span",{draggable:!0,className:"min-w-0 flex-1 cursor-grab truncate",onDragStart:t=>{t.dataTransfer.setData("text/plain",e.path),t.dataTransfer.effectAllowed="move"},children:e.name}),e.mediaKind?(0,I.jsx)("span",{className:"text-modiff-tiny rounded bg-modiff-surface-hover/50 px-1.5 py-0.5 uppercase text-modiff-subtle-text",children:e.mediaKind}):null,(0,I.jsx)(M,{size:"compact",className:"text-modiff-subtle-text opacity-0 group-hover:opacity-100 focus-visible:opacity-100",label:`Open ${e.name}`,onClick:()=>{Be(e)},children:(0,I.jsx)(d,{size:14})})]},e.path)};return(0,I.jsxs)("div",{className:"flex h-full min-w-0 flex-col overflow-hidden",children:[(0,I.jsx)("header",{className:"border-b border-modiff-border bg-modiff-surface px-3 py-3",children:(0,I.jsx)("div",{className:"flex items-start justify-between gap-3",children:(0,I.jsxs)("div",{className:"flex min-w-0 items-center gap-2",children:[(0,I.jsx)("span",{className:"grid size-8 shrink-0 place-items-center rounded-modiff-compact border border-modiff-border bg-modiff-bg text-hf-yellow",children:(0,I.jsx)(s,{size:18})}),(0,I.jsxs)("div",{className:"min-w-0",children:[(0,I.jsx)("h2",{className:"truncate text-sm font-semibold text-modiff-text",children:"Workflows"}),(0,I.jsx)("p",{className:"truncate text-xs text-modiff-subtle-text",children:"Saved and example graphs"})]})]})})}),(0,I.jsxs)("div",{className:"flex flex-wrap gap-2 p-2",children:[(0,I.jsx)(H,{"aria-pressed":"start"===e,onClick:()=>o("start"),children:"Start"}),(0,I.jsx)(H,{"aria-pressed":"examples"===e,onClick:()=>o("examples"),children:"Example workflows"}),(0,I.jsx)(H,{"aria-pressed":"saved"===e,onClick:()=>o("saved"),children:"My workflows"}),(0,I.jsx)(H,{"aria-pressed":"drafts"===e,onClick:()=>o("drafts"),children:"Recovery drafts"})]}),"start"===e?(0,I.jsx)("div",{className:"min-h-0 flex-1 overflow-y-auto overscroll-contain",children:(0,I.jsx)(B.Suspense,{fallback:(0,I.jsx)("p",{role:"status",children:"Loading tasks…"}),children:(0,I.jsx)(U,{embedded:!0})})}):(0,I.jsxs)(I.Fragment,{children:[(0,I.jsxs)("div",{className:"flex items-center gap-2 p-2",children:[(0,I.jsx)(_,{"aria-label":"Search workflows",className:"min-w-0 flex-1",placeholder:"Search workflows",value:te,onChange:e=>{ae(e.currentTarget.value),se(0)},onClear:()=>{ae(""),se(0)}}),(0,I.jsx)(M,{label:"Reload workflows",disabled:n,className:"text-hf-yellow hover:text-hf-orange",onClick:()=>{Te(),$e(),ee(new Set),ae(""),se(0),le("all"),ne("all"),ce("all"),me("all"),pe("all")},children:n?(0,I.jsx)(r,{size:18,className:"animate-spin"}):(0,I.jsx)(t,{size:17})})]}),"examples"===e?(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)("div",{className:"grid shrink-0 grid-cols-2 gap-1 px-2 pb-2",children:[["Media",ie,le,Ee.media],["Model",oe,ne,Ee.models],["Mode",de,ce,Ee.modes],["Tier",fe,me,["supported","experimental"]],["Readiness",ue,pe,["graph-qualified","runtime-qualified","unqualified"]]].map(([e,t,a,r])=>(0,I.jsxs)("div",{className:"min-w-0 text-modiff-label uppercase text-modiff-subtle-text",children:[(0,I.jsx)("span",{children:String(e)}),(0,I.jsx)(R,{"aria-label":`Filter workflows by ${String(e).toLowerCase()}`,value:String(t),onValueChange:a,options:[{value:"all",label:"All"},...r.sort().map(e=>({value:e,label:e.replace(/_/g," ")}))],size:"compact",className:"mt-0.5 w-full normal-case"})]},String(e)))}),n?(0,I.jsx)("div",{className:"text-center text-sm text-modiff-subtle-text",children:"Loading..."}):(0,I.jsx)("div",{className:"min-h-0 flex-1 select-none overflow-y-auto overscroll-contain","data-testid":"workflow-list",children:_e.length>0?_e.map(e=>Ue(e,0)):0===We.length?(0,I.jsx)("div",{className:"m-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-sm text-modiff-subtle-text",children:"No saved, imported, or example workflows found."}):null})]}):(0,I.jsxs)("section",{className:"min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain px-2 pb-2","data-testid":"my-workflows",children:[(0,I.jsx)("h3",{className:"text-modiff-label mb-1 px-1 font-semibold uppercase text-modiff-subtle-text",children:"drafts"===e?"Recovery drafts — open and Save to keep in My workflows":"My workflows"}),Oe>0&&(0,I.jsxs)("div",{className:"mb-1 flex min-w-0 items-center justify-between gap-1 text-xs text-modiff-subtle-text",children:[(0,I.jsx)(M,{size:"compact",label:"Previous saved workflows",disabled:0===qe,onClick:()=>se(qe-1),children:(0,I.jsx)(i,{size:14})}),(0,I.jsxs)("span",{children:[He+1,"–",He+Ke.length," of ",We.length]}),(0,I.jsx)(M,{size:"compact",label:"Next saved workflows",disabled:qe===Oe,onClick:()=>se(qe+1),children:(0,I.jsx)(a,{size:14})})]}),(0,I.jsx)("div",{className:"grid gap-1",children:Ke.map(e=>(0,I.jsxs)("div",{className:"group flex min-h-8 min-w-0 items-center gap-1 overflow-hidden rounded-modiff-compact px-2 text-xs "+(e.id===ge?"bg-hf-yellow/10 text-hf-yellow":"text-modiff-text hover:bg-modiff-surface-hover/50"),"data-testid":`saved-workflow-${e.id}`,children:[(0,I.jsx)(d,{size:14,className:"shrink-0"}),Se===e.id?(0,I.jsx)(O,{autoFocus:!0,value:Ae,onChange:e=>De(e.target.value),onBlur:()=>{(async(e,t)=>{let a=t.trim();if(a&&a!==e.title){if(be.some(t=>t.id===e.id))return void ke(e.id,a);try{let t=await A({...await Ie(e),title:a,dirty:!0});Y(e=>e.map(e=>e.id===t.id?t:e))}catch(t){g(w(t,`Could not rename ${e.title}.`),{variant:"error",autoHideDuration:6e3})}}})(e,Ae),Ce(null)},onKeyDown:e=>{"Enter"===e.key&&e.currentTarget.blur(),"Escape"===e.key&&Ce(null)},controlSize:"compact",className:"nodrag nowheel min-w-0 flex-1 border-hf-yellow"}):(0,I.jsxs)($,{type:"button",className:"min-h-7 min-w-0 flex-1 truncate text-left",onClick:()=>{(async e=>{try{let t=await Ie(e);E(t.id),be.some(e=>e.id===t.id)||Ne(t),je(t.id)}catch(t){g(w(t,`Could not open ${e.title}.`),{variant:"error",autoHideDuration:6e3})}})(e)},children:[e.dirty?"* ":"",e.title]}),(0,I.jsxs)(h,{className:"shrink-0",children:[(0,I.jsx)(k,{children:(0,I.jsx)(M,{size:"compact",label:`Actions for ${e.title}`,className:"text-modiff-subtle-text hover:text-hf-yellow",children:(0,I.jsx)(c,{size:14})})}),(0,I.jsxs)(j,{anchor:"bottom end",className:"min-w-40",children:[(0,I.jsx)(S,{icon:(0,I.jsx)(u,{size:13}),onClick:()=>{Ce(e.id),De(e.title)},children:"Rename"}),(0,I.jsx)(S,{icon:(0,I.jsx)(f,{size:13}),onClick:()=>{(async e=>{W.getState().saveActiveWorkflowTab(!0);let t=Re.find(t=>t.id===e);if(t)try{let e=await Ie(t);ye(`${e.title} copy`,e.snapshot,"manual",e.sourceLabel),g("Workflow duplicated",{variant:"success",autoHideDuration:2200})}catch(e){g(w(e,`Could not duplicate ${t.title}.`),{variant:"error",autoHideDuration:6e3})}})(e.id)},children:"Duplicate"}),(0,I.jsx)(S,{icon:(0,I.jsx)(m,{size:13}),onClick:()=>{(async e=>{W.getState().saveActiveWorkflowTab(!0);let t=Re.find(t=>t.id===e);if(t)try{let e=await Ie(t),a=new Blob([JSON.stringify(e.snapshot,null,2)],{type:"application/json"}),r=URL.createObjectURL(a),s=document.createElement("a");s.href=r,s.download=`${e.title.replace(/[^a-z0-9_-]+/gi,"-").replace(/^-|-$/g,"")||"workflow"}.json`,s.click(),URL.revokeObjectURL(r)}catch(e){g(w(e,`Could not export ${t.title}.`),{variant:"error",autoHideDuration:6e3})}})(e.id)},children:"Export"}),(0,I.jsx)(S,{tone:"danger",icon:(0,I.jsx)(p,{size:13}),onClick:()=>(e=>{let t=Re.find(t=>t.id===e);t&&ve({title:"Delete workflow?",message:`${t.title}${t.dirty?" has unsaved changes.":""} This removes it from My workflows.`,confirmText:"Delete",cancelText:"Cancel",onConfirm:()=>{z(e).then(()=>{Y(t=>t.filter(t=>t.id!==e)),g(`Deleted ${t.title}`,{variant:"success",autoHideDuration:2200})}).catch(e=>{g(w(e,`Could not delete ${t.title}.`),{variant:"error",autoHideDuration:6e3})}),ve(null)},onCancel:()=>ve(null)})})(e.id),children:"Delete"})]})]})]},e.id))})]})]})]})}export{Q as default}; \ No newline at end of file diff --git a/web/assets/GraphNodeInputs.js b/web/assets/GraphNodeInputs.js new file mode 100644 index 00000000..cb4406a0 --- /dev/null +++ b/web/assets/GraphNodeInputs.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as a,ct as t,dr as s,lt as n}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$o as d,Br as i,Bt as o,Ci as l,Cs as r,In as c,Vr as p,Zo as u,dn as m,ft as f,ii as b,ji as x,os as h,rc as g,xn as j,zt as I}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as k}from"./presetDiff.js?v=cc046c26d6fe7aa4";var v=e(s(),1),N=new Set(["ui_image","ui_video","ui_audio","ui_text","ui_imagecompare"]),w=new WeakMap,S=new WeakMap;function y(e){let a=e.data.blockInstanceV2??e.data.params,t=w.get(a);if(t)return t;let s=e.data.blockInstanceV2?u(e.data.blockInstanceV2):e.data.params,n=Object.fromEntries(Object.entries(s).filter(([e,a])=>{let t=a.isInput?"input":a.display||"";return"input"!==t&&"output"!==t&&!N.has(t)&&!a.hidden&&!["output","images","latents"].includes(e)}).map(([e,a])=>[e,{...a,fieldOptions:{...a.fieldOptions,suppressInitialFieldAction:!0}}]));return w.set(a,n),n}function V(e,a=[]){let t=e.flatMap(e=>Object.entries(y(e)).map(([a,t])=>({id:`graph:${e.id}:${a}`,kind:"graph-param",label:`${e.data.label||e.id} / ${t.label||a}`,nodeId:e.id,paramKey:a,node:e,param:t}))),s=new Set(a.filter(e=>!t.some(a=>a.id===e)));if(!s.size)return t;let n=new Set(e.map(({id:e})=>e));for(let a of e){let e=a.data.blockInstanceV2;if(!e||![...s].some(e=>e.startsWith(`graph:block-v2-node:${a.id.length}:${a.id}:`)))continue;let d=S.get(e);d||(d=h({...a,data:{...a.data,blockInstanceV2:{...e,presentation:{...e.presentation,expanded:!0,collapsedContainerNodeIds:[]}}}}).nodes,S.set(e,d)),t.push(...V(d.filter(({id:e})=>!n.has(e))).filter(({id:e})=>s.has(e)))}return t}function O(e,a){if(x.getState().activeWorkflowTabId!==e)return;let t=a.data.blockProjectionOwnerId,s=p.getState().nodes.find(({id:e})=>e===t)?.data.blockInstanceV2;if(!s||!t||!a.data.blockProjectionNodeId)return;let n=d(s),i=new Set,o=n.get(a.data.blockProjectionNodeId);for(;o;)i.add(o),o=n.get(o);p.getState().setBlockPresentationV2(t,{expanded:!0,collapsedContainerNodeIds:(s.presentation.collapsedContainerNodeIds??[]).filter(e=>!i.has(e))})}function $(e,a,t,s,n,d){if(x.getState().activeWorkflowTabId!==e)return;let o=p.getState(),l=o.nodes.find(({id:e})=>e===a);if(l){if(void 0!==d){let e=l.data.blockInstanceV2?u(l.data.blockInstanceV2):l.data.params;if(!Object.prototype.hasOwnProperty.call(e,d)||!Object.prototype.hasOwnProperty.call(e,t))return}else if(!y(l)[t])return;if(l.data.blockInstanceV2)return void((void 0===n||"value"===n)&&o.setBlockInstanceValueV2(a,t,s));void 0===d?o.setParamWithHistory(a,t,s,n):o.setParam(a,t,s,n),i(a,t,s,n)}}var _=a(),P=(0,v.lazy)(()=>l(()=>import(`./NodeInspectionDetails.js?v=cc046c26d6fe7aa4`),[])),C=(0,v.lazy)(()=>l(()=>import(`./OperationOwnerControls.js?v=cc046c26d6fe7aa4`),[])),M=(0,v.lazy)(()=>l(()=>import(`./GuidanceNodeControls.js?v=cc046c26d6fe7aa4`),[])),z=[{value:"parameters",label:"Parameters"},{value:"interface",label:"Interface"},{value:"implementation",label:"Implementation"},{value:"docs",label:"Docs"},{value:"run",label:"Run details"}];function B({node:e,children:a}){let[t,s]=(0,v.useState)("parameters"),n=(0,v.useId)();return(0,_.jsxs)("div",{className:"grid min-w-0 gap-3",children:[(0,_.jsx)(j,{"aria-label":"Node inspector sections",className:"flex-wrap",size:"dense",value:t,onValueChange:s,options:z.map(e=>({...e,id:`${n}-${e.value}`,controls:`${n}-panel`}))}),(0,_.jsx)("div",{role:"tabpanel",id:`${n}-panel`,"aria-labelledby":`${n}-${t}`,tabIndex:0,children:"parameters"===t?(0,_.jsxs)("div",{className:"grid gap-3",children:[e.data.blockInstanceV2||e.data.userBlockSnapshot||e.data.userBlockId||g(e.data.operationAuthoring?.operation)?(0,_.jsx)(v.Suspense,{fallback:(0,_.jsx)(I,{children:"Loading model controls…"}),children:(0,_.jsx)(C,{node:e})}):null,r(e.data.blockInstanceV2)?(0,_.jsx)(v.Suspense,{fallback:(0,_.jsx)(I,{children:"Loading guidance controls…"}),children:(0,_.jsx)(M,{instance:e.data.blockInstanceV2})}):a]}):(0,_.jsx)(v.Suspense,{fallback:(0,_.jsx)(I,{children:"Loading node details…"}),children:(0,_.jsx)(P,{node:e,section:t})})})]})}var E=new Map;function D({candidates:e,nodes:a,onTogglePin:s,pinnedIds:d,pinnedInputs:i,selectedNodes:l,workflowId:r,controlIdPrefix:p="studio-inspector"}){let[u,x]=(0,v.useState)({}),h=(0,v.useMemo)(()=>new Set(d),[d]),g=1===l.length?l[0]:null,j=(0,v.useMemo)(()=>{let e=new Map;if(i.forEach(a=>{if(!a.nodeId||!a.paramKey)return;let t=e.get(a.nodeId)??{node:a.node,params:{}};t.params[a.paramKey]=a.param,e.set(a.nodeId,t)}),g){let a=y(g);e.set(g.id,{node:g,params:a})}return[...g?[g.id]:[],...[...new Set([...a.map(e=>e.id),...e.keys()])].filter(e=>e!==g?.id)].flatMap(a=>{let t=e.get(a);return t?[t]:[]})},[a,i,g]),N=(0,v.useCallback)((e,a,t,s)=>{$(r,e,a,t,s)},[r]);return(0,_.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2","data-testid":l.length>0?"studio-custom-graph-inspector":"studio-pinned-graph-inputs",children:[(0,_.jsx)("div",{className:"flex items-center justify-between gap-2",children:(0,_.jsx)("span",{className:"text-sm font-semibold text-modiff-text",children:"Node controls"})}),l.length>1?(0,_.jsx)(I,{children:"One node at a time"}):null,j.length>0?j.map(({node:e,params:t},s)=>{let n=g?.id===e.id,d=`${r??"unscoped"}:${e.id}`,i=u[d]??E.get(d),l=n||(i??0===s),h=b(e.data),j=Object.keys(t).length,v=a.some(({id:a})=>a===e.id)?j>0?(0,_.jsx)("div",{className:"grid min-w-0 grid-cols-1 gap-3 [&>[data-key]]:m-0 [&>[data-key]]:min-w-0",children:(0,_.jsx)(f,{nodeId:e.id,controlIdPrefix:p,params:t,updateStore:(a,t,s)=>N(e.id,a,t,s),updateFieldActionStore:(a,t,s,n)=>$(r,e.id,t,s,n,a),module:e.data.blockInstanceV2?e.data.blockInstanceV2.definitionSnapshot.source.library??e.data.blockInstanceV2.definitionSnapshot.source.provider??"MoDiff":e.data.module,action:e.data.blockInstanceV2?"BlockV2":e.data.action,mode:"controls",hidePreviews:!0,executionStatus:e.data.executionStatus,progressMessage:e.data.progressMessage,uiStateMessage:e.data.uiState?.validationMessage??e.data.uiState?.errorMessage})}):(0,_.jsx)(I,{tone:"secondary",children:"No editable params"}):(0,_.jsxs)("div",{className:"grid gap-2","data-testid":`studio-hidden-pinned-inputs-${e.id}`,children:[Object.entries(t).map(([e,a])=>(0,_.jsxs)("p",{className:"break-words text-xs text-modiff-subtle-text",children:[a.label||e,": ",k(a.value??a.default)]},e)),(0,_.jsx)(o,{tone:"secondary",onClick:()=>O(r,e),children:"Reveal in Block to edit"})]});return(0,_.jsx)(m,{"aria-label":`Node controls: ${h}`,className:c("overflow-hidden rounded-modiff-compact border bg-modiff-bg",n?"border-hf-yellow/60":"border-modiff-border"),"data-testid":`studio-node-disclosure-${e.id}`,defaultOpen:l,label:(0,_.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,_.jsx)("span",{className:"min-w-0 flex-1 truncate",title:h,children:h}),(0,_.jsx)("span",{className:"text-xs font-normal text-modiff-subtle-text",children:j})]}),onOpenChange:e=>{E.set(d,e),x(a=>a[d]===e?a:{...a,[d]:e})},panelClassName:"border-t border-modiff-border-subtle p-3",children:n?(0,_.jsx)(B,{node:e,children:v}):v},`${d}:${n?"selected":"idle"}`)}):(0,_.jsx)(I,{tone:"secondary",children:"No pinned inputs"}),e.length>0?(0,_.jsx)(m,{className:"border-t border-modiff-border pt-2",label:"Pin inputs",buttonClassName:"min-h-0 justify-start p-0 text-xs text-modiff-subtle-text hover:bg-transparent",panelClassName:"mt-2 grid gap-1",children:e.map(e=>(0,_.jsx)(o,{tone:"ghost",align:"left",fullWidth:!0,className:"min-h-8 px-2 text-xs",onClick:()=>s(e.id),icon:h.has(e.id)?(0,_.jsx)(n,{size:14,className:"text-hf-yellow"}):(0,_.jsx)(t,{size:14,className:"text-modiff-subtle-text"}),children:(0,_.jsx)("span",{className:"min-w-0 flex-1 truncate",children:e.label})},e.id))}):null]})}export{V as n,D as t}; \ No newline at end of file diff --git a/web/assets/GroupNode.js b/web/assets/GroupNode.js index 6712cc67..c9cd3b8f 100644 --- a/web/assets/GroupNode.js +++ b/web/assets/GroupNode.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{In as a,Nn as o,jn as t,pr as r,ur as d}from"./graph-vendor.js?v=5bcd315505f5670a";import{Cn as l,Gt as s,bn as n,kr as i}from"./studio-templates.js?v=5bcd315505f5670a";var c=e(r(),1),f=d(),m=(0,c.memo)(e=>{let r=(0,c.useRef)(null);l(e.id,r);let d=!0===e.data.blockProjectionContainer,m=e.data.blockProjectionChildCount??0,p=!1!==e.data.blockProjectionContainerExpanded,x=i(e=>e.toggleBlockContainerExpandedV2);return(0,f.jsxs)("div",{ref:r,className:d?"relative h-full w-full rounded-modiff-panel border-2 border-dashed border-hf-yellow/45 bg-hf-yellow/[0.025] shadow-inner":"relative h-full w-full rounded-modiff-panel border-2 border-dashed border-modiff-border bg-modiff-surface/20 shadow-inner","data-testid":`group-node-${e.id}`,children:[(0,f.jsxs)("div",{className:"drag-handle flex h-11 items-center gap-2 border-b border-modiff-border/80 bg-modiff-panel/90 px-3 text-sm font-bold text-modiff-text",children:[(0,f.jsx)(a,{size:16,className:d?"text-hf-yellow":"text-modiff-subtle-text"}),(0,f.jsx)("span",{className:"truncate",children:e.data.label||"Group"}),d?(0,f.jsxs)("div",{className:"nodrag ml-auto flex shrink-0 items-center gap-1.5",children:[(0,f.jsxs)("span",{className:"rounded-modiff-compact border border-hf-yellow/30 px-1.5 py-0.5 text-xs font-semibold uppercase tracking-wide text-hf-yellow",children:[m," ",1===m?"block":"blocks"]}),(0,f.jsx)(s,{label:`${p?"Collapse":"Expand"} ${e.data.label||"Modular Diffusers container"}`,size:"compact","data-testid":`toggle-modular-container-${e.id}`,onClick:()=>{let a=e.data.blockProjectionOwnerId,o=e.data.blockProjectionNodeId;a&&o&&x(a,o)},children:p?(0,f.jsx)(o,{size:14}):(0,f.jsx)(t,{size:14})})]}):null]}),e.data.description?(0,f.jsx)("span",{className:"pointer-events-none absolute left-3 top-12 max-w-[calc(100%-1.5rem)] truncate text-xs text-modiff-subtle-text/80",children:e.data.description}):null,!d||p?(0,f.jsx)(n,{}):null]})});m.displayName="GroupNode";export{m as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Dn as a,Mn as o,cr as r,dr as t,kn as d}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ln as l,Pn as s,Vr as n,an as i}from"./studio-templates.js?v=cc046c26d6fe7aa4";var c=e(t(),1),f=r(),m=(0,c.memo)(e=>{let r=(0,c.useRef)(null);l(e.id,r);let t=!0===e.data.blockProjectionContainer,m=e.data.blockProjectionChildCount??0,p=!1!==e.data.blockProjectionContainerExpanded,x=n(e=>e.toggleBlockContainerExpandedV2);return(0,f.jsxs)("div",{ref:r,className:t?"relative h-full w-full rounded-modiff-panel border-2 border-dashed border-hf-yellow/45 bg-hf-yellow/[0.025] shadow-inner":"relative h-full w-full rounded-modiff-panel border-2 border-dashed border-modiff-border bg-modiff-surface/20 shadow-inner","data-testid":`group-node-${e.id}`,children:[(0,f.jsxs)("div",{className:"drag-handle flex h-11 items-center gap-2 border-b border-modiff-border/80 bg-modiff-panel/90 px-3 text-sm font-bold text-modiff-text",children:[(0,f.jsx)(o,{size:16,className:t?"text-hf-yellow":"text-modiff-subtle-text"}),(0,f.jsx)("span",{className:"truncate",children:e.data.label||"Group"}),t?(0,f.jsxs)("div",{className:"nodrag ml-auto flex shrink-0 items-center gap-1.5",children:[(0,f.jsxs)("span",{className:"rounded-modiff-compact border border-hf-yellow/30 px-1.5 py-0.5 text-xs font-semibold uppercase tracking-wide text-hf-yellow",children:[m," ",1===m?"block":"blocks"]}),(0,f.jsx)(i,{label:`${p?"Collapse":"Expand"} ${e.data.label||"Modular Diffusers container"}`,size:"compact","data-testid":`toggle-modular-container-${e.id}`,onClick:()=>{let a=e.data.blockProjectionOwnerId,o=e.data.blockProjectionNodeId;a&&o&&x(a,o)},children:p?(0,f.jsx)(d,{size:14}):(0,f.jsx)(a,{size:14})})]}):null]}),e.data.description?(0,f.jsx)("span",{className:"pointer-events-none absolute left-3 top-12 max-w-[calc(100%-1.5rem)] truncate text-xs text-modiff-subtle-text/80",children:e.data.description}):null,!t||p?(0,f.jsx)(s,{}):null]})});m.displayName="GroupNode";export{m as default}; \ No newline at end of file diff --git a/web/assets/GuidanceNodeControls.js b/web/assets/GuidanceNodeControls.js new file mode 100644 index 00000000..a16a3d86 --- /dev/null +++ b/web/assets/GuidanceNodeControls.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as a,dr as t}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{ft as s}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{r,t as o}from"./guidanceNodeFields.js?v=cc046c26d6fe7aa4";var d=e(t(),1),n=a();function l({instance:e}){let a=(0,d.useMemo)(()=>o(e),[e]),[t,l]=(0,d.useState)(!1),[i,m]=(0,d.useState)(null);return(0,n.jsxs)("div",{className:"nodrag nowheel grid gap-2",children:[(0,n.jsx)(s,{nodeId:e.instanceId,params:Object.fromEntries(a.map(e=>[e.id,{...e.param,disabled:t||e.param.disabled}])),module:"MoDiff",action:"Guidance",mode:"controls",updateStore:(a,s,o)=>{t||o&&"value"!==o||(l(!0),m(null),r(e.instanceId,a,s).catch(e=>{m(e instanceof Error?e.message:"Could not update Guidance.")}).finally(()=>l(!1)))}}),t?(0,n.jsx)("p",{role:"status",className:"text-xs text-modiff-subtle-text",children:"Updating guidance controls…"}):null,i?(0,n.jsx)("p",{role:"alert",className:"text-xs text-modiff-red",children:i}):null]})}export{l as default}; \ No newline at end of file diff --git a/web/assets/HuggingFaceClusterNode.js b/web/assets/HuggingFaceClusterNode.js index 8dff5ad4..f85bb19c 100644 --- a/web/assets/HuggingFaceClusterNode.js +++ b/web/assets/HuggingFaceClusterNode.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{Ct as t,Et as a,J as i,_t as s,ir as n,pr as r,rr as o,ur as l}from"./graph-vendor.js?v=5bcd315505f5670a";import{$ as d,Cn as u,Fr as c,Gn as g,Gt as f,Kn as h,Lr as m,Pr as p,Qt as x,R as b,Sn as v,Ua as w,Wn as C,kr as S,ni as y,on as j,ui as N,yn as E,zr as I}from"./studio-templates.js?v=5bcd315505f5670a";var F=e(r(),1),k=l(),H=(0,F.memo)(e=>{let r=(0,F.useRef)(null),l="root"===e.data.huggingFaceClusterRole,H=!!e.data.huggingFaceClusterInstance?.presentation.expanded,R=e.data.huggingFaceClusterInstance?.definition,z=c(e=>e.library?.definitions.find(e=>e.id===R?.id&&e.libraryRevision===R.libraryRevision&&e.contentHash===R.contentHash)),M=c(e=>e.loaded),T=c(e=>e.fetchLibrary),$=p(e=>e.snapshot),P=p(e=>e.loaded),L=p(e=>e.fetchSnapshot);(0,F.useEffect)(()=>{l&&!M&&T()},[T,l,M]),(0,F.useEffect)(()=>{l&&"diffusers"===z?.provider&&!P&&L()},[P,z?.provider,L,l]);let _="diffusers"===z?.provider?$?.pipelines.find(e=>e.pipelineClass===z.pipelineClass)?.contentHash:void 0,D=(0,F.useRef)(null);(0,F.useEffect)(()=>{if(!l||!z||!e.data.huggingFaceClusterInstance)return;let t=e.data.huggingFaceClusterInstance,a=[e.id,z.libraryRevision,z.contentHash,_??""].join("\0");if(D.current===a)return;D.current=a;let i=S.getState(),s=I({nodes:i.nodes,edges:i.edges},t.instanceId,z);S.setState({nodes:s.nodes,edges:s.edges}),S.getState().updateHandleConnectionStatus(),S.getState().updateSignalValues(s.edges)},[_,z,l,e.data.huggingFaceClusterInstance,e.id]);let W=(0,F.useMemo)(()=>z?.executionAdmissions.filter(e=>"admitted"===e.status&&"static_graph_contract_compatible"===e.claim&&"graph_qualified"===e.publication.readiness&&e.publication.insertable)??[],[z]),A=e.data.huggingFaceClusterInstance?.execution?.admissionId??"",U="transformers"===z?.provider||"Transformers Nodes"===e.data.category?"Transformers":"Diffusers",q=z?.suggestedInputs?.source,O=!(!z||!Object.keys(z.suggestedInputs?.values??{}).length),[V,G]=(0,F.useState)(!1),[J,X]=(0,F.useState)("idle"),[Y,K]=(0,F.useState)(null),Q=(0,F.useRef)(null),B=N(e=>e.workflowCanvasEpoch),Z=S(t=>t.nodes.some(t=>"execution"===t.data.huggingFaceClusterRole&&t.data.huggingFaceClusterInstanceId===e.id)),ee=S(t=>t.nodes.filter(t=>"execution"===t.data.huggingFaceClusterRole&&t.data.huggingFaceClusterInstanceId===e.id&&!t.hidden).map(e=>[e.id,e.position.x,e.position.y,e.measured?.width??e.width??0,e.measured?.height??e.height??0].join(":")).join("|")),te=!0===e.data.uiState?.clusterExecutionEdgesReady,ae=S(e=>e.toggleHuggingFaceClusterExpanded),ie=S(e=>e.setNodeSize),se=S(e=>e.setNodeUiState),ne=S(e=>e.beginHistoryTransaction),re=S(e=>e.commitHistoryTransaction),oe=o(),le=n(),de=u(e.id,r),ue=S(e=>e.setHuggingFaceClusterParameter),ce=S(e=>e.setHuggingFaceClusterExecution),ge=(0,F.useCallback)((t,a,i)=>{i&&"value"!==i||ue(l?e.id:e.data.huggingFaceClusterInstanceId??e.id,t,a)},[l,e.data.huggingFaceClusterInstanceId,e.id,ue]),fe=(0,F.useCallback)(()=>{ae(e.id),N.getState().saveActiveWorkflowTab(!0)},[e.id,ae]),he=(0,F.useCallback)(t=>{ce(e.id,t||null),N.getState().saveActiveWorkflowTab(!0)},[e.id,ce]);(0,F.useEffect)(()=>{H||(Q.current=null,X("idle"),K(null))},[H]),(0,F.useEffect)(()=>{let t=e.data.huggingFaceClusterInstance;if(!l||!z||!t||Z)return void(Z&&(X("ready"),K(null)));let a=W.find(e=>e.id===A);if(!a)return X("error"),void K("Select a workflow mode before loading its graph.");let i=JSON.stringify({canvasEpoch:B,definition:[z.id,z.libraryRevision,z.contentHash],admission:a.id,parameters:t.parameterOverrides,executionParameters:t.execution?.parameterOverrides,explicitSources:t.execution?.explicitParameterSources});if(Q.current===i)return;Q.current=i;let s=!0;X("loading"),K(null);let n=()=>y.getState().studioModelCapabilities.find(e=>e.modelType===z.pipelineClass)?.studioExecutionSpecs?.find(e=>e.id===a.studioExecutionSpec?.id&&e.contentHash===a.studioExecutionSpec.contentHash&&e.executionProfileId===a.studioExecutionSpec.executionProfileId),r=g(z,t,a);return Promise.resolve().then(async()=>{let e=await async function(e){return e()||(await y.getState().fetchStudioModelCapabilities(),e())}(n);if(!e)throw Error("The exact reviewed graph specification is unavailable.");return{spec:e,skeleton:await h({definition:z,instance:t,admission:a,executionSpec:e,nodesRegistry:y.getState().nodesRegistry,bindingValues:r,expanded:!0})}}).then(({spec:e,skeleton:i})=>C({definition:z,instance:t,admission:a,executionSpec:e,skeleton:i,bindingValues:r})).then(()=>{s&&(X("ready"),K(null))}).catch(e=>{s&&(X("error"),K(e instanceof Error?e.message:"Could not materialize the reviewed Cluster graph."))}),()=>{s=!1}},[z,W,H,Z,l,e.data.huggingFaceClusterInstance,A,B]),(0,F.useEffect)(()=>{if(!H||!ee)return;let t=S.getState().nodes.filter(t=>"execution"===t.data.huggingFaceClusterRole&&t.data.huggingFaceClusterInstanceId===e.id).map(e=>e.id),a=new Set,i=e=>{let t=window.requestAnimationFrame(()=>{a.delete(t),e()});a.add(t)},s=new Set(t),n=a=>{le([e.id,...t]),S.getState().edges.filter(e=>s.has(e.source)||s.has(e.target)).every(e=>{let t=!e.sourceHandle||!!document.querySelector(`[data-testid="node-handle-${e.source}-${e.sourceHandle}"]`),a=!e.targetHandle||!!document.querySelector(`[data-testid="node-handle-${e.target}-${e.targetHandle}"]`);return t&&a})?i(()=>se(e.id,{clusterExecutionEdgesReady:!0})):a<120&&i(()=>n(a+1))};te?le([e.id,...t]):i(()=>n(0));let r=window.setTimeout(()=>{S.setState(t=>{let a=m(t,e.id);return{...a===t?{}:{nodes:a.nodes},layoutRevision:t.layoutRevision+1}})},100);return()=>{a.forEach(e=>window.cancelAnimationFrame(e)),window.clearTimeout(r)}},[te,ee,H,e.id,se,le]);let me=(0,F.useCallback)(()=>{!z||!e.data.huggingFaceClusterInstance||!A||V||(G(!0),b(e.id).then(()=>{w("Cluster is now an editable User Node for this workflow.",{variant:"success",autoHideDuration:2400})}).catch(e=>{w(e instanceof Error?e.message:"Could not customize the Cluster as a User Node.",{variant:"error",autoHideDuration:4200})}).finally(()=>G(!1)))},[z,V,e.data.huggingFaceClusterInstance,e.id,A]),pe=(0,F.useCallback)(t=>{if(H)return;t.preventDefault(),t.stopPropagation(),ne("Resize Cluster Node");let a=r.current?.clientWidth||e.width||360,i=r.current?.clientHeight||e.height||320,s=t.clientX,n=t.clientY,o=oe.getState().transform[2]||1,l=a,d=i,u=t=>{l=Math.min(720,Math.max(360,a+(t.clientX-s)/o)),d=Math.max(320,i+(t.clientY-n)/o),ie(e.id,Math.round(l),Math.round(d)),de()},c=()=>{document.removeEventListener("mousemove",u),document.removeEventListener("mouseup",c),se(e.id,{clusterCollapsedWidth:Math.round(l),clusterCollapsedHeight:Math.round(d)}),re(),de()};document.addEventListener("mousemove",u),document.addEventListener("mouseup",c)},[ne,re,H,e.height,e.id,e.width,oe,de,ie,se]);return l?(0,k.jsxs)("div",{ref:r,className:v("relative flex h-full w-full min-w-[260px] flex-col overflow-visible rounded-modiff-panel border-2 border-hf-yellow/60 shadow-modiff-node",H?"bg-hf-yellow/[0.055]":"bg-modiff-surface"),"data-cluster-role":"root","data-testid":`hugging-face-cluster-${e.id}`,children:[(0,k.jsxs)("header",{className:v("drag-handle flex h-11 shrink-0 items-center gap-2 rounded-t-modiff-panel border-b border-hf-yellow/25 px-3 text-sm font-bold text-modiff-text",H?"bg-hf-yellow/10":"bg-modiff-bg"),children:[(0,k.jsx)(s,{size:16,className:"shrink-0 text-hf-yellow"}),(0,k.jsx)("span",{className:"min-w-0 flex-1 truncate",children:e.data.label}),H&&"loading"===J?(0,k.jsx)("span",{className:"text-xs font-semibold text-modiff-subtle-text",children:"Loading graph…"}):null,H&&"error"===J?(0,k.jsx)("span",{className:"max-w-40 truncate text-xs font-semibold text-modiff-red",title:Y??void 0,children:"Graph unavailable"}):null,(0,k.jsx)(f,{className:"nodrag nowheel",size:"compact",label:"Customize as User Node",title:A?"Make this workflow instance structurally editable while leaving the first-party Cluster unchanged.":"Select a reviewed workflow mode before customizing.",disabled:!z||!A||V,onClick:me,children:(0,k.jsx)(i,{size:15})}),(0,k.jsx)(f,{className:"nodrag nowheel",size:"compact",label:H?`Collapse ${U} Cluster Node`:`Expand ${U} Cluster Node`,title:z?void 0:"The exact reviewed library definition is unavailable.",disabled:!z,onClick:fe,"data-testid":`hugging-face-cluster-toggle-${e.id}`,children:H?(0,k.jsx)(t,{size:16}):(0,k.jsx)(a,{size:16})})]}),H?(0,k.jsx)("div",{className:"pointer-events-none min-h-0 flex-1","aria-hidden":"true",children:"error"===J?(0,k.jsx)("p",{className:"pointer-events-auto m-4 max-w-md text-xs text-modiff-red",role:"status",children:Y}):null}):(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)("div",{className:"nodrag nowheel min-h-24 flex-1 overflow-x-hidden overflow-y-auto bg-modiff-surface p-3",children:[W.length>1?(0,k.jsx)(x,{className:"mb-3",label:"Workflow",children:(0,k.jsx)(j,{value:A,onValueChange:he,options:W.map(e=>({value:e.id,label:e.studioMode.replace(/_/gu," ")})),placeholder:"Select a mode"})}):null,O&&q?(0,k.jsxs)("p",{className:"mb-3 rounded-modiff-compact border border-modiff-border-subtle bg-modiff-bg/60 px-2 py-1.5 text-modiff-label text-modiff-subtle-text","data-testid":`hugging-face-cluster-suggestion-source-${e.id}`,children:["Starter values:"," ",q.url?(0,k.jsx)("a",{className:"nodrag nowheel font-semibold text-hf-yellow hover:underline",href:q.url,target:"_blank",rel:"noreferrer",children:q.label}):(0,k.jsx)("span",{className:"font-semibold text-modiff-text",children:q.label})]}):null,(0,k.jsx)(d,{nodeId:e.id,params:e.data.params,updateStore:ge,module:z?.provider??"huggingface",action:"cluster",mode:"controls"})]}),(0,k.jsx)("div",{className:"shrink-0",children:(0,k.jsx)(d,{nodeId:e.id,params:e.data.params,updateStore:ge,module:z?.provider??"huggingface",action:"cluster",mode:"connectors"})})]}),H?null:(0,k.jsx)(E,{label:"Drag to resize Cluster Node",onMouseDown:pe})]}):null});H.displayName="HuggingFaceClusterNode";export{H as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as a,ht as i,nr as s,q as n,tr as r,wt as o,xt as l}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$r as d,H as c,In as u,Ln as g,Mo as f,Nn as h,Si as m,Vr as p,Xr as x,Yr as b,an as v,bn as w,cr as C,fn as S,ft as y,ji as k,or as j,sr as I,ti as E}from"./studio-templates.js?v=cc046c26d6fe7aa4";var F=e(a(),1),H=t(),N=(0,F.memo)(e=>{let t=(0,F.useRef)(null),a="root"===e.data.huggingFaceClusterRole,N=!!e.data.huggingFaceClusterInstance?.presentation.expanded,R=e.data.huggingFaceClusterInstance?.definition,M=x(e=>e.library?.definitions.find(e=>e.id===R?.id&&e.libraryRevision===R.libraryRevision&&e.contentHash===R.contentHash)),z=x(e=>e.loaded),T=x(e=>e.fetchLibrary),$=b(e=>e.snapshot),B=b(e=>e.loaded),L=b(e=>e.fetchSnapshot);(0,F.useEffect)(()=>{a&&!z&&T()},[T,a,z]),(0,F.useEffect)(()=>{a&&"diffusers"===M?.provider&&!B&&L()},[B,M?.provider,L,a]);let P="diffusers"===M?.provider?$?.pipelines.find(e=>e.pipelineClass===M.pipelineClass)?.contentHash:void 0,D=(0,F.useRef)(null);(0,F.useEffect)(()=>{if(!a||!M||!e.data.huggingFaceClusterInstance)return;let t=e.data.huggingFaceClusterInstance,i=[e.id,M.libraryRevision,M.contentHash,P??""].join("\0");if(D.current===i)return;D.current=i;let s=p.getState(),n=E({nodes:s.nodes,edges:s.edges},t.instanceId,M);p.setState({nodes:n.nodes,edges:n.edges}),p.getState().updateHandleConnectionStatus(),p.getState().updateSignalValues(n.edges)},[P,M,a,e.data.huggingFaceClusterInstance,e.id]);let _=(0,F.useMemo)(()=>M?.executionAdmissions.filter(e=>"admitted"===e.status&&"static_graph_contract_compatible"===e.claim&&"graph_qualified"===e.publication.readiness&&e.publication.insertable)??[],[M]),q=e.data.huggingFaceClusterInstance?.execution?.admissionId??"",A="transformers"===M?.provider||"Transformers Nodes"===e.data.category?"Transformers":"Diffusers",V=M?.suggestedInputs?.source,W=!(!M||!Object.keys(M.suggestedInputs?.values??{}).length),[O,X]=(0,F.useState)(!1),[Y,G]=(0,F.useState)("idle"),[J,U]=(0,F.useState)(null),K=(0,F.useRef)(null),Q=k(e=>e.workflowCanvasEpoch),Z=p(t=>t.nodes.some(t=>"execution"===t.data.huggingFaceClusterRole&&t.data.huggingFaceClusterInstanceId===e.id)),ee=p(t=>t.nodes.filter(t=>"execution"===t.data.huggingFaceClusterRole&&t.data.huggingFaceClusterInstanceId===e.id&&!t.hidden).map(e=>[e.id,e.position.x,e.position.y,e.measured?.width??e.width??0,e.measured?.height??e.height??0].join(":")).join("|")),te=!0===e.data.uiState?.clusterExecutionEdgesReady,ae=p(e=>e.toggleHuggingFaceClusterExpanded),ie=p(e=>e.setNodeSize),se=p(e=>e.setNodeUiState),ne=p(e=>e.beginHistoryTransaction),re=p(e=>e.commitHistoryTransaction),oe=r(),le=s(),de=g(e.id,t),ce=p(e=>e.setHuggingFaceClusterParameter),ue=p(e=>e.setHuggingFaceClusterExecution),ge=(0,F.useCallback)((t,i,s)=>{s&&"value"!==s||ce(a?e.id:e.data.huggingFaceClusterInstanceId??e.id,t,i)},[a,e.data.huggingFaceClusterInstanceId,e.id,ce]),fe=(0,F.useCallback)(()=>{ae(e.id),k.getState().saveActiveWorkflowTab(!0)},[e.id,ae]),he=(0,F.useCallback)(t=>{ue(e.id,t||null),k.getState().saveActiveWorkflowTab(!0)},[e.id,ue]);(0,F.useEffect)(()=>{N||(K.current=null,G("idle"),U(null))},[N]),(0,F.useEffect)(()=>{let t=e.data.huggingFaceClusterInstance;if(!a||!M||!t||Z)return void(Z&&(G("ready"),U(null)));let i=_.find(e=>e.id===q);if(!i)return G("error"),void U("Select a workflow mode before loading its graph.");let s=JSON.stringify({canvasEpoch:Q,definition:[M.id,M.libraryRevision,M.contentHash],admission:i.id,parameters:t.parameterOverrides,executionParameters:t.execution?.parameterOverrides,explicitSources:t.execution?.explicitParameterSources});if(K.current===s)return;K.current=s;let n=!0;G("loading"),U(null);let r=()=>m.getState().studioModelCapabilities.find(e=>e.modelType===M.pipelineClass)?.studioExecutionSpecs?.find(e=>e.id===i.studioExecutionSpec?.id&&e.contentHash===i.studioExecutionSpec.contentHash&&e.executionProfileId===i.studioExecutionSpec.executionProfileId),o=I(M,t,i);return Promise.resolve().then(async()=>{let e=await async function(e){return e()||(await m.getState().fetchStudioModelCapabilities(),e())}(r);if(!e)throw Error("The exact reviewed graph specification is unavailable.");return{spec:e,skeleton:await C({definition:M,instance:t,admission:i,executionSpec:e,nodesRegistry:m.getState().nodesRegistry,bindingValues:o,expanded:!0})}}).then(({spec:e,skeleton:a})=>j({definition:M,instance:t,admission:i,executionSpec:e,skeleton:a,bindingValues:o})).then(()=>{n&&(G("ready"),U(null))}).catch(e=>{n&&(G("error"),U(e instanceof Error?e.message:"Could not materialize the reviewed Block graph."))}),()=>{n=!1}},[M,_,N,Z,a,e.data.huggingFaceClusterInstance,q,Q]),(0,F.useEffect)(()=>{if(!N||!ee)return;let t=p.getState().nodes.filter(t=>"execution"===t.data.huggingFaceClusterRole&&t.data.huggingFaceClusterInstanceId===e.id).map(e=>e.id),a=new Set,i=e=>{let t=window.requestAnimationFrame(()=>{a.delete(t),e()});a.add(t)},s=new Set(t),n=a=>{le([e.id,...t]),p.getState().edges.filter(e=>s.has(e.source)||s.has(e.target)).every(e=>{let t=!e.sourceHandle||!!document.querySelector(`[data-testid="node-handle-${e.source}-${e.sourceHandle}"]`),a=!e.targetHandle||!!document.querySelector(`[data-testid="node-handle-${e.target}-${e.targetHandle}"]`);return t&&a})?i(()=>se(e.id,{clusterExecutionEdgesReady:!0})):a<120&&i(()=>n(a+1))};te?le([e.id,...t]):i(()=>n(0));let r=window.setTimeout(()=>{p.setState(t=>{let a=d(t,e.id);return{...a===t?{}:{nodes:a.nodes},layoutRevision:t.layoutRevision+1}})},100);return()=>{a.forEach(e=>window.cancelAnimationFrame(e)),window.clearTimeout(r)}},[te,ee,N,e.id,se,le]);let me=(0,F.useCallback)(()=>{!M||!e.data.huggingFaceClusterInstance||!q||O||(X(!0),c(e.id).then(()=>{f("Created an editable Block copy for this workflow.",{variant:"success",autoHideDuration:2400})}).catch(e=>{f(e instanceof Error?e.message:"Could not create an editable Block copy.",{variant:"error",autoHideDuration:4200})}).finally(()=>X(!1)))},[M,O,e.data.huggingFaceClusterInstance,e.id,q]),pe=(0,F.useCallback)(a=>{if(N)return;a.preventDefault(),a.stopPropagation(),ne("Resize Block");let i=t.current?.clientWidth||e.width||360,s=t.current?.clientHeight||e.height||320,n=a.clientX,r=a.clientY,o=oe.getState().transform[2]||1,l=i,d=s,c=t=>{l=Math.min(720,Math.max(360,i+(t.clientX-n)/o)),d=Math.max(320,s+(t.clientY-r)/o),ie(e.id,Math.round(l),Math.round(d)),de()},u=()=>{document.removeEventListener("mousemove",c),document.removeEventListener("mouseup",u),se(e.id,{clusterCollapsedWidth:Math.round(l),clusterCollapsedHeight:Math.round(d)}),re(),de()};document.addEventListener("mousemove",c),document.addEventListener("mouseup",u)},[ne,re,N,e.height,e.id,e.width,oe,de,ie,se]);return a?(0,H.jsxs)("div",{ref:t,className:u("relative flex h-full w-full min-w-[260px] flex-col overflow-visible rounded-modiff-panel border-2 border-hf-yellow/60 shadow-modiff-node",N?"bg-hf-yellow/[0.055]":"bg-modiff-surface"),"data-cluster-role":"root","data-testid":`hugging-face-cluster-${e.id}`,children:[(0,H.jsxs)("header",{className:u("drag-handle flex h-11 shrink-0 items-center gap-2 rounded-t-modiff-panel border-b border-hf-yellow/25 px-3 text-sm font-bold text-modiff-text",N?"bg-hf-yellow/10":"bg-modiff-bg"),children:[(0,H.jsx)(i,{size:16,className:"shrink-0 text-hf-yellow"}),(0,H.jsx)("span",{className:"min-w-0 flex-1 truncate",children:e.data.label}),N&&"loading"===Y?(0,H.jsx)("span",{className:"text-xs font-semibold text-modiff-subtle-text",children:"Loading graph…"}):null,N&&"error"===Y?(0,H.jsx)("span",{className:"max-w-40 truncate text-xs font-semibold text-modiff-red",title:J??void 0,children:"Graph unavailable"}):null,(0,H.jsx)(v,{className:"nodrag nowheel",size:"compact",label:"Customize as Block",title:q?"Make this workflow instance structurally editable while leaving the first-party Block unchanged.":"Select a reviewed workflow mode before customizing.",disabled:!M||!q||O,onClick:me,children:(0,H.jsx)(n,{size:15})}),(0,H.jsx)(v,{className:"nodrag nowheel",size:"compact",label:N?`Collapse ${A} Block`:`Expand ${A} Block`,title:M?void 0:"The exact reviewed library definition is unavailable.",disabled:!M,onClick:fe,"data-testid":`hugging-face-cluster-toggle-${e.id}`,children:N?(0,H.jsx)(l,{size:16}):(0,H.jsx)(o,{size:16})})]}),N?(0,H.jsx)("div",{className:"pointer-events-none min-h-0 flex-1","aria-hidden":"true",children:"error"===Y?(0,H.jsx)("p",{className:"pointer-events-auto m-4 max-w-md text-xs text-modiff-red",role:"status",children:J}):null}):(0,H.jsxs)(H.Fragment,{children:[(0,H.jsxs)("div",{className:"nodrag nowheel min-h-24 flex-1 overflow-x-hidden overflow-y-auto bg-modiff-surface p-3",children:[_.length>1?(0,H.jsx)(S,{className:"mb-3",label:"Workflow",children:(0,H.jsx)(w,{value:q,onValueChange:he,options:_.map(e=>({value:e.id,label:e.studioMode.replace(/_/gu," ")})),placeholder:"Select a mode"})}):null,W&&V?(0,H.jsxs)("p",{className:"mb-3 rounded-modiff-compact border border-modiff-border-subtle bg-modiff-bg/60 px-2 py-1.5 text-modiff-label text-modiff-subtle-text","data-testid":`hugging-face-cluster-suggestion-source-${e.id}`,children:["Starter values:"," ",V.url?(0,H.jsx)("a",{className:"nodrag nowheel font-semibold text-hf-yellow hover:underline",href:V.url,target:"_blank",rel:"noreferrer",children:V.label}):(0,H.jsx)("span",{className:"font-semibold text-modiff-text",children:V.label})]}):null,(0,H.jsx)(y,{nodeId:e.id,params:e.data.params,updateStore:ge,module:M?.provider??"huggingface",action:"cluster",mode:"controls"})]}),(0,H.jsx)("div",{className:"shrink-0",children:(0,H.jsx)(y,{nodeId:e.id,params:e.data.params,updateStore:ge,module:M?.provider??"huggingface",action:"cluster",mode:"connectors"})})]}),N?null:(0,H.jsx)(h,{label:"Drag to resize Block",onMouseDown:pe})]}):null});N.displayName="HuggingFaceClusterNode";export{N as default}; \ No newline at end of file diff --git a/web/assets/LeftLibraryPanels.js b/web/assets/LeftLibraryPanels.js index 461ba17e..63d96fa4 100644 --- a/web/assets/LeftLibraryPanels.js +++ b/web/assets/LeftLibraryPanels.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{F as t,H as s,In as a,Mt as l,Nn as i,Ot as n,Rt as r,Wt as d,Yt as o,bt as c,pr as m,q as u,un as f,ur as p}from"./graph-vendor.js?v=5bcd315505f5670a";import{$t as x,Ba as h,Bn as g,Ca as b,Ea as y,Gi as j,Ha as v,Lo as N,Lt as w,Sn as k,T as C,U as $,Ua as I,Ut as T,Va as M,bi as A,cr as _,ct as z,ga as P,gi as L,jn as O,kr as R,li as S,lt as D,mi as E,ni as q,or as W,pi as K,qa as U,ta as V,ui as H,ur as B,za as G}from"./studio-templates.js?v=5bcd315505f5670a";import{r as F,t as J}from"./templateBrowser.js?v=5bcd315505f5670a";import{t as Y}from"./WorkflowArtifactRequirementRow.js?v=5bcd315505f5670a";var Q=e(m(),1),X=p();function Z({action:e,actionPlacement:t="inline",children:s,icon:a,meta:l,title:i}){return(0,X.jsxs)("div",{className:"flex h-full flex-col",children:[(0,X.jsxs)("header",{className:"border-b border-modiff-border bg-modiff-surface px-3 py-3",children:[(0,X.jsxs)("div",{className:"flex items-start justify-between gap-3",children:[(0,X.jsxs)("div",{className:"flex min-w-0 flex-1 items-center gap-2",children:[(0,X.jsx)("span",{className:"grid size-8 shrink-0 place-items-center rounded-modiff-compact border border-modiff-border bg-modiff-bg text-hf-yellow",children:a}),(0,X.jsxs)("div",{className:"min-w-0",children:[(0,X.jsx)("h2",{className:"truncate text-sm font-semibold text-modiff-text",children:i}),(0,X.jsx)("p",{className:"truncate text-xs text-modiff-subtle-text",children:l})]})]}),"inline"===t?e:null]}),"stacked"===t&&e?(0,X.jsx)("div",{className:"mt-3 min-w-0",children:e}):null]}),(0,X.jsx)("div",{className:"min-h-0 flex-1 overflow-y-auto p-2",children:s})]})}function ee({children:e,className:t,onClick:s}){return s?(0,X.jsx)(T,{align:"left",className:k("h-auto min-h-0 w-full bg-modiff-surface p-3 text-sm font-normal",t),fullWidth:!0,onClick:s,tone:"secondary",children:e}):(0,X.jsx)("div",{className:k("w-full rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-left text-sm text-modiff-text",t),children:e})}function te({label:e,value:t}){return(0,X.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,X.jsx)("div",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:e}),(0,X.jsx)("div",{className:"mt-1 text-base font-bold text-modiff-text",children:t})]})}function se(e){return"default"===e?"neutral":e}function ae({children:e,disabled:t,onClick:s,progress:a,testId:l,title:i,tone:n}){return(0,X.jsx)(w,{action:"Installing"===("string"==typeof e?e:void 0)?"installing":s?"install":"success"===n?"ready":"warning"===n||"error"===n?"missing":"details",className:"min-h-7 px-2 py-0.5 text-xs",disabled:t,label:e,onClick:s,progress:a,testId:l,title:i,tone:se(n)})}function le(e){if("string"==typeof e)return e;if(e&&"object"==typeof e){let t=e;return String(t.id??t.repo_id??t.label??t.rel_path??t.path??JSON.stringify(e))}return String(e??"")}function ie(e){let t=le(e);return t.replace(/\\/g,"/").split("/").filter(Boolean).slice(-2).join("/")||t}var ne=["Image","Edit","Control","Video","Audio","Adapters","Components","Other"];function re(e){return"Audio"===e?"Audio":"Video"===e?"Video":"Control"===e?"Control":"Image Edit"===e?"Edit":"Utility"===e?"Adapters":"Image"===e?"Image":"Other"}function de(e){let t=le(e).toLowerCase();return t.includes("text_encoder")||t.includes("text encoder")||t.includes("vae")||t.includes("tokenizer")||t.includes("transformer")||t.includes("diffusion_model")||t.includes("clip")?"Components":t.includes("wan")||t.includes("video")?"Video":t.includes("ace")||t.includes("audio")?"Audio":t.includes("control")||t.includes("canny")||t.includes("depth")?"Control":t.includes("lora")||t.includes("adapter")?"Adapters":t.includes("edit")||t.includes("inpaint")||t.includes("fill")||t.includes("kontext")||t.includes("redux")?"Edit":t.includes("image")||t.includes("flux")||t.includes("z-image")||t.includes("qwen")?"Image":"Other"}var oe=["repo_id","model_id","repository_id","model","model_name","checkpoint","ckpt_name","path"];function ce(e){return String(e??"").replace(/\\/g,"/").toLowerCase()}function me(e,t,s){let a=function(e){let t=ce(le(e));return Object.values(P).find(e=>function(e,t){return[e.defaultRepo,e.alternateArtifact,e.label,e.displayName,e.family].some(e=>{let s=ce(e);return s.length>0&&(t.includes(s)||s.includes(t))})}(e,t))}(e),l=t.find(e=>e.modelType===a?.modelType)?.executionProfiles??[];if(l.length>0){let e=new Set;return l.map(e=>({key:`${e.loader_module}.${e.loader_action}`,identityKey:"ModelsLoader"===e.loader_action?"model_type":"pipeline_class",identity:"ModelsLoader"===e.loader_action?e.model_type:e.pipeline_class})).filter(t=>{let s=`${t.key}:${t.identityKey}:${t.identity}`;return!e.has(s)&&(e.add(s),!0)})}if(a&&s)return[];let i=a?re(a.surfaceCategory):de(e);return[{key:`modules.Diffusers${"Audio"===i?"Audio":"Video"===i?"Video":"Image"}.LoadPipeline`},{key:"modules.ModularDiffusers.ModelsLoader",...a?{identityKey:"model_type",identity:a.modelType}:{}}]}function ue(e,t){return t.find(t=>e.data.params[t])}function fe(e,t,s){e.data.params={...e.data.params,[t]:{...e.data.params[t],value:s}}}function pe(e,t){let s=new Map;return ne.forEach(e=>s.set(e,[])),e.forEach(e=>s.get(t(e))?.push(e)),ne.map(e=>[e,s.get(e)??[]]).filter(([,e])=>e.length>0)}function xe(e){return"audio"===e?(0,X.jsx)(c,{size:14}):"video"===e?(0,X.jsx)(s,{size:14}):(0,X.jsx)(r,{size:14})}async function he(e){return e?(await Promise.all(Array.from(e).map(async(e,t)=>{let s=function(e){return z(e,["image","video","audio"])??"unknown"}(e);if("unknown"===s)return null;let a=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${t}-${e.name}`;try{let l=await async function(e,t){let[s]=await g(e,function(e){return"audio"===e?"audio":"video"===e?"videos":"images"}(t));if(!s)throw Error("Imported asset upload did not return a file path.");return s}(e,s);return{id:a,kind:"imported",name:e.name,url:`${N.serverAddress}/file?file=${encodeURIComponent(l)}`,backendPath:l,displayType:s,mimeType:e.type,byteSize:e.size,createdAt:Date.now()-t,source:"upload",storage:"backend"}}catch(t){throw Error(`${e.name}: ${t instanceof Error?t.message:"The backend could not validate this media file."}`)}}))).filter(e=>null!==e):[]}function ge(){let e=V(e=>e.setTemplateBrowserOpen),t=V(e=>e.openTemplateBrowser),s=j.filter(e=>"blocked"===e.difficulty||"blocked"===e.example?.status).length,a=j.length-s,i=new Map(J.map(e=>[e.id,"all"===e.id?a:j.filter(t=>F(t)===e.id&&"blocked"!==t.difficulty&&"blocked"!==t.example?.status).length])),n=J.filter(e=>"recommended"!==e.id&&("all"===e.id||(i.get(e.id)??0)>0));return(0,X.jsx)(Z,{title:"Templates",meta:`${a} usable, ${s} blocked`,icon:(0,X.jsx)(l,{size:18}),action:(0,X.jsx)(T,{className:"h-8 px-2 text-xs","data-testid":"left-open-template-browser",icon:(0,X.jsx)(l,{size:14}),onClick:()=>t("recommended"),tone:"primary",children:"Browse"}),children:(0,X.jsxs)("div",{className:"grid gap-2",children:[(0,X.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,X.jsx)(te,{label:"Templates",value:j.length}),(0,X.jsx)(te,{label:"Planning",value:s})]}),(0,X.jsxs)(ee,{className:"flex-col items-stretch gap-2",onClick:()=>e(!0),children:[(0,X.jsxs)("div",{className:"flex items-center gap-2 font-semibold",children:[(0,X.jsx)(l,{size:16,className:"text-hf-yellow"}),"Template browser"]}),(0,X.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"Search by task, model, inputs, media type, and backend requirement."})]}),(0,X.jsx)("div",{className:"grid gap-1",children:n.map(e=>(0,X.jsxs)(T,{align:"left",className:"min-h-9 justify-between bg-modiff-bg px-2 font-normal",fullWidth:!0,onClick:()=>t(e.id),tone:"secondary",children:[(0,X.jsx)("span",{className:"truncate",children:e.label}),(0,X.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:i.get(e.id)??0})]},e.id))})]})})}function be(){let e=V(e=>e.setGalleryLibraryOpen),s=H(e=>e.outputs),a=H(e=>e.importedAssets),l=H(e=>e.addImportedAssets),i=H(e=>e.useImportedAssetAsReference),d=H(e=>e.activeWorkflowTabId),c=(0,Q.useRef)(null),m=B(e=>e.taskCount),[u,p]=(0,Q.useState)(!1),h=(0,Q.useMemo)(()=>S(s,d),[d,s]),g=h.filter(e=>"video"===e.displayType).length,b=h.filter(e=>"audio"===e.displayType).length,y=h.slice(0,9),j=a.slice(0,6);return(0,X.jsx)(Z,{title:"Assets",meta:`${h.length} generated, ${a.length} imported`,icon:(0,X.jsx)(o,{size:18}),actionPlacement:"stacked",action:(0,X.jsxs)("div",{className:"grid min-w-0 grid-cols-3 gap-1",children:[(0,X.jsx)(x,{ref:c,"aria-label":"Import assets",className:"hidden",multiple:!0,onChange:e=>{(async e=>{try{let t=await he(e);l(t)}catch(e){I(e instanceof Error?e.message:"The selected media could not be imported.",{variant:"error",autoHideDuration:6e3})}finally{c.current&&(c.current.value="")}})(e.currentTarget.files)},accept:D(["image","video","audio"])}),(0,X.jsx)(T,{className:"h-8 min-w-0 px-1.5 text-xs","data-testid":"left-clean-temporary-media",disabled:u||m>0,icon:u?(0,X.jsx)(n,{size:14,className:"animate-spin"}):(0,X.jsx)(f,{size:14}),onClick:()=>{(async()=>{if(!(u||m>0)){p(!0);try{let e=await U();I(0===e.removed.length?"No unprotected temporary media to clean":`Removed ${e.removed.length} temporary media ${1===e.removed.length?"file":"files"}`,{variant:"success",autoHideDuration:3e3})}catch(e){I(String(e),{variant:"error",autoHideDuration:6e3})}finally{p(!1)}}})()},tone:"secondary",title:m>0?"Wait for the active generation to finish":"Remove unprotected temporary media",children:"Clean"}),(0,X.jsx)(T,{className:"h-8 min-w-0 px-1.5 text-xs","data-testid":"left-import-assets",icon:(0,X.jsx)(t,{size:14}),onClick:()=>c.current?.click(),tone:"secondary",title:"Import local media. Assets are persisted through the backend when connected.",children:"Import"}),(0,X.jsx)(T,{className:"h-8 min-w-0 px-1.5 text-xs","data-testid":"left-open-gallery-library",icon:(0,X.jsx)(o,{size:14}),onClick:()=>e(!0),tone:"primary",children:"Open"})]}),children:(0,X.jsxs)("div",{className:"grid gap-2",children:[(0,X.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,X.jsx)(te,{label:"Generated",value:h.length}),(0,X.jsx)(te,{label:"Imported",value:a.length}),(0,X.jsx)(te,{label:"Videos",value:g}),(0,X.jsx)(te,{label:"Audio",value:b})]}),(0,X.jsxs)("div",{className:"flex items-center justify-between gap-2 px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,X.jsx)("span",{children:"Generated"}),(0,X.jsx)(ae,{tone:"default",children:h.length})]}),(0,X.jsx)("div",{className:"grid grid-cols-3 gap-1","data-testid":"left-gallery-thumbnails",children:0===y.length?(0,X.jsx)("div",{className:"col-span-3 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:"No outputs yet."}):y.map((t,s)=>(0,X.jsx)(T,{"data-testid":`left-gallery-output-${s}`,title:`${t.modelLabel}${t.prompt?` | ${t.prompt}`:""}`,"aria-label":`Open gallery item from ${t.modelLabel}`,className:"aspect-square h-auto min-h-0 overflow-hidden bg-modiff-bg !p-0",onClick:()=>e(!0),tone:"secondary",children:"audio"===t.displayType||"video"===t.displayType?(0,X.jsx)("span",{className:"grid h-full w-full place-items-center text-hf-yellow",children:xe(t.displayType)}):(0,X.jsx)("img",{src:t.url,alt:"",className:"h-full w-full object-cover"})},t.id))}),(0,X.jsxs)("div",{className:"flex items-center justify-between gap-2 px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,X.jsx)("span",{children:"Imported"}),(0,X.jsx)(ae,{tone:"default",children:a.length})]}),(0,X.jsx)("div",{className:"grid grid-cols-3 gap-1","data-testid":"left-imported-thumbnails",children:0===j.length?(0,X.jsx)(T,{align:"left",className:"col-span-3 h-auto min-h-0 border-dashed bg-modiff-bg p-3 text-xs text-modiff-subtle-text",fullWidth:!0,onClick:()=>c.current?.click(),tone:"secondary",title:"Import local media. Assets are persisted through the backend when connected.",children:"Import assets"}):j.map((e,t)=>(0,X.jsx)(T,{"data-testid":`left-imported-asset-${t}`,title:e.name,"aria-label":`Use imported asset ${e.name}`,className:"aspect-square h-auto min-h-0 overflow-hidden bg-modiff-bg !p-0",onClick:()=>i(e),tone:"secondary",children:"image"===e.displayType?(0,X.jsx)("img",{src:e.url,alt:"",className:"h-full w-full object-cover"}):(0,X.jsx)("span",{className:"grid h-full w-full place-items-center text-hf-yellow",children:xe(e.displayType)})},e.id))}),(0,X.jsx)(ee,{onClick:()=>e(!0),children:(0,X.jsxs)("div",{className:"flex items-center justify-between gap-2 font-semibold",children:[(0,X.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,X.jsx)(r,{size:16,className:"text-hf-yellow"}),(0,X.jsx)("span",{className:"truncate",children:"Open gallery"})]}),(0,X.jsx)(ae,{tone:"default",children:h.length})]})})]})})}function ye(){let e=q(e=>e.hfCache),t=q(e=>e.localModels),s=q(e=>e.modelCacheDiagnostics),l=q(e=>e.hfDownloadProgress),n=q(e=>e.nodesRegistry),r=q(e=>e.studioModelCapabilities),o=q(e=>e.studioModelCapabilitiesAuthoritative),c=q(e=>e.installHfModel),m=q(e=>e.fetchRuntimeStatus),f=q(e=>e.fetchHfCache),p=q(e=>e.fetchLocalModels),x=q(e=>e.fetchModelCacheDiagnostics),g=V(e=>e.setModelManagerOpener),j=V(e=>e.setRightPanelOpen),N=V(e=>e.setRightPanelTab),w=V(e=>e.studioViewMode),z=H(e=>e.form),L=H(e=>e.activeTemplateId),S=H(e=>e.graphBinding),D=H(e=>e.sourceOutputId),E=H(e=>e.autoResourcePlans),U=H(e=>e.setAutoResourcePlans),B=O(e=>e.sid),G=R(e=>e.addNode),F=R(e=>e.viewport),J=R(e=>e.nodes.length),[ee,te]=(0,Q.useState)(new Set);(0,Q.useEffect)(()=>{m(),f(!1),p(!1),x(!1)},[f,p,x,m]);let se=!!(L||S||D),ae=P[z.modelType],ne=(0,Q.useMemo)(()=>b({currentModelType:se?z.modelType:null,includeWorkflowOnly:"expert"===w}),[z.modelType,se,w]),ce=(0,Q.useMemo)(()=>ne.map(e=>y(e.modelType)),[ne]),xe=(0,Q.useMemo)(()=>ce.map(e=>K(e)),[ce]),he=(0,Q.useMemo)(()=>function(e){let t=new Set;return e.filter(({item:e,source:s})=>{let a=`${s}:${le(e).toLowerCase()}`;return!t.has(a)&&(t.add(a),!0)})}([...e.map(e=>({source:"HF",item:e})),...t.map(e=>({source:"Local",item:e}))]),[e,t]),ge=(0,Q.useMemo)(()=>pe(he,({item:e})=>de(e)),[he]),be=(0,Q.useMemo)(()=>Object.values(l).filter(e=>M(e)||h(e)||v(e)).sort((e,t)=>(t.updated_at??t.completed_at??t.started_at??0)-(e.updated_at??e.completed_at??e.started_at??0)),[l]),ye=(0,Q.useMemo)(()=>ne.filter(a=>!_(a,e,t,s).runnable),[e,t,s,ne]),ve=(0,Q.useMemo)(()=>pe(ye,e=>re(e.surfaceCategory)),[ye]),Ne=(0,Q.useMemo)(()=>"expert"===w?ge:ge.filter(([e])=>"Components"!==e),[ge,w]),we=se&&ae?W({form:z,autoResourcePlan:E[K(z)],hfCache:e,localModels:t,modelCacheDiagnostics:s,includePipeline:!1}):[];(0,Q.useEffect)(()=>{if(0===ce.length)return;let e=!1;return A(ce,xe).then(t=>{if(e)return;let s={};t.forEach((e,t)=>{let a=e.planKey??xe[t];a&&(s[a]=e)}),U(s)}).catch(e=>{console.error(e)}),()=>{e=!0}},[U,ce,xe]);let ke=()=>{j(!0),N("setup")},Ce=(0,Q.useCallback)(e=>{te(t=>{let s=new Set(t);return s.has(e)?s.delete(e):s.add(e),s})},[]),$e=async e=>{try{await c(e.repo,B,{repair:e.repair,revision:e.revision,files:e.files})}catch(e){console.error(e)}},Ie=(0,Q.useCallback)((e,t)=>{let s=le(e),a=me(e,r,o),l=function(e,t){let s=e.zoom||1,a=t%6*36;return{x:(220-e.x+a)/s,y:(140-e.y+a)/s}}(F,J),i=a.map(e=>({candidate:e,node:$(e.key,n,l)})).find(({candidate:e,node:t})=>t&&(ue(t,oe)||ue(t,["model_type"]))&&(!e.identityKey||ue(t,[e.identityKey]))),d=i?.node;if(!d)return void I("No matching loader node is available yet.",{variant:"error",autoHideDuration:2800});let c=i.candidate.identityKey;c&&i.candidate.identity&&fe(d,c,i.candidate.identity);let m=ue(d,oe);m&&fe(d,m,function(e,t,s,a){let l=t.data.params[s],i=l?.value??l?.default;return"modelselect"===l?.display||i&&"object"==typeof i&&!Array.isArray(i)?{source:"Local"===e?"local":"hub",value:a}:a}(t,d,m,s)),G({...d,selected:!0}),I(`Added ${ie(e)} loader`,{variant:"success",autoHideDuration:1800})},[G,J,n,r,o,F]);return(0,X.jsx)(Z,{title:"Models",meta:`${he.length} installed, ${ye.length} available`,icon:(0,X.jsx)(a,{size:18}),action:(0,X.jsx)(T,{className:"h-8 px-2 text-xs","data-testid":"left-open-model-manager",icon:(0,X.jsx)(u,{size:14}),onClick:()=>g({nodeId:null,fieldKey:null}),tone:"primary",children:"Models"}),children:(0,X.jsxs)("div",{className:"grid gap-2",children:[be.length>0?(0,X.jsxs)("section",{className:"grid gap-1","data-testid":"left-model-downloads",children:[(0,X.jsxs)("div",{className:"flex items-center justify-between gap-2 px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,X.jsx)("span",{children:"Downloads"}),(0,X.jsxs)("span",{children:[be.filter(M).length," active"]})]}),be.slice(0,3).map(e=>(0,X.jsx)(C,{compact:!0,progress:e,showTechnicalDetails:!1,testId:`left-model-download-${e.repo_id}`},e.repo_id)),be.length>3?(0,X.jsxs)(T,{tone:"ghost",onClick:()=>g({nodeId:null,fieldKey:null}),children:["View all ",be.length," downloads"]}):null]}):null,se&&ae?(0,X.jsxs)("section",{className:"grid gap-1","data-testid":"left-model-current",children:[(0,X.jsx)("div",{className:"flex items-center justify-between gap-2 px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:(0,X.jsx)("span",{children:"Current"})}),(0,X.jsx)(je,{autoPlan:E[K(z)],installProgress:l,onInstall:$e,onOpenSetup:ke,profile:ae,status:_(ae,e,t,s)}),we.map(e=>(0,X.jsx)(Y,{actionTestId:`left-install-requirement-${e.id}`,compact:!0,installProgress:l,onInstall:$e,onUseLocal:()=>g({nodeId:null,fieldKey:null,focus:{repo:e.repo,requirementId:e.id,label:e.label,source:"workflow"}}),requirement:e,testId:`left-model-requirement-${e.id}`},e.id))]}):null,(0,X.jsxs)("section",{className:"grid gap-1","data-testid":"left-model-installed",children:[(0,X.jsxs)("div",{className:"px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:["Installed (",he.length,")"]}),0===he.length||0===Ne.length?(0,X.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:"No visible model artifacts indexed yet."}):Ne.map(([e,t])=>{let s=`installed:${e}`,a=ee.has(s);return(0,X.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg",children:[(0,X.jsxs)(T,{align:"left",className:"min-h-9 justify-between rounded-none border-0 bg-transparent px-2",fullWidth:!0,tone:"ghost",onClick:()=>Ce(s),children:[(0,X.jsx)("span",{className:"truncate",children:e}),(0,X.jsxs)("span",{className:"flex shrink-0 items-center gap-2 text-xs text-modiff-subtle-text",children:[t.length,(0,X.jsx)(i,{size:15,className:k("transition-transform",a&&"rotate-180")})]})]}),a?(0,X.jsxs)("div",{className:"grid gap-1 border-t border-modiff-border p-1",children:[t.slice(0,"expert"===w?t.length:6).map(({item:e,source:t})=>(0,X.jsxs)(T,{align:"left",fullWidth:!0,title:le(e),onClick:()=>Ie(e,t),className:"min-h-10 justify-between bg-modiff-bg p-2 font-normal",tone:"secondary",children:[(0,X.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,X.jsx)(d,{size:15,className:"shrink-0 text-modiff-subtle-text"}),(0,X.jsx)("span",{className:"min-w-0 truncate text-sm font-semibold text-modiff-text",children:ie(e)})]}),(0,X.jsx)("span",{className:"shrink-0 text-xs font-semibold text-modiff-subtle-text",children:t})]},`${t}-${le(e)}`)),"expert"!==w&&t.length>6?(0,X.jsxs)(T,{align:"left",className:"h-auto min-h-0 bg-modiff-bg p-2 text-xs text-modiff-subtle-text",fullWidth:!0,onClick:()=>g({nodeId:null,fieldKey:null}),tone:"secondary",children:["View ",t.length-6," more ",e.toLowerCase()," artifacts"]}):null]}):null]},e)})]}),ye.length>0?(0,X.jsxs)("section",{className:"grid gap-1","data-testid":"left-model-supported",children:[(0,X.jsxs)("div",{className:"px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:["Available (",ye.length,")"]}),ve.map(([a,n])=>{let r=`available:${a}`,d=ee.has(r);return(0,X.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg",children:[(0,X.jsxs)(T,{align:"left",className:"min-h-9 justify-between rounded-none border-0 bg-transparent px-2",fullWidth:!0,onClick:()=>Ce(r),tone:"ghost",children:[(0,X.jsx)("span",{className:"truncate",children:a}),(0,X.jsxs)("span",{className:"flex shrink-0 items-center gap-2 text-xs text-modiff-subtle-text",children:[n.length,(0,X.jsx)(i,{size:15,className:k("transition-transform",d&&"rotate-180")})]})]}),d?(0,X.jsx)("div",{className:"grid gap-1 border-t border-modiff-border p-1",children:n.map(a=>{let i=K(y(a.modelType));return(0,X.jsx)(je,{autoPlan:E[i],installProgress:l,onInstall:$e,onOpenSetup:ke,profile:a,status:_(a,e,t,s)},a.modelType)})}):null]},a)})]}):null]})})}function je({autoPlan:e,installProgress:t,onInstall:s,onOpenSetup:a,profile:l,status:i}){let n=L(e)??{repo:l.defaultRepo,label:l.label,actionLabel:"Install"},r=t[n.repo],d=E(e),o="queued"===r?.status||"running"===r?.status||"downloading"===r?.status,c="unsuitable"!==d.state||i.runnable?"warning":"error",m=o?"Installing":n.actionLabel??"Install",u=n.reason||`Install ${n.repo}`;return(0,X.jsx)("div",{"data-testid":`left-model-${l.modelType}`,className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:(0,X.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,X.jsxs)(T,{align:"left",className:"h-auto min-h-0 min-w-0 flex-1 bg-transparent p-0 font-normal",tone:"ghost",title:`${l.defaultRepo} | ${d.summary} | ${d.detail}`,onClick:a,children:[(0,X.jsx)("div",{className:"truncate text-sm font-semibold text-modiff-text",children:l.label}),(0,X.jsx)("div",{className:"mt-0.5 truncate text-xs text-modiff-subtle-text",children:l.family})]}),!i.runnable&&(0,X.jsx)(ae,{tone:c,testId:`left-install-${l.modelType}`,title:u,disabled:o,progress:o?G(r):null,onClick:()=>{s(n)},children:m})]})})}export{be as AssetsLibraryPanel,ye as ModelsLibraryPanel,ge as TemplateLibraryPanel}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{At as t,Et as s,Ht as a,It as l,K as i,Kt as n,Mn as r,P as d,V as o,cr as c,dr as m,kn as u,sn as f,vt as p}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ai as x,Cr as h,Fi as g,Ga as b,In as y,Ja as j,Jn as v,Li as N,Mo as w,Ns as k,Pi as C,Ra as I,S as $,Sa as T,Si as A,Sr as M,St as _,Vi as P,Vr as z,Zt as S,_o as L,co as O,ji as R,lo as D,oc as E,pa as K,pn as q,q as V,rn as W,rr as H,so as U,uo as B,xt as G}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{r as J,t as F}from"./templateBrowser.js?v=cc046c26d6fe7aa4";import{t as Z}from"./WorkflowArtifactRequirementRow.js?v=cc046c26d6fe7aa4";var Q=e(m(),1),X=c();function Y({action:e,actionPlacement:t="inline",children:s,icon:a,meta:l,title:i}){return(0,X.jsxs)("div",{className:"flex h-full flex-col",children:[(0,X.jsxs)("header",{className:"border-b border-modiff-border bg-modiff-surface px-3 py-3",children:[(0,X.jsxs)("div",{className:"flex items-start justify-between gap-3",children:[(0,X.jsxs)("div",{className:"flex min-w-0 flex-1 items-center gap-2",children:[(0,X.jsx)("span",{className:"grid size-8 shrink-0 place-items-center rounded-modiff-compact border border-modiff-border bg-modiff-bg text-hf-yellow",children:a}),(0,X.jsxs)("div",{className:"min-w-0",children:[(0,X.jsx)("h2",{className:"truncate text-sm font-semibold text-modiff-text",children:i}),(0,X.jsx)("p",{className:"truncate text-xs text-modiff-subtle-text",children:l})]})]}),"inline"===t?e:null]}),"stacked"===t&&e?(0,X.jsx)("div",{className:"mt-3 min-w-0",children:e}):null]}),(0,X.jsx)("div",{className:"min-h-0 flex-1 overflow-y-auto p-2",children:s})]})}function ee({children:e,className:t,onClick:s}){return s?(0,X.jsx)(W,{align:"left",className:y("h-auto min-h-0 w-full bg-modiff-surface p-3 text-sm font-normal",t),fullWidth:!0,onClick:s,tone:"secondary",children:e}):(0,X.jsx)("div",{className:y("w-full rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-left text-sm text-modiff-text",t),children:e})}function te({label:e,value:t}){return(0,X.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,X.jsx)("div",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:e}),(0,X.jsx)("div",{className:"mt-1 text-base font-bold text-modiff-text",children:t})]})}function se(e){return"default"===e?"neutral":e}function ae({children:e,disabled:t,onClick:s,progress:a,testId:l,title:i,tone:n}){return(0,X.jsx)(S,{action:"Installing"===("string"==typeof e?e:void 0)?"installing":s?"install":"success"===n?"ready":"warning"===n||"error"===n?"missing":"details",className:"min-h-7 px-2 py-0.5 text-xs",disabled:t,label:e,onClick:s,progress:a,testId:l,title:i,tone:se(n)})}function le(e){if("string"==typeof e)return e;if(e&&"object"==typeof e){let t=e;return String(t.id??t.repo_id??t.label??t.rel_path??t.path??JSON.stringify(e))}return String(e??"")}function ie(e){let t=le(e);return t.replace(/\\/g,"/").split("/").filter(Boolean).slice(-2).join("/")||t}var ne=["Image","Edit","Control","Video","Audio","Adapters","Components","Other"];function re(e){return"Audio"===e?"Audio":"Video"===e?"Video":"Control"===e?"Control":"Image Edit"===e?"Edit":"Utility"===e?"Adapters":"Image"===e?"Image":"Other"}function de(e){let t=le(e).toLowerCase();return t.includes("text_encoder")||t.includes("text encoder")||t.includes("vae")||t.includes("tokenizer")||t.includes("transformer")||t.includes("diffusion_model")||t.includes("clip")?"Components":t.includes("wan")||t.includes("video")?"Video":t.includes("ace")||t.includes("audio")?"Audio":t.includes("control")||t.includes("canny")||t.includes("depth")?"Control":t.includes("lora")||t.includes("adapter")?"Adapters":t.includes("edit")||t.includes("inpaint")||t.includes("fill")||t.includes("kontext")||t.includes("redux")?"Edit":t.includes("image")||t.includes("flux")||t.includes("z-image")||t.includes("qwen")?"Image":"Other"}var oe=["repo_id","model_id","repository_id","model","model_name","checkpoint","ckpt_name","path"];function ce(e){return String(e??"").replace(/\\/g,"/").toLowerCase()}function me(e,t,s){let a=function(e){let t=ce(le(e));return Object.values(I).find(e=>function(e,t){return[e.defaultRepo,e.alternateArtifact,e.label,e.displayName,e.family].some(e=>{let s=ce(e);return s.length>0&&(t.includes(s)||s.includes(t))})}(e,t))}(e),l=t.find(e=>e.modelType===a?.modelType)?.executionProfiles??[];if(l.length>0){let e=new Set;return l.map(e=>({key:`${e.loader_module}.${e.loader_action}`,identityKey:"ModelsLoader"===e.loader_action?"model_type":"pipeline_class",identity:"ModelsLoader"===e.loader_action?e.model_type:e.pipeline_class})).filter(t=>{let s=`${t.key}:${t.identityKey}:${t.identity}`;return!e.has(s)&&(e.add(s),!0)})}if(a&&s)return[];let i=a?re(a.surfaceCategory):de(e);return[{key:`modules.Diffusers${"Audio"===i?"Audio":"Video"===i?"Video":"Image"}.LoadPipeline`},{key:"modules.ModularDiffusers.ModelsLoader",...a?{identityKey:"model_type",identity:a.modelType}:{}}]}function ue(e,t){return t.find(t=>e.data.params[t])}function fe(e,t,s){e.data.params={...e.data.params,[t]:{...e.data.params[t],value:s}}}function pe(e,t){let s=new Map;return ne.forEach(e=>s.set(e,[])),e.forEach(e=>s.get(t(e))?.push(e)),ne.map(e=>[e,s.get(e)??[]]).filter(([,e])=>e.length>0)}function xe(e){return"audio"===e?(0,X.jsx)(p,{size:14}):"video"===e?(0,X.jsx)(o,{size:14}):(0,X.jsx)(l,{size:14})}function he(){let e=T(e=>e.setTemplateBrowserOpen),s=T(e=>e.openTemplateBrowser),a=K.filter(e=>"blocked"===e.difficulty||"blocked"===e.example?.status).length,l=K.length-a,i=new Map(F.map(e=>[e.id,"all"===e.id?l:K.filter(t=>J(t)===e.id&&"blocked"!==t.difficulty&&"blocked"!==t.example?.status).length])),n=F.filter(e=>"recommended"!==e.id&&("all"===e.id||(i.get(e.id)??0)>0));return(0,X.jsx)(Y,{title:"Templates",meta:`${l} usable, ${a} blocked`,icon:(0,X.jsx)(t,{size:18}),action:(0,X.jsx)(W,{className:"h-8 px-2 text-xs","data-testid":"left-open-template-browser",icon:(0,X.jsx)(t,{size:14}),onClick:()=>s("recommended"),tone:"primary",children:"Browse"}),children:(0,X.jsxs)("div",{className:"grid gap-2",children:[(0,X.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,X.jsx)(te,{label:"Templates",value:K.length}),(0,X.jsx)(te,{label:"Planning",value:a})]}),(0,X.jsxs)(ee,{className:"flex-col items-stretch gap-2",onClick:()=>e(!0),children:[(0,X.jsxs)("div",{className:"flex items-center gap-2 font-semibold",children:[(0,X.jsx)(t,{size:16,className:"text-hf-yellow"}),"Template browser"]}),(0,X.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"Search by task, model, inputs, media type, and backend requirement."})]}),(0,X.jsx)("div",{className:"grid gap-1",children:n.map(e=>(0,X.jsxs)(W,{align:"left",className:"min-h-9 justify-between bg-modiff-bg px-2 font-normal",fullWidth:!0,onClick:()=>s(e.id),tone:"secondary",children:[(0,X.jsx)("span",{className:"truncate",children:e.label}),(0,X.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:i.get(e.id)??0})]},e.id))})]})})}function ge(){let e=T(e=>e.setGalleryLibraryOpen),t=R(e=>e.outputs),a=R(e=>e.importedAssets),i=R(e=>e.addImportedAssets),r=R(e=>e.useImportedAssetAsReference),o=R(e=>e.activeWorkflowTabId),c=(0,Q.useRef)(null),m=h(e=>e.taskCount),[u,p]=(0,Q.useState)(!1),g=(0,Q.useMemo)(()=>x(t,o),[o,t]),b=g.filter(e=>"video"===e.displayType).length,y=g.filter(e=>"audio"===e.displayType).length,j=g.slice(0,9),v=a.slice(0,6);return(0,X.jsx)(Y,{title:"Assets",meta:`${g.length} generated, ${a.length} imported`,icon:(0,X.jsx)(n,{size:18}),actionPlacement:"stacked",action:(0,X.jsxs)("div",{className:"grid min-w-0 grid-cols-3 gap-1",children:[(0,X.jsx)(q,{ref:c,"aria-label":"Import assets",className:"hidden",multiple:!0,onChange:e=>{(async e=>{try{let t=await async function(e){return e?(await Promise.all(Array.from(e).map(async(e,t)=>{let s=function(e){return G(e,["image","video","audio"])??"unknown"}(e);if("unknown"===s)return null;let a=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${t}-${e.name}`;try{let l=await async function(e,t){let[s]=await H(e,function(e){return"audio"===e?"audio":"video"===e?"videos":"images"}(t));if(!s)throw Error("Imported asset upload did not return a file path.");return s}(e,s);return{id:a,kind:"imported",name:e.name,url:`${E.serverAddress}/file?file=${encodeURIComponent(l)}`,backendPath:l,displayType:s,mimeType:e.type,byteSize:e.size,createdAt:Date.now()-t,source:"upload",storage:"backend"}}catch(t){throw Error(`${e.name}: ${t instanceof Error?t.message:"The backend could not validate this media file."}`)}}))).filter(e=>null!==e):[]}(e);i(t)}catch(e){w(e instanceof Error?e.message:"The selected media could not be imported.",{variant:"error",autoHideDuration:6e3})}finally{c.current&&(c.current.value="")}})(e.currentTarget.files)},accept:_(["image","video","audio"])}),(0,X.jsx)(W,{className:"h-8 min-w-0 px-1.5 text-xs","data-testid":"left-clean-temporary-media",disabled:u||m>0,icon:u?(0,X.jsx)(s,{size:14,className:"animate-spin"}):(0,X.jsx)(f,{size:14}),onClick:()=>{(async()=>{if(!(u||m>0)){p(!0);try{let e=await L();w(0===e.removed.length?"No unprotected temporary media to clean":`Removed ${e.removed.length} temporary media ${1===e.removed.length?"file":"files"}`,{variant:"success",autoHideDuration:3e3})}catch(e){w(String(e),{variant:"error",autoHideDuration:6e3})}finally{p(!1)}}})()},tone:"secondary",title:m>0?"Wait for the active generation to finish":"Remove unprotected temporary media",children:"Clean"}),(0,X.jsx)(W,{className:"h-8 min-w-0 px-1.5 text-xs","data-testid":"left-import-assets",icon:(0,X.jsx)(d,{size:14}),onClick:()=>c.current?.click(),tone:"secondary",title:"Import local media. Assets are persisted through the backend when connected.",children:"Import"}),(0,X.jsx)(W,{className:"h-8 min-w-0 px-1.5 text-xs","data-testid":"left-open-gallery-library",icon:(0,X.jsx)(n,{size:14}),onClick:()=>e(!0),tone:"primary",children:"Open"})]}),children:(0,X.jsxs)("div",{className:"grid gap-2",children:[(0,X.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,X.jsx)(te,{label:"Generated",value:g.length}),(0,X.jsx)(te,{label:"Imported",value:a.length}),(0,X.jsx)(te,{label:"Videos",value:b}),(0,X.jsx)(te,{label:"Audio",value:y})]}),(0,X.jsxs)("div",{className:"flex items-center justify-between gap-2 px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,X.jsx)("span",{children:"Generated"}),(0,X.jsx)(ae,{tone:"default",children:g.length})]}),(0,X.jsx)("div",{className:"grid grid-cols-3 gap-1","data-testid":"left-gallery-thumbnails",children:0===j.length?(0,X.jsx)("div",{className:"col-span-3 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:"No outputs yet."}):j.map((t,s)=>(0,X.jsx)(W,{"data-testid":`left-gallery-output-${s}`,title:`${t.modelLabel}${t.prompt?` | ${t.prompt}`:""}`,"aria-label":`Open gallery item from ${t.modelLabel}`,className:"aspect-square h-auto min-h-0 overflow-hidden bg-modiff-bg !p-0",onClick:()=>e(!0),tone:"secondary",children:"audio"===t.displayType||"video"===t.displayType?(0,X.jsx)("span",{className:"grid h-full w-full place-items-center text-hf-yellow",children:xe(t.displayType)}):(0,X.jsx)("img",{src:t.url,alt:"",className:"h-full w-full object-cover"})},t.id))}),(0,X.jsxs)("div",{className:"flex items-center justify-between gap-2 px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,X.jsx)("span",{children:"Imported"}),(0,X.jsx)(ae,{tone:"default",children:a.length})]}),(0,X.jsx)("div",{className:"grid grid-cols-3 gap-1","data-testid":"left-imported-thumbnails",children:0===v.length?(0,X.jsx)(W,{align:"left",className:"col-span-3 h-auto min-h-0 border-dashed bg-modiff-bg p-3 text-xs text-modiff-subtle-text",fullWidth:!0,onClick:()=>c.current?.click(),tone:"secondary",title:"Import local media. Assets are persisted through the backend when connected.",children:"Import assets"}):v.map((e,t)=>(0,X.jsx)(W,{"data-testid":`left-imported-asset-${t}`,title:e.name,"aria-label":`Use imported asset ${e.name}`,className:"aspect-square h-auto min-h-0 overflow-hidden bg-modiff-bg !p-0",onClick:()=>r(e),tone:"secondary",children:"image"===e.displayType?(0,X.jsx)("img",{src:e.url,alt:"",className:"h-full w-full object-cover"}):(0,X.jsx)("span",{className:"grid h-full w-full place-items-center text-hf-yellow",children:xe(e.displayType)})},e.id))}),(0,X.jsx)(ee,{onClick:()=>e(!0),children:(0,X.jsxs)("div",{className:"flex items-center justify-between gap-2 font-semibold",children:[(0,X.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,X.jsx)(l,{size:16,className:"text-hf-yellow"}),(0,X.jsx)("span",{className:"truncate",children:"Open gallery"})]}),(0,X.jsx)(ae,{tone:"default",children:g.length})]})})]})})}function be(){let e=A(e=>e.hfCache),t=A(e=>e.localModels),s=A(e=>e.modelCacheDiagnostics),l=A(e=>e.hfDownloadProgress),n=A(e=>e.nodesRegistry),d=A(e=>e.studioModelCapabilities),o=A(e=>e.studioModelCapabilitiesAuthoritative),c=A(e=>e.installHfModel),m=A(e=>e.fetchRuntimeStatus),f=A(e=>e.fetchHfCache),p=A(e=>e.fetchLocalModels),x=A(e=>e.fetchModelCacheDiagnostics),h=T(e=>e.setModelManagerOpener),g=T(e=>e.setRightPanelOpen),N=T(e=>e.setRightPanelTab),_=R(e=>e.form),S=R(e=>e.activeTemplateId),L=R(e=>e.graphBinding),E=R(e=>e.sourceOutputId),K=R(e=>e.autoResourcePlans),q=R(e=>e.setAutoResourcePlans),H=v(e=>e.sid),U=z(e=>e.addNode),G=z(e=>e.viewport),J=z(e=>e.nodes.length),[F,ee]=(0,Q.useState)(new Set);(0,Q.useEffect)(()=>{m(),f(!1),p(!1),x(!1)},[f,p,x,m]);let te=!!(S||L||E),se=I[_.modelType],ae=(0,Q.useMemo)(()=>b({currentModelType:te?_.modelType:null,includeWorkflowOnly:!0}),[_.modelType,te]),ne=(0,Q.useMemo)(()=>ae.map(e=>j(e.modelType)),[ae]),ce=(0,Q.useMemo)(()=>ne.map(e=>C(e)),[ne]),xe=(0,Q.useMemo)(()=>function(e){let t=new Set;return e.filter(({item:e,source:s})=>{let a=`${s}:${le(e).toLowerCase()}`;return!t.has(a)&&(t.add(a),!0)})}([...e.map(e=>({source:"HF",item:e})),...t.map(e=>({source:"Local",item:e}))]),[e,t]),he=(0,Q.useMemo)(()=>pe(xe,({item:e})=>de(e)),[xe]),ge=(0,Q.useMemo)(()=>Object.values(l).filter(e=>D(e)||O(e)||B(e)).sort((e,t)=>(t.updated_at??t.completed_at??t.started_at??0)-(e.updated_at??e.completed_at??e.started_at??0)),[l]),be=(0,Q.useMemo)(()=>ae.filter(a=>!k(a,e,t,s).runnable),[e,t,s,ae]),je=(0,Q.useMemo)(()=>pe(be,e=>re(e.surfaceCategory)),[be]),ve=he,Ne=te&&se?M({form:_,autoResourcePlan:K[C(_)],hfCache:e,localModels:t,modelCacheDiagnostics:s,includePipeline:!1}):[];(0,Q.useEffect)(()=>{if(0===ne.length)return;let e=!1;return P(ne,ce).then(t=>{if(e)return;let s={};t.forEach((e,t)=>{let a=e.planKey??ce[t];a&&(s[a]=e)}),q(s)}).catch(e=>{console.error(e)}),()=>{e=!0}},[q,ne,ce]);let we=()=>{g(!0),N("setup")},ke=(0,Q.useCallback)(e=>{ee(t=>{let s=new Set(t);return s.has(e)?s.delete(e):s.add(e),s})},[]),Ce=async e=>{try{await c(e.repo,H,{repair:e.repair,revision:e.revision,files:e.files})}catch(e){console.error(e)}},Ie=(0,Q.useCallback)((e,t)=>{let s=le(e),a=me(e,d,o),l=function(e,t){let s=e.zoom||1,a=t%6*36;return{x:(220-e.x+a)/s,y:(140-e.y+a)/s}}(G,J),i=a.map(e=>({candidate:e,node:V(e.key,n,l)})).find(({candidate:e,node:t})=>t&&(ue(t,oe)||ue(t,["model_type"]))&&(!e.identityKey||ue(t,[e.identityKey]))),r=i?.node;if(!r)return void w("No matching loader node is available yet.",{variant:"error",autoHideDuration:2800});let c=i.candidate.identityKey;c&&i.candidate.identity&&fe(r,c,i.candidate.identity);let m=ue(r,oe);m&&fe(r,m,function(e,t,s,a){let l=t.data.params[s],i=l?.value??l?.default;return"modelselect"===l?.display||i&&"object"==typeof i&&!Array.isArray(i)?{source:"Local"===e?"local":"hub",value:a}:a}(t,r,m,s)),U({...r,selected:!0}),w(`Added ${ie(e)} loader`,{variant:"success",autoHideDuration:1800})},[U,J,n,d,o,G]);return(0,X.jsx)(Y,{title:"Models",meta:`${xe.length} installed, ${be.length} available`,icon:(0,X.jsx)(r,{size:18}),action:(0,X.jsx)(W,{className:"h-8 px-2 text-xs","data-testid":"left-open-model-manager",icon:(0,X.jsx)(i,{size:14}),onClick:()=>h({nodeId:null,fieldKey:null}),tone:"primary",children:"Models"}),children:(0,X.jsxs)("div",{className:"grid gap-2",children:[ge.length>0?(0,X.jsxs)("section",{className:"grid gap-1","data-testid":"left-model-downloads",children:[(0,X.jsxs)("div",{className:"flex items-center justify-between gap-2 px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,X.jsx)("span",{children:"Downloads"}),(0,X.jsxs)("span",{children:[ge.filter(D).length," active"]})]}),ge.slice(0,3).map(e=>(0,X.jsx)($,{compact:!0,progress:e,showTechnicalDetails:!1,testId:`left-model-download-${e.repo_id}`},e.repo_id)),ge.length>3?(0,X.jsxs)(W,{tone:"ghost",onClick:()=>h({nodeId:null,fieldKey:null}),children:["View all ",ge.length," downloads"]}):null]}):null,te&&se?(0,X.jsxs)("section",{className:"grid gap-1","data-testid":"left-model-current",children:[(0,X.jsx)("div",{className:"flex items-center justify-between gap-2 px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:(0,X.jsx)("span",{children:"Current"})}),(0,X.jsx)(ye,{autoPlan:K[C(_)],installProgress:l,onInstall:Ce,onOpenSetup:we,profile:se,status:k(se,e,t,s)}),Ne.map(e=>(0,X.jsx)(Z,{actionTestId:`left-install-requirement-${e.id}`,compact:!0,installProgress:l,onInstall:Ce,onUseLocal:()=>h({nodeId:null,fieldKey:null,focus:{repo:e.repo,requirementId:e.id,label:e.label,source:"workflow"}}),requirement:e,testId:`left-model-requirement-${e.id}`},e.id))]}):null,(0,X.jsxs)("section",{className:"grid gap-1","data-testid":"left-model-installed",children:[(0,X.jsxs)("div",{className:"px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:["Installed (",xe.length,")"]}),0===xe.length||0===ve.length?(0,X.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:"No visible model artifacts indexed yet."}):ve.map(([e,t])=>{let s=`installed:${e}`,l=F.has(s);return(0,X.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg",children:[(0,X.jsxs)(W,{align:"left",className:"min-h-9 justify-between rounded-none border-0 bg-transparent px-2",fullWidth:!0,tone:"ghost",onClick:()=>ke(s),children:[(0,X.jsx)("span",{className:"truncate",children:e}),(0,X.jsxs)("span",{className:"flex shrink-0 items-center gap-2 text-xs text-modiff-subtle-text",children:[t.length,(0,X.jsx)(u,{size:15,className:y("transition-transform",l&&"rotate-180")})]})]}),l?(0,X.jsx)("div",{className:"grid gap-1 border-t border-modiff-border p-1",children:t.map(({item:e,source:t})=>(0,X.jsxs)(W,{align:"left",fullWidth:!0,title:le(e),onClick:()=>Ie(e,t),className:"min-h-10 justify-between bg-modiff-bg p-2 font-normal",tone:"secondary",children:[(0,X.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,X.jsx)(a,{size:15,className:"shrink-0 text-modiff-subtle-text"}),(0,X.jsx)("span",{className:"min-w-0 truncate text-sm font-semibold text-modiff-text",children:ie(e)})]}),(0,X.jsx)("span",{className:"shrink-0 text-xs font-semibold text-modiff-subtle-text",children:t})]},`${t}-${le(e)}`))}):null]},e)})]}),be.length>0?(0,X.jsxs)("section",{className:"grid gap-1","data-testid":"left-model-supported",children:[(0,X.jsxs)("div",{className:"px-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:["Available (",be.length,")"]}),je.map(([a,i])=>{let n=`available:${a}`,r=F.has(n);return(0,X.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg",children:[(0,X.jsxs)(W,{align:"left",className:"min-h-9 justify-between rounded-none border-0 bg-transparent px-2",fullWidth:!0,onClick:()=>ke(n),tone:"ghost",children:[(0,X.jsx)("span",{className:"truncate",children:a}),(0,X.jsxs)("span",{className:"flex shrink-0 items-center gap-2 text-xs text-modiff-subtle-text",children:[i.length,(0,X.jsx)(u,{size:15,className:y("transition-transform",r&&"rotate-180")})]})]}),r?(0,X.jsx)("div",{className:"grid gap-1 border-t border-modiff-border p-1",children:i.map(a=>{let i=C(j(a.modelType));return(0,X.jsx)(ye,{autoPlan:K[i],installProgress:l,onInstall:Ce,onOpenSetup:we,profile:a,status:k(a,e,t,s)},a.modelType)})}):null]},a)})]}):null]})})}function ye({autoPlan:e,installProgress:t,onInstall:s,onOpenSetup:a,profile:l,status:i}){let n=N(e)??{repo:l.defaultRepo,label:l.label,actionLabel:"Install"},r=t[n.repo],d=g(e),o="queued"===r?.status||"running"===r?.status||"downloading"===r?.status,c="unsuitable"!==d.state||i.runnable?"warning":"error",m=o?"Installing":n.actionLabel??"Install",u=n.reason||`Install ${n.repo}`;return(0,X.jsx)("div",{"data-testid":`left-model-${l.modelType}`,className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:(0,X.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,X.jsxs)(W,{align:"left",className:"h-auto min-h-0 min-w-0 flex-1 bg-transparent p-0 font-normal",tone:"ghost",title:`${l.defaultRepo} | ${d.summary} | ${d.detail}`,onClick:a,children:[(0,X.jsx)("div",{className:"truncate text-sm font-semibold text-modiff-text",children:l.label}),(0,X.jsx)("div",{className:"mt-0.5 truncate text-xs text-modiff-subtle-text",children:l.family})]}),!i.runnable&&(0,X.jsx)(ae,{tone:c,testId:`left-install-${l.modelType}`,title:u,disabled:o,progress:o?U(r):null,onClick:()=>{s(n)},children:m})]})})}export{ge as AssetsLibraryPanel,be as ModelsLibraryPanel,he as TemplateLibraryPanel}; \ No newline at end of file diff --git a/web/assets/LegacyOperationOwnerControls.js b/web/assets/LegacyOperationOwnerControls.js new file mode 100644 index 00000000..2d77ca8b --- /dev/null +++ b/web/assets/LegacyOperationOwnerControls.js @@ -0,0 +1 @@ +import{r}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as o,dr as e}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Vr as t}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n}from"./operationLegacyBlockChange.js?v=cc046c26d6fe7aa4";import{BlockOwnerChoices as s}from"./OperationOwnerControls.js?v=cc046c26d6fe7aa4";var a=r(e(),1),i=o();function d({nodeId:r}){let o=t(r=>r.nodes),e=t(r=>r.edges),d=(0,a.useMemo)(()=>{try{return{root:n({nodes:o,edges:e},r).root}}catch(r){return{error:r instanceof Error?r.message:String(r)}}},[o,e,r]);return d.root?(0,i.jsx)(s,{node:d.root,ownerBlockId:r,inline:!0}):(0,i.jsx)("p",{role:"alert",children:d.error})}export{d as default}; \ No newline at end of file diff --git a/web/assets/LegacyUserBlockNode.js b/web/assets/LegacyUserBlockNode.js index e42ae189..e0885fbe 100644 --- a/web/assets/LegacyUserBlockNode.js +++ b/web/assets/LegacyUserBlockNode.js @@ -1 +1 @@ -import{Kr as e,L as t,Wr as a,Zr as o,ai as r,ii as i,kr as s,ui as n}from"./studio-templates.js?v=5bcd315505f5670a";import{a as l}from"./block-composition-tools.js?v=5bcd315505f5670a";function d(e){let t=s.getState(),a=new Set(t.nodes.filter(t=>t.id===e||t.data.userBlockInstanceId===e).map(e=>e.id));return JSON.stringify({nodes:t.nodes.filter(e=>a.has(e.id)).map(e=>[e.id,e.parentId,e.position,e.data.params,e.data.userBlockSnapshot]).sort(([e],[t])=>String(e).localeCompare(String(t))),edges:t.edges.filter(e=>a.has(e.source)||a.has(e.target)).map(e=>[e.id,e.source,e.sourceHandle,e.target,e.targetHandle]).sort(([e],[t])=>String(e).localeCompare(String(t)))})}async function c({instanceId:l,choice:c,workflowTitle:f,context:g=r()},p=e=>a.getState().saveBlock(e)){let u=o(s.getState(),l,a.getState().blocks);if(!u)throw Error("The current User Node definition could not be captured.");let h=d(l),m="new"===c?e(u,t(u.name,f)):u,w="workflow"===c?m:await p(m);if(i(g,{includeForm:!1}),d(l)!==h)throw Error("The User Node changed while its reusable definition was saving. Retry the operation.");let S=s.getState();if(!S.nodes.some(e=>e.id===l&&"block"===e.data.type))throw Error("The User Node instance is no longer available in this workflow.");return S.applyUserBlockDefinition(l,w),n.getState().saveActiveWorkflowTab(!0),w}export{l as default,c as t}; \ No newline at end of file +import{Ei as e,Ti as t,V as a,Vr as o,ji as r,li as i,mi as s,si as n}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{a as l}from"./block-composition-tools.js?v=cc046c26d6fe7aa4";function d(e){let t=o.getState(),a=new Set(t.nodes.filter(t=>t.id===e||t.data.userBlockInstanceId===e).map(e=>e.id));return JSON.stringify({nodes:t.nodes.filter(e=>a.has(e.id)).map(e=>[e.id,e.parentId,e.position,e.data.params,e.data.userBlockSnapshot]).sort(([e],[t])=>String(e).localeCompare(String(t))),edges:t.edges.filter(e=>a.has(e.source)||a.has(e.target)).map(e=>[e.id,e.source,e.sourceHandle,e.target,e.targetHandle]).sort(([e],[t])=>String(e).localeCompare(String(t)))})}async function c({instanceId:l,choice:c,workflowTitle:f,context:g=e()},p=e=>n.getState().saveBlock(e)){let m=s(o.getState(),l,n.getState().blocks);if(!m)throw Error("The current User Node definition could not be captured.");let u=d(l),h="new"===c?i(m,a(m.name,f)):m,w="workflow"===c?h:await p(h);if(t(g,{includeForm:!1}),d(l)!==u)throw Error("The User Node changed while its reusable definition was saving. Retry the operation.");let S=o.getState();if(!S.nodes.some(e=>e.id===l&&"block"===e.data.type))throw Error("The User Node instance is no longer available in this workflow.");return S.applyUserBlockDefinition(l,w),r.getState().saveActiveWorkflowTab(!0),w}export{l as default,c as t}; \ No newline at end of file diff --git a/web/assets/LightboxDialog.js b/web/assets/LightboxDialog.js index 9fcdd02f..e772ad28 100644 --- a/web/assets/LightboxDialog.js +++ b/web/assets/LightboxDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{E as o,Mn as t,cn as a,gn as n,jn as r,pn as l,pr as i,ur as s}from"./graph-vendor.js?v=5bcd315505f5670a";import{Gt as d,Sn as f,Ua as m,Ut as c,Wt as u,gn as g,nt as h,ta as p,ut as x}from"./studio-templates.js?v=5bcd315505f5670a";var b=e(i(),1),w=s();function v({opener:e,onClose:i}){let s=p(e=>e.setMediaExportOpener),d=(0,b.useMemo)(()=>(e&&!Array.isArray(e.images)?[e.images]:e?.images||[]).filter(e=>""!==e&&null!=e),[e]),v=(0,b.useMemo)(()=>x({value:d,artifacts:e?.artifacts,dataType:e?.dataType??"url",mimeType:e?.mimeType||"image/webp"}),[e?.artifacts,e?.dataType,e?.mimeType,d]),k=e?Math.min(Math.max(e.currentIndex,0),Math.max(v.length-1,0)):0,[C,M]=(0,b.useState)(k),[E,L]=(0,b.useState)({}),[N,T]=(0,b.useState)(50),[$,z]=(0,b.useState)(!1),D=(0,b.useRef)(null);(0,b.useEffect)(()=>{M(k)},[k]),(0,b.useEffect)(()=>{let e=()=>z(!1);return window.addEventListener("mouseup",e),window.addEventListener("touchend",e),()=>{window.removeEventListener("mouseup",e),window.removeEventListener("touchend",e)}},[]);let P=(0,b.useCallback)(e=>{let o=D.current?.getBoundingClientRect();if(!o?.width)return;let t=Math.max(0,Math.min(e-o.left,o.width));T(t/o.width*100)},[]);(0,b.useEffect)(()=>{if(!e)return;let o=e=>{"ArrowLeft"===e.key?(e.preventDefault(),M(e=>(e-1+v.length)%v.length)):"ArrowRight"===e.key&&(e.preventDefault(),M(e=>(e+1)%v.length))};return document.addEventListener("keydown",o),()=>document.removeEventListener("keydown",o)},[v.length,i,e]);let S=e=>{e.currentTarget.src=h};if(!e||0===v.length)return null;let I=v[C]??v[0];return I?(0,w.jsx)(u,{open:!0,onClose:i,title:e.comparison?"A/B image comparison":v.length>1?`Image ${C+1} of ${v.length}`:"Image preview",panelClassName:"flex h-[96vh] max-h-none max-w-[96vw] flex-col bg-modiff-media-backdrop/85",bodyClassName:"!max-h-none min-h-0 flex-1 !p-0",children:(0,w.jsxs)("div",{className:"flex h-full min-h-0 flex-col",children:[(0,w.jsxs)("div",{className:"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden px-4 py-8",onMouseDown:e=>{e.target===e.currentTarget&&i()},children:[e.comparison?(0,w.jsx)(g,{ref:D,imageFrom:e.comparison.beforeUrl,imageTo:I.url,beforeLabel:e.comparison.beforeLabel,afterLabel:e.comparison.afterLabel,sliderPosition:N,onSliderPositionChange:T,onError:S,className:"max-h-full max-w-full border-[16px] border-modiff-bg bg-modiff-bg",onMouseDown:e=>{e.stopPropagation(),z(!0),P(e.clientX)},onMouseMove:e=>{$&&P(e.clientX)},onMouseUp:()=>z(!1),onTouchStart:e=>{e.stopPropagation(),z(!0),P(e.touches[0]?.clientX??0)},onTouchMove:e=>{$&&P(e.touches[0]?.clientX??0)},onTouchEnd:()=>z(!1)}):(0,w.jsx)("img",{src:I.url,alt:`Image ${C+1}`,className:"modiff-generated-image block max-h-full max-w-full border-[16px] border-modiff-bg bg-modiff-bg object-contain",onClick:e=>e.stopPropagation(),onMouseDown:e=>e.stopPropagation(),onError:S,onLoad:e=>{let{naturalWidth:o,naturalHeight:t}=e.currentTarget;o&&t&&L(e=>({...e,[C]:`${o}x${t}`}))}}),(0,w.jsxs)("div",{className:"nodrag absolute right-4 top-4 flex gap-1 rounded-modiff-compact border border-modiff-border bg-modiff-panel/90 p-1 text-modiff-text shadow-modiff-panel",children:[(0,w.jsx)(j,{label:"Download image as…",onClick:()=>{s({source:I.url,filename:I.filename||"MoDiff-image.png",kind:"image",defaultFormat:"png",title:"Download image"})},children:(0,w.jsx)(l,{size:18})}),(0,w.jsx)(j,{label:"Copy image URL",onClick:()=>{(async()=>{try{await navigator.clipboard.writeText(I.url),m("Image URL copied",{variant:"success",autoHideDuration:1600})}catch{m("Could not copy image URL",{variant:"error",autoHideDuration:2400})}})()},children:(0,w.jsx)(n,{size:18})}),(0,w.jsx)(j,{label:"Open full size",onClick:()=>window.open(I.url,"_blank","noopener,noreferrer"),children:(0,w.jsx)(a,{size:18})}),(0,w.jsx)(j,{label:"Close preview",onClick:i,children:(0,w.jsx)(o,{size:20})})]}),v.length>1&&(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(y,{label:"Previous image",side:"left",onClick:()=>M(e=>(e-1+v.length)%v.length),children:(0,w.jsx)(t,{size:28})}),(0,w.jsx)(y,{label:"Next image",side:"right",onClick:()=>M(e=>(e+1)%v.length),children:(0,w.jsx)(r,{size:28})})]}),(0,w.jsxs)("div",{className:"pointer-events-none absolute bottom-4 left-1/2 -translate-x-1/2 rounded-modiff-compact border border-modiff-border bg-modiff-panel/90 px-3 py-1 text-sm font-semibold text-modiff-subtle-text shadow-modiff-panel",children:[v.length>1?`${C+1}/${v.length}`:"Image",E[C]?` | ${E[C]}`:""]})]}),v.length>1&&(0,w.jsx)("div",{className:"flex h-32 w-full flex-none items-center justify-center gap-2 overflow-x-auto overflow-y-hidden bg-modiff-panel/90 p-3",children:v.map((e,o)=>(0,w.jsx)(c,{tone:"ghost","aria-label":`Show image ${o+1}`,onClick:()=>M(o),className:f("h-full flex-none border-2 border-transparent p-0",C===o&&"border-hf-yellow"),children:(0,w.jsx)("img",{src:e.url,alt:`Image ${o+1}`,className:"modiff-generated-image block h-full w-auto object-contain",onError:S})},`${e.url}-${o}`))})]})}):null}function j({label:e,onClick:o,children:t}){return(0,w.jsx)(d,{label:e,size:"prominent",className:"text-modiff-subtle-text hover:bg-modiff-surface-hover hover:text-hf-yellow",onClick:e=>{e.stopPropagation(),o()},children:t})}function y({label:e,side:o,onClick:t,children:a}){return(0,w.jsx)(d,{label:e,size:"prominent",className:f("nodrag absolute top-1/2 size-12 -translate-y-1/2 border border-modiff-border bg-modiff-panel/80 text-modiff-subtle-text shadow-modiff-panel hover:bg-modiff-surface-hover hover:text-hf-yellow","left"===o?"left-4":"right-4"),onClick:e=>{e.stopPropagation(),t()},children:a})}export{v as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Dn as o,E as t,On as a,an as n,cr as r,dr as l,pn as i,un as s}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{An as d,In as f,Mo as m,Sa as c,an as u,ht as h,in as g,rn as p,wt as x}from"./studio-templates.js?v=cc046c26d6fe7aa4";var b=e(l(),1),w=r();function v({opener:e,onClose:r}){let l=c(e=>e.setMediaExportOpener),u=(0,b.useMemo)(()=>(e&&!Array.isArray(e.images)?[e.images]:e?.images||[]).filter(e=>""!==e&&null!=e),[e]),v=(0,b.useMemo)(()=>x({value:u,artifacts:e?.artifacts,dataType:e?.dataType??"url",mimeType:e?.mimeType||"image/webp"}),[e?.artifacts,e?.dataType,e?.mimeType,u]),k=e?Math.min(Math.max(e.currentIndex,0),Math.max(v.length-1,0)):0,[C,M]=(0,b.useState)(k),[E,L]=(0,b.useState)({}),[N,T]=(0,b.useState)(50),[$,D]=(0,b.useState)(!1),z=(0,b.useRef)(null);(0,b.useEffect)(()=>{M(k)},[k]),(0,b.useEffect)(()=>{let e=()=>D(!1);return window.addEventListener("mouseup",e),window.addEventListener("touchend",e),()=>{window.removeEventListener("mouseup",e),window.removeEventListener("touchend",e)}},[]);let P=(0,b.useCallback)(e=>{let o=z.current?.getBoundingClientRect();if(!o?.width)return;let t=Math.max(0,Math.min(e-o.left,o.width));T(t/o.width*100)},[]);(0,b.useEffect)(()=>{if(!e)return;let o=e=>{"ArrowLeft"===e.key?(e.preventDefault(),M(e=>(e-1+v.length)%v.length)):"ArrowRight"===e.key&&(e.preventDefault(),M(e=>(e+1)%v.length))};return document.addEventListener("keydown",o),()=>document.removeEventListener("keydown",o)},[v.length,r,e]);let I=e=>{e.currentTarget.src=h};if(!e||0===v.length)return null;let S=v[C]??v[0];return S?(0,w.jsx)(g,{open:!0,onClose:r,title:e.comparison?"A/B image comparison":v.length>1?`Image ${C+1} of ${v.length}`:"Image preview",panelClassName:"flex h-[96vh] max-h-none max-w-[96vw] flex-col bg-modiff-media-backdrop/85",bodyClassName:"!max-h-none min-h-0 flex-1 !p-0",children:(0,w.jsxs)("div",{className:"flex h-full min-h-0 flex-col",children:[(0,w.jsxs)("div",{className:"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden px-4 py-8",onMouseDown:e=>{e.target===e.currentTarget&&r()},children:[e.comparison?(0,w.jsx)(d,{ref:z,imageFrom:e.comparison.beforeUrl,imageTo:S.url,beforeLabel:e.comparison.beforeLabel,afterLabel:e.comparison.afterLabel,sliderPosition:N,onSliderPositionChange:T,onError:I,className:"max-h-full max-w-full border-[16px] border-modiff-bg bg-modiff-bg",onMouseDown:e=>{e.stopPropagation(),D(!0),P(e.clientX)},onMouseMove:e=>{$&&P(e.clientX)},onMouseUp:()=>D(!1),onTouchStart:e=>{e.stopPropagation(),D(!0),P(e.touches[0]?.clientX??0)},onTouchMove:e=>{$&&P(e.touches[0]?.clientX??0)},onTouchEnd:()=>D(!1)}):(0,w.jsx)("img",{src:S.url,alt:`Image ${C+1}`,className:"modiff-generated-image block max-h-full max-w-full border-[16px] border-modiff-bg bg-modiff-bg object-contain",onClick:e=>e.stopPropagation(),onMouseDown:e=>e.stopPropagation(),onError:I,onLoad:e=>{let{naturalWidth:o,naturalHeight:t}=e.currentTarget;o&&t&&L(e=>({...e,[C]:`${o}x${t}`}))}}),(0,w.jsxs)("div",{className:"nodrag absolute right-4 top-4 flex gap-1 rounded-modiff-compact border border-modiff-border bg-modiff-panel/90 p-1 text-modiff-text shadow-modiff-panel",children:[(0,w.jsx)(j,{label:"Download image as…",onClick:()=>{l({source:S.url,filename:S.filename||"MoDiff-image.png",kind:"image",defaultFormat:"png",title:"Download image"})},children:(0,w.jsx)(s,{size:18})}),(0,w.jsx)(j,{label:"Copy image URL",onClick:()=>{(async()=>{try{await navigator.clipboard.writeText(S.url),m("Image URL copied",{variant:"success",autoHideDuration:1600})}catch{m("Could not copy image URL",{variant:"error",autoHideDuration:2400})}})()},children:(0,w.jsx)(i,{size:18})}),(0,w.jsx)(j,{label:"Open full size",onClick:()=>window.open(S.url,"_blank","noopener,noreferrer"),children:(0,w.jsx)(n,{size:18})}),(0,w.jsx)(j,{label:"Close preview",onClick:r,children:(0,w.jsx)(t,{size:20})})]}),v.length>1&&(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(y,{label:"Previous image",side:"left",onClick:()=>M(e=>(e-1+v.length)%v.length),children:(0,w.jsx)(a,{size:28})}),(0,w.jsx)(y,{label:"Next image",side:"right",onClick:()=>M(e=>(e+1)%v.length),children:(0,w.jsx)(o,{size:28})})]}),(0,w.jsxs)("div",{className:"pointer-events-none absolute bottom-4 left-1/2 -translate-x-1/2 rounded-modiff-compact border border-modiff-border bg-modiff-panel/90 px-3 py-1 text-sm font-semibold text-modiff-subtle-text shadow-modiff-panel",children:[v.length>1?`${C+1}/${v.length}`:"Image",E[C]?` | ${E[C]}`:""]})]}),v.length>1&&(0,w.jsx)("div",{className:"flex h-32 w-full flex-none items-center justify-center gap-2 overflow-x-auto overflow-y-hidden bg-modiff-panel/90 p-3",children:v.map((e,o)=>(0,w.jsx)(p,{tone:"ghost","aria-label":`Show image ${o+1}`,onClick:()=>M(o),className:f("h-full flex-none border-2 border-transparent p-0",C===o&&"border-hf-yellow"),children:(0,w.jsx)("img",{src:e.url,alt:`Image ${o+1}`,className:"modiff-generated-image block h-full w-auto object-contain",onError:I})},`${e.url}-${o}`))})]})}):null}function j({label:e,onClick:o,children:t}){return(0,w.jsx)(u,{label:e,size:"prominent",className:"text-modiff-subtle-text hover:bg-modiff-surface-hover hover:text-hf-yellow",onClick:e=>{e.stopPropagation(),o()},children:t})}function y({label:e,side:o,onClick:t,children:a}){return(0,w.jsx)(u,{label:e,size:"prominent",className:f("nodrag absolute top-1/2 size-12 -translate-y-1/2 border border-modiff-border bg-modiff-panel/80 text-modiff-subtle-text shadow-modiff-panel hover:bg-modiff-surface-hover hover:text-hf-yellow","left"===o?"left-4":"right-4"),onClick:e=>{e.stopPropagation(),t()},children:a})}export{v as default}; \ No newline at end of file diff --git a/web/assets/LoopNode.js b/web/assets/LoopNode.js index 86e5ec3a..78908cba 100644 --- a/web/assets/LoopNode.js +++ b/web/assets/LoopNode.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{it as a,pr as o,ur as t}from"./graph-vendor.js?v=5bcd315505f5670a";import{Jt as l,Qt as r,bn as s,kr as d,on as n,tn as i}from"./studio-templates.js?v=5bcd315505f5670a";var m=e(o(),1),c=t();function p(e,a){let o=Number(e);return Number.isFinite(o)?o:a}var u=(0,m.memo)(e=>{let o=d(e=>e.setParamWithHistory),t=d(e=>e.nodes).filter(a=>a.parentId===e.id),m=p(e.data.params.iterations?.value,2),u="collection"===e.data.params.iteration_mode?.value?"collection":"count",x=p(e.data.params.max_iterations?.value,100),f=!1!==e.data.params.carry?.value,b=!1!==e.data.params.collect?.value,h=!0===e.data.params.durable?.value,g=p(e.data.params.max_retries?.value,1),v=t.some(e=>"modules.WorkflowControl"===e.data.module&&"LoopResult"===e.data.action),j=t.some(e=>"modules.WorkflowControl"===e.data.module&&"LoopIndex"===e.data.action),N=t.some(e=>"modules.WorkflowControl"===e.data.module&&"LoopItems"===e.data.action);return(0,c.jsxs)("div",{className:"relative h-full w-full rounded-modiff-panel border-2 border-dashed border-hf-yellow/55 bg-hf-yellow/[0.035] shadow-inner",children:[(0,c.jsxs)("div",{className:"drag-handle flex h-11 items-center gap-2 border-b border-hf-yellow/25 bg-modiff-panel/90 px-3 text-sm font-bold text-modiff-text",children:[(0,c.jsx)(a,{size:16,className:"text-hf-yellow"}),(0,c.jsx)("span",{className:"truncate",children:e.data.label||"Loop"}),e.data.progressMessage?(0,c.jsx)("span",{className:"ml-auto truncate text-xs font-medium text-modiff-subtle-text",children:e.data.progressMessage}):null]}),(0,c.jsx)("div",{className:"nodrag nowheel absolute right-2 top-1.5 flex items-center gap-2 text-xs text-modiff-subtle-text",children:(0,c.jsxs)("div",{className:"flex items-center gap-1",children:[(0,c.jsx)(n,{"aria-label":"Loop repeat mode",value:u,onValueChange:a=>o(e.id,"iteration_mode",a),options:[{value:"count",label:"Count"},{value:"collection",label:"Items"}],size:"compact"}),(0,c.jsx)(i,{"aria-label":"Loop iterations",min:1,max:x,value:m,disabled:"collection"===u,onValueChange:a=>o(e.id,"iterations",a??1),controlSize:"compact",className:"nodrag w-16"})]})}),(0,c.jsxs)("div",{className:"nodrag nowheel absolute bottom-2 left-2 flex items-center gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-panel/90 px-2 py-1 text-xs text-modiff-subtle-text",children:[(0,c.jsx)(l,{checked:f,label:"Carry",onCheckedChange:a=>o(e.id,"carry",a)}),(0,c.jsx)(l,{checked:b,label:"Collect",onCheckedChange:a=>o(e.id,"collect",a)}),(0,c.jsx)(l,{checked:h,label:"Resume",onCheckedChange:a=>o(e.id,"durable",a)}),(0,c.jsxs)("span",{children:["Limit ",x]}),(0,c.jsx)(r,{label:"Retries",layout:"inline",className:"gap-1",children:(0,c.jsx)(i,{"aria-label":"Loop retries",min:0,max:10,value:g,onValueChange:a=>o(e.id,"max_retries",a??1),controlSize:"compact",className:"nodrag w-12 px-1"})})]}),v?"collection"!==u||N?j||"count"!==u?null:(0,c.jsx)("div",{className:"pointer-events-none absolute bottom-12 left-2 rounded-modiff-compact border border-modiff-border bg-modiff-panel/90 px-2 py-1 text-xs text-modiff-subtle-text",children:"Add Loop Index when the body needs a changing seed, item, or parameter."}):(0,c.jsx)("div",{className:"pointer-events-none absolute bottom-12 left-2 rounded-modiff-compact border border-hf-orange/40 bg-modiff-panel/90 px-2 py-1 text-xs text-hf-orange",children:"Add one Loop Items node and connect its collection."}):(0,c.jsx)("div",{className:"pointer-events-none absolute bottom-12 left-2 rounded-modiff-compact border border-hf-orange/40 bg-modiff-panel/90 px-2 py-1 text-xs text-hf-orange",children:"Add one Loop Result node and connect the value to return."}),(0,c.jsx)(s,{})]})});u.displayName="LoopNode";export{u as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as a,dr as o,nt as t}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Pn as l,Vr as r,bn as s,cn as d,fn as n,hn as i}from"./studio-templates.js?v=cc046c26d6fe7aa4";var m=e(o(),1),c=a();function p(e,a){let o=Number(e);return Number.isFinite(o)?o:a}var f=(0,m.memo)(e=>{let a=r(e=>e.setParamWithHistory),o=r(e=>e.nodes).filter(a=>a.parentId===e.id),m=p(e.data.params.iterations?.value,2),f="collection"===e.data.params.iteration_mode?.value?"collection":"count",x=p(e.data.params.max_iterations?.value,100),u=!1!==e.data.params.carry?.value,b=!1!==e.data.params.collect?.value,h=!0===e.data.params.durable?.value,g=p(e.data.params.max_retries?.value,1),v=o.some(e=>"modules.WorkflowControl"===e.data.module&&"LoopResult"===e.data.action),j=o.some(e=>"modules.WorkflowControl"===e.data.module&&"LoopIndex"===e.data.action),N=o.some(e=>"modules.WorkflowControl"===e.data.module&&"LoopItems"===e.data.action);return(0,c.jsxs)("div",{className:"relative h-full w-full rounded-modiff-panel border-2 border-dashed border-hf-yellow/55 bg-hf-yellow/[0.035] shadow-inner",children:[(0,c.jsxs)("div",{className:"drag-handle flex h-11 items-center gap-2 border-b border-hf-yellow/25 bg-modiff-panel/90 px-3 text-sm font-bold text-modiff-text",children:[(0,c.jsx)(t,{size:16,className:"text-hf-yellow"}),(0,c.jsx)("span",{className:"truncate",children:e.data.label||"Loop"}),e.data.progressMessage?(0,c.jsx)("span",{className:"ml-auto truncate text-xs font-medium text-modiff-subtle-text",children:e.data.progressMessage}):null]}),(0,c.jsx)("div",{className:"nodrag nowheel absolute right-2 top-1.5 flex items-center gap-2 text-xs text-modiff-subtle-text",children:(0,c.jsxs)("div",{className:"flex items-center gap-1",children:[(0,c.jsx)(s,{"aria-label":"Loop repeat mode",value:f,onValueChange:o=>a(e.id,"iteration_mode",o),options:[{value:"count",label:"Count"},{value:"collection",label:"Items"}],size:"compact"}),(0,c.jsx)(i,{"aria-label":"Loop iterations",min:1,max:x,value:m,disabled:"collection"===f,onValueChange:o=>a(e.id,"iterations",o??1),controlSize:"compact",className:"nodrag w-16"})]})}),(0,c.jsxs)("div",{className:"nodrag nowheel absolute bottom-2 left-2 flex items-center gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-panel/90 px-2 py-1 text-xs text-modiff-subtle-text",children:[(0,c.jsx)(d,{checked:u,label:"Carry",onCheckedChange:o=>a(e.id,"carry",o)}),(0,c.jsx)(d,{checked:b,label:"Collect",onCheckedChange:o=>a(e.id,"collect",o)}),(0,c.jsx)(d,{checked:h,label:"Resume",onCheckedChange:o=>a(e.id,"durable",o)}),(0,c.jsxs)("span",{children:["Limit ",x]}),(0,c.jsx)(n,{label:"Retries",layout:"inline",className:"gap-1",children:(0,c.jsx)(i,{"aria-label":"Loop retries",min:0,max:10,value:g,onValueChange:o=>a(e.id,"max_retries",o??1),controlSize:"compact",className:"nodrag w-12 px-1"})})]}),v?"collection"!==f||N?j||"count"!==f?null:(0,c.jsx)("div",{className:"pointer-events-none absolute bottom-12 left-2 rounded-modiff-compact border border-modiff-border bg-modiff-panel/90 px-2 py-1 text-xs text-modiff-subtle-text",children:"Add Loop Index when the body needs a changing seed, item, or parameter."}):(0,c.jsx)("div",{className:"pointer-events-none absolute bottom-12 left-2 rounded-modiff-compact border border-hf-orange/40 bg-modiff-panel/90 px-2 py-1 text-xs text-hf-orange",children:"Add one Loop Items node and connect its collection."}):(0,c.jsx)("div",{className:"pointer-events-none absolute bottom-12 left-2 rounded-modiff-compact border border-hf-orange/40 bg-modiff-panel/90 px-2 py-1 text-xs text-hf-orange",children:"Add one Loop Result node and connect the value to return."}),(0,c.jsx)(l,{})]})});f.displayName="LoopNode";export{f as default}; \ No newline at end of file diff --git a/web/assets/MediaAttachmentControls.js b/web/assets/MediaAttachmentControls.js new file mode 100644 index 00000000..f134878b --- /dev/null +++ b/web/assets/MediaAttachmentControls.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as a}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ei as n,Eo as r,Fo as i,Jo as l,Po as o,Si as s,Ti as d,Vr as u,bn as p,dn as c,fn as m,rn as f}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as b,t as h}from"./operationGraphTransaction.js?v=cc046c26d6fe7aa4";import{t as g}from"./operationStarterRequest.js?v=cc046c26d6fe7aa4";import"./operationAuthoring.js?v=cc046c26d6fe7aa4";import{a as j,i as y,n as x,t as S}from"./mediaAttachment.js?v=cc046c26d6fe7aa4";var k=e(a(),1),v=t();function I({node:e,initialSource:t,onAttached:a,inline:I=!1}){let A=s(e=>e.operationContracts),C=s(e=>e.pipelineSupport),O=u(e=>e.nodes),[w,N]=(0,k.useState)(""),[P,E]=(0,k.useState)(t?JSON.stringify([t.nodeId,t.handleId]):""),[R,J]=(0,k.useState)(null),[M,T]=(0,k.useState)(!1),V=(0,k.useRef)(null);(0,k.useEffect)(()=>()=>V.current?.abort(),[]);let q=l(e),F=x(S(A,C),q?.operation.pipelineClass??""),$=F.find(e=>e.key===w)??F.find(e=>e.task===q?.operation.task)??F[0];if(!$||e.parentId&&!e.data.blockProjectionOwnerId)return null;let _=O.flatMap(e=>{let a={...i(e)};if(t?.nodeId===e.id){let n=o(e,t.handleId);n&&(a[t.handleId]=n)}return Object.entries(a).filter(([,e])=>"output"===e.display&&!e.hidden&&!e.disabled&&(Array.isArray(e.type)?e.type:[e.type]).includes($.kind)).map(([t,a])=>({value:JSON.stringify([e.id,t]),label:`${e.data.label} · ${a.label??t}`}))});return(0,v.jsxs)(c,{label:"Add image / audio input",collapsible:!t&&!I,panelClassName:"grid gap-2 py-2",children:[(0,v.jsx)(m,{label:"Use input for",children:(0,v.jsx)(p,{"aria-label":"Media input role",value:$.key,disabled:M,options:F.map(e=>({value:e.key,label:e.label})),onValueChange:e=>{N(e),J(null)}})}),(0,v.jsx)(m,{label:"Source",children:(0,v.jsx)(p,{"aria-label":"Media input source",value:_.some(e=>e.value===P)?P:"",disabled:M,options:[{value:"",label:"Add Load "+("image"===$.kind?"Image":"Audio")},..._],onValueChange:E})}),(0,v.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Reuses this model and adds only the required encoding stages. Existing prompts and custom branches are retained."}),(0,v.jsx)(f,{tone:"primary",disabled:M,onClick:()=>{!async function(){if(!$||V.current)return;let t=new AbortController;V.current=t;let i=n(),l=u.getState().toObject(),o=b(l);T(!0),J(null);try{if(P&&!_.some(e=>e.value===P))throw Error("The selected source is no longer compatible. Choose a source for this input role.");let n=e.data.params.execution_profile_id?.value,r=await g(q.operation.pipelineClass,$.task,A,t.signal,"string"==typeof n&&n?n:void 0);if(t.signal.aborted)return;d(i,{includeForm:!1});let u=P&&_.some(e=>e.value===P)?JSON.parse(P):null,p=u?{nodeId:u[0],handleId:u[1]}:void 0;h(e.data.blockProjectionOwnerId?y(l,e.data.blockProjectionOwnerId,e.data.blockProjectionNodeId,r,$,s.getState().nodesRegistry,p):j(l,e.id,r,$,s.getState().nodesRegistry,p),i,o,"Attach media input"),a?.()}catch(e){t.signal.aborted||J(r(e,"Could not attach this input."))}finally{V.current===t&&(V.current=null,T(!1))}}()},children:M?"Preparing input…":"Attach input"}),R?(0,v.jsx)("p",{role:"alert",className:"text-xs text-modiff-red",children:R}):null]})}export{I as t}; \ No newline at end of file diff --git a/web/assets/MediaAttachmentDialog.js b/web/assets/MediaAttachmentDialog.js new file mode 100644 index 00000000..41a83651 --- /dev/null +++ b/web/assets/MediaAttachmentDialog.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as a}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{bn as s,fn as o,in as i}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as l}from"./MediaAttachmentControls.js?v=cc046c26d6fe7aa4";var n=e(a(),1),r=t();function d({owners:e,source:t,onClose:a}){let[d,u]=(0,n.useState)(e[0]?.id??""),h=e.find(e=>e.id===d);return(0,r.jsx)(i,{open:!0,title:"Attach media input",onClose:a,children:(0,r.jsxs)("div",{className:"grid gap-3",children:[(0,r.jsx)("p",{className:"text-sm text-modiff-subtle-text",children:"Choose how this workflow should use the input. Required stages are added to the existing graph."}),e.length>1?(0,r.jsx)(o,{label:"Operation",children:(0,r.jsx)(s,{"aria-label":"Media attachment operation",value:d,onValueChange:u,options:e.map(e=>({value:e.id,label:e.data.label||e.id}))})}):null,h?(0,r.jsx)(l,{node:h,initialSource:t,onAttached:a},h.id):null]})})}export{d as default}; \ No newline at end of file diff --git a/web/assets/MediaExportDialog.js b/web/assets/MediaExportDialog.js index b4aa37a9..2d85848e 100644 --- a/web/assets/MediaExportDialog.js +++ b/web/assets/MediaExportDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{pn as a,pr as t,ur as r}from"./graph-vendor.js?v=5bcd315505f5670a";import{Lo as i,Qt as l,Ua as o,Ut as n,Wt as s,no as d,on as u,ro as p,ta as m,to as f}from"./studio-templates.js?v=5bcd315505f5670a";var c=e(t(),1),h=null;function v(e){return"object"==typeof e&&e&&!Array.isArray(e)?e:null}function g(e,a){let t=e[a];if("string"!=typeof t||!t.trim())throw Error(`MoDiff returned an invalid media capability ${a}.`);return t}function b(e){if("string"!=typeof e||!/^\.[A-Za-z0-9]{1,10}$/.test(e))throw Error("MoDiff returned an invalid media file extension.");return e.toLowerCase()}function x(e){let a=v(e);if(!a)throw Error("MoDiff returned an invalid media export format.");let t=g(a,"mimeType");if(!/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$/.test(t))throw Error("MoDiff returned an invalid media MIME type.");let r=a.preset;if(void 0!==r&&("string"!=typeof r||!r.trim()))throw Error("MoDiff returned an invalid media export preset.");let i=a.sampleRates;if(void 0!==i&&(!Array.isArray(i)||i.some(e=>"number"!=typeof e||!Number.isInteger(e)||e<=0)))throw Error("MoDiff returned invalid media sample rates.");for(let e of["supportsAlpha","hasAudio"])if(void 0!==a[e]&&"boolean"!=typeof a[e])throw Error(`MoDiff returned an invalid media capability ${e}.`);return{value:g(a,"value"),label:g(a,"label"),extension:b(a.extension),mimeType:t,preset:r,sampleRates:i,supportsAlpha:a.supportsAlpha,hasAudio:a.hasAudio}}function w(e){let a=v(e),t=a?v(a.media):null;if(!a||1!==a.version||!t)throw Error("MoDiff returned an unsupported media capability contract.");return{version:1,media:Object.fromEntries(["image","video","audio"].map(e=>{let a=v(t[e]);if(!a||!Array.isArray(a.importExtensions)||!Array.isArray(a.exportFormats))throw Error(`MoDiff returned invalid ${e} media capabilities.`);let r=a.importExtensions.map(b);if(new Set(r).size!==r.length)throw Error(`MoDiff returned duplicate ${e} import extensions.`);let i=a.exportFormats.map(x);if(new Set(i.map(e=>e.value)).size!==i.length)throw Error(`MoDiff returned duplicate ${e} export formats.`);return[e,{importExtensions:r,exportFormats:i}]}))}}function y(){return h||=p(`${i.serverAddress}/media/capabilities`,{parse:w,timeoutMs:1e4}).catch(e=>{throw h=null,e}),h}function j(e){try{return new URL(e,i.serverAddress)}catch{return null}}function S(e){let a=j(e);if(!a)return!1;let t=new URL(i.serverAddress);return a.origin===t.origin&&("/file"===a.pathname||"/stream"===a.pathname||a.pathname.startsWith("/cache/"))}function A(e,a,t){if("original"===t.format)return e;let r=j(e);if(!r)throw Error("This output does not have a downloadable media URL.");let l=new URL(i.serverAddress);if(r.origin!==l.origin)throw Error("Converted downloads are available for MoDiff-managed media. Download this source as Original.");if("/stream"===r.pathname){let e=r.searchParams.get("file");if(!e)throw Error("This video source does not identify its backing file.");r.pathname="/file",r.search="",r.searchParams.set("file",e)}if("/file"!==r.pathname&&!r.pathname.startsWith("/cache/"))throw Error("Converted downloads are not available for this source. Download it as Original.");return function(e,a,t){e.searchParams.set("download_format",t.format),e.searchParams.set("media_kind",a),[["sample_rate",t.sampleRate],["channels",t.channels],["bit_depth",t.bitDepth],["bitrate",t.bitrate],["quality",t.quality],["compression",t.compression],["fps",t.fps],["width",t.width]].forEach(([a,t])=>{void 0!==t&&Number.isFinite(t)&&e.searchParams.set(a,String(t))})}(r,a,t),r.toString()}function $(e,a,t,r){if("original"===r.format||!t){let t=`MoDiff-${a}`,r=e.replace(/\\/g,"/").split("/").filter(Boolean).pop()||t,i=Array.from(r,e=>e.charCodeAt(0)<32?"-":e).join("").replace(/[<>:"/\\|?*]/g,"-").replace(/[. ]+$/,"").trim()||t,l=i.replace(/\.[A-Za-z0-9]{1,10}$/,"");return/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(l)?`_${i}`:i}let i="audio"===a&&r.sampleRate?`-${r.sampleRate,r.sampleRate/1e3}kHz`:"";return`${function(e,a){let t=`MoDiff-${a}`,r=e.replace(/\\/g,"/").split("/").filter(Boolean).pop()||t,i=Array.from(r,e=>e.charCodeAt(0)<32?"-":e).join("").replace(/\.[A-Za-z0-9]{1,8}$/,"").replace(/[<>:"/\\|?*]/g,"-").trim()||t;return/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(i)?`_${i}`:i}(e,a)}${i}${t.extension}`}async function E(e,a,t,r,i){let l=A(e,t,i),o=$(a,t,r,i),n=null;try{let e=function(e,a){let t=window.showSaveFilePicker;return t?t.call(window,{suggestedName:e,types:a?.mimeType&&a.extension?[{description:`${a.label} file`,accept:{[a.mimeType]:[a.extension]}}]:void 0}):null}(o,r);n=e?await e:null}catch(e){if(e instanceof DOMException&&"AbortError"===e.name)return!1;n=null}let s=await d(l,{timeoutMs:36e5});if(n)return await async function(e,a){let t=await a.createWritable();await t.write(e),await t.close()}(s,n),!0;let u=URL.createObjectURL(s);try{let e=document.createElement("a");e.href=u,e.download=o,e.rel="noopener noreferrer",document.body.append(e),e.click(),e.remove()}finally{window.setTimeout(()=>URL.revokeObjectURL(u),1e3)}return!0}var D="modiff.media-export.v1";function M(e){try{let a=JSON.parse(localStorage.getItem(D)||"{}");if(!a||"object"!=typeof a)return{};let t=a[e];return t&&"object"==typeof t?t:{}}catch{return{}}}function k(e,a){try{let t=JSON.parse(localStorage.getItem(D)||"{}"),r=t&&"object"==typeof t?t:{};localStorage.setItem(D,JSON.stringify({...r,[e]:a}))}catch{}}var R=r(),C={value:"original",label:"Original file",extension:"",mimeType:"application/octet-stream",preset:"Original"};function N(){let e=m(e=>e.mediaExportOpener),t=m(e=>e.setMediaExportOpener),[r,i]=(0,c.useState)(null),[d,p]=(0,c.useState)(!1),[h,v]=(0,c.useState)(""),[g,b]=(0,c.useState)("original"),[x,w]=(0,c.useState)(48e3),[j,A]=(0,c.useState)(16),[D,N]=(0,c.useState)(256),[O,z]=(0,c.useState)(90),[F,L]=(0,c.useState)(12),[P,U]=(0,c.useState)(640);(0,c.useEffect)(()=>{if(!e)return;let a=M(e.kind);b(a.format||e.defaultFormat||function(e){return"audio"===e?"wav":"image"===e?"png":"video"===e?"mp4":"original"}(e.kind)),w(e.defaultSampleRate||a.sampleRate||48e3),A(a.bitDepth||16),N(a.bitrate||256),z(a.quality||90),L(a.fps||12),U(a.width||640),v(""),i(null);let t=!0;return y().then(e=>{t&&i(e)}).catch(e=>{t&&v(f(e,"Media export options could not be loaded."))}),()=>{t=!1}},[e]);let I=(0,c.useMemo)(()=>e?[C,...r&&S(e.source)?r.media[e.kind].exportFormats:[]]:[],[r,e]),T=I.find(e=>e.value===g)??I.find(e=>"original"===e.value)??null;if((0,c.useEffect)(()=>{!r||0===I.length||I.some(e=>e.value===g)||b(I[0]?.value??"original")},[r,g,I]),(0,c.useEffect)(()=>{!T?.sampleRates?.length||T.sampleRates.includes(x)||w(T.sampleRates[0]??48e3)},[x,T]),!e)return null;let V={format:T?.value??"original",sampleRate:"audio"===e.kind&&"original"!==T?.value?x:void 0,bitDepth:"wav"===T?.value?j:void 0,bitrate:["mp3","m4a","aac","opus"].includes(T?.value??"")?D:void 0,quality:"image"===e.kind&&["jpeg","webp","avif"].includes(T?.value??"")?O:void 0,fps:"gif"===T?.value?F:void 0,width:"gif"===T?.value?P:void 0},_=$(e.filename,e.kind,T,V),Z=()=>{d||t(null)};return(0,R.jsx)(s,{open:!0,onClose:Z,title:e.title||`Download ${e.kind}`,description:"Download the original unchanged, or convert a copy using the available export formats.",dismissible:!d,testId:"media-export-dialog",panelClassName:"max-w-xl",footer:(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(n,{tone:"secondary",disabled:d,onClick:Z,children:"Cancel"}),(0,R.jsxs)(n,{tone:"primary",disabled:!T,loading:d,onClick:()=>{(async()=>{if(T&&!d){p(!0),v("");try{if(!await E(e.source,e.filename,e.kind,T,V))return;k(e.kind,V),t(null),o(`${T.label} download ready`,{variant:"success",autoHideDuration:1800})}catch(e){v(f(e,`Could not create the ${T.label} download.`))}finally{p(!1)}}})()},children:[(0,R.jsx)(a,{size:16}),d?"Preparing…":"Download"]})]}),children:(0,R.jsxs)("div",{className:"grid gap-4",children:[(0,R.jsx)(l,{label:"Format",children:(0,R.jsx)(u,{"aria-label":"Download format",value:T?.value??"",onValueChange:b,options:I.map(e=>({value:e.value,label:e.label,group:e.preset})),placeholder:r?"Select format":"Loading formats…",disabled:d||!r})}),"audio"===e.kind&&T?.sampleRates&&T.sampleRates.length>1?(0,R.jsx)(l,{label:"Sample rate",children:(0,R.jsx)(u,{"aria-label":"Audio sample rate",value:String(x),onValueChange:e=>w(Number(e)),options:T.sampleRates.map(e=>({value:String(e),label:e/1e3+" kHz"}))})}):null,"wav"===T?.value?(0,R.jsx)(l,{label:"Bit depth",children:(0,R.jsx)(u,{"aria-label":"WAV bit depth",value:String(j),onValueChange:e=>A(Number(e)),options:[{value:"16",label:"16-bit PCM"},{value:"24",label:"24-bit PCM"}]})}):null,["mp3","m4a","aac","opus"].includes(T?.value??"")?(0,R.jsx)(l,{label:"Bitrate",children:(0,R.jsx)(u,{"aria-label":"Audio bitrate",value:String(D),onValueChange:e=>N(Number(e)),options:[128,192,256,320].map(e=>({value:String(e),label:`${e} kbps`}))})}):null,"image"===e.kind&&["jpeg","webp","avif"].includes(T?.value??"")?(0,R.jsx)(l,{label:"Quality",children:(0,R.jsx)(u,{"aria-label":"Image export quality",value:String(O),onValueChange:e=>z(Number(e)),options:[{value:"100",label:"Maximum · 100"},{value:"90",label:"High · 90"},{value:"80",label:"Balanced · 80"},{value:"65",label:"Small · 65"}]})}):null,"gif"===T?.value?(0,R.jsxs)("div",{className:"grid grid-cols-2 gap-3",children:[(0,R.jsx)(l,{label:"Frame rate",children:(0,R.jsx)(u,{"aria-label":"GIF frame rate",value:String(F),onValueChange:e=>L(Number(e)),options:[8,12,16,24].map(e=>({value:String(e),label:`${e} fps`}))})}),(0,R.jsx)(l,{label:"Width",children:(0,R.jsx)(u,{"aria-label":"GIF width",value:String(P),onValueChange:e=>U(Number(e)),options:[480,640,960,1280].map(e=>({value:String(e),label:`${e} px`}))})})]}):null,(0,R.jsx)("div",{className:"break-all rounded-modiff-compact border border-modiff-border bg-modiff-bg px-3 py-2 text-modiff-metadata text-modiff-subtle-text",children:_}),(0,R.jsx)("p",{role:"status",className:"text-xs text-modiff-subtle-text",children:d?"Preparing your download. The generated source asset is not changed.":r||h?"original"===T?.value?"Original file: no conversion or quality loss.":"A converted copy is downloaded; the source asset is kept.":"Loading conversion formats… Original download is available."}),h?(0,R.jsx)("div",{role:"alert",className:"rounded-modiff-compact border border-modiff-invalid bg-modiff-invalid/10 px-3 py-2 text-sm text-modiff-invalid",children:h}):null]})})}export{N as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as a,dr as t,un as r}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Do as i,Eo as l,Mo as o,Oo as n,Sa as s,bn as d,fn as u,in as p,oc as m,rn as f}from"./studio-templates.js?v=cc046c26d6fe7aa4";var c=e(t(),1),h=null;function v(e){return"object"==typeof e&&e&&!Array.isArray(e)?e:null}function g(e,a){let t=e[a];if("string"!=typeof t||!t.trim())throw Error(`MoDiff returned an invalid media capability ${a}.`);return t}function b(e){if("string"!=typeof e||!/^\.[A-Za-z0-9]{1,10}$/.test(e))throw Error("MoDiff returned an invalid media file extension.");return e.toLowerCase()}function x(e){let a=v(e);if(!a)throw Error("MoDiff returned an invalid media export format.");let t=g(a,"mimeType");if(!/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$/.test(t))throw Error("MoDiff returned an invalid media MIME type.");let r=a.preset;if(void 0!==r&&("string"!=typeof r||!r.trim()))throw Error("MoDiff returned an invalid media export preset.");let i=a.sampleRates;if(void 0!==i&&(!Array.isArray(i)||i.some(e=>"number"!=typeof e||!Number.isInteger(e)||e<=0)))throw Error("MoDiff returned invalid media sample rates.");for(let e of["supportsAlpha","hasAudio"])if(void 0!==a[e]&&"boolean"!=typeof a[e])throw Error(`MoDiff returned an invalid media capability ${e}.`);return{value:g(a,"value"),label:g(a,"label"),extension:b(a.extension),mimeType:t,preset:r,sampleRates:i,supportsAlpha:a.supportsAlpha,hasAudio:a.hasAudio}}function w(e){let a=v(e),t=a?v(a.media):null;if(!a||1!==a.version||!t)throw Error("MoDiff returned an unsupported media capability contract.");return{version:1,media:Object.fromEntries(["image","video","audio"].map(e=>{let a=v(t[e]);if(!a||!Array.isArray(a.importExtensions)||!Array.isArray(a.exportFormats))throw Error(`MoDiff returned invalid ${e} media capabilities.`);let r=a.importExtensions.map(b);if(new Set(r).size!==r.length)throw Error(`MoDiff returned duplicate ${e} import extensions.`);let i=a.exportFormats.map(x);if(new Set(i.map(e=>e.value)).size!==i.length)throw Error(`MoDiff returned duplicate ${e} export formats.`);return[e,{importExtensions:r,exportFormats:i}]}))}}function y(){return h||=n(`${m.serverAddress}/media/capabilities`,{parse:w,timeoutMs:1e4}).catch(e=>{throw h=null,e}),h}function j(e){try{return new URL(e,m.serverAddress)}catch{return null}}function S(e){let a=j(e);if(!a)return!1;let t=new URL(m.serverAddress);return a.origin===t.origin&&("/file"===a.pathname||"/stream"===a.pathname||a.pathname.startsWith("/cache/"))}function A(e,a,t,r){if("original"===r.format||!t){let t=`MoDiff-${a}`,r=e.replace(/\\/g,"/").split("/").filter(Boolean).pop()||t,i=Array.from(r,e=>e.charCodeAt(0)<32?"-":e).join("").replace(/[<>:"/\\|?*]/g,"-").replace(/[. ]+$/,"").trim()||t,l=i.replace(/\.[A-Za-z0-9]{1,10}$/,"");return/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(l)?`_${i}`:i}let i="audio"===a&&r.sampleRate?`-${r.sampleRate,r.sampleRate/1e3}kHz`:"";return`${function(e,a){let t=`MoDiff-${a}`,r=e.replace(/\\/g,"/").split("/").filter(Boolean).pop()||t,i=Array.from(r,e=>e.charCodeAt(0)<32?"-":e).join("").replace(/\.[A-Za-z0-9]{1,8}$/,"").replace(/[<>:"/\\|?*]/g,"-").trim()||t;return/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(i)?`_${i}`:i}(e,a)}${i}${t.extension}`}async function E(e,a,t,r,l){let o=function(e,a,t){if("original"===t.format)return e;let r=j(e);if(!r)throw Error("This output does not have a downloadable media URL.");let i=new URL(m.serverAddress);if(r.origin!==i.origin)throw Error("Converted downloads are available for MoDiff-managed media. Download this source as Original.");if("/stream"===r.pathname){let e=r.searchParams.get("file");if(!e)throw Error("This video source does not identify its backing file.");r.pathname="/file",r.search="",r.searchParams.set("file",e)}if("/file"!==r.pathname&&!r.pathname.startsWith("/cache/"))throw Error("Converted downloads are not available for this source. Download it as Original.");return function(e,a,t){e.searchParams.set("download_format",t.format),e.searchParams.set("media_kind",a),[["sample_rate",t.sampleRate],["channels",t.channels],["bit_depth",t.bitDepth],["bitrate",t.bitrate],["quality",t.quality],["compression",t.compression],["fps",t.fps],["width",t.width]].forEach(([a,t])=>{void 0!==t&&Number.isFinite(t)&&e.searchParams.set(a,String(t))})}(r,a,t),r.toString()}(e,t,l),n=A(a,t,r,l),s=null;try{let e=function(e,a){let t=window.showSaveFilePicker;return t?t.call(window,{suggestedName:e,types:a?.mimeType&&a.extension?[{description:`${a.label} file`,accept:{[a.mimeType]:[a.extension]}}]:void 0}):null}(n,r);s=e?await e:null}catch(e){if(e instanceof DOMException&&"AbortError"===e.name)return!1;s=null}let d=await i(o,{timeoutMs:36e5});if(s)return await async function(e,a){let t=await a.createWritable();await t.write(e),await t.close()}(d,s),!0;let u=URL.createObjectURL(d);try{let e=document.createElement("a");e.href=u,e.download=n,e.rel="noopener noreferrer",document.body.append(e),e.click(),e.remove()}finally{window.setTimeout(()=>URL.revokeObjectURL(u),1e3)}return!0}var $="modiff.media-export.v1";function D(e){try{let a=JSON.parse(localStorage.getItem($)||"{}");if(!a||"object"!=typeof a)return{};let t=a[e];return t&&"object"==typeof t?t:{}}catch{return{}}}function M(e,a){try{let t=JSON.parse(localStorage.getItem($)||"{}"),r=t&&"object"==typeof t?t:{};localStorage.setItem($,JSON.stringify({...r,[e]:a}))}catch{}}var k=a(),R={value:"original",label:"Original file",extension:"",mimeType:"application/octet-stream",preset:"Original"};function C(){let e=s(e=>e.mediaExportOpener),a=s(e=>e.setMediaExportOpener),[t,i]=(0,c.useState)(null),[n,m]=(0,c.useState)(!1),[h,v]=(0,c.useState)(""),[g,b]=(0,c.useState)("original"),[x,w]=(0,c.useState)(48e3),[j,$]=(0,c.useState)(16),[C,N]=(0,c.useState)(256),[O,z]=(0,c.useState)(90),[F,L]=(0,c.useState)(12),[P,I]=(0,c.useState)(640);(0,c.useEffect)(()=>{if(!e)return;let a=D(e.kind);b(a.format||e.defaultFormat||function(e){return"audio"===e?"wav":"image"===e?"png":"video"===e?"mp4":"original"}(e.kind)),w(e.defaultSampleRate||a.sampleRate||48e3),$(a.bitDepth||16),N(a.bitrate||256),z(a.quality||90),L(a.fps||12),I(a.width||640),v(""),i(null);let t=!0;return y().then(e=>{t&&i(e)}).catch(e=>{t&&v(l(e,"Media export options could not be loaded."))}),()=>{t=!1}},[e]);let T=(0,c.useMemo)(()=>e?[R,...t&&S(e.source)?t.media[e.kind].exportFormats:[]]:[],[t,e]),U=T.find(e=>e.value===g)??T.find(e=>"original"===e.value)??null;if((0,c.useEffect)(()=>{!t||0===T.length||T.some(e=>e.value===g)||b(T[0]?.value??"original")},[t,g,T]),(0,c.useEffect)(()=>{!U?.sampleRates?.length||U.sampleRates.includes(x)||w(U.sampleRates[0]??48e3)},[x,U]),!e)return null;let V={format:U?.value??"original",sampleRate:"audio"===e.kind&&"original"!==U?.value?x:void 0,bitDepth:"wav"===U?.value?j:void 0,bitrate:["mp3","m4a","aac","opus"].includes(U?.value??"")?C:void 0,quality:"image"===e.kind&&["jpeg","webp","avif"].includes(U?.value??"")?O:void 0,fps:"gif"===U?.value?F:void 0,width:"gif"===U?.value?P:void 0},_=A(e.filename,e.kind,U,V),Z=()=>{n||a(null)};return(0,k.jsx)(p,{open:!0,onClose:Z,title:e.title||`Download ${e.kind}`,description:"Download the original unchanged, or convert a copy using the available export formats.",dismissible:!n,testId:"media-export-dialog",panelClassName:"max-w-xl",footer:(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(f,{tone:"secondary",disabled:n,onClick:Z,children:"Cancel"}),(0,k.jsxs)(f,{tone:"primary",disabled:!U,loading:n,onClick:()=>{(async()=>{if(U&&!n){m(!0),v("");try{if(!await E(e.source,e.filename,e.kind,U,V))return;M(e.kind,V),a(null),o(`${U.label} download ready`,{variant:"success",autoHideDuration:1800})}catch(e){v(l(e,`Could not create the ${U.label} download.`))}finally{m(!1)}}})()},children:[(0,k.jsx)(r,{size:16}),n?"Preparing…":"Download"]})]}),children:(0,k.jsxs)("div",{className:"grid gap-4",children:[(0,k.jsx)(u,{label:"Format",children:(0,k.jsx)(d,{"aria-label":"Download format",value:U?.value??"",onValueChange:b,options:T.map(e=>({value:e.value,label:e.label,group:e.preset})),placeholder:t?"Select format":"Loading formats…",disabled:n||!t})}),"audio"===e.kind&&U?.sampleRates&&U.sampleRates.length>1?(0,k.jsx)(u,{label:"Sample rate",children:(0,k.jsx)(d,{"aria-label":"Audio sample rate",value:String(x),onValueChange:e=>w(Number(e)),options:U.sampleRates.map(e=>({value:String(e),label:e/1e3+" kHz"}))})}):null,"wav"===U?.value?(0,k.jsx)(u,{label:"Bit depth",children:(0,k.jsx)(d,{"aria-label":"WAV bit depth",value:String(j),onValueChange:e=>$(Number(e)),options:[{value:"16",label:"16-bit PCM"},{value:"24",label:"24-bit PCM"}]})}):null,["mp3","m4a","aac","opus"].includes(U?.value??"")?(0,k.jsx)(u,{label:"Bitrate",children:(0,k.jsx)(d,{"aria-label":"Audio bitrate",value:String(C),onValueChange:e=>N(Number(e)),options:[128,192,256,320].map(e=>({value:String(e),label:`${e} kbps`}))})}):null,"image"===e.kind&&["jpeg","webp","avif"].includes(U?.value??"")?(0,k.jsx)(u,{label:"Quality",children:(0,k.jsx)(d,{"aria-label":"Image export quality",value:String(O),onValueChange:e=>z(Number(e)),options:[{value:"100",label:"Maximum · 100"},{value:"90",label:"High · 90"},{value:"80",label:"Balanced · 80"},{value:"65",label:"Small · 65"}]})}):null,"gif"===U?.value?(0,k.jsxs)("div",{className:"grid grid-cols-2 gap-3",children:[(0,k.jsx)(u,{label:"Frame rate",children:(0,k.jsx)(d,{"aria-label":"GIF frame rate",value:String(F),onValueChange:e=>L(Number(e)),options:[8,12,16,24].map(e=>({value:String(e),label:`${e} fps`}))})}),(0,k.jsx)(u,{label:"Width",children:(0,k.jsx)(d,{"aria-label":"GIF width",value:String(P),onValueChange:e=>I(Number(e)),options:[480,640,960,1280].map(e=>({value:String(e),label:`${e} px`}))})})]}):null,(0,k.jsx)("div",{className:"break-all rounded-modiff-compact border border-modiff-border bg-modiff-bg px-3 py-2 text-modiff-metadata text-modiff-subtle-text",children:_}),(0,k.jsx)("p",{role:"status",className:"text-xs text-modiff-subtle-text",children:n?"Preparing your download. The generated source asset is not changed.":t||h?"original"===U?.value?"Original file: no conversion or quality loss.":"A converted copy is downloaded; the source asset is kept.":"Loading conversion formats… Original download is available."}),h?(0,k.jsx)("div",{role:"alert",className:"rounded-modiff-compact border border-modiff-invalid bg-modiff-invalid/10 px-3 py-2 text-sm text-modiff-invalid",children:h}):null]})})}export{C as default}; \ No newline at end of file diff --git a/web/assets/MediaViewerDialog.js b/web/assets/MediaViewerDialog.js index 4eeea6a3..73517556 100644 --- a/web/assets/MediaViewerDialog.js +++ b/web/assets/MediaViewerDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{E as t,Mn as l,O as o,cn as r,gn as a,jn as i,pn as n,pr as d,ur as s}from"./graph-vendor.js?v=5bcd315505f5670a";import{Gt as f,Lo as c,Sn as u,Ua as m,Ut as x,Wt as p,dn as h,no as b,ot as w,rt as v,st as g,ta as j}from"./studio-templates.js?v=5bcd315505f5670a";var k=e(d(),1),y=s();function N(e){let t={image:"png",video:"mp4",audio:"wav",text:"txt"}[e.kind];return e.downloadName?.split(/[\\/]/).pop()?.split("").filter(e=>{let t=e.codePointAt(0)??0;return t>31&&127!==t}).join("").trim()||`modiff-${e.kind}.${t}`}async function C(e){try{let t="text"===e.kind?new Blob([e.text??""],{type:"text/plain;charset=utf-8"}):await b(e.url??""),l=URL.createObjectURL(t);try{!function(e,t){let l=document.createElement("a");l.href=e,l.download=t,l.rel="noopener noreferrer",document.body.append(l),l.click(),l.remove()}(l,N(e))}finally{window.setTimeout(()=>URL.revokeObjectURL(l),1e3)}}catch{m("Could not download output",{variant:"error",autoHideDuration:2400})}}function z({children:e,label:t,onClick:l}){return(0,y.jsx)(f,{label:t,size:"prominent",className:"text-modiff-subtle-text hover:bg-modiff-surface-hover hover:text-hf-yellow",onClick:l,children:e})}function L({item:e}){return"video"===e.kind&&e.url?(0,y.jsx)("video",{src:e.url,controls:!0,controlsList:"nodownload noremoteplayback",playsInline:!0,className:"block max-h-full max-w-full bg-modiff-media-backdrop object-contain","data-testid":"media-viewer-video"},e.url):"audio"===e.kind&&e.url?(0,y.jsxs)("div",{className:"flex w-[min(720px,90%)] flex-col items-center gap-5 rounded-modiff-compact border border-modiff-border bg-modiff-panel p-6 shadow-modiff-panel","data-testid":"media-viewer-audio",children:[(0,y.jsxs)("div",{className:"text-center",children:[(0,y.jsx)("div",{className:"text-base font-semibold text-modiff-text",children:e.label}),(0,y.jsx)("div",{className:"mt-1 text-xs text-modiff-subtle-text",children:"Previous audio render"})]}),(0,y.jsx)("audio",{src:e.url,controls:!0,className:"w-full"},e.url)]}):"text"===e.kind?(0,y.jsx)("pre",{className:"max-h-full w-[min(900px,92%)] overflow-auto whitespace-pre-wrap break-words rounded-modiff-compact border border-modiff-border bg-modiff-panel p-5 text-sm text-modiff-text shadow-modiff-panel","data-testid":"media-viewer-text",children:(0,y.jsx)("code",{children:e.text??""})}):e.url?(0,y.jsx)("img",{src:e.url,alt:e.label,className:"modiff-generated-image block max-h-full max-w-full border-[12px] border-modiff-bg bg-modiff-bg object-contain","data-testid":"media-viewer-image"}):null}function O({active:e,item:t,onClick:l}){return(0,y.jsx)(x,{tone:"ghost","aria-label":`Show ${t.label}`,"aria-pressed":e,onClick:l,className:u("h-20 w-28 flex-none overflow-hidden border-2 border-transparent p-0",e&&"border-hf-yellow"),children:"image"===t.kind&&t.url?(0,y.jsx)("img",{src:t.url,alt:"",className:"h-full w-full object-cover"}):"video"===t.kind&&t.url?(0,y.jsx)("video",{src:t.url,muted:!0,playsInline:!0,preload:"metadata",className:"h-full w-full object-cover"}):(0,y.jsx)("span",{className:"flex h-full w-full items-center justify-center bg-modiff-surface px-2 text-center text-xs font-semibold text-modiff-subtle-text",children:"text"===t.kind?(t.text??"").slice(0,48)||"Text":"Audio"})})}function $(){let e=j(e=>e.mediaViewerOpener),d=j(e=>e.setMediaViewerOpener),s=j(e=>e.setMediaExportOpener),u=(0,k.useMemo)(()=>w(e?.items??[],c.serverAddress),[e]),x=v(e?.currentIndex??0,u.length),[b,$]=(0,k.useState)(x),D=v(b,u.length),[E,U]=(0,k.useState)(!1);if((0,k.useEffect)(()=>{$(x)},[x,e]),(0,k.useEffect)(()=>{e&&0===u.length&&d(null)},[u.length,e,d]),(0,k.useEffect)(()=>{if(!e||u.length<2)return;let t=e=>{e.defaultPrevented||e.altKey||e.ctrlKey||e.metaKey||g(e.target)||("ArrowLeft"===e.key?(e.preventDefault(),$(e=>(v(e,u.length)-1+u.length)%u.length)):"ArrowRight"===e.key&&(e.preventDefault(),$(e=>(v(e,u.length)+1)%u.length)))};return document.addEventListener("keydown",t),()=>document.removeEventListener("keydown",t)},[u.length,e]),!e||0===u.length)return null;let R=u[D]??u[0];if(!R)return null;let A=()=>d(null);return(0,y.jsx)(p,{open:!0,onClose:A,title:u.length>1?`${e.title} · ${D+1} of ${u.length}`:e.title,panelClassName:"flex h-[80vh] w-[82vw] max-h-[80vh] max-w-[82vw] flex-col bg-modiff-media-backdrop/95",bodyClassName:"!max-h-none min-h-0 flex-1 !p-0",children:(0,y.jsxs)("div",{className:"flex h-full min-h-0 flex-col","data-testid":"media-viewer-dialog",children:[(0,y.jsxs)("div",{className:"relative flex min-h-0 flex-1 items-center justify-center overflow-hidden p-6",children:[(0,y.jsx)(L,{item:R}),(0,y.jsxs)("div",{className:"nodrag absolute right-3 top-3 flex gap-1 rounded-modiff-compact border border-modiff-border bg-modiff-panel/95 p-1 shadow-modiff-panel",children:[(0,y.jsx)(z,{label:`Download ${R.kind}`,onClick:()=>{"text"!==R.kind?R.url&&s({source:R.url,kind:R.kind,filename:N(R),title:`Download ${R.kind}`,defaultFormat:"audio"===R.kind?"wav":"video"===R.kind?"mp4":"png"}):C(R)},children:(0,y.jsx)(n,{size:18})}),(0,y.jsx)(z,{label:"Copy output",onClick:()=>{(async()=>{try{await navigator.clipboard.writeText(R.url??R.text??""),m(R.url?"Output URL copied":"Output text copied",{variant:"success",autoHideDuration:1600})}catch{m("Could not copy output",{variant:"error",autoHideDuration:2400})}})()},children:(0,y.jsx)(a,{size:18})}),R.url?(0,y.jsx)(z,{label:"Open output in new tab",onClick:()=>window.open(R.url,"_blank","noopener,noreferrer"),children:(0,y.jsx)(r,{size:18})}):null,e.workflow?(0,y.jsx)(f,{label:`Open workflow ${e.workflow.name}`,size:"prominent",disabled:E,"aria-busy":E,className:"text-modiff-subtle-text hover:bg-modiff-surface-hover hover:text-hf-yellow",onClick:()=>{(async()=>{if(e.workflow&&!E){U(!0);try{"workflow"===await h({...e.workflow,status:"completed",preferWorkflow:!0})?A():m(`Could not open ${e.workflow.name}.`,{variant:"error",autoHideDuration:4e3})}finally{U(!1)}}})()},children:(0,y.jsx)(o,{size:18})}):null,(0,y.jsx)(z,{label:"Close media viewer",onClick:A,children:(0,y.jsx)(t,{size:20})})]}),u.length>1?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(f,{label:"Previous output",size:"prominent",className:"absolute left-4 top-1/2 size-12 -translate-y-1/2 border border-modiff-border bg-modiff-panel/90 text-modiff-subtle-text hover:text-hf-yellow",onClick:()=>$(e=>(v(e,u.length)-1+u.length)%u.length),children:(0,y.jsx)(l,{size:28})}),(0,y.jsx)(f,{label:"Next output",size:"prominent",className:"absolute right-4 top-1/2 size-12 -translate-y-1/2 border border-modiff-border bg-modiff-panel/90 text-modiff-subtle-text hover:text-hf-yellow",onClick:()=>$(e=>(v(e,u.length)+1)%u.length),children:(0,y.jsx)(i,{size:28})})]}):null]}),u.length>1?(0,y.jsx)("div",{className:"flex h-24 flex-none items-center justify-center gap-2 overflow-x-auto border-t border-modiff-border bg-modiff-panel/95 p-2",children:u.map((e,t)=>(0,y.jsx)(O,{active:t===D,item:e,onClick:()=>$(t)},e.id))}):null]})})}export{$ as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Dn as t,E as l,O as o,On as r,an as a,cr as i,dr as n,pn as d,un as s}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Do as f,In as c,Mo as u,Sa as m,Tn as x,an as p,bt as h,gt as b,in as w,oc as v,rn as g,yt as j}from"./studio-templates.js?v=cc046c26d6fe7aa4";var k=e(n(),1),y=i();function N(e){let t={image:"png",video:"mp4",audio:"wav",text:"txt"}[e.kind];return e.downloadName?.split(/[\\/]/).pop()?.split("").filter(e=>{let t=e.codePointAt(0)??0;return t>31&&127!==t}).join("").trim()||`modiff-${e.kind}.${t}`}async function C(e){try{let t="text"===e.kind?new Blob([e.text??""],{type:"text/plain;charset=utf-8"}):await f(e.url??""),l=URL.createObjectURL(t);try{!function(e,t){let l=document.createElement("a");l.href=e,l.download=t,l.rel="noopener noreferrer",document.body.append(l),l.click(),l.remove()}(l,N(e))}finally{window.setTimeout(()=>URL.revokeObjectURL(l),1e3)}}catch{u("Could not download output",{variant:"error",autoHideDuration:2400})}}function z({children:e,label:t,onClick:l}){return(0,y.jsx)(p,{label:t,size:"prominent",className:"text-modiff-subtle-text hover:bg-modiff-surface-hover hover:text-hf-yellow",onClick:l,children:e})}function O({item:e}){return"video"===e.kind&&e.url?(0,y.jsx)("video",{src:e.url,controls:!0,controlsList:"nodownload noremoteplayback",playsInline:!0,className:"block max-h-full max-w-full bg-modiff-media-backdrop object-contain","data-testid":"media-viewer-video"},e.url):"audio"===e.kind&&e.url?(0,y.jsxs)("div",{className:"flex w-[min(720px,90%)] flex-col items-center gap-5 rounded-modiff-compact border border-modiff-border bg-modiff-panel p-6 shadow-modiff-panel","data-testid":"media-viewer-audio",children:[(0,y.jsxs)("div",{className:"text-center",children:[(0,y.jsx)("div",{className:"text-base font-semibold text-modiff-text",children:e.label}),(0,y.jsx)("div",{className:"mt-1 text-xs text-modiff-subtle-text",children:"Previous audio render"})]}),(0,y.jsx)("audio",{src:e.url,controls:!0,className:"w-full"},e.url)]}):"text"===e.kind?(0,y.jsx)("pre",{className:"max-h-full w-[min(900px,92%)] overflow-auto whitespace-pre-wrap break-words rounded-modiff-compact border border-modiff-border bg-modiff-panel p-5 text-sm text-modiff-text shadow-modiff-panel","data-testid":"media-viewer-text",children:(0,y.jsx)("code",{children:e.text??""})}):e.url?(0,y.jsx)("img",{src:e.url,alt:e.label,className:"modiff-generated-image block max-h-full max-w-full border-[12px] border-modiff-bg bg-modiff-bg object-contain","data-testid":"media-viewer-image"}):null}function D({active:e,item:t,onClick:l}){return(0,y.jsx)(g,{tone:"ghost","aria-label":`Show ${t.label}`,"aria-pressed":e,onClick:l,className:c("h-20 w-28 flex-none overflow-hidden border-2 border-transparent p-0",e&&"border-hf-yellow"),children:"image"===t.kind&&t.url?(0,y.jsx)("img",{src:t.url,alt:"",className:"h-full w-full object-cover"}):"video"===t.kind&&t.url?(0,y.jsx)("video",{src:t.url,muted:!0,playsInline:!0,preload:"metadata",className:"h-full w-full object-cover"}):(0,y.jsx)("span",{className:"flex h-full w-full items-center justify-center bg-modiff-surface px-2 text-center text-xs font-semibold text-modiff-subtle-text",children:"text"===t.kind?(t.text??"").slice(0,48)||"Text":"Audio"})})}function $(){let e=m(e=>e.mediaViewerOpener),i=m(e=>e.setMediaViewerOpener),n=m(e=>e.setMediaExportOpener),f=(0,k.useMemo)(()=>j(e?.items??[],v.serverAddress),[e]),c=b(e?.currentIndex??0,f.length),[g,$]=(0,k.useState)(c),L=b(g,f.length),[E,R]=(0,k.useState)(!1);if((0,k.useEffect)(()=>{$(c)},[c,e]),(0,k.useEffect)(()=>{e&&0===f.length&&i(null)},[f.length,e,i]),(0,k.useEffect)(()=>{if(!e||f.length<2)return;let t=e=>{e.defaultPrevented||e.altKey||e.ctrlKey||e.metaKey||h(e.target)||("ArrowLeft"===e.key?(e.preventDefault(),$(e=>(b(e,f.length)-1+f.length)%f.length)):"ArrowRight"===e.key&&(e.preventDefault(),$(e=>(b(e,f.length)+1)%f.length)))};return document.addEventListener("keydown",t),()=>document.removeEventListener("keydown",t)},[f.length,e]),!e||0===f.length)return null;let A=f[L]??f[0];if(!A)return null;let U=()=>i(null);return(0,y.jsx)(w,{open:!0,onClose:U,title:f.length>1?`${e.title} · ${L+1} of ${f.length}`:e.title,panelClassName:"flex h-[80vh] w-[82vw] max-h-[80vh] max-w-[82vw] flex-col bg-modiff-media-backdrop/95",bodyClassName:"!max-h-none min-h-0 flex-1 !p-0",children:(0,y.jsxs)("div",{className:"flex h-full min-h-0 flex-col","data-testid":"media-viewer-dialog",children:[(0,y.jsxs)("div",{className:"relative flex min-h-0 flex-1 items-center justify-center overflow-hidden p-6",children:[(0,y.jsx)(O,{item:A}),(0,y.jsxs)("div",{className:"nodrag absolute right-3 top-3 flex gap-1 rounded-modiff-compact border border-modiff-border bg-modiff-panel/95 p-1 shadow-modiff-panel",children:[(0,y.jsx)(z,{label:`Download ${A.kind}`,onClick:()=>{"text"!==A.kind?A.url&&n({source:A.url,kind:A.kind,filename:N(A),title:`Download ${A.kind}`,defaultFormat:"audio"===A.kind?"wav":"video"===A.kind?"mp4":"png"}):C(A)},children:(0,y.jsx)(s,{size:18})}),(0,y.jsx)(z,{label:"Copy output",onClick:()=>{(async()=>{try{await navigator.clipboard.writeText(A.url??A.text??""),u(A.url?"Output URL copied":"Output text copied",{variant:"success",autoHideDuration:1600})}catch{u("Could not copy output",{variant:"error",autoHideDuration:2400})}})()},children:(0,y.jsx)(d,{size:18})}),A.url?(0,y.jsx)(z,{label:"Open output in new tab",onClick:()=>window.open(A.url,"_blank","noopener,noreferrer"),children:(0,y.jsx)(a,{size:18})}):null,e.workflow?(0,y.jsx)(p,{label:`Open workflow ${e.workflow.name}`,size:"prominent",disabled:E,"aria-busy":E,className:"text-modiff-subtle-text hover:bg-modiff-surface-hover hover:text-hf-yellow",onClick:()=>{(async()=>{if(e.workflow&&!E){R(!0);try{"workflow"===await x({...e.workflow,status:"completed",preferWorkflow:!0})?U():u(`Could not open ${e.workflow.name}.`,{variant:"error",autoHideDuration:4e3})}finally{R(!1)}}})()},children:(0,y.jsx)(o,{size:18})}):null,(0,y.jsx)(z,{label:"Close media viewer",onClick:U,children:(0,y.jsx)(l,{size:20})})]}),f.length>1?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(p,{label:"Previous output",size:"prominent",className:"absolute left-4 top-1/2 size-12 -translate-y-1/2 border border-modiff-border bg-modiff-panel/90 text-modiff-subtle-text hover:text-hf-yellow",onClick:()=>$(e=>(b(e,f.length)-1+f.length)%f.length),children:(0,y.jsx)(r,{size:28})}),(0,y.jsx)(p,{label:"Next output",size:"prominent",className:"absolute right-4 top-1/2 size-12 -translate-y-1/2 border border-modiff-border bg-modiff-panel/90 text-modiff-subtle-text hover:text-hf-yellow",onClick:()=>$(e=>(b(e,f.length)+1)%f.length),children:(0,y.jsx)(t,{size:28})})]}):null]}),f.length>1?(0,y.jsx)("div",{className:"flex h-24 flex-none items-center justify-center gap-2 overflow-x-auto border-t border-modiff-border bg-modiff-panel/95 p-2",children:f.map((e,t)=>(0,y.jsx)(D,{active:t===L,item:e,onClick:()=>$(t)},e.id))}):null]})})}export{$ as default}; \ No newline at end of file diff --git a/web/assets/ModelManagerDialog.js b/web/assets/ModelManagerDialog.js index 9c92d712..4d4548dd 100644 --- a/web/assets/ModelManagerDialog.js +++ b/web/assets/ModelManagerDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{An as a,B as t,Ft as s,Nn as l,Ot as n,Wt as i,at as r,gn as o,mn as d,pn as c,pr as u,q as m,st as f,ur as p}from"./graph-vendor.js?v=5bcd315505f5670a";import{Ba as x,Ca as h,Ci as g,Gt as b,Ha as j,Ia as v,Ja as y,Ka as w,Lo as N,Lt as C,Pa as k,Ra as $,Sa as _,Sn as S,T as H,Ta as D,Ua as R,Ut as T,Va as M,Wt as I,Xa as F,Za as z,Zt as E,an as L,bi as A,cr as B,en as q,eo as O,f as P,gi as U,hi as V,jn as G,ka as W,kr as K,lr as J,m as Z,mi as X,ni as Q,nn as Y,or as ee,pi as ae,ro as te,sn as se,sr as le,ta as ne,to as ie,ui as re,za as oe}from"./studio-templates.js?v=5bcd315505f5670a";import{t as de}from"./WorkflowArtifactRequirementRow.js?v=5bcd315505f5670a";import{n as ce,t as ue}from"./TemplateUsageTermsDialog.js?v=5bcd315505f5670a";var me=e(u(),1),fe=p(),pe=O(),xe=[{value:"all",label:"All models"},{value:"installed",label:"Installed"},{value:"supported",label:"Supported"},{value:"downloads",label:"Downloads"}];function he(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function ge(e){if(!Array.isArray(e))throw Error("The Hugging Face cache response must be an array.");return e.map((e,a)=>{if(!he(e)||"string"!=typeof e.id||!Array.isArray(e.revisions))throw Error(`Hugging Face cache entry ${a+1} is invalid.`);if(e.revisions.some(e=>!he(e)||"string"!=typeof e.hash))throw Error(`Hugging Face cache entry ${a+1} has an invalid revision.`);return e})}var be=["Image","Edit","Control","Video","Audio","Adapters","Components","Other"];function je(e){return"default"===e?"neutral":e}function ve({children:e,disabled:a,onClick:t,progress:s,testId:l,title:n,tone:i}){let r="string"==typeof e?e:void 0;return(0,fe.jsx)(C,{action:"Installing"===r?"installing":"Retry"===r?"retry":t?"install":"success"===i?"ready":"warning"===i||"error"===i?"missing":"details",className:"min-h-7 px-2 py-0.5 text-xs",disabled:a,label:e,onClick:t,progress:s,testId:l,title:n,tone:je(i)})}function ye({children:e,tone:a="muted"}){return(0,fe.jsx)("p",{className:S("break-words text-xs leading-5","muted"===a&&"text-modiff-subtle-text","success"===a&&"text-modiff-green","warning"===a&&"text-hf-orange","error"===a&&"text-modiff-red"),children:e})}function we(e){if("string"==typeof e)return e;if(e&&"object"==typeof e){let a=e;return String(a.id??a.repo_id??a.label??a.rel_path??a.path??JSON.stringify(e))}return String(e??"")}function Ne(e){let a=we(e);return a.replace(/\\/g,"/").split("/").filter(Boolean).slice(-2).join("/")||a}function Ce(e){return!!(e&&"object"==typeof e&&"id"in e&&"revisions"in e)}function ke(e,a){let t=new Map;return be.forEach(e=>t.set(e,[])),e.forEach(e=>t.get(a(e))?.push(e)),be.map(e=>[e,t.get(e)??[]]).filter(([,e])=>e.length>0)}function $e(e){let a=k(e);return"spandrel"===a?"Spandrel":v(e)?"diffusers_accelerated"===a?"Accelerated":"Diffusers":"Legacy"}function _e(e){if(0===e)return"0 B";let a=Math.floor(Math.log(e)/Math.log(1024));return`${(e/1024**a).toFixed(2)} ${["B","KB","MB","GB","TB"][a]}`}function Se(e){let a=we(e),t=a.split("/"),s=t.pop()||a,l=t.join("/");return(0,fe.jsxs)(fe.Fragment,{children:[l?(0,fe.jsx)("span",{className:"text-modiff-subtle-text",children:l}):null,l?"/":null,(0,fe.jsx)("strong",{children:s})]})}function He({children:e,count:a,testId:t,title:s}){return(0,fe.jsxs)("section",{className:"grid gap-2","data-testid":t,children:[(0,fe.jsxs)("div",{className:"flex items-center justify-between gap-2 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,fe.jsx)("span",{children:s}),(0,fe.jsx)(ve,{tone:"default",children:a})]}),e]})}function De({activeInstallCount:e,autoPlan:a,expertMode:t,form:s,focused:l,hfTokenConfigured:n,installProgress:i,onConfigureHfToken:r,onInstall:o,profile:d,status:c}){let u=U(a,s),m=t?J(d,c):null,f=u&&m&&u.repo===m.repo?{...u,repair:m.repair,revision:m.revision,files:m.files}:u??m,p=f?.repo??d.defaultRepo,h=i[p],b=M(h),j=x(h),v=j&&("huggingface_access_required"===h?.error_code||"access"===$(h)),y=v||!n&&Z(p),w=g(c.runnable,a,s),N=X(a,s),C=w?"Ready":f?.actionLabel??V(a,s),k=!w&&("Not suitable locally"===C||"Will not work on this machine"===C||"unsuitable"===N.state),_=w?"success":k?"error":"warning",H=v?"Review access":y?"Add HF token":w?"Ready":b?"Installing":j?"Retry":function(e){return"This should work"===e?"Estimated":"Community option"===e?"Community":"Not suitable locally"===e||"Will not work on this machine"===e?"Blocked":"Repair required"===e?"Repair":"Failed here before"===e?"Failed":"Expert only"===e?"Expert":"Needs setup"===e||"Install model"===e||"Install Auto artifact"===e?"Install":e}(f?.actionLabel??C),D=y?v&&h?.error?h.error:"This gated Hugging Face repository needs an accepted license and a configured read token.":w?c.reason:j&&h?.error?h.error:f?.reason??N.detail,R=!y&&!w&&f&&!b&&e<2,T=d.artifactSelections?.length?d.artifactLabel:null,I=T??d.label,F=T?d.defaultRepo:null;return(0,fe.jsx)("article",{className:S("rounded-modiff-compact border bg-modiff-bg p-2",l?"border-hf-yellow/80":"border-modiff-border"),"data-testid":`model-manager-supported-${d.modelType}`,"data-model-repo":d.defaultRepo,"data-model-manager-focused":l?"true":void 0,children:(0,fe.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,fe.jsxs)("div",{className:"min-w-0",children:[(0,fe.jsx)("div",{className:"truncate text-sm font-semibold text-modiff-text","data-testid":"model-manager-artifact-label",children:I}),F?(0,fe.jsx)("div",{className:"mt-0.5 truncate text-xs text-modiff-subtle-text",title:d.defaultRepo,children:F}):null,(0,fe.jsxs)("div",{className:"mt-1 flex flex-wrap gap-1",children:[(0,fe.jsx)(ve,{tone:"default",children:d.surfaceCategory??d.family}),(0,fe.jsx)(ve,{tone:"default",children:$e(d)}),"workflowOnly"===d.catalogVisibility?(0,fe.jsx)(ve,{tone:"warning",children:"Legacy"}):null,f?.repair?(0,fe.jsx)(ve,{tone:"warning",children:"Repair"}):null]})]}),(0,fe.jsx)(ve,{disabled:b||!y&&!w&&!R,onClick:y?()=>r(p):R?()=>{f&&o(f,f.repair)}:void 0,progress:b?oe(h):null,testId:`model-manager-install-${d.modelType}`,title:`${p} | ${D}`,tone:_,children:H})]})})}function Re({opener:e,onClose:u}){let{setAlertOpener:p}=ne(),g=ne(e=>e.studioViewMode),v=G(e=>e.sid),C=Q(e=>e.hfCache),k=Q(e=>e.localModels),$=Q(e=>e.modelCacheDiagnostics),S=Q(e=>e.runtimeStatus),O=Q(e=>e.nodesRegistry),V=Q(e=>e.studioModelCapabilities),J=Q(e=>e.studioModelCapabilitiesAuthoritative),Z=Q(e=>e.refreshModelIndexes),X=Q(e=>e.fetchRegistry),oe=Q(e=>e.installHfModel),he=Q(e=>e.hfDownloadProgress),be=Q(e=>e.reconcileHfDownloadProgress),je=Q(e=>e.customModules),$e=Q(e=>e.customModuleError),Re=Q(e=>e.fetchCustomModules),Te=Q(e=>e.refreshCustomModules),Me=Q(e=>e.installCustomModule),Ie=Q(e=>e.updateCustomModule),Fe=Q(e=>e.setCustomModuleEnabled),ze=K(e=>e.nodes),Ee=K(e=>e.setNodeUiState),Le=re(e=>e.form),Ae=re(e=>e.activeTemplateId),Be=re(e=>e.graphBinding),qe=re(e=>e.sourceOutputId),Oe=re(e=>e.autoResourcePlans),Pe=re(e=>e.setAutoResourcePlans),[Ue,Ve]=(0,me.useState)(!1),Ge=(0,me.useRef)(0),[We,Ke]=(0,me.useState)([]),[Je,Ze]=(0,me.useState)([]),[Xe,Qe]=(0,me.useState)(""),[Ye,ea]=(0,me.useState)("all"),aa=(0,me.useRef)(null),ta=e=>{ea(e),aa.current?.closest("[data-dialog-scroll-body]")?.scrollTo(0,0)},[sa,la]=(0,me.useState)(""),[na,ia]=(0,me.useState)(""),[ra,oa]=(0,me.useState)(null),[da,ca]=(0,me.useState)(!1),[ua,ma]=(0,me.useState)(null),[fa,pa]=(0,me.useState)(""),[xa,ha]=(0,me.useState)(!1),[ga,ba]=(0,me.useState)(null),ja=ce(),va="expert"===g,ya=e?.focus,wa=Object.values(he).filter(M).length,Na=Object.entries(he).filter(([e,a])=>(M(a)||x(a)||j(a))&&e.toLowerCase().includes(Xe.trim().toLowerCase())),Ca=ze.filter(e=>{if("group"===e.data.type||"loop"===e.data.type)return!1;let a=`${e.data.module}.${e.data.action}`;return!O[a]}),ka=(0,me.useMemo)(()=>function(e){let a=new Set;return e.filter(({item:e,source:t})=>{let s=`${t}:${we(e).toLowerCase()}`;return!a.has(s)&&(a.add(s),!0)})}([...We.map(e=>({source:"HF",item:e})),...k.map(e=>({source:"Local",item:e}))].filter(({item:e})=>{let a=Xe.trim().toLowerCase();return!a||we(e).toLowerCase().includes(a)})),[Xe,We,k]),$a=(0,me.useMemo)(()=>ke(ka,({item:e})=>function(e){let a=we(e).toLowerCase();return a.includes("text_encoder")||a.includes("text encoder")||a.includes("vae")||a.includes("tokenizer")||a.includes("transformer")||a.includes("diffusion_model")||a.includes("clip")?"Components":a.includes("wan")||a.includes("video")?"Video":a.includes("ace")||a.includes("audio")?"Audio":a.includes("control")||a.includes("canny")||a.includes("depth")?"Control":a.includes("lora")||a.includes("adapter")?"Adapters":a.includes("edit")||a.includes("inpaint")||a.includes("fill")||a.includes("kontext")||a.includes("redux")?"Edit":a.includes("image")||a.includes("flux")||a.includes("z-image")||a.includes("qwen")?"Image":"Other"}(e)),[ka]),_a=(0,me.useMemo)(()=>va?$a:$a.filter(([e])=>"Components"!==e),[va,$a]),Sa=(0,me.useMemo)(()=>h({currentModelType:ya?.modelType??Le.modelType,includeWorkflowOnly:va}).flatMap(e=>{if(!J)return[e];let a=V.find(a=>a.modelType===e.modelType);return a?_({...e,defaultRepo:a.defaultRepo??e.defaultRepo,artifactLabel:a.artifactLabel??e.artifactLabel,executionStatus:a.executionStatus??e.executionStatus,revisionCandidates:a.revisionCandidates??e.revisionCandidates,downloadFiles:a.downloadFiles??e.downloadFiles,artifactSelections:a.artifactSelections??e.artifactSelections,optionalRuntimeRequirement:a.optionalRuntimeRequirement??e.optionalRuntimeRequirement}):[e]}).sort((e,a)=>{if(ya?.modelType===e.modelType)return-1;if(ya?.modelType===a.modelType)return 1;if(ya?.repo===e.defaultRepo)return-1;if(ya?.repo===a.defaultRepo)return 1;let t=String(e.label??e.modelType??e.defaultRepo??""),s=String(a.label??a.modelType??a.defaultRepo??"");return t.localeCompare(s)}),[va,ya?.modelType,ya?.repo,Le.modelType,V,J]),Ha=(0,me.useMemo)(()=>{let e=Xe.trim().toLowerCase();return Sa.filter(a=>`${a.label} ${a.defaultRepo} ${a.family} ${a.surfaceCategory??""}`.toLowerCase().includes(e))},[Sa,Xe]),Da=(0,me.useMemo)(()=>Sa.map(e=>D(W(e),e.modelType)),[Sa]),Ra=(0,me.useMemo)(()=>Da.map(ae),[Da]),Ta=(0,me.useMemo)(()=>ke(Ha,e=>function(e){return"Audio"===e?"Audio":"Video"===e?"Video":"Control"===e?"Control":"Image Edit"===e?"Edit":"Utility"===e?"Adapters":"Image"===e?"Image":"Other"}(e.surfaceCategory)),[Ha]);(0,me.useEffect)(()=>{if(!ya)return;let e=window.setTimeout(()=>{document.querySelector('[data-model-manager-focused="true"]')?.scrollIntoView({behavior:"smooth",block:"center"})},80);return()=>window.clearTimeout(e)},[ya,Ha.length]);let Ma=ya?.label??Ha.find(e=>e.modelType===ya?.modelType||e.defaultRepo===ya?.repo)?.label??ya?.repo,Ia=ae(Le),Fa=!!(ya||Ae||Be||qe),za=(0,me.useMemo)(()=>Fa?ee({autoResourcePlan:Oe[Ia],form:Le,hfCache:C,includePipeline:!1,localModels:k,modelCacheDiagnostics:$}):[],[Oe,C,Fa,k,$,Le,Ia]),Ea=(0,me.useCallback)(()=>{Ge.current+=1,Ve(!0)},[]),La=(0,me.useCallback)(()=>{Ge.current=Math.max(0,Ge.current-1),0===Ge.current&&Ve(!1)},[]),Aa=(0,me.useCallback)(async()=>{let e=pe.begin("fullCache");Ea();try{let a=await te(`${N.serverAddress}/hf_cache`,{signal:e.signal,timeoutMs:12e4,parse:ge});e.isLatest()&&Ke(a)}catch(a){e.isLatest()&&console.error("Error fetching Hugging Face models:",a)}finally{e.finish(),La()}},[Ea,La]),Ba=(0,me.useCallback)(async()=>{let e=await A(Da,Ra),a={};e.forEach((e,t)=>{let s=e.planKey??Ra[t];s&&(a[s]=e)}),Pe(a)},[Pe,Da,Ra]),qa=e=>{let a="string"==typeof e.workflowId?` (${e.workflowId})`:"";switch(e.code){case"graph_execution_active":return"a graph is running";case"graph_execution_queued":return"a graph is queued";case"model_download_active":return"a model download is active";case"template_gallery_install_active":return"the Template Gallery is installing";case"canonical_dependency_open":return`template evidence is not turnover-complete${a}`;case"saved_workflow_dependency":return`a saved workflow still uses the model${a}`;case"recent_incomplete_download":return"an incomplete file is newer than one hour";default:return e.code.split("_").join(" ")}},Oa=async(e,a)=>{Ea();try{let t=await F(e),s=!1;if(!t.canDelete){let a=new Set(["canonical_dependency_open","saved_workflow_dependency"]);if(!(t.blockers.length>0&&t.blockers.every(e=>a.has(e.code)))){let e=t.blockers.slice(0,4).map(qa).join("; ");return void R(`Deletion blocked: ${e||"the revision is not turnover-safe"}.`,{variant:"error",autoHideDuration:Math.max(7e3,70*e.length)})}if(t=await F(e,!0),s=!0,!t.canDelete){let e=t.blockers.slice(0,4).map(qa).join("; ");return void R(`Local eviction blocked: ${e||"the cache state changed"}.`,{variant:"error",autoHideDuration:Math.max(7e3,70*e.length)})}}let l=t.targets.map(e=>`${e.repoId}@${e.revision}${e.sizeBytes?` (${_e(e.sizeBytes)})`:""}`).join(", "),n=t.canonicalDependencies.length,i=t.savedWorkflowDependencies.length;p({title:s?"Free local model storage?":"Confirm safe model deletion",message:s?`Evict the local copy of ${a}? Exact cache target: ${l}. ${n} canonical definition${1===n?"":"s"} and ${i} saved workflow${1===i?"":"s"} will be kept, but they must download this exact model revision before they can run again.`:`Delete ${a}? Exact cache target: ${l}. ${n} canonical workflow ${1===n?"dependency":"dependencies"} verified turnover-complete. This action cannot be undone without downloading the model again.`,confirmText:s?"Evict local copy":"Delete exact revision",cancelText:"Cancel",onConfirm:()=>{p(null),(async(e,a,t=!1)=>{Ea();try{await y(e,a,t),await Aa()}catch(e){console.error("Error deleting Hugging Face model:",e);let a=ie(e,"Error deleting Hugging Face model.");R(a,{variant:"error",autoHideDuration:80*a.length})}finally{La()}})(e,t.planHash,s)},onCancel:()=>p(null)})}catch(e){let a=ie(e,"Could not inspect model dependencies before deletion.");R(a,{variant:"error",autoHideDuration:80*a.length})}finally{La()}},Pa=async(e,a=!1)=>{let t="string"==typeof e?{repo:e,label:e,repair:a}:e;ja.request("install",P(t.repo,t.revision),()=>(async(e,a=!1)=>{let t="string"==typeof e?{repo:e,label:e,repair:a}:e;try{await oe(t.repo,v,{repair:t.repair,revision:t.revision,files:t.files}),await Promise.all([Aa(),Z(!0)]),await Ba()}catch(e){console.error(e)}})(t,t.repair))},Ua=(e,a)=>{navigator.clipboard.writeText(`${e}\n${a}`),R("Missing node key copied",{variant:"success",autoHideDuration:1600})},Va=()=>{u(),Ze([]),Qe(""),ea("all"),ca(!1),pa(""),ba(null),Ke([]),la(""),ia(""),oa(null)};return(0,me.useEffect)(()=>{Aa(),Ba(),va&&Re()},[va,Re,Aa,Ba]),(0,me.useEffect)(()=>{if(!e||0===wa)return;let a=!1,t=async()=>{try{await be()}catch(e){}};t();let s=window.setInterval(()=>{t()},2e3);return()=>{a=!0,window.clearInterval(s)}},[wa,e,be]),(0,fe.jsxs)(fe.Fragment,{children:[(0,fe.jsxs)(I,{open:!!e,onClose:Va,title:(0,fe.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,fe.jsx)(d,{size:17,className:"shrink-0 text-hf-yellow"}),(0,fe.jsx)("span",{className:"truncate",children:"Models"})]}),description:"Manage local artifacts and install models from Hugging Face Hub.",panelClassName:"max-w-4xl",bodyClassName:"!p-0",testId:"model-manager-dialog",toolbar:(0,fe.jsxs)("div",{className:"grid gap-3 p-3","data-testid":"model-manager-toolbar",children:[(0,fe.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,fe.jsx)(L,{"aria-label":"Filter models and artifacts",className:"min-w-0 flex-1 basis-56",controlSize:"normal",placeholder:"Search by model, repository or family",value:Xe,onChange:e=>Qe(e.target.value),onClear:()=>Qe("")}),(0,fe.jsx)(T,{icon:(0,fe.jsx)(s,{size:14}),"aria-expanded":da,"aria-controls":"model-manager-token-editor",onClick:()=>{ca(e=>!e),pa(""),ba(null)},children:S?.config?.hf_token_configured?"HF token ready":"Add HF token"}),(0,fe.jsx)(T,{loading:Ue,icon:(0,fe.jsx)(r,{size:14}),onClick:()=>{(async(e=!0)=>{Ea();try{await Promise.all([Aa(),Z(e)]),await Ba(),await Re(),R("Models refreshed",{variant:"success",autoHideDuration:1800})}catch(e){R(ie(e,"Could not refresh model health."),{variant:"error",autoHideDuration:6e3})}finally{La()}})(!0)},children:"Refresh"})]}),(0,fe.jsx)(se,{"aria-label":"Model inventory sections",size:"dense",className:"flex-wrap",value:Ye,onValueChange:ta,options:xe.map(e=>({...e,label:"downloads"===e.value&&wa>0?`Downloads (${wa})`:e.label,id:`model-inventory-tab-${e.value}`,controls:`model-inventory-panel-${e.value}`}))})]}),footer:(0,fe.jsxs)(fe.Fragment,{children:[(0,fe.jsx)("span",{className:"mr-auto flex items-center text-xs text-modiff-subtle-text",role:"status","aria-live":"polite",children:Ue?"Refreshing model inventory…":`${ka.length} installed · ${Ha.length} supported${Xe?" matches":""}`}),(0,fe.jsx)(T,{"data-testid":"model-manager-close",onClick:Va,children:"Close"})]}),children:[da?(0,fe.jsxs)("div",{id:"model-manager-token-editor",className:"m-3 grid gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3",children:[(0,fe.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Hugging Face access"}),(0,fe.jsxs)("div",{className:"text-xs text-modiff-subtle-text",children:["Accept the"," ",(0,fe.jsx)("a",{className:"text-hf-yellow underline underline-offset-2",href:ua?`https://huggingface.co/${ua}`:"https://huggingface.co/models",target:"_blank",rel:"noreferrer",children:"repository terms"}),", create a"," ",(0,fe.jsx)("a",{className:"text-hf-yellow underline underline-offset-2",href:"https://huggingface.co/settings/tokens",target:"_blank",rel:"noreferrer",children:"read token"}),", then paste it below. The token is validated and stored on the backend; it is never returned to the browser."]}),(0,fe.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,fe.jsx)(Y,{"aria-label":"Hugging Face access token",autoFocus:!0,autoComplete:"off",value:fa,onChange:e=>pa(e.target.value),placeholder:"hf_...",disabled:xa,className:"min-w-0 flex-1 basis-56"}),(0,fe.jsx)(T,{tone:"primary",disabled:xa||!fa.trim(),loading:xa,icon:(0,fe.jsx)(s,{size:14}),onClick:()=>{(async()=>{let e=fa.trim();if(e){ha(!0),ba(null);try{await te(`${N.serverAddress}/hf_token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:e}),timeoutMs:3e4}),pa(""),ca(!1),await Z(!0),R("Hugging Face token saved. Retry the model download.",{variant:"success",autoHideDuration:3500})}catch(e){ba(ie(e,"Could not validate the Hugging Face token."))}finally{ha(!1)}}else ba("Paste a Hugging Face read token first.")})()},children:"Save token"}),(0,fe.jsx)(T,{disabled:xa,onClick:()=>{ca(!1),pa(""),ba(null)},children:"Cancel"})]}),ga?(0,fe.jsx)(ye,{tone:"warning",children:ga}):null]}):null,xe.filter(e=>e.value!==Ye).map(e=>(0,fe.jsx)("div",{id:`model-inventory-panel-${e.value}`,role:"tabpanel","aria-labelledby":`model-inventory-tab-${e.value}`,hidden:!0},e.value)),(0,fe.jsxs)("div",{ref:aa,className:"p-3",id:`model-inventory-panel-${Ye}`,role:"tabpanel","aria-labelledby":`model-inventory-tab-${Ye}`,children:[Ma?(0,fe.jsxs)("div",{className:"mb-3 flex flex-wrap items-center gap-2 rounded-modiff-compact border border-hf-yellow/70 bg-hf-yellow/10 p-2","data-testid":"model-manager-focus",children:[(0,fe.jsx)(ve,{tone:"warning",children:"Focused"}),(0,fe.jsx)("span",{className:"min-w-0 truncate text-sm font-semibold text-modiff-text",children:Ma})]}):null,("all"===Ye||"downloads"===Ye)&&Na.length>0?(0,fe.jsxs)("section",{className:"mb-3 grid gap-2","data-testid":"model-manager-downloads",children:[(0,fe.jsxs)("div",{className:"flex items-center justify-between gap-2 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,fe.jsx)("span",{children:"Downloads"}),(0,fe.jsxs)(ve,{tone:wa>0?"warning":"default",children:[wa," active"]})]}),Na.map(([e,a])=>(0,fe.jsx)(H,{repoId:e,progress:a,compact:!va,testId:`model-manager-download-${e}`},e))]}):null,"downloads"===Ye&&0===Na.length?(0,fe.jsxs)("div",{className:"grid justify-items-start gap-2 p-3 text-sm text-modiff-subtle-text",children:[(0,fe.jsx)(c,{size:22,"aria-hidden":"true"}),(0,fe.jsx)("p",{children:Xe?"No downloads match this search.":"No downloads to show. Choose a model in Supported to install it."}),Xe?(0,fe.jsx)(T,{onClick:()=>Qe(""),children:"Clear search"}):(0,fe.jsx)(T,{onClick:()=>ta("supported"),children:"Browse supported models"})]}):null,(0,fe.jsxs)("div",{className:"grid gap-4",children:["all"===Ye&&Fa&&za.length>0?(0,fe.jsx)(He,{count:za.length,testId:"model-manager-requirements",title:"Current workflow",children:(0,fe.jsx)("div",{className:"grid gap-1",children:za.map(e=>(0,fe.jsx)(de,{activeInstallCount:wa,actionTestId:`model-manager-install-requirement-${e.id}`,compact:!va,installProgress:he,onInstall:e=>Pa(e),onUseLocal:()=>Qe(e.repo.split("/").pop()??e.repo),requirement:e,testId:`model-manager-requirement-${e.id}`},e.id))})}):null,"all"===Ye||"installed"===Ye?(0,fe.jsx)(He,{count:ka.length,testId:"model-manager-installed",title:"Installed",children:0===_a.length?(0,fe.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:[Xe?"No installed models match this search.":Ue?"Reading the model inventory…":"No visible model artifacts indexed yet.",Xe?(0,fe.jsx)(T,{className:"mt-2",onClick:()=>Qe(""),children:"Clear search"}):null]}):_a.map(([e,a])=>(0,fe.jsxs)("div",{className:"grid gap-1",children:[(0,fe.jsx)("div",{className:"px-1 text-xs font-semibold text-modiff-subtle-text",children:e}),a.map(({item:e,source:a})=>(0,fe.jsxs)("article",{className:"flex min-h-10 items-center justify-between gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",title:we(e),children:[(0,fe.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,fe.jsx)(i,{size:15,className:"shrink-0 text-hf-yellow"}),(0,fe.jsx)("span",{className:"min-w-0 truncate text-sm font-semibold text-modiff-text",children:Ne(e)})]}),(0,fe.jsxs)("div",{className:"flex flex-none items-center gap-1",children:[Ce(e)?(0,fe.jsx)(ve,{tone:"default",title:_e(e.size),children:_e(e.size)}):null,(0,fe.jsx)(ve,{tone:"success",children:a})]})]},`${a}-${we(e)}`))]},e))}):null,"all"===Ye||"supported"===Ye?(0,fe.jsx)(He,{count:Ha.length,testId:"model-manager-supported",title:"Supported",children:0===Ta.length?(0,fe.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:["No supported models match the current filter.",Xe?(0,fe.jsx)(T,{className:"mt-2",onClick:()=>Qe(""),children:"Clear search"}):null]}):Ta.map(([e,a])=>(0,fe.jsxs)("div",{className:"grid gap-1",children:[(0,fe.jsx)("div",{className:"px-1 text-xs font-semibold text-modiff-subtle-text",children:e}),a.map(e=>{let a=D(W(e),e.modelType),t=ae(a),s=Oe[t],l=U(s);return(0,fe.jsx)(De,{activeInstallCount:wa,autoPlan:s,expertMode:va,form:a,focused:ya?.modelType===e.modelType||ya?.repo===e.defaultRepo||ya?.repo===l?.repo,installProgress:he,hfTokenConfigured:!!S?.config?.hf_token_configured,onConfigureHfToken:e=>{ma(e),ca(!0)},onInstall:Pa,profile:e,status:B(e,C,k,$)},`${e.modelType}:${e.defaultRepo}`)})]},e))}):null,va&&"all"===Ye?(0,fe.jsxs)(E,{className:"rounded-modiff-compact border border-modiff-border bg-modiff-panel p-3","data-testid":"model-manager-diagnostics",label:(0,fe.jsxs)("span",{className:"flex items-center justify-between gap-2",children:[(0,fe.jsxs)("span",{className:"flex items-center gap-2",children:[(0,fe.jsx)(m,{size:15,className:"text-hf-yellow"}),"Diagnostics"]}),(0,fe.jsx)(ve,{tone:"default",children:"Expert"})]}),buttonClassName:"p-0",panelClassName:"mt-3 grid gap-4",children:[(0,fe.jsx)("section",{className:"grid gap-2",children:(0,fe.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,fe.jsx)(T,{className:"h-8 px-2 text-xs",icon:(0,fe.jsx)(r,{size:14}),onClick:()=>{X()},children:"Update registry"}),(0,fe.jsx)(T,{className:"h-8 px-2 text-xs",icon:(0,fe.jsx)(o,{size:14}),onClick:()=>{let e={schemaVersion:1,capturedAt:(new Date).toISOString(),runtimeStatus:S,modelCacheDiagnostics:$,hfCache:C,localModels:k,workflow:{nodeCount:ze.length,missingNodes:Ca.map(e=>({id:e.id,label:e.data.label,module:e.data.module,action:e.data.action,key:`${e.data.module}.${e.data.action}`,disabled:!!e.data.uiState?.disabled}))}};navigator.clipboard.writeText(JSON.stringify(e,null,2)),R("Environment snapshot copied",{variant:"success",autoHideDuration:1800})},children:"Copy snapshot"})]})}),$?(0,fe.jsxs)("section",{className:"grid gap-1",children:[(0,fe.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Scanned folders"}),$.locations.map(e=>(0,fe.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,fe.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,fe.jsx)(ve,{tone:e.exists?!1===e.runnable?"warning":"success":"error",children:e.exists?!1===e.runnable?"Not runnable":"Indexed":"Missing"}),(0,fe.jsxs)("span",{className:"min-w-0 break-all text-xs font-semibold text-modiff-subtle-text",children:[e.label,": ",e.path||"default"]})]}),(0,fe.jsxs)(ye,{tone:!1===e.runnable?"warning":e.exists?"muted":"error",children:[e.repo_count??e.file_count??0," item(s),"," ",e.compatible_hf_repo_count??0," compatible HF repo(s),"," ",e.external_package_count??0," external package(s)",e.reason?` | ${e.reason}`:""]})]},`${e.label}-${e.path}`))]}):null,($?.external_model_packages?.length??0)>0?(0,fe.jsxs)("section",{className:"grid gap-1",children:[(0,fe.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"External packages"}),$?.external_model_packages?.slice(0,8).map(e=>(0,fe.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,fe.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,fe.jsx)(ve,{tone:e.runnable?"success":"warning",children:e.runnable?"Compatible":"Needs link"}),(0,fe.jsx)("span",{className:"min-w-0 break-all text-xs font-semibold text-modiff-subtle-text",children:e.label})]}),(0,fe.jsxs)(ye,{tone:e.runnable?"success":"warning",children:[e.format," | ",_e(e.model_file_bytes??0)," | ",e.path," | ",e.reason]})]},`${e.source}-${e.path}`))]}):null,($?.hf_compatible_external_repos?.length??0)>0?(0,fe.jsxs)("section",{className:"grid gap-1",children:[(0,fe.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"HF-compatible external repos"}),$?.hf_compatible_external_repos?.slice(0,8).map(e=>{let a=le(e.repo_id,C,k,$);return(0,fe.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,fe.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,fe.jsx)(ve,{tone:a.runnable?"success":"warning",children:a.runnable?"Runnable":"Link needed"}),(0,fe.jsx)("span",{className:"min-w-0 break-all text-xs font-semibold text-modiff-subtle-text",children:e.repo_id})]}),(0,fe.jsxs)(ye,{tone:a.runnable?"success":"warning",children:[e.path," | ",e.reason]})]},`${e.source}-${e.path}`)})]}):null,(0,fe.jsxs)("section",{className:"grid gap-1",children:[(0,fe.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Workflow node definitions"}),0===Ca.length?(0,fe.jsx)(ye,{tone:"success",children:"Every current graph node has a matching backend registry definition."}):Ca.slice(0,8).map(e=>(0,fe.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-red/70 bg-modiff-red/10 p-2",children:[(0,fe.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,fe.jsx)(ve,{tone:e.data.uiState?.disabled?"warning":"error",children:e.data.uiState?.disabled?"Disabled":"Missing"}),(0,fe.jsx)("span",{className:"min-w-0 flex-1 break-all text-xs font-semibold text-modiff-subtle-text",children:e.data.label||e.id})]}),(0,fe.jsxs)(ye,{tone:e.data.uiState?.disabled?"warning":"error",children:[e.data.module,".",e.data.action]}),(0,fe.jsxs)("div",{className:"mt-2 flex flex-wrap gap-2",children:[(0,fe.jsx)(T,{tone:"secondary",icon:(0,fe.jsx)(f,{size:14}),onClick:()=>((e,a)=>{Ee(e,{disabled:a,validationSeverity:a?"warning":void 0,validationMessage:a?"Disabled nodes are skipped during run export.":void 0}),R(a?"Node disabled for run export":"Node enabled for run export",{variant:"success",autoHideDuration:1800})})(e.id,!e.data.uiState?.disabled),children:e.data.uiState?.disabled?"Enable":"Disable"}),(0,fe.jsx)(T,{tone:"secondary",icon:(0,fe.jsx)(o,{size:14}),onClick:()=>Ua(e.id,`${e.data.module}.${e.data.action}`),children:"Copy key"})]})]},e.id))]}),(0,fe.jsxs)("section",{className:"grid gap-2",children:[(0,fe.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,fe.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Custom modules"}),(0,fe.jsxs)(ve,{tone:je.length>0?"success":"default",children:[je.length," module(s)"]}),(0,fe.jsx)(T,{className:"h-8 px-2 text-xs",disabled:null!==ra,icon:"refresh"===ra?(0,fe.jsx)(n,{size:14,className:"animate-spin"}):(0,fe.jsx)(r,{size:14}),onClick:()=>{(async()=>{oa("refresh");try{R((await Te()).message||"Custom modules refreshed",{variant:"success",autoHideDuration:2200})}catch(e){R(String(e),{variant:"error",autoHideDuration:7e3})}finally{oa(null)}})()},children:"Refresh"})]}),(0,fe.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,fe.jsx)(q,{"aria-label":"Custom module Git URL or local folder path",value:sa,onChange:e=>la(e.target.value),placeholder:"Git URL or local folder path",disabled:null!==ra}),(0,fe.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,fe.jsx)(q,{"aria-label":"Custom module folder name",value:na,onChange:e=>ia(e.target.value),placeholder:"Folder name (optional)",disabled:null!==ra,className:"min-w-40 flex-1"}),(0,fe.jsx)(T,{tone:"primary",icon:"install"===ra?(0,fe.jsx)(n,{size:14,className:"animate-spin"}):(0,fe.jsx)(c,{size:14}),disabled:null!==ra||!sa.trim(),onClick:()=>{(async()=>{let e=sa.trim();if(e){oa("install");try{R((await Me(e,na.trim()||void 0)).message||"Custom module installed",{variant:"success",autoHideDuration:2500}),la(""),ia("")}catch(e){R(String(e),{variant:"error",autoHideDuration:7e3})}finally{oa(null)}}else R("Add a Git URL or local folder path before installing.",{variant:"error",autoHideDuration:3e3})})()},children:"Install"})]}),(0,fe.jsx)(ye,{tone:$e?"warning":"muted",children:$e||"Installs are limited to the backend custom module folder. Disable moves a module to custom/.disabled instead of deleting it."})]}),(0,fe.jsx)("div",{className:"grid gap-1",children:0===je.length?(0,fe.jsx)(ye,{tone:"muted",children:"No custom modules are registered yet."}):je.map(e=>{let a=ra===`${e.name}:update`,t=ra===`${e.name}:${e.enabled?"disable":"enable"}`;return(0,fe.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,fe.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,fe.jsx)(ve,{tone:e.enabled?"success":"warning",children:e.enabled?"Enabled":"Disabled"}),(0,fe.jsxs)(ve,{tone:e.nodeCount>0?"success":"warning",children:[e.nodeCount," node(s)"]}),e.hasGit?(0,fe.jsx)(ve,{tone:"default",children:"Git"}):null,(0,fe.jsx)("span",{className:"min-w-0 flex-1 break-all text-xs font-semibold text-modiff-subtle-text",children:e.moduleKey})]}),(0,fe.jsxs)(ye,{tone:e.enabled&&0===e.nodeCount?"warning":"muted",children:[e.path,e.remote?` | ${e.remote}`:"",e.branch?` | ${e.branch}`:""]}),e.nodes.length>0?(0,fe.jsx)("div",{className:"mt-1 flex flex-wrap gap-1",children:e.nodes.slice(0,8).map(e=>(0,fe.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-panel px-1.5 py-0.5 text-xs text-modiff-subtle-text",children:e},e))}):null,(0,fe.jsxs)("div",{className:"mt-2 flex flex-wrap gap-2",children:[(0,fe.jsx)(T,{tone:"secondary",icon:a?(0,fe.jsx)(n,{size:14,className:"animate-spin"}):(0,fe.jsx)(r,{size:14}),disabled:null!==ra||!e.canUpdate,onClick:()=>{(async e=>{oa(`${e}:update`);try{R((await Ie(e)).message||`${e} updated`,{variant:"success",autoHideDuration:2600})}catch(e){R(String(e),{variant:"error",autoHideDuration:7e3})}finally{oa(null)}})(e.name)},children:"Update"}),(0,fe.jsx)(T,{tone:e.enabled?"danger":"secondary",icon:t?(0,fe.jsx)(n,{size:14,className:"animate-spin"}):(0,fe.jsx)(f,{size:14}),disabled:null!==ra,onClick:()=>{(async(e,a)=>{oa(`${e}:${a?"enable":"disable"}`);try{R((await Fe(e,a)).message||`${e} ${a?"enabled":"disabled"}`,{variant:"success",autoHideDuration:2600})}catch(e){R(String(e),{variant:"error",autoHideDuration:7e3})}finally{oa(null)}})(e.name,!e.enabled)},children:e.enabled?"Disable":"Enable"}),(0,fe.jsx)(T,{tone:"secondary",icon:(0,fe.jsx)(o,{size:14}),onClick:()=>Ua(e.name,e.moduleKey),children:"Copy key"})]})]},`${e.name}-${e.enabled?"enabled":"disabled"}`)})})]}),(0,fe.jsxs)("section",{className:"grid gap-1",children:[(0,fe.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,fe.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Raw Hugging Face cache"}),(0,fe.jsx)(T,{tone:"secondary",size:"compact",icon:Ue?(0,fe.jsx)(n,{size:13,className:"animate-spin"}):(0,fe.jsx)(t,{size:13}),disabled:Ue,onClick:()=>{(async()=>{Ea();try{let e=await z();if(!e.canCleanup){let a=e.blockers.slice(0,4).map(qa).join("; ");return void R(0!==e.eligibleFileCount||a?`Cleanup blocked: ${a||"no stale files are eligible"}.`:"No stale incomplete Hugging Face files are eligible for cleanup.",{variant:"default",autoHideDuration:6e3})}p({title:"Clean stale partial downloads?",message:`Remove ${e.eligibleFileCount} inactive .incomplete file(s) and reclaim ${_e(e.eligibleBytes)}? Only regular blob files older than one hour are included; installed snapshots and model revisions are not removed.`,confirmText:"Clean exact files",cancelText:"Cancel",onConfirm:()=>{p(null),(async e=>{Ea();try{let a=await w(e),t="number"==typeof a.removedFileCount?a.removedFileCount:0,s="number"==typeof a.removedBytes?a.removedBytes:0;await Aa(),R(`Removed ${t} stale partial file(s), reclaiming ${_e(s)}.`,{variant:"success",autoHideDuration:5e3})}catch(e){R(ie(e,"Could not clean stale Hugging Face downloads."),{variant:"error",autoHideDuration:7e3})}finally{La()}})(e.planHash)},onCancel:()=>p(null)})}catch(e){R(ie(e,"Could not inspect stale Hugging Face downloads."),{variant:"error",autoHideDuration:7e3})}finally{La()}})()},children:"Clean partial files"})]}),(0,fe.jsx)(ye,{tone:"muted",children:"Removes only hash-planned inactive .incomplete blobs older than one hour; installed revisions remain."}),0!==We.length||Ue?We.map(e=>{if(!Ce(e)){let a=we(e);return(0,fe.jsx)("article",{className:"border-b border-modiff-border py-3 last:border-b-0",children:(0,fe.jsx)("div",{className:"flex items-start gap-3",children:(0,fe.jsxs)("div",{className:"min-w-0 flex-1",children:[(0,fe.jsx)("h4",{className:"truncate text-sm font-semibold text-modiff-text",children:Se(a)}),(0,fe.jsx)("div",{className:"mt-1 flex flex-wrap gap-1",children:(0,fe.jsx)(ve,{tone:"default",children:"Indexed"})})]})})},a)}let s=Je.includes(e.id);return(0,fe.jsxs)("article",{className:"border-b border-modiff-border py-3 last:border-b-0",children:[(0,fe.jsxs)("div",{className:"flex items-start gap-3",children:[(0,fe.jsxs)("div",{className:"min-w-0 flex-1",children:[(0,fe.jsx)("h4",{className:"truncate text-sm font-semibold text-modiff-text",children:Se(e.id)}),(0,fe.jsxs)("div",{className:"mt-1 flex flex-wrap gap-1",children:[(0,fe.jsx)(ve,{tone:"default",children:_e(e.size)}),(0,fe.jsx)(ve,{tone:"default",children:new Date(1e3*e.last_accessed).toLocaleDateString()}),e.class_names.slice(0,5).map(e=>(0,fe.jsx)(ve,{tone:"default",children:e},e))]}),(0,fe.jsx)(T,{tone:"ghost",size:"compact",className:"mt-2 px-2 text-xs",onClick:()=>(e=>{Ze(a=>a.includes(e)?a.filter(a=>a!==e):[...a,e])})(e.id),icon:s?(0,fe.jsx)(a,{size:14}):(0,fe.jsx)(l,{size:14}),children:"Revisions"})]}),(0,fe.jsx)(b,{label:`Delete ${e.id}`,disabled:Ue,className:"border border-modiff-red text-modiff-red hover:bg-modiff-red/10 hover:text-modiff-red",onClick:()=>{Oa(e.revisions.map(e=>e.hash).join(","),`${e.id} model`)},children:(0,fe.jsx)(t,{size:15})})]}),s?(0,fe.jsx)("div",{className:"mt-2 space-y-1 pl-4",children:e.revisions.map(a=>(0,fe.jsxs)("div",{className:"flex items-center gap-2 text-xs text-modiff-subtle-text",children:[(0,fe.jsxs)("div",{className:"min-w-0 flex-1 break-words",children:[(0,fe.jsx)("strong",{className:"text-modiff-text",children:a.hash.slice(0,8)})," |"," ",new Date(1e3*a.last_modified).toLocaleString()," |"," ",_e(a.size)]}),(0,fe.jsx)(b,{label:`Delete revision ${a.hash.slice(0,8)}`,disabled:Ue,size:"compact",className:"border border-modiff-red text-modiff-red hover:bg-modiff-red/10 hover:text-modiff-red",onClick:()=>{Oa(a.hash,`${a.hash.slice(0,8)} revision of ${e.id}`)},children:(0,fe.jsx)(t,{size:13})})]},a.hash))}):null]},e.id)}):(0,fe.jsx)(ye,{tone:"muted",children:Xe?"No cache entries match the current filter.":"The local cache is empty."})]})]}):null]})]})]}),(0,fe.jsx)(ue,{open:!!ja.pending,policies:ja.pending?.policies??[],action:ja.pending?.action??"install",onCancel:ja.cancel,onConfirm:ja.confirm})]})}export{Re as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{En as a,Et as t,Ht as s,K as l,Nt as n,at as i,cr as o,dn as r,dr as d,kn as c,pn as m,rt as u,un as f,z as p}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ci as x,Eo as h,Fi as g,Ga as b,Ii as j,In as v,Jn as y,Li as w,Mo as N,Ms as C,Ns as k,Oo as $,Pi as _,Ps as S,S as T,Sa as R,Si as H,Sr as I,To as D,Vi as F,Vr as M,Wa as z,Wi as E,Za as A,Zt as L,an as P,bo as O,co as q,d as B,dn as V,gn as W,go as U,in as J,io as G,ji as K,l as Z,lo as Q,no as X,oc as Y,oo as ee,qa as ae,rn as te,so as se,uo as le,vo as ne,xn as ie,xo as oe,yn as re}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as de}from"./WorkflowArtifactRequirementRow.js?v=cc046c26d6fe7aa4";import{n as ce,t as me}from"./TemplateUsageTermsDialog.js?v=cc046c26d6fe7aa4";var ue=o();function fe({children:e,tone:a="muted"}){return(0,ue.jsx)("p",{className:v("break-words text-xs leading-5","muted"===a&&"text-modiff-subtle-text","success"===a&&"text-modiff-green","warning"===a&&"text-hf-orange","error"===a&&"text-modiff-red"),children:e})}var pe=e(d(),1),xe=D(),he=[{value:"all",label:"All models"},{value:"installed",label:"Installed"},{value:"supported",label:"Supported"},{value:"downloads",label:"Downloads"}],ge=(0,pe.lazy)(()=>x(()=>import(`./CustomExtensionsPanel.js?v=cc046c26d6fe7aa4`),[]));function be(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function je(e){if(!Array.isArray(e))throw Error("The Hugging Face cache response must be an array.");return e.map((e,a)=>{if(!be(e)||"string"!=typeof e.id||!Array.isArray(e.revisions))throw Error(`Hugging Face cache entry ${a+1} is invalid.`);if(e.revisions.some(e=>!be(e)||"string"!=typeof e.hash))throw Error(`Hugging Face cache entry ${a+1} has an invalid revision.`);return e})}var ve=["Image","Edit","Control","Video","Audio","Adapters","Components","Other"];function ye(e){return"default"===e?"neutral":e}function we({children:e,disabled:a,onClick:t,progress:s,testId:l,title:n,tone:i}){let o="string"==typeof e?e:void 0;return(0,ue.jsx)(L,{action:"Installing"===o?"installing":"Retry"===o?"retry":t?"install":"success"===i?"ready":"warning"===i||"error"===i?"missing":"details",className:"min-h-7 px-2 py-0.5 text-xs",disabled:a,label:e,onClick:t,progress:s,testId:l,title:n,tone:ye(i)})}function Ne(e){if("string"==typeof e)return e;if(e&&"object"==typeof e){let a=e;return String(a.id??a.repo_id??a.label??a.rel_path??a.path??JSON.stringify(e))}return String(e??"")}function Ce(e){let a=Ne(e);return a.replace(/\\/g,"/").split("/").filter(Boolean).slice(-2).join("/")||a}function ke(e){return!!(e&&"object"==typeof e&&"id"in e&&"revisions"in e)}function $e(e,a){let t=new Map;return ve.forEach(e=>t.set(e,[])),e.forEach(e=>t.get(a(e))?.push(e)),ve.map(e=>[e,t.get(e)??[]]).filter(([,e])=>e.length>0)}function _e(e){let a=X(e);return"spandrel"===a?"Spandrel":G(e)?"diffusers_accelerated"===a?"Accelerated":"Diffusers":"Legacy"}function Se(e){if(0===e)return"0 B";let a=Math.floor(Math.log(e)/Math.log(1024));return`${(e/1024**a).toFixed(2)} ${["B","KB","MB","GB","TB"][a]}`}function Te(e){let a=Ne(e),t=a.split("/"),s=t.pop()||a,l=t.join("/");return(0,ue.jsxs)(ue.Fragment,{children:[l?(0,ue.jsx)("span",{className:"text-modiff-subtle-text",children:l}):null,l?"/":null,(0,ue.jsx)("strong",{children:s})]})}function Re({children:e,count:a,testId:t,title:s}){return(0,ue.jsxs)("section",{className:"grid gap-2","data-testid":t,children:[(0,ue.jsxs)("div",{className:"flex items-center justify-between gap-2 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,ue.jsx)("span",{children:s}),(0,ue.jsx)(we,{tone:"default",children:a})]}),e]})}function He({activeInstallCount:e,autoPlan:a,expertMode:t,form:s,focused:l,hfTokenConfigured:n,installProgress:i,onConfigureHfToken:o,onInstall:r,profile:d,status:c}){let m=w(a,s),u=t?S(d,c):null,f=m&&u&&m.repo===u.repo?{...m,repair:u.repair,revision:u.revision,files:u.files}:m??u,p=f?.repo??d.defaultRepo,x=i[p],h=Q(x),b=q(x),y=b&&("huggingface_access_required"===x?.error_code||"access"===ee(x)),N=y||!n&&B(p),C=E(c.runnable,a,s),k=g(a,s),$=C?"Ready":f?.actionLabel??j(a,s),_=!C&&("Not suitable locally"===$||"Will not work on this machine"===$||"unsuitable"===k.state),T=C?"success":_?"error":"warning",R=y?"Review access":N?"Add HF token":C?"Ready":h?"Installing":b?"Retry":function(e){return"This should work"===e?"Estimated":"Community option"===e?"Community":"Not suitable locally"===e||"Will not work on this machine"===e?"Blocked":"Repair required"===e?"Repair":"Failed here before"===e?"Failed":"Expert only"===e?"Custom memory":"Needs setup"===e||"Install model"===e||"Install Auto artifact"===e?"Install":e}(f?.actionLabel??$),H=N?y&&x?.error?x.error:"This gated Hugging Face repository needs an accepted license and a configured read token.":C?c.reason:b&&x?.error?x.error:f?.reason??k.detail,I=!N&&!C&&f&&!h&&e<2,D=d.artifactSelections?.length?d.artifactLabel:null,F=D??d.label,M=D?d.defaultRepo:null;return(0,ue.jsx)("article",{className:v("rounded-modiff-compact border bg-modiff-bg p-2",l?"border-hf-yellow/80":"border-modiff-border"),"data-testid":`model-manager-supported-${d.modelType}`,"data-model-repo":d.defaultRepo,"data-model-manager-focused":l?"true":void 0,children:(0,ue.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,ue.jsxs)("div",{className:"min-w-0",children:[(0,ue.jsx)("div",{className:"truncate text-sm font-semibold text-modiff-text","data-testid":"model-manager-artifact-label",children:F}),M?(0,ue.jsx)("div",{className:"mt-0.5 truncate text-xs text-modiff-subtle-text",title:d.defaultRepo,children:M}):null,(0,ue.jsxs)("div",{className:"mt-1 flex flex-wrap gap-1",children:[(0,ue.jsx)(we,{tone:"default",children:d.surfaceCategory??d.family}),(0,ue.jsx)(we,{tone:"default",children:_e(d)}),"workflowOnly"===d.catalogVisibility?(0,ue.jsx)(we,{tone:"warning",children:"Legacy"}):null,f?.repair?(0,ue.jsx)(we,{tone:"warning",children:"Repair"}):null]})]}),(0,ue.jsx)(we,{disabled:h||!N&&!C&&!I,onClick:N?()=>o(p):I?()=>{f&&r(f,f.repair)}:void 0,progress:h?se(x):null,testId:`model-manager-install-${d.modelType}`,title:`${p} | ${H}`,tone:T,children:R})]})})}function Ie({opener:e,onClose:o}){let{setAlertOpener:d}=R(),x=y(e=>e.sid),g=H(e=>e.hfCache),j=H(e=>e.localModels),v=H(e=>e.modelCacheDiagnostics),S=H(e=>e.runtimeStatus),D=H(e=>e.nodesRegistry),E=H(e=>e.studioModelCapabilities),L=H(e=>e.studioModelCapabilitiesAuthoritative),B=H(e=>e.refreshModelIndexes),G=H(e=>e.fetchRegistry),X=H(e=>e.installHfModel),ee=H(e=>e.hfDownloadProgress),se=H(e=>e.reconcileHfDownloadProgress),be=H(e=>e.fetchCustomModules),ve=M(e=>e.nodes),ye=M(e=>e.setNodeUiState),_e=K(e=>e.form),Ie=K(e=>e.activeTemplateId),De=K(e=>e.graphBinding),Fe=K(e=>e.sourceOutputId),Me=K(e=>e.autoResourcePlans),ze=K(e=>e.setAutoResourcePlans),[Ee,Ae]=(0,pe.useState)(!1),Le=(0,pe.useRef)(0),[Pe,Oe]=(0,pe.useState)([]),[qe,Be]=(0,pe.useState)([]),[Ve,We]=(0,pe.useState)(""),[Ue,Je]=(0,pe.useState)("all"),Ge=(0,pe.useRef)(null),Ke=e=>{Je(e),Ge.current?.closest("[data-dialog-scroll-body]")?.scrollTo(0,0)},[Ze,Qe]=(0,pe.useState)(!1),[Xe,Ye]=(0,pe.useState)(null),[ea,aa]=(0,pe.useState)(""),[ta,sa]=(0,pe.useState)(!1),[la,na]=(0,pe.useState)(null),ia=ce(),oa=e?.focus,ra=Object.values(ee).filter(Q).length,da=Object.entries(ee).filter(([e,a])=>(Q(a)||q(a)||le(a))&&e.toLowerCase().includes(Ve.trim().toLowerCase())),ca=ve.filter(e=>{if("group"===e.data.type||"loop"===e.data.type)return!1;let a=`${e.data.module}.${e.data.action}`;return!D[a]}),ma=(0,pe.useMemo)(()=>function(e){let a=new Set;return e.filter(({item:e,source:t})=>{let s=`${t}:${Ne(e).toLowerCase()}`;return!a.has(s)&&(a.add(s),!0)})}([...Pe.map(e=>({source:"HF",item:e})),...j.map(e=>({source:"Local",item:e}))].filter(({item:e})=>{let a=Ve.trim().toLowerCase();return!a||Ne(e).toLowerCase().includes(a)})),[Ve,Pe,j]),ua=(0,pe.useMemo)(()=>$e(ma,({item:e})=>function(e){let a=Ne(e).toLowerCase();return a.includes("text_encoder")||a.includes("text encoder")||a.includes("vae")||a.includes("tokenizer")||a.includes("transformer")||a.includes("diffusion_model")||a.includes("clip")?"Components":a.includes("wan")||a.includes("video")?"Video":a.includes("ace")||a.includes("audio")?"Audio":a.includes("control")||a.includes("canny")||a.includes("depth")?"Control":a.includes("lora")||a.includes("adapter")?"Adapters":a.includes("edit")||a.includes("inpaint")||a.includes("fill")||a.includes("kontext")||a.includes("redux")?"Edit":a.includes("image")||a.includes("flux")||a.includes("z-image")||a.includes("qwen")?"Image":"Other"}(e)),[ma]),fa=(0,pe.useMemo)(()=>ua,[!0,ua]),pa=(0,pe.useMemo)(()=>b({currentModelType:oa?.modelType??_e.modelType,includeWorkflowOnly:!0}).flatMap(e=>{if(!L)return[e];let a=E.find(a=>a.modelType===e.modelType);return a?z({...e,defaultRepo:a.defaultRepo??e.defaultRepo,artifactLabel:a.artifactLabel??e.artifactLabel,executionStatus:a.executionStatus??e.executionStatus,revisionCandidates:a.revisionCandidates??e.revisionCandidates,downloadFiles:a.downloadFiles??e.downloadFiles,artifactSelections:a.artifactSelections??e.artifactSelections,optionalRuntimeRequirement:a.optionalRuntimeRequirement??e.optionalRuntimeRequirement}):[e]}).sort((e,a)=>{if(oa?.modelType===e.modelType)return-1;if(oa?.modelType===a.modelType)return 1;if(oa?.repo===e.defaultRepo)return-1;if(oa?.repo===a.defaultRepo)return 1;let t=String(e.label??e.modelType??e.defaultRepo??""),s=String(a.label??a.modelType??a.defaultRepo??"");return t.localeCompare(s)}),[!0,oa?.modelType,oa?.repo,_e.modelType,E,L]),xa=(0,pe.useMemo)(()=>{let e=Ve.trim().toLowerCase();return pa.filter(a=>`${a.label} ${a.defaultRepo} ${a.family} ${a.surfaceCategory??""}`.toLowerCase().includes(e))},[pa,Ve]),ha=(0,pe.useMemo)(()=>pa.map(e=>ae(A(e),e.modelType)),[pa]),ga=(0,pe.useMemo)(()=>ha.map(_),[ha]),ba=(0,pe.useMemo)(()=>$e(xa,e=>function(e){return"Audio"===e?"Audio":"Video"===e?"Video":"Control"===e?"Control":"Image Edit"===e?"Edit":"Utility"===e?"Adapters":"Image"===e?"Image":"Other"}(e.surfaceCategory)),[xa]);(0,pe.useEffect)(()=>{if(!oa)return;let e=window.setTimeout(()=>{document.querySelector('[data-model-manager-focused="true"]')?.scrollIntoView({behavior:"smooth",block:"center"})},80);return()=>window.clearTimeout(e)},[oa,xa.length]);let ja=oa?.label??xa.find(e=>e.modelType===oa?.modelType||e.defaultRepo===oa?.repo)?.label??oa?.repo,va=_(_e),ya=!!(oa||Ie||De||Fe),wa=(0,pe.useMemo)(()=>ya?I({autoResourcePlan:Me[va],form:_e,hfCache:g,includePipeline:!1,localModels:j,modelCacheDiagnostics:v}):[],[Me,g,ya,j,v,_e,va]),Na=(0,pe.useCallback)(()=>{Le.current+=1,Ae(!0)},[]),Ca=(0,pe.useCallback)(()=>{Le.current=Math.max(0,Le.current-1),0===Le.current&&Ae(!1)},[]),ka=(0,pe.useCallback)(async()=>{let e=xe.begin("fullCache");Na();try{let a=await $(`${Y.serverAddress}/hf_cache`,{signal:e.signal,timeoutMs:12e4,parse:je});e.isLatest()&&Oe(a)}catch(a){e.isLatest()&&console.error("Error fetching Hugging Face models:",a)}finally{e.finish(),Ca()}},[Na,Ca]),$a=(0,pe.useCallback)(async()=>{let e=await F(ha,ga),a={};e.forEach((e,t)=>{let s=e.planKey??ga[t];s&&(a[s]=e)}),ze(a)},[ze,ha,ga]),_a=e=>{let a="string"==typeof e.workflowId?` (${e.workflowId})`:"";switch(e.code){case"graph_execution_active":return"a graph is running";case"graph_execution_queued":return"a graph is queued";case"model_download_active":return"a model download is active";case"template_gallery_install_active":return"the Template Gallery is installing";case"canonical_dependency_open":return`template evidence is not turnover-complete${a}`;case"saved_workflow_dependency":return`a saved workflow still uses the model${a}`;case"recent_incomplete_download":return"an incomplete file is newer than one hour";default:return e.code.split("_").join(" ")}},Sa=async(e,a)=>{Na();try{let t=await O(e),s=!1;if(!t.canDelete){let a=new Set(["canonical_dependency_open","saved_workflow_dependency"]);if(!(t.blockers.length>0&&t.blockers.every(e=>a.has(e.code)))){let e=t.blockers.slice(0,4).map(_a).join("; ");return void N(`Deletion blocked: ${e||"the revision is not turnover-safe"}.`,{variant:"error",autoHideDuration:Math.max(7e3,70*e.length)})}if(t=await O(e,!0),s=!0,!t.canDelete){let e=t.blockers.slice(0,4).map(_a).join("; ");return void N(`Local eviction blocked: ${e||"the cache state changed"}.`,{variant:"error",autoHideDuration:Math.max(7e3,70*e.length)})}}let l=t.targets.map(e=>`${e.repoId}@${e.revision}${e.sizeBytes?` (${Se(e.sizeBytes)})`:""}`).join(", "),n=t.canonicalDependencies.length,i=t.savedWorkflowDependencies.length;d({title:s?"Free local model storage?":"Confirm safe model deletion",message:s?`Evict the local copy of ${a}? Exact cache target: ${l}. ${n} canonical definition${1===n?"":"s"} and ${i} saved workflow${1===i?"":"s"} will be kept, but they must download this exact model revision before they can run again.`:`Delete ${a}? Exact cache target: ${l}. ${n} canonical workflow ${1===n?"dependency":"dependencies"} verified turnover-complete. This action cannot be undone without downloading the model again.`,confirmText:s?"Evict local copy":"Delete exact revision",cancelText:"Cancel",onConfirm:()=>{d(null),(async(e,a,t=!1)=>{Na();try{await ne(e,a,t),await ka()}catch(e){console.error("Error deleting Hugging Face model:",e);let a=h(e,"Error deleting Hugging Face model.");N(a,{variant:"error",autoHideDuration:80*a.length})}finally{Ca()}})(e,t.planHash,s)},onCancel:()=>d(null)})}catch(e){let a=h(e,"Could not inspect model dependencies before deletion.");N(a,{variant:"error",autoHideDuration:80*a.length})}finally{Ca()}},Ta=async(e,a=!1)=>{let t="string"==typeof e?{repo:e,label:e,repair:a}:e;ia.request("install",Z(t.repo,t.revision),()=>(async(e,a=!1)=>{let t="string"==typeof e?{repo:e,label:e,repair:a}:e;try{await X(t.repo,x,{repair:t.repair,revision:t.revision,files:t.files}),await Promise.all([ka(),B(!0)]),await $a()}catch(e){console.error(e)}})(t,t.repair))},Ra=()=>{o(),Be([]),We(""),Je("all"),Qe(!1),aa(""),na(null),Oe([])};return(0,pe.useEffect)(()=>{ka(),$a(),be()},[!0,be,ka,$a]),(0,pe.useEffect)(()=>{if(!e||0===ra)return;let a=!1,t=async()=>{try{await se()}catch(e){}};t();let s=window.setInterval(()=>{t()},2e3);return()=>{a=!0,window.clearInterval(s)}},[ra,e,se]),(0,ue.jsxs)(ue.Fragment,{children:[(0,ue.jsxs)(J,{open:!!e,onClose:Ra,title:(0,ue.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,ue.jsx)(r,{size:17,className:"shrink-0 text-hf-yellow"}),(0,ue.jsx)("span",{className:"truncate",children:"Models"})]}),description:"Manage local artifacts and install models from Hugging Face Hub.",panelClassName:"max-w-4xl",bodyClassName:"!p-0",testId:"model-manager-dialog",toolbar:(0,ue.jsxs)("div",{className:"grid gap-3 p-3","data-testid":"model-manager-toolbar",children:[(0,ue.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,ue.jsx)(re,{"aria-label":"Filter models and artifacts",className:"min-w-0 flex-1 basis-56",controlSize:"normal",placeholder:"Search by model, repository or family",value:Ve,onChange:e=>We(e.target.value),onClear:()=>We("")}),(0,ue.jsx)(te,{icon:(0,ue.jsx)(n,{size:14}),"aria-expanded":Ze,"aria-controls":"model-manager-token-editor",onClick:()=>{Qe(e=>!e),aa(""),na(null)},children:S?.config?.hf_token_configured?"HF token ready":"Add HF token"}),(0,ue.jsx)(te,{loading:Ee,icon:(0,ue.jsx)(u,{size:14}),onClick:()=>{(async(e=!0)=>{Na();try{await Promise.all([ka(),B(e)]),await $a(),await be(),N("Models refreshed",{variant:"success",autoHideDuration:1800})}catch(e){N(h(e,"Could not refresh model health."),{variant:"error",autoHideDuration:6e3})}finally{Ca()}})(!0)},children:"Refresh"})]}),(0,ue.jsx)(ie,{"aria-label":"Model inventory sections",size:"dense",className:"flex-wrap",value:Ue,onValueChange:Ke,options:he.map(e=>({...e,label:"downloads"===e.value&&ra>0?`Downloads (${ra})`:e.label,id:`model-inventory-tab-${e.value}`,controls:`model-inventory-panel-${e.value}`}))})]}),footer:(0,ue.jsxs)(ue.Fragment,{children:[(0,ue.jsx)("span",{className:"mr-auto flex items-center text-xs text-modiff-subtle-text",role:"status","aria-live":"polite",children:Ee?"Refreshing model inventory…":`${ma.length} installed · ${xa.length} supported${Ve?" matches":""}`}),(0,ue.jsx)(te,{"data-testid":"model-manager-close",onClick:Ra,children:"Close"})]}),children:[Ze?(0,ue.jsxs)("div",{id:"model-manager-token-editor",className:"m-3 grid gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3",children:[(0,ue.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Hugging Face access"}),(0,ue.jsxs)("div",{className:"text-xs text-modiff-subtle-text",children:["Accept the"," ",(0,ue.jsx)("a",{className:"text-hf-yellow underline underline-offset-2",href:Xe?`https://huggingface.co/${Xe}`:"https://huggingface.co/models",target:"_blank",rel:"noreferrer",children:"repository terms"}),", create a"," ",(0,ue.jsx)("a",{className:"text-hf-yellow underline underline-offset-2",href:"https://huggingface.co/settings/tokens",target:"_blank",rel:"noreferrer",children:"read token"}),", then paste it below. The token is validated and stored on the backend; it is never returned to the browser."]}),(0,ue.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,ue.jsx)(W,{"aria-label":"Hugging Face access token",autoFocus:!0,autoComplete:"off",value:ea,onChange:e=>aa(e.target.value),placeholder:"hf_...",disabled:ta,className:"min-w-0 flex-1 basis-56"}),(0,ue.jsx)(te,{tone:"primary",disabled:ta||!ea.trim(),loading:ta,icon:(0,ue.jsx)(n,{size:14}),onClick:()=>{(async()=>{let e=ea.trim();if(e){sa(!0),na(null);try{await $(`${Y.serverAddress}/hf_token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:e}),timeoutMs:3e4}),aa(""),Qe(!1),await B(!0),N("Hugging Face token saved. Retry the model download.",{variant:"success",autoHideDuration:3500})}catch(e){na(h(e,"Could not validate the Hugging Face token."))}finally{sa(!1)}}else na("Paste a Hugging Face read token first.")})()},children:"Save token"}),(0,ue.jsx)(te,{disabled:ta,onClick:()=>{Qe(!1),aa(""),na(null)},children:"Cancel"})]}),la?(0,ue.jsx)(fe,{tone:"warning",children:la}):null]}):null,he.filter(e=>e.value!==Ue).map(e=>(0,ue.jsx)("div",{id:`model-inventory-panel-${e.value}`,role:"tabpanel","aria-labelledby":`model-inventory-tab-${e.value}`,hidden:!0},e.value)),(0,ue.jsxs)("div",{ref:Ge,className:"p-3",id:`model-inventory-panel-${Ue}`,role:"tabpanel","aria-labelledby":`model-inventory-tab-${Ue}`,children:[ja?(0,ue.jsxs)("div",{className:"mb-3 flex flex-wrap items-center gap-2 rounded-modiff-compact border border-hf-yellow/70 bg-hf-yellow/10 p-2","data-testid":"model-manager-focus",children:[(0,ue.jsx)(we,{tone:"warning",children:"Focused"}),(0,ue.jsx)("span",{className:"min-w-0 truncate text-sm font-semibold text-modiff-text",children:ja})]}):null,("all"===Ue||"downloads"===Ue)&&da.length>0?(0,ue.jsxs)("section",{className:"mb-3 grid gap-2","data-testid":"model-manager-downloads",children:[(0,ue.jsxs)("div",{className:"flex items-center justify-between gap-2 text-xs font-semibold uppercase text-modiff-subtle-text",children:[(0,ue.jsx)("span",{children:"Downloads"}),(0,ue.jsxs)(we,{tone:ra>0?"warning":"default",children:[ra," active"]})]}),da.map(([e,a])=>(0,ue.jsx)(T,{repoId:e,progress:a,compact:!1,testId:`model-manager-download-${e}`},e))]}):null,"downloads"===Ue&&0===da.length?(0,ue.jsxs)("div",{className:"grid justify-items-start gap-2 p-3 text-sm text-modiff-subtle-text",children:[(0,ue.jsx)(f,{size:22,"aria-hidden":"true"}),(0,ue.jsx)("p",{children:Ve?"No downloads match this search.":"No downloads to show. Choose a model in Supported to install it."}),Ve?(0,ue.jsx)(te,{onClick:()=>We(""),children:"Clear search"}):(0,ue.jsx)(te,{onClick:()=>Ke("supported"),children:"Browse supported models"})]}):null,(0,ue.jsxs)("div",{className:"grid gap-4",children:["all"===Ue&&ya&&wa.length>0?(0,ue.jsx)(Re,{count:wa.length,testId:"model-manager-requirements",title:"Current workflow",children:(0,ue.jsx)("div",{className:"grid gap-1",children:wa.map(e=>(0,ue.jsx)(de,{activeInstallCount:ra,actionTestId:`model-manager-install-requirement-${e.id}`,compact:!1,installProgress:ee,onInstall:e=>Ta(e),onUseLocal:()=>We(e.repo.split("/").pop()??e.repo),requirement:e,testId:`model-manager-requirement-${e.id}`},e.id))})}):null,"all"===Ue||"installed"===Ue?(0,ue.jsx)(Re,{count:ma.length,testId:"model-manager-installed",title:"Installed",children:0===fa.length?(0,ue.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:[Ve?"No installed models match this search.":Ee?"Reading the model inventory…":"No visible model artifacts indexed yet.",Ve?(0,ue.jsx)(te,{className:"mt-2",onClick:()=>We(""),children:"Clear search"}):null]}):fa.map(([e,a])=>(0,ue.jsxs)("div",{className:"grid gap-1",children:[(0,ue.jsx)("div",{className:"px-1 text-xs font-semibold text-modiff-subtle-text",children:e}),a.map(({item:e,source:a})=>(0,ue.jsxs)("article",{className:"flex min-h-10 items-center justify-between gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",title:Ne(e),children:[(0,ue.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,ue.jsx)(s,{size:15,className:"shrink-0 text-hf-yellow"}),(0,ue.jsx)("span",{className:"min-w-0 truncate text-sm font-semibold text-modiff-text",children:Ce(e)})]}),(0,ue.jsxs)("div",{className:"flex flex-none items-center gap-1",children:[ke(e)?(0,ue.jsx)(we,{tone:"default",title:Se(e.size),children:Se(e.size)}):null,(0,ue.jsx)(we,{tone:"success",children:a})]})]},`${a}-${Ne(e)}`))]},e))}):null,"all"===Ue||"supported"===Ue?(0,ue.jsx)(Re,{count:xa.length,testId:"model-manager-supported",title:"Supported",children:0===ba.length?(0,ue.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:["No supported models match the current filter.",Ve?(0,ue.jsx)(te,{className:"mt-2",onClick:()=>We(""),children:"Clear search"}):null]}):ba.map(([e,a])=>(0,ue.jsxs)("div",{className:"grid gap-1",children:[(0,ue.jsx)("div",{className:"px-1 text-xs font-semibold text-modiff-subtle-text",children:e}),a.map(e=>{let a=ae(A(e),e.modelType),t=_(a),s=Me[t],l=w(s);return(0,ue.jsx)(He,{activeInstallCount:ra,autoPlan:s,expertMode:!0,form:a,focused:oa?.modelType===e.modelType||oa?.repo===e.defaultRepo||oa?.repo===l?.repo,installProgress:ee,hfTokenConfigured:!!S?.config?.hf_token_configured,onConfigureHfToken:e=>{Ye(e),Qe(!0)},onInstall:Ta,profile:e,status:k(e,g,j,v)},`${e.modelType}:${e.defaultRepo}`)})]},e))}):null,"all"===Ue?(0,ue.jsxs)(V,{className:"rounded-modiff-compact border border-modiff-border bg-modiff-panel p-3","data-testid":"model-manager-diagnostics",label:(0,ue.jsxs)("span",{className:"flex items-center justify-between gap-2",children:[(0,ue.jsxs)("span",{className:"flex items-center gap-2",children:[(0,ue.jsx)(l,{size:15,className:"text-hf-yellow"}),"Diagnostics"]}),(0,ue.jsx)(we,{tone:"default",children:"Developer"})]}),buttonClassName:"p-0",panelClassName:"mt-3 grid gap-4",children:[(0,ue.jsx)("section",{className:"grid gap-2",children:(0,ue.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,ue.jsx)(te,{className:"h-8 px-2 text-xs",icon:(0,ue.jsx)(u,{size:14}),onClick:()=>{G()},children:"Update registry"}),(0,ue.jsx)(te,{className:"h-8 px-2 text-xs",icon:(0,ue.jsx)(m,{size:14}),onClick:()=>{let e={schemaVersion:1,capturedAt:(new Date).toISOString(),runtimeStatus:S,modelCacheDiagnostics:v,hfCache:g,localModels:j,workflow:{nodeCount:ve.length,missingNodes:ca.map(e=>({id:e.id,label:e.data.label,module:e.data.module,action:e.data.action,key:`${e.data.module}.${e.data.action}`,disabled:!!e.data.uiState?.disabled}))}};navigator.clipboard.writeText(JSON.stringify(e,null,2)),N("Environment snapshot copied",{variant:"success",autoHideDuration:1800})},children:"Copy snapshot"})]})}),v?(0,ue.jsxs)("section",{className:"grid gap-1",children:[(0,ue.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Scanned folders"}),v.locations.map(e=>(0,ue.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,ue.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,ue.jsx)(we,{tone:e.exists?!1===e.runnable?"warning":"success":"error",children:e.exists?!1===e.runnable?"Not runnable":"Indexed":"Missing"}),(0,ue.jsxs)("span",{className:"min-w-0 break-all text-xs font-semibold text-modiff-subtle-text",children:[e.label,": ",e.path||"default"]})]}),(0,ue.jsxs)(fe,{tone:!1===e.runnable?"warning":e.exists?"muted":"error",children:[e.repo_count??e.file_count??0," item(s),"," ",e.compatible_hf_repo_count??0," compatible HF repo(s),"," ",e.external_package_count??0," external package(s)",e.reason?` | ${e.reason}`:""]})]},`${e.label}-${e.path}`))]}):null,(v?.external_model_packages?.length??0)>0?(0,ue.jsxs)("section",{className:"grid gap-1",children:[(0,ue.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"External packages"}),v?.external_model_packages?.slice(0,8).map(e=>(0,ue.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,ue.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,ue.jsx)(we,{tone:e.runnable?"success":"warning",children:e.runnable?"Compatible":"Needs link"}),(0,ue.jsx)("span",{className:"min-w-0 break-all text-xs font-semibold text-modiff-subtle-text",children:e.label})]}),(0,ue.jsxs)(fe,{tone:e.runnable?"success":"warning",children:[e.format," | ",Se(e.model_file_bytes??0)," | ",e.path," | ",e.reason]})]},`${e.source}-${e.path}`))]}):null,(v?.hf_compatible_external_repos?.length??0)>0?(0,ue.jsxs)("section",{className:"grid gap-1",children:[(0,ue.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"HF-compatible external repos"}),v?.hf_compatible_external_repos?.slice(0,8).map(e=>{let a=C(e.repo_id,g,j,v);return(0,ue.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,ue.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,ue.jsx)(we,{tone:a.runnable?"success":"warning",children:a.runnable?"Runnable":"Link needed"}),(0,ue.jsx)("span",{className:"min-w-0 break-all text-xs font-semibold text-modiff-subtle-text",children:e.repo_id})]}),(0,ue.jsxs)(fe,{tone:a.runnable?"success":"warning",children:[e.path," | ",e.reason]})]},`${e.source}-${e.path}`)})]}):null,(0,ue.jsxs)("section",{className:"grid gap-1",children:[(0,ue.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Workflow node definitions"}),0===ca.length?(0,ue.jsx)(fe,{tone:"success",children:"Every current graph node has a matching backend registry definition."}):ca.slice(0,8).map(e=>(0,ue.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-red/70 bg-modiff-red/10 p-2",children:[(0,ue.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,ue.jsx)(we,{tone:e.data.uiState?.disabled?"warning":"error",children:e.data.uiState?.disabled?"Disabled":"Missing"}),(0,ue.jsx)("span",{className:"min-w-0 flex-1 break-all text-xs font-semibold text-modiff-subtle-text",children:e.data.label||e.id})]}),(0,ue.jsxs)(fe,{tone:e.data.uiState?.disabled?"warning":"error",children:[e.data.module,".",e.data.action]}),(0,ue.jsxs)("div",{className:"mt-2 flex flex-wrap gap-2",children:[(0,ue.jsx)(te,{tone:"secondary",icon:(0,ue.jsx)(i,{size:14}),onClick:()=>((e,a)=>{ye(e,{disabled:a,validationSeverity:a?"warning":void 0,validationMessage:a?"Disabled nodes are skipped during run export.":void 0}),N(a?"Node disabled for run export":"Node enabled for run export",{variant:"success",autoHideDuration:1800})})(e.id,!e.data.uiState?.disabled),children:e.data.uiState?.disabled?"Enable":"Disable"}),(0,ue.jsx)(te,{tone:"secondary",icon:(0,ue.jsx)(m,{size:14}),onClick:()=>((e,a)=>{navigator.clipboard.writeText(`${e}\n${a}`),N("Missing node key copied",{variant:"success",autoHideDuration:1600})})(e.id,`${e.data.module}.${e.data.action}`),children:"Copy key"})]})]},e.id))]}),(0,ue.jsx)(pe.Suspense,{fallback:null,children:(0,ue.jsx)(ge,{})}),(0,ue.jsxs)("section",{className:"grid gap-1",children:[(0,ue.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,ue.jsx)("h3",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:"Raw Hugging Face cache"}),(0,ue.jsx)(te,{tone:"secondary",size:"compact",icon:Ee?(0,ue.jsx)(t,{size:13,className:"animate-spin"}):(0,ue.jsx)(p,{size:13}),disabled:Ee,onClick:()=>{(async()=>{Na();try{let e=await oe();if(!e.canCleanup){let a=e.blockers.slice(0,4).map(_a).join("; ");return void N(0!==e.eligibleFileCount||a?`Cleanup blocked: ${a||"no stale files are eligible"}.`:"No stale incomplete Hugging Face files are eligible for cleanup.",{variant:"default",autoHideDuration:6e3})}d({title:"Clean stale partial downloads?",message:`Remove ${e.eligibleFileCount} inactive .incomplete file(s) and reclaim ${Se(e.eligibleBytes)}? Only regular blob files older than one hour are included; installed snapshots and model revisions are not removed.`,confirmText:"Clean exact files",cancelText:"Cancel",onConfirm:()=>{d(null),(async e=>{Na();try{let a=await U(e),t="number"==typeof a.removedFileCount?a.removedFileCount:0,s="number"==typeof a.removedBytes?a.removedBytes:0;await ka(),N(`Removed ${t} stale partial file(s), reclaiming ${Se(s)}.`,{variant:"success",autoHideDuration:5e3})}catch(e){N(h(e,"Could not clean stale Hugging Face downloads."),{variant:"error",autoHideDuration:7e3})}finally{Ca()}})(e.planHash)},onCancel:()=>d(null)})}catch(e){N(h(e,"Could not inspect stale Hugging Face downloads."),{variant:"error",autoHideDuration:7e3})}finally{Ca()}})()},children:"Clean partial files"})]}),(0,ue.jsx)(fe,{tone:"muted",children:"Removes only hash-planned inactive .incomplete blobs older than one hour; installed revisions remain."}),0!==Pe.length||Ee?Pe.map(e=>{if(!ke(e)){let a=Ne(e);return(0,ue.jsx)("article",{className:"border-b border-modiff-border py-3 last:border-b-0",children:(0,ue.jsx)("div",{className:"flex items-start gap-3",children:(0,ue.jsxs)("div",{className:"min-w-0 flex-1",children:[(0,ue.jsx)("h4",{className:"truncate text-sm font-semibold text-modiff-text",children:Te(a)}),(0,ue.jsx)("div",{className:"mt-1 flex flex-wrap gap-1",children:(0,ue.jsx)(we,{tone:"default",children:"Indexed"})})]})})},a)}let t=qe.includes(e.id);return(0,ue.jsxs)("article",{className:"border-b border-modiff-border py-3 last:border-b-0",children:[(0,ue.jsxs)("div",{className:"flex items-start gap-3",children:[(0,ue.jsxs)("div",{className:"min-w-0 flex-1",children:[(0,ue.jsx)("h4",{className:"truncate text-sm font-semibold text-modiff-text",children:Te(e.id)}),(0,ue.jsxs)("div",{className:"mt-1 flex flex-wrap gap-1",children:[(0,ue.jsx)(we,{tone:"default",children:Se(e.size)}),(0,ue.jsx)(we,{tone:"default",children:new Date(1e3*e.last_accessed).toLocaleDateString()}),e.class_names.slice(0,5).map(e=>(0,ue.jsx)(we,{tone:"default",children:e},e))]}),(0,ue.jsx)(te,{tone:"ghost",size:"compact",className:"mt-2 px-2 text-xs",onClick:()=>(e=>{Be(a=>a.includes(e)?a.filter(a=>a!==e):[...a,e])})(e.id),icon:t?(0,ue.jsx)(a,{size:14}):(0,ue.jsx)(c,{size:14}),children:"Revisions"})]}),(0,ue.jsx)(P,{label:`Delete ${e.id}`,disabled:Ee,className:"border border-modiff-red text-modiff-red hover:bg-modiff-red/10 hover:text-modiff-red",onClick:()=>{Sa(e.revisions.map(e=>e.hash).join(","),`${e.id} model`)},children:(0,ue.jsx)(p,{size:15})})]}),t?(0,ue.jsx)("div",{className:"mt-2 space-y-1 pl-4",children:e.revisions.map(a=>(0,ue.jsxs)("div",{className:"flex items-center gap-2 text-xs text-modiff-subtle-text",children:[(0,ue.jsxs)("div",{className:"min-w-0 flex-1 break-words",children:[(0,ue.jsx)("strong",{className:"text-modiff-text",children:a.hash.slice(0,8)})," |"," ",new Date(1e3*a.last_modified).toLocaleString()," |"," ",Se(a.size)]}),(0,ue.jsx)(P,{label:`Delete revision ${a.hash.slice(0,8)}`,disabled:Ee,size:"compact",className:"border border-modiff-red text-modiff-red hover:bg-modiff-red/10 hover:text-modiff-red",onClick:()=>{Sa(a.hash,`${a.hash.slice(0,8)} revision of ${e.id}`)},children:(0,ue.jsx)(p,{size:13})})]},a.hash))}):null]},e.id)}):(0,ue.jsx)(fe,{tone:"muted",children:Ve?"No cache entries match the current filter.":"The local cache is empty."})]})]}):null]})]})]}),(0,ue.jsx)(me,{open:!!ia.pending,policies:ia.pending?.policies??[],action:ia.pending?.action??"install",onCancel:ia.cancel,onConfirm:ia.confirm})]})}export{Ie as default}; \ No newline at end of file diff --git a/web/assets/NodeDiscoveryFilters.js b/web/assets/NodeDiscoveryFilters.js new file mode 100644 index 00000000..bfaf62af --- /dev/null +++ b/web/assets/NodeDiscoveryFilters.js @@ -0,0 +1 @@ +import{cr as e}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Q as a,cn as l}from"./studio-templates.js?v=cc046c26d6fe7aa4";var o=e();function n(){let e=a(e=>e.view),n=a(e=>e.setView),r="advanced"===e||"all"===e,s="experimental"===e||"all"===e;return(0,o.jsxs)("div",{className:"flex flex-wrap gap-x-4 gap-y-1 px-3 pb-2",role:"group","aria-label":"Node discovery options",children:[(0,o.jsx)(l,{label:"Show implementation nodes",checked:r,onCheckedChange:e=>n(e?s?"all":"advanced":s?"experimental":"common")}),(0,o.jsx)(l,{label:"Show experimental nodes",checked:s,onCheckedChange:e=>n(e?r?"all":"experimental":r?"advanced":"common")})]})}export{n as t}; \ No newline at end of file diff --git a/web/assets/NodeInspectionDetails.js b/web/assets/NodeInspectionDetails.js new file mode 100644 index 00000000..c25451d7 --- /dev/null +++ b/web/assets/NodeInspectionDetails.js @@ -0,0 +1 @@ +import{cr as e}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$s as t,Cs as s,Fo as i,Io as a,Jo as n,Si as r,Ts as o,Vr as d,Xo as l}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as c}from"./operationAuthoring.js?v=cc046c26d6fe7aa4";import{n as p}from"./presetDiff.js?v=cc046c26d6fe7aa4";var u=e();function h({node:e,section:h}){let x=d(e=>e.edges),m=d(t=>t.nodes.find(t=>t.id===e.data.blockProjectionOwnerId)),f=r(t=>t.customModules.find(t=>t.moduleKey===e.data.module)),j=n(e),g=e.data.blockInstanceV2,b=g?.definitionSnapshot.source,v=`${e.data.module}.${e.data.action}`,N=e.data.modularDiffusersCatalogNode??m?.data.blockInstanceV2?.effectiveGraph.nodes.find(t=>t.nodeId===e.data.blockProjectionNodeId)?.modularDiffusers;if("interface"===h){let t=g?l(g):{inputs:Object.fromEntries(Object.entries(e.data.params).filter(([,e])=>"output"!==e.display&&("input"===e.display||e.isInput))),outputs:Object.fromEntries(Object.entries(e.data.params).filter(([,e])=>"output"===e.display))};if(o(g)){let s=i(e);t.inputs=Object.fromEntries(Object.entries(s).filter(([,e])=>"output"!==e.display)),t.outputs=Object.fromEntries(Object.entries(s).filter(([,e])=>"output"===e.display))}return(0,u.jsxs)("div",{className:"grid gap-3 text-xs text-modiff-text",children:[g?(0,u.jsx)("p",{children:o(g)?"Sockets of this node. Its execution stages remain separate.":"Public sockets of this Block instance. Its internal graph remains unchanged."}):null,["inputs","outputs"].map(s=>(0,u.jsxs)("div",{className:"grid gap-2",children:[(0,u.jsx)("h3",{className:"font-semibold",children:"inputs"===s?"Inputs":"Outputs"}),Object.entries(t[s]).map(([t,i])=>{let n=x.filter(i=>"inputs"===s?i.target===e.id&&i.targetHandle===t:i.source===e.id&&i.sourceHandle===t);return(0,u.jsxs)("div",{className:"break-words",children:[(0,u.jsxs)("p",{children:[i.label||t," · ",Array.isArray(i.type)?i.type.join(" | "):i.type||"Any",i.required?" · Required":""]}),(0,u.jsxs)("p",{className:"text-modiff-subtle-text",children:["Socket: ",t]}),a(t)?(0,u.jsx)("p",{className:"text-modiff-subtle-text",children:"Optional adaptation socket — connecting prepares the supported stage; unused sockets do not execute."}):null,n.length?n.map(e=>(0,u.jsx)("p",{className:"text-modiff-subtle-text",children:"inputs"===s?`From ${e.source}.${e.sourceHandle}`:`To ${e.target}.${e.targetHandle}`},e.id)):(0,u.jsx)("p",{className:"text-modiff-subtle-text",children:"Not connected"})]},t)}),Object.keys(t[s]).length?null:(0,u.jsxs)("p",{children:["No declared ",s,"."]})]},s))]})}return"docs"===h?(0,u.jsxs)("div",{className:"grid gap-3 break-words text-xs text-modiff-text",children:[(0,u.jsx)("p",{className:"whitespace-pre-wrap",children:g?.definitionSnapshot.description||e.data.description||"No description supplied by this node."}),g?(0,u.jsx)("p",{children:o(g)?`Use Separate ${s(g)?"guidance":"encoding"} stages to edit individual stages on the canvas.`:"Use Expand Block on the canvas to edit this composition. This does not edit the Python implementation."}):null,Object.entries(e.data.params).filter(([,e])=>e.description&&!e.hidden).map(([e,t])=>(0,u.jsxs)("div",{children:[(0,u.jsx)("h3",{className:"font-semibold",children:t.label||e}),(0,u.jsx)("p",{className:"whitespace-pre-wrap text-modiff-subtle-text",children:t.description})]},e)),g?.effectiveInterface.controls.filter(e=>e.help).map(e=>(0,u.jsxs)("div",{children:[(0,u.jsx)("h3",{className:"font-semibold",children:e.label}),(0,u.jsx)("p",{className:"whitespace-pre-wrap text-modiff-subtle-text",children:e.help})]},e.controlId))]}):"run"===h?(0,u.jsxs)("div",{className:"grid gap-2 break-words text-xs text-modiff-text",children:[(0,u.jsx)("p",{children:e.data.executionStatus?`Status: ${e.data.executionStatus}`:"No execution recorded for this node."}),(0,u.jsx)("p",{children:e.data.isCached?"Cached outputs reported.":"No cached outputs reported."}),(0,u.jsx)("p",{className:"text-modiff-subtle-text",children:"Reuse on the next run still requires matching execution inputs."}),e.data.activeTaskId?(0,u.jsxs)("p",{children:["Task: ",e.data.activeTaskId]}):null,e.data.executionPhase?(0,u.jsxs)("p",{children:["Phase: ",e.data.executionPhase]}):null,e.data.progressMessage?(0,u.jsx)("p",{children:e.data.progressMessage}):null,e.data.uiState?.validationMessage?(0,u.jsxs)("p",{children:["Validation: ",e.data.uiState.validationMessage]}):null,e.data.uiState?.errorMessage?(0,u.jsxs)("p",{children:["Error: ",e.data.uiState.errorMessage]}):null,(0,u.jsx)("p",{className:"text-modiff-subtle-text",children:"These are the node's current diagnostics. Previous runs remain in history."})]}):(0,u.jsxs)("div",{className:"grid gap-3 break-words text-xs text-modiff-text",children:[(0,u.jsx)("p",{children:v}),(0,u.jsxs)("p",{className:"text-modiff-subtle-text",children:["Canvas node: ",e.id]}),j?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)("p",{children:[j.operation.pipelineClass," · ",j.operation.task]}),(0,u.jsx)("p",{children:j.operation.blockName??("loader"===j.operation.decomposition?"Model loader":"integrated"===j.operation.decomposition?"Model operation":"pipeline"===j.operation.decomposition?"Whole pipeline call":"Execution stage")}),(0,u.jsx)("dl",{className:"grid gap-2",children:Object.entries(j.defaults).map(([s,i])=>{let a=x.find(t=>t.target===e.id&&t.targetHandle===s);return(0,u.jsxs)("div",{children:[(0,u.jsxs)("dt",{children:[s," —"," ",a?`Connected from ${a.source}.${a.sourceHandle}; saved fallback`:t(i,c(e.data.params[s]))?"Selected contract default":"User override"]}),(0,u.jsx)("dd",{className:"text-modiff-subtle-text",children:p(c(e.data.params[s]))})]},s)})}),j.retained.length?(0,u.jsxs)("div",{children:[(0,u.jsx)("p",{children:"Retained settings — excluded from execution:"}),(0,u.jsx)("ul",{className:"list-inside list-disc",children:j.retained.map((e,t)=>(0,u.jsxs)("li",{children:[e.pipeline," / ",e.field,": ",p(e.value),". ",e.reason]},t))})]}):null]}):null,g&&b?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)("p",{children:["Editable graph composition · ",g.effectiveGraph.nodes.length," internal nodes ·"," ",g.effectiveGraph.edges.length," internal connections"]}),(0,u.jsxs)("p",{children:["Definition: ",g.definitionSnapshot.definitionId]}),(0,u.jsxs)("p",{children:["Revision: ",g.definitionSnapshot.contentHash]}),(0,u.jsxs)("p",{children:["Source: ",b.kind," ",b.library," ",b.pipelineClass]}),b.repository?(0,u.jsxs)("p",{children:["Repository: ",b.repository," · ",b.repositoryRevision??"Revision not supplied"]}):null,b.libraryRevision?(0,u.jsxs)("p",{children:["Library revision: ",b.libraryRevision]}):null,(0,u.jsxs)("p",{children:["Customization: ",g.customization.state.replace(/_/gu," ")]})]}):null,N?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)("p",{children:["Pinned upstream implementation: ",N.blockClass??N.blocksClass]}),(0,u.jsxs)("p",{children:[N.pipelineClass," · ",N.workflowId]}),(0,u.jsxs)("p",{children:["Library revision: ",N.libraryRevision]}),N.placementPath?.length?(0,u.jsxs)("p",{children:["Placement: ",N.placementPath.join(" / ")]}):null,N.blockContractHash?(0,u.jsxs)("p",{children:["Contract: ",N.blockContractHash]}):null]}):null,f?(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)("p",{children:["Custom Python implementation · ",f.name," · ",f.status]}),(0,u.jsxs)("p",{children:["Source: ",f.path]}),f.revision?(0,u.jsxs)("p",{children:["Revision: ",f.revision]}):null,(0,u.jsxs)("p",{children:["Inspected code: ",f.codeHash??"Not inspected"]}),f.diagnostic?(0,u.jsx)("p",{children:f.diagnostic}):null,(0,u.jsx)("p",{className:"text-modiff-subtle-text",children:"Manage source and approvals in Custom nodes. Inspection grants no code permission."})]}):null,g||j||f||N?null:(0,u.jsx)("p",{className:"text-modiff-subtle-text",children:"Registered node implementation. Its controls and sockets come from the backend contract."})]})}export{h as default}; \ No newline at end of file diff --git a/web/assets/NodeInspectorDialog.js b/web/assets/NodeInspectorDialog.js new file mode 100644 index 00000000..43cf1475 --- /dev/null +++ b/web/assets/NodeInspectorDialog.js @@ -0,0 +1 @@ +import{n as o,r as s}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as e,dr as n}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ei as a,Ts as t,Vr as r,in as d,ji as i}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as l}from"./GraphNodeInputs.js?v=cc046c26d6fe7aa4";var c=o({default:()=>m}),p=s(n(),1),f=e();function m({nodeId:o,onClose:s}){let[e]=(0,p.useState)(a),n=r(s=>s.nodes.find(s=>s.id===o)),c=i(o=>o.activeWorkflowTabId),m=i(o=>o.workflowCanvasEpoch),u=!(!n?.selected||c!==e.workflowTabId||m!==e.canvasEpoch);return(0,p.useEffect)(()=>{u||s()},[u,s]),u&&n?(0,f.jsx)(d,{open:!0,title:n.data.blockInstanceV2&&!t(n.data.blockInstanceV2)?"Block inspector":"Node inspector",onClose:s,children:(0,f.jsx)(l,{nodes:[n],selectedNodes:[n],workflowId:c,candidates:[],pinnedInputs:[],pinnedIds:[],onTogglePin:()=>{},controlIdPrefix:"dialog-inspector"})}):null}export{c as n,m as t}; \ No newline at end of file diff --git a/web/assets/NodeList.js b/web/assets/NodeList.js index a37c0be6..627fd402 100644 --- a/web/assets/NodeList.js +++ b/web/assets/NodeList.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{B as t,Gn as s,In as a,L as r,Ln as i,Nn as n,Nt as o,Ot as l,P as d,R as c,Rt as u,Tt as m,Xt as f,en as p,j as x,pr as h,ur as g,vn as b}from"./graph-vendor.js?v=5bcd315505f5670a";import{A as v,Br as j,Do as y,F as N,Fr as w,Gr as k,Hn as C,Hr as I,I as $,Jr as z,Jt as F,Lo as M,M as S,N as D,O as _,P as H,Pr as T,Qt as A,Ro as U,Sn as E,U as G,Ua as B,Ut as R,Vr as O,W as P,Wr as L,Wt as Z,a as q,an as V,bt as W,c as J,en as K,i as X,j as Q,k as Y,kr as ee,l as te,ln as se,mo as ae,n as re,ni as ie,o as ne,on as oe,qr as le,r as de,ro as ce,s as ue,sn as me,ta as fe,to as pe,u as xe,ui as he,xt as ge}from"./studio-templates.js?v=5bcd315505f5670a";var be=g();function ve({entries:e,search:t,openGroups:s,toggle:r,insert:i}){let o=new Map;for(let t of e){let e=t.groupPath.slice(1).join(" / ")||"Operations";o.set(e,[...o.get(e)??[],t])}return[...o].sort(([e],[t])=>e.localeCompare(t)).map(([e,o])=>{let l=`runtime:${o[0].groupPath[0]}:${e}`,d=!!t.trim()||s.includes(l);return(0,be.jsxs)("div",{"data-testid":`node-subgroup-${l.replace(/[^a-zA-Z0-9]+/gu,"-")}`,children:[(0,be.jsxs)(W,{level:1,"aria-expanded":d,open:d,onClick:()=>r(l),className:"min-h-9 justify-between font-medium",children:[(0,be.jsx)("span",{className:"min-w-0 truncate",children:e}),(0,be.jsxs)("span",{className:"flex items-center gap-2 text-xs text-modiff-subtle-text",children:[o.length,(0,be.jsx)(n,{size:14,className:E("transition-transform",d&&"rotate-180")})]})]}),d?(0,be.jsx)(ge,{level:2,children:[...o].sort((e,t)=>e.label.localeCompare(t.label)||e.key.localeCompare(t.key)).map(e=>(0,be.jsxs)(W,{"data-testid":`node-row-${e.key.replace(/[^a-zA-Z0-9]+/gu,"-").replace(/^-|-$/gu,"")}`,level:2,draggable:!0,onClick:()=>i(e.dragKey),onDragStart:t=>{t.dataTransfer.setData("text/plain",e.dragKey),t.dataTransfer.effectAllowed="move"},title:`${e.node.label||e.label} · ${e.key}${e.description?`\n${e.description}`:""}`,className:"cursor-grab hover:bg-modiff-surface-hover",children:["experimental"===e.visibility?(0,be.jsx)(p,{size:14,className:"shrink-0 text-hf-yellow"}):(0,be.jsx)(a,{size:14,className:"shrink-0 text-hf-yellow"}),(0,be.jsx)("span",{className:"min-w-0 flex-1 break-words",children:e.label})]},e.key))}):null]},l)})}var je=e(h(),1);function ye(e){return"object"==typeof e&&e&&!Array.isArray(e)?e:null}function Ne(e){return Array.isArray(e)&&e.every(e=>"string"==typeof e)?e:null}function we(e){let t=ye(e),s=ye(t?.sidecar),a=ye(t?.definition),r=ye(t?.remoteCode),i=ye(t?.admission),n=null===t?.runtimeNode?null:ye(t?.runtimeNode),o=Array.isArray(a?.blocks)?a.blocks:null,l=Array.isArray(i?.components)?i.components:null,d=Ne(i?.reasons),c=Ne(t?.rejectedFields);if(!(1===t?.schemaVersion&&"string"==typeof t.repository&&/^[a-f0-9]{40}$/u.test(String(t.revision))&&"string"==typeof s?.filename&&["modiff","mellon"].includes(String(s.format))&&"string"==typeof s.sha256&&"boolean"==typeof s.translatedFromMellon&&"string"==typeof a?.label&&o&&"number"==typeof a.blockCount&&"number"==typeof a.parameterCount&&!1===r?.allowed&&"boolean"==typeof r.repositoryPythonPresent&&"boolean"==typeof r.requiredForExecution&&["executable","preview_only","rejected"].includes(String(i?.status))&&"boolean"==typeof i?.executable&&l&&d&&c))throw Error("The backend returned an invalid custom Modular inspection contract.");if(o.some(e=>{let t=ye(e);return!("string"==typeof t?.id&&"string"==typeof t.label&&Ne(t.hierarchy)&&Ne(t.inputNames)&&Ne(t.modelInputNames)&&Ne(t.outputNames)&&Array.isArray(t.parameters))}))throw Error("The backend returned an invalid custom Modular block hierarchy.");if(n&&("string"!=typeof n.label||!ye(n.params)||!ye(n.identity)))throw Error("The backend returned an invalid executable custom Modular node contract.");return e}function ke(e,t,s){return ce(`${M.serverAddress}/custom_modular/inspect`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo_id:e,revision:t}),signal:s,timeoutMs:3e4,parse:we})}function Ce(e){if(!e.admission.executable||!e.runtimeNode)return[];let t=ye(e.runtimeNode.identity),s=ye(t?.component_revisions);if("modiff.custom-pipeline-identity.v3"!==t?.schema||!s)throw Error("The executable custom Modular contract did not pin every component repository.");let a=Object.entries(s).map(([e,t])=>{if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/u.test(e)||"string"!=typeof t||!/^[a-f0-9]{40}$/u.test(t))throw Error("The executable custom Modular contract contains an invalid component pin.");return{repository:e,revision:t}});if(0===a.length)throw Error("The executable custom Modular contract did not declare any component repositories.");return a.sort((e,t)=>e.repository.localeCompare(t.repository))}function Ie(){let{hfDownloadProgress:e,installHfModel:s,nodesRegistry:r}=ie(),i=fe(e=>e.studioViewMode),n=ee(e=>e.addNode),o=ee(e=>e.viewport),l=ee(e=>e.nodes.length),d=L(e=>e.blocks),c=L(e=>e.blockDefinitionsV2),u=L(e=>e.loaded),m=L(e=>e.fetchBlocks),f=L(e=>e.deleteBlock),p=L(e=>e.saveBlock),x=w(e=>e.library),h=T(e=>e.snapshot),g=T(e=>e.loaded),b=T(e=>e.fetchSnapshot),k=he(e=>e.form),I=he(e=>e.activeWorkflowTabId),$=fe(e=>e.setWorkflowFocusRequest),M=w(e=>e.loaded),_=w(e=>e.error),E=w(e=>e.fetchLibrary),[P,W]=(0,je.useState)(""),[J,X]=(0,je.useState)(null),[te,re]=(0,je.useState)(null),[ne,oe]=(0,je.useState)(!1),[de,ce]=(0,je.useState)(""),[xe,ge]=(0,je.useState)(""),[ve,ye]=(0,je.useState)(!1),[Ne,we]=(0,je.useState)(!1),[Ie,Fe]=(0,je.useState)(null),[Me,Se]=(0,je.useState)(null),[De,_e]=(0,je.useState)(()=>new Set),[He,Te]=(0,je.useState)("expert"===i?"advanced":"essential"),Ae="expert"===i;(0,je.useEffect)(()=>{Te("expert"===i?"advanced":"essential")},[i]),(0,je.useEffect)(()=>{u||m()},[m,u]),(0,je.useEffect)(()=>{M||E()},[E,M]),(0,je.useEffect)(()=>{g||b()},[b,g]);let Ue=(0,je.useMemo)(()=>x?j(x,h):[],[x,h]),Ee=(0,je.useMemo)(()=>O(Ue,P),[Ue,P]),Ge=(0,je.useMemo)(()=>Ue.reduce((e,t)=>e+t.entries.length,0),[Ue]),Re=(0,je.useCallback)(e=>{N();let t=ze($e(o,l),ee.getState().nodes,{width:420,height:480}),s=q(e)?ae(y(e,{instanceId:`block-v2-${U(16)}`,position:t,size:{width:420,height:480}})):z(e,t);if(n(s),I){let e=Date.now();$({workflowTabId:I,nodeId:s.id,requestId:e,requestedAt:e})}},[I,n,l,$,o]),Oe=(0,je.useCallback)(e=>{N();let t=G(e,r,$e(o,l));t?n({...t,selected:!0}):B(`Node ${e} is no longer available. Reload the node list and try again.`,{variant:"error",autoHideDuration:5e3})},[n,l,r,o]),Pe=(0,je.useCallback)(async e=>{if(!x||!e.insertable||De.has(e.id))return;if("block"===e.kind){try{N(),n(Q(e,x,h)?Y(e,x,r,$e(o,l),h):v(e,x,r,$e(o,l),h))}catch(e){B(pe(e,"The Modular Diffusers block could not be added."),{variant:"error",autoHideDuration:7e3})}return}if("cluster"!==e.kind)return;let t=x.definitions.find(t=>t.id===e.id);if(!t)return void B("The exact reviewed Cluster Node definition is unavailable. Reload the catalog and try again.",{variant:"error",autoHideDuration:5e3});N();let s=ze($e(o,l),ee.getState().nodes,{width:360,height:320});_e(t=>new Set(t).add(e.id));let a=S(e.label,s);try{let{createHuggingFaceClusterForGraph:e}=await C(async()=>{let{createHuggingFaceClusterForGraph:e}=await import(`./huggingFaceClusterInsertion.js?v=5bcd315505f5670a`).then(e=>e.n);return{createHuggingFaceClusterForGraph:e}},[]),r=await e(t,s,k,{insert:!1});if(H(a,r),I){let e=Date.now();$({workflowTabId:I,nodeId:r.id,requestId:e,requestedAt:e})}}catch(e){B(pe(e,"The registered Block could not be added."),{variant:"error",autoHideDuration:1e4})}finally{D(a),_e(t=>{let s=new Set(t);return s.delete(e.id),s})}},[I,n,De,x,h,l,r,$,k,o]),Le=(0,je.useCallback)(()=>{oe(!1),ce(""),ge(""),ye(!1),we(!1),Fe(null),Se(null)},[]),Ze=(0,je.useCallback)(()=>{let e=de.trim(),t=xe.trim().toLowerCase();if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/u.test(e))throw Error("Enter a Hugging Face repository as owner/name.");if(!/^[a-f0-9]{40}$/u.test(t))throw Error("Enter the exact 40-character Hugging Face commit revision.");if(!ve)throw Error("Confirm that you reviewed the repository, license, and exact commit before installing it.");return{repo:e,revision:t}},[de,ve,xe]),qe=(0,je.useCallback)(async()=>{let e;Fe(null);try{e=Ze()}catch(e){return void Fe(pe(e,"The Hub selector is invalid."))}we(!0);try{await s(e.repo,null,{revision:e.revision});let t=await ke(e.repo,e.revision);Se(t)}catch(e){Fe(pe(e,"Could not install and inspect the exact Hub revision."))}finally{we(!1)}},[s,Ze]),Ve=(0,je.useCallback)(async()=>{let e=Me;if(!e)return;let t=G("modules.ModularDiffusers.DynamicBlockNode",r,$e(o,l));if(t){we(!0),Fe(null);try{let a=e.runtimeNode?.params??{},r=e.admission.executable&&!!e.runtimeNode;if(r)for(let t of Ce(e))await s(t.repository,null,{revision:t.revision});let i=`${e.definition.label} — ${e.repository}@${e.revision.slice(0,7)}`,d=le({nodes:[{...t,selected:!0,data:{...t.data,label:e.runtimeNode?.label||e.definition.label,category:"User Nodes",description:r?"User-owned Modular Diffusers contract imported from an immutable reviewed Hub revision.":"Remote-code-disabled Hub contract preview. Expand to inspect it; execution remains disabled.",params:{...t.data.params,...a,repo_id:{...t.data.params.repo_id,value:{source:"hub",value:e.repository}},revision:{...t.data.params.revision,value:e.revision},trust_remote_code:{...t.data.params.trust_remote_code,value:!1,disabled:!0},modiff_pipeline_identity:{...t.data.params.modiff_pipeline_identity,value:e.runtimeNode?.identity??null},...r?{}:{load_block_button:{...t.data.params.load_block_button,label:"Repository Python is not authorized",disabled:!0}}},uiState:r?t.data.uiState:{...t.data.uiState,disabled:!0,validationSeverity:"error",validationMessage:e.admission.reasons.join(" ")||"Remote-code execution is disabled."}}}],edges:[]},i);if(!d.ok)throw Error(d.reason);let c=await p({...d.block,origin:{schemaVersion:1,kind:"hugging_face_hub_import",provider:"diffusers",repo:e.repository,revision:e.revision,pipelineClass:e.admission.pipelineClass??void 0,compositionKind:"modiff_graph_snapshot",importedAt:Date.now()}});n(z(c,$e(o,l))),Le(),B(r?`Imported executable User Node: ${c.name}`:`Saved remote-code-disabled preview to User Nodes: ${c.name}`,{variant:"success",autoHideDuration:4600})}catch(e){Fe(pe(e,"Could not install the pinned components or save the imported User Node."))}finally{we(!1)}}else B("The backend does not expose the reviewed Modular Diffusers Dynamic Block Node.",{variant:"error",autoHideDuration:5e3})},[n,Me,s,l,r,Le,p,o]),We=e[de.trim()];return(0,be.jsxs)("div",{className:"flex h-full flex-col",children:[(0,be.jsx)("header",{className:"border-b border-modiff-border bg-modiff-surface px-3 py-3",children:(0,be.jsx)("div",{className:"flex items-start justify-between gap-3",children:(0,be.jsxs)("div",{className:"flex min-w-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"grid size-8 shrink-0 place-items-center rounded-modiff-compact border border-modiff-border bg-modiff-bg text-hf-yellow",children:(0,be.jsx)(a,{size:18})}),(0,be.jsxs)("div",{className:"min-w-0",children:[(0,be.jsx)("h2",{className:"truncate text-sm font-semibold text-modiff-text",children:"Nodes"}),(0,be.jsxs)("p",{className:"truncate text-xs text-modiff-subtle-text",children:[Object.keys(r).length," runtime · ",Ge," HF catalog"]})]})]})})}),(0,be.jsx)("div",{className:"p-2",children:(0,be.jsx)(V,{"aria-label":"Search nodes",placeholder:"Search nodes",value:P,onChange:e=>W(e.currentTarget.value),onClear:()=>W("")})}),Ae?(0,be.jsx)(me,{"aria-label":"Node catalog level",className:"mb-0.5 border-y border-modiff-border bg-modiff-surface p-0.5",options:[{value:"essential",label:"Essentials"},{value:"advanced",label:"Advanced"},{value:"experimental",label:"Experimental"}],value:He,onValueChange:Te,size:"dense"}):null,(0,be.jsx)("div",{className:"min-h-0 flex-1 select-none overflow-y-auto p-1",children:(0,be.jsx)(Be,{nodes:r,search:P,view:Ae?He:"essential",expertMode:Ae,userBlocks:[...c,...d],huggingFaceSections:Ee,huggingFaceLoading:!M,huggingFaceError:_,insertingHuggingFaceDefinitionIds:De,onRetryHuggingFaceLibrary:E,onInsertNode:Oe,onInsertHuggingFaceCluster:Pe,onInsertUserBlock:Re,onImportHuggingFaceUserNode:()=>oe(!0),onOpenUserBlockMenu:(e,t)=>X({block:e,anchor:t})})}),(0,be.jsx)(se,{open:!!J,onClose:()=>X(null),anchor:J?.anchor,ariaLabel:"Block actions",role:"menu",gap:0,panelClassName:"w-52 p-1",testId:"user-block-context-menu",children:(0,be.jsx)(R,{fullWidth:!0,align:"left",tone:"danger",icon:(0,be.jsx)(t,{size:15}),onClick:()=>{re(J?.block??null),X(null)},children:"Delete from User Nodes"})}),(0,be.jsx)(Z,{open:!!te,onClose:()=>re(null),title:"Delete block definition?",testId:"delete-user-block-dialog",panelClassName:"max-w-md",footer:(0,be.jsxs)(be.Fragment,{children:[(0,be.jsx)(R,{onClick:()=>re(null),children:"Cancel"}),(0,be.jsx)(R,{tone:"danger",onClick:()=>{te&&f(ue(te)).then(()=>re(null)).catch(()=>{})},"data-testid":"confirm-delete-user-block",children:"Delete definition"})]}),children:(0,be.jsx)("p",{className:"text-sm text-modiff-text",children:"Existing workflow instances keep their embedded snapshot and continue to work."})}),(0,be.jsx)(Z,{open:ne,onClose:Le,title:"Import User Node from Hugging Face Hub",description:"Inspect an exact commit before saving it to your library. Repository Python stays disabled.",dismissible:!Ne,testId:"import-hugging-face-user-node-dialog",panelClassName:"max-w-2xl",toolbar:(0,be.jsxs)("div",{className:"flex flex-wrap gap-x-4 gap-y-1 px-4 py-2 text-xs text-modiff-subtle-text",role:"status",children:[(0,be.jsx)("span",{className:Me?"":"font-semibold text-hf-yellow",children:"1. Choose and inspect"}),(0,be.jsx)("span",{className:Me?"font-semibold text-hf-yellow":"",children:"2. Review and import"}),Ne?(0,be.jsx)("span",{children:"Working… Please keep this dialog open."}):null]}),footer:(0,be.jsxs)(be.Fragment,{children:[(0,be.jsx)(R,{disabled:Ne,onClick:Le,children:"Cancel"}),Me?(0,be.jsxs)(be.Fragment,{children:[(0,be.jsx)(R,{disabled:Ne,onClick:()=>{Se(null),Fe(null)},children:"Change repository"}),(0,be.jsx)(R,{"data-testid":"confirm-hugging-face-user-node-import",loading:Ne,tone:"primary",onClick:()=>{Ve()},children:Me.admission.executable?"Install pinned components, save and add":"Save to User Nodes and add"})]}):(0,be.jsx)(R,{"data-testid":"inspect-hugging-face-user-node",loading:Ne,tone:"primary",onClick:()=>{qe()},children:"Install exact revision and inspect"})]}),children:(0,be.jsxs)("div",{className:"grid gap-4",children:[(0,be.jsx)("p",{className:"text-sm text-modiff-subtle-text",children:"Inspecting installs the exact repository revision through Hugging Face Hub. Only contracts made entirely from admitted installed Diffusers blocks can run. Saving an inspection-only import does not enable Run."}),Me?(0,be.jsxs)("div",{className:"grid gap-3","data-testid":"hugging-face-user-node-inspection",children:[(0,be.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3",children:[(0,be.jsx)("div",{className:"text-sm font-semibold text-modiff-text",children:Me.definition.label}),(0,be.jsxs)("div",{className:"mt-1 break-all text-xs text-modiff-subtle-text",children:[Me.repository,"@",Me.revision]}),(0,be.jsxs)("div",{className:"mt-2 flex flex-wrap gap-2 text-xs text-modiff-subtle-text",children:[(0,be.jsx)("span",{children:Me.sidecar.filename}),(0,be.jsx)("span",{children:"·"}),(0,be.jsxs)("span",{children:[Me.definition.blockCount," blocks"]}),(0,be.jsx)("span",{children:"·"}),(0,be.jsxs)("span",{children:[Me.definition.parameterCount," parameters"]}),(0,be.jsx)("span",{children:"·"}),(0,be.jsx)("span",{children:Me.admission.status.replace("_"," ")})]})]}),Me.sidecar.translatedFromMellon?(0,be.jsx)("div",{className:"rounded-modiff-compact border border-hf-yellow/40 bg-hf-yellow/5 p-3 text-xs text-modiff-text",children:Me.admission.executable?"The official Mellon sidecar was translated into a bounded MoDiff contract. No repository Python was detected or executed.":"The official Mellon sidecar was translated for visual inspection. Its repository Python remains disabled, so this import is saved as a non-runnable User Node preview."}):null,Me.admission.reasons.length>0?(0,be.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/5 p-3 text-xs text-modiff-text",children:[Me.admission.reasons.join(" "),Me.admission.recoveryHint?` ${Me.admission.recoveryHint}`:""]}):null,(0,be.jsx)("div",{className:"min-w-0 break-words rounded-modiff-compact border border-modiff-border",children:Me.definition.blocks.map(e=>(0,be.jsxs)("section",{className:"border-b border-modiff-border p-3 last:border-b-0",children:[(0,be.jsx)("div",{className:"text-sm font-semibold text-modiff-text",children:e.label}),(0,be.jsx)("div",{className:"mt-1 text-xs text-modiff-subtle-text",children:e.hierarchy.join(" / ")}),(0,be.jsxs)("div",{className:"mt-2 grid gap-1 text-xs text-modiff-subtle-text",children:[(0,be.jsxs)("div",{children:["Inputs: ",e.inputNames.join(", ")||"None"]}),(0,be.jsxs)("div",{children:["Model inputs: ",e.modelInputNames.join(", ")||"None"]}),(0,be.jsxs)("div",{children:["Outputs: ",e.outputNames.join(", ")||"None"]})]})]},e.id))}),Me.admission.components.length>0?(0,be.jsxs)("div",{className:"break-words rounded-modiff-compact border border-modiff-border p-3 text-xs text-modiff-subtle-text",children:[(0,be.jsx)("div",{className:"mb-2 font-semibold text-modiff-text",children:"Reviewed components"}),Me.admission.components.map(e=>(0,be.jsxs)("div",{children:[e.name,": ",e.library,".",e.className," · ",e.repository,e.revision?`@${e.revision.slice(0,7)}`:" · unresolved revision"]},e.name))]}):null]}):(0,be.jsxs)(be.Fragment,{children:[(0,be.jsx)(A,{label:"Repository",children:(0,be.jsx)(K,{autoFocus:!0,"aria-label":"Hugging Face User Node repository",disabled:Ne,placeholder:"owner/repository",value:de,onChange:e=>{ce(e.currentTarget.value),ye(!1),Fe(null)}})}),(0,be.jsx)(A,{label:"Exact commit revision",children:(0,be.jsx)(K,{"aria-label":"Hugging Face User Node revision",disabled:Ne,placeholder:"40-character commit SHA",value:xe,onChange:e=>{ge(e.currentTarget.value),ye(!1),Fe(null)}})}),(0,be.jsx)(F,{checked:ve,disabled:Ne,label:"I reviewed this repository, its license, and this exact commit",onCheckedChange:ye,"data-testid":"hugging-face-user-node-reviewed"}),Ne&&We?(0,be.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text","data-testid":"hugging-face-user-node-install-progress",children:"complete"===We.status?"Installed. Inspecting the declarative contract…":`Installing exact revision… ${Math.round(100*(We.progress??0))}%`}):null]}),Ie?(0,be.jsx)("div",{className:"rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/5 p-3 text-xs text-modiff-text",role:"alert","data-testid":"hugging-face-user-node-import-error",children:Ie}):null]})})]})}function $e(e,t){let s=e.zoom||1,a=t%6*36;return{x:(180-e.x+a)/s,y:(120-e.y+a)/s}}function ze(e,t,s){let a=t.filter(e=>!e.parentId&&!e.hidden),r=e=>a.some(t=>{let a=t.measured?.width??t.width??320,r=t.measured?.height??t.height??220;return e.xt.position.x&&e.yt.position.y});for(let t=0;t<36;t+=1){let a=t%6,i=Math.floor(t/6),n={x:e.x+a*(s.width+32),y:e.y+i*(s.height+32)};if(!r(n))return n}return{x:Math.max(e.x,...a.map(e=>e.position.x+(e.measured?.width??e.width??320)+32)),y:e.y}}function Fe(e){switch(e||="default",e.toLowerCase()){case"image":case"preview":return(0,be.jsx)(u,{size:16});case"edit":case"editing":case"image_filter":return(0,be.jsx)(f,{size:16});case"generate":case"generation":case"audio":case"sampler":return(0,be.jsx)(s,{size:16});case"condition":case"conditioning":case"adapters":default:return(0,be.jsx)(x,{size:16});case"export":case"upscaler":return(0,be.jsx)(m,{size:16});case"video":case"embedding":return(0,be.jsx)(o,{size:16});case"text":return(0,be.jsx)(r,{size:16});case"primitive":return(0,be.jsx)(i,{size:16});case"custom":return(0,be.jsx)(d,{size:16});case"load":case"loader":case"loaders":return(0,be.jsx)(c,{size:16})}}function Me(e){return e.replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-|-$/g,"")}function Se(e){return"reviewed_modiff_contract"===e?"Reviewed MoDiff contract":"reviewed_transformers_contract"===e?"Reviewed Transformers contract":"equivalent_standard_route"===e?"Equivalent standard route":"contract_only"===e?"Contract only":"Reviewed upstream metadata"}function De(e){let t=new Map;e.entries.forEach(s=>{let a=t;s.groupPath.forEach((t,r)=>{let i=s.groupPath.slice(0,r+1),n=`${e.id}:${i.join("/")}`,o=a.get(t)??{id:n,label:t,entries:[],children:new Map};a.set(t,o),a=o.children,r===s.groupPath.length-1&&o.entries.push(s)})});let s=e=>[...e.values()].sort((e,t)=>e.label.localeCompare(t.label)).map(e=>({id:e.id,label:e.label,entries:[...e.entries].sort((e,t)=>e.label.localeCompare(t.label)||e.id.localeCompare(t.id)),children:s(e.children)}));return s(t)}function _e(e){return e.entries.length+e.children.reduce((e,t)=>e+_e(t),0)}function He({entry:e,insertingDefinitionIds:t,level:s,onInsertCluster:a}){let r=!!t?.has(e.id),n=(0,be.jsxs)(be.Fragment,{children:[(0,be.jsx)("span",{className:"mt-0.5 grid size-4 shrink-0 self-start place-items-center text-hf-yellow",children:"cluster"===e.kind?(0,be.jsx)(o,{size:15}):(0,be.jsx)(i,{size:15})}),(0,be.jsxs)("span",{className:"min-w-[min(100%,6rem)] flex-1",children:[(0,be.jsx)("span",{className:"block break-words whitespace-normal","data-catalog-entry-label":!0,children:e.label}),(0,be.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:e.detail})]}),(0,be.jsxs)("span",{"data-catalog-entry-readiness":!0,className:"ml-auto inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:[r?(0,be.jsx)(l,{size:12,className:"animate-spin","aria-hidden":"true"}):null,r?"Adding…":e.readinessLabel]})]}),d=e.insertable?"block"===e.kind?`${e.description} ${Se(e.integrationStatus)}. Add this exact pinned Modular Diffusers definition from ${e.modularBlockContexts?.length??1} compatible context${1===(e.modularBlockContexts?.length??1)?"":"s"} as an ordinary composable node.`:"graph_qualified"===e.readiness?`${e.description} ${Se(e.integrationStatus)}. Runs directly in Expert mode; Auto checks the exact runtime, installed artifacts, and available resources before submission.`:`${e.description} ${Se(e.integrationStatus)}. Insert the reviewed structural Cluster Node; execution remains unavailable until its exact graph, artifact, runtime, and resource admission completes.`:`${e.description} ${Se(e.integrationStatus)}. Catalog metadata only; insertion is unavailable until its exact graph compiler is admitted.`;return e.insertable?(0,be.jsx)(W,{"aria-label":`${e.label}. ${e.readinessLabel}; insert ${"cluster"===e.kind?"Cluster Node":"Modular Diffusers block"}.`,className:"cursor-pointer flex-wrap gap-y-1 py-1 hover:bg-modiff-surface-hover","data-readiness":e.readiness,"data-testid":`hugging-face-node-row-${Me(e.id)}`,"aria-busy":r||void 0,disabled:r,draggable:!r,level:s,onClick:()=>a(e),onDragStart:t=>{t.dataTransfer.setData("text/plain","cluster"===e.kind?`${$}${e.id}`:`${_}${e.id}`),t.dataTransfer.effectAllowed="move"},title:d,children:n}):(0,be.jsx)("div",{"aria-disabled":"true","aria-label":`${e.label}. ${e.readinessLabel}; insertion unavailable.`,className:"flex min-h-10 min-w-0 flex-wrap items-center gap-x-2 gap-y-1 rounded-modiff-compact py-1 pl-2 pr-2 text-sm text-modiff-text","data-readiness":e.readiness,"data-testid":`hugging-face-node-row-${Me(e.id)}`,role:"listitem",title:d,children:n})}function Te({activeGroupIds:e,forceOpen:t,insertingDefinitionIds:s,level:a,nodes:r,onInsertCluster:i,onToggleGroup:o}){return r.map(r=>{let l=`hugging-face:${r.id}`,d=t||e.includes(l);return(0,be.jsxs)("div",{"data-testid":`node-subgroup-${Me(r.id)}`,children:[(0,be.jsxs)(W,{"aria-expanded":d,className:"min-h-9 justify-between font-medium",level:a,onClick:()=>o(l),open:d,children:[(0,be.jsx)("span",{className:"min-w-0 truncate",children:r.label}),(0,be.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"text-xs text-modiff-subtle-text",children:_e(r)}),(0,be.jsx)(n,{size:14,className:E("text-modiff-subtle-text transition-transform",d&&"rotate-180")})]})]}),d?(0,be.jsxs)(ge,{level:a+1,children:[(0,be.jsx)(Te,{activeGroupIds:e,forceOpen:t,insertingDefinitionIds:s,level:a+1,nodes:r.children,onInsertCluster:i,onToggleGroup:o}),r.entries.map(e=>(0,be.jsx)(He,{entry:e,insertingDefinitionIds:s,level:a+1,onInsertCluster:i},e.id))]}):null]},r.id)})}function Ae({activeGroupIds:e,error:t,insertingDefinitionIds:s,loading:r,onToggleGroup:i,onRetry:l,onInsertCluster:d,searching:c,sections:u}){return r?(0,be.jsx)("div",{"aria-live":"polite",className:"mb-1 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-sm text-modiff-subtle-text","data-testid":"hugging-face-node-catalog-loading",role:"status",children:"Loading Hugging Face node catalog…"}):t?(0,be.jsxs)("div",{className:"mb-1 grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-sm text-modiff-subtle-text","data-testid":"hugging-face-node-catalog-error",role:"alert",children:[(0,be.jsx)("span",{children:"Hugging Face node catalog unavailable. Runtime nodes and User Nodes are unaffected."}),(0,be.jsx)(R,{align:"left",className:"h-8 px-2 text-xs",onClick:()=>{l()},tone:"secondary",children:"Retry catalog"})]}):u.map(t=>{let r=`hugging-face:${t.id}`,l=c||e.includes(r);return(0,be.jsxs)("div",{"data-testid":`node-group-${Me(t.label)}`,className:E("mb-1 rounded-modiff-compact border border-transparent transition-colors",l&&"border-modiff-border bg-modiff-surface-hover/40"),children:[(0,be.jsxs)(W,{"aria-expanded":l,className:"min-h-10 justify-between px-2 font-semibold",onClick:()=>i(r),open:l,children:[(0,be.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"grid size-4 shrink-0 place-items-center text-hf-yellow",children:t.id.endsWith("_cluster_nodes")?(0,be.jsx)(o,{size:15}):(0,be.jsx)(a,{size:15})}),(0,be.jsx)("span",{className:"min-w-0 truncate leading-none",children:t.label})]}),(0,be.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:t.entries.length}),(0,be.jsx)(n,{size:16,className:E("text-modiff-subtle-text transition-transform",l&&"rotate-180")})]})]}),l?(0,be.jsx)(ge,{level:1,children:(0,be.jsx)("div",{role:"list","aria-label":t.label,children:(0,be.jsx)(Te,{activeGroupIds:e,forceOpen:c,insertingDefinitionIds:s,level:1,nodes:De(t),onInsertCluster:d,onToggleGroup:i})})}):null]},t.id)})}function Ue(e,t){let s=new Map;e.forEach(e=>{let a=s,r=ne(e,t);r.forEach((s,i)=>{let n=`user-nodes:${t}:${JSON.stringify(r.slice(0,i+1))}`,o=a.get(s)??{id:n,label:s,blocks:[],children:new Map};a.set(s,o),a=o.children,i===r.length-1&&o.blocks.push(e)})});let a=e=>[...e.values()].sort((e,t)=>e.label.localeCompare(t.label)).map(e=>({id:e.id,label:e.label,blocks:[...e.blocks].sort((e,t)=>J(e).localeCompare(J(t))||ue(e).localeCompare(ue(t))),children:a(e.children)}));return a(s)}function Ee(e){return e.blocks.length+e.children.reduce((e,t)=>e+Ee(t),0)}function Ge({activeGroupIds:e,blocks:t,forceOpen:s,level:r,onInsertUserBlock:i,onOpenUserBlockMenu:o,onToggleGroup:l}){return t.map(t=>{let d=`user-block:${t.id}`,c=s||e.includes(d);return(0,be.jsxs)("div",{"data-testid":`user-node-subgroup-${Me(t.id)}`,children:[(0,be.jsxs)(W,{"aria-expanded":c,className:"min-h-9 justify-between font-medium",level:r,onClick:()=>l(d),open:c,children:[(0,be.jsx)("span",{className:"min-w-0 truncate",children:t.label}),(0,be.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"text-xs text-modiff-subtle-text",children:Ee(t)}),(0,be.jsx)(n,{size:14,className:E("text-modiff-subtle-text transition-transform",c&&"rotate-180")})]})]}),c?(0,be.jsxs)(ge,{level:r+1,children:[(0,be.jsx)(Ge,{activeGroupIds:e,blocks:t.children,forceOpen:s,level:r+1,onInsertUserBlock:i,onOpenUserBlockMenu:o,onToggleGroup:l}),t.blocks.map(e=>(0,be.jsxs)(W,{"data-testid":`user-block-row-${Me(ue(e))}`,draggable:!0,level:r+1,title:`Add User Node · ${ue(e)} · ${te(e)}`,className:"cursor-grab",onClick:()=>i(e),onContextMenu:t=>{t.preventDefault(),o(e,{left:t.clientX,top:t.clientY})},onDragStart:t=>{t.dataTransfer.setData("text/plain",`${q(e)?P:k}${ue(e)}`),t.dataTransfer.effectAllowed="move"},children:[(0,be.jsx)("span",{className:"grid size-4 shrink-0 place-items-center self-start text-hf-yellow",children:(0,be.jsx)(a,{size:15})}),(0,be.jsxs)("span",{className:"min-w-0 flex-1 whitespace-normal break-words text-left",children:[(0,be.jsx)("span",{className:"block",children:J(e)}),(0,be.jsx)("span",{className:"block text-xs text-modiff-subtle-text",children:te(e)}),(0,be.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:ue(e)})]})]},ue(e)))]}):null]},t.id)})}function Be({nodes:e,search:t,view:s,expertMode:r,userBlocks:i,huggingFaceSections:o,huggingFaceLoading:l,huggingFaceError:d,insertingHuggingFaceDefinitionIds:c,onRetryHuggingFaceLibrary:u,onInsertNode:m,onInsertHuggingFaceCluster:f,onInsertUserBlock:p,onImportHuggingFaceUserNode:x,onOpenUserBlockMenu:h}){let{activeNodeGroups:g,setActiveNodeGroups:v}=fe(),j=(0,je.useMemo)(()=>re(e).filter(e=>function(e,t){return"internal"!==e.visibility&&("essential"===t?"essential"===e.visibility:"advanced"===t?"essential"===e.visibility||"advanced"===e.visibility:"experimental"===e.visibility)}(e,s)).filter(e=>de(e,t)).reduce((e,t)=>{let s=X(t,r);return e[s]||(e[s]=[]),e[s].push(t),e},{}),[r,e,t,s]),y=Object.entries(j).sort(([e],[t])=>e.localeCompare(t)),N=fe(e=>e.userBlockGrouping),w=fe(e=>e.setUserBlockGrouping),k=(0,je.useMemo)(()=>xe(i).filter(e=>I(t,["User Nodes",J(e),ue(e),...ne(e),...ne(e,"workflow"),te(e)])),[t,i]),C=g.includes("User Nodes")||!(!t.trim()||!k.length);return(0,be.jsxs)(be.Fragment,{children:[(0,be.jsx)(Ae,{activeGroupIds:g,error:d,insertingDefinitionIds:c,loading:l,onToggleGroup:v,onRetry:u,onInsertCluster:f,searching:!!t.trim(),sections:o}),(0,be.jsxs)("div",{"data-testid":"node-group-User-Nodes",className:E("mb-1 rounded-modiff-compact border border-transparent transition-colors",C&&"border-modiff-border bg-modiff-surface-hover/40"),children:[(0,be.jsxs)(W,{"aria-expanded":C,className:"min-h-10 justify-between px-2 font-semibold",onClick:()=>v("User Nodes"),open:C,children:[(0,be.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"grid size-4 shrink-0 place-items-center text-hf-yellow",children:(0,be.jsx)(a,{size:15})}),(0,be.jsx)("span",{className:"min-w-0 truncate leading-none",children:"User Nodes"})]}),(0,be.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:k.length}),(0,be.jsx)(n,{size:16,className:E("text-modiff-subtle-text transition-transform",C&&"rotate-180")})]})]}),C?(0,be.jsxs)(ge,{level:1,children:[(0,be.jsxs)("div",{className:"grid gap-1 px-2 py-2",children:[(0,be.jsx)(oe,{"aria-label":"Group User Nodes by",value:N,options:[{value:"source",label:"By source and family"},{value:"workflow",label:"By saved workflow context"}],onValueChange:e=>w("workflow"===e?"workflow":"source")}),(0,be.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Saved copies only. Updating replaces the library revision; open workflows keep their own copy."})]}),(0,be.jsxs)(W,{"data-testid":"import-hugging-face-user-node",level:1,title:"Import an immutable Modular Diffusers block from Hugging Face Hub",onClick:x,children:[(0,be.jsx)("span",{className:"grid size-4 shrink-0 place-items-center text-hf-yellow",children:(0,be.jsx)(b,{size:15})}),(0,be.jsx)("span",{className:"min-w-0 flex-1 truncate",children:"Import from Hugging Face Hub"})]}),(0,be.jsx)(Ge,{activeGroupIds:g,blocks:Ue(k,N),forceOpen:!!t.trim(),level:1,onInsertUserBlock:p,onOpenUserBlockMenu:h,onToggleGroup:v})]}):null]}),0===y.length&&0===k.length&&0===o.length?(0,be.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-sm text-modiff-subtle-text",children:"No nodes match this view."}):y.map(([e,s])=>{let a=!!t.trim()||g.includes(e);return(0,be.jsxs)("div",{"data-testid":`node-group-${Me(e)}`,className:E("mb-1 rounded-modiff-compact border border-transparent transition-colors",a&&"border-modiff-border bg-modiff-surface-hover/40"),children:[(0,be.jsxs)(W,{"aria-expanded":a,className:"min-h-10 justify-between px-2 font-semibold",onClick:()=>v(e),open:a,children:[(0,be.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"grid size-4 shrink-0 place-items-center text-hf-yellow",children:Fe(e)}),(0,be.jsx)("span",{className:"min-w-0 truncate leading-none",children:e})]}),(0,be.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,be.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:s.length}),(0,be.jsx)(n,{size:16,className:E("text-modiff-subtle-text transition-transform",a&&"rotate-180")})]})]}),a?(0,be.jsx)(ge,{level:1,children:(0,be.jsx)(ve,{entries:s,search:t,openGroups:g,toggle:v,insert:m})}):null]},e)})]})}export{Ae as HuggingFaceNodeGroups,Ie as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Ct as s,Et as t,I as a,It as n,L as r,Mn as i,N as l,Nn as o,Vn as d,Zt as c,cr as u,dr as m,hn as f,j as x,jt as p,kn as h,ot as g,qt as b,z as j}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$ as k,A as w,B as v,Ci as N,Cn as y,D as C,E as I,Eo as $,F as z,Ft as D,I as S,In as B,L as F,M,Mo as T,N as G,O as E,P as _,Pt as H,R as L,Sa as O,Si as A,T as R,Vr as U,Xr as P,Yr as q,Z as W,a as V,bn as Z,ci as K,i as X,in as Y,j as J,ji as Q,k as ee,n as se,ni as te,nt as ae,o as ne,q as re,r as ie,ri as le,rn as oe,si as de,yn as ce,z as ue}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as me}from"./NodeDiscoveryFilters.js?v=cc046c26d6fe7aa4";var fe=e(m(),1),xe=u();function pe({search:e,insert:s,manage:t}){let a=A(e=>e.customModules),n=A(e=>e.nodesRegistry),r=A(e=>e.fetchCustomModules),l=A(e=>e.customModuleError),[o,d]=(0,fe.useState)(!1);(0,fe.useEffect)(()=>{let e=!1,s=async()=>{if(!e&&"hidden"!==document.visibilityState){e=!0;try{await r()}finally{e=!1}}};s();let t=window.setInterval(()=>{s()},15e3);return window.addEventListener("focus",s),()=>{window.clearInterval(t),window.removeEventListener("focus",s)}},[r]);let c=e.trim().toLowerCase(),u=o||!!c,m=Object.entries(n).filter(([e,s])=>e.startsWith("custom.")&&a.some(s=>s.enabled&&e.startsWith(s.moduleKey+"."))&&(!c||`${s.label} ${e}`.toLowerCase().includes(c)));return(0,xe.jsxs)("section",{"data-testid":"node-group-custom-nodes",className:"mb-2 rounded-modiff-compact border border-modiff-border",children:[(0,xe.jsxs)(H,{"aria-expanded":u,open:u,onClick:()=>d(!o),className:"min-h-10 justify-between font-semibold",children:[(0,xe.jsxs)("span",{className:"flex items-center gap-2",children:[(0,xe.jsx)(i,{size:16,className:"text-hf-yellow"}),"Custom nodes"]}),(0,xe.jsxs)("span",{className:"flex items-center gap-2 text-xs text-modiff-subtle-text",children:[m.length,(0,xe.jsx)(h,{size:14,className:B(u&&"rotate-180")})]})]}),u?(0,xe.jsxs)(D,{level:1,children:[m.map(([e,t])=>(0,xe.jsx)(H,{draggable:!0,level:1,onClick:()=>s(e),onDragStart:s=>{s.dataTransfer.setData("text/plain",e),s.dataTransfer.effectAllowed="move"},children:(0,xe.jsx)("span",{className:"min-w-0 break-words",children:t.label||t.action})},e)),a.filter(e=>!e.enabled&&(!c||e.name.toLowerCase().includes(c))).map(e=>(0,xe.jsxs)(H,{level:1,onClick:t,title:e.diagnostic??"Load this source in Manage nodes",children:[(0,xe.jsx)("span",{className:"min-w-0 flex-1 truncate",children:e.name}),(0,xe.jsx)("span",{className:"text-xs text-modiff-subtle-text",children:"disabled"!==e.status||e.approvedHash?e.status:"Not loaded"})]},e.name)),a.length?null:(0,xe.jsx)("p",{className:"p-2 text-xs text-modiff-subtle-text",children:"No custom nodes yet. Add a node or drop a Python node onto the canvas."}),l?(0,xe.jsx)("p",{role:"status",className:"p-2 text-xs text-modiff-red",children:l}):null,(0,xe.jsx)(oe,{fullWidth:!0,tone:"ghost",onClick:t,children:"Manage nodes"})]}):null]})}function he({entries:e,search:s,openGroups:t,toggle:a,insert:n}){let r=new Map;for(let s of e){let e=s.groupPath.slice(1).join(" / ")||"Operations";r.set(e,[...r.get(e)??[],s])}return[...r].sort(([e],[s])=>e.localeCompare(s)).map(([e,r])=>{let l=`runtime:${r[0].groupPath[0]}:${e}`,o=!!s.trim()||t.includes(l);return(0,xe.jsxs)("div",{"data-testid":`node-subgroup-${l.replace(/[^a-zA-Z0-9]+/gu,"-")}`,children:[(0,xe.jsxs)(H,{level:1,"aria-expanded":o,open:o,onClick:()=>a(l),className:"min-h-9 justify-between font-medium",children:[(0,xe.jsx)("span",{className:"min-w-0 truncate",children:e}),(0,xe.jsxs)("span",{className:"flex items-center gap-2 text-xs text-modiff-subtle-text",children:[r.length,(0,xe.jsx)(h,{size:14,className:B("transition-transform",o&&"rotate-180")})]})]}),o?(0,xe.jsx)(D,{level:2,children:[...r].sort((e,s)=>e.label.localeCompare(s.label)||e.key.localeCompare(s.key)).map(e=>(0,xe.jsxs)(H,{"data-testid":`node-row-${e.key.replace(/[^a-zA-Z0-9]+/gu,"-").replace(/^-|-$/gu,"")}`,level:2,draggable:!0,onClick:()=>n(e.dragKey),onDragStart:s=>{s.dataTransfer.setData("text/plain",e.dragKey),s.dataTransfer.effectAllowed="move"},title:`${e.node.label||e.label} · ${e.key}${e.description?`\n${e.description}`:""}`,className:"cursor-grab hover:bg-modiff-surface-hover",children:["experimental"===e.visibility?(0,xe.jsx)(c,{size:14,className:"shrink-0 text-hf-yellow"}):(0,xe.jsx)(i,{size:14,className:"shrink-0 text-hf-yellow"}),(0,xe.jsx)("span",{className:"min-w-0 flex-1 break-words",children:e.label})]},e.key))}):null]},l)})}var ge=(0,fe.lazy)(()=>N(()=>import(`./CustomExtensionsDialog.js?v=cc046c26d6fe7aa4`),[])),be=(0,fe.lazy)(()=>N(()=>import(`./OperationCatalogPanel.js?v=cc046c26d6fe7aa4`),[]));function je(){let e=A(e=>e.nodesRegistry),s=A(e=>e.operationContracts),t=A(e=>e.pipelineSupport),a=U(e=>e.addNode),n=U(e=>e.viewport),r=U(e=>e.nodes.length),l=de(e=>e.blocks),o=de(e=>e.blockDefinitionsV2),d=de(e=>e.loaded),c=de(e=>e.fetchBlocks),u=de(e=>e.deleteBlock),m=P(e=>e.library),f=q(e=>e.snapshot),x=q(e=>e.loaded),p=q(e=>e.fetchSnapshot),h=Q(e=>e.form),b=Q(e=>e.activeWorkflowTabId),v=O(e=>e.setWorkflowFocusRequest),z=P(e=>e.loaded),D=P(e=>e.error),B=P(e=>e.fetchLibrary),[F,G]=(0,fe.useState)(""),[_,H]=(0,fe.useState)(null),[L,R]=(0,fe.useState)(null),[V,Z]=(0,fe.useState)(!1),[K,ae]=(0,fe.useState)("add"),[ie,ue]=(0,fe.useState)("local"),[he,je]=(0,fe.useState)(()=>new Set),{view:ve,pipeline:Ne,task:ye}=W(),Ce=(0,fe.useMemo)(()=>ne(e,s,t,ve,{pipeline:Ne,task:ye}),[ve,e,s,t,Ne,ye]);(0,fe.useEffect)(()=>{d||c()},[c,d]),(0,fe.useEffect)(()=>{z||B()},[B,z]),(0,fe.useEffect)(()=>{x||p()},[p,x]);let Ie=(0,fe.useMemo)(()=>m?te(m,f):[],[m,f]),$e=(0,fe.useMemo)(()=>le(Ie,F,ve),[Ie,F,ve]),ze=(0,fe.useMemo)(()=>le(Ie,"",ve).reduce((e,s)=>e+s.entries.length,0),[Ie,ve]),De=(0,fe.useMemo)(()=>se(Ce).filter(e=>X(e,ve)).length,[Ce,ve]),Se=(0,fe.useCallback)(e=>{k();let s=M(e,we(ke(n,r),U.getState().nodes,{width:420,height:480}));if(a(s),b){let e=Date.now();v({workflowTabId:b,nodeId:s.id,requestId:e,requestedAt:e})}},[b,a,r,v,n]),Be=(0,fe.useCallback)((s,t)=>{k();let i=re(s,t?{[s]:t}:e,ke(n,r));i?a({...i,selected:!0}):T(`Node ${s} is no longer available. Reload the node list and try again.`,{variant:"error",autoHideDuration:5e3})},[a,r,e,n]),Fe=(0,fe.useCallback)(async s=>{if(!m||!s.insertable||he.has(s.id))return;if("block"===s.kind){try{k(),a(E(s,m,f)?I(s,m,e,ke(n,r),f):C(s,m,e,ke(n,r),f))}catch(e){T($(e,"The Modular Diffusers block could not be added."),{variant:"error",autoHideDuration:7e3})}return}if("cluster"!==s.kind)return;let t=m.definitions.find(e=>e.id===s.id);if(!t)return void T("The exact reviewed Block definition is unavailable. Reload the catalog and try again.",{variant:"error",autoHideDuration:5e3});k();let i=we(ke(n,r),U.getState().nodes,{width:360,height:320});je(e=>new Set(e).add(s.id));let l=ee(s.label,i);try{let{createHuggingFaceClusterForGraph:e}=await N(async()=>{let{createHuggingFaceClusterForGraph:e}=await import(`./huggingFaceClusterInsertion.js?v=cc046c26d6fe7aa4`).then(e=>e.n);return{createHuggingFaceClusterForGraph:e}},[]),s=await e(t,i,h,{insert:!1});if(J(l,s),b){let e=Date.now();v({workflowTabId:b,nodeId:s.id,requestId:e,requestedAt:e})}}catch(e){T($(e,"The registered Block could not be added."),{variant:"error",autoHideDuration:1e4})}finally{w(l),je(e=>{let t=new Set(e);return t.delete(s.id),t})}},[b,a,he,m,f,r,e,v,h,n]);return(0,xe.jsxs)("div",{className:"flex h-full flex-col",children:[(0,xe.jsx)("header",{className:"border-b border-modiff-border bg-modiff-surface px-3 py-3",children:(0,xe.jsx)("div",{className:"flex items-start justify-between gap-3",children:(0,xe.jsxs)("div",{className:"flex min-w-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"grid size-8 shrink-0 place-items-center rounded-modiff-compact border border-modiff-border bg-modiff-bg text-hf-yellow",children:(0,xe.jsx)(i,{size:18})}),(0,xe.jsxs)("div",{className:"min-w-0",children:[(0,xe.jsx)("h2",{className:"truncate text-sm font-semibold text-modiff-text",children:"Nodes"}),(0,xe.jsxs)("p",{className:"truncate text-xs text-modiff-subtle-text",children:[De," nodes · ",ze," catalog entries"]})]})]})})}),(0,xe.jsx)("div",{className:"p-2",children:(0,xe.jsx)(ce,{"aria-label":"Search nodes",placeholder:"Search nodes",value:F,onChange:e=>G(e.currentTarget.value),onClear:()=>G("")})}),(0,xe.jsx)(me,{}),(0,xe.jsx)("div",{className:"px-3 pb-2",children:(0,xe.jsx)(oe,{tone:"primary",fullWidth:!0,icon:(0,xe.jsx)(g,{size:16}),onClick:()=>{ae("add"),ue("local"),Z(!0)},children:"Add custom node"})}),V?(0,xe.jsx)(fe.Suspense,{fallback:null,children:(0,xe.jsx)(ge,{initialView:K,initialKind:ie,onClose:()=>Z(!1)})}):null,(0,xe.jsx)("p",{className:"px-3 pb-2 text-xs text-modiff-subtle-text",children:"Nodes, task Blocks and Saved Blocks share this library. Choose a pipeline to add nodes with its declared inputs."}),(0,xe.jsxs)("div",{className:"min-h-0 flex-1 select-none overflow-y-auto p-1",children:[(0,xe.jsx)(pe,{search:F,insert:Be,manage:()=>{ae("manage"),Z(!0)}}),(0,xe.jsx)(fe.Suspense,{fallback:null,children:(0,xe.jsx)(be,{search:F,onInsert:Be})}),(0,xe.jsx)(Me,{nodes:Object.fromEntries(Object.entries(Ce).filter(([e])=>!e.startsWith("custom."))),search:F,view:ve,userBlocks:[...o,...l],huggingFaceSections:$e,huggingFaceLoading:!z,huggingFaceError:D,insertingHuggingFaceDefinitionIds:he,onRetryHuggingFaceLibrary:B,onInsertNode:Be,onInsertHuggingFaceCluster:Fe,onInsertUserBlock:Se,onImportHuggingFaceUserNode:()=>{ae("add"),ue("hub"),Z(!0)},onOpenUserBlockMenu:(e,s)=>H({block:e,anchor:s})})]}),(0,xe.jsx)(y,{open:!!_,onClose:()=>H(null),anchor:_?.anchor,ariaLabel:"Block actions",role:"menu",gap:0,panelClassName:"w-52 p-1",testId:"user-block-context-menu",children:(0,xe.jsx)(oe,{fullWidth:!0,align:"left",tone:"danger",icon:(0,xe.jsx)(j,{size:15}),onClick:()=>{R(_?.block??null),H(null)},children:"Delete from Saved Blocks"})}),(0,xe.jsx)(Y,{open:!!L,onClose:()=>R(null),title:"Delete block definition?",testId:"delete-user-block-dialog",panelClassName:"max-w-md",footer:(0,xe.jsxs)(xe.Fragment,{children:[(0,xe.jsx)(oe,{onClick:()=>R(null),children:"Cancel"}),(0,xe.jsx)(oe,{tone:"danger",onClick:()=>{L&&u(S(L)).then(()=>R(null)).catch(()=>{})},"data-testid":"confirm-delete-user-block",children:"Delete definition"})]}),children:(0,xe.jsx)("p",{className:"text-sm text-modiff-text",children:"Existing workflow instances keep their embedded snapshot and continue to work."})})]})}function ke(e,s){let t=e.zoom||1,a=s%6*36;return{x:(180-e.x+a)/t,y:(120-e.y+a)/t}}function we(e,s,t){let a=s.filter(e=>!e.parentId&&!e.hidden),n=e=>a.some(s=>{let a=s.measured?.width??s.width??320,n=s.measured?.height??s.height??220;return e.xs.position.x&&e.ys.position.y});for(let s=0;s<36;s+=1){let a=s%6,r=Math.floor(s/6),i={x:e.x+a*(t.width+32),y:e.y+r*(t.height+32)};if(!n(i))return i}return{x:Math.max(e.x,...a.map(e=>e.position.x+(e.measured?.width??e.width??320)+32)),y:e.y}}function ve(e){switch(e||="default",e.toLowerCase()){case"image":case"preview":return(0,xe.jsx)(n,{size:16});case"edit":case"editing":case"image_filter":return(0,xe.jsx)(b,{size:16});case"generate":case"generation":case"audio":case"sampler":return(0,xe.jsx)(d,{size:16});case"condition":case"conditioning":case"adapters":default:return(0,xe.jsx)(x,{size:16});case"export":case"upscaler":return(0,xe.jsx)(s,{size:16});case"video":case"embedding":return(0,xe.jsx)(p,{size:16});case"text":return(0,xe.jsx)(a,{size:16});case"primitive":return(0,xe.jsx)(o,{size:16});case"custom":return(0,xe.jsx)(l,{size:16});case"load":case"loader":case"loaders":return(0,xe.jsx)(r,{size:16})}}function Ne(e){return e.replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-|-$/g,"")}function ye(e){return"reviewed_modiff_contract"===e?"Reviewed MoDiff contract":"reviewed_transformers_contract"===e?"Reviewed Transformers contract":"equivalent_standard_route"===e?"Equivalent standard route":"contract_only"===e?"Contract only":"Reviewed upstream metadata"}function Ce(e){let s=new Map;e.entries.forEach(t=>{let a=s;t.groupPath.forEach((s,n)=>{let r=t.groupPath.slice(0,n+1),i=`${e.id}:${r.join("/")}`,l=a.get(s)??{id:i,label:s,entries:[],children:new Map};a.set(s,l),a=l.children,n===t.groupPath.length-1&&l.entries.push(t)})});let t=e=>[...e.values()].sort((e,s)=>e.label.localeCompare(s.label)).map(e=>({id:e.id,label:e.label,entries:[...e.entries].sort((e,s)=>e.label.localeCompare(s.label)||e.id.localeCompare(s.id)),children:t(e.children)}));return t(s)}function Ie(e){return e.entries.length+e.children.reduce((e,s)=>e+Ie(s),0)}function $e({entry:e,insertingDefinitionIds:s,level:a,onInsertCluster:n}){let r=!!s?.has(e.id),i=(0,xe.jsxs)(xe.Fragment,{children:[(0,xe.jsx)("span",{className:"mt-0.5 grid size-4 shrink-0 self-start place-items-center text-hf-yellow",children:"cluster"===e.kind?(0,xe.jsx)(p,{size:15}):(0,xe.jsx)(o,{size:15})}),(0,xe.jsxs)("span",{className:"min-w-[min(100%,6rem)] flex-1",children:[(0,xe.jsx)("span",{className:"block break-words whitespace-normal","data-catalog-entry-label":!0,children:e.label}),(0,xe.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:e.detail})]}),(0,xe.jsxs)("span",{"data-catalog-entry-readiness":!0,className:"ml-auto inline-flex shrink-0 items-center gap-1 whitespace-nowrap rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:[r?(0,xe.jsx)(t,{size:12,className:"animate-spin","aria-hidden":"true"}):null,r?"Adding…":e.readinessLabel]})]}),l=e.insertable?"block"===e.kind?`${e.description} ${ye(e.integrationStatus)}. Add this exact pinned Modular Diffusers definition from ${e.modularBlockContexts?.length??1} compatible context${1===(e.modularBlockContexts?.length??1)?"":"s"} as an ordinary composable node.`:"graph_qualified"===e.readiness?`${e.description} ${ye(e.integrationStatus)}. Custom memory settings use your configured recipe; Automatic checks the exact runtime, installed artifacts, and available resources before submission.`:`${e.description} ${ye(e.integrationStatus)}. Insert the reviewed structural Block; execution remains unavailable until its exact graph, artifact, runtime, and resource admission completes.`:`${e.description} ${ye(e.integrationStatus)}. Catalog metadata only; insertion is unavailable until its exact graph compiler is admitted.`;return e.insertable?(0,xe.jsx)(H,{"aria-label":`${e.label}. ${e.readinessLabel}; insert ${"cluster"===e.kind?"Block":"Modular Diffusers block"}.`,className:"cursor-pointer flex-wrap gap-y-1 py-1 hover:bg-modiff-surface-hover","data-readiness":e.readiness,"data-testid":`hugging-face-node-row-${Ne(e.id)}`,"aria-busy":r||void 0,disabled:r,draggable:!r,level:a,onClick:()=>n(e),onDragStart:s=>{s.dataTransfer.setData("text/plain","cluster"===e.kind?`${v}${e.id}`:`${R}${e.id}`),s.dataTransfer.effectAllowed="move"},title:l,children:i}):(0,xe.jsx)("div",{"aria-disabled":"true","aria-label":`${e.label}. ${e.readinessLabel}; insertion unavailable.`,className:"flex min-h-10 min-w-0 flex-wrap items-center gap-x-2 gap-y-1 rounded-modiff-compact py-1 pl-2 pr-2 text-sm text-modiff-text","data-readiness":e.readiness,"data-testid":`hugging-face-node-row-${Ne(e.id)}`,role:"listitem",title:l,children:i})}function ze({activeGroupIds:e,forceOpen:s,insertingDefinitionIds:t,level:a,nodes:n,onInsertCluster:r,onToggleGroup:i}){return n.map(n=>{let l=`hugging-face:${n.id}`,o=s||e.includes(l);return(0,xe.jsxs)("div",{"data-testid":`node-subgroup-${Ne(n.id)}`,children:[(0,xe.jsxs)(H,{"aria-expanded":o,className:"min-h-9 justify-between font-medium",level:a,onClick:()=>i(l),open:o,children:[(0,xe.jsx)("span",{className:"min-w-0 truncate",children:n.label}),(0,xe.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"text-xs text-modiff-subtle-text",children:Ie(n)}),(0,xe.jsx)(h,{size:14,className:B("text-modiff-subtle-text transition-transform",o&&"rotate-180")})]})]}),o?(0,xe.jsxs)(D,{level:a+1,children:[(0,xe.jsx)(ze,{activeGroupIds:e,forceOpen:s,insertingDefinitionIds:t,level:a+1,nodes:n.children,onInsertCluster:r,onToggleGroup:i}),n.entries.map(e=>(0,xe.jsx)($e,{entry:e,insertingDefinitionIds:t,level:a+1,onInsertCluster:r},e.id))]}):null]},n.id)})}function De({activeGroupIds:e,error:s,insertingDefinitionIds:t,loading:a,onToggleGroup:n,onRetry:r,onInsertCluster:l,searching:o,sections:d}){return a?(0,xe.jsx)("div",{"aria-live":"polite",className:"mb-1 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-sm text-modiff-subtle-text","data-testid":"hugging-face-node-catalog-loading",role:"status",children:"Loading Hugging Face node catalog…"}):s?(0,xe.jsxs)("div",{className:"mb-1 grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-sm text-modiff-subtle-text","data-testid":"hugging-face-node-catalog-error",role:"alert",children:[(0,xe.jsx)("span",{children:"Hugging Face node catalog unavailable. Runtime nodes and Saved Blocks are unaffected."}),(0,xe.jsx)(oe,{align:"left",className:"h-8 px-2 text-xs",onClick:()=>{r()},tone:"secondary",children:"Retry catalog"})]}):d.map(s=>{let a=`hugging-face:${s.id}`,r=o||e.includes(a);return(0,xe.jsxs)("div",{"data-testid":`node-group-${Ne(s.label)}`,className:B("mb-1 rounded-modiff-compact border border-transparent transition-colors",r&&"border-modiff-border bg-modiff-surface-hover/40"),children:[(0,xe.jsxs)(H,{"aria-expanded":r,className:"min-h-10 justify-between px-2 font-semibold",onClick:()=>n(a),open:r,children:[(0,xe.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"grid size-4 shrink-0 place-items-center text-hf-yellow",children:s.id.endsWith("_cluster_nodes")?(0,xe.jsx)(p,{size:15}):(0,xe.jsx)(i,{size:15})}),(0,xe.jsx)("span",{className:"min-w-0 truncate leading-none",children:s.label})]}),(0,xe.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:s.entries.length}),(0,xe.jsx)(h,{size:16,className:B("text-modiff-subtle-text transition-transform",r&&"rotate-180")})]})]}),r?(0,xe.jsx)(D,{level:1,children:(0,xe.jsx)("div",{role:"list","aria-label":s.label,children:(0,xe.jsx)(ze,{activeGroupIds:e,forceOpen:o,insertingDefinitionIds:t,level:1,nodes:Ce(s),onInsertCluster:l,onToggleGroup:n})})}):null]},s.id)})}function Se(e,s){let t=new Map;e.forEach(e=>{let a=t,n=z(e,s);n.forEach((t,r)=>{let i=`user-nodes:${s}:${JSON.stringify(n.slice(0,r+1))}`,l=a.get(t)??{id:i,label:t,blocks:[],children:new Map};a.set(t,l),a=l.children,r===n.length-1&&l.blocks.push(e)})});let a=e=>[...e.values()].sort((e,s)=>e.label.localeCompare(s.label)).map(e=>({id:e.id,label:e.label,blocks:[...e.blocks].sort((e,s)=>F(e).localeCompare(F(s))||S(e).localeCompare(S(s))),children:a(e.children)}));return a(t)}function Be(e){return e.blocks.length+e.children.reduce((e,s)=>e+Be(s),0)}function Fe({activeGroupIds:e,blocks:s,forceOpen:t,level:a,onInsertUserBlock:n,onOpenUserBlockMenu:r,onToggleGroup:l}){return s.map(s=>{let o=`user-block:${s.id}`,d=t||e.includes(o);return(0,xe.jsxs)("div",{"data-testid":`user-node-subgroup-${Ne(s.id)}`,children:[(0,xe.jsxs)(H,{"aria-expanded":d,className:"min-h-9 justify-between font-medium",level:a,onClick:()=>l(o),open:d,children:[(0,xe.jsx)("span",{className:"min-w-0 truncate",children:s.label}),(0,xe.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"text-xs text-modiff-subtle-text",children:Be(s)}),(0,xe.jsx)(h,{size:14,className:B("text-modiff-subtle-text transition-transform",d&&"rotate-180")})]})]}),d?(0,xe.jsxs)(D,{level:a+1,children:[(0,xe.jsx)(Fe,{activeGroupIds:e,blocks:s.children,forceOpen:t,level:a+1,onInsertUserBlock:n,onOpenUserBlockMenu:r,onToggleGroup:l}),s.blocks.map(e=>(0,xe.jsxs)(H,{"data-testid":`user-block-row-${Ne(S(e))}`,draggable:!0,level:a+1,title:`Add Block · ${S(e)} · ${L(e)}`,className:"cursor-grab",onClick:()=>n(e),onContextMenu:s=>{s.preventDefault(),r(e,{left:s.clientX,top:s.clientY})},onDragStart:s=>{s.dataTransfer.setData("text/plain",`${G(e)?ae:K}${S(e)}`),s.dataTransfer.effectAllowed="move"},children:[(0,xe.jsx)("span",{className:"grid size-4 shrink-0 place-items-center self-start text-hf-yellow",children:(0,xe.jsx)(i,{size:15})}),(0,xe.jsxs)("span",{className:"min-w-0 flex-1 whitespace-normal break-words text-left",children:[(0,xe.jsx)("span",{className:"block",children:F(e)}),(0,xe.jsx)("span",{className:"block text-xs text-modiff-subtle-text",children:L(e)}),(0,xe.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:S(e)})]})]},S(e)))]}):null]},s.id)})}function Me({nodes:e,search:s,view:t,userBlocks:a,huggingFaceSections:n,huggingFaceLoading:r,huggingFaceError:l,insertingHuggingFaceDefinitionIds:o,onRetryHuggingFaceLibrary:d,onInsertNode:c,onInsertHuggingFaceCluster:u,onInsertUserBlock:m,onImportHuggingFaceUserNode:x,onOpenUserBlockMenu:p}){let{activeNodeGroups:g,setActiveNodeGroups:b}=O(),j=(0,fe.useMemo)(()=>se(e).filter(e=>X(e,t)).filter(e=>ie(e,s)).reduce((e,s)=>{let t=V(s);return e[t]||(e[t]=[]),e[t].push(s),e},{}),[e,s,t]),k=Object.entries(j).sort(([e],[s])=>e.localeCompare(s)),w=O(e=>e.userBlockGrouping),v=O(e=>e.setUserBlockGrouping),N=(0,fe.useMemo)(()=>ue(a).filter(e=>_(e,s)),[s,a]),y=g.includes("User Nodes")||!(!s.trim()||!N.length);return(0,xe.jsxs)(xe.Fragment,{children:[(0,xe.jsx)(De,{activeGroupIds:g,error:l,insertingDefinitionIds:o,loading:r,onToggleGroup:b,onRetry:d,onInsertCluster:u,searching:!!s.trim(),sections:n}),(0,xe.jsxs)("div",{"data-testid":"node-group-User-Nodes",className:B("mb-1 rounded-modiff-compact border border-transparent transition-colors",y&&"border-modiff-border bg-modiff-surface-hover/40"),children:[(0,xe.jsxs)(H,{"aria-expanded":y,className:"min-h-10 justify-between px-2 font-semibold",onClick:()=>b("User Nodes"),open:y,children:[(0,xe.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"grid size-4 shrink-0 place-items-center text-hf-yellow",children:(0,xe.jsx)(i,{size:15})}),(0,xe.jsx)("span",{className:"min-w-0 truncate leading-none",children:"Saved Blocks"})]}),(0,xe.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:N.length}),(0,xe.jsx)(h,{size:16,className:B("text-modiff-subtle-text transition-transform",y&&"rotate-180")})]})]}),y?(0,xe.jsxs)(D,{level:1,children:[(0,xe.jsxs)("div",{className:"grid gap-1 px-2 py-2",children:[(0,xe.jsx)(Z,{"aria-label":"Group Saved Blocks by",value:w,options:[{value:"source",label:"By source and family"},{value:"workflow",label:"By saved workflow context"}],onValueChange:e=>v("workflow"===e?"workflow":"source")}),(0,xe.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Saved copies only. Updating replaces the library revision; open workflows keep their own copy."})]}),(0,xe.jsxs)(H,{"data-testid":"import-hugging-face-user-node",level:1,title:"Import an immutable Modular Diffusers block from Hugging Face Hub",onClick:x,children:[(0,xe.jsx)("span",{className:"grid size-4 shrink-0 place-items-center text-hf-yellow",children:(0,xe.jsx)(f,{size:15})}),(0,xe.jsx)("span",{className:"min-w-0 flex-1 truncate",children:"Import from Hugging Face Hub"})]}),(0,xe.jsx)(Fe,{activeGroupIds:g,blocks:Se(N,w),forceOpen:!!s.trim(),level:1,onInsertUserBlock:m,onOpenUserBlockMenu:p,onToggleGroup:b})]}):null]}),0===k.length&&0===N.length&&0===n.length?(0,xe.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-sm text-modiff-subtle-text",children:"No nodes match this view."}):k.map(([e,t])=>{let a=!!s.trim()||g.includes(e);return(0,xe.jsxs)("div",{"data-testid":`node-group-${Ne(e)}`,className:B("mb-1 rounded-modiff-compact border border-transparent transition-colors",a&&"border-modiff-border bg-modiff-surface-hover/40"),children:[(0,xe.jsxs)(H,{"aria-expanded":a,className:"min-h-10 justify-between px-2 font-semibold",onClick:()=>b(e),open:a,children:[(0,xe.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"grid size-4 shrink-0 place-items-center text-hf-yellow",children:ve(e)}),(0,xe.jsx)("span",{className:"min-w-0 truncate leading-none",children:e})]}),(0,xe.jsxs)("span",{className:"flex shrink-0 items-center gap-2",children:[(0,xe.jsx)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:t.length}),(0,xe.jsx)(h,{size:16,className:B("text-modiff-subtle-text transition-transform",a&&"rotate-180")})]})]}),a?(0,xe.jsx)(D,{level:1,children:(0,xe.jsx)(he,{entries:t,search:s,openGroups:g,toggle:b,insert:c})}):null]},e)})]})}export{De as HuggingFaceNodeGroups,je as default}; \ No newline at end of file diff --git a/web/assets/NodeSearchDialog.js b/web/assets/NodeSearchDialog.js index 3627eb1e..baaa8483 100644 --- a/web/assets/NodeSearchDialog.js +++ b/web/assets/NodeSearchDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{pr as t,ur as a}from"./graph-vendor.js?v=5bcd315505f5670a";import{Sn as s,an as r,dt as l,ln as n}from"./studio-templates.js?v=5bcd315505f5670a";var o=e(t(),1),i=a(),c=({anchorPosition:e,onClose:t,onSelect:a,nodes:c,dataType:d,handleType:u})=>{let[f,m]=(0,o.useState)(""),[p,h]=(0,o.useState)(0),x=(0,o.useRef)(null);(0,o.useEffect)(()=>{if(e){let e=setTimeout(()=>{x.current&&x.current.focus()},10);return()=>clearTimeout(e)}},[e]);let b=(0,o.useMemo)(()=>{if(!d)return Object.entries(c);let e=Array.isArray(d)?d:[d];return Object.entries(c).filter(([,t])=>Object.values(t.params).some(t=>{if("source"===u&&"input"!==t.display||"target"===u&&"output"!==t.display)return!1;let a=t.type||"default",s=Array.isArray(a)?a:[a];return s.includes("any")||e.includes("any")||s.some(t=>e.includes(t))}))},[c,d,u]).filter(([,e])=>e.label.toLowerCase().includes(f.toLowerCase())||(e.description??"").toLowerCase().includes(f.toLowerCase())),v=(0,o.useCallback)(()=>{t(),setTimeout(()=>{m(""),h(0)},0)},[t]),g=(0,o.useCallback)(e=>{switch(e.key){case"ArrowDown":e.preventDefault(),b.length>0&&h(e=>(e+1)%b.length);break;case"ArrowUp":e.preventDefault(),b.length>0&&h(e=>(e-1+b.length)%b.length);break;case"Enter":if(b[p]){let[e,t]=b[p];a(e,t)}v();break;case"Escape":v()}},[b,p,a,v]);return e?(0,i.jsxs)(n,{anchor:e,ariaLabel:"Search nodes",closeOnOutside:!1,gap:0,modal:!0,onClose:v,open:!0,panelClassName:"w-[368px] max-h-[512px] overflow-hidden border-4 border-modiff-bg bg-modiff-panel",placement:"bottom-start",children:[(0,i.jsx)("div",{className:"p-2",children:(0,i.jsx)(r,{ref:x,"aria-label":"Search nodes","aria-activedescendant":b[p]?`node-search-${p}`:void 0,autoFocus:!0,placeholder:"Search nodes",value:f,onChange:e=>{m(e.currentTarget.value),h(0)},onClear:()=>{m(""),h(0)},onKeyDown:e=>{g(e.nativeEvent)}})}),(0,i.jsx)("div",{className:"max-h-[456px] overflow-auto",role:"listbox","aria-label":"Matching nodes",children:0===b.length?(0,i.jsxs)("div",{className:"px-4 py-6 text-center",children:[(0,i.jsx)("div",{className:"text-sm font-semibold text-modiff-text",children:"No results found"}),(0,i.jsx)("div",{className:"text-xs text-modiff-subtle-text",children:"Try a different search query"})]}):b.map(([e,t],r)=>(0,i.jsxs)(l,{type:"button",id:`node-search-${r}`,role:"option","aria-selected":r===p,onClick:()=>{a(e,t),v()},className:s("block w-full px-3 py-2 text-left transition hover:bg-modiff-surface-hover",r===p&&"bg-modiff-surface"),children:[(0,i.jsx)("div",{className:"truncate text-sm text-modiff-text",children:t.label}),t.description?(0,i.jsx)("div",{className:"truncate text-xs text-modiff-subtle-text",children:t.description.substring(0,72)+(t.description.length>72?"...":"")}):null]},e))})]}):null};export{c as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as a}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ao as n,Ci as o,Cn as s,D as i,E as r,Ei as l,Eo as c,In as d,M as u,O as f,Oo as p,P as h,Q as m,Si as x,Ti as g,Tt as b,Xr as y,Yr as k,Z as v,cc as j,cn as C,ii as w,ji as N,ni as S,o as D,oa as I,oc as E,q as T,ri as M,si as A,yn as B}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as H}from"./huggingFaceCatalogAdmission.js?v=cc046c26d6fe7aa4";import{n as F}from"./operationCatalog.js?v=cc046c26d6fe7aa4";import{a as O}from"./operationAuthoring.js?v=cc046c26d6fe7aa4";import{t as $}from"./NodeDiscoveryFilters.js?v=cc046c26d6fe7aa4";import{a as L,i as R,n as _,o as K,r as P,t as V}from"./nodeConnectionSearch.js?v=cc046c26d6fe7aa4";var G=e(a(),1);function W(){throw Error("Invalid registered Block interface index.")}function q(e){return("string"!=typeof e||!e||e.length>2048||/\p{Cc}/u.test(e))&&W(),e}function z(e){(!Array.isArray(e)||e.length>512)&&W();let t=e.map(e=>{(!I(e)||"portId,valueType"!==Object.keys(e).sort().join(","))&&W();let t=Array.isArray(e.valueType)?e.valueType.length&&e.valueType.length<=64?e.valueType.map(q):W():q(e.valueType);return{portId:q(e.portId),valueType:t}});return new Set(t.map(e=>e.portId)).size!==t.length&&W(),t}function Q(e){(!I(e)||"entries,error,schemaVersion"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||!1!==e.error||!Array.isArray(e.entries)||e.entries.length>2048)&&W();let t=e.entries.map(e=>{(!I(e)||"admissionId,catalogDefinitionContentHash,catalogDefinitionId,compiledDefinitionCanonicalSha256,compiledDefinitionContentHash,inputs,outputs"!==Object.keys(e).sort().join(","))&&W();let t={catalogDefinitionId:q(e.catalogDefinitionId),catalogDefinitionContentHash:q(e.catalogDefinitionContentHash),admissionId:q(e.admissionId),compiledDefinitionContentHash:q(e.compiledDefinitionContentHash),compiledDefinitionCanonicalSha256:q(e.compiledDefinitionCanonicalSha256),inputs:z(e.inputs),outputs:z(e.outputs)};return/^sha256:[a-f0-9]{64}$/u.test(t.compiledDefinitionCanonicalSha256)||W(),t});return new Set(t.map(e=>`${e.catalogDefinitionId}\0${e.admissionId}`)).size!==t.length&&W(),t}var U=null,X=j(e=>({entries:[],loaded:!1,error:null,fetch:()=>U||(U=(async()=>{try{e({entries:await p(`${E.serverAddress}/huggingface/registered-block-interfaces`,{parse:Q,timeoutMs:3e4}),loaded:!0,error:null})}catch(t){e({entries:[],loaded:!0,error:c(t,"Could not load Block interfaces.")})}})().finally(()=>{U=null}),U)}));function Y(e,t,a,o,s,r,l,c=!1){return M(e,o,s).flatMap(({entries:e})=>e).filter(e=>{if(!e.insertable||"component"===e.kind)return!1;if(!l)return!0;let o=null;if("cluster"===e.kind){let n=t.library.definitions.find(t=>t.id===e.id),s=n?H(n,t.form).route:null;s&&(o=function(e,t){let a=e.find(e=>e.catalogDefinitionId===t.definitionId&&e.admissionId===t.admissionId);if(!a||a.catalogDefinitionContentHash!==t.definitionContentHash||a.compiledDefinitionContentHash!==t.compiledDefinitionContentHash||a.compiledDefinitionCanonicalSha256!==t.compiledDefinitionCanonicalSha256)return null;let n={};for(let[e,t]of[["input",a.inputs],["output",a.outputs]])for(let a of t)n[`${e}:${a.portId}`]={display:e,type:a.valueType};return{type:"block",module:"",action:"",label:"",category:"",params:n}}(a,s))}else{if(f(e,t.library,t.snapshot))return!1;try{o=i(e,t.library,t.registry,{x:0,y:0},t.snapshot).data}catch{return!1}}return!(!o||!n(o,r,l,void 0,c))})}async function Z(e,t,a,n){if(n.aborted)throw new DOMException("Insertion cancelled.","AbortError");if(!e.insertable)throw Error("This catalog entry is not available for insertion.");if("block"===e.kind)return f(e,t.library,t.snapshot)?r(e,t.library,t.registry,a,t.snapshot):i(e,t.library,t.registry,a,t.snapshot);let s=t.library.definitions.find(t=>t.id===e.id);if("cluster"!==e.kind||!s)throw Error("The selected catalog Block is no longer available.");let{createHuggingFaceClusterForGraph:l}=await o(async()=>{let{createHuggingFaceClusterForGraph:e}=await import(`./huggingFaceClusterInsertion.js?v=cc046c26d6fe7aa4`).then(e=>e.n);return{createHuggingFaceClusterForGraph:e}},[]);return l(s,a,t.form,{insert:!1,signal:n})}var J=t(),ee=({anchorPosition:e,onClose:t,onSelect:a,nodes:o,dataType:i,handleType:r,origin:f})=>{let{pipeline:p,task:j,view:I,support:E}=v(),M=x(e=>e.operationContracts),H=x(e=>e.resolveOperation),W=N(e=>e.activeWorkflowTabId),q=N(e=>e.workflowCanvasEpoch),z=(0,G.useRef)(null),[Q,U]=(0,G.useState)(!1),[ee,te]=(0,G.useState)(null),[ae,ne]=(0,G.useState)(""),[oe,se]=(0,G.useState)(0),[ie,re]=(0,G.useState)(!0),[le,ce]=(0,G.useState)(!0),[de,ue]=(0,G.useState)(!1),fe=(0,G.useRef)(null),pe=A(e=>e.blocks),he=A(e=>e.blockDefinitionsV2),me=A(e=>e.loaded),xe=A(e=>e.fetchBlocks),ge=A(e=>e.error),be=(0,G.useMemo)(()=>L([...he,...pe]),[he,pe]),ye=y(e=>e.library),ke=y(e=>e.loaded),ve=y(e=>e.error),je=y(e=>e.fetchLibrary),Ce=k(e=>e.snapshot),we=k(e=>e.loaded),Ne=k(e=>e.error),Se=k(e=>e.fetchSnapshot),De=X(e=>e.entries),Ie=X(e=>e.loaded),Ee=X(e=>e.error),Te=X(e=>e.fetch),Me=N(e=>e.form),Ae=(0,G.useMemo)(()=>ye?{library:ye,snapshot:Ce,registry:o,form:Me}:null,[ye,Ce,o,Me]),Be=(0,G.useMemo)(()=>ye?S(ye,Ce):[],[ye,Ce]),He=(0,G.useMemo)(()=>Ae?Y(Be,Ae,De,ae,I,i,r,de):[],[Be,Ae,De,ae,I,i,r,de]);(0,G.useEffect)(()=>{ke||je()},[ke,je]),(0,G.useEffect)(()=>{we||Se()},[we,Se]),(0,G.useEffect)(()=>{r&&!Ie&&Te()},[r,Ie,Te]),(0,G.useEffect)(()=>{me||xe()},[me,xe]),(0,G.useEffect)(()=>{if(e){let e=setTimeout(()=>{fe.current&&fe.current.focus()},10);return()=>clearTimeout(e)}},[e]);let Fe=(0,G.useMemo)(()=>D(o,M,E,I,{pipeline:p,task:j}),[o,M,E,I,p,j]),Oe=(0,G.useMemo)(()=>R([..._(M,p,j,i,r,ae,o,f,de).map(e=>({key:`operation:${e.operationId}`,label:F(e),operation:e,node:o[e.nodeKey]})),...V(Fe,i,r,ae,I,f,de).map(([e,t])=>({key:e,node:t,label:w(t)})),...He.map(e=>({key:`catalog:${e.id}`,label:e.label,catalog:e})),...be.filter(({block:e,node:t})=>h(e,ae)&&(!r||n(t,i,r,f,de))).map(e=>({...e,label:e.node.label}))],i,r,f),[Fe,M,p,j,I,i,r,ae,be,o,He,f,de]),{builtInEntries:$e,customEntries:Le}=(0,G.useMemo)(()=>({builtInEntries:Oe.filter(e=>!e.node?.module.startsWith("custom.")),customEntries:Oe.filter(e=>e.node?.module.startsWith("custom."))}),[Oe]),Re=(0,G.useMemo)(()=>[...ie?$e:[],...le?Le:[]],[$e,ie,Le,le]);(0,G.useEffect)(()=>(z.current?.abort(),z.current=null,U(!1),te(null),()=>{z.current?.abort()}),[p,j,I,W,q,e]);let _e=Math.min(oe,Math.max(0,Re.length-1)),Ke=(0,G.useCallback)(()=>{z.current?.abort(),t(),setTimeout(()=>{ne(""),se(0)},0)},[t]),Pe=(0,G.useCallback)(async e=>{if(z.current)return;let t=new AbortController,n=l(),o=m.getState().selection;z.current=t,U(!0),te(null);try{let s;if(e.operation){let a=e.operation,n=O(P(await H(a,t.signal),a,i,r),a);s=e=>T(a.nodeKey,{[a.nodeKey]:n},e)}else if(e.catalog&&Ae){let a=e.catalog;s=async e=>{let n=await Z(a,Ae,e,t.signal);if(y.getState().library!==Ae.library)throw Error("The catalog changed. Select the Block again.");return n}}else if(e.block){let t=e.block;s=e=>u(t,e)}else{if(!e.node)return;{let t=e.node;s=a=>T(e.key,{[e.key]:t},a)}}if(t.signal.aborted||m.getState().selection!==o||(g(n,{includeForm:!1}),await a(s,t.signal,de),t.signal.aborted))return;Ke()}catch(e){t.signal.aborted||te(c(e,"Could not resolve the selected node."))}finally{z.current===t&&(z.current=null,U(!1))}},[H,a,Ke,i,r,Ae,de]),Ve=(0,G.useCallback)(e=>{switch(e.key){case"ArrowDown":e.preventDefault(),Re.length>0&&se(e=>(e+1)%Re.length);break;case"ArrowUp":e.preventDefault(),Re.length>0&&se(e=>(e-1+Re.length)%Re.length);break;case"Enter":Re[_e]&&Pe(Re[_e]);break;case"Escape":Ke()}},[Re,_e,Pe,Ke]);if(!e)return null;let Ge=(e,t)=>(0,J.jsxs)(b,{type:"button",id:`node-search-${t}`,role:"option","aria-selected":t===_e,disabled:Q,"aria-busy":Q||void 0,onClick:()=>{Pe(e)},className:d("block w-full px-3 py-2 text-left transition hover:bg-modiff-surface-hover",t===_e&&"bg-modiff-surface"),children:[(0,J.jsx)("div",{className:"truncate text-sm text-modiff-text",children:e.label}),e.block?(0,J.jsxs)("div",{className:"text-xs text-modiff-subtle-text",children:["Saved Block · ",e.revision]}):null,e.catalog?(0,J.jsxs)("div",{className:"text-xs text-modiff-subtle-text",children:["cluster"===e.catalog.kind?"Block":"Implementation"," · ",e.catalog.readinessLabel]}):null,e.operation?(0,J.jsxs)("div",{className:"text-xs text-modiff-subtle-text",children:[e.operation.pipelineClass," · ",e.operation.task?.replace(/_/gu," ")]}):null,e.node?.description?(0,J.jsx)("div",{className:"truncate text-xs text-modiff-subtle-text",children:e.node.description.substring(0,72)+(e.node.description.length>72?"...":"")}):null]},e.key),We=(e,t,a,n)=>(0,J.jsxs)(b,{type:"button","aria-expanded":a,onClick:n,className:"flex w-full shrink-0 items-center gap-2 border-b border-modiff-border px-3 py-2 text-left text-xs font-semibold text-modiff-text",children:[(0,J.jsx)("span",{"aria-hidden":"true",children:a?"▾":"▸"}),(0,J.jsx)("span",{className:"min-w-0 flex-1 truncate",children:e}),(0,J.jsx)("span",{className:"text-modiff-subtle-text",children:t})]});return(0,J.jsxs)(s,{anchor:e,ariaLabel:"Search nodes",closeOnOutside:!1,gap:0,modal:!0,onClose:Ke,open:!0,panelClassName:"flex h-[min(512px,calc(100dvh-16px))] w-[368px] flex-col overflow-hidden border-4 border-modiff-bg bg-modiff-panel",placement:"bottom-start",children:[(0,J.jsx)("div",{className:"shrink-0 p-2",children:(0,J.jsx)(B,{ref:fe,"aria-label":"Search nodes","aria-activedescendant":Re[_e]?`node-search-${_e}`:void 0,autoFocus:!0,placeholder:"Search nodes and Blocks",value:ae,onChange:e=>{ne(e.currentTarget.value),se(0)},onClear:()=>{ne(""),se(0)},onKeyDown:e=>{Ve(e.nativeEvent)}})}),(0,J.jsx)($,{}),(0,J.jsx)(K,{inPopover:!0}),ee?(0,J.jsx)("p",{role:"alert",className:"px-3 pb-2 text-xs text-modiff-text",children:ee}):null,me?null:(0,J.jsx)("p",{role:"status",className:"px-3 pb-2 text-xs text-modiff-subtle-text",children:"Loading Saved Blocks…"}),ge?(0,J.jsxs)("div",{role:"status",className:"px-3 pb-2 text-xs text-modiff-subtle-text",children:["Saved Blocks could not be loaded.",(0,J.jsx)(b,{onClick:()=>{xe()},children:"Retry Saved Blocks"})]}):null,!ke||!we||r&&!Ie?(0,J.jsx)("p",{role:"status",className:"px-3 pb-2 text-xs text-modiff-subtle-text",children:"Loading catalog Blocks…"}):null,ve||Ne||r&&Ee?(0,J.jsxs)("div",{role:"status",className:"px-3 pb-2 text-xs text-modiff-subtle-text",children:["Some catalog Blocks or compatible ports could not be loaded.",(0,J.jsx)(b,{onClick:()=>{ve&&je(),Ne&&Se(),Ee&&Te()},children:"Retry catalog"})]}):null,r?(0,J.jsxs)("div",{className:"shrink-0 px-3 pb-2 text-xs text-modiff-subtle-text",children:[(0,J.jsx)(C,{checked:de,onCheckedChange:ue,label:"Show unverified matches (generic or missing types)"}),(0,J.jsx)("p",{children:"source"===r?"Nodes with matching input port types":"Nodes with matching output port types"}),(0,J.jsx)("p",{children:"Model, shape, and custom-code constraints are checked when declared and again at Run."})]}):null,(0,J.jsxs)("div",{className:"flex min-h-0 flex-1 flex-col overflow-hidden",role:"listbox","aria-label":"Matching nodes",children:[(0,J.jsxs)("section",{className:d("flex min-h-0 flex-col",ie&&$e.length?"flex-1":"shrink-0"),children:[We("Built-in nodes and Blocks",$e.length,ie,()=>{re(e=>!e),se(0)}),ie?(0,J.jsx)("div",{className:d($e.length?"min-h-0 flex-1 overflow-auto":"shrink-0"),children:$e.length?$e.map((e,t)=>Ge(e,t)):(0,J.jsxs)("div",{className:"px-4 py-4 text-center",children:[(0,J.jsx)("div",{className:"text-sm font-semibold text-modiff-text",children:r?"No compatible nodes found":"No results found"}),(0,J.jsx)("div",{className:"text-xs text-modiff-subtle-text",children:r?"Try another search or start from a different port.":"Try a different search query"})]})}):null]}),(0,J.jsxs)("section",{className:d("flex min-h-0 flex-col",le&&Le.length?"flex-1":"shrink-0"),children:[We("Custom nodes",Le.length,le,()=>{ce(e=>!e),se(0)}),le?(0,J.jsx)("div",{className:d(Le.length?"min-h-0 flex-1 overflow-auto":"shrink-0"),children:Le.length?Le.map((e,t)=>Ge(e,(ie?$e.length:0)+t)):(0,J.jsx)("div",{className:"px-4 py-3 text-center text-xs text-modiff-subtle-text",children:"No matching enabled custom nodes"})}):null]})]})]})};export{ee as default}; \ No newline at end of file diff --git a/web/assets/OperationCatalogPanel.js b/web/assets/OperationCatalogPanel.js new file mode 100644 index 00000000..a909b914 --- /dev/null +++ b/web/assets/OperationCatalogPanel.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as a}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ei as o,Eo as s,Pt as n,Q as r,Si as i,Ti as l,X as d,Y as p,Z as c,ji as u}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as m}from"./operationCatalog.js?v=cc046c26d6fe7aa4";import{a as f}from"./operationAuthoring.js?v=cc046c26d6fe7aa4";import{t as x}from"./OperationGraphControls.js?v=cc046c26d6fe7aa4";import{n as h,o as b}from"./nodeConnectionSearch.js?v=cc046c26d6fe7aa4";var g=e(a(),1),j=t();function v({search:e,onInsert:t}){let a=i(e=>e.operationContracts),v=i(e=>e.nodesRegistry),k=i(e=>e.resolveOperation),N=u(e=>e.activeWorkflowTabId),{pipeline:w,task:C,selected:S,support:y}=c(),E=u(e=>e.workflowCanvasEpoch),[D,R]=(0,g.useState)(!1),[I,T]=(0,g.useState)(null),$=(0,g.useRef)(null),A=(0,g.useMemo)(()=>S?h(a,w,S.task,void 0,void 0,e,v):[],[a,w,S,e,v]);return(0,g.useEffect)(()=>($.current?.abort(),$.current=null,R(!1),T(null),()=>{$.current?.abort()}),[w,C,N,E]),y.length?(0,j.jsxs)("section",{"aria-label":"Diffusers operations",className:"mb-2 border-b border-modiff-border pb-2",children:[(0,j.jsxs)("div",{className:"space-y-2 px-2 pb-2",children:[(0,j.jsx)(b,{}),S?(0,j.jsxs)("p",{role:"status",className:"text-xs text-modiff-subtle-text",children:["adapter"===S.execution?"Execution adapter available.":"declared"===S.execution?"Declared nodes; no execution adapter.":"No operation binding for this task."," ","pipeline"===S.decomposition?"Whole pipeline call.":"stages"===S.decomposition?"Editable nodes.":""," ","blocked"===S.dependencies?"Runtime setup required in Setup.":"ready"===S.dependencies?"Runtime available; Run still checks models and resources.":"Runtime support unverified."]}):null,I?(0,j.jsx)("p",{role:"alert",className:"text-xs text-modiff-text",children:I}):null]}),S?.operationIds.length?(0,j.jsx)(x,{pipeline:w,task:S.task}):null,A.map(e=>(0,j.jsxs)(n,{level:0,draggable:!D,onDragStart:t=>{D?t.preventDefault():(t.dataTransfer.setData("text/plain",p(e)),t.dataTransfer.effectAllowed="move")},onDragEnd:d,disabled:D,"aria-busy":D||void 0,onClick:()=>{!async function(e){if($.current)return;let a=o(),n=r.getState().selection,i=new AbortController;$.current=i,R(!0),T(null);try{let o=await k(e,i.signal);if(i.signal.aborted||r.getState().selection!==n)return;l(a,{includeForm:!1}),t(`${o.module}.${o.action}`,f(o,e))}catch(e){i.signal.aborted||T(s(e,"Could not resolve the selected operation."))}finally{$.current===i&&($.current=null,R(!1))}}(e)},title:`Add ${m(e)} for ${e.pipelineClass}. ${"pipeline"===e.decomposition?"This operation calls the complete pipeline.":"Uses the existing node execution path."}`,children:[(0,j.jsx)("span",{className:"min-w-0 flex-1 break-words",children:m(e)}),(0,j.jsx)("span",{className:"text-xs text-modiff-subtle-text",children:"pipeline"===e.decomposition?"Pipeline":"loader"===e.decomposition?"Load":"Node"})]},e.operationId)),S&&!A.length&&S.operationIds.length>0?(0,j.jsx)("p",{className:"px-2 text-xs text-modiff-subtle-text",children:"No operations match this search."}):null]}):null}export{v as default}; \ No newline at end of file diff --git a/web/assets/OperationGraphControls.js b/web/assets/OperationGraphControls.js new file mode 100644 index 00000000..c17ffb4d --- /dev/null +++ b/web/assets/OperationGraphControls.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as r}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$ as n,Bo as a,Ci as s,Ei as l,Eo as i,Jo as o,Si as d,Ti as c,Vr as p,bn as u,dn as h,fn as g,in as m,ji as x,rc as j,rn as f}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as b}from"./operationGraphTransaction.js?v=cc046c26d6fe7aa4";import{t as w}from"./operationStarterRequest.js?v=cc046c26d6fe7aa4";import{r as k,t as v}from"./operationAuthoring.js?v=cc046c26d6fe7aa4";import{t as C}from"./NodeInspectorDialog.js?v=cc046c26d6fe7aa4";var I=e(r(),1),y=t();function S({pipeline:e,task:t,ownerId:r,blockId:S,executionProfileId:N}){let O=p(e=>e.nodes),E=x(e=>e.activeWorkflowTabId),B=x(e=>e.workflowCanvasEpoch),A=d(e=>e.operationContracts),[R,T]=(0,I.useState)(""),q=r??R,[P,_]=(0,I.useState)(!1),[$,F]=(0,I.useState)(null),[J,U]=(0,I.useState)(null),[D,M]=(0,I.useState)(!1),V=(0,I.useRef)(null),G=O.filter(e=>!e.parentId&&!e.data.blockProjectionOwnerId&&j(e.data.operationAuthoring?.operation)),L=O.find(e=>e.selected&&e.data.operationAuthoring),W=L?o(L):null;async function K(r){if(V.current)return;let n=new AbortController;V.current=n;let a=l(),o=p.getState().toObject(),d=JSON.stringify(o);_(!0),F(null);try{let l=await w(e,t,A,n.signal,N);if(n.signal.aborted)return;if(c(a,{includeForm:!1}),JSON.stringify(p.getState().toObject())!==d)throw Error("The graph changed. Request a fresh preview.");let i=r?S?(await s(async()=>{let{planOwnerBlockOperationChange:e}=await import(`./operationLegacyBlockChange.js?v=cc046c26d6fe7aa4`).then(e=>e.t);return{planOwnerBlockOperationChange:e}},[])).planOwnerBlockOperationChange(o,S,q,l,{replaceModel:!!N}):k(o,q,l,{replaceModel:!!N}):null;if(n.signal.aborted)return;if(c(a,{includeForm:!1}),JSON.stringify(p.getState().toObject())!==d)throw Error("The graph changed. Request a fresh preview.");U({starter:l,plan:i,context:a,signature:d})}catch(e){n.signal.aborted||F(i(e,"Could not prepare the graph."))}finally{V.current===n&&(V.current=null,_(!1))}}return(0,I.useEffect)(()=>(V.current?.abort(),V.current=null,_(!1),U(null),F(null),M(!1),()=>{V.current?.abort()}),[e,t,E,B,q,S,N]),(0,y.jsxs)("div",{className:"space-y-2 px-2 pb-2",children:[r?null:(0,y.jsx)(f,{disabled:P,onClick:()=>{K(!1)},children:"Preview connected starter"}),S||G.length?(0,y.jsxs)(y.Fragment,{children:[r?null:(0,y.jsx)(g,{label:"Graph to change",children:(0,y.jsx)(u,{"aria-label":"Operation graph to change",value:q,onValueChange:T,options:[{value:"",label:"Select a loader on this canvas"},...G.map(e=>({value:e.id,label:`${e.data.operationAuthoring.operation.pipelineClass} · ${e.id.slice(-6)}`}))]})}),(0,y.jsx)(f,{disabled:P||!S&&!G.some(e=>e.id===q),onClick:()=>{K(!0)},children:"Preview model / task change"})]}):null,!r&&W?(0,y.jsx)(f,{onClick:()=>M(!0),children:"Inspect selected node"}):null,P?(0,y.jsx)("p",{role:"status",className:"text-xs text-modiff-subtle-text",children:"Preparing ordinary nodes and connections…"}):null,$?(0,y.jsx)("p",{role:"alert",className:"text-xs text-modiff-text",children:$}):null,J?(0,y.jsx)(m,{open:!0,title:J.plan?"Review model / task change":"Connected starter",onClose:()=>{U(null),F(null)},children:(0,y.jsxs)("div",{className:"space-y-3 text-sm",children:[(0,y.jsxs)("p",{children:[J.starter.pipelineClass," · ",J.starter.task.replace(/_/gu," ")]}),(0,y.jsx)("p",{children:J.starter.nodes.map(e=>e.node.label).join(" → ")}),(0,y.jsxs)("p",{children:[S?"The result updates the editable graph inside this Block.":"The result is an editable canvas graph."," ","Run checks model files, runtime support, required inputs and resources."]}),(0,y.jsx)("p",{children:"Connect the final result to a Preview, Save or Export node before running. Drag from its output to see compatible nodes."}),J.starter.sharedInputs.map((e,t)=>(0,y.jsxs)("p",{children:["Shared ",e.name,":"," ",e.members.map(e=>e.operationId.split(".")[1]?.replace(/_/gu," ")).join(", "),". Editing either control updates the group.","seed"===e.name?" Random mode draws one value per run.":""]},t)),J.starter.requiredInputs.length?(0,y.jsxs)("div",{children:[(0,y.jsx)("p",{children:"Provide or connect the required inputs:"}),(0,y.jsx)("ul",{className:"list-inside list-disc",children:J.starter.requiredInputs.map(e=>(0,y.jsxs)("li",{children:[e.operationId.split(".")[1]?.replace(/_/gu," ")," · ",e.field]},`${e.operationId}:${e.field}`))})]}):null,J.plan?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)("ul",{className:"list-inside list-disc",children:J.plan.changes.map((e,t)=>(0,y.jsx)("li",{children:e},t))}),J.plan.diagnostics.length?(0,y.jsxs)("div",{role:"status",children:[(0,y.jsx)("p",{children:"Review before applying:"}),(0,y.jsx)("ul",{className:"list-inside list-disc",children:J.plan.diagnostics.map((e,t)=>(0,y.jsx)("li",{children:e},t))})]}):null,(0,y.jsx)("p",{children:"Custom nodes are retained. Unsupported settings remain available in the inspector’s Implementation tab. Undo restores the entire change."})]}):null,(0,y.jsxs)(h,{label:"Inspect implementation",children:[(0,y.jsx)("p",{children:J.starter.workflowId?`Upstream workflow: ${J.starter.workflowId}`:"Whole pipeline adapter"}),(0,y.jsx)("ul",{className:"list-inside list-disc",children:J.starter.nodes.map(e=>(0,y.jsxs)("li",{children:[e.node.label,": ",e.operation.nodeKey,e.operation.blockName?` / ${e.operation.blockName}`:""]},e.operation.operationId))}),J.starter.upstreamBlocks.length?(0,y.jsx)("p",{className:"break-words",children:J.starter.upstreamBlocks.join(" → ")}):null,(0,y.jsx)("p",{children:"Inspection does not expand or convert saved Blocks. Use the existing Block composition inspector for structural editing."})]}),$?(0,y.jsx)("p",{role:"alert",children:$}):null,(0,y.jsxs)("div",{className:"flex gap-2",children:[(0,y.jsx)(f,{onClick:function(){if(J)try{c(J.context,{includeForm:!1});let e=p.getState();if(JSON.stringify(e.toObject())!==J.signature)throw Error("The graph changed after this preview. Close it and request a fresh preview.");if(J.plan)b(J.plan.graph,J.context,J.signature,"Change operation model or task");else{let t=e.nodes.length?Math.max(...e.nodes.map(e=>e.position.x+(e.measured?.width??e.width??360)))+100:80,r=a(v(J.starter,{x:t,y:120}));n(),b({nodes:[...e.nodes,...r.nodes],edges:[...e.edges,...r.edges]},l(),J.signature,"Add operation starter")}U(null)}catch(e){F(i(e,"Could not apply the graph change."))}},children:J.plan?"Apply graph change":"Add starter to canvas"}),(0,y.jsx)(f,{onClick:()=>{U(null),F(null)},children:"Cancel"})]})]})}):null,D&&L&&W?(0,y.jsx)(C,{nodeId:L.id,onClose:()=>M(!1)},L.id):null]})}export{S as t}; \ No newline at end of file diff --git a/web/assets/OperationModelField.js b/web/assets/OperationModelField.js new file mode 100644 index 00000000..37c2b6ba --- /dev/null +++ b/web/assets/OperationModelField.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as o,dr as t}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Jo as n,es as a,jn as d,rc as r}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as l}from"./operationLegacyBlockChange.js?v=cc046c26d6fe7aa4";import i from"./OperationModelPicker.js?v=cc046c26d6fe7aa4";var s=e(t(),1);function c(e,o){let t=(e,o)=>["model_id","repo_id"].includes(o)&&!!n(e)?.operation.task&&r(n(e)?.operation);if(t(e,o))return e;if(!e.data.blockInstanceV2&&!e.data.userBlockSnapshot||e.data.userBlockSnapshot&&e.data.uiState?.blockExpanded)return null;try{let n=l({nodes:[e],edges:[]},e.id).root.data.blockInstanceV2;if(!n)return null;let d=n.effectiveInterface.controls.find(e=>e.controlId===o);if(!d||d.sealed||d.mirrorBindings?.length)return null;let r=a(n).nodes.find(e=>e.data.blockProjectionNodeId===d.binding.nodeId);return r&&t(r,d.binding.fieldId)?{...r,data:{...r.data,blockProjectionOwnerId:e.id}}:null}catch{return null}}var f=o();function u({root:e,field:o,value:t,fallback:n}){let a=(0,s.useMemo)(()=>c(e,o.fieldKey),[e,o.fieldKey]);return a?(0,f.jsx)(d,{dataKey:o.fieldKey,hidden:o.hidden,layoutStyle:o.style,className:"modiff-field",children:(0,f.jsx)(i,{node:a,value:t,disabled:o.disabled||o.isConnected})}):n}export{u as default}; \ No newline at end of file diff --git a/web/assets/OperationModelPicker.js b/web/assets/OperationModelPicker.js new file mode 100644 index 00000000..840fa35a --- /dev/null +++ b/web/assets/OperationModelPicker.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as l,dr as t}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{As as s,Ci as a,Ei as n,Eo as o,Jo as i,Si as r,Ti as d,Vr as c,dn as p,in as m,ji as u,js as h,ks as f,rc as x,rn as b,yn as g}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as j,t as w}from"./operationGraphTransaction.js?v=cc046c26d6fe7aa4";import{t as C}from"./operationStarterRequest.js?v=cc046c26d6fe7aa4";import{n as k}from"./taskModelPreferences.js?v=cc046c26d6fe7aa4";import{i as N,r as v}from"./operationAuthoring.js?v=cc046c26d6fe7aa4";var y=e(t(),1),S=l();function O(e){let l=(e.repo||"").split("/");return e.label||l[l.length-1]||e.pipeline}function I({node:e,value:l,disabled:t}){let I=i(e),D=r(e=>e.pipelineSupport),R=r(e=>e.studioModelCapabilities),A=r(e=>e.workflowModelDescriptors),E=r(e=>e.hfCache),M=r(e=>e.localModels),P=r(e=>e.modelCacheDiagnostics),T=r(e=>e.operationContracts),L=u(e=>e.activeWorkflowTabId),[W,$]=(0,y.useState)(!1),[_,B]=(0,y.useState)(""),[q,F]=(0,y.useState)(!1),[V,U]=(0,y.useState)(!1),[G,J]=(0,y.useState)(null),[K,z]=(0,y.useState)(null),H=(0,y.useRef)(null),Q=(0,y.useMemo)(()=>s(D,R,E,M,P,A).filter(e=>e.task===I.operation.task&&e.repo&&e.profileId),[D,R,E,M,P,A,I.operation.task]),X=Q.find(e=>e.repo===l&&e.pipeline===I.operation.pipelineClass),Y=Q.filter(e=>(!q||e.cache?.runnable)&&`${e.label} ${e.repo}`.toLowerCase().includes(_.trim().toLowerCase()));function Z(){H.current?.abort(),H.current=null,U(!1),z(null),J(null),$(!1)}function ee(e){w(e.plan.graph,e.context,e.signature,e.restoreDefaults?"Restore model defaults":"Change model"),u.getState().saveActiveWorkflowTab(!0),!e.restoreDefaults&&e.choice.profileId&&k(e.choice.task,e.choice.profileId),Z()}async function le(l,t=!1){if(H.current)return;let s=new AbortController;H.current=s;let i=n(),r=c.getState().toObject(),p=j(r);U(!0),J(null),z(null);try{let n=await C(l.pipeline,l.task,T,s.signal,l.profileId);if(s.signal.aborted)return;d(i,{includeForm:!1});let o=e.data.blockProjectionOwnerId?null:await C(I.operation.pipelineClass,I.operation.task,T,s.signal);if(s.signal.aborted)return;let m=e.data.blockProjectionOwnerId?(await a(async()=>{let{planOwnerBlockOperationChange:e}=await import(`./operationLegacyBlockChange.js?v=cc046c26d6fe7aa4`).then(e=>e.t);return{planOwnerBlockOperationChange:e}},[])).planOwnerBlockOperationChange(r,e.data.blockProjectionOwnerId,e.data.blockProjectionNodeId??e.id,n,{replaceModel:!t,restoreDefaults:t}):!t&&o&&N(r,e.id,o,n)||v(r,e.id,n,{replaceModel:!t,restoreDefaults:t,baseline:o});if(s.signal.aborted)return;if(d(i,{includeForm:!1}),j(c.getState().toObject())!==p)throw Error("The workflow changed. Select the model again.");let u={plan:m,context:i,signature:p,choice:l,restoreDefaults:t};m.review.required?z(u):ee(u)}catch(e){s.signal.aborted||J(o(e,"Could not change this model."))}finally{H.current===s&&(H.current=null,U(!1))}}(0,y.useEffect)(()=>($(!1),()=>{H.current?.abort()}),[L,e.id]);let te=K?(0,S.jsxs)("section",{className:"w-full space-y-2","aria-label":"Review model selection","data-testid":"model-selection-review",children:[(0,S.jsxs)("div",{children:[(0,S.jsx)("p",{className:"font-semibold text-modiff-text",children:"Review selection"}),(0,S.jsxs)("p",{className:"text-sm text-modiff-subtle-text",children:["Apply ",O(K.choice)," to this workflow, or keep the current model."]})]}),K.plan.review.changes.length?(0,S.jsx)(p,{label:`What will change (${K.plan.review.changes.length})`,buttonClassName:"px-0 py-1 text-sm",panelClassName:"pt-1",children:(0,S.jsx)("ul",{className:"list-inside list-disc text-sm text-modiff-subtle-text",children:K.plan.review.changes.map((e,l)=>(0,S.jsx)("li",{children:e},l))})}):null,K.plan.review.preserved.length?(0,S.jsx)(p,{label:`Values and connections kept (${K.plan.review.preserved.length})`,buttonClassName:"px-0 py-1 text-sm",panelClassName:"pt-1",children:(0,S.jsx)("ul",{className:"list-inside list-disc text-sm text-modiff-subtle-text",children:K.plan.review.preserved.map((e,l)=>(0,S.jsx)("li",{children:e},l))})}):null,K.plan.review.attention.length?(0,S.jsx)(p,{label:`Needs attention (${K.plan.review.attention.length})`,buttonClassName:"px-0 py-1 text-sm",panelClassName:"pt-1",children:(0,S.jsx)("ul",{className:"list-inside list-disc text-sm text-modiff-subtle-text",children:K.plan.review.attention.map((e,l)=>(0,S.jsx)("li",{children:e},l))})}):null,(0,S.jsxs)("div",{className:"flex flex-wrap justify-end gap-2",children:[(0,S.jsx)(b,{onClick:()=>z(null),children:"Keep current model"}),(0,S.jsx)(b,{tone:"primary",onClick:()=>{try{ee(K)}catch(e){J(o(e,"Could not apply the model change."))}},children:"Apply model change"})]})]}):void 0;return(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(b,{"aria-label":"Choose model",className:"w-full",align:"left",disabled:t,onClick:()=>$(!0),title:l,children:(0,S.jsx)("span",{className:"min-w-0 truncate",children:X?O(X):l||"Choose model"})}),X&&T.some(e=>e.pipelineClass===I.operation.pipelineClass&&e.task===I.operation.task&&!x(e))?(0,S.jsx)(b,{disabled:t||V,onClick:()=>{$(!0),le(X,!0)},title:"Reset unconnected prompts and generation controls; keep model, memory settings, media and custom nodes. Undo is available.",children:"Restore model defaults"}):null,W?(0,S.jsx)(m,{open:!0,onClose:Z,title:"Choose model for Load Models",panelClassName:"max-w-3xl",footer:te,children:(0,S.jsxs)("div",{className:"space-y-3",children:[(0,S.jsxs)("p",{className:"text-sm text-modiff-subtle-text",children:[h(I.operation.task??""),". Selecting a model updates its connected nodes. Nothing is downloaded or executed."]}),(0,S.jsx)(g,{"aria-label":"Search compatible models",placeholder:"Search by model or repository",value:_,onChange:e=>B(e.target.value),onClear:()=>B("")}),(0,S.jsxs)("div",{className:"flex gap-2",children:[(0,S.jsx)(b,{tone:q?"secondary":"primary",onClick:()=>F(!1),children:"All supported"}),(0,S.jsx)(b,{tone:q?"primary":"secondary",onClick:()=>F(!0),children:"Downloaded"})]}),G?(0,S.jsx)("p",{role:"alert",children:G}):null,V?(0,S.jsx)("p",{role:"status",children:"Updating model and node contracts…"}):null,(0,S.jsxs)("div",{className:"grid max-h-[min(24rem,50dvh)] gap-2 overflow-y-auto overscroll-contain pr-1","aria-label":"Compatible models",children:[f(Y,X?.id).map(({id:e,primary:l,routes:t})=>(0,S.jsxs)("div",{className:"overflow-hidden rounded-modiff-panel border border-modiff-border bg-modiff-panel",children:[(0,S.jsxs)(b,{"aria-pressed":K?.choice.id===l.id||!K&&X?.id===l.id,className:"h-auto w-full rounded-none p-3",align:"left",disabled:V,onClick:()=>{le(l)},tone:K?.choice.id===l.id?"primary":"ghost",children:[(0,S.jsxs)("span",{className:"min-w-0 flex-1",children:[(0,S.jsx)("span",{className:"block font-semibold",children:O(l)}),(0,S.jsx)("span",{className:"block break-all text-xs text-modiff-subtle-text",children:l.repo})]}),(0,S.jsxs)("span",{className:"ml-auto flex shrink-0 flex-col items-end gap-1 text-xs text-modiff-subtle-text",children:[l.cache?.runnable?(0,S.jsx)("span",{className:"rounded-modiff-compact bg-modiff-selected-surface px-2 py-1 text-hf-yellow",children:"Downloaded"}):null,(0,S.jsx)("span",{children:"stages"===l.support.decomposition?"Editable nodes":"Whole pipeline"}),"blocked"===l.support.dependencies?(0,S.jsx)("span",{children:"Runtime setup required"}):null]})]}),t.length>1?(0,S.jsx)(p,{label:(0,S.jsxs)("span",{className:"flex items-center justify-between gap-2",children:[(0,S.jsx)("span",{children:"Other implementations"}),(0,S.jsx)("span",{className:"text-modiff-subtle-text",children:t.length-1})]}),className:"border-t border-modiff-border",buttonClassName:"rounded-none px-3 py-2 text-xs",panelClassName:"grid gap-1 border-t border-modiff-border bg-modiff-bg/40 p-2",children:t.slice(1).map(e=>(0,S.jsxs)(b,{"aria-pressed":K?.choice.id===e.id,align:"left",className:"h-auto w-full justify-between px-2 py-2",disabled:V,onClick:()=>{le(e)},tone:K?.choice.id===e.id?"primary":"ghost",children:[(0,S.jsx)("span",{children:"stages"===e.support.decomposition?"Editable nodes":"Whole pipeline"}),(0,S.jsxs)("span",{className:"min-w-0 break-words text-xs text-modiff-subtle-text",children:[O(e)," · ",e.pipeline," · ",e.profileId]})]},e.id))}):null]},e)),Y.length?null:(0,S.jsx)("p",{role:"status",children:"No compatible models match these filters."})]})]})}):null]})}export{I as default}; \ No newline at end of file diff --git a/web/assets/OperationOwnerControls.js b/web/assets/OperationOwnerControls.js new file mode 100644 index 00000000..6f266320 --- /dev/null +++ b/web/assets/OperationOwnerControls.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as a,dr as l}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{As as o,Ci as s,Go as n,Jo as t,Si as i,Ts as r,bn as d,dn as p,es as c,fn as u,rc as k}from"./studio-templates.js?v=cc046c26d6fe7aa4";import"./operationAuthoring.js?v=cc046c26d6fe7aa4";import{t as j}from"./OperationGraphControls.js?v=cc046c26d6fe7aa4";var b=e(l(),1),f=a(),h=(0,b.lazy)(()=>s(()=>import(`./VisualStageControls.js?v=cc046c26d6fe7aa4`),[])),m=(0,b.lazy)(()=>s(()=>import(`./OperationModelPicker.js?v=cc046c26d6fe7aa4`),[])),x=(0,b.lazy)(()=>s(()=>import(`./LegacyOperationOwnerControls.js?v=cc046c26d6fe7aa4`),[])),I=(0,b.lazy)(()=>s(()=>import(`./BlockModelTaskControls.js?v=cc046c26d6fe7aa4`),[]));function g({node:e}){if(r(e.data.blockInstanceV2))return null;if(n(e))return(0,f.jsx)(b.Suspense,{fallback:null,children:(0,f.jsx)(h,{node:e})});if(e.data.blockInstanceV2)return(0,f.jsx)(C,{node:e});if(e.data.userBlockSnapshot||e.data.userBlockId)return(0,f.jsx)(p,{label:"Change model / task",panelClassName:"grid gap-2 py-2",children:(0,f.jsx)(b.Suspense,{fallback:(0,f.jsx)("p",{role:"status",children:"Loading model/task controls…"}),children:(0,f.jsx)(x,{nodeId:e.id})})});let a=t(e);return a&&a.operation.pipelineClass&&a.operation.task&&k(a.operation)&&(!e.parentId||e.data.blockProjectionOwnerId)?(0,f.jsxs)(f.Fragment,{children:[(0,f.jsx)(b.Suspense,{fallback:null,children:(0,f.jsx)(h,{node:e})}),(0,f.jsx)(v,{ownerId:e.data.blockProjectionNodeId??e.id,blockId:e.data.blockProjectionOwnerId,pipelineClass:a.operation.pipelineClass,currentTask:a.operation.task},`${e.id}:${a.operation.pipelineClass}:${a.operation.task}`)]}):null}function C({node:e,ownerBlockId:a=e.id,inline:l=!1}){let o=c(e.data.blockInstanceV2).nodes.filter(e=>k(t(e)?.operation)),[s,n]=(0,b.useState)(""),i=o.find(e=>e.data.blockProjectionNodeId===s)??o[0],r=i?t(i):null;return i&&r?.operation.pipelineClass&&r.operation.task?(0,f.jsxs)("div",{className:"grid gap-2",children:[o.length>1?(0,f.jsx)(u,{label:"Loader to change",children:(0,f.jsx)(d,{"aria-label":"Block loader to change",value:i.data.blockProjectionNodeId,onValueChange:n,options:o.map(e=>({value:e.data.blockProjectionNodeId,label:`${e.data.label} · ${e.data.operationAuthoring.operation.pipelineClass} · ${e.data.blockProjectionNodeId}`}))})}):null,(0,f.jsx)(b.Suspense,{fallback:(0,f.jsx)("p",{role:"status",children:"Loading model choices…"}),children:(0,f.jsx)(m,{node:{...i,data:{...i.data,blockProjectionOwnerId:a}},value:(()=>{let e=i.data.params.repo_id?.value??i.data.params.model_id?.value;return"string"==typeof e?e:e&&"object"==typeof e&&"value"in e?String(e.value??""):""})()})}),(0,f.jsx)(v,{ownerId:i.data.blockProjectionNodeId,blockId:a,inline:l,pipelineClass:r.operation.pipelineClass,currentTask:r.operation.task},`${i.id}:${r.operation.pipelineClass}:${r.operation.task}`),(0,f.jsx)(b.Suspense,{fallback:null,children:(0,f.jsx)(h,{node:{...i,data:{...i.data,blockProjectionOwnerId:a}}})})]}):e.data.blockInstanceV2.effectiveGraph.nodes.some(e=>e.modularDiffusers)||"diffusers.definition-switch:v1"===e.data.blockInstanceV2.routeSelection?.routeSetId?(0,f.jsx)(b.Suspense,{fallback:(0,f.jsx)("p",{role:"status",children:"Loading model/task controls…"}),children:(0,f.jsx)(I,{node:e})}):null}function v({ownerId:e,blockId:a,pipelineClass:l,currentTask:s,inline:n=!1}){let t=i(e=>e.pipelineSupport),r=i(e=>e.studioModelCapabilities),c=i(e=>e.workflowModelDescriptors),[k,h]=(0,b.useState)(l),[m,x]=(0,b.useState)(s),[I,g]=(0,b.useState)(""),C=t.filter(e=>e.tasks.some(e=>e.operationIds.length)),v=C.find(e=>e.pipelineClass===k),_=v?.tasks.filter(e=>e.operationIds.length)??[],w=_.find(e=>e.task===m),S=o(v&&w?[{...v,tasks:[w]}]:[],r,[],[],null,c).flatMap(e=>e.profileId?[{value:e.profileId,label:`${e.label} · ${e.repo}`}]:[]);return(0,f.jsxs)(p,{label:"Change model / task",collapsible:!n,panelClassName:"grid gap-2 py-2",children:[(0,f.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Review changes to this node and its connected nodes. Model files and resources are checked when you run."}),(0,f.jsx)(u,{label:"Pipeline",children:(0,f.jsx)(d,{"aria-label":"Replacement pipeline",value:k,onValueChange:e=>{let a=C.find(a=>a.pipelineClass===e)?.tasks.filter(e=>e.operationIds.length);h(e),g(""),x(a?.find(e=>e.task===m)?.task??a?.[0]?.task??"")},options:C.map(e=>({value:e.pipelineClass,label:e.pipelineClass}))})}),(0,f.jsx)(u,{label:"Task",children:(0,f.jsx)(d,{"aria-label":"Replacement task",value:w?.task??"",onValueChange:e=>{x(e),g("")},options:_.map(e=>({value:e.task,label:e.task.replace(/_/gu," ")}))})}),S.length?(0,f.jsx)(u,{label:"Model",children:(0,f.jsx)(d,{"aria-label":"Replacement model",value:I,onValueChange:g,options:[{value:"",label:"Keep compatible model selection"},...S]})}):null,w?(0,f.jsx)(j,{pipeline:k,task:w.task,ownerId:e,blockId:a,executionProfileId:S.some(e=>e.value===I)?I:void 0}):(0,f.jsx)("p",{role:"status",className:"text-xs text-modiff-subtle-text",children:"Select an available pipeline and task to preview a change."})]})}export{C as BlockOwnerChoices,g as default}; \ No newline at end of file diff --git a/web/assets/PreviewHistoryStripContent.js b/web/assets/PreviewHistoryStripContent.js index 6ea0a785..ce14c5ca 100644 --- a/web/assets/PreviewHistoryStripContent.js +++ b/web/assets/PreviewHistoryStripContent.js @@ -1 +1 @@ -import{Ht as e,bn as t,fn as i,gn as a,pn as l,rn as s,tt as o,ur as d,xt as r}from"./graph-vendor.js?v=5bcd315505f5670a";import{$i as n,Sn as m,Ua as f,dt as c,ea as u,ft as x,gt as p,mt as b,ta as h,ui as v,ut as g,vt as y,yt as w}from"./studio-templates.js?v=5bcd315505f5670a";var j=d();function N(e){return e.mediaItems?.[0]?.url??e.url}function I(e){return e.mediaItems?.[0]?.displayType??e.displayType??"unknown"}function $(e){let t=I(e);if("image"===t)return n(e)??"Image";if("video"===t){let t=u(e,"numFrames");return void 0===t?"Video":`${t} frames`}return"audio"===t?"Audio":"text"===t?"Text":"Output"}function k(e){return"string"==typeof e.value?e.value:JSON.stringify(e.value,null,2)}async function T(e){try{await navigator.clipboard.writeText(e),f("Output URL copied",{variant:"success",autoHideDuration:1600})}catch{f("Could not copy output URL",{variant:"error",autoHideDuration:2400})}}function R({history:d,onSelectImage:n,selectedUrl:f,audioDownloadSampleRate:u}){let R=v(e=>e.restoreWorkflowFromOutput),O=h(e=>e.setLightboxOpener),L=h(e=>e.setMediaViewerOpener),P=h(e=>e.setMediaExportOpener),U=h(e=>e.setRightPanelOpen),z=h(e=>e.setRightPanelTab);return(0,j.jsxs)("div",{className:"nodrag mb-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg/80 p-2",children:[(0,j.jsxs)("div",{className:"mb-2 flex items-center justify-between gap-2 text-xs text-modiff-subtle-text",children:[(0,j.jsxs)("span",{className:"inline-flex items-center gap-1 font-semibold text-modiff-text",children:[(0,j.jsx)(e,{size:13}),"Previous"]}),(0,j.jsx)(c,{type:"button",className:"min-h-7 px-1 text-modiff-subtle-text transition hover:text-hf-yellow",onClick:()=>{U(!0),z("gallery")},children:"Gallery"})]}),(0,j.jsx)("div",{className:"flex gap-2 overflow-x-auto pb-1",children:d.map(e=>{let d=I(e),h=N(e);return(0,j.jsxs)("div",{className:"group/history relative min-w-[96px] max-w-[120px] overflow-hidden rounded-modiff-compact border border-modiff-border bg-modiff-panel",children:[(0,j.jsx)(c,{type:"button",className:m("block h-16 w-full overflow-hidden border-2 border-transparent text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-modiff-focus",f===h&&"border-hf-yellow"),"aria-pressed":"image"===d&&n?f===h:void 0,onClick:()=>{"image"===d&&n&&n(h),(e=>{let t=I(e);if("image"===t||"image_collection"===e.displayType){let t=g({value:e.mediaItems?.map(e=>e.url)??[e.url],artifacts:e.mediaItems?.map(t=>({url:t.url,nodeId:e.nodeId,fieldKey:e.fieldKey,index:t.index,mimeType:t.contentType,filename:t.backendPath?.split("/").pop(),width:t.width,height:t.height})),dataType:"url",mimeType:"image/webp",nodeId:e.nodeId,fieldKey:e.fieldKey});return void O({images:t.map(e=>e.url),artifacts:t,currentIndex:0,dataType:"url",mimeType:t[0]?.mimeType??"image/webp"})}if("text"!==t&&"text"!==e.displayType){if("video"===t||"audio"===t){let i=e.mediaItems&&e.mediaItems.length>0?e.mediaItems:[{index:0,url:N(e),displayType:t}];L({title:"video"===t?"Previous video render":"Previous audio render",currentIndex:0,items:i.filter(e=>e.url).map((a,l)=>({id:`${e.id}:${a.index??l}`,kind:t,label:a.label||`${$(e)} ${i.length>1?l+1:""}`.trim(),url:a.url,downloadName:a.backendPath?.split("/").pop()??`modiff-${e.nodeId}-${e.fieldKey}-${l+1}.${"video"===t?"mp4":"wav"}`}))})}}else L({title:"Previous text render",currentIndex:0,items:[{id:e.id,kind:"text",label:$(e),text:k(e),downloadName:`modiff-${e.nodeId}-${e.fieldKey}.txt`}]})})(e)},title:`${$(e)} from ${new Date(e.createdAt).toLocaleTimeString()}`,children:"image"===d?(0,j.jsx)("img",{src:h,alt:$(e),className:"h-full w-full object-cover modiff-generated-image"}):"video"===d?(0,j.jsx)("video",{src:h,muted:!0,playsInline:!0,preload:"metadata","aria-label":$(e),className:"pointer-events-none h-full w-full object-cover"}):"audio"===d?(0,j.jsxs)("span",{className:"flex h-full w-full flex-col items-center justify-center gap-1 bg-modiff-surface px-2 text-center text-xs font-semibold text-modiff-subtle-text",children:[(0,j.jsx)(r,{size:20,className:"text-hf-yellow"}),$(e)]}):"text"===d?(0,j.jsxs)("span",{className:"flex h-full w-full items-start gap-1 overflow-hidden bg-modiff-surface p-2 text-left text-xs text-modiff-subtle-text",children:[(0,j.jsx)(s,{size:14,className:"mt-0.5 shrink-0 text-hf-yellow"}),(0,j.jsx)("span",{className:"line-clamp-3",children:k(e)})]}):(0,j.jsx)("span",{className:"flex h-full w-full items-center justify-center bg-modiff-surface px-2 text-center text-xs font-semibold text-modiff-subtle-text",children:$(e)})}),(0,j.jsxs)("div",{className:"text-modiff-label flex items-center gap-1 border-t border-modiff-border px-1 py-1 text-modiff-subtle-text",children:[(0,j.jsx)(t,{size:11}),(0,j.jsx)("span",{className:"min-w-0 flex-1 truncate",children:new Date(e.createdAt).toLocaleTimeString()})]}),(0,j.jsx)("div",{className:"absolute right-1 top-1",children:(0,j.jsxs)(p,{children:[(0,j.jsx)(w,{children:(0,j.jsx)(x,{label:"Output actions",className:"border border-modiff-border bg-modiff-panel/90 hover:bg-modiff-surface hover:text-hf-yellow",children:(0,j.jsx)(i,{size:14})})}),(0,j.jsxs)(y,{layer:"graph",anchor:"bottom end",className:"min-w-44",children:[(0,j.jsx)(b,{icon:(0,j.jsx)(l,{size:14}),onClick:()=>{"text"!==d?("video"===d||"audio"===d||"image"===d)&&P({source:h,kind:d,filename:e.mediaItems?.[0]?.backendPath?.split("/").pop()??`modiff-${e.nodeId}-${e.fieldKey}-${e.createdAt}.${"audio"===d?"wav":"video"===d?"mp4":"webp"}`,title:`Download ${d}`,defaultFormat:"audio"===d?"wav":"video"===d?"mp4":"png",defaultSampleRate:"audio"===d?u:void 0}):function(e){let t=URL.createObjectURL(new Blob([k(e)],{type:"text/plain;charset=utf-8"})),i=document.createElement("a");i.href=t,i.download=`modiff-${e.nodeId}-${e.fieldKey}.txt`,document.body.append(i),i.click(),i.remove(),window.setTimeout(()=>URL.revokeObjectURL(t),1e3)}(e)},disabled:"unknown"===d,children:"Download…"}),(0,j.jsx)(b,{icon:(0,j.jsx)(a,{size:14}),onClick:()=>{T(h)},children:"Copy URL"}),(0,j.jsx)(b,{icon:(0,j.jsx)(o,{size:14}),onClick:()=>R(e),children:"Restore settings"})]})]})})]},e.id)})})]})}export{R as default}; \ No newline at end of file +import{$ as e,Bt as t,_n as i,cr as a,en as l,ln as o,pn as s,un as d,yt as r}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{At as n,Et as m,Mo as f,Mt as c,Nt as u,Ot as x,Sa as p,Tt as b,ji as h,wt as v,xa as y,ya as g}from"./studio-templates.js?v=cc046c26d6fe7aa4";var w=a();function j(e){return e.mediaItems?.[0]?.url??e.url}function N(e){return e.mediaItems?.[0]?.displayType??e.displayType??"unknown"}function I(e){let t=N(e);if("image"===t)return g(e)??"Image";if("video"===t){let t=y(e,"numFrames");return void 0===t?"Video":`${t} frames`}return"audio"===t?"Audio":"text"===t?"Text":"Output"}function $(e){return"string"==typeof e.value?e.value:JSON.stringify(e.value,null,2)}async function T(e){try{await navigator.clipboard.writeText(e),f("Output URL copied",{variant:"success",autoHideDuration:1600})}catch{f("Could not copy output URL",{variant:"error",autoHideDuration:2400})}}function k({history:a,audioDownloadSampleRate:f}){let y=h(e=>e.restoreWorkflowFromOutput),g=p(e=>e.setLightboxOpener),k=p(e=>e.setMediaViewerOpener),O=p(e=>e.setMediaExportOpener),R=p(e=>e.setRightPanelOpen),L=p(e=>e.setRightPanelTab);return(0,w.jsxs)("div",{className:"nodrag mb-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg/80 p-2",children:[(0,w.jsxs)("div",{className:"mb-2 flex items-center justify-between gap-2 text-xs text-modiff-subtle-text",children:[(0,w.jsxs)("span",{className:"inline-flex items-center gap-1 font-semibold text-modiff-text",children:[(0,w.jsx)(t,{size:13}),"Previous"]}),(0,w.jsx)(b,{type:"button",className:"min-h-7 px-1 text-modiff-subtle-text transition hover:text-hf-yellow",onClick:()=>{R(!0),L("gallery")},children:"Gallery"})]}),(0,w.jsx)("div",{className:"flex gap-2 overflow-x-auto pb-1",children:a.map(t=>{let a=N(t),p=j(t);return(0,w.jsxs)("div",{className:"group/history relative min-w-[96px] max-w-[120px] overflow-hidden rounded-modiff-compact border border-modiff-border bg-modiff-panel",children:[(0,w.jsx)(b,{type:"button",className:"block h-16 w-full overflow-hidden border-2 border-transparent text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-modiff-focus",onClick:()=>(e=>{let t=N(e);if("image"===t||"image_collection"===e.displayType){let t=v({value:e.mediaItems?.map(e=>e.url)??[e.url],artifacts:e.mediaItems?.map(t=>({url:t.url,nodeId:e.nodeId,fieldKey:e.fieldKey,index:t.index,mimeType:t.contentType,filename:t.backendPath?.split("/").pop(),width:t.width,height:t.height})),dataType:"url",mimeType:"image/webp",nodeId:e.nodeId,fieldKey:e.fieldKey});return void g({images:t.map(e=>e.url),artifacts:t,currentIndex:0,dataType:"url",mimeType:t[0]?.mimeType??"image/webp"})}if("text"!==t&&"text"!==e.displayType){if("video"===t||"audio"===t){let i=e.mediaItems&&e.mediaItems.length>0?e.mediaItems:[{index:0,url:j(e),displayType:t}];return void k({title:"video"===t?"Previous video render":"Previous audio render",currentIndex:0,items:i.filter(e=>e.url).map((a,l)=>({id:`${e.id}:${a.index??l}`,kind:t,label:a.label||`${I(e)} ${i.length>1?l+1:""}`.trim(),url:a.url,downloadName:a.backendPath?.split("/").pop()??`modiff-${e.nodeId}-${e.fieldKey}-${l+1}.${"video"===t?"mp4":"wav"}`}))})}}else k({title:"Previous text render",currentIndex:0,items:[{id:e.id,kind:"text",label:I(e),text:$(e),downloadName:`modiff-${e.nodeId}-${e.fieldKey}.txt`}]})})(t),title:`${I(t)} from ${new Date(t.createdAt).toLocaleTimeString()}`,children:"image"===a?(0,w.jsx)("img",{src:p,alt:I(t),className:"h-full w-full object-cover modiff-generated-image"}):"video"===a?(0,w.jsx)("video",{src:p,muted:!0,playsInline:!0,preload:"metadata","aria-label":I(t),className:"pointer-events-none h-full w-full object-cover"}):"audio"===a?(0,w.jsxs)("span",{className:"flex h-full w-full flex-col items-center justify-center gap-1 bg-modiff-surface px-2 text-center text-xs font-semibold text-modiff-subtle-text",children:[(0,w.jsx)(r,{size:20,className:"text-hf-yellow"}),I(t)]}):"text"===a?(0,w.jsxs)("span",{className:"flex h-full w-full items-start gap-1 overflow-hidden bg-modiff-surface p-2 text-left text-xs text-modiff-subtle-text",children:[(0,w.jsx)(l,{size:14,className:"mt-0.5 shrink-0 text-hf-yellow"}),(0,w.jsx)("span",{className:"line-clamp-3",children:$(t)})]}):(0,w.jsx)("span",{className:"flex h-full w-full items-center justify-center bg-modiff-surface px-2 text-center text-xs font-semibold text-modiff-subtle-text",children:I(t)})}),(0,w.jsxs)("div",{className:"text-modiff-label flex items-center gap-1 border-t border-modiff-border px-1 py-1 text-modiff-subtle-text",children:[(0,w.jsx)(i,{size:11}),(0,w.jsx)("span",{className:"min-w-0 flex-1 truncate",children:new Date(t.createdAt).toLocaleTimeString()})]}),(0,w.jsx)("div",{className:"absolute right-1 top-1",children:(0,w.jsxs)(n,{children:[(0,w.jsx)(u,{children:(0,w.jsx)(m,{label:"Output actions",className:"border border-modiff-border bg-modiff-panel/90 hover:bg-modiff-surface hover:text-hf-yellow",children:(0,w.jsx)(o,{size:14})})}),(0,w.jsxs)(c,{layer:"graph",anchor:"bottom end",className:"min-w-44",children:[(0,w.jsx)(x,{icon:(0,w.jsx)(d,{size:14}),onClick:()=>{"text"!==a?("video"===a||"audio"===a||"image"===a)&&O({source:p,kind:a,filename:t.mediaItems?.[0]?.backendPath?.split("/").pop()??`modiff-${t.nodeId}-${t.fieldKey}-${t.createdAt}.${"audio"===a?"wav":"video"===a?"mp4":"webp"}`,title:`Download ${a}`,defaultFormat:"audio"===a?"wav":"video"===a?"mp4":"png",defaultSampleRate:"audio"===a?f:void 0}):function(e){let t=URL.createObjectURL(new Blob([$(e)],{type:"text/plain;charset=utf-8"})),i=document.createElement("a");i.href=t,i.download=`modiff-${e.nodeId}-${e.fieldKey}.txt`,document.body.append(i),i.click(),i.remove(),window.setTimeout(()=>URL.revokeObjectURL(t),1e3)}(t)},disabled:"unknown"===a,children:"Download…"}),(0,w.jsx)(x,{icon:(0,w.jsx)(s,{size:14}),onClick:()=>{T(p)},children:"Copy URL"}),(0,w.jsx)(x,{icon:(0,w.jsx)(e,{size:14}),onClick:()=>y(t),children:"Restore settings"})]})]})})]},t.id)})})]})}export{k as default}; \ No newline at end of file diff --git a/web/assets/RunIssuesDialog.js b/web/assets/RunIssuesDialog.js index dcdf73c2..63b89407 100644 --- a/web/assets/RunIssuesDialog.js +++ b/web/assets/RunIssuesDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{$t as t,Jt as n,X as r,at as i,pn as a,pr as o,q as s,un as l,ur as d}from"./graph-vendor.js?v=5bcd315505f5670a";import{Ea as c,Ga as u,Ua as m,Ut as h,Vn as f,Vt as p,Wt as x,Zi as g,ai as w,br as b,gr as y,hr as j,ja as k,jn as C,kr as v,mn as I,ni as _,qi as R,ri as M,si as T,ta as D,ui as S,wt as z,xi as A,ya as N}from"./studio-templates.js?v=5bcd315505f5670a";var q=e(o(),1),H=d();function P(e){return"success"===e?"success":"warning"===e?"warning":"info"===e?"info":"error"}function O(){let e=y(e=>e.issues),o=y(e=>e.issueDialogOpen),d=y(e=>e.closeIssues),p=y(e=>e.failure),x=y(e=>e.failureDialogOpen),O=y(e=>e.closeFailure),Z=_(e=>e.installHfModel),$=_(e=>e.refreshModelIndexes),B=C(e=>e.sid),J=D(e=>e.setRightPanelOpen),K=D(e=>e.setRightPanelTab),L=D(e=>e.setStudioViewMode),X=D(e=>e.setWorkflowFocusRequest),Q=D(e=>e.setModelManagerOpener),Y=S(e=>e.form),ee=S(e=>e.updateForm),te=S(e=>e.applyAutoResourcePlan),ne=S(e=>e.detachManagedGraph),re=S(e=>e.autoResourcePlan),ie=S(e=>e.activeWorkflowTabId),ae=S(e=>e.currentRunContext),oe=S(e=>e.workflowCanvasHydrated),se=S(e=>e.workflowTabs),le=S(e=>{let t=p?.taskId?.trim();if(t)return e.runContextsByTaskId[t]??null;let n=p?.clientRunId?.trim();return n?e.runContextsByClientRunId[n]??null:null}),[de,ce]=(0,q.useState)("idle"),[ue,me]=(0,q.useState)(null),he="running"===de,fe=e.some(e=>"cleanup_gpu"===e.action),pe=re?.nextCandidate,xe="backend_worker_exited"===p?.errorCode,ge=j(p,le,{activeWorkflowTabId:ie,currentRunContext:ae,workflowCanvasHydrated:oe,workflowTabs:se}),we=()=>{J(!0),K("setup"),d(),O()},be=()=>{J(!0),K("studio"),d(),O()},ye=(e,t)=>{if(J(!0),K("studio"),e){L("expert");let t=v.getState();t.nodes.some(t=>t.id===e)&&t.onNodesChange(t.nodes.map(t=>({id:t.id,type:"select",selected:t.id===e}))),ie&&X({workflowTabId:ie,nodeId:e,requestId:Date.now(),requestedAt:Date.now()})}else ie&&(X({workflowTabId:ie,nodeId:null,requestId:Date.now(),requestedAt:Date.now()}),m("Graph centered. Readiness details remain visible in Studio.",{variant:"info",autoHideDuration:2600}));"issues"===t?d():O()},je=()=>{J(!0),K("setup"),Q({nodeId:null,fieldKey:null}),d(),O()},ke=()=>{let e=k(Y),t=g(Y);ee(t),b({...Y,...t}),J(!0),K("studio"),d(),O();let n=N[t.offloadMode];m(`Applied ${e.label} low-VRAM defaults with ${n}.`,{variant:"success",autoHideDuration:4200})},Ce=()=>{let e=c("ZImageModularPipeline"),t={...e,prompt:Y.prompt,negativePrompt:Y.negativePrompt,width:Math.min(Y.width,e.width),height:Math.min(Y.height,e.height),aspectRatio:Y.width<=e.width&&Y.height<=e.height?Y.aspectRatio:e.aspectRatio};ee(t),b(t),J(!0),K("studio"),d(),O(),m("Switched to Z-Image Turbo with safer local settings.",{variant:"success",autoHideDuration:3600})},ve=(0,H.jsx)(h,{tone:"primary",icon:he?(0,H.jsx)(z,{size:15}):(0,H.jsx)(l,{size:15}),onClick:()=>{(async()=>{if(!he){ce("running"),me("Releasing cached models, nodes, and accelerator memory...");try{let e=await u(),t=W(e);if(U(e))throw Error(t??"Accelerator cleanup failed.");let n=t??"Accelerator cache released. Cached model state was dropped, so rerun the workflow to rebuild outputs; this action does not intentionally stop the backend.";ce("success"),me(n),m(n,{variant:"success",autoHideDuration:5200})}catch(e){let t=function(e){let t=e instanceof Error?e.message:String(e);return t.includes("Failed to fetch")?"MoDiff backend did not answer the cleanup request. This button does not intentionally stop the backend; restart the backend if it is no longer running, then rerun the workflow.":t||"Accelerator cleanup failed."}(e);ce("error"),me(t),m(t,{variant:"error",autoHideDuration:8e3})}}})()},disabled:he,children:he?"Releasing...":"Release accelerator cache"});return(0,H.jsxs)(H.Fragment,{children:[(0,H.jsx)(G,{open:o,onClose:d,title:"Run blocked",testId:"run-issues-dialog",footer:(0,H.jsxs)(E,{cleanupMessage:ue,cleanupState:de,children:[fe?ve:null,(0,H.jsx)(h,{onClick:d,children:"Close"})]}),children:e.map(e=>(0,H.jsxs)(I,{tone:P(e.severity),title:e.message,meta:e.blocking?"Required before this run can start.":"This run can continue, but the setting is risky.",testId:`run-issue-${e.id}`,children:[(0,H.jsx)("div",{className:"grid min-w-0 gap-2",children:e.details&&(0,H.jsx)("p",{className:"whitespace-pre-wrap break-words",children:e.details})}),(0,H.jsxs)("div",{className:"mt-3 flex flex-wrap gap-2",children:["install_model"===e.action&&e.repoId&&(0,H.jsx)(h,{tone:"primary",icon:(0,H.jsx)(a,{size:15}),onClick:()=>{(async e=>{if(e.repoId)try{await Z(e.repoId,B,e.installOptions)}catch(e){console.error(e)}})(e)},children:"Install"}),"refresh_cache"===e.action&&(0,H.jsx)(h,{icon:(0,H.jsx)(i,{size:15}),onClick:()=>{(async()=>{await $(!0),m("Model cache refreshed",{variant:"success",autoHideDuration:2200})})()},children:"Refresh cache"}),"select_image"===e.action&&(0,H.jsx)(h,{icon:(0,H.jsx)(t,{size:15}),onClick:be,children:"Open Studio inputs"}),"inspect_node"===e.action&&(0,H.jsx)(h,{icon:(0,H.jsx)(r,{size:15}),onClick:()=>ye(e.nodeId,"issues"),children:e.nodeId?"Inspect node":"Inspect graph"}),"detach_graph"===e.action&&(0,H.jsx)(h,{icon:(0,H.jsx)(l,{size:15}),onClick:()=>{ne(),d()},children:"Detach invalid receipt"}),"open_model_manager"===e.action&&(0,H.jsx)(h,{tone:"primary",icon:(0,H.jsx)(s,{size:15}),onClick:je,children:"Models"}),"apply_low_vram_preset"===e.action&&(0,H.jsx)(h,{tone:"primary",icon:(0,H.jsx)(n,{size:15}),onClick:ke,children:"Use low-VRAM preset"}),"switch_to_z_image"===e.action&&(0,H.jsx)(h,{tone:"primary",icon:(0,H.jsx)(n,{size:15}),onClick:Ce,children:"Use Z-Image Turbo"}),("open_setup"===e.action||"install_model"===e.action)&&(0,H.jsx)(h,{tone:"ghost",icon:(0,H.jsx)(s,{size:15}),onClick:we,children:"Setup"})]})]},e.id))}),(0,H.jsx)(G,{open:x,onClose:O,title:"Run failed",testId:"run-failure-dialog",footer:(0,H.jsxs)(E,{cleanupMessage:ue,cleanupState:de,children:[pe&&ge?(0,H.jsx)(h,{icon:(0,H.jsx)(i,{size:15}),onClick:()=>{(async()=>{if(!pe||!ge)return;if(!1===pe.installed||pe.repairRequired)return J(!0),K("compatibility"),void O();if(!B)return void m("Connect to the MoDiff backend before retrying.",{variant:"error"});let e=A(pe,Y),t=re.candidates?.find(e=>e.id!==pe.id&&e.id!==re.selectedCandidate?.id&&R(e.proof))??null,n={...re,selectedCandidate:pe,nextCandidate:t},r=w();try{te(n,e),M(r),await b(S.getState().form,r),await f({sid:B,studioContext:{},workflowContext:r}),O()}catch(e){if(T(e))return;m(String(e),{variant:"error",autoHideDuration:7e3})}})()},disabled:he,children:"Retry with next option"}):null,p?.oom?ve:null,(p?.oom||xe)&&ge?(0,H.jsx)(h,{icon:(0,H.jsx)(n,{size:15}),onClick:()=>{ge&&ke()},disabled:he,children:xe?"Apply lower-memory settings":"Low-VRAM preset"}):null,p?.nodeId&&ge?(0,H.jsx)(h,{icon:(0,H.jsx)(r,{size:15}),onClick:()=>ye(p.nodeId,"failure"),children:"Inspect node"}):null,(0,H.jsx)(h,{icon:(0,H.jsx)(s,{size:15}),onClick:we,disabled:he,children:"Setup"}),(0,H.jsx)(h,{onClick:O,children:"Close"})]}),children:p&&(0,H.jsx)("div",{children:(0,H.jsxs)(I,{tone:p.oom||xe?"warning":"error",title:V(p),children:[(0,H.jsx)("p",{className:"break-words",children:F(p)}),p.message.trim()&&p.message.trim()!==F(p)?(0,H.jsx)("p",{className:"mt-2 max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs text-modiff-text","data-testid":"run-failure-message",children:p.message}):null,p.recoveryHint&&(0,H.jsx)("p",{className:"mt-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs text-modiff-text",children:p.recoveryHint}),p.memorySummary&&(0,H.jsx)("p",{className:"mt-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs text-modiff-subtle-text","data-testid":"run-failure-memory-summary",children:p.memorySummary})]})})})]})}function V(e){return e.oom||"oom"===e.category?"The run ran out of available accelerator memory":"backend_worker_exited"===e.errorCode?"The native model worker stopped unexpectedly":"missing_model"===e.category?"A required model is missing":"missing_dependency"===e.category?"A backend dependency is missing":"backend_unavailable"===e.category?"The backend became unavailable":"The backend stopped this run"}function F(e){return e.oom||"oom"===e.category?"This graph needs more available memory than the current settings allow.":"missing_model"===e.category?"Install or select the model needed by this workflow.":"missing_dependency"===e.category?"Complete the required setup, then try the run again.":"backend_unavailable"===e.category?"Reconnect to MoDiff, then check the queue before retrying.":"backend_worker_exited"===e.errorCode?"The accelerator or native model runtime ended the backend process before Python could return a normal error. MoDiff recovered the failed run from its supervisor instead of leaving it active.":"The generation could not finish. Check the highlighted step and try again."}function U(e){return Z(e)&&!!e.error}function W(e){return Z(e)?$(e.message)??$(e.error):null}function Z(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function $(e){return"string"==typeof e&&e.trim()?e.trim():null}function E({children:e,cleanupMessage:t,cleanupState:n}){return(0,H.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-end",children:["running"===n||t?(0,H.jsxs)("div",{className:"min-w-0 flex-1 text-xs",children:[(0,H.jsx)("p",{className:"error"===n?"text-modiff-red":"success"===n?"text-modiff-green":"text-modiff-subtle-text",children:t}),"running"===n?(0,H.jsx)(p,{value:null,className:"mt-2 max-w-80 rounded-modiff-compact"}):null]}):null,(0,H.jsx)("div",{className:"flex flex-wrap justify-end gap-2",children:e})]})}function G({children:e,footer:t,onClose:n,open:r,testId:i,title:a}){return r?(0,H.jsx)(x,{open:r,onClose:n,title:a,footer:t,panelClassName:"max-w-xl",bodyClassName:"min-h-0",testId:i,children:e}):null}export{O as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Gt as t,K as n,Xt as r,Y as i,cr as a,dr as o,rt as s,sn as l,un as d}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$a as c,Ei as u,Hi as m,Ja as h,Jn as f,Mo as p,Nr as x,Oi as g,On as w,Or as b,Rt as y,Sa as j,Si as k,Va as C,Vr as v,_a as I,ha as _,ho as R,in as M,ir as T,ji as D,kr as S,rn as z,tn as N,wi as A}from"./studio-templates.js?v=cc046c26d6fe7aa4";var H=e(o(),1),q=a();function O(e){return"success"===e?"success":"warning"===e?"warning":"info"===e?"info":"error"}function P(){let e=S(e=>e.issues),a=S(e=>e.issueDialogOpen),o=S(e=>e.closeIssues),M=S(e=>e.failure),N=S(e=>e.failureDialogOpen),P=S(e=>e.closeFailure),G=k(e=>e.installHfModel),W=k(e=>e.refreshModelIndexes),J=f(e=>e.sid),K=j(e=>e.setRightPanelOpen),U=j(e=>e.setRightPanelTab),L=j(e=>e.setWorkflowFocusRequest),X=j(e=>e.setModelManagerOpener),Y=D(e=>e.form),Q=D(e=>e.updateForm),ee=D(e=>e.applyAutoResourcePlan),te=D(e=>e.detachManagedGraph),ne=D(e=>e.autoResourcePlan),re=D(e=>e.activeWorkflowTabId),ie=D(e=>e.currentRunContext),ae=D(e=>e.workflowCanvasHydrated),oe=D(e=>e.workflowTabs),se=D(e=>{let t=M?.taskId?.trim();if(t)return e.runContextsByTaskId[t]??null;let n=M?.clientRunId?.trim();return n?e.runContextsByClientRunId[n]??null:null}),[le,de]=(0,H.useState)("idle"),[ce,ue]=(0,H.useState)(null),me="running"===le,he=e.some(e=>"cleanup_gpu"===e.action),fe=ne?.nextCandidate,pe="backend_worker_exited"===M?.errorCode,xe=b(M,se,{activeWorkflowTabId:re,currentRunContext:ie,workflowCanvasHydrated:ae,workflowTabs:oe}),ge=()=>{K(!0),U("setup"),o(),P()},we=()=>{K(!0),U("studio"),o(),P()},be=(e,t)=>{if(K(!0),U("studio"),e){let t=v.getState();t.nodes.some(t=>t.id===e)&&t.onNodesChange(t.nodes.map(t=>({id:t.id,type:"select",selected:t.id===e}))),re&&L({workflowTabId:re,nodeId:e,requestId:Date.now(),requestedAt:Date.now()})}else re&&(L({workflowTabId:re,nodeId:null,requestId:Date.now(),requestedAt:Date.now()}),p("Graph centered. Readiness details remain visible in Studio.",{variant:"info",autoHideDuration:2600}));"issues"===t?o():P()},ye=()=>{K(!0),U("setup"),X({nodeId:null,fieldKey:null}),o(),P()},je=()=>{let e=c(Y),t=I(Y);Q(t),x({...Y,...t}),K(!0),U("studio"),o(),P();let n=C[t.offloadMode];p(`Applied ${e.label} low-VRAM defaults with ${n}.`,{variant:"success",autoHideDuration:4200})},ke=()=>{let e=h("ZImageModularPipeline"),t={...e,prompt:Y.prompt,negativePrompt:Y.negativePrompt,width:Math.min(Y.width,e.width),height:Math.min(Y.height,e.height),aspectRatio:Y.width<=e.width&&Y.height<=e.height?Y.aspectRatio:e.aspectRatio};Q(t),x(t),K(!0),U("studio"),o(),P(),p("Switched to Z-Image Turbo with safer local settings.",{variant:"success",autoHideDuration:3600})},Ce=(0,q.jsx)(z,{tone:"primary",icon:me?(0,q.jsx)(y,{size:15}):(0,q.jsx)(l,{size:15}),onClick:()=>{(async()=>{if(!me){de("running"),ue("Releasing cached models, nodes, and accelerator memory...");try{let e=await R(),t=E(e);if($(e))throw Error(t??"Accelerator cleanup failed.");let n=t??"Accelerator cache released. Cached model state was dropped, so rerun the workflow to rebuild outputs; this action does not intentionally stop the backend.";de("success"),ue(n),p(n,{variant:"success",autoHideDuration:5200})}catch(e){let t=function(e){let t=e instanceof Error?e.message:String(e);return t.includes("Failed to fetch")?"MoDiff backend did not answer the cleanup request. This button does not intentionally stop the backend; restart the backend if it is no longer running, then rerun the workflow.":t||"Accelerator cleanup failed."}(e);de("error"),ue(t),p(t,{variant:"error",autoHideDuration:8e3})}}})()},disabled:me,children:me?"Releasing...":"Release accelerator cache"});return(0,q.jsxs)(q.Fragment,{children:[(0,q.jsx)(B,{open:a,onClose:o,title:"Run blocked",testId:"run-issues-dialog",footer:(0,q.jsxs)(Z,{cleanupMessage:ce,cleanupState:le,children:[he?Ce:null,(0,q.jsx)(z,{onClick:o,children:"Close"})]}),children:e.map(e=>(0,q.jsxs)(w,{tone:O(e.severity),title:e.message,meta:e.blocking?"Required before this run can start.":"This run can continue, but the setting is risky.",testId:`run-issue-${e.id}`,children:[(0,q.jsx)("div",{className:"grid min-w-0 gap-2",children:e.details&&(0,q.jsx)("p",{className:"whitespace-pre-wrap break-words",children:e.details})}),(0,q.jsxs)("div",{className:"mt-3 flex flex-wrap gap-2",children:["install_model"===e.action&&e.repoId&&(0,q.jsx)(z,{tone:"primary",icon:(0,q.jsx)(d,{size:15}),onClick:()=>{(async e=>{if(e.repoId)try{await G(e.repoId,J,e.installOptions)}catch(e){console.error(e)}})(e)},children:"Install"}),"refresh_cache"===e.action&&(0,q.jsx)(z,{icon:(0,q.jsx)(s,{size:15}),onClick:()=>{(async()=>{await W(!0),p("Model cache refreshed",{variant:"success",autoHideDuration:2200})})()},children:"Refresh cache"}),"select_image"===e.action&&(0,q.jsx)(z,{icon:(0,q.jsx)(r,{size:15}),onClick:we,children:"Open Studio inputs"}),"inspect_node"===e.action&&(0,q.jsx)(z,{icon:(0,q.jsx)(i,{size:15}),onClick:()=>be(e.nodeId,"issues"),children:e.nodeId?"Inspect node":"Inspect graph"}),"detach_graph"===e.action&&(0,q.jsx)(z,{icon:(0,q.jsx)(l,{size:15}),onClick:()=>{te(),o()},children:"Detach invalid receipt"}),"open_model_manager"===e.action&&(0,q.jsx)(z,{tone:"primary",icon:(0,q.jsx)(n,{size:15}),onClick:ye,children:"Models"}),"apply_low_vram_preset"===e.action&&(0,q.jsx)(z,{tone:"primary",icon:(0,q.jsx)(t,{size:15}),onClick:je,children:"Use low-VRAM preset"}),"switch_to_z_image"===e.action&&(0,q.jsx)(z,{tone:"primary",icon:(0,q.jsx)(t,{size:15}),onClick:ke,children:"Use Z-Image Turbo"}),("open_setup"===e.action||"install_model"===e.action)&&(0,q.jsx)(z,{tone:"ghost",icon:(0,q.jsx)(n,{size:15}),onClick:ge,children:"Setup"})]})]},e.id))}),(0,q.jsx)(B,{open:N,onClose:P,title:"Run failed",testId:"run-failure-dialog",footer:(0,q.jsxs)(Z,{cleanupMessage:ce,cleanupState:le,children:[fe&&xe?(0,q.jsx)(z,{icon:(0,q.jsx)(s,{size:15}),onClick:()=>{(async()=>{if(!fe||!xe)return;if(!1===fe.installed||fe.repairRequired)return K(!0),U("compatibility"),void P();if(!J)return void p("Connect to the MoDiff backend before retrying.",{variant:"error"});let e=m(fe,Y),t=ne.candidates?.find(e=>e.id!==fe.id&&e.id!==ne.selectedCandidate?.id&&_(e.proof))??null,n={...ne,selectedCandidate:fe,nextCandidate:t},r=u();try{ee(n,e),A(r),await x(D.getState().form,r),await T({sid:J,studioContext:{},workflowContext:r}),P()}catch(e){if(g(e))return;p(String(e),{variant:"error",autoHideDuration:7e3})}})()},disabled:me,children:"Retry with next option"}):null,M?.oom?Ce:null,(M?.oom||pe)&&xe?(0,q.jsx)(z,{icon:(0,q.jsx)(t,{size:15}),onClick:()=>{xe&&je()},disabled:me,children:pe?"Apply lower-memory settings":"Low-VRAM preset"}):null,M?.nodeId&&xe?(0,q.jsx)(z,{icon:(0,q.jsx)(i,{size:15}),onClick:()=>be(M.nodeId,"failure"),children:"Inspect node"}):null,(0,q.jsx)(z,{icon:(0,q.jsx)(n,{size:15}),onClick:ge,disabled:me,children:"Setup"}),(0,q.jsx)(z,{onClick:P,children:"Close"})]}),children:M&&(0,q.jsx)("div",{children:(0,q.jsxs)(w,{tone:M.oom||pe?"warning":"error",title:F(M),children:[(0,q.jsx)("p",{className:"break-words",children:V(M)}),M.message.trim()&&M.message.trim()!==V(M)?(0,q.jsx)("p",{className:"mt-2 max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs text-modiff-text","data-testid":"run-failure-message",children:M.message}):null,M.recoveryHint&&(0,q.jsx)("p",{className:"mt-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs text-modiff-text",children:M.recoveryHint}),M.memorySummary&&(0,q.jsx)("p",{className:"mt-2 rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs text-modiff-subtle-text","data-testid":"run-failure-memory-summary",children:M.memorySummary})]})})})]})}function F(e){return e.oom||"oom"===e.category?"The run ran out of available accelerator memory":"backend_worker_exited"===e.errorCode?"The native model worker stopped unexpectedly":"missing_model"===e.category?"A required model is missing":"missing_dependency"===e.category?"A backend dependency is missing":"backend_unavailable"===e.category?"The backend became unavailable":"The backend stopped this run"}function V(e){return e.oom||"oom"===e.category?"This graph needs more available memory than the current settings allow.":"missing_model"===e.category?"Install or select the model needed by this workflow.":"missing_dependency"===e.category?"Complete the required setup, then try the run again.":"backend_unavailable"===e.category?"Reconnect to MoDiff, then check the queue before retrying.":"backend_worker_exited"===e.errorCode?"The accelerator or native model runtime ended the backend process before Python could return a normal error. MoDiff recovered the failed run from its supervisor instead of leaving it active.":"The generation could not finish. Check the highlighted step and try again."}function $(e){return G(e)&&!!e.error}function E(e){return G(e)?W(e.message)??W(e.error):null}function G(e){return!!e&&"object"==typeof e&&!Array.isArray(e)}function W(e){return"string"==typeof e&&e.trim()?e.trim():null}function Z({children:e,cleanupMessage:t,cleanupState:n}){return(0,q.jsxs)("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-end",children:["running"===n||t?(0,q.jsxs)("div",{className:"min-w-0 flex-1 text-xs",children:[(0,q.jsx)("p",{className:"error"===n?"text-modiff-red":"success"===n?"text-modiff-green":"text-modiff-subtle-text",children:t}),"running"===n?(0,q.jsx)(N,{value:null,className:"mt-2 max-w-80 rounded-modiff-compact"}):null]}):null,(0,q.jsx)("div",{className:"flex flex-wrap justify-end gap-2",children:e})]})}function B({children:e,footer:t,onClose:n,open:r,testId:i,title:a}){return r?(0,q.jsx)(M,{open:r,onClose:n,title:a,footer:t,panelClassName:"max-w-xl",bodyClassName:"min-h-0",testId:i,children:e}):null}export{P as default}; \ No newline at end of file diff --git a/web/assets/SelectionToolbar.js b/web/assets/SelectionToolbar.js index cb73dcae..a17f3c6a 100644 --- a/web/assets/SelectionToolbar.js +++ b/web/assets/SelectionToolbar.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{B as a,Cn as t,Ct as o,Dt as n,Et as i,I as l,Q as s,Rn as d,ar as r,dn as c,gn as u,it as b,lt as p,nn as g,ot as k,pr as h,st as x,tr as f,ur as j,z as m}from"./graph-vendor.js?v=5bcd315505f5670a";import{$n as C,On as v,Rr as y,Sn as w,Ua as z,Un as S,Vn as N,Xr as P,Ya as I,Z as D,ft as E,gt as R,jn as M,kn as B,kr as F,mt as H,ui as T,vt as W,yt as O}from"./studio-templates.js?v=5bcd315505f5670a";var V=e(h(),1),$=j(),A=(0,V.forwardRef)(function({active:e=!1,children:a,className:t,danger:o=!1,disabled:n=!1,label:i,onClick:l,type:s="button",...d},r){return(0,$.jsx)(E,{ref:r,type:s,label:i,size:"dense",active:e&&!n,tone:o&&!n?"danger":"ghost","aria-disabled":n,onClick:e=>{n?e.preventDefault():l?.(e)},className:w(n&&"cursor-not-allowed opacity-40",t),...d,children:a})});function U(){return(0,$.jsx)("span",{className:"mx-0.5 h-6 w-px bg-modiff-border","aria-hidden":"true"})}function K({canvasSuspended:e,createBlockDisabledReason:h,nodes:j,onCreateBlockFromSelection:E,selectionDragging:K}){let L=(0,V.useRef)(null),[Q,X]=(0,V.useState)(!1),[Y,Z]=(0,V.useState)(0),q=r(),{flowToScreenPosition:G,getNodesBounds:J}=f(),[_,ee]=(0,V.useState)(null),ae=M(e=>e.sid),te=M(e=>e.isConnected),oe=F(e=>e.removeNodes),ne=F(e=>e.duplicateNode),ie=F(e=>e.toggleNodeCollapsed),le=F(e=>e.toggleUserBlockExpanded),se=F(e=>e.toggleHuggingFaceClusterExpanded),de=F(e=>e.toggleBlockContainerExpandedV2),re=F(e=>e.setNodeUiState),ce=F(e=>e.resetNodeSize),ue=F(e=>e.loopNodes),be=F(e=>e.ungroupNodes),pe=F(e=>e.setNodeCached),ge=(0,V.useMemo)(()=>j.filter(e=>e.selected),[j]),ke=(0,V.useMemo)(()=>ge.filter(e=>"group"!==e.data.type&&"loop"!==e.data.type),[ge]),he=1===ge.length?ge[0]:null,xe=2===ge.length?ge.find(e=>"custom"===e.data.type&&!e.parentId&&!e.data.blockProjectionOwnerId):void 0,fe=xe?ge.find(e=>"internal"===e.data.blockProjectionKind&&!e.data.blockProjectionContainer):void 0,je=B(j,ge.map(e=>e.id)),me=je.length>0&&je.every(e=>e.data.blockProjectionOwnerId&&"internal"===e.data.blockProjectionKind||e.data.userBlockInstanceId||"execution"===e.data.huggingFaceClusterRole&&e.parentId),Ce="group"===he?.data.type||"loop"===he?.data.type?he:null,ve="internal"===he?.data.blockProjectionKind&&(!0===he.data.blockProjectionModular||"group"===he.data.type)&&he.data.blockProjectionOwnerId&&he.data.blockProjectionNodeId?he:null,ye=ve||"group"===he?.data.type||"loop"===he?.data.type?null:he,we="block"===ye?.data.type,ze="root"===ye?.data.huggingFaceClusterRole,Se=!(!we&&!ze),Ne=!!ye?.data.blockInstanceV2?.presentation.expanded||!!(we&&ye&&P({nodes:j,edges:[]},ye.id))||!!(ze&&ye&&y({nodes:j,edges:[]},ye.id)),Pe=!(!ye?.data.uiState?.collapsed&&!ye?.data.minimized),Ie=!!ye?.data.uiState?.disabled,De=ye?.data.uiState?.validationMessage||ye?.data.uiState?.errorMessage,Ee=!!ye,Re="custom"===ye?.data.type||Se,Me=ke.length>0&&!ve&&!(1===ke.length&&("block"===ke[0]?.data.type||"root"===ke[0]?.data.huggingFaceClusterRole)),Be=!!h,Fe=!e&&!K&&ge.length>0&&(Ee||Me||!!Ce||!!ve);(0,V.useEffect)(()=>{let e=()=>Z(e=>e+1);return window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[]),(0,V.useLayoutEffect)(()=>{let e=L.current;if(!Fe||!e)return void X(!1);let a=J(ge),t=a.x+a.width/2,o=G({x:t,y:a.y}),n=G({x:t,y:a.y+a.height}),i=e.offsetWidth,l=e.offsetHeight,s=e.parentElement?.getBoundingClientRect(),d=(s?.left??0)+8,r=(s?.top??0)+8,c=Math.max(d,(s?.right??window.innerWidth)-i-8),u=Math.max(r,(s?.bottom??window.innerHeight)-l-8),b=o.x-i/2,p=o.y-l-12;p{oe(ge.map(e=>e.id))},[oe,ge]),Te=(0,V.useCallback)(()=>{ue(ke.map(e=>e.id))},[ue,ke]),We=(0,V.useCallback)(()=>{Ce&&be(Ce.id)},[Ce,be]),Oe=(0,V.useCallback)(()=>{!ve?.data.blockProjectionContainer||!ve.data.blockProjectionOwnerId||!ve.data.blockProjectionNodeId||(de(ve.data.blockProjectionOwnerId,ve.data.blockProjectionNodeId),T.getState().saveActiveWorkflowTab(!0))},[ve,de]),Ve=(0,V.useCallback)(()=>{ye&&ne(ye.id)},[ne,ye]),$e=(0,V.useCallback)(async()=>{if(ye)try{if(("root"===ye.data.huggingFaceClusterRole||ye.data.blockInstanceV2)&&await S([ye.id]),!C({sid:ae,isConnected:te,includeStudio:!1,targetNodeId:ye.id}).canRun||!ae)return;await N({sid:ae,targetNodeId:ye.id})}catch(e){z(e instanceof Error?e.message:String(e),{variant:"error",persist:!0})}},[te,ae,ye]),Ae=(0,V.useCallback)(()=>{ye&&("block"===ye.data.type?(le(ye.id),T.getState().saveActiveWorkflowTab(!0)):"root"===ye.data.huggingFaceClusterRole?(se(ye.id),T.getState().saveActiveWorkflowTab(!0)):ie(ye.id))},[ye,se,ie,le]),Ue=(0,V.useCallback)(()=>{ye&&re(ye.id,{disabled:!Ie,validationSeverity:Ie?void 0:"warning",validationMessage:Ie?void 0:"Disabled nodes are skipped during run export."})},[Ie,re,ye]),Ke=(0,V.useCallback)(()=>{ye&&ce(ye.id)},[ce,ye]),Le=(0,V.useCallback)(async()=>{if(ye)try{await I([ye.id]),pe(ye.id,!1),z("Cache cleared",{variant:"success",autoHideDuration:1500})}catch(e){z(e instanceof Error?e.message:String(e),{variant:"error",autoHideDuration:2600})}},[pe,ye]),Qe=(0,V.useCallback)(()=>{ye&&navigator.clipboard.writeText(`${ye.id}\n${ye.data.module}.${ye.data.action}`).then(()=>z("Node info copied",{variant:"success",autoHideDuration:1600})).catch(e=>z(String(e),{variant:"error",autoHideDuration:2600}))},[ye]),Xe=(0,V.useCallback)(()=>{z(De||"No issues recorded for this node.",{variant:De?"error":"default",autoHideDuration:De?4200:2200})},[De]);return Fe?(0,$.jsxs)("div",{ref:L,className:w("nodrag nowheel fixed left-0 top-0 z-40 flex h-10 items-center gap-1 rounded-modiff-panel border border-modiff-border bg-modiff-panel p-1 shadow-modiff-node transition-opacity translate-x-[var(--modiff-selection-toolbar-x)] translate-y-[var(--modiff-selection-toolbar-y)]",Q?"opacity-100":"opacity-0"),"data-testid":"selection-toolbar",role:"toolbar","aria-label":"Selection toolbar",onPointerDown:e=>e.stopPropagation(),onWheel:e=>e.stopPropagation(),children:[(0,$.jsx)(A,{label:"Delete selection",title:"Delete selection · Delete / Backspace","aria-keyshortcuts":"Delete Backspace",danger:!0,onClick:He,"data-testid":"selection-toolbar-delete",children:(0,$.jsx)(a,{size:16})}),xe&&fe?(0,$.jsx)(A,{label:"Replace internal node with selected node","data-testid":"selection-toolbar-replace-internal",onClick:()=>{try{F.getState().replaceNodeInBlockV2(xe.id,fe.id),T.getState().saveActiveWorkflowTab(!0),z("The internal node was replaced in this workflow Block.",{variant:"success"})}catch(e){z(e instanceof Error?e.message:"Could not replace this internal node.",{variant:"error"})}},children:(0,$.jsx)(k,{size:16})}):null,me?(0,$.jsx)(A,{label:"Move out of Block",title:"Move out of Block · Ctrl/Cmd + drag to move between Blocks","data-testid":"selection-toolbar-move-out",onClick:async()=>{try{await v(ge.map(e=>e.id),null,"out"),T.getState().saveActiveWorkflowTab(!0)}catch(e){z(e instanceof Error?e.message:"Could not move this item out of the Block.",{variant:"error",autoHideDuration:6e3})}},children:(0,$.jsx)(n,{size:16})}):null,Ce&&!ve&&(0,$.jsx)(A,{label:"loop"===Ce.data.type?"Remove loop container":"Ungroup nodes",onClick:We,"data-testid":"selection-toolbar-ungroup",children:(0,$.jsx)(l,{size:16})}),ve&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(A,{label:"Save Block changes",onClick:()=>ee(ve.id),"data-testid":"selection-toolbar-save-modular-subtree",children:(0,$.jsx)(s,{size:16})}),ve.data.blockProjectionContainer?(0,$.jsx)(A,{label:!1===ve.data.blockProjectionContainerExpanded?"Expand Modular Diffusers block":"Collapse Modular Diffusers block",onClick:Oe,"data-testid":"selection-toolbar-toggle-modular-block",children:!1===ve.data.blockProjectionContainerExpanded?(0,$.jsx)(i,{size:16}):(0,$.jsx)(o,{size:16})}):null]}),Ee&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(U,{}),(0,$.jsx)(A,{label:"Duplicate node",onClick:Ve,"data-testid":"selection-toolbar-duplicate",children:(0,$.jsx)(g,{size:16})}),(0,$.jsx)(A,{label:ye?.data.blockInstanceV2?"Run Block":"Run from node",onClick:()=>{$e()},"data-testid":"selection-toolbar-run-from-node",children:(0,$.jsx)(p,{size:16})}),Re&&(0,$.jsx)(A,{label:Se?Ne?ze?"Collapse Cluster Node":"Collapse block":ze?"Expand Cluster Node to view internal nodes":"Expand block to edit internal nodes":Pe?"Expand node":"Collapse node",onClick:Ae,"data-testid":"selection-toolbar-collapse",children:Se?Ne?(0,$.jsx)(o,{size:16}):(0,$.jsx)(i,{size:16}):Pe?(0,$.jsx)(i,{size:16}):(0,$.jsx)(o,{size:16})})]}),Me&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(U,{}),(0,$.jsx)(A,{label:"Repeat selection in a loop",onClick:Te,"data-testid":"selection-toolbar-create-loop",children:(0,$.jsx)(b,{size:17})}),(0,$.jsx)(A,{label:h||"Create block from selection",active:!0,disabled:Be,onClick:E,"data-testid":"selection-toolbar-create-block",children:(0,$.jsx)(d,{size:17})})]}),Ee&&(0,$.jsxs)($.Fragment,{children:[(0,$.jsx)(U,{}),(0,$.jsxs)(R,{children:[(0,$.jsx)(O,{children:(0,$.jsx)(A,{label:"More node actions","data-testid":"selection-toolbar-more",children:(0,$.jsx)(c,{size:16})})}),ye?(0,$.jsxs)(W,{anchor:"bottom end",layer:"graph",className:"w-52","data-testid":"selection-toolbar-more-menu",children:[(0,$.jsx)(H,{icon:(0,$.jsx)(x,{size:15}),onClick:Ue,children:Ie?"Enable for run":"Disable for run"}),(0,$.jsx)(H,{icon:(0,$.jsx)(t,{size:15}),disabled:!ye.data.isCached,onClick:()=>{Le()},children:"Clear cache"}),(0,$.jsx)(H,{icon:(0,$.jsx)(k,{size:15}),disabled:"block"!==ye.data.type&&void 0===ye.width&&void 0===ye.height,onClick:Ke,children:Ne?ze?"Fit Cluster Node to contents":"Fit block to contents":Se?ze?"Reset Cluster Node size":"Reset block size":"Reset automatic size"}),(0,$.jsx)(H,{icon:(0,$.jsx)(u,{size:15}),onClick:Qe,children:"Copy node info"}),(0,$.jsx)(H,{icon:(0,$.jsx)(m,{size:15}),onClick:Xe,children:"Inspect issues"})]}):null]})]}),_?(0,$.jsx)(D,{nodeId:_,onClose:()=>ee(null)},_):null]}):null}export{K as SelectionToolbar}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{$n as t,$t as a,F as o,Pn as n,R as l,Tt as i,X as s,at as d,cn as r,cr as c,dr as u,it as p,nt as b,pn as g,rr as k,st as h,wt as x,xt as f,yn as j,z as m}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{At as C,Ci as v,Et as y,Hn as w,In as z,Jn as S,Mo as I,Mt as N,Nt as P,Ot as E,So as R,Ts as B,Un as D,Vr as M,ar as F,ei as T,hr as H,ir as V,ji as O,pi as W,ut as A,yo as $}from"./studio-templates.js?v=cc046c26d6fe7aa4";var L=e(u(),1),_=c(),U=(0,L.lazy)(()=>v(()=>import(`./NodeInspectorDialog.js?v=cc046c26d6fe7aa4`).then(e=>e.n),[])),K=(0,L.forwardRef)(function({active:e=!1,children:t,className:a,danger:o=!1,disabled:n=!1,label:l,onClick:i,type:s="button",...d},r){return(0,_.jsx)(y,{ref:r,type:s,label:l,size:"dense",active:e&&!n,tone:o&&!n?"danger":"ghost","aria-disabled":n,onClick:e=>{n?e.preventDefault():i?.(e)},className:z(n&&"cursor-not-allowed opacity-40",a),...d,children:t})});function J(){return(0,_.jsx)("span",{className:"mx-0.5 h-6 w-px bg-modiff-border","aria-hidden":"true"})}function X({canvasSuspended:e,createBlockDisabledReason:c,nodes:u,onCreateBlockFromSelection:v,selectionDragging:y}){let X=(0,L.useRef)(null),[q,G]=(0,L.useState)(!1),[Q,Y]=(0,L.useState)(0),Z=k(),{flowToScreenPosition:ee,getNodesBounds:te}=t(),[ae,oe]=(0,L.useState)(null),[ne,le]=(0,L.useState)(null),ie=S(e=>e.sid),se=S(e=>e.isConnected),de=M(e=>e.removeNodes),re=M(e=>e.duplicateNode),ce=M(e=>e.toggleNodeCollapsed),ue=M(e=>e.toggleUserBlockExpanded),pe=M(e=>e.toggleHuggingFaceClusterExpanded),be=M(e=>e.toggleBlockContainerExpandedV2),ge=M(e=>e.setNodeUiState),ke=M(e=>e.resetNodeSize),he=M(e=>e.loopNodes),xe=M(e=>e.ungroupNodes),fe=M(e=>e.setNodeCached),je=(0,L.useMemo)(()=>u.filter(e=>e.selected),[u]),me=(0,L.useMemo)(()=>je.filter(e=>"group"!==e.data.type&&"loop"!==e.data.type),[je]),Ce=1===je.length?je[0]:null;(0,L.useEffect)(()=>{ne&&Ce?.id!==ne&&le(null)},[ne,Ce?.id]);let ve=2===je.length?je.find(e=>"custom"===e.data.type&&!e.parentId&&!e.data.blockProjectionOwnerId):void 0,ye=ve?je.find(e=>"internal"===e.data.blockProjectionKind&&!e.data.blockProjectionContainer):void 0,we=D(u,je.map(e=>e.id)),ze=we.length>0&&we.every(e=>e.data.blockProjectionOwnerId&&"internal"===e.data.blockProjectionKind||e.data.userBlockInstanceId||"execution"===e.data.huggingFaceClusterRole&&e.parentId),Se="group"===Ce?.data.type||"loop"===Ce?.data.type?Ce:null,Ie="internal"===Ce?.data.blockProjectionKind&&(!0===Ce.data.blockProjectionModular||"group"===Ce.data.type)&&Ce.data.blockProjectionOwnerId&&Ce.data.blockProjectionNodeId?Ce:null,Ne=Ie||"group"===Ce?.data.type||"loop"===Ce?.data.type?null:Ce,Pe=B(Ne?.data.blockInstanceV2),Ee=!Pe&&"block"===Ne?.data.type,Re="root"===Ne?.data.huggingFaceClusterRole,Be=!(!Ee&&!Re),De=!!Ne?.data.blockInstanceV2?.presentation.expanded||!!(Ee&&Ne&&W({nodes:u,edges:[]},Ne.id))||!!(Re&&Ne&&T({nodes:u,edges:[]},Ne.id)),Me=!(!Ne?.data.uiState?.collapsed&&!Ne?.data.minimized),Fe=!!Ne?.data.uiState?.disabled,Te=Ne?.data.uiState?.validationMessage||Ne?.data.uiState?.errorMessage,He=!!Ne,Ve=!Pe&&("custom"===Ne?.data.type||Be),Oe=me.length>0&&!Ie&&!(1===me.length&&("block"===me[0]?.data.type||"root"===me[0]?.data.huggingFaceClusterRole)),We=!!c,Ae=!e&&!y&&je.length>0&&(He||Oe||!!Se||!!Ie);(0,L.useEffect)(()=>{let e=()=>Y(e=>e+1);return window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[]),(0,L.useLayoutEffect)(()=>{let e=X.current;if(!Ae||!e)return void G(!1);let t=te(je),a=t.x+t.width/2,o=ee({x:a,y:t.y}),n=ee({x:a,y:t.y+t.height}),l=e.offsetWidth,i=e.offsetHeight,s=e.parentElement?.getBoundingClientRect(),d=(s?.left??0)+8,r=(s?.top??0)+8,c=Math.max(d,(s?.right??window.innerWidth)-l-8),u=Math.max(r,(s?.bottom??window.innerHeight)-i-8),p=o.x-l/2,b=o.y-i-12;b{de(je.map(e=>e.id))},[de,je]),Le=(0,L.useCallback)(()=>{he(me.map(e=>e.id))},[he,me]),_e=(0,L.useCallback)(()=>{Se&&xe(Se.id)},[Se,xe]),Ue=(0,L.useCallback)(()=>{!Ie?.data.blockProjectionContainer||!Ie.data.blockProjectionOwnerId||!Ie.data.blockProjectionNodeId||(be(Ie.data.blockProjectionOwnerId,Ie.data.blockProjectionNodeId),O.getState().saveActiveWorkflowTab(!0))},[Ie,be]),Ke=(0,L.useCallback)(()=>{Ne&&re(Ne.id)},[re,Ne]),Je=(0,L.useCallback)(async()=>{if(Ne)try{if(("root"===Ne.data.huggingFaceClusterRole||Ne.data.blockInstanceV2)&&await F([Ne.id]),!H({sid:ie,isConnected:se,includeStudio:!1,targetNodeId:Ne.id}).canRun||!ie)return;await V({sid:ie,targetNodeId:Ne.id})}catch(e){I(e instanceof Error?e.message:String(e),{variant:"error",persist:!0})}},[se,ie,Ne]),Xe=(0,L.useCallback)(()=>{Ne&&("block"===Ne.data.type?(ue(Ne.id),O.getState().saveActiveWorkflowTab(!0)):"root"===Ne.data.huggingFaceClusterRole?(pe(Ne.id),O.getState().saveActiveWorkflowTab(!0)):ce(Ne.id))},[Ne,pe,ce,ue]),qe=(0,L.useCallback)(()=>{Ne&&ge(Ne.id,{disabled:!Fe,validationSeverity:Fe?void 0:"warning",validationMessage:Fe?void 0:"Disabled nodes are skipped during run export."})},[Fe,ge,Ne]),Ge=(0,L.useCallback)(()=>{Ne&&ke(Ne.id)},[ke,Ne]),Qe=(0,L.useCallback)(async()=>{if(Ne)try{let e=await R([Ne.id]);e.nodes.length&&fe(Ne.id,!1),I(e.nodes.length?"Outputs will recompute on the next Run. Loaded models are retained.":"Loaded models retained; no computed outputs to invalidate.",{variant:"success",autoHideDuration:3500})}catch(e){I(e instanceof Error?e.message:String(e),{variant:"error"})}},[Ne,fe]),Ye=(0,L.useCallback)(async()=>{if(Ne)try{await $([Ne.id]),fe(Ne.id,!1),I("Node cache released",{variant:"success",autoHideDuration:1500})}catch(e){I(e instanceof Error?e.message:String(e),{variant:"error",autoHideDuration:2600})}},[fe,Ne]),Ze=(0,L.useCallback)(()=>{Ne&&navigator.clipboard.writeText(`${Ne.id}\n${Ne.data.module}.${Ne.data.action}`).then(()=>I("Node info copied",{variant:"success",autoHideDuration:1600})).catch(e=>I(String(e),{variant:"error",autoHideDuration:2600}))},[Ne]),et=(0,L.useCallback)(()=>{I(Te||"No issues recorded for this node.",{variant:Te?"error":"default",autoHideDuration:Te?4200:2200})},[Te]);return Ae?(0,_.jsxs)("div",{ref:X,className:z("nodrag nowheel fixed left-0 top-0 z-40 flex h-10 items-center gap-1 rounded-modiff-panel border border-modiff-border bg-modiff-panel p-1 shadow-modiff-node transition-opacity translate-x-[var(--modiff-selection-toolbar-x)] translate-y-[var(--modiff-selection-toolbar-y)]",q?"opacity-100":"opacity-0"),"data-testid":"selection-toolbar",role:"toolbar","aria-label":"Selection toolbar",onPointerDown:e=>e.stopPropagation(),onWheel:e=>e.stopPropagation(),children:[(0,_.jsx)(K,{label:"Delete selection",title:"Delete selection · Delete / Backspace","aria-keyshortcuts":"Delete Backspace",danger:!0,onClick:$e,"data-testid":"selection-toolbar-delete",children:(0,_.jsx)(m,{size:16})}),ve&&ye?(0,_.jsx)(K,{label:"Replace internal node with selected node","data-testid":"selection-toolbar-replace-internal",onClick:()=>{try{M.getState().replaceNodeInBlockV2(ve.id,ye.id),O.getState().saveActiveWorkflowTab(!0),I("The internal node was replaced in this workflow Block.",{variant:"success"})}catch(e){I(e instanceof Error?e.message:"Could not replace this internal node.",{variant:"error"})}},children:(0,_.jsx)(p,{size:16})}):null,ze?(0,_.jsx)(K,{label:"Move out of Block",title:"Move out of Block · Ctrl/Cmd + drag to move between Blocks","data-testid":"selection-toolbar-move-out",onClick:async()=>{try{await w(je.map(e=>e.id),null,"out"),O.getState().saveActiveWorkflowTab(!0)}catch(e){I(e instanceof Error?e.message:"Could not move this item out of the Block.",{variant:"error",autoHideDuration:6e3})}},children:(0,_.jsx)(i,{size:16})}):null,Se&&!Ie&&(0,_.jsx)(K,{label:"loop"===Se.data.type?"Remove loop container":"Ungroup nodes",onClick:_e,"data-testid":"selection-toolbar-ungroup",children:(0,_.jsx)(o,{size:16})}),Ie&&(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(K,{label:"Save Block changes",onClick:()=>oe(Ie.id),"data-testid":"selection-toolbar-save-modular-subtree",children:(0,_.jsx)(s,{size:16})}),Ie.data.blockProjectionContainer?(0,_.jsx)(K,{label:!1===Ie.data.blockProjectionContainerExpanded?"Expand Modular Diffusers block":"Collapse Modular Diffusers block",onClick:Ue,"data-testid":"selection-toolbar-toggle-modular-block",children:!1===Ie.data.blockProjectionContainerExpanded?(0,_.jsx)(x,{size:16}):(0,_.jsx)(f,{size:16})}):null]}),Ce?(0,_.jsx)(K,{label:Ce.data.blockInstanceV2&&!Pe?"Inspect Block":"Inspect node",onClick:()=>le(Ce.id),"data-testid":"selection-toolbar-inspect-node",children:(0,_.jsx)(n,{size:16})}):null,He&&(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(J,{}),(0,_.jsx)(K,{label:"Duplicate node",onClick:Ke,"data-testid":"selection-toolbar-duplicate",children:(0,_.jsx)(a,{size:16})}),(0,_.jsx)(K,{label:Ne?.data.blockInstanceV2&&!Pe?"Run Block":"Run from node",onClick:()=>{Je()},"data-testid":"selection-toolbar-run-from-node",children:(0,_.jsx)(h,{size:16})}),Ve&&(0,_.jsx)(K,{label:Be?De?Re?"Collapse Block":"Collapse block":Re?"Expand Block to view internal nodes":"Expand block to edit internal nodes":Me?"Expand node":"Collapse node",onClick:Xe,"data-testid":"selection-toolbar-collapse",children:Be?De?(0,_.jsx)(f,{size:16}):(0,_.jsx)(x,{size:16}):Me?(0,_.jsx)(x,{size:16}):(0,_.jsx)(f,{size:16})})]}),Oe&&(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(J,{}),(0,_.jsx)(K,{label:"Repeat selection in a loop",onClick:Le,"data-testid":"selection-toolbar-create-loop",children:(0,_.jsx)(b,{size:17})}),(0,_.jsx)(K,{label:c||"Create block from selection",active:!0,disabled:We,onClick:v,"data-testid":"selection-toolbar-create-block",children:(0,_.jsx)(n,{size:17})})]}),He&&(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(J,{}),(0,_.jsxs)(C,{children:[(0,_.jsx)(P,{children:(0,_.jsx)(K,{label:"More node actions","data-testid":"selection-toolbar-more",children:(0,_.jsx)(r,{size:16})})}),Ne?(0,_.jsxs)(N,{anchor:"bottom end",layer:"graph",className:"w-52","data-testid":"selection-toolbar-more-menu",children:[(0,_.jsx)(E,{icon:(0,_.jsx)(d,{size:15}),onClick:qe,children:Fe?"Enable for run":"Disable for run"}),(0,_.jsx)(E,{icon:(0,_.jsx)(p,{size:15}),onClick:()=>{Qe()},children:"Recompute on next Run"}),(0,_.jsx)(E,{icon:(0,_.jsx)(j,{size:15}),disabled:!Ne.data.isCached,onClick:()=>{Ye()},children:"Release node cache"}),(0,_.jsx)(E,{icon:(0,_.jsx)(p,{size:15}),disabled:"block"!==Ne.data.type&&void 0===Ne.width&&void 0===Ne.height,onClick:Ge,children:De?Re?"Fit Block to contents":"Fit block to contents":Be?Re?"Reset Block size":"Reset block size":"Reset automatic size"}),(0,_.jsx)(E,{icon:(0,_.jsx)(g,{size:15}),onClick:Ze,children:"Copy node info"}),(0,_.jsx)(E,{icon:(0,_.jsx)(l,{size:15}),onClick:et,children:"Inspect issues"})]}):null]})]}),ae?(0,_.jsx)(A,{nodeId:ae,onClose:()=>oe(null)},ae):null,ne?(0,_.jsx)(L.Suspense,{fallback:null,children:(0,_.jsx)(U,{nodeId:ne,onClose:()=>le(null)},ne)}):null]}):null}export{X as SelectionToolbar}; \ No newline at end of file diff --git a/web/assets/ServiceExportDialog.js b/web/assets/ServiceExportDialog.js new file mode 100644 index 00000000..b410200e --- /dev/null +++ b/web/assets/ServiceExportDialog.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as r}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Ei as i,Eo as a,Oo as s,Ti as n,fn as o,in as l,mn as d,oc as p,rn as c}from"./studio-templates.js?v=cc046c26d6fe7aa4";var u=e(r(),1),f=e=>"object"==typeof e&&!!e&&!Array.isArray(e);function h(e){if(!f(e)||!1!==e.error)throw Error("Invalid service inspection response.");let t=e=>{if(!Array.isArray(e)||e.length>32768)throw Error("Invalid service fields.");return e.map(e=>{if(!f(e)||!["nodeId","field","type"].every(t=>"string"==typeof e[t]))throw Error("Invalid service field identity.");return{nodeId:String(e.nodeId),field:String(e.field),type:String(e.type)}})};return{inputs:t(e.inputs),outputs:t(e.outputs)}}function g(e,t=0){return!(t>64)&&(null===e||"string"==typeof e||"boolean"==typeof e||("number"==typeof e?Number.isFinite(e):Array.isArray(e)?e.every(e=>g(e,t+1)):f(e)&&Object.values(e).every(e=>g(e,t+1))))}function m(e){if(!f(e)||!1!==e.error||!f(e.package))throw Error("Invalid service export.");let t=e.package;if(!("modiff-service-v1"===t.schema&&"string"==typeof t.contentHash&&/^sha256:[a-f0-9]{64}$/u.test(t.contentHash)&&f(t.graph)&&f(t.graph.nodes)&&Array.isArray(t.graph.paths)&&f(t.interface)&&f(t.interface.inputs)&&f(t.interface.outputs)&&f(t.requirements)&&g(t))||JSON.stringify(t).length>8388608)throw Error("Invalid service package contract.");return t}var v=e=>s(`${p.serverAddress}/service_package`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({operation:"inspect",graph:e}),parse:h}),y=(e,t)=>s(`${p.serverAddress}/service_package`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({operation:"build",graph:e,interface:t}),parse:m}),w=t();function b({getGraph:e,onClose:t}){let r=(0,u.useRef)(i()),[s,p]=(0,u.useState)(null),[f,h]=(0,u.useState)({inputs:[],outputs:[]}),[g,m]=(0,u.useState)({}),[b,x]=(0,u.useState)(""),[j,O]=(0,u.useState)(!0);return(0,u.useEffect)(()=>{let t=!0;return(async()=>{try{let{apiGraph:i}=await e();if(!i)throw Error("Connect to MoDiff before exporting a service.");let a=await v(i);n(r.current),t&&(p(i),h(a))}catch(e){t&&x(a(e,"Could not inspect this graph."))}finally{t&&O(!1)}})(),()=>{t=!1}},[e]),(0,w.jsx)(l,{open:!0,onClose:j?()=>{}:t,dismissible:!j,title:"Export service package",description:"Name the inputs callers supply and the outputs they receive. Leave unused fields blank.",testId:"service-export-dialog",panelClassName:"max-w-2xl",footer:(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(c,{disabled:j,onClick:t,children:"Cancel"}),(0,w.jsx)(c,{disabled:!s||j,loading:j,onClick:()=>{!async function(){if(s){O(!0),x("");try{n(r.current);let i=await e();if(JSON.stringify(i.apiGraph)!==JSON.stringify(s))throw Error("The graph changed during export. Close this dialog and review the service fields again.");let a=await y(s,function(e,t){let r={inputs:{},outputs:{}};for(let i of["inputs","outputs"])for(let a of e[i]){let e=t[JSON.stringify([i,a.nodeId,a.field])]?.trim();if(e){if(!/^[A-Za-z][A-Za-z0-9_]{0,63}$/u.test(e))throw Error("Use names starting with a letter, followed by letters, digits or underscores.");if(Object.prototype.hasOwnProperty.call(r[i],e))throw Error(`Use a different name for each ${i} field.`);Object.defineProperty(r[i],e,{value:[{nodeId:a.nodeId,field:a.field}],enumerable:!0})}}if(!Object.keys(r.outputs).length)throw Error("Name at least one preview output.");return r}(f,g));n(r.current);let o=await e();if(JSON.stringify(o.apiGraph)!==JSON.stringify(s))throw Error("The graph changed during export. Close this dialog and review the service fields again.");let l=URL.createObjectURL(new Blob([JSON.stringify(a,null,2)],{type:"application/json"})),d=document.createElement("a");d.href=l,d.download="modiff-service.json",d.click(),setTimeout(()=>URL.revokeObjectURL(l),1e3),t()}catch(e){x(a(e,"Could not export this service."))}finally{O(!1)}}}()},tone:"primary",children:"Download service package"})]}),children:(0,w.jsxs)("div",{className:"grid gap-4",children:[(0,w.jsx)("p",{className:"text-modiff-metadata text-modiff-subtle-text",children:"Named inputs are required on each call; their current values are omitted. Other graph values remain in the package. Review prompts and metadata before sharing. Models need immutable revisions and custom nodes need the same reviewed source. This package runs through a local MoDiff server."}),b&&(0,w.jsx)("p",{role:"alert",className:"text-modiff-metadata text-modiff-red",children:b}),!j&&!f.outputs.length&&(0,w.jsx)("p",{children:"Add an Image Preview, Data Viewer, or another preview node to expose a service output."}),["inputs","outputs"].map(e=>(0,w.jsxs)("fieldset",{disabled:j,className:"grid gap-2",children:[(0,w.jsx)("legend",{className:"mb-2 font-semibold",children:"inputs"===e?"Inputs":"Outputs"}),f[e].map(t=>{let r=JSON.stringify([e,t.nodeId,t.field]);return(0,w.jsx)(o,{label:`${t.nodeId} · ${t.field}`,description:t.type,children:(0,w.jsx)(d,{"aria-label":`${e} ${t.nodeId}.${t.field}`,value:g[r]??"",placeholder:"Service name (optional)",onChange:e=>m({...g,[r]:e.currentTarget.value})})},r)})]},e))]})})}export{b as default}; \ No newline at end of file diff --git a/web/assets/SettingsDialog.js b/web/assets/SettingsDialog.js index 52fda717..fe319be1 100644 --- a/web/assets/SettingsDialog.js +++ b/web/assets/SettingsDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{B as s,Gn as a,bn as t,pr as l,q as i,tt as r,ur as d}from"./graph-vendor.js?v=5bcd315505f5670a";import{Gt as n,Kt as o,Qt as c,Ua as m,Ut as p,Wa as x,Wt as f,in as b,kr as u,sn as h,ta as g,to as j,ur as v}from"./studio-templates.js?v=5bcd315505f5670a";var N=e(l(),1),k=d(),w=[{id:"preferences",label:"Preferences"},{id:"tasks",label:"Tasks"},{id:"about",label:"About"}],y=[{id:"default",label:"Curve"},{id:"smoothstep",label:"Step"}],C=({opener:e,onClose:l})=>{let{edgeType:d,setEdgeType:C,resetToDefault:$}=g(),z=u(e=>e.setAllEdgesType),{queuedTasks:S,currentTask:T,taskCount:A}=v(),[q,R]=(0,N.useState)("preferences");return(0,k.jsxs)(f,{open:!!e,onClose:l,title:(0,k.jsxs)("span",{className:"flex items-center gap-2",children:[(0,k.jsx)(i,{size:17,className:"text-hf-yellow"}),"Settings"]}),description:"App preferences and current task activity.",panelClassName:"max-w-2xl",bodyClassName:"!p-0",testId:"settings-dialog",footer:(0,k.jsx)(p,{onClick:l,children:"Close"}),toolbar:(0,k.jsx)(h,{"aria-label":"Settings sections",className:"p-2",options:w.map(e=>({value:e.id,label:e.label,id:`settings-tab-${e.id}`,controls:`settings-panel-${e.id}`})),value:q,onValueChange:R,size:"normal"}),children:[w.filter(e=>e.id!==q).map(e=>(0,k.jsx)("div",{id:`settings-panel-${e.id}`,role:"tabpanel","aria-labelledby":`settings-tab-${e.id}`,hidden:!0},e.id)),(0,k.jsxs)("div",{id:`settings-panel-${q}`,role:"tabpanel","aria-labelledby":`settings-tab-${q}`,className:"p-4",children:["preferences"===q&&(0,k.jsxs)("div",{className:"grid gap-5 text-sm text-modiff-text",children:[(0,k.jsxs)("section",{className:"grid gap-3",children:[(0,k.jsx)("h3",{className:"font-semibold",children:"Graph appearance"}),(0,k.jsx)(c,{label:"Connection style",description:"Applies to connections on the canvas and newly added links.",children:(0,k.jsx)(b,{"aria-label":"Line type",className:"flex flex-wrap gap-3",name:"edge-type",value:d,options:y.map(e=>({value:e.id,label:e.label})),onValueChange:e=>(e=>{C(e),z(e)})(e)})})]}),(0,k.jsxs)("section",{className:"flex flex-wrap items-center justify-between gap-3 border-t border-modiff-border pt-4",children:[(0,k.jsxs)("div",{className:"min-w-0 flex-1 basis-56",children:[(0,k.jsx)("h3",{className:"font-semibold",children:"Restore app preferences"}),(0,k.jsx)("p",{className:"mt-1 text-modiff-subtle-text",children:"Reset view and panel preferences. Saved workflow files and downloaded models are not deleted."})]}),(0,k.jsx)(p,{icon:(0,k.jsx)(r,{size:15}),onClick:()=>{$(),z("default")},children:"Reset preferences"})]})]}),"tasks"===q&&(0,k.jsxs)("div",{className:"overflow-hidden rounded-modiff-compact border border-modiff-border",children:[(0,k.jsxs)("div",{className:"flex items-center gap-2 border-b border-modiff-border bg-modiff-panel px-3 py-3 text-sm font-semibold text-modiff-text",children:[(0,k.jsx)(t,{size:16,className:"text-modiff-subtle-text"}),A?`${A} task${1===A?"":"s"} in queue`:"No queued tasks"]}),T||0!==Object.keys(S).length?null:(0,k.jsx)("p",{className:"p-4 text-sm text-modiff-subtle-text",children:"Run a workflow to see its progress here. Pending tasks can be cancelled individually."}),T&&(0,k.jsxs)("div",{className:"flex items-center gap-3 border-b border-modiff-border px-3 py-3 text-sm text-modiff-text",children:[(0,k.jsx)(a,{size:18,className:"shrink-0 text-hf-yellow"}),(0,k.jsxs)("div",{className:"min-w-0 flex-1",children:[(0,k.jsx)("div",{className:"truncate",children:`[${T.task_id?.substring(0,6)}] ${T.name}`}),(0,k.jsx)(o,{value:T.progress??0,className:"mt-2 max-w-[250px]"})]})]}),Object.entries(S).map(([e,a])=>(0,k.jsxs)("div",{className:"flex items-center gap-3 border-b border-modiff-border px-3 py-3 text-sm text-modiff-text last:border-b-0",children:[(0,k.jsx)(t,{size:18,className:"shrink-0 text-modiff-subtle-text"}),(0,k.jsx)("div",{className:"min-w-0 flex-1 truncate",children:`[${e.substring(0,6)}] ${a.name}`}),(0,k.jsx)(n,{label:"Cancel task",className:"border border-modiff-red text-modiff-red hover:bg-modiff-red/10 hover:text-modiff-red",onClick:()=>{(async e=>{try{await x(e)}catch(e){console.error("Failed to cancel task",e),m(j(e,"Failed to cancel task"),{variant:"error",autoHideDuration:1500})}})(e)},children:(0,k.jsx)(s,{size:15})})]},e))]}),"about"===q&&(0,k.jsxs)("div",{className:"grid gap-3 text-sm text-modiff-subtle-text",children:[(0,k.jsx)("h3",{className:"text-base font-semibold text-modiff-text",children:"MoDiff"}),(0,k.jsx)("p",{children:"A graph workspace for generating images, video and audio with modular model workflows."}),(0,k.jsx)("p",{children:"Use a Cluster as one node, or expand it to inspect and customize its connected blocks. Save workflow changes to keep prompts, parameters and layout."}),(0,k.jsx)("p",{children:"Models manages local artifacts and Hugging Face downloads. Setup contains runtime diagnostics and recovery actions."})]})]})]})};export{C as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{$ as s,K as a,Vn as t,_n as l,cr as i,dr as r,z as n}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Cr as d,Eo as o,Mo as c,Sa as m,Vr as x,an as f,fn as p,in as b,mo as u,on as h,rn as g,vn as j,xn as v}from"./studio-templates.js?v=cc046c26d6fe7aa4";var N=e(r(),1),k=i(),w=[{id:"preferences",label:"Preferences"},{id:"tasks",label:"Tasks"},{id:"about",label:"About"}],y=[{id:"default",label:"Curve"},{id:"smoothstep",label:"Step"}],C=({opener:e,onClose:i})=>{let{edgeType:r,setEdgeType:C,resetToDefault:$}=m(),z=x(e=>e.setAllEdgesType),{queuedTasks:S,currentTask:T,taskCount:A}=d(),[R,V]=(0,N.useState)("preferences");return(0,k.jsxs)(b,{open:!!e,onClose:i,title:(0,k.jsxs)("span",{className:"flex items-center gap-2",children:[(0,k.jsx)(a,{size:17,className:"text-hf-yellow"}),"Settings"]}),description:"App preferences and current task activity.",panelClassName:"max-w-2xl",bodyClassName:"!p-0",testId:"settings-dialog",footer:(0,k.jsx)(g,{onClick:i,children:"Close"}),toolbar:(0,k.jsx)(v,{"aria-label":"Settings sections",className:"p-2",options:w.map(e=>({value:e.id,label:e.label,id:`settings-tab-${e.id}`,controls:`settings-panel-${e.id}`})),value:R,onValueChange:V,size:"normal"}),children:[w.filter(e=>e.id!==R).map(e=>(0,k.jsx)("div",{id:`settings-panel-${e.id}`,role:"tabpanel","aria-labelledby":`settings-tab-${e.id}`,hidden:!0},e.id)),(0,k.jsxs)("div",{id:`settings-panel-${R}`,role:"tabpanel","aria-labelledby":`settings-tab-${R}`,className:"p-4",children:["preferences"===R&&(0,k.jsxs)("div",{className:"grid gap-5 text-sm text-modiff-text",children:[(0,k.jsxs)("section",{className:"grid gap-3",children:[(0,k.jsx)("h3",{className:"font-semibold",children:"Graph appearance"}),(0,k.jsx)(p,{label:"Connection style",description:"Applies to connections on the canvas and newly added links.",children:(0,k.jsx)(j,{"aria-label":"Line type",className:"flex flex-wrap gap-3",name:"edge-type",value:r,options:y.map(e=>({value:e.id,label:e.label})),onValueChange:e=>(e=>{C(e),z(e)})(e)})})]}),(0,k.jsxs)("section",{className:"flex flex-wrap items-center justify-between gap-3 border-t border-modiff-border pt-4",children:[(0,k.jsxs)("div",{className:"min-w-0 flex-1 basis-56",children:[(0,k.jsx)("h3",{className:"font-semibold",children:"Restore app preferences"}),(0,k.jsx)("p",{className:"mt-1 text-modiff-subtle-text",children:"Reset view and panel preferences. Saved workflow files and downloaded models are not deleted."})]}),(0,k.jsx)(g,{icon:(0,k.jsx)(s,{size:15}),onClick:()=>{$(),z("default")},children:"Reset preferences"})]})]}),"tasks"===R&&(0,k.jsxs)("div",{className:"overflow-hidden rounded-modiff-compact border border-modiff-border",children:[(0,k.jsxs)("div",{className:"flex items-center gap-2 border-b border-modiff-border bg-modiff-panel px-3 py-3 text-sm font-semibold text-modiff-text",children:[(0,k.jsx)(l,{size:16,className:"text-modiff-subtle-text"}),A?`${A} task${1===A?"":"s"} in queue`:"No queued tasks"]}),T||0!==Object.keys(S).length?null:(0,k.jsx)("p",{className:"p-4 text-sm text-modiff-subtle-text",children:"Run a workflow to see its progress here. Pending tasks can be cancelled individually."}),T&&(0,k.jsxs)("div",{className:"flex items-center gap-3 border-b border-modiff-border px-3 py-3 text-sm text-modiff-text",children:[(0,k.jsx)(t,{size:18,className:"shrink-0 text-hf-yellow"}),(0,k.jsxs)("div",{className:"min-w-0 flex-1",children:[(0,k.jsx)("div",{className:"truncate",children:`[${T.task_id?.substring(0,6)}] ${T.name}`}),(0,k.jsx)(h,{value:T.progress??0,className:"mt-2 max-w-[250px]"})]})]}),Object.entries(S).map(([e,s])=>(0,k.jsxs)("div",{className:"flex items-center gap-3 border-b border-modiff-border px-3 py-3 text-sm text-modiff-text last:border-b-0",children:[(0,k.jsx)(l,{size:18,className:"shrink-0 text-modiff-subtle-text"}),(0,k.jsx)("div",{className:"min-w-0 flex-1 truncate",children:`[${e.substring(0,6)}] ${s.name}`}),(0,k.jsx)(f,{label:"Cancel task",className:"border border-modiff-red text-modiff-red hover:bg-modiff-red/10 hover:text-modiff-red",onClick:()=>{(async e=>{try{await u(e)}catch(e){console.error("Failed to cancel task",e),c(o(e,"Failed to cancel task"),{variant:"error",autoHideDuration:1500})}})(e)},children:(0,k.jsx)(n,{size:15})})]},e))]}),"about"===R&&(0,k.jsxs)("div",{className:"grid gap-3 text-sm text-modiff-subtle-text",children:[(0,k.jsx)("h3",{className:"text-base font-semibold text-modiff-text",children:"MoDiff"}),(0,k.jsx)("p",{children:"A graph workspace for generating images, video and audio with modular model workflows."}),(0,k.jsx)("p",{children:"Use a Block as one node, or expand it to inspect and customize its connected blocks. Save workflow changes to keep prompts, parameters and layout."}),(0,k.jsx)("p",{children:"Models manages local artifacts and Hugging Face downloads. Setup contains runtime diagnostics and recovery actions."})]})]})]})};export{C as default}; \ No newline at end of file diff --git a/web/assets/SplineField.js b/web/assets/SplineField.js index 689aae37..1fc337e5 100644 --- a/web/assets/SplineField.js +++ b/web/assets/SplineField.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{nr as t,pr as r,ur as l}from"./graph-vendor.js?v=5bcd315505f5670a";import{Tn as n,Ut as i,_n as a,wn as u}from"./studio-templates.js?v=5bcd315505f5670a";var o=e(r(),1),s=l(),c=n.hoverLight,f=u.splineCurve,x=u.primary,d=u.paper;function m(e){return!(!e||"object"!=typeof e||"number"!=typeof e.x||"number"!=typeof e.y)}function h(e){let r=(0,o.useRef)(null),l=(0,o.useRef)(null),n=(0,o.useRef)([]),u=(0,o.useRef)(null),h=(0,o.useRef)(null),y=t(e=>e.transform[2]),{fieldKey:p,hidden:v,label:g,style:b,updateStore:k,value:w}=e,M=(0,o.useCallback)(()=>{let e=n.current,t=e.length-1;if(t<1)return[];for(let r=0;re.x),l=e.map(e=>e.y),i=Array(t);for(let e=0;e=0;e--)u[e]=c[e]-s[e]*u[e+1];let f=Array(t);for(let e=0;e{let t=u.current;if(!t||0===t.length)return null;let r=t.findIndex(t=>t.x>e)-1;r<0&&(r=t.length-1);let l=t[r];if(!l)return null;let n=e-l.x;return l.y+l.b*n+l.c*n*n+l.d*n*n*n},[]),E=(0,o.useCallback)((e,t)=>{let r=n.current;for(let l=0;l{let e=n.current,t=r.current,i=l.current;if(!t||!i)return;let a=t.width,o=t.height,s=window.devicePixelRatio||1,m=Math.max(.4,Math.min(y,1.4));i.clearRect(0,0,a,o),i.strokeStyle=c,i.lineWidth=1*s*m;for(let e=0;e<10;e++){let t=e*a/10,r=e*o/10;i.beginPath(),i.moveTo(t,0),i.lineTo(t,o),i.stroke(),i.beginPath(),i.moveTo(0,r),i.lineTo(a,r),i.stroke()}if(e.length<2)return;let h=e[0],p=e[e.length-1];if(!h||!p)return;i.beginPath(),i.strokeStyle=f,i.lineWidth=2.5*s*m;let v=Math.max(0,Math.min(o,h.y));if(i.moveTo(0,v),i.lineTo(h.x,v),u.current&&u.current.length>0)for(let e=Math.ceil(h.x);e<=Math.floor(p.x);e++){let t=C(e);if(null!==t){let r=Math.max(0,Math.min(o,t));i.lineTo(e,r)}}let g=Math.max(0,Math.min(o,p.y));i.lineTo(p.x,g),i.lineTo(a,g),i.stroke(),e.forEach(e=>{i.beginPath(),i.arc(e.x,e.y,6*s*m,0,2*Math.PI),i.fillStyle=x,i.fill(),i.strokeStyle=d,i.lineWidth=2*s*m,i.stroke()})},[C,y]),A=(0,o.useCallback)(e=>{let t=r.current;if(!t)return{x:0,y:0};let l=t.getBoundingClientRect(),n=window.devicePixelRatio||1;return{x:(e.clientX-l.left)*n,y:(e.clientY-l.top)*n}},[]),L=(0,o.useCallback)(e=>{let t=r.current;if(!t)return;n.current=e;let l=e.map(e=>({x:Math.max(0,Math.min(1,parseFloat((e.x/t.width).toFixed(6)))),y:Math.max(0,Math.min(1,parseFloat((1-e.y/t.height).toFixed(6))))}));k(p,l)},[p,k]),P=(0,o.useCallback)(e=>{let t=h.current;if(null===t)return;e.preventDefault(),e.stopPropagation();let l=n.current;if(t>=l.length)return;let i=l[t];if(!i)return;let a=A(e),u=r.current;if(!u)return;let o=u.width,s=u.height;i.x=a.x,i.y=Math.max(0,Math.min(s,a.y)),l.sort((e,t)=>e.x-t.x);let c=l.indexOf(i);if(c<0)return;h.current=c;let f=l[c-1],x=l[c+1],d=0===c?0:(f?.x??0)+3,m=c===l.length-1?o:(x?.x??o)-3;i.x=Math.max(d,Math.min(m,i.x)),L(l)},[A,L]),R=(0,o.useCallback)(e=>{e.preventDefault(),e.stopPropagation(),h.current=null;let t=n.current;t.sort((e,t)=>e.x-t.x),L(t),document.removeEventListener("mousemove",P),document.removeEventListener("mouseup",R)},[P,L]),T=(0,o.useCallback)(e=>{if(0!==e.button||null==r.current)return;let t=A(e),l=n.current,i=E(t.x,t.y);if(null!==i)h.current=l.indexOf(i);else{let e=l[0],r=l[l.length-1];if(!e||!r)return;if(t.x>e.x&&t.xe.x-t.x),h.current=n.indexOf(r),L(n)}}}document.addEventListener("mousemove",P),document.addEventListener("mouseup",R)},[C,E,A,P,R,L]),N=(0,o.useCallback)(e=>{if(0!==e.button)return;e.preventDefault(),e.stopPropagation();let t=n.current;if(t.length<=2)return;let r=A(e),l=E(r.x,r.y);if(l){let e=t.filter(e=>e!==l);L(e)}},[E,A,L]);return(0,o.useEffect)(()=>{let e=r.current;if(null==e)return;l.current||=e.getContext("2d");let t=Array.isArray(w)?w.filter(m):[];if(!Array.isArray(t)||t.length<2)return void k(p,[{x:0,y:0},{x:1,y:1}]);let i=window.devicePixelRatio||1,a=320*y*i,o=320*y*i;e.width=a,e.height=o,n.current=t.map(t=>({x:t.x*e.width,y:(1-t.y)*e.height})),u.current=M(),j()},[M,j,p,k,w,y]),(0,o.useEffect)(()=>{let e=r.current;if(null!=e)return l.current=e.getContext("2d"),e.addEventListener("mousedown",T),e.addEventListener("dblclick",N),()=>{e.removeEventListener("mousedown",T),document.removeEventListener("mousemove",P),document.removeEventListener("mouseup",R),e.removeEventListener("dblclick",N)}},[N,T,P,R]),(0,s.jsxs)(a,{dataKey:p,hidden:v,layoutStyle:b,className:"nodrag",children:[(0,s.jsx)("div",{className:"text-modiff-control truncate text-modiff-subtle-text",title:g,children:g}),(0,s.jsxs)("div",{className:"flex w-full flex-col items-center justify-between",children:[(0,s.jsx)("div",{className:"m-0 w-[320px] overflow-hidden rounded-modiff-compact border border-modiff-border p-0",children:(0,s.jsx)("canvas",{ref:r,className:"block aspect-square w-[320px] cursor-crosshair"})}),(0,s.jsx)("div",{className:"mt-2 w-full",children:(0,s.jsx)(i,{className:"min-h-0 px-3 py-1.5",onClick:()=>{k(p,[{x:0,y:0},{x:1,y:1}])},children:"Reset"})})]})]})}export{h as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as r,er as l}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Rn as n,jn as i,rn as a,zn as u}from"./studio-templates.js?v=cc046c26d6fe7aa4";var o=e(r(),1),s=t(),c=u.hoverLight,f=n.splineCurve,d=n.primary,x=n.paper;function m(e){return!(!e||"object"!=typeof e||"number"!=typeof e.x||"number"!=typeof e.y)}function h(e){let t=(0,o.useRef)(null),r=(0,o.useRef)(null),n=(0,o.useRef)([]),u=(0,o.useRef)(null),h=(0,o.useRef)(null),y=l(e=>e.transform[2]),{fieldKey:p,hidden:v,label:g,style:b,updateStore:k,value:w}=e,M=(0,o.useCallback)(()=>{let e=n.current,t=e.length-1;if(t<1)return[];for(let r=0;re.x),l=e.map(e=>e.y),i=Array(t);for(let e=0;e=0;e--)u[e]=c[e]-s[e]*u[e+1];let f=Array(t);for(let e=0;e{let t=u.current;if(!t||0===t.length)return null;let r=t.findIndex(t=>t.x>e)-1;r<0&&(r=t.length-1);let l=t[r];if(!l)return null;let n=e-l.x;return l.y+l.b*n+l.c*n*n+l.d*n*n*n},[]),j=(0,o.useCallback)((e,t)=>{let r=n.current;for(let l=0;l{let e=n.current,l=t.current,i=r.current;if(!l||!i)return;let a=l.width,o=l.height,s=window.devicePixelRatio||1,m=Math.max(.4,Math.min(y,1.4));i.clearRect(0,0,a,o),i.strokeStyle=c,i.lineWidth=1*s*m;for(let e=0;e<10;e++){let t=e*a/10,r=e*o/10;i.beginPath(),i.moveTo(t,0),i.lineTo(t,o),i.stroke(),i.beginPath(),i.moveTo(0,r),i.lineTo(a,r),i.stroke()}if(e.length<2)return;let h=e[0],p=e[e.length-1];if(!h||!p)return;i.beginPath(),i.strokeStyle=f,i.lineWidth=2.5*s*m;let v=Math.max(0,Math.min(o,h.y));if(i.moveTo(0,v),i.lineTo(h.x,v),u.current&&u.current.length>0)for(let e=Math.ceil(h.x);e<=Math.floor(p.x);e++){let t=C(e);if(null!==t){let r=Math.max(0,Math.min(o,t));i.lineTo(e,r)}}let g=Math.max(0,Math.min(o,p.y));i.lineTo(p.x,g),i.lineTo(a,g),i.stroke(),e.forEach(e=>{i.beginPath(),i.arc(e.x,e.y,6*s*m,0,2*Math.PI),i.fillStyle=d,i.fill(),i.strokeStyle=x,i.lineWidth=2*s*m,i.stroke()})},[C,y]),R=(0,o.useCallback)(e=>{let r=t.current;if(!r)return{x:0,y:0};let l=r.getBoundingClientRect(),n=window.devicePixelRatio||1;return{x:(e.clientX-l.left)*n,y:(e.clientY-l.top)*n}},[]),A=(0,o.useCallback)(e=>{let r=t.current;if(!r)return;n.current=e;let l=e.map(e=>({x:Math.max(0,Math.min(1,parseFloat((e.x/r.width).toFixed(6)))),y:Math.max(0,Math.min(1,parseFloat((1-e.y/r.height).toFixed(6))))}));k(p,l)},[p,k]),L=(0,o.useCallback)(e=>{let r=h.current;if(null===r)return;e.preventDefault(),e.stopPropagation();let l=n.current;if(r>=l.length)return;let i=l[r];if(!i)return;let a=R(e),u=t.current;if(!u)return;let o=u.width,s=u.height;i.x=a.x,i.y=Math.max(0,Math.min(s,a.y)),l.sort((e,t)=>e.x-t.x);let c=l.indexOf(i);if(c<0)return;h.current=c;let f=l[c-1],d=l[c+1],x=0===c?0:(f?.x??0)+3,m=c===l.length-1?o:(d?.x??o)-3;i.x=Math.max(x,Math.min(m,i.x)),A(l)},[R,A]),P=(0,o.useCallback)(e=>{e.preventDefault(),e.stopPropagation(),h.current=null;let t=n.current;t.sort((e,t)=>e.x-t.x),A(t),document.removeEventListener("mousemove",L),document.removeEventListener("mouseup",P)},[L,A]),T=(0,o.useCallback)(e=>{if(0!==e.button||null==t.current)return;let r=R(e),l=n.current,i=j(r.x,r.y);if(null!==i)h.current=l.indexOf(i);else{let e=l[0],t=l[l.length-1];if(!e||!t)return;if(r.x>e.x&&r.xe.x-t.x),h.current=n.indexOf(t),A(n)}}}document.addEventListener("mousemove",L),document.addEventListener("mouseup",P)},[C,j,R,L,P,A]),N=(0,o.useCallback)(e=>{if(0!==e.button)return;e.preventDefault(),e.stopPropagation();let t=n.current;if(t.length<=2)return;let r=R(e),l=j(r.x,r.y);if(l){let e=t.filter(e=>e!==l);A(e)}},[j,R,A]);return(0,o.useEffect)(()=>{let e=t.current;if(null==e)return;r.current||=e.getContext("2d");let l=Array.isArray(w)?w.filter(m):[];if(!Array.isArray(l)||l.length<2)return void k(p,[{x:0,y:0},{x:1,y:1}]);let i=window.devicePixelRatio||1,a=320*y*i,o=320*y*i;e.width=a,e.height=o,n.current=l.map(t=>({x:t.x*e.width,y:(1-t.y)*e.height})),u.current=M(),E()},[M,E,p,k,w,y]),(0,o.useEffect)(()=>{let e=t.current;if(null!=e)return r.current=e.getContext("2d"),e.addEventListener("mousedown",T),e.addEventListener("dblclick",N),()=>{e.removeEventListener("mousedown",T),document.removeEventListener("mousemove",L),document.removeEventListener("mouseup",P),e.removeEventListener("dblclick",N)}},[N,T,L,P]),(0,s.jsxs)(i,{dataKey:p,hidden:v,layoutStyle:b,className:"nodrag",children:[(0,s.jsx)("div",{className:"text-modiff-control truncate text-modiff-subtle-text",title:g,children:g}),(0,s.jsxs)("div",{className:"flex w-full flex-col items-center justify-between",children:[(0,s.jsx)("div",{className:"m-0 w-[320px] overflow-hidden rounded-modiff-compact border border-modiff-border p-0",children:(0,s.jsx)("canvas",{ref:t,className:"block aspect-square w-[320px] cursor-crosshair"})}),(0,s.jsx)("div",{className:"mt-2 w-full",children:(0,s.jsx)(a,{className:"min-h-0 px-3 py-1.5",onClick:()=>{k(p,[{x:0,y:0},{x:1,y:1}])},children:"Reset"})})]})]})}export{h as default}; \ No newline at end of file diff --git a/web/assets/TemplateBrowserDialog.js b/web/assets/TemplateBrowserDialog.js index c39b423d..f114c4a3 100644 --- a/web/assets/TemplateBrowserDialog.js +++ b/web/assets/TemplateBrowserDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{D as t,In as a,It as r,M as s,Nt as l,On as o,Pt as i,Rt as n,U as d,Vn as c,bn as m,lt as u,pr as p,q as f,tn as g,ur as h,wn as b,xt as x,z as v}from"./graph-vendor.js?v=5bcd315505f5670a";import{Ca as w,Di as y,Dr as j,Ei as k,Fo as N,Gi as _,Gt as S,Ki as P,Mi as R,Oa as T,Oi as I,Qt as C,Rn as M,Si as $,Sn as q,Ta as z,Ua as A,Ui as E,Ut as B,Wt as L,Yi as O,Yn as V,_a as D,ai as F,an as U,bi as K,br as G,fi as W,ga as H,gi as X,gn as Y,ha as Q,ii as J,ji as Z,jn as ee,ki as te,kr as ae,mi as re,mn as se,ni as le,oi as oe,on as ie,p as ne,pi as de,ri as ce,ro as me,si as ue,sr as pe,t as fe,ta as ge,ui as he,un as be,ur as xe,wi as ve,xi as we,yi as ye,zn as je}from"./studio-templates.js?v=5bcd315505f5670a";import{a as ke,i as Ne,n as _e,t as Se}from"./templateBrowser.js?v=5bcd315505f5670a";import{n as Pe,t as Re}from"./TemplateUsageTermsDialog.js?v=5bcd315505f5670a";import{a as Te,i as Ie,l as Ce,n as Me,o as $e,r as qe,s as ze,t as Ae}from"./controlledWorkflows.js?v=5bcd315505f5670a";var Ee=e(p(),1);function Be(e){return"install_model"===e?"install_model":"open_models"===e?"open_model_manager":"open_assets"===e||"add_input"===e?"select_image":"open_setup"===e?"open_setup":"apply_low_vram"===e?"apply_low_vram_preset":void 0}function Le(e){return{id:`template:${e.id}`,category:e.category,severity:"error"===e.tone?"error":"warning"===e.tone?"warning":"success"===e.tone?"success":"info",repoId:e.repoId,message:e.title,details:e.detail,blocking:e.blocking,action:Be(e.action)}}function Oe(e){return Object.keys(e).length>0}function Ve(e){return"hub"===e?.source&&e.value.trim().length>0}function De(e){let[t,a]=e.value.split("/");return t&&a?`${t}/${a}`:e.value}function Fe(e){let t=[],a=(a,r,s)=>{Ve(s)&&t.push({id:a,label:r,repo:De(s),kind:"adapter",requiredForModes:[e.mode],description:`${r} pinned by ${e.label}.`})};a(`${e.id}-lora`,"LoRA adapter",e.workflowBlockSettings?.lora?.model),a(`${e.id}-lora-base`,"LoRA base model",e.workflowBlockSettings?.lora?.baseModel);for(let[t,r]of(e.workflowBlockSettings?.lora?.additionalAdapters??[]).entries())a(`${e.id}-lora-${t+2}`,`LoRA adapter ${t+2}`,r.model),a(`${e.id}-lora-${t+2}-base`,`LoRA base model ${t+2}`,r.baseModel);return a(`${e.id}-upscaler`,"Upscaler model",e.workflowBlockSettings?.upscaler?.model),t}function Ue(e){let t=H[e.modelType];return[...!1===t.artifactInstallRequired?[]:[{id:`${e.modelType}-base`,label:t.label,repo:t.defaultRepo,kind:"base",requiredForModes:[e.mode],description:`Base model for ${t.label}.`}],...T(t,e.mode),...Fe(e)]}function Ke(e,t){let a=H[e.modelType],r=W(t.autoResourcePlan,t.form),s=X(t.autoResourcePlan,t.form),l=re(t.autoResourcePlan,t.form),o=!0===t.modelIndexesRefreshing,i="planning"===e.readinessPolicy||"blocked"===e.difficulty||"blocked"===e.example?.status,n=!t.runtimeStatus&&!Oe(t.nodesRegistry),d=function(e,t){let a=e.requiredBackendCapabilities??[];return 0===a.length?[]:a.filter(e=>{let a=fe(e);return!a||Oe(t)&&!a.every(e=>t[e])})}(e,t.nodesRegistry),c=function(e,t){let a=je(e).missingLabels;return t?(t.loraAdapter&&!Ve(e.workflowBlockSettings?.lora?.model)&&a.push("LoRA adapter"),t.upscalerModel&&!Ve(e.workflowBlockSettings?.upscaler?.model)&&a.push("Upscaler model"),Array.from(new Set(a))):a}(e,e.inputRequirements),m=o?[]:[...t.autoResourcePlan?T(a,e.mode):Ue(e),...t.autoResourcePlan?Fe(e):[]].filter(e=>!pe(e.repo,t.hfCache,t.localModels,t.modelCacheDiagnostics).runnable).map(e=>e.repo),u=[],p=[];i&&u.push({id:"planning",category:"graph",blocking:!0,tone:"info",title:"Planning template",detail:e.example?.blockReason??"This recipe can be opened and inspected, but MoDiff should not present it as runnable yet.",action:"open_planning"}),o&&u.push({id:"model-index-refresh",category:"model",blocking:!0,tone:"info",title:"Checking installed models",detail:"Refreshing the local model index before reporting template readiness."}),!o&&"checking"===l.state&&u.push({id:"auto-plan-checking",category:"environment",blocking:!0,tone:"info",title:l.summary,detail:l.detail}),n&&u.push({id:"backend:connection",category:"backend",blocking:!0,tone:"error",title:"Backend connection needed",detail:"Connect to the MoDiff backend before checking installed models.",action:"open_setup"}),d.forEach(e=>{u.push({id:`backend:${e}`,category:"package",blocking:!0,tone:"error",title:"Backend contract needed",detail:e,action:"open_setup"})}),m.forEach(e=>{p.push({repoId:e,actionLabel:"Install"}),u.push({id:`model:${e}`,category:"model",blocking:!0,tone:"warning",title:"Model package missing",detail:e,action:"install_model",repoId:e,actionLabel:"Install"})}),o||r||!s?!o&&!r&&t.autoResourcePlan&&!s&&u.push({id:"auto-plan-blocked",category:"environment"===t.autoResourcePlan.issue?.category?"environment":"package"===t.autoResourcePlan.issue?.category?"package":"device"===t.autoResourcePlan.issue?.category?"hardware_fit":"environment",blocking:!0,tone:"error"===l.severity?"error":"warning"===l.severity?"warning":"info",title:l.summary,detail:l.detail,action:"expert_only"===l.state?"open_planning":"open_setup"}):(p.push({repoId:s.repo,repair:s.repair,actionLabel:s.actionLabel}),u.push({id:`auto-model:${s.repo}`,category:s.repair?"model_integrity":"model",blocking:!0,tone:s.repair?"error":"warning",title:s.repair?"Model package repair required":"Auto model package missing",detail:`${s.actionLabel??"Install Auto artifact"}: ${s.repo}`,action:"install_model",repoId:s.repo,repair:s.repair,actionLabel:s.actionLabel})),c.forEach(e=>{u.push({id:`input:${e}`,category:"asset",blocking:!0,tone:"warning",title:`${e} required`,detail:"Add the required asset in Studio after creating this workflow, or open the Assets library to reuse an existing output.",action:"open_assets"})});let f=V(u.map(Le),{preparing:o||"checking"===l.state}),g=f.primaryIssue?.category,h=i||u.some(e=>"open_planning"===e.action)?"planning":"preparing"===f.state?"preparing":"blocked"===f.state?"backend"===g||"package"===g?"needs_backend":"model"===g||"model_integrity"===g?"needs_model":"asset"===g||"user_input"===g?"needs_input":"needs_setup":"ready",b=function(e){return"ready"===e?{label:"Ready now",tone:"success"}:"needs_input"===e?{label:"Needs input",tone:"warning"}:"needs_model"===e?{label:"Needs model",tone:"warning"}:"needs_backend"===e?{label:"Backend blocked",tone:"error"}:"needs_setup"===e?{label:"Needs setup",tone:"warning"}:"preparing"===e?{label:"Checking models",tone:"info"}:{label:"Planning only",tone:"info"}}(h),x="ready"===h?`${Q[e.modelType]} Auto plan is ready, hardware looks usable, and no required inputs are missing.`:u[0]?.detail??"Review the required setup before running this recipe.";return{status:h,label:b.label,tone:b.tone,summary:x,compatibility:l,issues:u,missingModelRepos:Array.from(new Set(m)),modelInstallTargets:Array.from(new Map(p.map(e=>[e.repoId,e])).values()),missingInputs:c,missingBackendCapabilities:d,decision:f}}async function Ge(e,t,a,r={}){"lora"!==e?"upscaler"!==e?"video_sequence"!==e?"quality_video_sequence"!==e?"soundtrack"!==e?await Me(t,a.workflowBlockSettings?.lyricVideo,r):await Ie(t,a.workflowBlockSettings?.soundtrack,r):await qe(t,a.workflowBlockSettings?.qualityVideoSequence,r):await $e(t,a.workflowBlockSettings?.videoSequence,r):await Te(t,a.workflowBlockSettings?.upscaler,r):await Ae(t,a.workflowBlockSettings?.lora,r)}function We(){return typeof window>"u"||"function"!=typeof window.requestAnimationFrame?Promise.resolve():new Promise(e=>{window.requestAnimationFrame(()=>{window.requestAnimationFrame(()=>e())})})}async function He(e){let t=he.getState().createWorkflowTab(e.label,void 0,"template",e.id),a=F();he.getState().setCanvasTransition({type:"template_graph_building",workflowTabId:t,templateId:e.id,startedAt:Date.now()});let r,s=he.getState().form,l=s,o=!1,i=[];try{if("success"!==le.getState().discoveryRequests.capabilities.status){await le.getState().fetchStudioModelCapabilities(),J(a);let e=le.getState().discoveryRequests.capabilities;if("success"!==e.status)throw Error(e.error||"Model capabilities are not ready. Retry opening the template.")}let t=await M(e);J(a),he.getState().applyTemplate(e,t),ce(a),he.getState().saveActiveWorkflowTab(!0),l=he.getState().form,await We(),J(a);let r=await G(l,a);if(J(a),"auto"===l.resourceMode){let e=de(l),t=he.getState().autoResourcePlans[e]??await ye(l);if(J(a),t.error||he.getState().setAutoResourcePlans({[e]:t}),he.getState().setAutoResourcePlan(t),W(t,l)){let e=oe(t,l,he.getState().graphBinding);if(e)he.getState().setLastError(e);else{let e=we(ve(t,l),l);he.getState().applyAutoResourcePlan(t,e),ce(a),l=he.getState().form,r=await G(l,a),J(a)}}}o=!0,i=r.warnings;for(let t of e.workflowBlocks??[])try{await Ge(t,l,e,{graphPrepared:!0,workflowContext:a}),J(a)}catch(a){if(ue(a))throw a;let r=a instanceof Error?a.message:String(a);i.push(`${e.label} could not add the ${t} block: ${r}`)}let s=await j(15e3,a);if(J(a),!s)throw Error(`${e.label} graph preparation timed out. No incomplete graph was kept; try again.`);await N(()=>ae.getState().nodes,500),J(a),await ae.getState().arrangeGraph({history:!1}),J(a),await We(),J(a),he.getState().saveActiveWorkflowTab(!0)}catch(e){if(ue(e))throw e;J(a),r=e instanceof Error?e.message:String(e),ae.getState().replaceGraph({nodes:[],edges:[],viewport:{x:0,y:0,zoom:1}}),he.getState().setGraphBinding(null),he.getState().setGraphFinalization(null),he.getState().saveActiveWorkflowTab(!0)}finally{let a=he.getState().canvasTransition;a?.workflowTabId===t&&a.templateId===e.id&&he.getState().setCanvasTransition(null)}let n=Object.keys(te(e)),d=ze(s,l,n);return Ce(`${e.label} recipe applied`,d,he.getState().graphBinding?.managedNodeIds??[]),ge.getState().setRightPanelOpen(!0),ge.getState().setRightPanelTab("studio"),{graphCreated:o,graphError:r,warnings:i}}function Xe(e){let t=null,a=!1;return{hasRejected:()=>a,load:(r=!1)=>{if(t&&!r)return t;a=!1;let s=e();return t=s,s.then(()=>{t===s&&(a=!1)},()=>{t===s&&(t=null,a=!0)}),s}}}var Ye=h(),Qe=["all","starter","intermediate","advanced","blocked"],Je=[..._,...E],Ze=[{value:"recommended",label:"Recommended"},{value:"task",label:"Task"},{value:"model",label:"Model"},{value:"runtime",label:"Runtime"}],et=Array.from(new Set(_.map(e=>e.mode))).sort((e,t)=>D[e].localeCompare(D[t]));function tt(e){let t=te(e);return O({...z(e.mode,e.modelType),...P(e.presetId)?.values??{},...t,mode:e.mode,modelType:e.modelType,prompt:e.prompt,negativePrompt:e.negativePrompt??t.negativePrompt})}var at=_.map(e=>{let t=tt(e);return{form:t,planKey:de(t),templateId:e.id}}),rt=Xe(async()=>me(k,{credentials:"omit",timeoutMs:12e4,parse:Z})),st=Xe(async()=>{let e=at.map(e=>e.templateId),t=await K(at.map(e=>e.form),e),a={};return t.forEach((e,t)=>{let r=at[t];r&&(a[r.templateId]=e,a[r.planKey]??=e)}),a});function lt(e=!1){return rt.load(e)}function ot(e=!1){return st.load(e)}function it(e){return"all"===e?"All levels":e.charAt(0).toUpperCase()+e.slice(1)}function nt(e){return e.outputKinds?.join(", ")||e.example?.mediaType||"image"}function dt(e){return R(e.example?.thumbnailPath??e.example?.outputPath)}function ct(e){let t=e.inputRequirements;if(!t)return["Prompt only"];let a="hub"===e.workflowBlockSettings?.lora?.model.source,r="hub"===e.workflowBlockSettings?.upscaler?.model.source,s=[];t.sourceImage&&s.push("source image"),t.referenceImages&&s.push(`${t.referenceImages} reference image${1===t.referenceImages?"":"s"}`),t.controlImage&&s.push("control image"),t.maskImage&&s.push("mask image"),t.sourceVideo&&s.push("source video"),t.maskVideo&&s.push("mask video"),t.controlVideo&&s.push("control video"),t.loraAdapter&&!a&&s.push("LoRA adapter"),t.upscalerModel&&!r&&s.push("upscaler model"),t.sampleAssets?.length&&s.push(...t.sampleAssets.filter(e=>!(a&&/lora adapter/i.test(e)||r&&/upscaler|real-esrgan/i.test(e))));let l=s.filter((e,t)=>s.findIndex(t=>t.toLocaleLowerCase()===e.toLocaleLowerCase())===t);return l.length>0?l:["Prompt only"]}function mt(e){return Se.find(t=>t.id===e)?.label??"Templates"}function ut(e,t){if(t){let a=y(e,t);return!(!a||"video"===a.mediaType&&(!a.cardPreviewPath||!a.cardPreviewSha256))}let a=e.verificationStatus??e.example?.status;return"video"!==e.example?.mediaType&&!e.outputKinds?.includes("video")&&!("reviewed"!==a&&"exact"!==a||!dt(e)&&!function(e){return R(e.example?.outputPath)}(e))}var pt=["h-3","h-7","h-5","h-10","h-6","h-12","h-8","h-14","h-9","h-5","h-11","h-7","h-12","h-8","h-4","h-9"],ft=["qwen_low_vram_product_concept","qwen_poster_logo_text","qwen_text_rendering","qwen_product_mockup","qwen_low_vram_poster_layout","qwen_low_vram_text_rendering","z_image_product_mockup","z_image_poster","z_image_cinematic_contact_sheet","z_image_quick_concept","wan_vace_cinematic_text_to_video","low_vram","high_quality"];function gt(e){let t=ft.indexOf(e);return-1===t?ft.length:t}function ht(){let e=ge(e=>e.templateBrowserOpen),t=ge(e=>e.setTemplateBrowserOpen),a=ge(e=>e.templateBrowserInitialCategory),r=ge(e=>e.clearTemplateBrowserInitialCategory),l=ge(e=>e.setRightPanelOpen),o=ge(e=>e.setRightPanelTab),i=ge(e=>e.setModelManagerOpener),n=ge(e=>e.setGalleryLibraryOpen),d=Pe(),c=he(e=>e.form),m=he(e=>e.activeTemplateId),u=he(e=>e.autoResourcePlans),p=he(e=>e.setAutoResourcePlans),f=xe(e=>e.sessionRuns.reduce((e,t)=>"completed"===t.status?Math.max(e,t.completedAtMs??0):e,0)),g=ee(e=>e.sid),h=le(e=>e.hfCache),b=le(e=>e.localModels),x=le(e=>e.modelCacheDiagnostics),v=le(e=>e.runtimeStatus),y=le(e=>e.nodesRegistry),j=le(e=>e.installHfModel),k=le(e=>["nodes","runtime","hfCache","localModels","modelCache","capabilities"].some(t=>{let a=e.discoveryRequests[t].status;return"idle"===a||"loading"===a})),[N,S]=(0,Ee.useState)(null),[P,R]=(0,Ee.useState)(!1),[T,I]=(0,Ee.useState)(!1),[C,M]=(0,Ee.useState)(!1),[z,E]=(0,Ee.useState)(null),[O,V]=(0,Ee.useState)(null),[F,K]=(0,Ee.useState)(12),G=(0,Ee.useRef)(new Map),W=(0,Ee.useRef)(!1),H=(0,Ee.useRef)(!1),X=(0,Ee.useRef)(f),[Y,J]=(0,Ee.useState)(null),[Z,te]=(0,Ee.useState)(null),[ae,re]=(0,Ee.useState)({category:"recommended",query:"",modelType:"all",mode:"all",difficulty:"all",sort:"recommended"});(0,Ee.useEffect)(()=>{!e||!a||(re(e=>({...e,category:a})),V(null),r())},[r,a,e]),(0,Ee.useEffect)(()=>{e||(V(null),K(12))},[e]),(0,Ee.useEffect)(()=>{if(!e)return;let t=!1;return lt().then(e=>{t||(S(e),R(!1))}).catch(()=>{t||(S(null),R(!0))}),()=>{t=!0}},[e]),(0,Ee.useEffect)(()=>{if(!e||!function(e){return e.failed&&!e.attempted}({attempted:W.current,failed:P&&rt.hasRejected()}))return;W.current=!0;let t=!1,a=globalThis.setTimeout(()=>{lt().then(e=>{t||(S(e),R(!1))}).catch(e=>console.error(e))},1500);return()=>{t=!0,globalThis.clearTimeout(a),W.current=!1}},[P,e]),(0,Ee.useEffect)(()=>{if(!e)return;let t=!1;return ot().then(e=>{t||(p(e),I(!1))}).catch(e=>{t||I(!0),console.error(e)}),()=>{t=!0}},[e,p]),(0,Ee.useEffect)(()=>{if(!e||!function(e){return e.failed&&e.backendReady&&!e.discoveryRefreshing&&!e.attempted}({attempted:H.current,backendReady:!!g,discoveryRefreshing:k,failed:T&&st.hasRejected()}))return;H.current=!0;let t=!1,a=globalThis.setTimeout(()=>{ot().then(e=>{t||(p(e),I(!1))}).catch(e=>console.error(e))},1500);return()=>{t=!0,globalThis.clearTimeout(a),H.current=!1}},[T,k,e,p,g]),(0,Ee.useEffect)(()=>{if(!e||!f||f<=X.current)return;X.current=f;let t=!1,a=globalThis.setTimeout(()=>{ot(!0).then(e=>{t||p(e)}).catch(e=>console.error(e))},250);return()=>{t=!0,globalThis.clearTimeout(a)}},[f,e,p]);let se=(0,Ee.useMemo)(()=>({hfCache:h,localModels:b,modelCacheDiagnostics:x,runtimeStatus:v,nodesRegistry:y,modelIndexesRefreshing:k}),[h,b,x,k,y,v]),oe=(0,Ee.useMemo)(()=>({...ae,includeBlocked:!0}),[ae]),ie=(0,Ee.useCallback)(e=>u[e.id]??u[de(tt(e))],[u]),ce=(0,Ee.useCallback)(e=>$(ie(e)),[ie]),me=(0,Ee.useCallback)(e=>!ut(e,N),[N]),pe=(0,Ee.useMemo)(()=>{let e="experimental"===ae.category?Je:_,t="experimental"===ae.category?"all":ae.category,a=_e(e,c,{...oe,category:t},e=>ce(e)?.observedSeconds??null);return"experimental"===ae.category?a.filter(me):a},[oe,ae.category,c,me,ce]),fe=(0,Ee.useMemo)(()=>_e(Je,c,{...oe,category:"all",query:"",modelType:"all",mode:"all",difficulty:"all"},e=>ce(e)?.observedSeconds??null),[oe,c,ce]),be=(0,Ee.useMemo)(()=>fe.filter(me).length,[fe,me]),ve=(0,Ee.useMemo)(()=>fe.filter(e=>ut(e,N)).length,[fe,N]),we=(0,Ee.useMemo)(()=>{if("recommended"!==ae.category||0!==ae.query.trim().length||"all"!==ae.modelType||"all"!==ae.mode||"all"!==ae.difficulty)return pe;let e=pe.filter(e=>ut(e,N));return 0===e.length?pe:"recommended"===ae.sort?[...e].sort((e,t)=>gt(e.id)-gt(t.id)):e},[ae.category,ae.difficulty,ae.mode,ae.modelType,ae.query,ae.sort,pe,N]),ye=(0,Ee.useMemo)(()=>Je.find(e=>e.id===O)??null,[O]),je=(0,Ee.useMemo)(()=>new Map(Se.map(e=>"recommended"===e.id&&ve>0?[e.id,ve]:"experimental"===e.id?[e.id,be]:[e.id,_e(_,c,{...oe,category:e.id,query:"",modelType:"all",mode:"all",difficulty:"all"}).length])),[oe,be,c,ve]),ke=(0,Ee.useMemo)(()=>Se.filter(e=>"recommended"===e.id||"all"===e.id||(je.get(e.id)??0)>0),[je]);(0,Ee.useLayoutEffect)(()=>{e&&G.current.get(ae.category)?.scrollIntoView({block:"nearest",inline:"nearest"})},[ae.category,e,ke]),(0,Ee.useEffect)(()=>{K(12),Y&&(Y.scrollTop=0)},[ae.category,ae.difficulty,ae.mode,ae.modelType,ae.query,ae.sort,Y]);let Te=(0,Ee.useMemo)(()=>we.slice(0,F),[we,F]),Ie=Te.length{if(!e||!Ie||!Y||!Z||typeof IntersectionObserver>"u")return;let t=new IntersectionObserver(([e])=>{e?.isIntersecting&&(t.disconnect(),K(e=>Math.min(e+12,we.length)))},{root:Y,rootMargin:"320px 0px",threshold:.01});return t.observe(Z),()=>t.disconnect()},[Y,Ie,Z,e,we.length,Te.length]);let Ce=e=>d.needsReview(ne(e)),Me=e=>{d.request("create",ne(e),()=>(async e=>{M(!0);try{let a=await He(e);if(t(!1),a.graphError)return void A(`${e.label} opened, but graph creation needs attention: ${a.graphError}`,{variant:"warning",autoHideDuration:7e3});if(a.warnings.length>0)return void A(`${Ne(e)}: ${a.warnings.join(" ")}`,{variant:"warning",autoHideDuration:12e3});A(`${Ne(e)} template created`,{variant:"success",autoHideDuration:2600})}catch(e){if(ue(e))return;A(String(e),{variant:"error",autoHideDuration:7e3})}finally{M(!1)}})(e))},$e=d.pending?.policies??[],qe=()=>{l(!0),o("setup"),t(!1)},ze=()=>{n(!0)};return(0,Ye.jsxs)(Ye.Fragment,{children:[(0,Ye.jsx)(L,{open:e,onClose:()=>t(!1),testId:"template-browser-dialog",title:(0,Ye.jsxs)("span",{className:"inline-flex items-center gap-2",children:[(0,Ye.jsx)(s,{size:18,className:"text-hf-yellow"}),"Templates"]}),panelClassName:"!h-[80vh] !w-[82vw] !max-h-[80vh] !max-w-[82vw]",bodyClassName:"!h-[calc(80vh-49px)] !max-h-[calc(80vh-49px)] overflow-hidden p-0",children:(0,Ye.jsxs)("div",{className:"grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] lg:grid-cols-[190px_minmax(0,1fr)] lg:grid-rows-1",children:[(0,Ye.jsx)("aside",{className:"min-h-0 overflow-x-auto border-b border-modiff-border bg-modiff-bg p-2 lg:overflow-y-auto lg:border-b-0 lg:border-r lg:p-3",children:(0,Ye.jsx)("div",{className:"flex min-w-max gap-1 lg:grid lg:min-w-0",role:"group","aria-label":"Template categories",children:ke.map(e=>{let t=ae.category===e.id;return(0,Ye.jsxs)(B,{ref:t=>{t?G.current.set(e.id,t):G.current.delete(e.id)},tone:t?"primary":"ghost",size:"prominent",align:"left","aria-pressed":t,"data-testid":`template-browser-category-${e.id}`,onClick:()=>{re(t=>({...t,category:e.id})),V(null)},className:"shrink-0 !justify-between px-3 lg:w-full",children:[(0,Ye.jsx)("span",{className:"truncate",children:e.label}),(0,Ye.jsx)("span",{className:q("min-w-6 rounded-full px-1.5 py-0.5 text-center text-xs",t?"bg-modiff-selected-surface text-hf-yellow":"bg-modiff-panel text-modiff-subtle-text"),children:je.get(e.id)??0})]},e.id)})})}),(0,Ye.jsxs)("section",{className:"@container grid min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)] bg-modiff-surface",children:[(0,Ye.jsxs)("div",{className:"border-b border-modiff-border bg-modiff-panel p-3","data-testid":"template-browser-header",children:[(0,Ye.jsx)("h1",{className:"mb-2 text-xl font-bold tracking-tight text-modiff-text",children:"recommended"===ae.category?"Recommended templates":mt(ae.category)}),(0,Ye.jsxs)("div",{className:"flex flex-wrap items-end gap-2",children:[(0,Ye.jsx)(U,{"data-testid":"template-browser-search",value:ae.query,onChange:e=>re(t=>({...t,query:e.target.value})),onClear:ae.query?()=>re(e=>({...e,query:""})):void 0,placeholder:"Search templates, models, prompts, or techniques","aria-label":"Search templates",controlSize:"prominent",className:"min-w-52 flex-1"}),(0,Ye.jsx)(bt,{label:"Model",value:ae.modelType,onValueChange:e=>re(t=>({...t,modelType:e})),options:[{value:"all",label:"All models"},...w({currentModelType:c.modelType,includeWorkflowOnly:!0}).map(e=>({value:e.modelType,label:e.label}))]}),(0,Ye.jsx)(bt,{label:"Operation",value:ae.mode??"all",onValueChange:e=>re(t=>({...t,mode:e})),options:[{value:"all",label:"All operations"},...et.map(e=>({value:e,label:D[e]}))]}),(0,Ye.jsx)(bt,{label:"Level",value:ae.difficulty,onValueChange:e=>re(t=>({...t,difficulty:e})),options:Qe.map(e=>({value:e,label:it(e)}))}),(0,Ye.jsx)(bt,{label:"Sort",value:ae.sort,onValueChange:e=>re(t=>({...t,sort:e})),options:Ze})]})]}),(0,Ye.jsxs)("div",{className:q("grid h-full min-h-0",ye&&"@[1024px]:grid-cols-[minmax(0,1fr)_minmax(420px,28vw)]"),children:[(0,Ye.jsxs)("div",{ref:J,className:q("min-h-0 overflow-y-auto p-3",ye&&"hidden @[1024px]:block"),"data-testid":"template-browser-gallery-scroll",children:[(0,Ye.jsxs)("div",{className:"grid items-stretch gap-3 [grid-template-columns:repeat(auto-fill,minmax(280px,1fr))] max-[1400px]:[grid-template-columns:repeat(auto-fill,minmax(250px,1fr))]",children:[Te.map((e,a)=>(0,Ye.jsx)(xt,{active:m===e.id,disabled:C,featured:"recommended"===ae.category&&0===a&&we.length>2,manifest:N,readiness:Ke(e,{...se,form:tt(e),autoResourcePlan:ie(e)}),selected:O===e.id,template:e,runtimeEstimate:ce(e),requiresTermsReview:Ce(e),onCreate:()=>{Me(e)},onView:()=>V(e.id),onReadinessAction:a=>{"needs_model"===a.status?((e,a)=>{let r=a.modelInstallTargets[0]?.repoId??a.missingModelRepos[0];i({nodeId:null,fieldKey:null,focus:{modelType:e.modelType,repo:r,label:Q[e.modelType],source:"template"}}),t(!1)})(e,a):"needs_input"===a.status?(ze(),t(!1)):("needs_backend"===a.status||"needs_setup"===a.status)&&qe()}},e.id)),0===we.length&&(0,Ye.jsx)("div",{className:"col-span-full rounded-modiff-compact border border-modiff-border bg-modiff-bg p-4 text-sm text-modiff-subtle-text",children:"No templates match the current filters."})]}),Ie?(0,Ye.jsx)("div",{ref:te,"aria-hidden":"true",className:"h-px w-full","data-testid":"template-browser-load-sentinel"}):null,(0,Ye.jsxs)("span",{className:"sr-only",role:"status","aria-live":"polite",children:[Te.length," of ",we.length," templates loaded"]})]}),ye?(0,Ye.jsx)(vt,{disabled:C,installingRepo:z,manifest:N,readiness:Ke(ye,{...se,form:tt(ye),autoResourcePlan:ie(ye)}),runtimeEstimate:ce(ye),requiresTermsReview:Ce(ye),template:ye,onBack:()=>V(null),onCreate:()=>{Me(ye)},onInstallRepo:(e,t)=>{((e,t,a=!1)=>{d.request("install",ne(e),()=>(async(e,t=!1)=>{E(e);try{await j(e,g,{repair:t}),p(await ot(!0))}catch(e){console.error(e)}finally{E(null)}})(t,a))})(ye,e,t)},onOpenAssets:ze,onOpenModels:()=>{i({nodeId:null,fieldKey:null})},onOpenSetup:qe,onUseLowVram:()=>{let e=_.find(e=>"low_vram"===e.id);e&&(re(e=>({...e,category:"performance",query:""})),V(e.id))}}):null]})]})]})}),(0,Ye.jsx)(Re,{open:!!d.pending,policies:$e,action:d.pending?.action??"create",onCancel:d.cancel,onConfirm:d.confirm})]})}function bt({label:e,onValueChange:t,options:a,value:r}){let s=(0,Ee.useId)();return(0,Ye.jsx)(C,{className:q("Sort"===e?"w-36":"w-32","shrink-0"),htmlFor:s,label:e,children:(0,Ye.jsx)(ie,{id:s,"data-testid":`template-browser-${e.toLowerCase()}-filter`,size:"normal",value:r,onValueChange:t,options:a,optionsClassName:"!min-w-44"})})}function xt({active:e,disabled:t,featured:a,manifest:r,onCreate:s,onReadinessAction:l,onView:o,readiness:i,runtimeEstimate:n,requiresTermsReview:c,selected:p,template:f}){let{beforePath:g,mediaPath:h,thumbnailPath:b}=I(f,r),x=ne(f).length>0;return(0,Ye.jsxs)("article",{"data-testid":`template-browser-card-${f.id}`,"data-active-template":e?"true":"false","data-inspected":p?"true":"false",className:q("group relative flex h-full min-h-0 flex-col overflow-hidden rounded-modiff-panel border bg-modiff-bg shadow-modiff-node",p?"border-hf-yellow ring-1 ring-hf-yellow/40":e?"border-modiff-blue ring-1 ring-modiff-blue/30":"border-modiff-border"),children:[(0,Ye.jsx)(B,{tone:"ghost",disabled:t,"aria-label":`View details for ${Ne(f)}`,"aria-current":e?"true":void 0,"aria-pressed":p,onClick:o,onFocus:o,className:"absolute inset-0 z-0 !h-auto !w-full !rounded-modiff-panel !border-0 !bg-transparent !p-0",children:(0,Ye.jsx)("span",{className:"sr-only",children:"View template details"})}),(0,Ye.jsxs)("div",{className:"pointer-events-none relative z-10 overflow-hidden bg-modiff-panel",children:[(0,Ye.jsx)(yt,{afterPath:h,beforePath:g,mediaKind:f.outputKinds?.[0],slots:ke(f),thumbnailPath:b,variant:f.thumbnailVariant}),(0,Ye.jsxs)("div",{className:"pointer-events-none absolute inset-x-0 top-0 flex items-start justify-end gap-2 bg-gradient-to-b from-modiff-bg/80 to-transparent p-3",children:[x?(0,Ye.jsx)(be,{content:"Usage restrictions — review before use",children:e=>(0,Ye.jsx)("span",{...e,"aria-label":"Usage restrictions — review before use",className:"pointer-events-auto grid size-7 place-items-center rounded-full border border-hf-orange/70 bg-modiff-bg/85 text-hf-orange backdrop-blur","data-testid":`template-usage-warning-${f.id}`,role:"img",tabIndex:0,children:(0,Ye.jsx)(v,{"aria-hidden":"true",size:14})})}):null,(0,Ye.jsx)("span",{className:"rounded-full border border-modiff-overlay-text/10 bg-modiff-bg/80 px-2 py-1 text-xs font-semibold text-modiff-text backdrop-blur",children:nt(f)})]}),a?(0,Ye.jsx)("div",{className:"pointer-events-none absolute left-3 top-3 grid size-7 place-items-center rounded-full border border-hf-yellow/50 bg-modiff-bg/85 text-hf-yellow backdrop-blur",title:"Featured template",children:(0,Ye.jsx)(d,{size:13})}):null]}),(0,Ye.jsxs)("div",{className:"pointer-events-none relative z-10 flex min-h-0 flex-1 flex-col gap-3 p-4",children:[(0,Ye.jsxs)("div",{className:"min-w-0",children:[(0,Ye.jsxs)("div",{className:"mb-1 flex min-h-12 items-start justify-between gap-2",children:[(0,Ye.jsx)("h3",{className:"min-w-0 flex-1",children:(0,Ye.jsx)(B,{tone:"ghost",size:"compact",align:"left",disabled:t,"data-testid":`template-browser-use-${f.id}`,onClick:o,className:"pointer-events-auto !h-auto min-w-0 max-w-full !justify-start !rounded-none !p-0 text-base !font-bold !leading-6 text-modiff-text hover:bg-transparent",children:(0,Ye.jsx)("span",{className:"line-clamp-2",children:Ne(f)})})}),(0,Ye.jsx)(Nt,{readiness:i,testId:`template-card-readiness-${f.id}`,onAction:()=>l(i)})]}),(0,Ye.jsxs)("p",{className:"truncate text-sm font-semibold text-modiff-subtle-text",children:[Q[f.modelType]," · ",D[f.mode]]})]}),(0,Ye.jsx)("p",{className:"line-clamp-2 min-h-10 text-sm leading-5 text-modiff-subtle-text",children:f.recipeSummary??f.description}),(0,Ye.jsxs)("div",{className:"mt-auto grid min-w-0 gap-2 border-t border-modiff-border pt-3",children:[n?(0,Ye.jsxs)("div",{className:"inline-flex min-w-0 items-center gap-2 text-xs text-modiff-subtle-text","data-testid":`template-runtime-${f.id}`,title:n.title,children:[(0,Ye.jsx)(m,{size:13,className:"shrink-0 text-hf-yellow"}),(0,Ye.jsx)("span",{className:"truncate",children:n.label})]}):null,(0,Ye.jsx)(B,{tone:"primary",className:"pointer-events-auto w-full",icon:(0,Ye.jsx)(u,{size:14}),disabled:t,"data-testid":`template-browser-create-card-${f.id}`,onClick:e=>{e.stopPropagation(),s()},children:c?"Review terms":"Use template"})]})]})]})}function vt({disabled:e,installingRepo:r,manifest:s,onBack:l,onCreate:m,onInstallRepo:p,onOpenAssets:g,onOpenModels:h,onOpenSetup:b,onUseLowVram:x,readiness:w,runtimeEstimate:j,requiresTermsReview:k,template:N}){let _=y(N,s,{requireCardPreview:!1}),S=_??function(e,t){return t?.examples.find(t=>t.templateId===e.id&&["approved_reviewed","approved_exact"].includes(t.qualityReviewStatus)&&!(!t.beforePath||!t.beforeMediaHash))}(N,s),P=N.inputBindings?.filter(e=>"template"===e.origin).flatMap(e=>e.defaultAssets).find(e=>e.previewPath)?.previewPath,T=R(_?.thumbnailPath??_?.posterPath)??dt(N),I=R(S?.beforePath??P),C=R(_?.afterPath??_?.outputPath),M=function(e){let t=je(e),a=t.templateDefaultBindings.map(e=>({included:!0,key:e.id,label:e.label.replace(/^Reviewed\s+/i,"")})),r=t.missingLabels.map(e=>({included:!1,key:`missing:${e}`,label:e}));return a.length>0||r.length>0?{title:0===r.length?"Included inputs":"Required inputs",items:[...a,...r]}:{title:"Required inputs",items:ct(e).map(e=>({included:!1,key:`legacy:${e}`,label:e}))}}(N),$=k?"Review terms":"Use template",q=w.issues.some(e=>"open_assets"===e.action),z=w.issues.some(e=>"open_setup"===e.action),A=w.issues.some(e=>"apply_low_vram"===e.action);return(0,Ye.jsxs)("aside",{className:"min-h-0 overflow-y-auto bg-modiff-bg p-4 @[1024px]:border-l @[1024px]:border-modiff-border","data-testid":`template-detail-${N.id}`,children:[(0,Ye.jsx)("div",{className:"mb-3 flex items-center justify-between gap-2",children:(0,Ye.jsx)(B,{tone:"ghost",size:"dense",icon:(0,Ye.jsx)(c,{size:15}),onClick:l,children:"Templates"})}),(0,Ye.jsx)(yt,{afterPath:C,beforePath:I,detail:!0,mediaKind:N.outputKinds?.[0],slots:ke(N),thumbnailPath:T,variant:N.thumbnailVariant}),(0,Ye.jsxs)("div",{className:"mt-4 grid gap-4",children:[(0,Ye.jsxs)("div",{children:[(0,Ye.jsxs)("div",{className:"mb-2 inline-flex items-center gap-1.5 rounded-full border border-modiff-border bg-modiff-panel px-2.5 py-1 text-xs font-semibold text-hf-yellow",children:[(0,Ye.jsx)(i,{size:12}),D[N.mode]]}),(0,Ye.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,Ye.jsxs)("div",{className:"min-w-0",children:[(0,Ye.jsx)("h2",{className:"break-words text-xl font-bold tracking-tight text-modiff-text",children:Ne(N)}),(0,Ye.jsxs)("p",{className:"mt-1 text-sm font-semibold text-modiff-subtle-text",children:[Q[N.modelType]," · ",D[N.mode]]})]}),(0,Ye.jsx)(_t,{readiness:w})]}),(0,Ye.jsx)("p",{className:"mt-3 text-sm leading-6 text-modiff-subtle-text",children:N.userGoal})]}),(0,Ye.jsxs)("div",{className:"grid grid-cols-2 gap-2 text-xs",children:[j?(0,Ye.jsx)(wt,{label:"Local runtime",value:j.label}):null,(0,Ye.jsx)(wt,{label:"VRAM",value:N.vramEstimate??"TBD"}),(0,Ye.jsx)(wt,{label:"Output",value:nt(N)}),(0,Ye.jsx)(wt,{label:"Level",value:N.difficulty??"starter"})]}),(0,Ye.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[(0,Ye.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"What this creates"}),(0,Ye.jsx)("p",{className:"mt-1 text-xs leading-5 text-modiff-subtle-text",children:N.recipeSummary??N.description})]}),(0,Ye.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[(0,Ye.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:M.title}),(0,Ye.jsx)("div",{className:"mt-2 flex flex-wrap gap-1",children:M.items.map(e=>(0,Ye.jsxs)("span",{className:"inline-flex items-center gap-1 rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:[e.included?(0,Ye.jsx)(o,{"aria-hidden":"true",className:"text-modiff-green",size:12}):null,e.label]},e.key))})]}),(0,Ye.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[(0,Ye.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Default prompt"}),(0,Ye.jsx)("p",{className:"mt-2 whitespace-pre-wrap break-words text-xs leading-5 text-modiff-text",children:N.prompt}),N.negativePrompt?(0,Ye.jsxs)(Ye.Fragment,{children:[(0,Ye.jsx)("h4",{className:"mt-3 text-xs font-semibold uppercase text-modiff-subtle-text",children:"Negative prompt"}),(0,Ye.jsx)("p",{className:"mt-1 whitespace-pre-wrap break-words text-xs leading-5 text-modiff-subtle-text",children:N.negativePrompt})]}):null]}),(0,Ye.jsxs)("section",{className:"grid gap-2","data-testid":`template-readiness-${N.id}`,children:[(0,Ye.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,Ye.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Preflight"}),(0,Ye.jsx)(_t,{readiness:w})]}),0===w.issues.length?(0,Ye.jsx)(se,{tone:"success",title:"Ready now",meta:w.summary,children:"Create a graph tab, build the graph, and then run when you are ready."}):w.issues.map(e=>(0,Ye.jsx)(se,{tone:e.tone,title:e.title,meta:e.detail},e.id))]}),(0,Ye.jsxs)("div",{className:"flex flex-wrap gap-2",children:[w.modelInstallTargets.map(e=>(0,Ye.jsx)(B,{icon:(0,Ye.jsx)(a,{size:15}),disabled:r===e.repoId,"aria-label":`Install ${e.repoId}`,onClick:()=>p(e.repoId,e.repair),children:r===e.repoId?"Installing...":e.actionLabel??"Install"},e.repoId)),q?(0,Ye.jsx)(B,{icon:(0,Ye.jsx)(n,{size:15}),onClick:g,children:"Open assets"}):null,z?(0,Ye.jsx)(B,{icon:(0,Ye.jsx)(t,{size:15}),onClick:b,children:"Open setup"}):null,A?(0,Ye.jsx)(B,{icon:(0,Ye.jsx)(d,{size:15}),onClick:x,children:"Try Low VRAM"}):null,(0,Ye.jsx)(B,{icon:(0,Ye.jsx)(f,{size:15}),onClick:h,children:"Models"})]}),(0,Ye.jsx)("div",{className:"sticky bottom-0 -mx-4 bg-gradient-to-t from-modiff-bg via-modiff-bg to-transparent px-4 pb-1 pt-5",children:(0,Ye.jsx)(B,{tone:"primary",className:"h-10 w-full",icon:"planning"===w.status||"needs_backend"===w.status||"needs_setup"===w.status?(0,Ye.jsx)(v,{size:15}):(0,Ye.jsx)(u,{size:15}),disabled:e,"data-testid":`template-browser-create-${N.id}`,onClick:m,children:$})})]})]})}function wt({label:e,value:t}){return(0,Ye.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",children:[(0,Ye.jsx)("div",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:e}),(0,Ye.jsx)("div",{className:"mt-1 break-words text-xs font-semibold text-modiff-text",children:t})]})}function yt({afterPath:e,beforePath:t,detail:a=!1,mediaKind:r="image",slots:s,thumbnailPath:l}){if("image"===r&&t&&e)return(0,Ye.jsx)(jt,{afterPath:e,beforePath:t,detail:a});if(!e&&!t&&l)return(0,Ye.jsx)(kt,{slot:{id:"editorial-poster",label:"Template preview",kind:"poster",placeholder:"template poster pending"},path:l,detail:a,large:!0});let o=2===s.length&&s.some(e=>"compare-before"===e.kind);if(o&&"video"===r&&!a&&e)return(0,Ye.jsx)(kt,{slot:{id:"published-result",label:"Generated video preview",kind:"video",posterPath:l,placeholder:"video preview pending"},path:e,large:!0});if(o){let o=(e,t)=>{if(!e)return t;let a=e.split(/[?#]/,1)[0]?.toLowerCase()??"";return/\.(mp4|webm|mov|mkv)$/.test(a)?"video":/\.(png|jpe?g|webp|gif|avif)$/.test(a)?"image":t};return(0,Ye.jsxs)("div",{className:"grid aspect-video grid-cols-2 overflow-hidden rounded-modiff-panel border border-modiff-border bg-modiff-border",children:[(0,Ye.jsx)(kt,{slot:"video"===r?{...s[0],kind:o(t,"image")}:s[0],path:t,detail:a}),(0,Ye.jsx)(kt,{slot:"video"===r?{...s[1],kind:o(e??l,e?"video":"image"),posterPath:l}:s[1],path:e??l,detail:a})]})}if(s.length>1&&(e||l)){let t="video"===r?"video":"audio"===r?"audio":"image";return(0,Ye.jsx)(kt,{slot:{id:"published-result",label:"Generated result",kind:t,posterPath:l,placeholder:`${t} preview pending`},path:"image"===t?l??e:e??l,detail:a,large:!0})}if(s.length>1)return(0,Ye.jsx)("div",{className:"grid aspect-video grid-cols-2 gap-px overflow-hidden rounded-modiff-panel border border-modiff-border bg-modiff-border",children:s.slice(0,4).map((e,t)=>(0,Ye.jsx)(kt,{slot:e,path:e.path??(0===t?l:void 0),compact:!0,detail:a},e.id))});let i=s[0],n="video"===r||"audio"===r;return(0,Ye.jsx)(kt,{slot:"video"===r?{...i,kind:"video",posterPath:i.posterPath??l}:"audio"===r?{...i,kind:"audio"}:i,path:n?e??t??i.path:l??i.path,detail:a,large:!0})}function jt({afterPath:e,beforePath:t,detail:a}){let[r,s]=(0,Ee.useState)(50),[l,o]=(0,Ee.useState)(!1),i=(0,Ee.useRef)(null),n=(e,t)=>{let a=e.getBoundingClientRect();if(a.width<=0)return;let r=(t-a.left)/a.width*100;s(Math.max(0,Math.min(100,r)))};return(0,Ye.jsxs)("div",{className:"relative aspect-video overflow-hidden rounded-modiff-panel border border-modiff-border bg-modiff-panel",children:[(0,Ye.jsx)(Y,{ref:i,"aria-label":"Before and after comparison",testId:"template-ab-comparison",imageFrom:t,imageTo:e,sliderPosition:r,onSliderPositionChange:s,onError:e=>{e.currentTarget.src="/assets/modiff-icon-256.png"},onMouseDown:e=>{o(!0),n(e.currentTarget,e.clientX)},onMouseEnter:e=>{n(e.currentTarget,e.clientX)},onMouseMove:e=>{n(e.currentTarget,e.clientX)},onPointerMove:e=>{"mouse"===e.pointerType&&n(e.currentTarget,e.clientX)},onMouseUp:()=>o(!1),onTouchStart:e=>{o(!0),n(e.currentTarget,e.touches[0]?.clientX??0)},onTouchMove:e=>{l&&n(e.currentTarget,e.touches[0]?.clientX??0)},onTouchEnd:()=>o(!1)}),a?(0,Ye.jsx)("span",{className:"pointer-events-none absolute left-1/2 top-2 -translate-x-1/2 rounded-full bg-modiff-bg/80 px-2 py-1 text-xs font-semibold text-modiff-text backdrop-blur",children:"Hover or drag to compare"}):null]})}function kt({compact:e=!1,detail:t=!1,large:a=!1,path:r,slot:s}){let o=(0,Ee.useRef)(null),i=(0,Ee.useRef)(null),[d,c]=(0,Ee.useState)(null),[m,p]=(0,Ee.useState)(t),[f,h]=(0,Ee.useState)(!1),[b,v]=(0,Ee.useState)(!1),[w,y]=(0,Ee.useState)(!1),j=r&&d!==r?r:void 0,k="video"===s.kind,N="audio"===s.kind,_="json"===s.kind,P=k||"gif"===s.kind?g:N?x:_?l:n,R=t||m&&(!f||b),T=!t&&m&&(!f||b);return(0,Ee.useEffect)(()=>{if(t||!k||!j)return;let e=o.current;if(!e||typeof IntersectionObserver>"u")return void p(!0);let a=new IntersectionObserver(([e])=>p(!!e?.isIntersecting),{rootMargin:"160px 0px",threshold:.01});return a.observe(e),()=>a.disconnect()},[t,k,j]),(0,Ee.useEffect)(()=>{if(t||!k)return;let e=window.matchMedia("(prefers-reduced-motion: reduce)"),a=navigator.connection,r=()=>h(e.matches||!0===a?.saveData);return r(),e.addEventListener("change",r),a?.addEventListener?.("change",r),()=>{e.removeEventListener("change",r),a?.removeEventListener?.("change",r)}},[t,k]),(0,Ee.useEffect)(()=>{if(!t&&i.current){if(!R||!T)return void i.current.pause();i.current.play().catch(()=>{})}},[R,T,t]),(0,Ee.useEffect)(()=>{c(null),v(!1),y(!1)},[r]),(0,Ye.jsxs)("div",{ref:o,className:q("relative grid place-items-center overflow-hidden bg-gradient-to-br from-modiff-panel via-modiff-bg to-modiff-surface",a&&"rounded-modiff-panel border border-modiff-border",a&&"aspect-video"),children:[j&&k?(0,Ye.jsxs)(Ye.Fragment,{children:[s.posterPath?(0,Ye.jsx)("img",{src:s.posterPath,alt:"","aria-hidden":"true",loading:"eager",decoding:"async",className:"absolute inset-0 h-full w-full object-cover"}):null,(0,Ye.jsx)("video",{ref:i,src:R?j:void 0,poster:s.posterPath,muted:!t,loop:!t,controls:t,autoPlay:T,playsInline:!0,preload:t||R?"metadata":"none","aria-label":s.label,"data-testid":t?"template-detail-video":"template-card-video","data-preview-attached":R?"true":"false",className:q("relative h-full w-full object-cover transition-opacity duration-150",w||!s.posterPath?"opacity-100":"opacity-0"),onError:()=>c(j),onLoadedData:()=>y(!0),onPlaying:()=>y(!0),onCanPlay:()=>{T&&i.current?.play().catch(()=>{})}})]}):k&&s.posterPath?(0,Ye.jsx)("img",{src:s.posterPath,alt:s.label,loading:"lazy",decoding:"async",className:"h-full w-full object-cover"}):N?(0,Ye.jsxs)("div",{className:"grid h-full w-full content-center gap-4 bg-gradient-to-br from-hf-yellow/15 via-modiff-panel to-modiff-bg p-4",children:[(0,Ye.jsxs)("div",{className:"flex items-center gap-3",children:[(0,Ye.jsx)("div",{className:"grid size-10 shrink-0 place-items-center rounded-full bg-hf-yellow text-modiff-on-accent",children:(0,Ye.jsx)(x,{size:18})}),(0,Ye.jsx)("div",{className:"flex h-16 flex-1 items-center justify-center gap-1 overflow-hidden",children:pt.map((e,t)=>(0,Ye.jsx)("span",{className:q("w-1 rounded-full bg-hf-yellow/80",e)},`${e}-${t}`))})]}),j?(0,Ye.jsx)("audio",{src:j,controls:!0,preload:t?"metadata":"none",className:"pointer-events-auto h-9 w-full","aria-label":s.label,onError:()=>c(j)}):(0,Ye.jsx)("p",{className:"text-center text-xs font-semibold text-modiff-subtle-text",children:"No preview yet"})]}):j&&!_?a?t?(0,Ye.jsxs)(Ye.Fragment,{children:[(0,Ye.jsx)("img",{src:j,alt:"","aria-hidden":"true",loading:"eager",decoding:"async",className:"absolute inset-0 h-full w-full scale-110 object-cover opacity-30 blur-xl",onError:()=>c(j)}),(0,Ye.jsx)("img",{src:j,alt:s.label,loading:"eager",decoding:"async",className:"relative h-full w-full object-contain p-1.5",onError:()=>c(j)})]}):(0,Ye.jsx)("img",{src:j,alt:s.label,loading:"eager",decoding:"async",className:"h-full w-full object-cover",onError:()=>c(j)}):(0,Ye.jsx)("img",{src:j,alt:s.label,loading:"lazy",decoding:"async",className:"h-full w-full object-cover",onError:()=>c(j)}):(0,Ye.jsx)("div",{className:"grid h-full min-h-28 w-full place-items-center p-3 text-center",children:(0,Ye.jsxs)("div",{children:[(0,Ye.jsx)("div",{className:"mx-auto mb-2 grid size-9 place-items-center rounded-full border border-modiff-border bg-modiff-bg/70 text-modiff-subtle-text",children:(0,Ye.jsx)(P,{size:e?16:19})}),(0,Ye.jsx)("p",{className:"text-xs font-semibold text-modiff-subtle-text",children:r?"Preview unavailable":"No preview yet"}),e?null:(0,Ye.jsx)("p",{className:"mt-1 text-xs text-modiff-subtle-text",children:r&&d===r?"The published media could not be decoded":s.placeholder})]})}),!N&&t?(0,Ye.jsx)("span",{className:"absolute left-2 top-2 rounded-full border border-modiff-overlay-text/10 bg-modiff-bg/75 px-2 py-1 text-xs font-semibold text-modiff-overlay-text backdrop-blur",children:s.label}):null,k&&j&&!t&&f&&!b?(0,Ye.jsx)(S,{label:"Play video preview",onClick:e=>{e.stopPropagation(),v(!0)},className:"pointer-events-auto absolute inset-0 m-auto !size-10 rounded-full border border-modiff-overlay-text/20 bg-modiff-bg/80 text-modiff-overlay-text shadow-modiff-node backdrop-blur hover:border-hf-yellow hover:text-hf-yellow",children:(0,Ye.jsx)(u,{size:17,fill:"currentColor"})}):null]})}function Nt({onAction:e,readiness:t,testId:a}){let s=["needs_input","needs_model","needs_backend","needs_setup"].includes(t.status),l="ready"===t.status?(0,Ye.jsx)(o,{size:18}):"needs_backend"===t.status?(0,Ye.jsx)(b,{size:18}):"preparing"===t.status||"planning"===t.status?(0,Ye.jsx)(r,{size:18}):(0,Ye.jsx)(v,{size:18}),i=q("pointer-events-auto relative grid size-8 shrink-0 place-items-center rounded-full border bg-modiff-panel/90 transition","success"===t.tone&&"border-modiff-green/70 text-modiff-green","warning"===t.tone&&"border-hf-orange/70 text-hf-orange","error"===t.tone&&"border-modiff-red/70 text-modiff-red","info"===t.tone&&"border-modiff-blue/70 text-modiff-blue","default"===t.tone&&"border-modiff-border text-modiff-subtle-text",s&&"hover:scale-105 hover:bg-modiff-surface-hover"),n=`${t.label}: ${t.summary}`;return s?(0,Ye.jsx)(S,{label:n,className:i,"data-testid":a,onClick:e,children:l}):(0,Ye.jsx)(be,{content:n,children:e=>(0,Ye.jsx)("span",{...e,className:i,"data-testid":a,"aria-label":n,role:"img",tabIndex:0,children:l})})}function _t({readiness:e}){return(0,Ye.jsxs)("span",{className:q("inline-flex shrink-0 items-center gap-1 rounded-modiff-compact border px-1.5 py-0.5 text-xs font-semibold","success"===e.tone&&"border-modiff-green/70 text-modiff-green","warning"===e.tone&&"border-hf-orange/70 text-hf-orange","error"===e.tone&&"border-modiff-red/70 text-modiff-red","info"===e.tone&&"border-modiff-blue/70 text-modiff-blue","default"===e.tone&&"border-modiff-border text-modiff-subtle-text"),children:["success"===e.tone?(0,Ye.jsx)(o,{size:12}):"error"===e.tone?(0,Ye.jsx)(v,{size:12}):(0,Ye.jsx)(r,{size:12}),e.label]})}export{ht as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{D as t,H as a,It as r,K as s,Ln as l,M as o,Mn as i,Mt as n,Pt as d,Qt as c,R as m,_n as u,bn as p,cr as f,dr as g,jt as h,st as b,wn as x,yt as v}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$ as w,$i as y,An as j,Bi as k,Cr as N,Di as _,Ei as S,Fi as T,Ga as P,Gi as R,Hi as C,In as I,Ji as M,Jn as $,La as q,Li as z,Mo as A,Ms as E,Ni as L,Nr as B,Oi as O,On as V,Oo as D,Pi as F,Qi as K,Ra as U,Ro as W,Sa as G,Si as H,Ti as X,Ui as J,Vi as Q,Vr as Y,Xa as Z,Xi as ee,Yi as te,an as ae,bn as re,da as se,dr as le,fn as oe,ga as ie,in as ne,ji as de,ma as ce,nr as me,pa as ue,qa as pe,qi as fe,rn as ge,t as he,tr as be,u as xe,wi as ve,wn as we,yn as ye,za as je,zr as ke}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{r as Ne,t as _e}from"./presetDiff.js?v=cc046c26d6fe7aa4";import{a as Se,i as Te,n as Pe,t as Re}from"./templateBrowser.js?v=cc046c26d6fe7aa4";import{n as Ce,t as Ie}from"./TemplateUsageTermsDialog.js?v=cc046c26d6fe7aa4";import{a as Me,i as $e,n as qe,o as ze,r as Ae,t as Ee}from"./controlledWorkflows.js?v=cc046c26d6fe7aa4";var Le=e(g(),1);function Be(e){return"install_model"===e?"install_model":"open_models"===e?"open_model_manager":"open_assets"===e||"add_input"===e?"select_image":"open_setup"===e?"open_setup":"apply_low_vram"===e?"apply_low_vram_preset":void 0}function Oe(e){return{id:`template:${e.id}`,category:e.category,severity:"error"===e.tone?"error":"warning"===e.tone?"warning":"success"===e.tone?"success":"info",repoId:e.repoId,message:e.title,details:e.detail,blocking:e.blocking,action:Be(e.action)}}function Ve(e){return Object.keys(e).length>0}function De(e){return"hub"===e?.source&&e.value.trim().length>0}function Fe(e){let[t,a]=e.value.split("/");return t&&a?`${t}/${a}`:e.value}function Ke(e){let t=[],a=(a,r,s)=>{De(s)&&t.push({id:a,label:r,repo:Fe(s),kind:"adapter",requiredForModes:[e.mode],description:`${r} pinned by ${e.label}.`})};a(`${e.id}-lora`,"LoRA adapter",e.workflowBlockSettings?.lora?.model),a(`${e.id}-lora-base`,"LoRA base model",e.workflowBlockSettings?.lora?.baseModel);for(let[t,r]of(e.workflowBlockSettings?.lora?.additionalAdapters??[]).entries())a(`${e.id}-lora-${t+2}`,`LoRA adapter ${t+2}`,r.model),a(`${e.id}-lora-${t+2}-base`,`LoRA base model ${t+2}`,r.baseModel);return a(`${e.id}-upscaler`,"Upscaler model",e.workflowBlockSettings?.upscaler?.model),t}function Ue(e){let t=U[e.modelType];return[...!1===t.artifactInstallRequired?[]:[{id:`${e.modelType}-base`,label:t.label,repo:t.defaultRepo,kind:"base",requiredForModes:[e.mode],description:`Base model for ${t.label}.`}],...Z(t,e.mode),...Ke(e)]}function We(e,t){let a=U[e.modelType],r=L(t.autoResourcePlan,t.form),s=z(t.autoResourcePlan,t.form),l=T(t.autoResourcePlan,t.form),o=!0===t.modelIndexesRefreshing,i="planning"===e.readinessPolicy||"blocked"===e.difficulty||"blocked"===e.example?.status,n=!t.runtimeStatus&&!Ve(t.nodesRegistry),d=function(e,t){let a=e.requiredBackendCapabilities??[];return 0===a.length?[]:a.filter(e=>{let a=he(e);return!a||Ve(t)&&!a.every(e=>t[e])})}(e,t.nodesRegistry),c=function(e,t){let a=me(e).missingLabels;return t?(t.loraAdapter&&!De(e.workflowBlockSettings?.lora?.model)&&a.push("LoRA adapter"),t.upscalerModel&&!De(e.workflowBlockSettings?.upscaler?.model)&&a.push("Upscaler model"),Array.from(new Set(a))):a}(e,e.inputRequirements),m=o?[]:[...t.autoResourcePlan?Z(a,e.mode):Ue(e),...t.autoResourcePlan?Ke(e):[]].filter(e=>!E(e.repo,t.hfCache,t.localModels,t.modelCacheDiagnostics).runnable).map(e=>e.repo),u=[],p=[];i&&u.push({id:"planning",category:"graph",blocking:!0,tone:"info",title:"Planning template",detail:e.example?.blockReason??"This recipe can be opened and inspected, but MoDiff should not present it as runnable yet.",action:"open_planning"}),o&&u.push({id:"model-index-refresh",category:"model",blocking:!0,tone:"info",title:"Checking installed models",detail:"Refreshing the local model index before reporting template readiness."}),!o&&"checking"===l.state&&u.push({id:"auto-plan-checking",category:"environment",blocking:!0,tone:"info",title:l.summary,detail:l.detail}),n&&u.push({id:"backend:connection",category:"backend",blocking:!0,tone:"error",title:"Backend connection needed",detail:"Connect to the MoDiff backend before checking installed models.",action:"open_setup"}),d.forEach(e=>{u.push({id:`backend:${e}`,category:"package",blocking:!0,tone:"error",title:"Backend contract needed",detail:e,action:"open_setup"})}),m.forEach(e=>{p.push({repoId:e,actionLabel:"Install"}),u.push({id:`model:${e}`,category:"model",blocking:!0,tone:"warning",title:"Model package missing",detail:e,action:"install_model",repoId:e,actionLabel:"Install"})}),o||r||!s?!o&&!r&&t.autoResourcePlan&&!s&&u.push({id:"auto-plan-blocked",category:"environment"===t.autoResourcePlan.issue?.category?"environment":"package"===t.autoResourcePlan.issue?.category?"package":"device"===t.autoResourcePlan.issue?.category?"hardware_fit":"environment",blocking:!0,tone:"error"===l.severity?"error":"warning"===l.severity?"warning":"info",title:l.summary,detail:l.detail,action:"expert_only"===l.state?"open_planning":"open_setup"}):(p.push({repoId:s.repo,repair:s.repair,actionLabel:s.actionLabel}),u.push({id:`auto-model:${s.repo}`,category:s.repair?"model_integrity":"model",blocking:!0,tone:s.repair?"error":"warning",title:s.repair?"Model package repair required":"Auto model package missing",detail:`${s.actionLabel??"Install Auto artifact"}: ${s.repo}`,action:"install_model",repoId:s.repo,repair:s.repair,actionLabel:s.actionLabel})),c.forEach(e=>{u.push({id:`input:${e}`,category:"asset",blocking:!0,tone:"warning",title:`${e} required`,detail:"Add the required asset in Studio after creating this workflow, or open the Assets library to reuse an existing output.",action:"open_assets"})});let f=le(u.map(Oe),{preparing:o||"checking"===l.state}),g=f.primaryIssue?.category,h=i||u.some(e=>"open_planning"===e.action)?"planning":"preparing"===f.state?"preparing":"blocked"===f.state?"backend"===g||"package"===g?"needs_backend":"model"===g||"model_integrity"===g?"needs_model":"asset"===g||"user_input"===g?"needs_input":"needs_setup":"ready",b=function(e){return"ready"===e?{label:"Ready now",tone:"success"}:"needs_input"===e?{label:"Needs input",tone:"warning"}:"needs_model"===e?{label:"Needs model",tone:"warning"}:"needs_backend"===e?{label:"Backend blocked",tone:"error"}:"needs_setup"===e?{label:"Needs setup",tone:"warning"}:"preparing"===e?{label:"Checking models",tone:"info"}:{label:"Planning only",tone:"info"}}(h),x="ready"===h?`${q[e.modelType]} Auto plan is ready, hardware looks usable, and no required inputs are missing.`:u[0]?.detail??"Review the required setup before running this recipe.";return{status:h,label:b.label,tone:b.tone,summary:x,compatibility:l,issues:u,missingModelRepos:Array.from(new Set(m)),modelInstallTargets:Array.from(new Map(p.map(e=>[e.repoId,e])).values()),missingInputs:c,missingBackendCapabilities:d,decision:f}}async function Ge(e,t,a,r={}){"lora"!==e?"upscaler"!==e?"video_sequence"!==e?"quality_video_sequence"!==e?"soundtrack"!==e?await qe(t,a.workflowBlockSettings?.lyricVideo,r):await $e(t,a.workflowBlockSettings?.soundtrack,r):await Ae(t,a.workflowBlockSettings?.qualityVideoSequence,r):await ze(t,a.workflowBlockSettings?.videoSequence,r):await Me(t,a.workflowBlockSettings?.upscaler,r):await Ee(t,a.workflowBlockSettings?.lora,r)}function He(){return typeof window>"u"||"function"!=typeof window.requestAnimationFrame?Promise.resolve():new Promise(e=>{window.requestAnimationFrame(()=>{window.requestAnimationFrame(()=>e())})})}async function Xe(e){let t=de.getState().createWorkflowTab(e.label,void 0,"template",e.id);de.getState().setLauncherDismissed(!0);let a=S();de.getState().setCanvasTransition({type:"template_graph_building",workflowTabId:t,templateId:e.id,startedAt:Date.now()});let r,s=de.getState().form,l=s,o=!1,i=[];try{if("success"!==H.getState().discoveryRequests.capabilities.status){await H.getState().fetchStudioModelCapabilities(),X(a);let e=H.getState().discoveryRequests.capabilities;if("success"!==e.status)throw Error(e.error||"Model capabilities are not ready. Retry opening the template.")}let t=await be(e);X(a),de.getState().applyTemplate(e,t),ve(a),de.getState().saveActiveWorkflowTab(!0),l=de.getState().form,await He(),X(a);let r=await B(l,a);if(X(a),"auto"===l.resourceMode){let e=F(l),t=de.getState().autoResourcePlans[e]??await k(l);if(X(a),t.error||de.getState().setAutoResourcePlans({[e]:t}),de.getState().setAutoResourcePlan(t),L(t,l)){let e=_(t,l,de.getState().graphBinding);if(e)de.getState().setLastError(e);else{let e=C(R(t,l),l);de.getState().applyAutoResourcePlan(t,e),ve(a),l=de.getState().form,r=await B(l,a),X(a)}}}o=!0,i=r.warnings;for(let t of e.workflowBlocks??[])try{await Ge(t,l,e,{graphPrepared:!0,workflowContext:a}),X(a)}catch(a){if(O(a))throw a;let r=a instanceof Error?a.message:String(a);i.push(`${e.label} could not add the ${t} block: ${r}`)}let s=await ke(15e3,a);if(X(a),!s)throw Error(`${e.label} graph preparation timed out. No incomplete graph was kept; try again.`);await W(()=>Y.getState().nodes,500),X(a),await Y.getState().arrangeGraph({history:!1}),X(a),await He(),X(a),de.getState().saveActiveWorkflowTab(!0)}catch(e){if(O(e))throw e;X(a),r=e instanceof Error?e.message:String(e),Y.getState().replaceGraph({nodes:[],edges:[],viewport:{x:0,y:0,zoom:1}}),de.getState().setGraphBinding(null),de.getState().setGraphFinalization(null),de.getState().saveActiveWorkflowTab(!0)}finally{let a=de.getState().canvasTransition;a?.workflowTabId===t&&a.templateId===e.id&&de.getState().setCanvasTransition(null)}let n=Object.keys(ee(e)),d=_e(s,l,n);return Ne(`${e.label} recipe applied`,d,de.getState().graphBinding?.managedNodeIds??[]),G.getState().setRightPanelOpen(!0),G.getState().setRightPanelTab("studio"),{graphCreated:o,graphError:r,warnings:i}}function Je(e){let t=null,a=!1;return{hasRejected:()=>a,load:(r=!1)=>{if(t&&!r)return t;a=!1;let s=e();return t=s,s.then(()=>{t===s&&(a=!1)},()=>{t===s&&(t=null,a=!0)}),s}}}var Qe=f();function Ye({templates:e=!1}){let t=de(e=>e.activeWorkflowTabId),a=[...de(e=>e.workflowTabs)].filter(e=>e.id!==t).sort((e,t)=>t.updatedAt-e.updatedAt).slice(0,6),r=()=>w({revealWorkspace:!1});return(0,Qe.jsxs)("div",{className:"space-y-2",children:[(0,Qe.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Qe.jsx)(ge,{onClick:()=>w(),"data-testid":"launcher-mode-advanced_workflow",children:"Empty workflow"}),(0,Qe.jsx)(ge,{onClick:()=>{r(),G.getState().setWorkflowLibraryView("saved"),G.getState().setLeftPanelTabIndex(4),G.getState().setLeftPanelOpen(!0)},children:"Open workflow"}),e?(0,Qe.jsx)(ge,{onClick:()=>{r(),G.getState().setTemplateBrowserOpen(!0)},children:"Browse templates"}):null]}),a.length?(0,Qe.jsxs)("section",{"aria-label":"Recent workflows",className:"space-y-1",children:[(0,Qe.jsx)("h3",{className:"text-xs font-semibold text-modiff-subtle-text",children:"Recent workflows"}),(0,Qe.jsx)("div",{className:"flex gap-2 overflow-x-auto pb-1",children:a.map(e=>(0,Qe.jsx)(ge,{title:e.title,className:"max-w-56 shrink-0",onClick:()=>{r(),de.getState().switchWorkflowTab(e.id)},children:(0,Qe.jsx)("span",{className:"truncate",children:e.title})},e.id))})]}):null]})}var Ze=["all","starter","intermediate","advanced","blocked"],et=[...ue,...se],tt=[{value:"recommended",label:"Recommended"},{value:"task",label:"Task"},{value:"model",label:"Model"},{value:"runtime",label:"Runtime"}],at=Array.from(new Set(ue.map(e=>e.mode))).sort((e,t)=>je[e].localeCompare(je[t]));function rt(e){let t=ee(e);return ie({...pe(e.mode,e.modelType),...ce(e.presetId)?.values??{},...t,mode:e.mode,modelType:e.modelType,prompt:e.prompt,negativePrompt:e.negativePrompt??t.negativePrompt})}var st=ue.map(e=>{let t=rt(e);return{form:t,planKey:F(t),templateId:e.id}}),lt=Je(async()=>D(fe,{credentials:"omit",timeoutMs:12e4,parse:K})),ot=Je(async()=>{let e=st.map(e=>e.templateId),t=await Q(st.map(e=>e.form),e),a={};return t.forEach((e,t)=>{let r=st[t];r&&(a[r.templateId]=e,a[r.planKey]??=e)}),a});function it(e=!1){return lt.load(e)}function nt(e=!1){return ot.load(e)}function dt(e){return"all"===e?"All levels":e.charAt(0).toUpperCase()+e.slice(1)}function ct(e){return e.outputKinds?.join(", ")||e.example?.mediaType||"image"}function mt(e){return y(e.example?.thumbnailPath??e.example?.outputPath)}function ut(e){let t=e.inputRequirements;if(!t)return["Prompt only"];let a="hub"===e.workflowBlockSettings?.lora?.model.source,r="hub"===e.workflowBlockSettings?.upscaler?.model.source,s=[];t.sourceImage&&s.push("source image"),t.referenceImages&&s.push(`${t.referenceImages} reference image${1===t.referenceImages?"":"s"}`),t.controlImage&&s.push("control image"),t.maskImage&&s.push("mask image"),t.sourceVideo&&s.push("source video"),t.maskVideo&&s.push("mask video"),t.controlVideo&&s.push("control video"),t.loraAdapter&&!a&&s.push("LoRA adapter"),t.upscalerModel&&!r&&s.push("upscaler model"),t.sampleAssets?.length&&s.push(...t.sampleAssets.filter(e=>!(a&&/lora adapter/i.test(e)||r&&/upscaler|real-esrgan/i.test(e))));let l=s.filter((e,t)=>s.findIndex(t=>t.toLocaleLowerCase()===e.toLocaleLowerCase())===t);return l.length>0?l:["Prompt only"]}function pt(e){return Re.find(t=>t.id===e)?.label??"Templates"}function ft(e,t){if(t){let a=M(e,t);return!(!a||"video"===a.mediaType&&(!a.cardPreviewPath||!a.cardPreviewSha256))}let a=e.verificationStatus??e.example?.status;return"video"!==e.example?.mediaType&&!e.outputKinds?.includes("video")&&!("reviewed"!==a&&"exact"!==a||!mt(e)&&!function(e){return y(e.example?.outputPath)}(e))}var gt=["h-3","h-7","h-5","h-10","h-6","h-12","h-8","h-14","h-9","h-5","h-11","h-7","h-12","h-8","h-4","h-9"],ht=["qwen_low_vram_product_concept","qwen_poster_logo_text","qwen_text_rendering","qwen_product_mockup","qwen_low_vram_poster_layout","qwen_low_vram_text_rendering","z_image_product_mockup","z_image_poster","z_image_cinematic_contact_sheet","z_image_quick_concept","wan_vace_cinematic_text_to_video","low_vram","high_quality"];function bt(e){let t=ht.indexOf(e);return-1===t?ht.length:t}function xt({entry:e=!1}){let t=G(e=>e.templateBrowserOpen),a=G(e=>e.setTemplateBrowserOpen),r=e||t,s=t=>{!t&&e&&w({revealWorkspace:!1}),a(t)},l=G(e=>e.templateBrowserInitialCategory),i=G(e=>e.clearTemplateBrowserInitialCategory),n=G(e=>e.setRightPanelOpen),d=G(e=>e.setRightPanelTab),c=G(e=>e.setModelManagerOpener),m=G(e=>e.setGalleryLibraryOpen),u=Ce(),p=de(e=>e.form),f=de(e=>e.activeTemplateId),g=de(e=>e.autoResourcePlans),h=de(e=>e.setAutoResourcePlans),b=N(e=>e.sessionRuns.reduce((e,t)=>"completed"===t.status?Math.max(e,t.completedAtMs??0):e,0)),x=$(e=>e.sid),v=H(e=>e.hfCache),y=H(e=>e.localModels),j=H(e=>e.modelCacheDiagnostics),k=H(e=>e.runtimeStatus),_=H(e=>e.nodesRegistry),S=H(e=>e.installHfModel),T=H(e=>["nodes","runtime","hfCache","localModels","modelCache","capabilities"].some(t=>{let a=e.discoveryRequests[t].status;return"idle"===a||"loading"===a})),R=(0,Le.useId)(),[C,M]=(0,Le.useState)(!1),[z,E]=(0,Le.useState)(null),[L,B]=(0,Le.useState)(!1),[V,D]=(0,Le.useState)(!1),[K,U]=(0,Le.useState)(!1),[W,X]=(0,Le.useState)(null),[Q,Y]=(0,Le.useState)(null),[Z,ee]=(0,Le.useState)(12),te=(0,Le.useRef)(new Map),ae=(0,Le.useRef)(!1),re=(0,Le.useRef)(!1),se=(0,Le.useRef)(b),[le,oe]=(0,Le.useState)(null),[ie,ce]=(0,Le.useState)(null),[me,pe]=(0,Le.useState)({category:"recommended",query:"",modelType:"all",mode:"all",difficulty:"all",sort:"recommended"}),fe=["all"!==me.modelType,"all"!==me.mode,"all"!==me.difficulty,"recommended"!==me.sort].filter(Boolean).length;(0,Le.useEffect)(()=>{!r||!l||(pe(e=>({...e,category:l})),Y(null),i())},[i,l,r]),(0,Le.useEffect)(()=>{r||(Y(null),ee(12))},[r]),(0,Le.useEffect)(()=>{if(!r)return;let e=!1;return it().then(t=>{e||(E(t),B(!1))}).catch(()=>{e||(E(null),B(!0))}),()=>{e=!0}},[r]),(0,Le.useEffect)(()=>{if(!r||!function(e){return e.failed&&!e.attempted}({attempted:ae.current,failed:L&<.hasRejected()}))return;ae.current=!0;let e=!1,t=globalThis.setTimeout(()=>{it().then(t=>{e||(E(t),B(!1))}).catch(e=>console.error(e))},1500);return()=>{e=!0,globalThis.clearTimeout(t),ae.current=!1}},[L,r]),(0,Le.useEffect)(()=>{if(!r)return;let e=!1;return nt().then(t=>{e||(h(t),D(!1))}).catch(t=>{e||D(!0),console.error(t)}),()=>{e=!0}},[r,h]),(0,Le.useEffect)(()=>{if(!r||!function(e){return e.failed&&e.backendReady&&!e.discoveryRefreshing&&!e.attempted}({attempted:re.current,backendReady:!!x,discoveryRefreshing:T,failed:V&&ot.hasRejected()}))return;re.current=!0;let e=!1,t=globalThis.setTimeout(()=>{nt().then(t=>{e||(h(t),D(!1))}).catch(e=>console.error(e))},1500);return()=>{e=!0,globalThis.clearTimeout(t),re.current=!1}},[V,T,r,h,x]),(0,Le.useEffect)(()=>{if(!r||!b||b<=se.current)return;se.current=b;let e=!1,t=globalThis.setTimeout(()=>{nt(!0).then(t=>{e||h(t)}).catch(e=>console.error(e))},250);return()=>{e=!0,globalThis.clearTimeout(t)}},[b,r,h]);let he=(0,Le.useMemo)(()=>({hfCache:v,localModels:y,modelCacheDiagnostics:j,runtimeStatus:k,nodesRegistry:_,modelIndexesRefreshing:T}),[v,y,j,T,_,k]),be=(0,Le.useMemo)(()=>({...me,includeBlocked:!0}),[me]),ve=(0,Le.useCallback)(e=>g[e.id]??g[F(rt(e))],[g]),we=(0,Le.useCallback)(e=>J(ve(e)),[ve]),ke=(0,Le.useCallback)(e=>!ft(e,z),[z]),Ne=(0,Le.useMemo)(()=>{let e="experimental"===me.category?et:ue,t="experimental"===me.category?"all":me.category,a=Pe(e,p,{...be,category:t},e=>we(e)?.observedSeconds??null);return"experimental"===me.category?a.filter(ke):a},[be,me.category,p,ke,we]),_e=(0,Le.useMemo)(()=>Pe(et,p,{...be,category:"all",query:"",modelType:"all",mode:"all",difficulty:"all"},e=>we(e)?.observedSeconds??null),[be,p,we]),Se=(0,Le.useMemo)(()=>_e.filter(ke).length,[_e,ke]),Me=(0,Le.useMemo)(()=>_e.filter(e=>ft(e,z)).length,[_e,z]),$e=(0,Le.useMemo)(()=>{if("recommended"!==me.category||0!==me.query.trim().length||"all"!==me.modelType||"all"!==me.mode||"all"!==me.difficulty)return Ne;let e=Ne.filter(e=>ft(e,z));return 0===e.length?Ne:"recommended"===me.sort?[...e].sort((e,t)=>bt(e.id)-bt(t.id)):e},[me.category,me.difficulty,me.mode,me.modelType,me.query,me.sort,Ne,z]),qe=(0,Le.useMemo)(()=>et.find(e=>e.id===Q)??null,[Q]),ze=(0,Le.useMemo)(()=>new Map(Re.map(e=>"recommended"===e.id&&Me>0?[e.id,Me]:"experimental"===e.id?[e.id,Se]:[e.id,Pe(ue,p,{...be,category:e.id,query:"",modelType:"all",mode:"all",difficulty:"all"}).length])),[be,Se,p,Me]),Ae=(0,Le.useMemo)(()=>Re.filter(e=>"recommended"===e.id||"all"===e.id||(ze.get(e.id)??0)>0),[ze]);(0,Le.useLayoutEffect)(()=>{r&&te.current.get(me.category)?.scrollIntoView({block:"nearest",inline:"nearest"})},[me.category,r,Ae]),(0,Le.useEffect)(()=>{ee(12),le&&(le.scrollTop=0)},[me.category,me.difficulty,me.mode,me.modelType,me.query,me.sort,le]);let Ee=(0,Le.useMemo)(()=>$e.slice(0,Z),[$e,Z]),Be=Ee.length<$e.length;(0,Le.useEffect)(()=>{if(!r||!Be||!le||!ie||typeof IntersectionObserver>"u")return;let e=new IntersectionObserver(([t])=>{t?.isIntersecting&&(e.disconnect(),ee(e=>Math.min(e+12,$e.length)))},{root:le,rootMargin:"320px 0px",threshold:.01});return e.observe(ie),()=>e.disconnect()},[le,Be,ie,r,$e.length,Ee.length]);let Oe=e=>u.needsReview(xe(e)),Ve=e=>{u.request("create",xe(e),()=>(async e=>{U(!0);try{let t=await Xe(e);if(s(!1),t.graphError)return void A(`${e.label} opened, but graph creation needs attention: ${t.graphError}`,{variant:"warning",autoHideDuration:7e3});if(t.warnings.length>0)return void A(`${Te(e)}: ${t.warnings.join(" ")}`,{variant:"warning",autoHideDuration:12e3});A(`${Te(e)} template created`,{variant:"success",autoHideDuration:2600})}catch(e){if(O(e))return;A(String(e),{variant:"error",autoHideDuration:7e3})}finally{U(!1)}})(e))},De=u.pending?.policies??[],Fe=()=>{n(!0),d("setup"),s(!1)},Ke=()=>{m(!0)};return(0,Qe.jsxs)(Qe.Fragment,{children:[(0,Qe.jsxs)(ne,{open:r,onClose:()=>s(!1),testId:e?"task-launcher":"template-browser-dialog",title:(0,Qe.jsxs)("span",{className:"inline-flex items-center gap-2",children:[(0,Qe.jsx)(o,{size:18,className:"text-hf-yellow"}),"Templates"]}),panelClassName:"!h-[90dvh] !w-[96vw] !max-h-[90dvh] !max-w-[96vw] sm:!h-[80vh] sm:!w-[82vw] sm:!max-h-[80vh] sm:!max-w-[82vw]",bodyClassName:"!h-[calc(90dvh-49px)] !max-h-[calc(90dvh-49px)] sm:!h-[calc(80vh-49px)] sm:!max-h-[calc(80vh-49px)] flex flex-col overflow-hidden p-0",children:[e?(0,Qe.jsx)("div",{className:"shrink-0 border-b border-modiff-border p-3",children:(0,Qe.jsx)(Ye,{})}):null,(0,Qe.jsxs)("div",{className:"grid min-h-0 flex-1 grid-rows-[auto_minmax(0,1fr)] lg:grid-cols-[190px_minmax(0,1fr)] lg:grid-rows-1",children:[(0,Qe.jsx)("aside",{className:"min-h-0 overflow-x-auto border-b border-modiff-border bg-modiff-bg p-2 lg:overflow-y-auto lg:border-b-0 lg:border-r lg:p-3",children:(0,Qe.jsx)("div",{className:"flex min-w-max gap-1 lg:grid lg:min-w-0",role:"group","aria-label":"Template categories",children:Ae.map(e=>{let t=me.category===e.id;return(0,Qe.jsxs)(ge,{ref:t=>{t?te.current.set(e.id,t):te.current.delete(e.id)},tone:t?"primary":"ghost",size:"prominent",align:"left","aria-pressed":t,"data-testid":`template-browser-category-${e.id}`,onClick:()=>{pe(t=>({...t,category:e.id})),Y(null)},className:"shrink-0 !justify-between px-3 lg:w-full",children:[(0,Qe.jsx)("span",{className:"truncate",children:e.label}),(0,Qe.jsx)("span",{className:I("min-w-6 rounded-full px-1.5 py-0.5 text-center text-xs",t?"bg-modiff-selected-surface text-hf-yellow":"bg-modiff-panel text-modiff-subtle-text"),children:ze.get(e.id)??0})]},e.id)})})}),(0,Qe.jsxs)("section",{className:"@container grid min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)] bg-modiff-surface",children:[(0,Qe.jsxs)("div",{className:"border-b border-modiff-border bg-modiff-panel p-3","data-testid":"template-browser-header",children:[(0,Qe.jsx)("h1",{className:"mb-2 hidden text-xl font-bold tracking-tight text-modiff-text sm:block",children:"recommended"===me.category?"Recommended templates":pt(me.category)}),(0,Qe.jsxs)("div",{className:"flex flex-wrap items-end gap-2",children:[(0,Qe.jsx)(ye,{"data-testid":"template-browser-search",value:me.query,onChange:e=>pe(t=>({...t,query:e.target.value})),onClear:me.query?()=>pe(e=>({...e,query:""})):void 0,placeholder:"Search templates, models, prompts, or techniques","aria-label":"Search templates",controlSize:"prominent",className:"min-w-52 flex-1"}),(0,Qe.jsxs)(ge,{className:"sm:hidden","aria-expanded":C,"aria-controls":R,onClick:()=>M(e=>!e),children:["Filters",fe?` (${fe})`:""]}),(0,Qe.jsxs)("div",{id:R,className:I("flex-wrap items-end gap-2 sm:contents",C?"flex":"hidden"),children:[(0,Qe.jsx)(vt,{label:"Model",value:me.modelType,onValueChange:e=>pe(t=>({...t,modelType:e})),options:[{value:"all",label:"All models"},...P({currentModelType:p.modelType,includeWorkflowOnly:!0}).map(e=>({value:e.modelType,label:e.label}))]}),(0,Qe.jsx)(vt,{label:"Operation",value:me.mode??"all",onValueChange:e=>pe(t=>({...t,mode:e})),options:[{value:"all",label:"All operations"},...at.map(e=>({value:e,label:je[e]}))]}),(0,Qe.jsx)(vt,{label:"Level",value:me.difficulty,onValueChange:e=>pe(t=>({...t,difficulty:e})),options:Ze.map(e=>({value:e,label:dt(e)}))}),(0,Qe.jsx)(vt,{label:"Sort",value:me.sort,onValueChange:e=>pe(t=>({...t,sort:e})),options:tt})]})]})]}),(0,Qe.jsxs)("div",{className:I("grid h-full min-h-0",qe&&"@[1024px]:grid-cols-[minmax(0,1fr)_minmax(420px,28vw)]"),children:[(0,Qe.jsxs)("div",{ref:oe,className:I("min-h-0 overflow-y-auto p-3",qe&&"hidden @[1024px]:block"),"data-testid":"template-browser-gallery-scroll",children:[(0,Qe.jsxs)("div",{className:"grid items-stretch gap-3 [grid-template-columns:repeat(auto-fill,minmax(280px,1fr))] max-[1400px]:[grid-template-columns:repeat(auto-fill,minmax(250px,1fr))]",children:[Ee.map((e,t)=>(0,Qe.jsx)(wt,{active:f===e.id,disabled:K,featured:"recommended"===me.category&&0===t&&$e.length>2,manifest:z,readiness:We(e,{...he,form:rt(e),autoResourcePlan:ve(e)}),selected:Q===e.id,template:e,runtimeEstimate:we(e),requiresTermsReview:Oe(e),onCreate:()=>{Ve(e)},onView:()=>Y(e.id),onReadinessAction:t=>{"needs_model"===t.status?((e,t)=>{let a=t.modelInstallTargets[0]?.repoId??t.missingModelRepos[0];c({nodeId:null,fieldKey:null,focus:{modelType:e.modelType,repo:a,label:q[e.modelType],source:"template"}}),s(!1)})(e,t):"needs_input"===t.status?(Ke(),s(!1)):("needs_backend"===t.status||"needs_setup"===t.status)&&Fe()}},e.id)),0===$e.length&&(0,Qe.jsx)("div",{className:"col-span-full rounded-modiff-compact border border-modiff-border bg-modiff-bg p-4 text-sm text-modiff-subtle-text",children:"No templates match the current filters."})]}),Be?(0,Qe.jsx)("div",{ref:ce,"aria-hidden":"true",className:"h-px w-full","data-testid":"template-browser-load-sentinel"}):null,(0,Qe.jsxs)("span",{className:"sr-only",role:"status","aria-live":"polite",children:[Ee.length," of ",$e.length," templates loaded"]})]}),qe?(0,Qe.jsx)(yt,{disabled:K,installingRepo:W,manifest:z,readiness:We(qe,{...he,form:rt(qe),autoResourcePlan:ve(qe)}),runtimeEstimate:we(qe),requiresTermsReview:Oe(qe),template:qe,onBack:()=>Y(null),onCreate:()=>{Ve(qe)},onInstallRepo:(e,t)=>{((e,t,a=!1)=>{u.request("install",xe(e),()=>(async(e,t=!1)=>{X(e);try{await S(e,x,{repair:t}),h(await nt(!0))}catch(e){console.error(e)}finally{X(null)}})(t,a))})(qe,e,t)},onOpenAssets:Ke,onOpenModels:()=>{c({nodeId:null,fieldKey:null})},onOpenSetup:Fe,onUseLowVram:()=>{let e=ue.find(e=>"low_vram"===e.id);e&&(pe(e=>({...e,category:"performance",query:""})),Y(e.id))}}):null]})]})]})]}),(0,Qe.jsx)(Ie,{open:!!u.pending,policies:De,action:u.pending?.action??"create",onCancel:u.cancel,onConfirm:u.confirm})]})}function vt({label:e,onValueChange:t,options:a,value:r}){let s=(0,Le.useId)();return(0,Qe.jsx)(oe,{className:I("Sort"===e?"w-36":"w-32","shrink-0"),htmlFor:s,label:e,children:(0,Qe.jsx)(re,{id:s,"data-testid":`template-browser-${e.toLowerCase()}-filter`,size:"normal",value:r,onValueChange:t,options:a,optionsClassName:"!min-w-44"})})}function wt({active:e,disabled:t,featured:r,manifest:s,onCreate:l,onReadinessAction:o,onView:i,readiness:n,runtimeEstimate:d,requiresTermsReview:c,selected:p,template:f}){let{beforePath:g,mediaPath:h,thumbnailPath:x}=te(f,s),v=xe(f).length>0;return(0,Qe.jsxs)("article",{"data-testid":`template-browser-card-${f.id}`,"data-active-template":e?"true":"false","data-inspected":p?"true":"false",className:I("group relative flex h-full min-h-0 flex-col overflow-hidden rounded-modiff-panel border bg-modiff-bg shadow-modiff-node",p?"border-hf-yellow ring-1 ring-hf-yellow/40":e?"border-modiff-blue ring-1 ring-modiff-blue/30":"border-modiff-border"),children:[(0,Qe.jsx)(ge,{tone:"ghost",disabled:t,"aria-label":`View details for ${Te(f)}`,"aria-current":e?"true":void 0,"aria-pressed":p,onClick:i,onFocus:i,className:"absolute inset-0 z-0 !h-auto !w-full !rounded-modiff-panel !border-0 !bg-transparent !p-0",children:(0,Qe.jsx)("span",{className:"sr-only",children:"View template details"})}),(0,Qe.jsxs)("div",{className:"pointer-events-none relative z-10 overflow-hidden bg-modiff-panel",children:[(0,Qe.jsx)(kt,{afterPath:h,beforePath:g,mediaKind:f.outputKinds?.[0],slots:Se(f),thumbnailPath:x,variant:f.thumbnailVariant}),(0,Qe.jsxs)("div",{className:"pointer-events-none absolute inset-x-0 top-0 flex items-start justify-end gap-2 bg-gradient-to-b from-modiff-bg/80 to-transparent p-3",children:[v?(0,Qe.jsx)(we,{content:"Usage restrictions — review before use",children:e=>(0,Qe.jsx)("span",{...e,"aria-label":"Usage restrictions — review before use",className:"pointer-events-auto grid size-7 place-items-center rounded-full border border-hf-orange/70 bg-modiff-bg/85 text-hf-orange backdrop-blur","data-testid":`template-usage-warning-${f.id}`,role:"img",tabIndex:0,children:(0,Qe.jsx)(m,{"aria-hidden":"true",size:14})})}):null,(0,Qe.jsx)("span",{className:"rounded-full border border-modiff-overlay-text/10 bg-modiff-bg/80 px-2 py-1 text-xs font-semibold text-modiff-text backdrop-blur",children:ct(f)})]}),r?(0,Qe.jsx)("div",{className:"pointer-events-none absolute left-3 top-3 grid size-7 place-items-center rounded-full border border-hf-yellow/50 bg-modiff-bg/85 text-hf-yellow backdrop-blur",title:"Featured template",children:(0,Qe.jsx)(a,{size:13})}):null]}),(0,Qe.jsxs)("div",{className:"pointer-events-none relative z-10 flex min-h-0 flex-1 flex-col gap-3 p-4",children:[(0,Qe.jsxs)("div",{className:"min-w-0",children:[(0,Qe.jsxs)("div",{className:"mb-1 flex min-h-12 items-start justify-between gap-2",children:[(0,Qe.jsx)("h3",{className:"min-w-0 flex-1",children:(0,Qe.jsx)(ge,{tone:"ghost",size:"compact",align:"left",disabled:t,"data-testid":`template-browser-use-${f.id}`,onClick:i,className:"pointer-events-auto !h-auto min-w-0 max-w-full !justify-start !rounded-none !p-0 text-base !font-bold !leading-6 text-modiff-text hover:bg-transparent",children:(0,Qe.jsx)("span",{className:"line-clamp-2",children:Te(f)})})}),(0,Qe.jsx)(St,{readiness:n,testId:`template-card-readiness-${f.id}`,onAction:()=>o(n)})]}),(0,Qe.jsxs)("p",{className:"truncate text-sm font-semibold text-modiff-subtle-text",children:[q[f.modelType]," · ",je[f.mode]]})]}),(0,Qe.jsx)("p",{className:"line-clamp-2 min-h-10 text-sm leading-5 text-modiff-subtle-text",children:f.recipeSummary??f.description}),(0,Qe.jsxs)("div",{className:"mt-auto grid min-w-0 gap-2 border-t border-modiff-border pt-3",children:[d?(0,Qe.jsxs)("div",{className:"inline-flex min-w-0 items-center gap-2 text-xs text-modiff-subtle-text","data-testid":`template-runtime-${f.id}`,title:d.title,children:[(0,Qe.jsx)(u,{size:13,className:"shrink-0 text-hf-yellow"}),(0,Qe.jsx)("span",{className:"truncate",children:d.label})]}):null,(0,Qe.jsx)(ge,{tone:"primary",className:"pointer-events-auto w-full",icon:(0,Qe.jsx)(b,{size:14}),disabled:t,"data-testid":`template-browser-create-card-${f.id}`,onClick:e=>{e.stopPropagation(),l()},children:c?"Review terms":"Use template"})]})]})]})}function yt({disabled:e,installingRepo:o,manifest:d,onBack:c,onCreate:u,onInstallRepo:p,onOpenAssets:f,onOpenModels:g,onOpenSetup:h,onUseLowVram:v,readiness:w,runtimeEstimate:j,requiresTermsReview:k,template:N}){let _=M(N,d,{requireCardPreview:!1}),S=_??function(e,t){return t?.examples.find(t=>t.templateId===e.id&&["approved_reviewed","approved_exact"].includes(t.qualityReviewStatus)&&!(!t.beforePath||!t.beforeMediaHash))}(N,d),T=N.inputBindings?.filter(e=>"template"===e.origin).flatMap(e=>e.defaultAssets).find(e=>e.previewPath)?.previewPath,P=y(_?.thumbnailPath??_?.posterPath)??mt(N),R=y(S?.beforePath??T),C=y(_?.afterPath??_?.outputPath),I=function(e){let t=me(e),a=t.templateDefaultBindings.map(e=>({included:!0,key:e.id,label:e.label.replace(/^Reviewed\s+/i,"")})),r=t.missingLabels.map(e=>({included:!1,key:`missing:${e}`,label:e}));return a.length>0||r.length>0?{title:0===r.length?"Included inputs":"Required inputs",items:[...a,...r]}:{title:"Required inputs",items:ut(e).map(e=>({included:!1,key:`legacy:${e}`,label:e}))}}(N),$=k?"Review terms":"Use template",z=w.issues.some(e=>"open_assets"===e.action),A=w.issues.some(e=>"open_setup"===e.action),E=w.issues.some(e=>"apply_low_vram"===e.action);return(0,Qe.jsxs)("aside",{className:"min-h-0 overflow-y-auto bg-modiff-bg p-4 @[1024px]:border-l @[1024px]:border-modiff-border","data-testid":`template-detail-${N.id}`,children:[(0,Qe.jsx)("div",{className:"mb-3 flex items-center justify-between gap-2",children:(0,Qe.jsx)(ge,{tone:"ghost",size:"dense",icon:(0,Qe.jsx)(l,{size:15}),onClick:c,children:"Templates"})}),(0,Qe.jsx)(kt,{afterPath:C,beforePath:R,detail:!0,mediaKind:N.outputKinds?.[0],slots:Se(N),thumbnailPath:P,variant:N.thumbnailVariant}),(0,Qe.jsxs)("div",{className:"mt-4 grid gap-4",children:[(0,Qe.jsxs)("div",{children:[(0,Qe.jsxs)("div",{className:"mb-2 inline-flex items-center gap-1.5 rounded-full border border-modiff-border bg-modiff-panel px-2.5 py-1 text-xs font-semibold text-hf-yellow",children:[(0,Qe.jsx)(n,{size:12}),je[N.mode]]}),(0,Qe.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,Qe.jsxs)("div",{className:"min-w-0",children:[(0,Qe.jsx)("h2",{className:"break-words text-xl font-bold tracking-tight text-modiff-text",children:Te(N)}),(0,Qe.jsxs)("p",{className:"mt-1 text-sm font-semibold text-modiff-subtle-text",children:[q[N.modelType]," · ",je[N.mode]]})]}),(0,Qe.jsx)(Tt,{readiness:w})]}),(0,Qe.jsx)("p",{className:"mt-3 text-sm leading-6 text-modiff-subtle-text",children:N.userGoal})]}),(0,Qe.jsxs)("div",{className:"grid grid-cols-2 gap-2 text-xs",children:[j?(0,Qe.jsx)(jt,{label:"Local runtime",value:j.label}):null,(0,Qe.jsx)(jt,{label:"VRAM",value:N.vramEstimate??"TBD"}),(0,Qe.jsx)(jt,{label:"Output",value:ct(N)}),(0,Qe.jsx)(jt,{label:"Level",value:N.difficulty??"starter"})]}),(0,Qe.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[(0,Qe.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"What this creates"}),(0,Qe.jsx)("p",{className:"mt-1 text-xs leading-5 text-modiff-subtle-text",children:N.recipeSummary??N.description})]}),(0,Qe.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[(0,Qe.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:I.title}),(0,Qe.jsx)("div",{className:"mt-2 flex flex-wrap gap-1",children:I.items.map(e=>(0,Qe.jsxs)("span",{className:"inline-flex items-center gap-1 rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-xs font-semibold text-modiff-subtle-text",children:[e.included?(0,Qe.jsx)(x,{"aria-hidden":"true",className:"text-modiff-green",size:12}):null,e.label]},e.key))})]}),(0,Qe.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[(0,Qe.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Default prompt"}),(0,Qe.jsx)("p",{className:"mt-2 whitespace-pre-wrap break-words text-xs leading-5 text-modiff-text",children:N.prompt}),N.negativePrompt?(0,Qe.jsxs)(Qe.Fragment,{children:[(0,Qe.jsx)("h4",{className:"mt-3 text-xs font-semibold uppercase text-modiff-subtle-text",children:"Negative prompt"}),(0,Qe.jsx)("p",{className:"mt-1 whitespace-pre-wrap break-words text-xs leading-5 text-modiff-subtle-text",children:N.negativePrompt})]}):null]}),(0,Qe.jsxs)("section",{className:"grid gap-2","data-testid":`template-readiness-${N.id}`,children:[(0,Qe.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,Qe.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Preflight"}),(0,Qe.jsx)(Tt,{readiness:w})]}),0===w.issues.length?(0,Qe.jsx)(V,{tone:"success",title:"Ready now",meta:w.summary,children:"Create a graph tab, build the graph, and then run when you are ready."}):w.issues.map(e=>(0,Qe.jsx)(V,{tone:e.tone,title:e.title,meta:e.detail},e.id))]}),(0,Qe.jsxs)("div",{className:"flex flex-wrap gap-2",children:[w.modelInstallTargets.map(e=>(0,Qe.jsx)(ge,{icon:(0,Qe.jsx)(i,{size:15}),disabled:o===e.repoId,"aria-label":`Install ${e.repoId}`,onClick:()=>p(e.repoId,e.repair),children:o===e.repoId?"Installing...":e.actionLabel??"Install"},e.repoId)),z?(0,Qe.jsx)(ge,{icon:(0,Qe.jsx)(r,{size:15}),onClick:f,children:"Open assets"}):null,A?(0,Qe.jsx)(ge,{icon:(0,Qe.jsx)(t,{size:15}),onClick:h,children:"Open setup"}):null,E?(0,Qe.jsx)(ge,{icon:(0,Qe.jsx)(a,{size:15}),onClick:v,children:"Try Low VRAM"}):null,(0,Qe.jsx)(ge,{icon:(0,Qe.jsx)(s,{size:15}),onClick:g,children:"Models"})]}),(0,Qe.jsx)("div",{className:"sticky bottom-0 -mx-4 bg-gradient-to-t from-modiff-bg via-modiff-bg to-transparent px-4 pb-1 pt-5",children:(0,Qe.jsx)(ge,{tone:"primary",className:"h-10 w-full",icon:"planning"===w.status||"needs_backend"===w.status||"needs_setup"===w.status?(0,Qe.jsx)(m,{size:15}):(0,Qe.jsx)(b,{size:15}),disabled:e,"data-testid":`template-browser-create-${N.id}`,onClick:u,children:$})})]})]})}function jt({label:e,value:t}){return(0,Qe.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2",children:[(0,Qe.jsx)("div",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:e}),(0,Qe.jsx)("div",{className:"mt-1 break-words text-xs font-semibold text-modiff-text",children:t})]})}function kt({afterPath:e,beforePath:t,detail:a=!1,mediaKind:r="image",slots:s,thumbnailPath:l}){if("image"===r&&t&&e)return(0,Qe.jsx)(Nt,{afterPath:e,beforePath:t,detail:a});if(!e&&!t&&l)return(0,Qe.jsx)(_t,{slot:{id:"editorial-poster",label:"Template preview",kind:"poster",placeholder:"template poster pending"},path:l,detail:a,large:!0});let o=2===s.length&&s.some(e=>"compare-before"===e.kind);if(o&&"video"===r&&!a&&e)return(0,Qe.jsx)(_t,{slot:{id:"published-result",label:"Generated video preview",kind:"video",posterPath:l,placeholder:"video preview pending"},path:e,large:!0});if(o){let o=(e,t)=>{if(!e)return t;let a=e.split(/[?#]/,1)[0]?.toLowerCase()??"";return/\.(mp4|webm|mov|mkv)$/.test(a)?"video":/\.(png|jpe?g|webp|gif|avif)$/.test(a)?"image":t};return(0,Qe.jsxs)("div",{className:"grid aspect-video grid-cols-2 overflow-hidden rounded-modiff-panel border border-modiff-border bg-modiff-border",children:[(0,Qe.jsx)(_t,{slot:"video"===r?{...s[0],kind:o(t,"image")}:s[0],path:t,detail:a}),(0,Qe.jsx)(_t,{slot:"video"===r?{...s[1],kind:o(e??l,e?"video":"image"),posterPath:l}:s[1],path:e??l,detail:a})]})}if(s.length>1&&(e||l)){let t="video"===r?"video":"audio"===r?"audio":"image";return(0,Qe.jsx)(_t,{slot:{id:"published-result",label:"Generated result",kind:t,posterPath:l,placeholder:`${t} preview pending`},path:"image"===t?l??e:e??l,detail:a,large:!0})}if(s.length>1)return(0,Qe.jsx)("div",{className:"grid aspect-video grid-cols-2 gap-px overflow-hidden rounded-modiff-panel border border-modiff-border bg-modiff-border",children:s.slice(0,4).map((e,t)=>(0,Qe.jsx)(_t,{slot:e,path:e.path??(0===t?l:void 0),compact:!0,detail:a},e.id))});let i=s[0],n="video"===r||"audio"===r;return(0,Qe.jsx)(_t,{slot:"video"===r?{...i,kind:"video",posterPath:i.posterPath??l}:"audio"===r?{...i,kind:"audio"}:i,path:n?e??t??i.path:l??i.path,detail:a,large:!0})}function Nt({afterPath:e,beforePath:t,detail:a}){let[r,s]=(0,Le.useState)(50),[l,o]=(0,Le.useState)(!1),i=(0,Le.useRef)(null),n=(e,t)=>{let a=e.getBoundingClientRect();if(a.width<=0)return;let r=(t-a.left)/a.width*100;s(Math.max(0,Math.min(100,r)))};return(0,Qe.jsxs)("div",{className:"relative aspect-video overflow-hidden rounded-modiff-panel border border-modiff-border bg-modiff-panel",children:[(0,Qe.jsx)(j,{ref:i,"aria-label":"Before and after comparison",testId:"template-ab-comparison",imageFrom:t,imageTo:e,sliderPosition:r,onSliderPositionChange:s,onError:e=>{e.currentTarget.src="/assets/modiff-icon-256.png"},onMouseDown:e=>{o(!0),n(e.currentTarget,e.clientX)},onMouseEnter:e=>{n(e.currentTarget,e.clientX)},onMouseMove:e=>{n(e.currentTarget,e.clientX)},onPointerMove:e=>{"mouse"===e.pointerType&&n(e.currentTarget,e.clientX)},onMouseUp:()=>o(!1),onTouchStart:e=>{o(!0),n(e.currentTarget,e.touches[0]?.clientX??0)},onTouchMove:e=>{l&&n(e.currentTarget,e.touches[0]?.clientX??0)},onTouchEnd:()=>o(!1)}),a?(0,Qe.jsx)("span",{className:"pointer-events-none absolute left-1/2 top-2 -translate-x-1/2 rounded-full bg-modiff-bg/80 px-2 py-1 text-xs font-semibold text-modiff-text backdrop-blur",children:"Hover or drag to compare"}):null]})}function _t({compact:e=!1,detail:t=!1,large:a=!1,path:s,slot:l}){let o=(0,Le.useRef)(null),i=(0,Le.useRef)(null),[n,d]=(0,Le.useState)(null),[m,u]=(0,Le.useState)(t),[p,f]=(0,Le.useState)(!1),[g,x]=(0,Le.useState)(!1),[w,y]=(0,Le.useState)(!1),j=s&&n!==s?s:void 0,k="video"===l.kind,N="audio"===l.kind,_="json"===l.kind,S=k||"gif"===l.kind?c:N?v:_?h:r,T=t||m&&(!p||g),P=!t&&m&&(!p||g);return(0,Le.useEffect)(()=>{if(t||!k||!j)return;let e=o.current;if(!e||typeof IntersectionObserver>"u")return void u(!0);let a=new IntersectionObserver(([e])=>u(!!e?.isIntersecting),{rootMargin:"160px 0px",threshold:.01});return a.observe(e),()=>a.disconnect()},[t,k,j]),(0,Le.useEffect)(()=>{if(t||!k)return;let e=window.matchMedia("(prefers-reduced-motion: reduce)"),a=navigator.connection,r=()=>f(e.matches||!0===a?.saveData);return r(),e.addEventListener("change",r),a?.addEventListener?.("change",r),()=>{e.removeEventListener("change",r),a?.removeEventListener?.("change",r)}},[t,k]),(0,Le.useEffect)(()=>{if(!t&&i.current){if(!T||!P)return void i.current.pause();i.current.play().catch(()=>{})}},[T,P,t]),(0,Le.useEffect)(()=>{d(null),x(!1),y(!1)},[s]),(0,Qe.jsxs)("div",{ref:o,className:I("relative grid place-items-center overflow-hidden bg-gradient-to-br from-modiff-panel via-modiff-bg to-modiff-surface",a&&"rounded-modiff-panel border border-modiff-border",a&&"aspect-video"),children:[j&&k?(0,Qe.jsxs)(Qe.Fragment,{children:[l.posterPath?(0,Qe.jsx)("img",{src:l.posterPath,alt:"","aria-hidden":"true",loading:"eager",decoding:"async",className:"absolute inset-0 h-full w-full object-cover"}):null,(0,Qe.jsx)("video",{ref:i,src:T?j:void 0,poster:l.posterPath,muted:!t,loop:!t,controls:t,autoPlay:P,playsInline:!0,preload:t||T?"metadata":"none","aria-label":l.label,"data-testid":t?"template-detail-video":"template-card-video","data-preview-attached":T?"true":"false",className:I("relative h-full w-full object-cover transition-opacity duration-150",w||!l.posterPath?"opacity-100":"opacity-0"),onError:()=>d(j),onLoadedData:()=>y(!0),onPlaying:()=>y(!0),onCanPlay:()=>{P&&i.current?.play().catch(()=>{})}})]}):k&&l.posterPath?(0,Qe.jsx)("img",{src:l.posterPath,alt:l.label,loading:"lazy",decoding:"async",className:"h-full w-full object-cover"}):N?(0,Qe.jsxs)("div",{className:"grid h-full w-full content-center gap-4 bg-gradient-to-br from-hf-yellow/15 via-modiff-panel to-modiff-bg p-4",children:[(0,Qe.jsxs)("div",{className:"flex items-center gap-3",children:[(0,Qe.jsx)("div",{className:"grid size-10 shrink-0 place-items-center rounded-full bg-hf-yellow text-modiff-on-accent",children:(0,Qe.jsx)(v,{size:18})}),(0,Qe.jsx)("div",{className:"flex h-16 flex-1 items-center justify-center gap-1 overflow-hidden",children:gt.map((e,t)=>(0,Qe.jsx)("span",{className:I("w-1 rounded-full bg-hf-yellow/80",e)},`${e}-${t}`))})]}),j?(0,Qe.jsx)("audio",{src:j,controls:!0,preload:t?"metadata":"none",className:"pointer-events-auto h-9 w-full","aria-label":l.label,onError:()=>d(j)}):(0,Qe.jsx)("p",{className:"text-center text-xs font-semibold text-modiff-subtle-text",children:"No preview yet"})]}):j&&!_?a?t?(0,Qe.jsxs)(Qe.Fragment,{children:[(0,Qe.jsx)("img",{src:j,alt:"","aria-hidden":"true",loading:"eager",decoding:"async",className:"absolute inset-0 h-full w-full scale-110 object-cover opacity-30 blur-xl",onError:()=>d(j)}),(0,Qe.jsx)("img",{src:j,alt:l.label,loading:"eager",decoding:"async",className:"relative h-full w-full object-contain p-1.5",onError:()=>d(j)})]}):(0,Qe.jsx)("img",{src:j,alt:l.label,loading:"eager",decoding:"async",className:"h-full w-full object-cover",onError:()=>d(j)}):(0,Qe.jsx)("img",{src:j,alt:l.label,loading:"lazy",decoding:"async",className:"h-full w-full object-cover",onError:()=>d(j)}):(0,Qe.jsx)("div",{className:"grid h-full min-h-28 w-full place-items-center p-3 text-center",children:(0,Qe.jsxs)("div",{children:[(0,Qe.jsx)("div",{className:"mx-auto mb-2 grid size-9 place-items-center rounded-full border border-modiff-border bg-modiff-bg/70 text-modiff-subtle-text",children:(0,Qe.jsx)(S,{size:e?16:19})}),(0,Qe.jsx)("p",{className:"text-xs font-semibold text-modiff-subtle-text",children:s?"Preview unavailable":"No preview yet"}),e?null:(0,Qe.jsx)("p",{className:"mt-1 text-xs text-modiff-subtle-text",children:s&&n===s?"The published media could not be decoded":l.placeholder})]})}),!N&&t?(0,Qe.jsx)("span",{className:"absolute left-2 top-2 rounded-full border border-modiff-overlay-text/10 bg-modiff-bg/75 px-2 py-1 text-xs font-semibold text-modiff-overlay-text backdrop-blur",children:l.label}):null,k&&j&&!t&&p&&!g?(0,Qe.jsx)(ae,{label:"Play video preview",onClick:e=>{e.stopPropagation(),x(!0)},className:"pointer-events-auto absolute inset-0 m-auto !size-10 rounded-full border border-modiff-overlay-text/20 bg-modiff-bg/80 text-modiff-overlay-text shadow-modiff-node backdrop-blur hover:border-hf-yellow hover:text-hf-yellow",children:(0,Qe.jsx)(b,{size:17,fill:"currentColor"})}):null]})}function St({onAction:e,readiness:t,testId:a}){let r=["needs_input","needs_model","needs_backend","needs_setup"].includes(t.status),s="ready"===t.status?(0,Qe.jsx)(x,{size:18}):"needs_backend"===t.status?(0,Qe.jsx)(p,{size:18}):"preparing"===t.status||"planning"===t.status?(0,Qe.jsx)(d,{size:18}):(0,Qe.jsx)(m,{size:18}),l=I("pointer-events-auto relative grid size-8 shrink-0 place-items-center rounded-full border bg-modiff-panel/90 transition","success"===t.tone&&"border-modiff-green/70 text-modiff-green","warning"===t.tone&&"border-hf-orange/70 text-hf-orange","error"===t.tone&&"border-modiff-red/70 text-modiff-red","info"===t.tone&&"border-modiff-blue/70 text-modiff-blue","default"===t.tone&&"border-modiff-border text-modiff-subtle-text",r&&"hover:scale-105 hover:bg-modiff-surface-hover"),o=`${t.label}: ${t.summary}`;return r?(0,Qe.jsx)(ae,{label:o,className:l,"data-testid":a,onClick:e,children:s}):(0,Qe.jsx)(we,{content:o,children:e=>(0,Qe.jsx)("span",{...e,className:l,"data-testid":a,"aria-label":o,role:"img",tabIndex:0,children:s})})}function Tt({readiness:e}){return(0,Qe.jsxs)("span",{className:I("inline-flex shrink-0 items-center gap-1 rounded-modiff-compact border px-1.5 py-0.5 text-xs font-semibold","success"===e.tone&&"border-modiff-green/70 text-modiff-green","warning"===e.tone&&"border-hf-orange/70 text-hf-orange","error"===e.tone&&"border-modiff-red/70 text-modiff-red","info"===e.tone&&"border-modiff-blue/70 text-modiff-blue","default"===e.tone&&"border-modiff-border text-modiff-subtle-text"),children:["success"===e.tone?(0,Qe.jsx)(x,{size:12}):"error"===e.tone?(0,Qe.jsx)(m,{size:12}):(0,Qe.jsx)(d,{size:12}),e.label]})}export{xt as default}; \ No newline at end of file diff --git a/web/assets/TemplateUsageTermsDialog.js b/web/assets/TemplateUsageTermsDialog.js index 10c607f9..06966951 100644 --- a/web/assets/TemplateUsageTermsDialog.js +++ b/web/assets/TemplateUsageTermsDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{cn as s,pr as r,ur as t,z as n}from"./graph-vendor.js?v=5bcd315505f5670a";import{Ut as i,Wt as l,h as a,ta as o}from"./studio-templates.js?v=5bcd315505f5670a";var d=e(r(),1);function c(){let e=o(e=>e.modelTermsAcknowledgements),s=o(e=>e.acknowledgeModelTerms),r=(0,d.useRef)(null),[t,n]=(0,d.useState)(null),i=s=>{let r=a(s);return!(!r||e[r])};return{pending:t,needsReview:i,request:(e,s,t)=>{if(i(s))return r.current=t,void n({action:e,policies:s});t()},cancel:()=>{r.current=null,n(null)},confirm:()=>{let e=a(t?.policies??[]);e&&s(e);let i=r.current;r.current=null,n(null),i?.()}}}var m=t();function u(e){return"personal_noncommercial"===e.useScope?"Personal / non-commercial":"research_academic_only"===e.useScope?"Research / academic only":"noncommercial_only"===e.useScope?"Restricted model license":"license_review_required"===e.useScope?"Product and user review required":"rights_undetermined"===e.useScope?"No weight license declared":"Usage terms"}function g({open:e,policies:r,action:t,onCancel:a,onConfirm:o}){let d="run"===t,c=r.some(e=>"rights_undetermined"===e.useScope);return(0,m.jsx)(l,{open:e,onClose:a,testId:d?"model-usage-terms-dialog":"template-usage-terms-dialog",title:(0,m.jsxs)("span",{className:"inline-flex items-center gap-2",children:[(0,m.jsx)(n,{"aria-hidden":"true",className:"text-hf-yellow",size:18}),"Review model terms"]}),panelClassName:"max-w-xl",footer:(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(i,{onClick:a,children:"Cancel"}),(0,m.jsxs)(i,{tone:"primary",onClick:o,"data-testid":d?"model-usage-terms-confirm":"template-usage-terms-confirm",children:[d?"I acknowledge":c?"I understand":"I have reviewed and agree"," —"," ","install"===t?"Install":"run"===t?"Run":"Create graph"]})]}),children:(0,m.jsxs)("div",{className:"grid gap-3",children:[(0,m.jsxs)("p",{className:"text-sm leading-5 text-modiff-subtle-text",children:["This ",d?"model run":"template"," uses ",r.length," model"," ",1===r.length?"dependency":"dependencies"," with usage restrictions. Review the original terms before"," ","install"===t?"installing its models":"run"===t?"running the workflow":"creating the graph","."]}),(0,m.jsx)("div",{className:"grid gap-2",children:r.map(e=>(0,m.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3",children:[(0,m.jsxs)("div",{className:"flex items-start justify-between gap-3",children:[(0,m.jsxs)("div",{className:"min-w-0",children:[(0,m.jsx)("div",{className:"break-words text-sm font-semibold text-modiff-text",children:e.repository}),(0,m.jsx)("div",{className:"mt-1 text-xs font-semibold text-hf-yellow",children:u(e)}),(0,m.jsx)("p",{className:"mt-1 text-xs leading-5 text-modiff-subtle-text",children:e.shortSummary})]}),(0,m.jsxs)("a",{className:"inline-flex shrink-0 items-center gap-1 text-xs font-semibold text-modiff-blue hover:underline",href:e.termsUrl,target:"_blank",rel:"noreferrer",children:["View terms",(0,m.jsx)(s,{"aria-hidden":"true",size:12})]})]}),"huggingface_gated"===e.access?(0,m.jsxs)("a",{className:"mt-2 inline-flex items-center gap-1 text-xs font-semibold text-modiff-blue hover:underline",href:e.modelCardUrl,target:"_blank",rel:"noreferrer",children:["Open on Hugging Face to request or review access",(0,m.jsx)(s,{"aria-hidden":"true",size:12})]}):null]},`${e.id}:${e.revision??""}`))}),(0,m.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"MoDiff does not grant model-use rights. This acknowledgement does not change or replace the model authors’ terms."})]})})}export{c as n,g as t}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{R as s,an as r,cr as t,dr as n}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{Sa as i,f as l,in as a,rn as o}from"./studio-templates.js?v=cc046c26d6fe7aa4";var d=e(n(),1);function c(){let e=i(e=>e.modelTermsAcknowledgements),s=i(e=>e.acknowledgeModelTerms),r=(0,d.useRef)(null),[t,n]=(0,d.useState)(null),a=s=>{let r=l(s);return!(!r||e[r])};return{pending:t,needsReview:a,request:(e,s,t)=>{if(a(s))return r.current=t,void n({action:e,policies:s});t()},cancel:()=>{r.current=null,n(null)},confirm:()=>{let e=l(t?.policies??[]);e&&s(e);let i=r.current;r.current=null,n(null),i?.()}}}var m=t();function u(e){return"personal_noncommercial"===e.useScope?"Personal / non-commercial":"research_academic_only"===e.useScope?"Research / academic only":"noncommercial_only"===e.useScope?"Restricted model license":"license_review_required"===e.useScope?"Product and user review required":"rights_undetermined"===e.useScope?"No weight license declared":"Usage terms"}function f({open:e,policies:t,action:n,onCancel:i,onConfirm:l}){let d="run"===n,c=t.some(e=>"rights_undetermined"===e.useScope);return(0,m.jsx)(a,{open:e,onClose:i,testId:d?"model-usage-terms-dialog":"template-usage-terms-dialog",title:(0,m.jsxs)("span",{className:"inline-flex items-center gap-2",children:[(0,m.jsx)(s,{"aria-hidden":"true",className:"text-hf-yellow",size:18}),"Review model terms"]}),panelClassName:"max-w-xl",footer:(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(o,{onClick:i,children:"Cancel"}),(0,m.jsxs)(o,{tone:"primary",onClick:l,"data-testid":d?"model-usage-terms-confirm":"template-usage-terms-confirm",children:[d?"I acknowledge":c?"I understand":"I have reviewed and agree"," —"," ","install"===n?"Install":"run"===n?"Run":"Create graph"]})]}),children:(0,m.jsxs)("div",{className:"grid gap-3",children:[(0,m.jsxs)("p",{className:"text-sm leading-5 text-modiff-subtle-text",children:["This ",d?"model run":"template"," uses ",t.length," model"," ",1===t.length?"dependency":"dependencies"," with usage restrictions. Review the original terms before"," ","install"===n?"installing its models":"run"===n?"running the workflow":"creating the graph","."]}),(0,m.jsx)("div",{className:"grid gap-2",children:t.map(e=>(0,m.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3",children:[(0,m.jsxs)("div",{className:"flex items-start justify-between gap-3",children:[(0,m.jsxs)("div",{className:"min-w-0",children:[(0,m.jsx)("div",{className:"break-words text-sm font-semibold text-modiff-text",children:e.repository}),(0,m.jsx)("div",{className:"mt-1 text-xs font-semibold text-hf-yellow",children:u(e)}),(0,m.jsx)("p",{className:"mt-1 text-xs leading-5 text-modiff-subtle-text",children:e.shortSummary})]}),(0,m.jsxs)("a",{className:"inline-flex shrink-0 items-center gap-1 text-xs font-semibold text-modiff-blue hover:underline",href:e.termsUrl,target:"_blank",rel:"noreferrer",children:["View terms",(0,m.jsx)(r,{"aria-hidden":"true",size:12})]})]}),"huggingface_gated"===e.access?(0,m.jsxs)("a",{className:"mt-2 inline-flex items-center gap-1 text-xs font-semibold text-modiff-blue hover:underline",href:e.modelCardUrl,target:"_blank",rel:"noreferrer",children:["Open on Hugging Face to request or review access",(0,m.jsx)(r,{"aria-hidden":"true",size:12})]}):null]},`${e.id}:${e.revision??""}`))}),(0,m.jsx)("p",{className:"text-xs leading-5 text-modiff-subtle-text",children:"MoDiff does not grant model-use rights. This acknowledgement does not change or replace the model authors’ terms."})]})})}export{c as n,f as t}; \ No newline at end of file diff --git a/web/assets/UIAudioField.js b/web/assets/UIAudioField.js index d63ac7e9..63b47da3 100644 --- a/web/assets/UIAudioField.js +++ b/web/assets/UIAudioField.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{dn as a,pn as t,pr as s,ur as o}from"./graph-vendor.js?v=5bcd315505f5670a";import{Bt as n,Pi as l,_n as i,_t as r,ei as d,et as u,ft as c,gt as f,kr as m,mt as p,no as h,ta as y,tt as g,vt as x,yt as j,zt as w}from"./studio-templates.js?v=5bcd315505f5670a";import{n as v,t as b}from"./mediaDownload.js?v=5bcd315505f5670a";var R=e(s(),1),k=o();function S(e,a,t){return e&&"string"==typeof e?l(e,a,t):null}function I({source:e,downloadSampleRate:s}){let o=(0,R.useRef)(null),[n,l]=(0,R.useState)(v(e)?null:e),[i,d]=(0,R.useState)(1),u=y(e=>e.setMediaExportOpener);(0,R.useEffect)(()=>{if(!v(e))return void l(e);let a=new AbortController,t=null;return l(null),h(e,{signal:a.signal}).then(e=>{a.signal.aborted||(t=URL.createObjectURL(e),l(t))}).catch(()=>{a.signal.aborted||l(e)}),()=>{a.abort(),t&&URL.revokeObjectURL(t)}},[e]);let m=e=>e.stopPropagation();return(0,k.jsxs)("div",{className:"nodrag nopan nowheel relative min-w-80",children:[(0,k.jsx)("audio",{ref:o,controls:!0,controlsList:"nodownload noplaybackrate noremoteplayback",preload:"metadata",src:n??void 0,"aria-busy":null===n,className:"modiff-audio-player w-full",onClick:m,onMouseDown:m,onPointerDown:m,onTouchStart:m}),(0,k.jsx)("div",{className:"absolute right-2 top-1/2 z-10 -translate-y-1/2",children:(0,k.jsxs)(f,{children:[(0,k.jsx)(j,{children:(0,k.jsx)(c,{label:"Audio actions",isRound:!0,className:"bg-modiff-surface/90 text-modiff-text hover:bg-modiff-selected-surface hover:text-hf-yellow",children:(0,k.jsx)(a,{size:17})})}),(0,k.jsxs)(x,{layer:"graph",anchor:"bottom end",className:"min-w-48",children:[(0,k.jsx)(p,{icon:(0,k.jsx)(t,{size:14}),onClick:()=>u({source:e,kind:"audio",filename:b(e,"MoDiff-audio.wav"),title:"Download audio",defaultFormat:"wav",defaultSampleRate:s}),children:"Download…"}),(0,k.jsx)(r,{}),[.75,1,1.25,1.5,2].map(e=>(0,k.jsxs)(p,{selected:i===e,onClick:()=>(e=>{d(e),o.current&&(o.current.playbackRate=e)})(e),children:["Playback ",e,"×"]},e))]})]})})]})}function K(e){let a=!0===e.fieldOptions?.compactPreview,t=d(e.nodeId,e.fieldKey,e.fieldOptions),s=g(t.nodeId,t.fieldKey),o=m(e=>{let a=e.nodes.find(e=>e.id===t.nodeId),s=a?.data.params.sample_rate?.value??a?.data.params.sample_rate?.default,o=Number(s);return[44100,48e3,88200,96e3].includes(o)?o:null}),l=s.hasAuthority?s.value:e.value,r=(Array.isArray(l)?l.map(e=>S(e,t.nodeId,t.fieldKey)):[S(l,t.nodeId,t.fieldKey)]).filter(e=>!!e),c=s.statusMessage||("string"==typeof e.uiStateMessage?e.uiStateMessage:""),f=c.startsWith("Waiting")||c.startsWith("Run failed")?c:"running"===e.executionStatus?e.progressMessage||"Waiting for this run":"failed"===e.executionStatus?"Run failed before producing new audio":"No current audio";return(0,k.jsxs)(i,{dataKey:e.fieldKey,hidden:e.hidden,layoutStyle:e.style,className:"flex flex-col gap-2",children:[0===r.length?(0,k.jsx)(w,{compact:a,kind:"audio",message:f,testId:`node-preview-empty-${e.nodeId}-${e.fieldKey}`}):r.map((t,s)=>(0,k.jsx)(n,{compact:a,kind:"audio",className:"p-2",testId:`node-preview-audio-${e.nodeId}-${e.fieldKey}-${s}`,children:(0,k.jsx)(I,{source:t,downloadSampleRate:o})},`${t}-${s}`)),(0,k.jsx)(u,{nodeId:t.nodeId,fieldKey:t.fieldKey,currentUrls:r,audioDownloadSampleRate:o})]})}export{K as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cn as a,cr as t,dr as s,un as o}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$t as n,At as l,Do as i,Et as r,Mt as d,Nt as u,Ot as c,Sa as f,Vr as m,_i as p,en as h,jn as y,jt as x,mt as g,pt as j,ta as w}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as b,t as v}from"./mediaDownload.js?v=cc046c26d6fe7aa4";var R=e(s(),1),S=t();function k(e,a,t){return e&&"string"==typeof e?w(e,a,t):null}function I({source:e,downloadSampleRate:t}){let s=(0,R.useRef)(null),[n,m]=(0,R.useState)(b(e)?null:e),[p,h]=(0,R.useState)(1),y=f(e=>e.setMediaExportOpener);(0,R.useEffect)(()=>{if(!b(e))return void m(e);let a=new AbortController,t=null;return m(null),i(e,{signal:a.signal}).then(e=>{a.signal.aborted||(t=URL.createObjectURL(e),m(t))}).catch(()=>{a.signal.aborted||m(e)}),()=>{a.abort(),t&&URL.revokeObjectURL(t)}},[e]);let g=e=>e.stopPropagation();return(0,S.jsxs)("div",{className:"nodrag nopan nowheel relative min-w-80",children:[(0,S.jsx)("audio",{ref:s,controls:!0,controlsList:"nodownload noplaybackrate noremoteplayback",preload:"metadata",src:n??void 0,"aria-busy":null===n,className:"modiff-audio-player w-full",onClick:g,onMouseDown:g,onPointerDown:g,onTouchStart:g}),(0,S.jsx)("div",{className:"absolute right-2 top-1/2 z-10 -translate-y-1/2",children:(0,S.jsxs)(l,{children:[(0,S.jsx)(u,{children:(0,S.jsx)(r,{label:"Audio actions",isRound:!0,className:"bg-modiff-surface/90 text-modiff-text hover:bg-modiff-selected-surface hover:text-hf-yellow",children:(0,S.jsx)(a,{size:17})})}),(0,S.jsxs)(d,{layer:"graph",anchor:"bottom end",className:"min-w-48",children:[(0,S.jsx)(c,{icon:(0,S.jsx)(o,{size:14}),onClick:()=>y({source:e,kind:"audio",filename:v(e,"MoDiff-audio.wav"),title:"Download audio",defaultFormat:"wav",defaultSampleRate:t}),children:"Download…"}),(0,S.jsx)(x,{}),[.75,1,1.25,1.5,2].map(e=>(0,S.jsxs)(c,{selected:p===e,onClick:()=>(e=>{h(e),s.current&&(s.current.playbackRate=e)})(e),children:["Playback ",e,"×"]},e))]})]})})]})}function K(e){let a=!0===e.fieldOptions?.compactPreview,t=p(e.nodeId,e.fieldKey,e.fieldOptions),s=g(t.nodeId,t.fieldKey),o=m(e=>{let a=e.nodes.find(e=>e.id===t.nodeId),s=a?.data.params.sample_rate?.value??a?.data.params.sample_rate?.default,o=Number(s);return[44100,48e3,88200,96e3].includes(o)?o:null}),l=s.hasAuthority?s.value:e.value,i=(Array.isArray(l)?l.map(e=>k(e,t.nodeId,t.fieldKey)):[k(l,t.nodeId,t.fieldKey)]).filter(e=>!!e),r=s.statusMessage||("string"==typeof e.uiStateMessage?e.uiStateMessage:""),d=r.startsWith("Waiting")||r.startsWith("Run failed")?r:"running"===e.executionStatus?e.progressMessage||"Waiting for this run":"failed"===e.executionStatus?"Run failed before producing new audio":"No current audio";return(0,S.jsxs)(y,{dataKey:e.fieldKey,hidden:e.hidden,layoutStyle:e.style,className:"flex flex-col gap-2",children:[0===i.length?(0,S.jsx)(n,{compact:a,kind:"audio",message:d,testId:`node-preview-empty-${e.nodeId}-${e.fieldKey}`}):i.map((t,s)=>(0,S.jsx)(h,{compact:a,kind:"audio",className:"p-2",testId:`node-preview-audio-${e.nodeId}-${e.fieldKey}-${s}`,children:(0,S.jsx)(I,{source:t,downloadSampleRate:o})},`${t}-${s}`)),(0,S.jsx)(j,{nodeId:t.nodeId,fieldKey:t.fieldKey,currentUrls:i,audioDownloadSampleRate:o})]})}export{K as default}; \ No newline at end of file diff --git a/web/assets/UIVideoField.js b/web/assets/UIVideoField.js index f7dbf228..f1703466 100644 --- a/web/assets/UIVideoField.js +++ b/web/assets/UIVideoField.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{dn as o,pn as t,pr as a,ur as s}from"./graph-vendor.js?v=5bcd315505f5670a";import{Bt as n,Ii as r,_n as i,ei as d,et as l,ft as c,gt as p,it as f,mt as u,nt as m,on as g,rt as h,ta as x,tt as v,vt as y,yt as j,zt as w}from"./studio-templates.js?v=5bcd315505f5670a";import{t as b}from"./mediaDownload.js?v=5bcd315505f5670a";var I=e(a(),1),K=s();function N(e){let[a,s]=(0,I.useState)(0),N=d(e.nodeId,e.fieldKey,e.fieldOptions),S=v(N.nodeId,N.fieldKey),D=!0===e.fieldOptions?.compactPreview,M=x(e=>e.setMediaExportOpener),P=f(S.hasAuthority?S.value:e.value,e=>r(e,N.nodeId,N.fieldKey)),$=h(a,P.length);(0,I.useEffect)(()=>{a!==$&&s($)},[$,a]);let k=P[$]||null,z=S.statusMessage||("string"==typeof e.uiStateMessage?e.uiStateMessage:""),V=z.startsWith("Waiting")||z.startsWith("Run failed")?z:"running"===e.executionStatus?e.progressMessage||"Waiting for this run":"failed"===e.executionStatus?"Run failed before producing a new video":"No current video";return(0,K.jsxs)(i,{dataKey:e.fieldKey,hidden:e.hidden,layoutStyle:e.style,className:"flex flex-col flex-wrap items-start justify-center gap-2",children:[P.length>1&&(0,K.jsx)(g,{value:String($),className:"nodrag","aria-label":"Preview Video",onValueChange:e=>s(h(Number(e),P.length)),options:P.map((e,o)=>({value:String(o),label:`Video ${o+1}`}))}),k?(0,K.jsxs)(n,{compact:D,className:"group/video",testId:`node-preview-video-${e.nodeId}-${e.fieldKey}`,children:[(0,K.jsx)("video",{src:k,controls:!0,controlsList:"nodownload noremoteplayback",preload:"metadata",className:"nodrag nopan nowheel block h-full w-full object-contain p-0.5",onClick:e=>e.stopPropagation(),onMouseDown:e=>e.stopPropagation(),onPointerDown:e=>e.stopPropagation(),onTouchStart:e=>e.stopPropagation(),onError:e=>{e.currentTarget.poster=m},onLoadedData:e=>{e.currentTarget.poster=""}},$),(0,K.jsx)("div",{className:"nodrag nopan absolute right-2 top-2 z-10",children:(0,K.jsxs)(p,{children:[(0,K.jsx)(j,{children:(0,K.jsx)(c,{label:"Video actions",isRound:!0,className:"border border-modiff-border bg-modiff-panel/90 text-modiff-text hover:bg-modiff-surface-hover hover:text-hf-yellow",children:(0,K.jsx)(o,{size:17})})}),(0,K.jsx)(y,{layer:"graph",anchor:"bottom end",className:"min-w-48",children:(0,K.jsx)(u,{icon:(0,K.jsx)(t,{size:14}),onClick:()=>{k&&M({source:k,kind:"video",filename:b(k,`MoDiff-${e.nodeId}-${e.fieldKey}.mp4`),title:"Download video",defaultFormat:"mp4"})},children:"Download…"})})]})})]}):(0,K.jsx)(w,{compact:D,message:V,testId:`node-preview-empty-${e.nodeId}-${e.fieldKey}`}),(0,K.jsx)(l,{nodeId:N.nodeId,fieldKey:N.fieldKey,currentUrls:P})]})}export{N as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cn as o,cr as a,dr as t,un as s}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$t as n,At as r,Et as i,Mt as d,Nt as l,Ot as c,Sa as p,_i as f,_t as u,bn as m,en as g,gt as h,ht as x,jn as j,mt as v,pt as y,ra as w}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as b}from"./mediaDownload.js?v=cc046c26d6fe7aa4";var I=e(t(),1),K=a();function N(e){let[a,t]=(0,I.useState)(0),N=f(e.nodeId,e.fieldKey,e.fieldOptions),S=v(N.nodeId,N.fieldKey),M=!0===e.fieldOptions?.compactPreview,$=p(e=>e.setMediaExportOpener),D=u(S.hasAuthority?S.value:e.value,e=>w(e,N.nodeId,N.fieldKey)),P=h(a,D.length);(0,I.useEffect)(()=>{a!==P&&t(P)},[P,a]);let k=D[P]||null,E=S.statusMessage||("string"==typeof e.uiStateMessage?e.uiStateMessage:""),O=E.startsWith("Waiting")||E.startsWith("Run failed")?E:"running"===e.executionStatus?e.progressMessage||"Waiting for this run":"failed"===e.executionStatus?"Run failed before producing a new video":"No current video";return(0,K.jsxs)(j,{dataKey:e.fieldKey,hidden:e.hidden,layoutStyle:e.style,className:"flex flex-col flex-wrap items-start justify-center gap-2",children:[D.length>1&&(0,K.jsx)(m,{value:String(P),className:"nodrag","aria-label":"Preview Video",onValueChange:e=>t(h(Number(e),D.length)),options:D.map((e,o)=>({value:String(o),label:`Video ${o+1}`}))}),k?(0,K.jsxs)(g,{compact:M,className:"group/video",testId:`node-preview-video-${e.nodeId}-${e.fieldKey}`,children:[(0,K.jsx)("video",{src:k,controls:!0,controlsList:"nodownload noremoteplayback",preload:"metadata",className:"nodrag nopan nowheel block h-full w-full object-contain p-0.5",onClick:e=>e.stopPropagation(),onMouseDown:e=>e.stopPropagation(),onPointerDown:e=>e.stopPropagation(),onTouchStart:e=>e.stopPropagation(),onError:e=>{e.currentTarget.poster=x},onLoadedData:e=>{e.currentTarget.poster=""}},P),(0,K.jsx)("div",{className:"nodrag nopan absolute right-2 top-2 z-10",children:(0,K.jsxs)(r,{children:[(0,K.jsx)(l,{children:(0,K.jsx)(i,{label:"Video actions",isRound:!0,className:"border border-modiff-border bg-modiff-panel/90 text-modiff-text hover:bg-modiff-surface-hover hover:text-hf-yellow",children:(0,K.jsx)(o,{size:17})})}),(0,K.jsx)(d,{layer:"graph",anchor:"bottom end",className:"min-w-48",children:(0,K.jsx)(c,{icon:(0,K.jsx)(s,{size:14}),onClick:()=>{k&&$({source:k,kind:"video",filename:b(k,`MoDiff-${e.nodeId}-${e.fieldKey}.mp4`),title:"Download video",defaultFormat:"mp4"})},children:"Download…"})})]})})]}):(0,K.jsx)(n,{compact:M,message:O,testId:`node-preview-empty-${e.nodeId}-${e.fieldKey}`}),(0,K.jsx)(y,{nodeId:N.nodeId,fieldKey:N.fieldKey,currentUrls:D})]})}export{N as default}; \ No newline at end of file diff --git a/web/assets/VisualStageControls.js b/web/assets/VisualStageControls.js new file mode 100644 index 00000000..b57056c0 --- /dev/null +++ b/web/assets/VisualStageControls.js @@ -0,0 +1 @@ +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{cr as t,dr as s,ln as a}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{At as n,Ei as o,Go as r,Mt as i,Nt as l,Ot as d,Si as c,Uo as p,Vo as u,Vr as g,an as h,in as m,ji as j}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as x,t as f}from"./operationGraphTransaction.js?v=cc046c26d6fe7aa4";import{n as w,t as k}from"./mediaAttachment.js?v=cc046c26d6fe7aa4";import{t as A}from"./MediaAttachmentControls.js?v=cc046c26d6fe7aa4";var C=e(s(),1),S=t();function b({node:e}){let[t,s]=(0,C.useState)(null),[b,v]=(0,C.useState)(!1),G=r(e),E=c(e=>e.operationContracts),N=c(e=>e.pipelineSupport),y=!G&&w(k(E,N),e.data.operationAuthoring?.operation.pipelineClass??"").length>0;function F(t){try{let a=g.getState().toObject(),n=o(),r=t?u(a,e.id,[t]):p(a,new Set([e.id])).graph;if(r===a&&!G)throw Error("No eligible ungrouped stages on this model branch. Existing groups are unchanged.");f(r,n,x(a),t?"Group workflow stages":"Ungroup workflow stages"),j.getState().saveActiveWorkflowTab(!0),s(null)}catch(e){s(e instanceof Error?e.message:"Could not group these stages.")}}return(0,S.jsxs)("div",{className:"nodrag nowheel grid gap-2 py-2",children:[(0,S.jsxs)(n,{children:[(0,S.jsx)(l,{children:(0,S.jsx)(h,{label:"Workflow stage actions",size:"compact",children:(0,S.jsx)(a,{size:16})})}),(0,S.jsx)(i,{layer:"graph",children:G?(0,S.jsx)(d,{onClick:()=>F(),children:"Ungroup stages"}):(0,S.jsxs)(S.Fragment,{children:[e.data.blockProjectionOwnerId?null:(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(d,{onClick:()=>F("inputs"),children:"Group input encoders"}),(0,S.jsx)(d,{onClick:()=>F("guidance"),children:"Group guidance"})]}),y?(0,S.jsx)(d,{onClick:()=>requestAnimationFrame(()=>v(!0)),children:"Add image / audio input…"}):null]})})]}),b?(0,S.jsx)(m,{open:!0,title:"Add image / audio input",onClose:()=>v(!1),children:(0,S.jsx)(A,{node:e,onAttached:()=>v(!1),inline:!0})}):null,t?(0,S.jsx)("p",{role:"alert",className:"text-xs text-modiff-red",children:t}):null]})}export{b as default}; \ No newline at end of file diff --git a/web/assets/WorkflowArtifactRequirementRow.js b/web/assets/WorkflowArtifactRequirementRow.js index f3b7f4d0..1ad4e41b 100644 --- a/web/assets/WorkflowArtifactRequirementRow.js +++ b/web/assets/WorkflowArtifactRequirementRow.js @@ -1 +1 @@ -import{ur as e}from"./graph-vendor.js?v=5bcd315505f5670a";import{Ba as a,Lt as l,Sn as t,T as s,Va as r,za as i}from"./studio-templates.js?v=5bcd315505f5670a";var n=e();function o(e){return"use_local"===e?"Use local":"repair"===e?"Repair":"retry"===e?"Retry":"installing"===e?"Installing":"ready"===e?"Ready":"install"===e?"Install":"Details"}function c(e){return"use_local"===e?"use_local":"repair"===e?"repair":"retry"===e?"retry":"installing"===e?"installing":"ready"===e?"ready":"install"===e?"install":"details"}function d(e,l){return e.status.runnable?"ready":r(l)?"installing":a(l)?"retry":"Repair"===e.primaryAction?"repair":"Use local"===e.primaryAction?"use_local":"Details"===e.primaryAction?"details":"install"}function p({activeInstallCount:e=0,actionTestId:a,className:r,compact:p=!1,installProgress:u,onInstall:m,onUseLocal:x,requirement:f,rightSlot:g,testId:b}){let y=u[f.repo],h=d(f,y),j=function(e,a){return["install","repair","retry"].includes(a)?e.installTarget??{repo:e.repo,label:e.label,actionLabel:o(a),repair:"repair"===a}:null}(f,h),v="installing"===h,N=function(e,a){return e.status.runnable||"ready"===a?"success":"use_local"===a||"repair"===a||"installing"===a||e.installTarget||"details"!==a?"warning":"error"}(f,h),I=v||!!j&&e>=2||"use_local"===h&&!x||!!j&&!m,_=[f.details||f.status.reason,f.modelPath?f.modelPath:f.repo,f.nodeLabel?`Node: ${f.nodeLabel}`:null].filter(Boolean).join(" | "),L=j&&m?()=>{m(j,f)}:"use_local"===h&&x?()=>x(f):void 0;return(0,n.jsxs)("article",{className:t("rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",r),"data-testid":b,title:_,children:[(0,n.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,n.jsxs)("div",{className:"min-w-0",children:[(0,n.jsx)("div",{className:"truncate text-sm font-semibold text-modiff-text",children:f.label}),(0,n.jsxs)("div",{className:"mt-1 flex flex-wrap gap-1",children:[(0,n.jsx)(l,{action:"details",className:"min-h-7 px-2 py-0.5 text-xs",label:f.role,title:f.repo,tone:"neutral"}),"graph"===f.source?(0,n.jsx)(l,{action:"details",className:"min-h-7 px-2 py-0.5 text-xs",label:"Graph",title:f.nodeLabel??f.nodeId,tone:"neutral"}):null]})]}),(0,n.jsxs)("div",{className:"flex shrink-0 items-center gap-1",children:[g,(0,n.jsx)(l,{action:c(h),className:"min-h-7 px-2 py-0.5 text-xs",disabled:I,label:o(h),onClick:L,progress:v?i(y):null,testId:a??(b?`${b}-action`:void 0),title:_,tone:N})]})]}),y&&!p?(0,n.jsx)("div",{className:"mt-2",children:(0,n.jsx)(s,{compact:!0,progress:y,repoId:f.repo,testId:b?`${b}-progress`:void 0})}):null]})}export{p as t}; \ No newline at end of file +import{cr as e}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{In as a,S as l,Zt as t,co as s,lo as r,so as i}from"./studio-templates.js?v=cc046c26d6fe7aa4";var n=e();function o(e){return"use_local"===e?"Use local":"repair"===e?"Repair":"retry"===e?"Retry":"installing"===e?"Installing":"ready"===e?"Ready":"install"===e?"Install":"Details"}function c(e){return"use_local"===e?"use_local":"repair"===e?"repair":"retry"===e?"retry":"installing"===e?"installing":"ready"===e?"ready":"install"===e?"install":"details"}function d(e,a){return e.status.runnable?"ready":r(a)?"installing":s(a)?"retry":"Repair"===e.primaryAction?"repair":"Use local"===e.primaryAction?"use_local":"Details"===e.primaryAction?"details":"install"}function p({activeInstallCount:e=0,actionTestId:s,className:r,compact:p=!1,installProgress:u,onInstall:m,onUseLocal:x,requirement:f,rightSlot:g,testId:b}){let y=u[f.repo],h=d(f,y),j=function(e,a){return["install","repair","retry"].includes(a)?e.installTarget??{repo:e.repo,label:e.label,actionLabel:o(a),repair:"repair"===a}:null}(f,h),v="installing"===h,N=function(e,a){return e.status.runnable||"ready"===a?"success":"use_local"===a||"repair"===a||"installing"===a||e.installTarget||"details"!==a?"warning":"error"}(f,h),I=v||!!j&&e>=2||"use_local"===h&&!x||!!j&&!m,_=[f.details||f.status.reason,f.modelPath?f.modelPath:f.repo,f.nodeLabel?`Node: ${f.nodeLabel}`:null].filter(Boolean).join(" | "),L=j&&m?()=>{m(j,f)}:"use_local"===h&&x?()=>x(f):void 0;return(0,n.jsxs)("article",{className:a("rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",r),"data-testid":b,title:_,children:[(0,n.jsxs)("div",{className:"flex items-start justify-between gap-2",children:[(0,n.jsxs)("div",{className:"min-w-0",children:[(0,n.jsx)("div",{className:"truncate text-sm font-semibold text-modiff-text",children:f.label}),(0,n.jsxs)("div",{className:"mt-1 flex flex-wrap gap-1",children:[(0,n.jsx)(t,{action:"details",className:"min-h-7 px-2 py-0.5 text-xs",label:f.role,title:f.repo,tone:"neutral"}),"graph"===f.source?(0,n.jsx)(t,{action:"details",className:"min-h-7 px-2 py-0.5 text-xs",label:"Graph",title:f.nodeLabel??f.nodeId,tone:"neutral"}):null]})]}),(0,n.jsxs)("div",{className:"flex shrink-0 items-center gap-1",children:[g,(0,n.jsx)(t,{action:c(h),className:"min-h-7 px-2 py-0.5 text-xs",disabled:I,label:o(h),onClick:L,progress:v?i(y):null,testId:s??(b?`${b}-action`:void 0),title:_,tone:N})]})]}),y&&!p?(0,n.jsx)("div",{className:"mt-2",children:(0,n.jsx)(l,{compact:!0,progress:y,repoId:f.repo,testId:b?`${b}-progress`:void 0})}):null]})}export{p as t}; \ No newline at end of file diff --git a/web/assets/WorkflowSaveDialog.js b/web/assets/WorkflowSaveDialog.js index 33370575..216b96ba 100644 --- a/web/assets/WorkflowSaveDialog.js +++ b/web/assets/WorkflowSaveDialog.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{Zt as a,pr as s,sn as o,ur as i}from"./graph-vendor.js?v=5bcd315505f5670a";import{Qt as l,Ut as r,Wt as t,en as n,rn as d}from"./studio-templates.js?v=5bcd315505f5670a";var f=e(s(),1),c=i(),m=[{value:"library",label:"My workflows",description:"Create a named workflow in MoDiff that stays available across sessions.",meta:"Recommended"},{value:"file",label:"JSON file",description:"Choose a folder and filename on this device. Unsupported browsers use Downloads."}];function p({initialDestination:e="library",initialName:s,loading:i=!1,onClose:p,onSave:u,open:w}){let[v,b]=(0,f.useState)(s),[h,k]=(0,f.useState)(e);(0,f.useEffect)(()=>{w&&(b(s),k(e))},[e,s,w]);let x=v.trim();return(0,c.jsx)(t,{open:w,onClose:i?()=>{}:p,title:"Save workflow as",description:"Keep a reusable workflow in MoDiff or download a portable JSON copy.",dismissible:!i,testId:"save-workflow-dialog",panelClassName:"max-w-lg",footer:(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(r,{disabled:i,onClick:p,children:"Cancel"}),(0,c.jsx)(r,{"data-testid":"confirm-save-workflow",disabled:!x,loading:i,onClick:()=>u(x,h),tone:"primary",icon:"library"===h?(0,c.jsx)(a,{size:16}):(0,c.jsx)(o,{size:16}),children:"library"===h?"Save to My workflows":"Choose location"})]}),children:(0,c.jsxs)("div",{className:"grid gap-4",children:[(0,c.jsx)(l,{label:"Workflow name",description:"file"===h?"This name is used as the suggested JSON filename.":"You can rename it later from My workflows.",required:!0,children:(0,c.jsx)(n,{autoFocus:!0,disabled:i,"data-testid":"save-workflow-name",value:v,onChange:e=>b(e.currentTarget.value),onKeyDown:e=>{"Enter"===e.key&&x&&!i&&(e.preventDefault(),u(x,h))}})}),(0,c.jsx)(l,{label:"Save to",children:(0,c.jsx)(d,{"aria-label":"Workflow save destination",disabled:i,value:h,onValueChange:e=>k(e),options:m})}),(0,c.jsx)("p",{className:"text-modiff-metadata leading-5 text-modiff-subtle-text",children:"MoDiff keeps recovery snapshots automatically. Save creates a named workflow; saving a JSON copy is for portable backups and sharing."})]})})}export{p as default}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Jt as a,cr as o,dr as s,in as i}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{_n as l,fn as r,in as n,mn as t,rn as d}from"./studio-templates.js?v=cc046c26d6fe7aa4";var f=e(s(),1),c=o(),m=[{value:"library",label:"My workflows",description:"Create a named workflow in MoDiff that stays available across sessions.",meta:"Recommended"},{value:"file",label:"JSON file",description:"Choose a folder and filename on this device. Unsupported browsers use Downloads."}];function p({initialDestination:e="library",initialName:o,loading:s=!1,onClose:p,onSave:u,open:w}){let[v,b]=(0,f.useState)(o),[h,k]=(0,f.useState)(e);(0,f.useEffect)(()=>{w&&(b(o),k(e))},[e,o,w]);let x=v.trim();return(0,c.jsx)(n,{open:w,onClose:s?()=>{}:p,title:"Save workflow as",description:"Keep a reusable workflow in MoDiff or download a portable JSON copy.",dismissible:!s,testId:"save-workflow-dialog",panelClassName:"max-w-lg",footer:(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(d,{disabled:s,onClick:p,children:"Cancel"}),(0,c.jsx)(d,{"data-testid":"confirm-save-workflow",disabled:!x,loading:s,onClick:()=>u(x,h),tone:"primary",icon:"library"===h?(0,c.jsx)(a,{size:16}):(0,c.jsx)(i,{size:16}),children:"library"===h?"Save to My workflows":"Choose location"})]}),children:(0,c.jsxs)("div",{className:"grid gap-4",children:[(0,c.jsx)(r,{label:"Workflow name",description:"file"===h?"This name is used as the suggested JSON filename.":"You can rename it later from My workflows.",required:!0,children:(0,c.jsx)(t,{autoFocus:!0,disabled:s,"data-testid":"save-workflow-name",value:v,onChange:e=>b(e.currentTarget.value),onKeyDown:e=>{"Enter"===e.key&&x&&!s&&(e.preventDefault(),u(x,h))}})}),(0,c.jsx)(r,{label:"Save to",children:(0,c.jsx)(l,{"aria-label":"Workflow save destination",disabled:s,value:h,onValueChange:e=>k(e),options:m})}),(0,c.jsx)("p",{className:"text-modiff-metadata leading-5 text-modiff-subtle-text",children:"MoDiff keeps recovery snapshots automatically. Save creates a named workflow; saving a JSON copy is for portable backups and sharing."})]})})}export{p as default}; \ No newline at end of file diff --git a/web/assets/WorkspacePanel.js b/web/assets/WorkspacePanel.js index 77937279..ba56286f 100644 --- a/web/assets/WorkspacePanel.js +++ b/web/assets/WorkspacePanel.js @@ -1 +1 @@ -import{r as e}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{$t as t,B as a,Bt as s,E as i,Fn as n,G as l,Hn as r,It as o,Jt as d,Lt as c,M as u,On as m,Pn as p,Pt as f,Q as h,Qt as x,Rn as g,Un as b,V as j,W as v,Wt as y,Y as k,Yt as w,at as C,bn as N,ct as S,dt as I,et as R,ht as $,jn as M,ln as T,lt as P,pn as A,pr as _,q as z,tt as q,un as O,ur as B,ut as D,vt as E,wn as G,xn as L,z as F,zn as H,zt as V}from"./graph-vendor.js?v=5bcd315505f5670a";import{$ as W,$n as K,$t as U,Aa as Q,Ai as J,At as Z,Ba as X,Bn as Y,Cr as ee,Ct as te,D as ae,Dn as se,Dt as ie,E as ne,Et as le,Fa as re,Fi as oe,Ft as de,Gi as ce,Ha as ue,Hi as me,Hn as pe,In as fe,Io as he,It as xe,Ji as ge,Jn as be,Ln as je,Lo as ve,Lt as ye,Ma as ke,Mt as we,Na as Ce,Nt as Ne,Or as Se,Ot as Ie,Pt as Re,Q as $e,Qa as Me,Qn as Te,Qt as Pe,Rt as Ae,Sn as _e,Sr as ze,T as qe,Tr as Oe,Tt as Be,U as De,Ua as Ee,Ut as Ge,Va as Le,Vn as Fe,Vt as He,Wa as Ve,Wi as We,Wt as Ke,Xi as Ue,Xn as Qe,Zi as Je,Zn as Ze,Zt as Xe,_a as Ye,_i as et,aa as tt,ai as at,ar as st,at as it,ba as nt,bi as lt,br as rt,ca as ot,ci as dt,cr as ct,d as ut,da as mt,di as pt,dn as ft,do as ht,dr as xt,fa as gt,fi as bt,fn as jt,fr as vt,ga as yt,gi as kt,go as wt,gr as Ct,ha as Nt,hi as St,hn as It,ia as Rt,ii as $t,ja as Mt,jn as Tt,jt as Pt,kr as At,kt as _t,la as zt,li as qt,lt as Ot,ma as Bt,mi as Dt,mn as Et,mr as Gt,na as Lt,ni as Ft,oa as Ht,on as Vt,or as Wt,pa as Kt,pi as Ut,pn as Qt,ra as Jt,ri as Zt,ro as Xt,sa as Yt,si as ea,ta,to as aa,ua as sa,ui as ia,un as na,uo as la,ur as ra,va as oa,vi as da,wa as ca,wi as ua,wr as ma,wt as pa,xa as fa,xi as ha,xn as xa,xr as ga,yi as ba,za as ja}from"./studio-templates.js?v=5bcd315505f5670a";import{i as va,n as ya,r as ka}from"./block-composition-tools.js?v=5bcd315505f5670a";import{t as wa}from"./WorkflowArtifactRequirementRow.js?v=5bcd315505f5670a";import{n as Ca,t as Na}from"./TemplateUsageTermsDialog.js?v=5bcd315505f5670a";import{a as Sa,c as Ia,l as Ra,s as $a,t as Ma}from"./controlledWorkflows.js?v=5bcd315505f5670a";var Ta=e(_(),1),Pa=B();function Aa(e){navigator.clipboard?.writeText(e)}function _a({error:e,status:t}){let a=t?.runtimeEnvironment,s=a?.devices.find(e=>e.device===a.defaultDevice),i=!(!t?.ready||!a?.executionReady),n="experimental"===a?.supportTier||"preview"===a?.supportTier,l=a?.installation,r=a?.issues.map(e=>e.message).join(" "),o=l?.steps?.filter(e=>"complete"!==e.status&&"skipped"!==e.status)??[];return(0,Pa.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"runtime-environment-card",children:[(0,Pa.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Pa.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Runtime environment"}),(0,Pa.jsx)(ye,{label:i?"Ready":"Needs setup",tone:i?"success":"error",title:e||r||a?.status})]}),(0,Pa.jsxs)("div",{className:"mt-2 flex flex-wrap gap-1.5",children:[(0,Pa.jsx)(ye,{label:`${a?.installedProfile??"Profile unverified"} · ${a?.supportTier??"unverified"}`,tone:n||!a?.profileVerified?"warning":"neutral",title:a?.profileVerified?`Requested: ${a.requestedProfile??"auto"}. Qualification: ${a.supportTier}.`:"Legacy backend: installation profile cannot be verified."}),(0,Pa.jsx)(ye,{label:s?`${s.backend} · ${s.device}`:"Detecting hardware",tone:s&&"cpu"!==s.backend?"success":"neutral",title:s?`${s.name} · ${s.vendor} · ${s.memoryKind} memory`:"Hardware has not been reported."}),(0,Pa.jsx)(ye,{label:`Torch ${t?.packages?.torch?.version??"unknown"}`,tone:"neutral",title:`Torch ${t?.packages?.torch?.version??"unknown"}`})]}),n&&(0,Pa.jsx)("p",{className:"mt-2 text-xs text-modiff-warning",children:"This profile passed local checks; model recipes still need physical qualification."}),!i&&a?.issues.length?(0,Pa.jsx)("ul",{className:"mt-2 grid gap-1 text-xs text-modiff-red","data-testid":"runtime-profile-issues",children:a.issues.map((e,t)=>(0,Pa.jsx)("li",{children:e.message},`${e.code??"runtime"}-${t}`))}):null,o.length>0&&(0,Pa.jsx)("ol",{className:"mt-3 grid gap-2","data-testid":"runtime-installation-checklist",children:o.map((e,t)=>(0,Pa.jsxs)("li",{className:"rounded-modiff-compact border border-modiff-border-subtle p-2 text-xs text-modiff-text",children:[(0,Pa.jsxs)("div",{className:"font-semibold",children:[t+1,". ",e.title??e.id]}),e.explanation&&(0,Pa.jsx)("p",{className:"mt-1 text-modiff-subtle-text",children:e.explanation}),e.requiresAdmin&&(0,Pa.jsx)("p",{className:"mt-1 text-modiff-warning",children:"Administrator approval required."}),e.requiresReboot&&(0,Pa.jsx)("p",{className:"mt-1 text-modiff-warning",children:"Resume after reboot or complete sign-out."}),e.command&&(0,Pa.jsxs)("div",{className:"mt-2 flex items-start gap-2",children:[(0,Pa.jsx)("code",{className:"min-w-0 flex-1 select-all break-all text-modiff-subtle-text",children:e.command}),(0,Pa.jsx)(Ge,{tone:"secondary",size:"compact",className:"h-auto px-2 py-1",onClick:()=>Aa(e.command),children:"Copy"})]}),e.verification&&(0,Pa.jsxs)("p",{className:"mt-1 text-modiff-subtle-text",children:["Verify: ",e.verification]}),e.failureHelp&&(0,Pa.jsx)("p",{className:"mt-1 text-modiff-subtle-text",children:e.failureHelp})]},e.id??`${e.phase??"step"}-${t}`))}),l?.resumeCommand&&"complete"!==l.status&&(0,Pa.jsxs)("div",{className:"mt-2 flex items-start gap-2",children:[(0,Pa.jsx)("code",{className:"min-w-0 flex-1 select-all break-all text-xs text-modiff-subtle-text",children:l.resumeCommand}),(0,Pa.jsx)(Ge,{tone:"secondary",size:"compact",className:"h-auto px-2 py-1 text-xs",onClick:()=>Aa(l.resumeCommand),children:"Copy resume"})]}),a?.repairCommand&&!i&&(!l?.resumeCommand||"complete"===l.status)&&(0,Pa.jsxs)("div",{className:"mt-2 flex items-start gap-2",children:[(0,Pa.jsx)("code",{className:"min-w-0 flex-1 select-all break-all text-xs text-modiff-subtle-text",children:a.repairCommand}),(0,Pa.jsx)(Ge,{tone:"secondary",size:"compact",className:"h-auto px-2 py-1 text-xs",onClick:()=>Aa(a.repairCommand),children:"Copy repair"})]})]})}function za(e,t,a){return Xt(`${ve.serverAddress}${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),timeoutMs:3e4,parse:a})}function qa(e,t){let a="active"===t&&"active"===e.overlayStatus&&e.cutoverReady&&"qualified"===e.contractState,s="active"!==t&&"base"!==t?t:a?"active":"qualified"===e.contractState&&e.cutoverReady?e.overlayStatus:"unavailable";return`${s.replace(/_/g," ")}${"unavailable"===s?` (${e.contractState})`:""}.`}function Oa(e){return`${"macos"===e.platform?"macOS":"windows"===e.platform?"Windows":"Linux"} ${"arm64"===e.machine?"ARM64":"x86-64"}`}function Ba(){let e=Ft(e=>e.optionalRuntimeCatalog),t=Ft(e=>e.discoveryRequests.optionalRuntimes),a=Ft(e=>e.fetchOptionalRuntimes),s=ta(e=>e.setAlertOpener),[i,n]=(0,Ta.useState)(null),[l,r]=(0,Ta.useState)([]),[o,d]=(0,Ta.useState)(null),[c,u]=(0,Ta.useState)(!0),[m,p]=(0,Ta.useState)(null),[f,h]=(0,Ta.useState)(null),x=(0,Ta.useRef)(0),g=(0,Ta.useRef)(!1),b=(0,Ta.useRef)(0),j=(0,Ta.useRef)(0),v=(0,Ta.useCallback)(async()=>{let e=++x.current;u(!0);try{let[t,a]=await Promise.all([Xt(`${ve.serverAddress}/runtime/optimizations`,{timeoutMs:15e3,parse:tt}),Xt(`${ve.serverAddress}/runtime/optimizations/receipts`,{timeoutMs:15e3,parse:Yt})]);if(e!==x.current)return;n(t),r(a.receipts),p(null)}catch(t){throw e===x.current&&p(aa(t,"Could not load runtime optimizations.")),t}finally{e===x.current&&u(!1)}},[]);(0,Ta.useEffect)(()=>{v().catch(()=>{})},[v]),(0,Ta.useEffect)(()=>()=>{j.current+=1},[]);let y=(0,Ta.useCallback)(async(e,t)=>{let s=++j.current,i=Date.now()+3e5;for(;s===j.current&&Date.now()window.setTimeout(e,1e3)),s!==j.current)return;await a();let i=Ft.getState().optionalRuntimeCatalog,n=i?.profiles.find(t=>t.id===e);if("active"===i?.processLoadStatus&&n?.specDigest===t&&"active"===n.overlayStatus){let e=Ft.getState();return void await Promise.all([e.fetchRuntimeStatus(),e.fetchStudioModelCapabilities()])}}s===j.current&&Ee("Runtime activation is still restarting. Refresh Setup before running this workflow.",{variant:"warning"})},[a]);(0,Ta.useEffect)(()=>{if(!f||["cancelled","failed","ready"].includes(f.status))return;let e=new AbortController,t=window.setTimeout(async()=>{if(++b.current>2400)Ee("Runtime setup polling stopped. Refresh status to continue.",{variant:"warning"});else try{let t=await Xt(`${ve.serverAddress}/runtime/optional-runtimes/jobs/${encodeURIComponent(f.id)}`,{signal:e.signal,parse:Lt});if(t.id!==f.id||t.profileId!==f.profileId||t.specDigest!==f.specDigest)throw Error("The optional-runtime job identity changed.");h(t),["cancelled","failed","ready"].includes(t.status)&&a()}catch{e.signal.aborted||h({...f})}},750);return()=>{e.abort(),window.clearTimeout(t)}},[a,f]);let k=async(e,t)=>{if(!g.current){g.current=!0,d(e);try{await t()}catch(e){Ee(aa(e,"Action failed."),{variant:"error"})}finally{g.current=!1,d(null)}}},w=(e,t,a)=>k(e,async()=>{Ee((await za(t,a,Ht)).message??"Updated.",{variant:"success"}),v().catch(()=>{})}),C=(e,t,a,i)=>s({title:e,message:t,confirmText:a,cancelText:"Cancel",onConfirm:()=>{s(null),k(a.toLowerCase(),i)},onCancel:()=>s(null)}),N=(e,t)=>{let s=t?"Activate":"repair_required"===e.overlayStatus||"staged_unchecked"===e.overlayStatus?"Repair":"Install";C(`${s} optional runtime?`,t?`Activate validated ${e.label} and restart MoDiff.`:`Install ${e.label} from reviewed, locked artifacts. Activation is separate.`,s,t?async()=>{Ee((await za("/runtime/optional-runtimes/activate",{environmentId:t,profileId:e.id,specDigest:e.specDigest,consent:!0},Jt)).message??"Activation selected.",{variant:"success"}),h(null),await a(),y(e.id,e.specDigest)}:async()=>{let t=await za("/runtime/optional-runtimes/install",{profileId:e.id,specDigest:e.specDigest,consent:!0},Lt);if(t.profileId!==e.id||t.specDigest!==e.specDigest)throw Error("Runtime job mismatch.");b.current=0,h(t),await a()})},S=()=>{f&&k("cancel",async()=>{let e=await za(`/runtime/optional-runtimes/jobs/${encodeURIComponent(f.id)}/cancel`,{},Lt);if(e.id!==f.id)throw Error("The cancellation response does not match the active job.");h(e),["cancelled","failed"].includes(e.status)&&await a()})},I=l.filter(e=>"workload"===e.kind&&"observed"===e.status),R=!(!e?.activeEnvironmentId||e.previousEnvironmentId||"repair_required"!==e.processLoadStatus),$=!(!e?.previousEnvironmentId&&!R);return(0,Pa.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[(0,Pa.jsxs)("div",{className:"grid gap-1 pb-2",children:[(0,Pa.jsx)("strong",{children:"Optional runtimes"}),t.error?(0,Pa.jsx)("p",{className:"text-xs text-modiff-warning",children:t.error}):"loading"===t.status?(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Loading optional runtimes..."}):"success"===t.status&&e?e.profiles.map(t=>{let a=Rt(e,t),s=f?.profileId===t.id&&f.specDigest===t.specDigest?f.result?.environmentId:void 0,i="qualified"===t.contractState&&t.cutoverReady,n=s??a,l=n?"Activate":"repair_required"===t.overlayStatus||"staged_unchecked"===t.overlayStatus?"Repair":"Install",r=i&&(n?t.activationAvailable:t.installActionAvailable&&"active"!==t.overlayStatus);return(0,Pa.jsxs)("div",{className:"grid gap-1 text-xs text-modiff-text","data-testid":`optional-runtime-${t.id}`,children:[(0,Pa.jsxs)("span",{children:[t.label," (",Oa(t),"):"," ",qa(t,e.processLoadStatus)]}),f?.profileId===t.id?(0,Pa.jsxs)("div",{className:"flex items-center gap-2","data-testid":"optional-runtime-progress",children:[(0,Pa.jsx)("span",{children:f.progress.message}),["queued","running"].includes(f.status)?(0,Pa.jsx)(Ge,{size:"compact",disabled:null!==o,onClick:S,children:"Cancel"}):null]}):null,(0,Pa.jsx)("div",{className:"flex gap-1.5",children:r?(0,Pa.jsx)(Ge,{size:"compact",disabled:null!==o||e.installBusy,loading:o===l.toLowerCase(),onClick:()=>N(t,n),children:l}):null})]},t.id)}):(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No optional runtimes."}),e&&$&&e.profiles.some(e=>e.activationAvailable&&e.cutoverReady&&"qualified"===e.contractState)?(0,Pa.jsx)(Ge,{size:"compact",disabled:null!==o||e.installBusy,loading:"rollback"===o,onClick:()=>C(R?"Roll back optional runtime to base?":"Roll back optional runtime?",R?"Remove the broken active optional environment and restart MoDiff with the validated base runtime. Active and queued runs must be stopped first.":"Select the previous validated optional environment and restart MoDiff. Active and queued runs must be stopped first.","Rollback",async()=>{Ee((await za("/runtime/optional-runtimes/rollback",{consent:!0},Jt)).message??"Optional runtime rollback selected.",{variant:"success"}),await a()}),children:R?"Rollback to base":"Rollback"}):null]}),(0,Pa.jsxs)(Xe,{label:"Optimizations",panelClassName:"grid gap-2 pt-2",children:[m?(0,Pa.jsxs)("div",{className:"flex items-center justify-between gap-2 rounded-modiff-compact border border-modiff-warning p-2",children:[(0,Pa.jsx)("span",{className:"text-xs text-modiff-warning",children:m}),(0,Pa.jsx)(Ge,{size:"compact",loading:c,onClick:()=>{v().catch(()=>{})},children:"Retry"})]}):c&&!i?(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Loading runtime optimizations…"}):null,i?.capabilities.map(e=>{let t=ot(e.documentation);return(0,Pa.jsx)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:(0,Pa.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,Pa.jsxs)("div",{className:"min-w-0",children:[(0,Pa.jsx)("span",{className:"text-sm font-semibold text-modiff-text",children:e.label}),(0,Pa.jsx)("p",{className:"mt-1 text-xs text-modiff-subtle-text",children:e.summary}),e.disabledReason?(0,Pa.jsx)("p",{className:"mt-1 text-xs text-modiff-warning",children:e.disabledReason}):null]}),(0,Pa.jsxs)("div",{className:"flex shrink-0 flex-wrap gap-1.5",children:[(0,Pa.jsx)(Ge,{size:"compact",disabled:!t||null!==o,onClick:()=>{t&&window.open(t,"_blank","noopener,noreferrer")},children:"Docs"}),e.canEnable?(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(Ge,{size:"compact",disabled:null!==o,tone:e.enabled?"primary":"secondary",loading:o===`toggle:${e.id}`,onClick:()=>{w(`toggle:${e.id}`,"/runtime/optimizations/enable",{capabilityId:e.id,enabled:!e.enabled})},children:e.enabled?"Enabled":"Enable"}),(0,Pa.jsx)(Ge,{size:"compact",disabled:null!==o,loading:o===`probe:${e.id}`,onClick:()=>{w(`probe:${e.id}`,"/runtime/optimizations/probe",{capabilityId:e.id})},children:"Probe"})]}):null]})]})},e.id)}),I.map(e=>(0,Pa.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:[(0,Pa.jsxs)("span",{className:"text-xs text-modiff-text",children:["Review ",e.capabilityId," · ",e.modelType||e.artifact||"workload"]}),(0,Pa.jsx)(Ge,{size:"compact",loading:o===`qualify:${e.id}`,disabled:null!==o,onClick:()=>{w(`qualify:${e.id}`,"/runtime/optimizations/qualify",{receiptId:e.id,outputReviewed:!0})},children:"Reviewed"})]},e.id))]})]})}var Da=/^[0-9a-f]{40}$/,Ea=/^sha256:canonical-json:[0-9a-f]{64}$/,Ga=/^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/,La=new Set(["installing","missing","ready","repair_required","unavailable"]);function Fa(e,t){if(!e||"object"!=typeof e||Array.isArray(e))throw Error(`${t} must be an object.`);return e}function Ha(e,t,a){if("string"!=typeof e||!e||e.length>512||a&&!a.test(e))throw Error(`${t} is invalid.`);return e}function Va(e,t){if(!Number.isSafeInteger(e)||Number(e)<0)throw Error(`${t} is invalid.`);return Number(e)}function Wa(e,t){if("boolean"!=typeof e)throw Error(`${t} is invalid.`);return e}function Ka(e){let t=Fa(e,"Template Gallery status");if(!1!==t.error||1!==t.schemaVersion)throw Error("Template Gallery status reports an unsupported contract.");let a=Ha(t.status,"Template Gallery status");if(!La.has(a))throw Error("Template Gallery status is unknown.");let s={status:a,installed:Wa(t.installed,"Template Gallery installed state"),complete:Wa(t.complete,"Template Gallery completion state"),repairRequired:Wa(t.repairRequired,"Template Gallery repair state"),installing:Wa(t.installing,"Template Gallery installation state"),...void 0===t.repoId?{}:{repoId:Ha(t.repoId,"Template Gallery repository",Ga)},...void 0===t.revision?{}:{revision:Ha(t.revision,"Template Gallery revision",Da)},...void 0===t.assetSetId?{}:{assetSetId:Ha(t.assetSetId,"Template Gallery asset set",Ea)},...void 0===t.assetCount?{}:{assetCount:Va(t.assetCount,"Template Gallery asset count")},...void 0===t.totalBytes?{}:{totalBytes:Va(t.totalBytes,"Template Gallery byte size")},...void 0===t.code?{}:{code:Ha(t.code,"Template Gallery status code")},...void 0===t.message?{}:{message:Ha(t.message,"Template Gallery status message")}};if("ready"===s.status&&(!s.installed||!s.complete||s.repairRequired||s.installing)||"missing"===s.status&&(s.installed||s.complete||s.repairRequired)||"repair_required"===s.status&&!s.repairRequired||"installing"===s.status&&!s.installing||"unavailable"!==s.status&&(!s.repoId||!s.revision||!s.assetSetId)||"ready"===s.status&&(void 0===s.assetCount||void 0===s.totalBytes))throw Error("Template Gallery status fields conflict.");return s}function Ua(e){let t=Fa(e,"Template Gallery plan");if(!1!==t.error||1!==t.schemaVersion)throw Error("Template Gallery plan reports an unsupported contract.");let a={repoId:Ha(t.repoId,"Template Gallery repository",Ga),repoType:Ha(t.repoType,"Template Gallery repository type"),revision:Ha(t.revision,"Template Gallery revision",Da),assetSetId:Ha(t.assetSetId,"Template Gallery asset set",Ea),assetCount:Va(t.assetCount,"Template Gallery asset count"),totalBytes:Va(t.totalBytes,"Template Gallery total bytes"),downloadBytes:Va(t.downloadBytes,"Template Gallery download bytes"),stagingBytes:Va(t.stagingBytes,"Template Gallery staging bytes"),reservationBytes:Va(t.reservationBytes,"Template Gallery reservation bytes"),queuedReservationBytes:Va(t.queuedReservationBytes,"Model queue reservation bytes"),reserveBytes:Va(t.reserveBytes,"Safety reserve bytes"),cacheFreeBytes:Va(t.cacheFreeBytes,"Gallery cache free bytes"),destinationFreeBytes:Va(t.destinationFreeBytes,"Gallery destination free bytes"),sameFilesystem:Wa(t.sameFilesystem,"Template Gallery filesystem identity"),sizeKnown:Wa(t.sizeKnown,"Template Gallery size state"),fitsWithQueue:Wa(t.fitsWithQueue,"Template Gallery fit state"),installed:Wa(t.installed,"Template Gallery installed state"),repairRequired:Wa(t.repairRequired,"Template Gallery repair state"),repairReason:null===t.repairReason||void 0===t.repairReason?null:Ha(t.repairReason,"Template Gallery repair reason"),installing:Wa(t.installing,"Template Gallery installation state")};if("dataset"!==a.repoType||a.reservationBytes!==a.downloadBytes+a.stagingBytes||a.installed&&a.repairRequired||a.installed&&(0!==a.downloadBytes||0!==a.stagingBytes||0!==a.reservationBytes))throw Error("Template Gallery plan fields conflict.");return a}function Qa(e){let t=Fa(e,"Template Gallery install result");if(!1!==t.error||!0!==t.complete||"boolean"!=typeof t.alreadyInstalled)throw Error("Template Gallery install did not complete.");let a=Ua({...Fa(t.plan,"Template Gallery result plan"),error:!1});if(void 0===t.result){if(!t.alreadyInstalled)throw Error("Template Gallery install result is missing.");return{complete:!0,alreadyInstalled:!0,plan:a}}let s=Fa(t.result,"Installed Template Gallery receipt");if(!0!==s.installed||!0!==s.complete||!1!==s.repairRequired||s.assetSetId!==a.assetSetId||s.repoId!==a.repoId||s.revision!==a.revision||s.assetCount!==a.assetCount||s.totalBytes!==a.totalBytes)throw Error("Installed Template Gallery receipt does not match its plan.");return{complete:!0,alreadyInstalled:!1,plan:a,result:{installed:!0,complete:!0,repairRequired:!1,assetCount:Va(s.assetCount,"Installed Template Gallery asset count"),totalBytes:Va(s.totalBytes,"Installed Template Gallery byte size"),assetSetId:Ha(s.assetSetId,"Installed Template Gallery asset set",Ea),repoId:Ha(s.repoId,"Installed Template Gallery repository",Ga),revision:Ha(s.revision,"Installed Template Gallery revision",Da),restartRequired:Wa(s.restartRequired,"Template Gallery restart requirement")}}}function Ja(e){if(!e||e<=0)return"0 B";let t=["B","KiB","MiB","GiB","TiB"],a=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),s=e/1024**a;return`${s.toFixed(s>=10||0===a?0:1)} ${t[a]}`}function Za(e,t){return t&&!e?"Checking":e?"ready"===e.status?"Ready":"installing"===e.status?"Installing":"repair_required"===e.status?"Repair required":"missing"===e.status?"Not installed":"Unavailable":"Unavailable"}function Xa(){let e=ta(e=>e.setAlertOpener),[t,a]=(0,Ta.useState)(null),[s,i]=(0,Ta.useState)(!0),[n,l]=(0,Ta.useState)(!1),[r,o]=(0,Ta.useState)(null),d=(0,Ta.useRef)(0),u=(0,Ta.useCallback)(async()=>{let e=++d.current;i(!0);try{let t=await Xt(`${ve.serverAddress}/template_gallery/status`,{timeoutMs:12e4,parse:Ka});if(e!==d.current)return;a(t),o(null)}catch(t){if(e!==d.current)return;o(aa(t,"Could not inspect Template Gallery assets."))}finally{e===d.current&&i(!1)}},[]);(0,Ta.useEffect)(()=>{u()},[u]);let m="ready"===t?.status,p="missing"===t?.status||"repair_required"===t?.status;return(0,Pa.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"template-gallery-setup-card",children:[(0,Pa.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Pa.jsxs)("div",{className:"flex items-center gap-2",children:[(0,Pa.jsx)(c,{size:15,className:"text-hf-yellow"}),(0,Pa.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Template Gallery assets"})]}),(0,Pa.jsx)(ye,{label:Za(t,s),tone:m?"success":r||t?.repairRequired?"error":"warning",title:r||t?.message||t?.assetSetId})]}),(0,Pa.jsx)("p",{className:"mt-2 text-xs leading-5 text-modiff-subtle-text",children:"Byte-pinned Gallery previews and default workflow inputs are installed from the app's immutable public Dataset. Model caches are separate and are never removed by this action."}),t?.complete&&(0,Pa.jsxs)("p",{className:"mt-1 text-xs text-modiff-green",children:[t.assetCount??0," files · ",Ja(t.totalBytes)," · ",t.revision?.slice(0,12)]}),r&&(0,Pa.jsx)("p",{className:"mt-1 break-words text-xs text-modiff-red",children:r}),(0,Pa.jsxs)("div",{className:"mt-2 flex flex-wrap gap-2",children:[p&&(0,Pa.jsx)(Ge,{size:"compact",loading:n||t?.installing,disabled:s||n||t?.installing,onClick:()=>{(async()=>{if(!n){l(!0);try{let t=await Xt(`${ve.serverAddress}/template_gallery/plan`,{timeoutMs:12e4,parse:Ua});if(!t.sizeKnown||!t.fitsWithQueue)throw Error(`The verified ${Ja(t.reservationBytes)} Gallery reservation does not fit alongside ${Ja(t.queuedReservationBytes)} of active model reservations and the ${Ja(t.reserveBytes)} safety reserve.`);e({title:t.repairRequired?"Repair Template Gallery assets?":"Install Template Gallery assets?",message:`Download and hash-verify ${t.assetCount} files (${Ja(t.totalBytes)}) from ${t.repoId} at immutable revision ${t.revision.slice(0,12)}. Existing model downloads and cached models will not be removed.`,confirmText:t.repairRequired?"Repair":"Install",cancelText:"Cancel",onConfirm:()=>{e(null),(async()=>{try{Ee((await Xt(`${ve.serverAddress}/template_gallery/install`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({}),timeoutMs:864e5,parse:Qa})).result?.restartRequired?"Template Gallery installed. Restart MoDiff after active downloads finish to serve it locally.":"Template Gallery is already installed.",{variant:"success"}),await u()}catch(e){let t=aa(e,"Template Gallery installation failed.");o(t),Ee(t,{variant:"error"})}finally{l(!1)}})()},onCancel:()=>{e(null),l(!1)}})}catch(e){let t=aa(e,"Could not plan the Template Gallery installation.");o(t),Ee(t,{variant:"error"}),l(!1)}}})()},"data-testid":"template-gallery-install",children:t?.repairRequired?"Check & repair":"Check & install"}),(0,Pa.jsx)(Ge,{size:"compact",tone:"secondary",icon:(0,Pa.jsx)(C,{size:14}),disabled:s||n,onClick:()=>{u()},"data-testid":"template-gallery-refresh",children:"Refresh"})]})]})}var Ya=(0,Ta.lazy)(()=>pe(()=>import(`./CompositeMigrationCard.js?v=5bcd315505f5670a`).then(e=>({default:e.CompositeMigrationCard})),[]));function es(e){if(!e||e<=0)return"0 B";let t=["B","KB","MB","GB","TB"],a=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),s=e/1024**a;return`${s.toFixed(s>=10||0===a?0:1)} ${t[a]}`}function ts(e){return e&&0!==Object.keys(e).length?Object.entries(e).slice(0,5).map(([e,t])=>`${e}: ${t}`).join(", "):"no files"}function as(e){return e?e.details?`${e.message} ${e.details}`:e.message:""}var ss=["environment","package","backend","model_integrity","model","hardware_fit","asset","user_input","graph"],is={environment:"Environment",package:"Packages",backend:"Backend",model_integrity:"Model integrity",model:"Models",hardware_fit:"Hardware fit",asset:"Assets",user_input:"Inputs",graph:"Graph"};function ns(e){return"success"===e?"success":"warning"===e?"warning":"info"===e?"info":"error"}function ls({children:e,tone:t="muted",testId:a}){return(0,Pa.jsx)("p",{"data-testid":a,className:_e("break-words text-xs leading-5","muted"===t&&"text-modiff-subtle-text","success"===t&&"text-modiff-green","error"===t&&"text-modiff-red","warning"===t&&"text-hf-orange"),children:e})}function rs({children:e,disabled:t,onClick:a,progress:s,testId:i,title:n,tone:l}){return(0,Pa.jsx)(ye,{action:"Installing"===("string"==typeof e?e:void 0)?"installing":a?"install":"success"===l?"ready":"warning"===l||"error"===l?"missing":"details",className:"min-h-7 px-2 py-0.5 text-xs",disabled:t,label:e,onClick:a,progress:s,testId:i,title:n,tone:"default"===l?"neutral":l})}function os(){let e=Ft(e=>e.hfCache),a=Ft(e=>e.localModels),s=Ft(e=>e.modelCacheDiagnostics),i=Ft(e=>e.studioModelCapabilities),n=Ft(e=>e.runtimeStatus),l=Ft(e=>e.runtimeError),r=Ft(e=>e.hfDownloadProgress),d=Ft(e=>e.refreshModelIndexes),c=Ft(e=>e.fetchRuntimeStatus),u=Ft(e=>e.fetchStudioModelCapabilities),m=Ft(e=>e.installHfModel),p=ia(e=>e.form),f=ia(e=>e.activeTemplateId),h=ia(e=>e.graphBinding),g=ia(e=>e.sourceOutputId);At(e=>[e.nodes.map(e=>{let t=Object.entries(e.data.params).filter(([e])=>/repo|model|checkpoint|ckpt|safetensors|lora|vae|controlnet|adapter/i.test(e)).map(([e,t])=>`${e}:${JSON.stringify(t.value??t.default??"")}`).join(",");return`${e.id}:${e.data.module}:${e.data.action}:${e.data.uiState?.disabled?"disabled":"enabled"}:${t}`}).join("|"),e.edges.map(e=>`${e.id}:${e.source}:${e.sourceHandle??""}->${e.target}:${e.targetHandle??""}`).join("|")].join("||"));let b=ia(e=>e.autoResourcePlans),j=ia(e=>e.setAutoResourcePlans),v=ta(e=>e.setModelManagerOpener),y=ta(e=>e.setRightPanelOpen),k=ta(e=>e.setRightPanelTab),w=Ct(e=>e.showIssues),N=Tt(e=>e.sid),S=Tt(e=>e.isConnected),I=Object.values(r).filter(Le).length,R=Object.entries(r).filter(([,e])=>Le(e)||X(e)||ue(e)),$=Ze(),M=!!(f||h||g),T=!(!h||!ma(h))||!M&&!h&&($.nodeCount>0||$.blockingIssues.length>0),P=Ut(p),_=yt[p.modelType],q=M&&_?[{profile:_,mode:p.mode,status:ct(_,e,a,s),backendCapability:i.find(e=>e.modelType===_.modelType),autoPlanKey:P,autoPlan:b[P]}]:[],O=M&&_?Wt({autoResourcePlan:b[P],form:p,hfCache:e,includePipeline:!1,localModels:a,modelCacheDiagnostics:s}):[],B=T?st({references:$.modelRefs,hfCache:e,localModels:a,modelCacheDiagnostics:s}).filter(e=>!e.status.runnable):[],D=$.blockingIssues[0]??null,E=function(e){return new Set(e.modelRefs.map(e=>`${e.kind}:${e.value}`)).size}($),G=ae({sid:N,isConnected:S,includeStudio:!T}),L=(0,Ta.useMemo)(()=>ss.map(e=>({category:e,label:is[e],issues:G.blockingIssues.filter(t=>t.category===e)})).filter(e=>e.issues.length>0),[G.blockingIssues]),F=async()=>{if(!M)return;let e=await lt([p],[P]),t={};e.forEach(e=>{let a=e.planKey??P;a&&(t[a]=e)}),j(t)},H=async()=>{await d(!0,{invalidateAutoPlans:!1}),await F()},V=()=>{y(!0),k("studio")},W=()=>{w(G.issues)},K=e=>"install_model"===e.action&&e.repoId?(0,Pa.jsx)(Ge,{tone:"primary",icon:(0,Pa.jsx)(A,{size:15}),onClick:()=>{(async e=>{try{await m(e,N),await H()}catch(e){console.error(e)}})(e.repoId)},children:"Install"}):"open_model_manager"===e.action?(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(z,{size:15}),onClick:()=>v({nodeId:null,fieldKey:null}),children:"Models"}):"refresh_cache"===e.action?(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(C,{size:15}),onClick:()=>{H()},children:"Refresh"}):"select_image"===e.action?(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(t,{size:15}),onClick:V,children:"Studio"}):(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(o,{size:15}),onClick:W,children:"Review"});(0,Ta.useEffect)(()=>{c(),u()},[c,u]),(0,Ta.useEffect)(()=>{if(!M)return;let e=!1;return lt([p],[P]).then(t=>{if(e)return;let a={};t.forEach(e=>{let t=e.planKey??P;t&&(a[t]=e)}),j(a)}).catch(e=>{console.error(e)}),()=>{e=!0}},[p,M,e.length,a.length,n?.ready,j,P]);let U=async e=>{try{await m(e.repo,N,{repair:e.repair,revision:e.revision,files:e.files}),await H()}catch(e){console.error(e)}},Q=async e=>{try{await et({modelType:e.modelType}),await F()}catch(e){console.error(e)}};return(0,Pa.jsxs)("div",{className:"grid gap-3 p-3 text-sm text-modiff-text","data-testid":"setup-panel",children:[(0,Pa.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Pa.jsx)("div",{children:(0,Pa.jsx)("h2",{className:"text-base font-bold text-modiff-text",children:"Model Health"})}),(0,Pa.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(z,{size:15}),"data-testid":"setup-models",onClick:()=>v({nodeId:null,fieldKey:null}),children:"Models"}),(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(C,{size:15}),"data-testid":"setup-refresh",onClick:()=>{H()},children:"Refresh"})]})]}),(0,Pa.jsx)(_a,{error:l,status:n}),(0,Pa.jsx)(Ba,{}),(0,Pa.jsx)(Xa,{}),L.length>0&&(0,Pa.jsxs)("section",{className:"grid gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"setup-current-issues",children:[(0,Pa.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Pa.jsx)("div",{children:(0,Pa.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Current issues"})}),(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(o,{size:15}),onClick:W,children:"Review all"})]}),L.map(e=>(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsxs)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:[e.label," (",e.issues.length,")"]}),(0,Pa.jsx)("div",{className:"grid gap-2",children:e.issues.map(e=>(0,Pa.jsx)(xa,{tone:ns(e.severity),title:e.message,action:K(e),testId:`setup-current-issue-${e.id}`},e.id))})]},e.category))]}),R.length>0&&(0,Pa.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"setup-downloads",children:[(0,Pa.jsxs)("div",{className:"mb-2 flex flex-wrap items-center gap-2",children:[(0,Pa.jsx)("h3",{className:"text-sm font-bold",children:"Downloads"}),(0,Pa.jsxs)(rs,{tone:I>0?"default":"warning",title:`${I} active model download${1===I?"":"s"}`,children:[I," active"]})]}),(0,Pa.jsx)("div",{className:"grid gap-2",children:R.map(([e,t])=>(0,Pa.jsx)(qe,{repoId:e,progress:t,testId:`setup-download-${e}`},e))})]}),(0,Pa.jsxs)(Xe,{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"setup-advanced-diagnostics",label:(0,Pa.jsxs)("span",{className:"flex items-center gap-2",children:[(0,Pa.jsx)(x,{size:15,className:"text-hf-yellow"}),"Advanced diagnostics"]}),buttonClassName:"p-0",panelClassName:"mt-3 grid gap-3",children:[(0,Pa.jsx)(Ta.Suspense,{fallback:(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Loading migration tools…"}),children:(0,Pa.jsx)(Ya,{})}),T?(0,Pa.jsxs)("section",{className:"grid gap-2","data-testid":"setup-workflow-model-health",children:[(0,Pa.jsxs)("div",{className:"flex items-center gap-2",children:[(0,Pa.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Current graph"}),(0,Pa.jsx)(rs,{tone:"default",title:"Models referenced by the visible canvas graph are shown here.",children:"Contextual"})]}),(0,Pa.jsx)(Et,{tone:D||B.length>0?"error":"success",title:D?"Graph needs attention":"Graph model checks",meta:as(D)||`${$.nodeCount} nodes, ${$.outputPathCount} connected output path${1===$.outputPathCount?"":"s"}.`,testId:"setup-current-graph-health",children:(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsxs)("div",{className:"flex flex-wrap gap-1.5",children:[(0,Pa.jsxs)(rs,{tone:$.outputPathCount>0?"success":"error",title:`${$.outputPathCount} connected output path${1===$.outputPathCount?"":"s"}.`,children:[$.outputPathCount," outputs"]}),(0,Pa.jsxs)(rs,{tone:B.length>0?"warning":"success",title:`${E} detected model reference${1===E?"":"s"}.`,children:[E," models"]})]}),B.length>0&&(0,Pa.jsx)("div",{className:"grid gap-2 border-t border-modiff-border pt-2",children:B.map(e=>(0,Pa.jsx)(wa,{activeInstallCount:I,actionTestId:`setup-install-graph-requirement-${e.id}`,installProgress:r,onInstall:e=>U(e),onUseLocal:e=>v({nodeId:null,fieldKey:null,focus:{label:e.label,repo:e.repo,requirementId:e.id,source:"setup"}}),requirement:e,testId:`setup-graph-requirement-${e.id}`},e.id))})]})})]}):M?(0,Pa.jsxs)("section",{className:"grid gap-2","data-testid":"setup-workflow-model-health",children:[(0,Pa.jsxs)("div",{className:"flex items-center gap-2",children:[(0,Pa.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Workflow model"}),(0,Pa.jsx)(rs,{tone:"default",title:"Only models used by the current workflow are shown here.",children:"Contextual"})]}),q.map(({autoPlan:e,backendCapability:t,profile:a,status:s})=>(0,Pa.jsx)(ds,{activeInstallCount:I,autoPlan:e,form:p,backendCapability:t,hfDownloadProgress:r,onInstall:U,onUseLocal:e=>v({nodeId:null,fieldKey:null,focus:{label:e.label,repo:e.repo,requirementId:e.id,source:"setup"}}),onClearHistory:Q,profile:a,requirements:O,status:s},a.modelType))]}):(0,Pa.jsxs)("section",{className:"flex items-center gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-xs text-modiff-subtle-text","data-testid":"setup-workflow-model-empty",children:[(0,Pa.jsx)(o,{size:15,className:"shrink-0 text-modiff-subtle-text"}),(0,Pa.jsx)("span",{children:"Workflow model checks appear after you create, update, or restore a workflow."})]}),s&&(0,Pa.jsxs)(Xe,{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3",label:(0,Pa.jsxs)("span",{className:"flex items-center gap-2",children:[(0,Pa.jsx)(x,{size:15,className:"text-hf-yellow"}),"Diagnostics and scanned locations"]}),buttonClassName:"p-0",panelClassName:"mt-3 grid gap-3",children:[(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)("h3",{className:"mb-1 text-sm font-bold",children:"Scanned model locations"}),(s.locations||[]).map(e=>{let t=e.repo_count??e.file_count??0,a=void 0===e.repo_count?`${t} files`:`${t} repos`,s=[e.external_package_count?`${e.external_package_count} external packages`:null,e.compatible_hf_repo_count?`${e.compatible_hf_repo_count} HF-compatible repos`:null,e.model_file_bytes?`${es(e.model_file_bytes)} model weights`:null].filter(Boolean).join(" | ");return(0,Pa.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,Pa.jsxs)(ls,{tone:e.exists?"muted":"warning",children:[e.label,": ",e.path||"default"," | ",e.exists?"exists":"missing"," |"," ",a," | ",!1===e.runnable?"discovered but not runnable":"runnable"]}),s&&(0,Pa.jsxs)(ls,{children:[s," | ",ts(e.extension_counts)]}),e.reason&&!1===e.runnable&&(0,Pa.jsx)(ls,{tone:"warning",children:e.reason}),e.error&&(0,Pa.jsx)(ls,{tone:"error",children:e.error})]},`${e.label}-${e.path}`)})]}),(s.external_model_packages?.length??0)>0&&(0,Pa.jsxs)("section",{"data-testid":"setup-external-models",children:[(0,Pa.jsx)("h3",{className:"mb-1 text-sm font-bold",children:"Detected external packages"}),(0,Pa.jsx)("div",{className:"grid gap-2",children:s.external_model_packages?.slice(0,8).map(e=>(0,Pa.jsxs)(Et,{tone:e.runnable?"success":"warning",title:e.label,meta:`${e.format} | ${e.file_count??0} files | ${es(e.model_file_bytes)}`,children:[(0,Pa.jsx)(ls,{children:e.path}),(0,Pa.jsx)(ls,{tone:e.runnable?"success":"warning",children:e.reason})]},`${e.source}-${e.path}`))})]}),(s.hf_compatible_external_repos?.length??0)>0&&(0,Pa.jsxs)("section",{"data-testid":"setup-external-hf-repos",children:[(0,Pa.jsx)("h3",{className:"mb-1 text-sm font-bold",children:"HF-compatible external repos"}),(0,Pa.jsx)("div",{className:"grid gap-2",children:s.hf_compatible_external_repos?.slice(0,8).map(e=>(0,Pa.jsxs)(Et,{tone:"warning",title:e.repo_id,meta:e.source,children:[(0,Pa.jsx)(ls,{children:e.path}),(0,Pa.jsx)(ls,{tone:"warning",children:e.reason})]},`${e.source}-${e.path}`))})]})]})]})]})}function ds({activeInstallCount:e,autoPlan:t,form:a,backendCapability:s,hfDownloadProgress:i,onClearHistory:n,onInstall:l,onUseLocal:r,profile:o,requirements:d,status:c}){let u=kt(t,a),m=ua(t,a),p=bt(t,a),f=t?p:c.runnable,h=u?.repo??o.defaultRepo,x=i[h],g=Le(x),b=s?.inpaintContract??o.inpaintContract,j=St(t,a),v=Dt(t,a),y=f?"success":"unsuitable"===v.state?"error":"warning",k=f?"success":"error"===y?"error":"warning",w=m?.resolvedArtifact??m?.artifact??u?.repo??o.defaultRepo,C=f?j:g?"Installing":X(x)?"Retry":u?.actionLabel??"Install",N=g||!f&&e>=2;return(0,Pa.jsx)(Et,{testId:`setup-model-${o.modelType}`,tone:y,title:o.label,meta:w,action:(0,Pa.jsx)(rs,{tone:k,title:f?j:u?.reason??u?.repo??j,disabled:N,progress:g?ja(x):null,testId:`setup-install-${o.modelType}`,onClick:!f&&u?()=>{l(u)}:void 0,children:C}),children:(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsxs)("div",{className:"flex flex-wrap gap-1.5",children:[(0,Pa.jsx)(rs,{tone:c.runnable?"success":"warning",title:c.reason,children:c.runnable?"Cached":"Missing"}),(0,Pa.jsx)(rs,{tone:f?"success":"error"===y?"error":"warning",title:t?.blockingReason||m?.reason||u?.reason||t?.willNotWorkReason||j,children:"Auto"}),(0,Pa.jsx)(rs,{tone:s?"success":"warning",title:s?`Backend modes: ${(s.modes||[]).join(", ")}`:"Backend capability metadata not reported for this profile.",children:"Backend"}),(0,Pa.jsx)(rs,{tone:"success"===v.severity?"success":"error"===v.severity?"error":"info"===v.severity?"default":"warning",title:`${v.summary} ${v.detail}`,children:"Compatibility"}),b&&(0,Pa.jsx)(rs,{tone:b.available?"success":"warning",title:b.available?"Native mask execution available.":b.reason,children:"Inpaint"})]}),c.matchingExternalPackages.length>0&&(0,Pa.jsx)(rs,{tone:"warning",title:`Matching external package: ${c.matchingExternalPackages.slice(0,3).join(", ")}`,children:"External package"}),c.matchingExternalRepos.length>0&&(0,Pa.jsx)(rs,{tone:"warning",title:`Matching HF-compatible external repo: ${c.matchingExternalRepos.slice(0,3).join(", ")}`,children:"External repo"}),x&&(0,Pa.jsx)(qe,{testId:`setup-download-progress-${o.modelType}`,repoId:h,progress:x}),(t?.failureHistory?.length??0)>0&&(0,Pa.jsx)("div",{children:(0,Pa.jsx)(Ge,{tone:"secondary","data-testid":`setup-clear-auto-history-${o.modelType}`,onClick:()=>{n(o)},children:"Clear Auto history"})}),d.length>0&&(0,Pa.jsx)("div",{className:"grid gap-2 border-t border-modiff-border pt-2",children:d.map(t=>(0,Pa.jsx)(wa,{activeInstallCount:e,actionTestId:`setup-install-requirement-${t.id}`,installProgress:i,onInstall:e=>l(e),onUseLocal:r,requirement:t,testId:`setup-model-requirement-${t.id}`},t.id))})]})})}function cs({children:e,disabled:t,icon:a,onClick:s,testId:i}){return(0,Pa.jsx)(Ge,{tone:"primary",icon:a,"data-testid":i,disabled:t,onClick:s,children:(0,Pa.jsx)("span",{children:e})})}function us(e){if("number"!=typeof e||!Number.isFinite(e))return"-";if(e<1e3)return`${Math.max(0,Math.round(e))}ms`;let t=e/1e3;return t<60?`${t.toFixed(+(t<10))}s`:`${Math.floor(t/60)}:${Math.round(t%60).toString().padStart(2,"0")}`}function ms(e){return"number"==typeof e&&Number.isFinite(e)?us(1e3*e):null}function ps(e){return"number"==typeof e.durationMs?e.durationMs:e.completedAtMs&&e.startedAtMs?Math.max(0,e.completedAtMs-e.startedAtMs):void 0}function fs({children:e,fallbackStatus:t,focused:a=!1,pending:s=!1,severity:i="default",task:n,taskId:l,testId:r}){if(!n)return null;let o=jt(n,l),d=xt(vt(n))||n.error||n.message||`${o} · ${t}`;return(0,Pa.jsx)(na,{content:d,placement:"top",children:c=>(0,Pa.jsx)(Ge,{...c,align:"left",fullWidth:!0,tone:"secondary",loading:s,"data-testid":r,"aria-label":`Open ${o} run details. ${d}`,onClick:()=>{ft(Qt(n,l,t))},className:_e("h-auto! min-w-0 rounded-none p-2","success"===i&&"border-modiff-green/60","error"===i&&"border-modiff-red/70 bg-modiff-red/10 hover:border-modiff-red",a&&"ring-2 ring-hf-yellow"),children:e})})}function hs(){let e=ra(e=>e.currentTask),t=ra(e=>e.queuedTasks),s=ra(e=>e.failedTasks),i=ra(e=>e.sessionRuns),n=ra(e=>e.fetchTasks),l=ra(e=>e.clearCompletedSessionRuns),r=ra(e=>e.focusedTaskId),o=ta(e=>e.runActivityPendingTaskId),d=Object.entries(t),c=Object.entries(s).slice(0,8),u=i.filter(e=>"completed"===e.status||"failed"===e.status||"cancelled"===e.status).slice(0,10);return(0,Ta.useEffect)(()=>{n()},[n]),(0,Ta.useEffect)(()=>{r&&document.getElementById(`queue-task-${r}`)?.scrollIntoView({block:"nearest",behavior:"smooth"})},[r,e,t,i,s]),(0,Pa.jsxs)("div",{className:"p-3 text-sm text-modiff-text","data-testid":"queue-panel",children:[(0,Pa.jsxs)("div",{className:"mb-2 flex flex-wrap gap-2",children:[(0,Pa.jsx)(cs,{icon:(0,Pa.jsx)(C,{size:15}),testId:"queue-refresh",onClick:()=>{n()},children:"Refresh"}),(0,Pa.jsx)(cs,{icon:(0,Pa.jsx)(j,{size:15}),testId:"queue-stop",onClick:()=>{(async()=>{try{Ee((await Me()).message||"Stop requested",{variant:"success",autoHideDuration:3e3})}catch(e){Ee(aa(e,"Could not stop the current execution."),{variant:"error",autoHideDuration:5e3})}})()},disabled:!e,children:"Stop"})]}),(0,Pa.jsx)("h2",{className:"mb-2 text-sm font-bold",children:"Current run"}),e?(0,Pa.jsx)(fs,{fallbackStatus:"running",focused:r===e.task_id,pending:o===e.task_id,task:e,taskId:e.task_id||"current",testId:"queue-current-run",children:(0,Pa.jsxs)("span",{id:`queue-task-${e.task_id||"current"}`,className:"block min-w-0 flex-1",children:[(0,Pa.jsxs)("span",{className:"flex items-center justify-between gap-2",children:[(0,Pa.jsxs)("span",{className:"min-w-0",children:[(0,Pa.jsx)("span",{className:"block truncate text-sm",children:jt(e,e.task_id||"Running task")}),(0,Pa.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:e.message||e.task_id||e.sid})]}),(0,Pa.jsx)(N,{size:16,className:"flex-none text-hf-yellow"})]}),(e.current_step||e.eta_seconds||e.average_step_seconds)&&(0,Pa.jsxs)("span",{className:"mt-2 flex flex-wrap gap-x-3 gap-y-1 text-xs text-modiff-subtle-text",children:[e.current_step&&e.total_steps?(0,Pa.jsx)("span",{children:`Step ${e.current_step}/${e.total_steps}`}):null,ms(e.eta_seconds)?(0,Pa.jsx)("span",{children:`ETA ${ms(e.eta_seconds)}`}):null,ms(e.average_step_seconds)?(0,Pa.jsx)("span",{children:`${ms(e.average_step_seconds)}/step`}):null]}),(0,Pa.jsx)(He,{value:e.progress,className:"mt-2"})]})}):(0,Pa.jsx)("p",{className:"mb-4 text-xs text-modiff-subtle-text",children:"No current run."}),(0,Pa.jsx)("h2",{className:"mb-2 text-sm font-bold",children:"Queued runs"}),0===d.length?(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No queued runs."}):(0,Pa.jsx)("div",{className:"grid gap-2",children:d.map(([e,t])=>(0,Pa.jsxs)("div",{id:`queue-task-${e}`,"data-testid":`queue-run-${e}`,className:_e("flex items-stretch border border-modiff-border bg-modiff-surface",r===e&&"ring-2 ring-hf-yellow"),children:[(0,Pa.jsx)(Ge,{align:"left",fullWidth:!0,tone:"ghost",loading:o===(t.task_id||e),"aria-label":`Open ${jt(t,e)} run details`,onClick:()=>{ft(Qt(t,e,"queued"))},className:"h-auto! min-w-0 flex-1 rounded-none p-2",children:(0,Pa.jsxs)("span",{className:"min-w-0 flex-1",children:[(0,Pa.jsx)("span",{className:"block truncate text-sm",children:jt(t,e)}),(0,Pa.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:t.task_id||t.sid})]})}),(0,Pa.jsx)(Ge,{tone:"ghost",size:"dense",title:"Cancel queued task",onClick:()=>{(async e=>{try{await Ve(e),Ee("Queued task cancelled",{variant:"success",autoHideDuration:2200}),await n()}catch(e){Ee(aa(e,"Could not cancel the queued task."),{variant:"error",autoHideDuration:5e3})}})(e)},icon:(0,Pa.jsx)(G,{size:15}),className:"h-auto! flex-none rounded-none border-l border-modiff-border px-2 text-modiff-subtle-text",children:(0,Pa.jsx)("span",{children:"Cancel"})})]},e))}),(0,Pa.jsxs)("div",{className:"mb-2 mt-4 flex items-center justify-between gap-2",children:[(0,Pa.jsx)("h2",{className:"text-sm font-bold",children:"Session history"}),(0,Pa.jsx)(Ge,{tone:"ghost",size:"dense",disabled:0===u.length,onClick:l,icon:(0,Pa.jsx)(a,{size:14}),className:"px-2 text-xs",children:"Clear finished"})]}),0===u.length?(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Completed runs will appear here for this browser session."}):(0,Pa.jsx)("div",{className:"grid gap-2",children:u.map(e=>(0,Pa.jsx)(fs,{fallbackStatus:e.status??"completed",focused:r===(e.task_id||e.id),pending:o===(e.task_id||e.id),severity:"failed"===e.status?"error":"completed"===e.status?"success":"default",task:e,taskId:e.task_id||e.id,testId:`queue-session-run-${e.id}`,children:(0,Pa.jsx)("span",{id:`queue-task-${e.task_id||e.id}`,className:"block min-w-0 flex-1",children:(0,Pa.jsxs)("span",{className:"flex items-start gap-2",children:["completed"===e.status?(0,Pa.jsx)(m,{size:16,className:"mt-0.5 flex-none text-modiff-green"}):(0,Pa.jsx)(G,{size:16,className:"mt-0.5 flex-none text-modiff-red"}),(0,Pa.jsxs)("span",{className:"min-w-0 flex-1",children:[(0,Pa.jsx)("span",{className:"block truncate text-sm",children:jt(e,e.task_id||e.id)}),(0,Pa.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:e.error||e.message||e.task_id||e.sid})]}),(0,Pa.jsx)("span",{className:"flex-none text-xs font-semibold text-modiff-subtle-text",children:us(ps(e))})]})})},e.id))}),(0,Pa.jsx)("h2",{className:"mb-2 mt-4 text-sm font-bold",children:"Failed runs"}),0===c.length?(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No failed runs recorded in this session."}):(0,Pa.jsx)("div",{className:"grid gap-2",children:c.map(([e,t])=>(0,Pa.jsx)(fs,{fallbackStatus:"failed",focused:r===e,pending:o===(t.task_id||e),severity:"error",task:t,taskId:e,testId:`queue-failed-run-${e}`,children:(0,Pa.jsxs)("span",{id:`queue-task-${e}`,className:"block min-w-0 flex-1",children:[(0,Pa.jsx)("span",{className:"block truncate text-sm text-modiff-red",children:jt(t,e)}),(0,Pa.jsx)("span",{className:"block break-all text-xs text-modiff-subtle-text",children:t.task_id||t.sid}),t.error&&(0,Pa.jsx)("span",{className:"block break-words text-xs text-modiff-red",children:t.error})]})},e))})]})}function xs(){let e=(0,Ta.useRef)(null),[i,n]=(0,Ta.useState)(""),[l,r]=(0,Ta.useState)(!1),{referenceImages:o,addReferenceImage:d,replaceReferenceImage:c,removeReferenceImage:u,moveReferenceImage:m,setReferenceImages:p,alphaMode:f,updateForm:h}=ia(se(e=>({referenceImages:e.form.referenceImages,alphaMode:e.form.alphaMode,addReferenceImage:e.addReferenceImage,replaceReferenceImage:e.replaceReferenceImage,removeReferenceImage:e.removeReferenceImage,moveReferenceImage:e.moveReferenceImage,setReferenceImages:e.setReferenceImages,updateForm:e.updateForm})));return(0,Pa.jsxs)("div",{children:[(0,Pa.jsx)(te,{title:"Image input workspace",action:(0,Pa.jsx)(Z,{title:"Upload images",onClick:()=>e.current?.click(),disabled:l,children:l?(0,Pa.jsx)(pa,{size:18}):(0,Pa.jsx)(t,{size:16})})}),(0,Pa.jsx)(U,{ref:e,"aria-label":"Upload reference images",accept:Ot(["image"]),multiple:!0,hidden:!0,onChange:e=>{(async e=>{if(!e||0===e.length)return;let t=at();r(!0);try{let a=[];for(let s of Array.from(e))a.push(...await Y(s,"images")),$t(t);p([...ia.getState().form.referenceImages,...a]),Zt(t),Oe()}catch(e){if(ea(e))return;Ee(aa(e,"Could not upload the reference image."),{variant:"error",autoHideDuration:5e3})}finally{r(!1)}})(e.target.files)}}),(0,Pa.jsxs)("div",{className:"mb-2 flex gap-2",children:[(0,Pa.jsx)(de,{"aria-label":"Reference image path or URL",value:i,onChange:e=>n(e.target.value),placeholder:"Image path or URL",className:"flex-1"}),(0,Pa.jsx)(Z,{title:"Add image",onClick:()=>{d(i),n(""),queueMicrotask(()=>Oe())},disabled:!i.trim(),children:(0,Pa.jsx)(s,{size:17})})]}),(0,Pa.jsx)(Ne,{"aria-label":"Reference image alpha handling",value:f,onValueChange:e=>{h({alphaMode:e}),queueMicrotask(()=>Oe())},options:[{value:"ignore",label:"Alpha: ignore"},{value:"add alpha",label:"Alpha: add alpha"},{value:"remove alpha",label:"Alpha: remove alpha"}],className:"mb-2 w-full"}),(0,Pa.jsx)("div",{className:"grid grid-cols-[repeat(auto-fit,minmax(86px,1fr))] gap-2",children:o.map((e,t)=>(0,Pa.jsxs)("div",{className:"border border-modiff-border bg-modiff-bg",children:[(0,Pa.jsx)(It,{src:oe(e),alt:e,fit:"cover",aspectRatio:"1 / 1",bordered:!1}),(0,Pa.jsx)(de,{"aria-label":`Reference image ${t+1} path`,value:e,onChange:e=>{c(t,e.target.value),queueMicrotask(()=>Oe())},className:"h-7 w-full border-0 bg-transparent px-1 text-xs"}),(0,Pa.jsxs)("div",{className:"flex justify-between",children:[(0,Pa.jsx)(Z,{title:"Move up",disabled:0===t,onClick:()=>{m(t,-1),queueMicrotask(()=>Oe())},children:(0,Pa.jsx)(H,{size:15})}),(0,Pa.jsx)(Z,{title:"Move down",disabled:t===o.length-1,onClick:()=>{m(t,1),queueMicrotask(()=>Oe())},children:(0,Pa.jsx)(b,{size:15})}),(0,Pa.jsx)(Z,{title:"Remove",onClick:()=>{u(t),queueMicrotask(()=>Oe())},children:(0,Pa.jsx)(a,{size:15})})]})]},`${e}-${t}`))})]})}function gs({isWorking:e,runBlocked:t,expertMode:a=!1,hasManagedForm:s=!0,hasComparePair:i,hasGalleryItems:n,onCompareLatest:l,onExport:o,onInterrupt:d,onRun:u,onUpdateGraph:m,onSavePrompt:p,onOpenGallery:f,onOpenTemplateGallery:x,onOpenSetup:g,onRestoreLatest:b,onRerunLatest:j}){let[v,y]=(0,Ta.useState)(!1),[k,N]=(0,Ta.useState)(""),S=(0,Ta.useMemo)(()=>[{id:"run",label:"Run workflow",detail:"Queue the current Studio graph.",icon:(0,Pa.jsx)(P,{size:15}),disabled:e||t,run:u},...a&&s?[{id:"update",label:"Sync graph",detail:"Reconcile Studio fields with visible graph nodes.",icon:(0,Pa.jsx)(C,{size:15}),disabled:e,run:m}]:[],{id:"interrupt",label:"Interrupt run",detail:"Ask the backend to stop the active or queued execution.",icon:(0,Pa.jsx)(E,{size:15}),run:d},...s?[{id:"save-prompt",label:"Save prompt",detail:"Store the current prompt as a reusable snippet.",icon:(0,Pa.jsx)(h,{size:15}),run:p}]:[],...a?[{id:"export",label:"Export API graph",detail:"Download the current runnable graph JSON.",icon:(0,Pa.jsx)(A,{size:15}),run:o}]:[],{id:"gallery",label:"Open gallery",detail:"Inspect, compare, restore, rerun, and export outputs.",icon:(0,Pa.jsx)(c,{size:15}),run:f},{id:"compare",label:"Compare latest",detail:"Open the latest two Gallery outputs side by side.",icon:(0,Pa.jsx)(r,{size:15}),disabled:!i,run:l},{id:"restore-latest",label:"Restore latest",detail:"Restore the newest Gallery workflow into Studio.",icon:(0,Pa.jsx)(q,{size:15}),disabled:!n,run:b},{id:"rerun-latest",label:"Rerun latest",detail:"Restore and queue the newest Gallery workflow.",icon:(0,Pa.jsx)(P,{size:15}),disabled:!n||e||t,run:j},{id:"templates",label:"Browse templates",detail:"Search categorized workflow templates for the current task.",icon:(0,Pa.jsx)(w,{size:15}),run:x},{id:"setup",label:"Open setup",detail:"Repair missing models and runtime readiness.",icon:(0,Pa.jsx)(z,{size:15}),run:g}],[a,s,i,n,e,l,o,d,f,g,x,b,j,u,p,m,t]),I=(0,Ta.useMemo)(()=>{let e=k.trim().toLowerCase();return e?S.filter(t=>`${t.label} ${t.detail}`.toLowerCase().includes(e)):S},[S,k]);return(0,Ta.useEffect)(()=>{let a=a=>{let s=a.ctrlKey||a.metaKey;s&&"k"===a.key.toLowerCase()&&(a.preventDefault(),y(!0)),s&&"Enter"===a.key&&!e&&!t&&(a.preventDefault(),u())};return window.addEventListener("keydown",a),()=>window.removeEventListener("keydown",a)},[e,u,t]),(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(le,{tone:"ghost",fullWidth:!0,icon:(0,Pa.jsx)(w,{size:15}),onClick:()=>y(!0),children:"Command palette"}),(0,Pa.jsxs)(Ke,{open:v,onClose:()=>{y(!1),N("")},title:"Command palette",panelClassName:"max-w-xl",bodyClassName:"!p-2",children:[(0,Pa.jsx)(we,{autoFocus:!0,"aria-label":"Search commands",value:k,onChange:e=>N(e.target.value),onClear:()=>N(""),placeholder:"Search commands",className:"mb-2"}),(0,Pa.jsxs)("div",{className:"grid gap-1",children:[I.map(e=>(0,Pa.jsxs)(le,{tone:"ghost",align:"left",fullWidth:!0,disabled:e.disabled,onClick:()=>(e=>{e.disabled||(e.run(),y(!1),N(""))})(e),className:"h-auto border border-transparent px-2 py-2 hover:border-hf-yellow hover:bg-modiff-bg",children:[(0,Pa.jsx)("span",{className:"grid size-7 shrink-0 place-items-center text-hf-yellow",children:e.icon}),(0,Pa.jsxs)("span",{className:"min-w-0",children:[(0,Pa.jsx)("span",{className:"block text-sm font-bold text-modiff-text",children:e.label}),(0,Pa.jsx)("span",{className:"block text-xs text-modiff-subtle-text",children:e.detail})]})]},e.id)),0===I.length&&(0,Pa.jsx)("p",{className:"p-2 text-xs text-modiff-subtle-text",children:"No matching commands."})]})]})]})}function bs(e){return"ready"===e?"success":"blocked"===e?"error":"default"}function js(e){return"ready"===e?"Ready":"draft"===e?"Draft":"blocked"===e?"Blocked":"Registry"}function vs({disabled:e=!1,form:t,onChange:a}){let[i,n]=(0,Ta.useState)(null),l=Ft(e=>e.nodesRegistry),r=Object.keys(l).length>0,o=yt[t.modelType],d=!!l[me.lora],c=!!l[me.upscaler],u=!!l[Kt],m=async(e,s)=>{let i={...t,...s};n(e);try{a(s),await rt(i),Ee("Controlled workflow prepared",{variant:"success",autoHideDuration:1800})}catch(e){Ee(String(e),{variant:"error",autoHideDuration:6500})}finally{n(null)}},p=async(e,t)=>{n(e);try{await t()}catch(e){Ee(String(e),{variant:"error",autoHideDuration:6500})}finally{n(null)}},h=[{id:"control",title:"ControlNet control image",detail:"Qwen Image plus Qwen ControlNet Union.",status:"ready",icon:(0,Pa.jsx)(s,{size:15}),button:"Use control",onClick:()=>m("control",{mode:"control_image",modelType:"QwenImageModularPipeline",controlImage:t.controlImage||t.referenceImages[0]||""})},{id:"lora",title:"LoRA adapter",detail:o.supportsLora?"Adds Lora -> ModelsLoader lora_list.":"Current model metadata does not support LoRA.",status:o.supportsLora?d||!r?"ready":"needs-registry":"blocked",icon:(0,Pa.jsx)(g,{size:15}),button:"Add block",onClick:o.supportsLora?()=>p("lora",()=>Ma()):void 0},{id:"upscale",title:"Upscale finish",detail:"Adds Spandrel Upscaler and a second Preview.",status:c||!r?"ready":"needs-registry",icon:(0,Pa.jsx)(V,{size:15}),button:"Add block",onClick:()=>p("upscale",()=>Sa())},{id:"inpaint",title:"Inpaint mask draft",detail:"Source image, mask image, Apply Mask, and image encode.",status:"draft",icon:(0,Pa.jsx)($,{size:15}),button:"Prepare draft",onClick:()=>m("inpaint",{mode:"inpaint",modelType:"QwenImageEditModularPipeline",referenceImages:t.referenceImages,maskImage:t.maskImage})},{id:"strength",title:"Image-to-image strength sweep",detail:"Use edit mode, then run the sweep grid.",status:"ready",icon:(0,Pa.jsx)(v,{size:15}),button:"Edit sweep",onClick:()=>m("strength",{mode:"edit_image",modelType:"QwenImageEditModularPipeline",strength:t.strength||.65,randomSeed:!1})},{id:"multi-reference",title:"Multi-reference edit",detail:"Qwen Edit Plus with multiple source images.",status:"ready",icon:(0,Pa.jsx)(f,{size:15}),button:"Use refs",onClick:()=>m("multi-reference",{mode:"multi_image_reference_edit",modelType:"QwenImageEditPlusModularPipeline",referenceImages:t.referenceImages})},{id:"outpaint",title:"Outpaint canvas",detail:"Expands the source image, generates a boundary mask, and feeds Qwen inpaint.",status:u||!r?"ready":"needs-registry",icon:(0,Pa.jsx)(T,{size:15}),button:"Use outpaint",onClick:()=>m("outpaint",{mode:"outpaint",modelType:"QwenImageEditModularPipeline",referenceImages:t.referenceImages,aspectRatio:"16:9",width:1344,height:768,outpaintLeft:t.outpaintLeft||256,outpaintRight:t.outpaintRight||256,outpaintTop:t.outpaintTop||0,outpaintBottom:t.outpaintBottom||0,outpaintOverlap:t.outpaintOverlap||24,outpaintFeather:t.outpaintFeather||8})}];return(0,Pa.jsxs)("section",{"data-testid":"studio-controlled-workflows",children:[(0,Pa.jsx)(te,{title:"Controlled workflows"}),(0,Pa.jsx)("div",{className:"grid gap-2",children:h.map(t=>{let a=i===t.id,s=e||null!==i||"blocked"===t.status||!t.onClick;return(0,Pa.jsx)("article",{className:"border border-modiff-border bg-modiff-bg p-2",children:(0,Pa.jsxs)("div",{className:"flex items-start gap-2",children:[(0,Pa.jsx)("div",{className:"grid size-8 shrink-0 place-items-center rounded-modiff-compact bg-modiff-panel text-hf-yellow",children:t.icon}),(0,Pa.jsxs)("div",{className:"min-w-0 flex-1",children:[(0,Pa.jsxs)("div",{className:"flex flex-wrap items-center gap-1.5",children:[(0,Pa.jsx)("h4",{className:"text-xs font-bold text-modiff-text",children:t.title}),(0,Pa.jsx)(Ie,{tone:bs(t.status),children:js(t.status)})]}),(0,Pa.jsx)("p",{className:_e("mt-1 text-xs","blocked"===t.status?"text-hf-orange":"text-modiff-subtle-text"),children:t.detail})]}),(0,Pa.jsx)(le,{tone:"blocked"===t.status?"ghost":"secondary",icon:a?(0,Pa.jsx)(pa,{size:14}):t.icon,disabled:s,onClick:()=>{t.onClick?.()},children:t.button})]})},t.id)})})]})}var ys=512,ks={25:"opacity-25",50:"opacity-50",75:"opacity-75",100:"opacity-100"};function ws({sourceImage:e,maskImage:t,onCancel:s,onSave:l}){let r=(0,Ta.useRef)(null),o=(0,Ta.useRef)(!1),[d,c]=(0,Ta.useState)("brush"),[u,m]=(0,Ta.useState)(34),[p,f]=(0,Ta.useState)(50),[x,g]=(0,Ta.useState)([]),[b,j]=(0,Ta.useState)([]),v=()=>r.current?.toDataURL("image/png")??"",y=()=>{let e=v();e&&(g(t=>[e,...t].slice(0,20)),j([]))},k=e=>{let t=r.current,a=t?.getContext("2d");if(!t||!a)return;let s=new Image;s.onload=()=>{a.clearRect(0,0,ys,ys),a.fillStyle="black",a.fillRect(0,0,ys,ys),a.drawImage(s,0,0,ys,ys)},s.src=e};(0,Ta.useEffect)(()=>{let e=r.current,a=e?.getContext("2d");!e||!a||(a.fillStyle="black",a.fillRect(0,0,ys,ys),t&&k(t))},[t]);let w=e=>{let t=r.current,a=t?.getContext("2d");if(!t||!a)return;let s=t.getBoundingClientRect(),i=(e.clientX-s.left)/s.width*ys,n=(e.clientY-s.top)/s.height*ys;a.beginPath(),a.arc(i,n,u/2,0,2*Math.PI),a.fillStyle="brush"===d?"white":"black",a.fill()},C=e=>{o.current=!1,e.currentTarget.releasePointerCapture(e.pointerId)};return(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Mask editor"}),(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsxs)("div",{className:"relative aspect-square overflow-hidden border border-modiff-border bg-modiff-bg",children:[e&&(0,Pa.jsx)("img",{src:e,alt:"Source overlay",className:_e("absolute inset-0 h-full w-full object-contain",ks[p])}),(0,Pa.jsx)("canvas",{ref:r,width:ys,height:ys,className:"relative z-10 h-full w-full touch-none mix-blend-screen",onPointerDown:e=>{y(),o.current=!0,e.currentTarget.setPointerCapture(e.pointerId),w(e)},onPointerMove:e=>{o.current&&w(e)},onPointerUp:C,onPointerCancel:C})]}),(0,Pa.jsxs)("div",{className:"flex flex-wrap gap-1",children:[(0,Pa.jsxs)(Ie,{active:"brush"===d,onClick:()=>c("brush"),children:[(0,Pa.jsx)(n,{size:13})," Brush"]}),(0,Pa.jsxs)(Ie,{active:"erase"===d,onClick:()=>c("erase"),children:[(0,Pa.jsx)(O,{size:13})," Erase"]}),(0,Pa.jsx)(Ie,{onClick:()=>{let e=r.current,t=e?.getContext("2d");if(!e||!t)return;y();let a=t.getImageData(0,0,ys,ys);for(let e=0;e{let e=r.current,t=e?.getContext("2d");!e||!t||(y(),t.fillStyle="black",t.fillRect(0,0,ys,ys))},children:[(0,Pa.jsx)(a,{size:13})," Clear"]}),(0,Pa.jsxs)(Ie,{onClick:()=>{let e=x[0];if(!e)return;let t=v();j(e=>t?[t,...e].slice(0,20):e),g(e=>e.slice(1)),k(e)},disabled:0===x.length,children:[(0,Pa.jsx)(q,{size:13})," Undo"]}),(0,Pa.jsxs)(Ie,{onClick:()=>{let e=b[0];if(!e)return;let t=v();g(e=>t?[t,...e].slice(0,20):e),j(e=>e.slice(1)),k(e)},disabled:0===b.length,children:[(0,Pa.jsx)(R,{size:13})," Redo"]})]}),(0,Pa.jsx)(Pe,{label:`Brush size: ${u}`,children:(0,Pa.jsx)(Re,{min:4,max:120,value:u,onChange:m})}),(0,Pa.jsxs)("div",{children:[(0,Pa.jsxs)("p",{className:"mb-1 text-xs text-modiff-subtle-text",children:["Source overlay: ",p,"%"]}),(0,Pa.jsx)("div",{className:"flex flex-wrap gap-1",children:[25,50,75,100].map(e=>(0,Pa.jsxs)(Ie,{active:p===e,onClick:()=>f(e),children:[e,"%"]},e))})]}),(0,Pa.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Pa.jsx)(le,{tone:"primary",icon:(0,Pa.jsx)(h,{size:15}),onClick:()=>l(v()),children:"Save mask"}),(0,Pa.jsx)(le,{tone:"ghost",icon:(0,Pa.jsx)(i,{size:15}),onClick:s,children:"Cancel"})]})]})]})}function Cs({form:e}){let t=[["Seed",e.randomSeed?"Random on run, so exact examples cannot be promised.":`Locked to ${e.seed} for reruns.`],["Guidance",`${e.guidanceScale} balances prompt obedience against natural detail.`],["Steps",`${e.steps} sampling steps sets quality/runtime tradeoff.`],["Strength",`${e.strength} controls how much source images can change in edit workflows.`],["Size",`${e.width}x${e.height} changes composition and exactness hash.`],["Dtype",`${e.dtype} affects memory use and can affect reproducibility.`],["Model",Nt[e.modelType]]];return(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Parameter notes"}),(0,Pa.jsx)("div",{className:"grid gap-1 border border-modiff-border bg-modiff-bg p-2",children:t.map(([e,t])=>(0,Pa.jsxs)("div",{className:"flex items-start gap-2 text-xs",children:[(0,Pa.jsx)(o,{size:13,className:"mt-0.5 shrink-0 text-modiff-subtle-text"}),(0,Pa.jsxs)("p",{className:"min-w-0 text-modiff-subtle-text",children:[(0,Pa.jsxs)("span",{className:"font-semibold text-modiff-text",children:[e,":"]})," ",t]})]},e))})]})}var Ns=["Describe the subject first, then composition, style, lighting, camera, and constraints.","Keep exact text short and put it in quotes when the model needs to render letters."],Ss=[["Subject",["one clear subject","premium product","same character identity"]],["Composition",["centered composition","negative space","preserve composition"]],["Style",["cinematic realism","editorial design","minimal studio look"]]],Is=[["Subject","subject"],["Environment","environment"],["Preserve","preservation"],["Change","change"],["Composition","composition"],["Texture / materials","materials"],["Style","style"],["Lighting","lighting"],["Camera","camera"],["Motion","motion"],["Continuity","continuity"],["Arrangement","arrangement"],["Mix","mix"],["Parameters","parameters"],["Text","textRendering"]];function Rs({form:e,template:t,onChange:a}){let s=t?.promptGuide,i=s?Is.map(([e,t])=>[e,s[t]]).filter(e=>Array.isArray(e[1])&&e[1].length>0):Ss,n=s?.negative??["low quality","blurry","warped text","distorted anatomy"],l=s?.modelHints??Ns,r=t=>{let s=e.prompt.trim();a({prompt:s?`${s}, ${t}`:t})},o=t=>{let s=e.negativePrompt.trim();a({negativePrompt:s?`${s}, ${t}`:t})};return(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Prompt composer"}),(0,Pa.jsxs)("div",{className:"grid gap-2",children:[i.map(([e,t])=>(0,Pa.jsxs)("fieldset",{children:[(0,Pa.jsx)("legend",{className:"mb-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:e}),(0,Pa.jsx)("div",{className:"flex flex-wrap gap-1",children:t.map(t=>(0,Pa.jsx)(Ie,{"aria-label":`Add ${e.toLowerCase()} detail: ${t}`,onClick:()=>r(t),children:t},`${e}-${t}`))})]},e)),n.length>0&&(0,Pa.jsxs)("fieldset",{children:[(0,Pa.jsx)("legend",{className:"mb-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:"Avoid"}),(0,Pa.jsx)("div",{className:"flex flex-wrap gap-1",children:n.map(e=>(0,Pa.jsx)(Ie,{"aria-label":`Add negative prompt detail: ${e}`,onClick:()=>o(e),children:e},`negative-${e}`))})]}),l.length>0&&(0,Pa.jsx)("div",{className:"grid gap-1 border border-modiff-border bg-modiff-bg p-2","aria-label":"Prompt guidance",children:l.map(e=>(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:e},e))}),t&&(0,Pa.jsx)(le,{tone:"ghost",icon:(0,Pa.jsx)(u,{size:15}),onClick:()=>a({prompt:t.prompt,negativePrompt:t.negativePrompt??e.negativePrompt}),children:"Restore template prompt"})]})]})}function $s(e){return e.match(/\S+/g)||[]}function Ms(e,t){let a=$s(e),s=$s(t),i=Array.from({length:a.length+1},()=>Array(s.length+1).fill(0));for(let e=a.length-1;e>=0;--e)for(let t=s.length-1;t>=0;--t){let n=a[e]??"",l=s[t]??"",r=i[e];r&&(r[t]=n===l?(i[e+1]?.[t+1]??0)+1:Math.max(i[e+1]?.[t]??0,r[t+1]??0))}let n=[],l=0,r=0;for(;l=(i[l]?.[r+1]??0)?(n.push({text:e,kind:"removed"}),l+=1):(n.push({text:t,kind:"added"}),r+=1)}for(;l[...a.map((e,t)=>({id:`snippet-${t}`,label:`Snippet ${t+1}`,prompt:e})),...t.map((e,t)=>({id:`history-${t}`,label:`History ${t+1}`,prompt:e}))].filter(t=>t.prompt.trim()&&t.prompt!==e),[e,t,a]),r=l.find(e=>e.id===i)||l[0],o=(0,Ta.useMemo)(()=>r?Ms(r.prompt,e):[],[e,r]),d=o.filter(e=>"added"===e.kind).length,c=o.filter(e=>"removed"===e.kind).length;return 0===l.length?null:(0,Pa.jsxs)("div",{children:[(0,Pa.jsx)(te,{title:"Prompt diff",action:(0,Pa.jsxs)("div",{className:"flex gap-1",children:[(0,Pa.jsx)(Ie,{tone:d?"success":"default",children:`+${d}`}),(0,Pa.jsx)(Ie,{tone:c?"error":"default",children:`-${c}`})]})}),(0,Pa.jsx)(Ne,{"aria-label":"Prompt version to compare",value:r?.id||"",onValueChange:n,options:l.map(e=>({value:e.id,label:e.label})),className:"mb-2 w-full"}),(0,Pa.jsx)("div",{className:"max-h-40 overflow-y-auto border border-modiff-border bg-modiff-bg p-2",children:o.map((e,t)=>(0,Pa.jsx)("span",{className:_e("mb-1 mr-1 inline-block rounded-modiff-compact px-1 text-xs","same"===e.kind&&"text-modiff-subtle-text","added"===e.kind&&"bg-modiff-green/10 text-modiff-green","removed"===e.kind&&"bg-modiff-red/10 text-modiff-red line-through"),children:e.text},`${e.text}-${t}`))}),(0,Pa.jsxs)("div",{className:"mt-2 flex gap-2",children:[(0,Pa.jsx)(le,{tone:"ghost",icon:(0,Pa.jsx)(L,{size:15}),onClick:()=>r&&function(e){navigator.clipboard.writeText(e),Ee("Prompt diff copied",{variant:"success",autoHideDuration:1800})}(function(e,t,a){let s=a.filter(e=>"added"===e.kind).map(e=>e.text).join(" "),i=a.filter(e=>"removed"===e.kind).map(e=>e.text).join(" ");return["Prompt diff","","Base:",e,"","Current:",t,"","Added:",s||"None","","Removed:",i||"None"].join("\n")}(r.prompt,e,o)),children:"Copy diff"}),(0,Pa.jsx)(le,{tone:"ghost",onClick:()=>r&&s(r.prompt),children:"Use baseline"})]})]})}function Ps(e){return e.split(",").map(e=>e.trim()).filter(Boolean)}function As(e,t){let a=new Set(e.map(e=>e.toLowerCase()));return t.forEach(t=>{let s=t.toLowerCase();a.has(s)||(a.add(s),e.push(t))}),e}function _s(e){return"edit_image"===e.mode?["preserve source identity","match original lighting","localized edit"]:"multi_image_reference_edit"===e.mode?["coherent blend of references","consistent perspective","preserve strongest design details"]:"control_image"===e.mode?["follow the control image structure","clean edges","stable composition"]:"control_edit_image"===e.mode?["preserve source identity","follow the control image structure","stable composition"]:"control_inpaint"===e.mode?["edit only inside the mask","follow the control image structure","preserve unmasked areas"]:"inpaint"===e.mode?["edit only inside the mask","preserve unmasked areas","consistent texture"]:"layer_decomposition"===e.mode?["clean layer boundaries","separated foreground and background","editable alpha"]:["one clear subject","balanced composition","professional finish"]}function zs({form:e,template:t,onApply:a}){let[s,n]=(0,Ta.useState)(null),l=(0,Ta.useMemo)(()=>s?Ms(e.prompt,s.prompt):[],[e.prompt,s]),r=l.filter(e=>"added"===e.kind).length,o=l.filter(e=>"removed"===e.kind).length;return(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Prompt enhancer",action:s?(0,Pa.jsxs)("div",{className:"flex gap-1",children:[(0,Pa.jsx)(Ie,{tone:r?"success":"default",children:`+${r}`}),(0,Pa.jsx)(Ie,{tone:o?"error":"default",children:`-${o}`})]}):void 0}),(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Pa.jsx)(le,{tone:"ghost",icon:(0,Pa.jsx)(u,{size:15}),onClick:()=>n(function(e,t){let a=Ps(e.prompt||t?.prompt||""),s=t?.promptGuide,i=[..._s(e),s?.lighting?.[0],s?.camera?.[0],s?.style?.[0]].filter(e=>!!e);return{prompt:As(a.length>0?a:["A polished image"],i).join(", "),negativePrompt:As(Ps(e.negativePrompt),s?.negative?.slice(0,3)??["low quality","blurry","artifacts"]).join(", ")}}(e,t)),children:"Suggest"}),s&&(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(le,{tone:"primary",icon:(0,Pa.jsx)(p,{size:15}),onClick:()=>{a(s),n(null)},children:"Apply"}),(0,Pa.jsx)(le,{tone:"ghost",icon:(0,Pa.jsx)(L,{size:15}),onClick:()=>function(e){navigator.clipboard.writeText(e),Ee("Enhanced prompt copied",{variant:"success",autoHideDuration:1800})}(`${s.prompt}\n\nNegative:\n${s.negativePrompt}`),children:"Copy"}),(0,Pa.jsx)(le,{tone:"ghost",icon:(0,Pa.jsx)(i,{size:15}),onClick:()=>n(null),children:"Dismiss"})]})]}),s&&(0,Pa.jsxs)("div",{className:"grid gap-2 border border-modiff-border bg-modiff-bg p-2","data-testid":"studio-prompt-enhancer-diff",children:[(0,Pa.jsx)("div",{className:"max-h-40 overflow-y-auto",children:l.map((e,t)=>(0,Pa.jsx)("span",{className:_e("mb-1 mr-1 inline-block rounded-modiff-compact px-1 text-xs","same"===e.kind&&"text-modiff-subtle-text","added"===e.kind&&"bg-modiff-green/10 text-modiff-green","removed"===e.kind&&"bg-modiff-red/10 text-modiff-red line-through"),children:e.text},`${e.text}-${t}`))}),(0,Pa.jsxs)("div",{className:"grid gap-1 border-t border-modiff-border pt-2",children:[(0,Pa.jsx)("p",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:"Negative"}),(0,Pa.jsx)("p",{className:"break-words text-xs text-modiff-subtle-text",children:s.negativePrompt||"None"})]})]})]})]})}function qs({disabled:e=!1,form:t,template:a,onChange:s,onRunSweep:i}){let n=!!a&&J(a,t),r=fa.includes(t.mode),o="edit_image"===t.mode||"multi_image_reference_edit"===t.mode||"inpaint"===t.mode||"control_edit_image"===t.mode||"control_inpaint"===t.mode||"layer_decomposition"===t.mode?[{label:"Lower strength",detail:`Strength ${Math.max(0,Number((t.strength-.1).toFixed(2)))}`,values:{strength:Math.max(0,Number((t.strength-.1).toFixed(2)))}},{label:"Higher strength",detail:`Strength ${Math.min(1,Number((t.strength+.1).toFixed(2)))}`,values:{strength:Math.min(1,Number((t.strength+.1).toFixed(2)))}}]:[],d=r?[{label:"Lower conditioning",detail:`Conditioning ${Math.max(0,Number((t.conditioningScale-.15).toFixed(2)))}`,values:{conditioningScale:Math.max(0,Number((t.conditioningScale-.15).toFixed(2)))}},{label:"Higher conditioning",detail:`Conditioning ${Math.min(2,Number((t.conditioningScale+.15).toFixed(2)))}`,values:{conditioningScale:Math.min(2,Number((t.conditioningScale+.15).toFixed(2)))}},{label:"Shorter clip",detail:`${Math.max(17,t.numFrames-32)} frames`,values:{numFrames:Math.max(17,t.numFrames-32)}},{label:"Quality clip",detail:"50 steps, 81 frames",values:{steps:50,numFrames:81,randomSeed:!1}}]:[],c=[{label:"Next seed",detail:`Seed ${t.seed+1}`,values:{seed:t.seed+1,randomSeed:!1}},{label:"Lower guidance",detail:`Guidance ${Math.max(0,Number((t.guidanceScale-.5).toFixed(1)))}`,values:{guidanceScale:Math.max(0,Number((t.guidanceScale-.5).toFixed(1)))}},{label:"Higher guidance",detail:`Guidance ${Math.min(10,Number((t.guidanceScale+.5).toFixed(1)))}`,values:{guidanceScale:Math.min(10,Number((t.guidanceScale+.5).toFixed(1)))}},{label:"Quality pass",detail:`${Math.min(80,t.steps+12)} steps`,values:{steps:Math.min(80,t.steps+12)}},...o,...d];return(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Variation planner"}),(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:n?"Current settings match the locked template. Any sweep below will become Modified.":"Current settings already differ from the locked template or no active template is selected."}),(0,Pa.jsx)("div",{className:"grid grid-cols-2 gap-2",children:c.map((e,t)=>(0,Pa.jsx)(le,{tone:"ghost",align:"left",icon:0===t?(0,Pa.jsx)(l,{size:15}):(0,Pa.jsx)(v,{size:15}),onClick:()=>s(e.values),children:(0,Pa.jsxs)("span",{children:[(0,Pa.jsx)("span",{className:"block text-xs font-bold text-modiff-text",children:e.label}),(0,Pa.jsxs)("span",{className:"block text-xs text-hf-orange",children:[e.detail," | Modified"]})]})},e.label))}),(0,Pa.jsx)(le,{fullWidth:!0,tone:"secondary",icon:(0,Pa.jsx)(l,{size:15}),disabled:e,onClick:()=>i(c),children:"Run sweep"})]})]})}var Os=new Set(["ui_image","ui_video","ui_audio","ui_text","ui_imagecompare"]),Bs=new WeakMap,Ds=new WeakMap;function Es(e){let t=e.data.blockInstanceV2??e.data.params,a=Bs.get(t);if(a)return a;let s=e.data.blockInstanceV2?la(e.data.blockInstanceV2):e.data.params,i=Object.fromEntries(Object.entries(s).filter(([e,t])=>{let a=t.isInput?"input":t.display||"";return"input"!==a&&"output"!==a&&!Os.has(a)&&!t.hidden&&!["output","images","latents"].includes(e)}).map(([e,t])=>[e,{...t,fieldOptions:{...t.fieldOptions,suppressInitialFieldAction:!0}}]));return Bs.set(t,i),i}function Gs(e,t=[]){let a=e.flatMap(e=>Object.entries(Es(e)).map(([t,a])=>({id:`graph:${e.id}:${t}`,kind:"graph-param",label:`${e.data.label||e.id} / ${a.label||t}`,nodeId:e.id,paramKey:t,node:e,param:a}))),s=new Set(t.filter(e=>!a.some(t=>t.id===e)));if(!s.size)return a;let i=new Set(e.map(({id:e})=>e));for(let t of e){let e=t.data.blockInstanceV2;if(!e||![...s].some(e=>e.startsWith(`graph:block-v2-node:${t.id.length}:${t.id}:`)))continue;let n=Ds.get(e);n||(n=wt({...t,data:{...t.data,blockInstanceV2:{...e,presentation:{...e.presentation,expanded:!0,collapsedContainerNodeIds:[]}}}}).nodes,Ds.set(e,n)),a.push(...Gs(n.filter(({id:e})=>!i.has(e))).filter(({id:e})=>s.has(e)))}return a}function Ls(e,t,a,s,i,n){if(ia.getState().activeWorkflowTabId!==e)return;let l=At.getState(),r=l.nodes.find(({id:e})=>e===t);if(r){if(void 0!==n){let e=r.data.blockInstanceV2?la(r.data.blockInstanceV2):r.data.params;if(!Object.prototype.hasOwnProperty.call(e,n)||!Object.prototype.hasOwnProperty.call(e,a))return}else if(!Es(r)[a])return;if(r.data.blockInstanceV2)return void((void 0===i||"value"===i)&&l.setBlockInstanceValueV2(t,a,s));l.setParamWithHistory(t,a,s,i),Se(t,a,s,i)}}var Fs=new Map;function Hs({candidates:e,nodes:t,onTogglePin:a,pinnedIds:s,pinnedInputs:i,selectedNodes:n,workflowId:l}){let[r,o]=(0,Ta.useState)({}),d=(0,Ta.useMemo)(()=>new Set(s),[s]),c=1===n.length?n[0]:null,u=(0,Ta.useMemo)(()=>{let e=new Map;if(i.forEach(t=>{if(!t.nodeId||!t.paramKey)return;let a=e.get(t.nodeId)??{node:t.node,params:{}};a.params[t.paramKey]=t.param,e.set(t.nodeId,a)}),c){let t=Es(c);e.set(c.id,{node:c,params:t})}return[...c?[c.id]:[],...[...new Set([...t.map(e=>e.id),...e.keys()])].filter(e=>e!==c?.id)].flatMap(t=>{let a=e.get(t);return a?[a]:[]})},[t,i,c]),m=(0,Ta.useCallback)((e,t,a,s)=>{Ls(l,e,t,a,s)},[l]);return(0,Pa.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-2","data-testid":n.length>0?"studio-custom-graph-inspector":"studio-pinned-graph-inputs",children:[(0,Pa.jsx)("div",{className:"flex items-center justify-between gap-2",children:(0,Pa.jsx)("span",{className:"text-sm font-semibold text-modiff-text",children:"Node controls"})}),n.length>1?(0,Pa.jsx)(Be,{children:"One node at a time"}):null,u.length>0?u.map(({node:e,params:a},s)=>{let i=c?.id===e.id,n=`${l??"unscoped"}:${e.id}`,d=r[n]??Fs.get(n),u=i||(d??0===s),p=e.data.label||`${e.data.module}.${e.data.action}`,f=Object.keys(a).length;return(0,Pa.jsx)(Xe,{"aria-label":`Node controls: ${p}`,className:_e("overflow-hidden rounded-modiff-compact border bg-modiff-bg",i?"border-hf-yellow/60":"border-modiff-border"),"data-testid":`studio-node-disclosure-${e.id}`,defaultOpen:u,label:(0,Pa.jsxs)("span",{className:"flex min-w-0 items-center gap-2",children:[(0,Pa.jsx)("span",{className:"min-w-0 flex-1 truncate",title:p,children:p}),(0,Pa.jsx)("span",{className:"text-xs font-normal text-modiff-subtle-text",children:f})]}),onOpenChange:e=>{Fs.set(n,e),o(t=>t[n]===e?t:{...t,[n]:e})},panelClassName:"border-t border-modiff-border-subtle p-3",children:t.some(({id:t})=>t===e.id)?f>0?(0,Pa.jsx)("div",{className:"grid min-w-0 grid-cols-1 gap-3 [&>[data-key]]:m-0 [&>[data-key]]:min-w-0",children:(0,Pa.jsx)(W,{nodeId:e.id,controlIdPrefix:"studio-inspector",params:a,updateStore:(t,a,s)=>m(e.id,t,a,s),updateFieldActionStore:(t,a,s,i)=>Ls(l,e.id,a,s,i,t),module:e.data.blockInstanceV2?e.data.blockInstanceV2.definitionSnapshot.source.library??e.data.blockInstanceV2.definitionSnapshot.source.provider??"MoDiff":e.data.module,action:e.data.blockInstanceV2?"BlockV2":e.data.action,mode:"controls",hidePreviews:!0,executionStatus:e.data.executionStatus,progressMessage:e.data.progressMessage,uiStateMessage:e.data.uiState?.validationMessage??e.data.uiState?.errorMessage})}):(0,Pa.jsx)(Be,{tone:"secondary",children:"No editable params"}):(0,Pa.jsxs)("div",{className:"grid gap-2","data-testid":`studio-hidden-pinned-inputs-${e.id}`,children:[Object.entries(a).map(([e,t])=>(0,Pa.jsxs)("p",{className:"break-words text-xs text-modiff-subtle-text",children:[t.label||e,": ",Ia(t.value??t.default)]},e)),(0,Pa.jsx)(le,{tone:"secondary",onClick:()=>function(e,t){if(ia.getState().activeWorkflowTabId!==e)return;let a=t.data.blockProjectionOwnerId,s=At.getState().nodes.find(({id:e})=>e===a)?.data.blockInstanceV2;if(!s||!a||!t.data.blockProjectionNodeId)return;let i=ht(s),n=new Set,l=i.get(t.data.blockProjectionNodeId);for(;l;)n.add(l),l=i.get(l);At.getState().setBlockPresentationV2(a,{expanded:!0,collapsedContainerNodeIds:(s.presentation.collapsedContainerNodeIds??[]).filter(e=>!n.has(e))})}(l,e),children:"Reveal in Block to edit"})]})},`${n}:${i?"selected":"idle"}`)}):(0,Pa.jsx)(Be,{tone:"secondary",children:"No pinned inputs"}),e.length>0?(0,Pa.jsx)(Xe,{className:"border-t border-modiff-border pt-2",label:"Pin inputs",buttonClassName:"min-h-0 justify-start p-0 text-xs text-modiff-subtle-text hover:bg-transparent",panelClassName:"mt-2 grid gap-1",children:e.map(e=>(0,Pa.jsx)(le,{tone:"ghost",align:"left",fullWidth:!0,className:"min-h-8 px-2 text-xs",onClick:()=>a(e.id),icon:d.has(e.id)?(0,Pa.jsx)(I,{size:14,className:"text-hf-yellow"}):(0,Pa.jsx)(D,{size:14,className:"text-modiff-subtle-text"}),children:(0,Pa.jsx)("span",{className:"min-w-0 flex-1 truncate",children:e.label})},e.id))}):null]})}function Vs({issues:e}){let{installHfModel:t,refreshModelIndexes:a,hfDownloadProgress:s}=Ft(se(e=>({installHfModel:e.installHfModel,refreshModelIndexes:e.refreshModelIndexes,hfDownloadProgress:e.hfDownloadProgress}))),i=Tt(e=>e.sid);return e.filter((t,a)=>"install_model"===t.action&&t.repoId&&e.findIndex(e=>"install_model"===e.action&&e.repoId===t.repoId&&JSON.stringify(e.installOptions??{})===JSON.stringify(t.installOptions??{}))===a).map(e=>{let n=e.repoId,l=Le(s[n]);return(0,Pa.jsxs)(le,{fullWidth:!0,tone:"secondary",disabled:l||!i,"data-testid":`studio-install-graph-model-${n}`,title:`${n}: ${e.message}`,onClick:()=>{(async()=>{try{await t(n,i,e.installOptions),await a(!0)}catch(e){Ee(aa(e,`Could not install ${n}.`),{variant:"error"})}})()},children:[l?"Installing":e.installOptions?.repair?"Repair":"Install"," ",n.split("/").pop()]},e.id)})}function Ws({form:e,hfCache:t,localModels:a,modelCacheDiagnostics:s,autoResourcePlan:i,backendCapabilities:n=[],backendCapabilitiesAuthoritative:l=!1}){let r=(0,Ta.useMemo)(()=>{let t=Mt(e),a=n.find(t=>t.modelType===e.modelType);return Q(a?{...t,...a}:t,e.mode)},[n,e]),o=(0,Ta.useMemo)(()=>l?n.filter(t=>sa(t).includes(e.mode)).map(e=>e.modelType):ca(e.mode,{currentModelType:e.modelType}),[n,l,e.mode,e.modelType]),d=(0,Ta.useMemo)(()=>{if(!l)return r.modes;let t=n.find(t=>t.modelType===e.modelType);return t?sa(t):[]},[n,l,r.modes,e.modelType]),c=(0,Ta.useMemo)(()=>ct(r,t,a,s),[r,t,a,s]),u=(0,Ta.useMemo)(()=>Wt({form:e,autoResourcePlan:i,hfCache:t,localModels:a,modelCacheDiagnostics:s,includePipeline:!1}),[i,e,t,a,s]),m=r.modeRequirements?.[e.mode]?.requiredImages??[],p=m.includes("referenceImages"),f=m.includes("maskImage"),h=m.includes("controlImage"),x=m.includes("ipAdapterImage"),g=p||"control_image"===e.mode||"inpaint"===e.mode||"layer_decomposition"===e.mode,b=r.supportsMask,j=r.inpaintContract,v=u.find(e=>!e.status.runnable),y=(0,Ta.useMemo)(()=>"auto"===e.resourceMode?kt(i,e):null,[i,e]),k=1===r.revisionCandidates?.length?r.revisionCandidates[0]:void 0;return{capability:r,compatibleModels:o,compatibleModes:d,showImageTray:g,requiresMaskImage:f,requiresControlImage:h,requiresIPAdapterImage:x,supportsMask:b,inpaintContract:j,missingInstallTarget:y??("auto"===e.resourceMode||!1===r.artifactInstallRequired||c.runnable?v?{repo:v.repo,label:v.label}:null:{repo:r.defaultRepo,label:r.label,revision:k,files:r.downloadFiles})}}var Ks=[{label:"1:1",width:1024,height:1024},{label:"4:3",width:1152,height:864},{label:"3:4",width:864,height:1152},{label:"16:9",width:1344,height:768},{label:"9:16",width:768,height:1344}];function Us(e,t){let a=Number(e);return Number.isFinite(a)?a:t}function Qs(){let[e,t]=(0,Ta.useState)(!1),s=Ca(),[i,n]=(0,Ta.useState)(!1),[l,r]=(0,Ta.useState)(!1),d=(0,Ta.useRef)(null),[c,m]=(0,Ta.useState)(0),p=ta(e=>e.setRightPanelOpen),f=ta(e=>e.setRightPanelTab),x=ta(e=>e.setLightboxOpener),g=ta(e=>e.setTemplateBrowserOpen),b=ta(e=>e.setGalleryLibraryOpen),j=ta(e=>e.studioViewMode),v=Ct(e=>e.showIssues),{sid:y,isConnected:k}=Tt(se(e=>({sid:e.sid,isConnected:e.isConnected}))),{hfCache:C,localModels:N,modelCacheDiagnostics:S,runtimeStatus:I,nodesRegistry:R,studioModelCapabilities:$,studioModelCapabilitiesAuthoritative:M,studioExecutionSpecInvalid:T,installHfModel:P}=Ft(se(e=>({hfCache:e.hfCache,localModels:e.localModels,modelCacheDiagnostics:e.modelCacheDiagnostics,runtimeStatus:e.runtimeStatus,nodesRegistry:e.nodesRegistry,studioModelCapabilities:e.studioModelCapabilities,studioModelCapabilitiesAuthoritative:e.studioModelCapabilitiesAuthoritative,studioExecutionSpecInvalid:e.studioExecutionSpecInvalid,installHfModel:e.installHfModel}))),{form:A,autoResourcePlan:_,autoResourceCheck:z,promptHistory:q,savedSnippets:O,negativePromptPresets:B,activeTemplateId:D,activeWorkflowTabId:E,outputs:G,lastError:F,recentChange:H,pinnedGraphInputIds:V,workflowCanvasHydrated:W,applyPreset:U,addPromptHistory:Q,togglePinnedGraphInput:J,saveCurrentPromptAsSnippet:X,removeSnippet:Y,applySnippet:oe,applyNegativePreset:ue,detachManagedGraph:me}=ia(se(e=>({form:e.form,autoResourceCheck:e.autoResourceCheck,promptHistory:e.promptHistory,savedSnippets:e.savedSnippets,negativePromptPresets:e.negativePromptPresets,autoResourcePlan:e.autoResourcePlan,activeTemplateId:e.activeTemplateId,activeWorkflowTabId:e.activeWorkflowTabId,outputs:e.outputs,lastError:e.lastError,recentChange:e.recentChange,pinnedGraphInputIds:e.pinnedGraphInputIds,workflowCanvasHydrated:e.workflowCanvasHydrated,applyPreset:e.applyPreset,addPromptHistory:e.addPromptHistory,togglePinnedGraphInput:e.togglePinnedGraphInput,saveCurrentPromptAsSnippet:e.saveCurrentPromptAsSnippet,removeSnippet:e.removeSnippet,applySnippet:e.applySnippet,applyNegativePreset:e.applyNegativePreset,detachManagedGraph:e.detachManagedGraph}))),pe=(0,Ta.useMemo)(()=>ce.find(e=>e.id===D),[D]),he=(0,Ta.useMemo)(()=>qt(G,E),[E,G]),ve=(0,Ta.useMemo)(()=>We.filter(e=>{let t=!!e.compatibleModes?.some(e=>fa.includes(e)),a=!!e.compatibleModes?.some(e=>mt.includes(e));if(fa.includes(A.mode)&&!t||mt.includes(A.mode)&&!a)return!1;let s=!e.compatibleModes||e.compatibleModes.includes(A.mode),i=!e.compatibleModelTypes||e.compatibleModelTypes.includes(A.modelType);return s&&i}),[A.mode,A.modelType]),ye=fa.includes(A.mode),we=mt.includes(A.mode),Se="unconditional_image"===A.mode,$e="depth_estimation"===A.mode,qe=Bt.includes(A.mode),Be=nt.includes(A.mode),De=!Se&&!$e&&!qe&&!1!==yt[A.modelType].supportsPrompt,Ge=(0,Ta.useMemo)(()=>Ue(A),[A]),Le="expert"===j,He=(0,Ta.useMemo)(()=>({device:A.device,autoOffload:A.autoOffload,offloadMode:A.offloadMode}),[A.autoOffload,A.device,A.offloadMode]),Ve=ua(_,He);ge({resourceMode:Ge.resourceMode,executionPath:Ve?.executionPath??Ge.executionPath}),i||z.status,"auto"!==A.resourceMode||bt(_,He),"auto"===A.resourceMode&&z.message;let Ke=Ce(yt[A.modelType]),et=re(yt[A.modelType],A),tt=ke(yt[A.modelType]),st=zt($,T,A),lt=st?.available_expert_quantization_modes??st?.expert_quantization_modes??[],ot=`${et}. ${Ke} defaults to ${yt[A.modelType].recommendedSteps} steps, ${yt[A.modelType].guidanceLabel.toLowerCase()} ${yt[A.modelType].recommendedGuidance}, ${yt[A.modelType].defaultDtype}. ${tt}`;Ve&&(Ve.resolvedArtifact??Ve.artifact??Ve.modelRepo,Ve.qualityTier,Ve.generation?.width??A.width,Ve.generation?.height??A.height,Ve.generation?.steps??A.steps,Ve.offloadMode??A.offloadMode);let ct=ia(e=>e.graphBinding);da(Ve,ct?.controlled?.contractIds);let ft=ia(e=>e.graphFinalization),ht=ct?ze(A):null,xt=ft??(ht?{status:"warning",message:ht}:null),jt=ia(e=>e.canvasTransition),vt="template_graph_building"===jt?.type&&jt.workflowTabId===E;At(se(e=>({nodes:e.nodes.map(e=>{let t=Object.entries(e.data.params).filter(([e])=>/repo|model|checkpoint|ckpt|safetensors|lora|vae|controlnet|adapter/i.test(e)).map(([e,t])=>`${e}:${JSON.stringify(t.value??t.default??"")}`).join(",");return`${e.id}:${e.data.module}:${e.data.action}:${e.data.uiState?.disabled?"disabled":"enabled"}:${t}`}).join("|"),edges:e.edges.map(e=>`${e.id}:${e.source}:${e.sourceHandle??""}->${e.target}:${e.targetHandle??""}`).join("|")})));let kt=Ze(),wt=W&&!vt&&ct?ma(ct):null,St=wt?`${wt.kind}:${wt.details??wt.message}`:"",It=W&&!vt&&(!ct&&kt.nodeCount>0||!!wt),Rt=It||"expert"===j,Mt=kt.modelRefs.length>0?`${new Set(kt.modelRefs.map(e=>`${e.kind}:${e.value}`)).size} model refs`:"Graph-defined",Ot=ae({sid:y,isConnected:k,includeStudio:!It}),Dt=(0,Ta.useMemo)(()=>ne(Ot.issues),[Ot.issues]),Et=Ot.blockingIssues.length>0?`${Ot.blockingIssues.length} issue${1===Ot.blockingIssues.length?"":"s"}`:Ot.warningIssues.length>0?`${Ot.warningIssues.length} warning${1===Ot.warningIssues.length?"":"s"}`:void 0,Gt=At(se(e=>e.nodes)),Lt=W&&!vt&&!D&&0===Gt.length,Ht=(0,Ta.useMemo)(()=>Gt.filter(e=>e.selected),[Gt]),Vt=Rt||Ht.length>0,Wt=(0,Ta.useMemo)(()=>Vt?Gs(Gt,V):[],[Gt,Vt,V]),Kt=V.filter(e=>Wt.some(t=>t.id===e)),Ut=(0,Ta.useMemo)(()=>{let e=new Set(Kt);return Wt.filter(t=>e.has(t.id))},[Kt,Wt]),{capability:Qt,compatibleModels:Jt,compatibleModes:Xt,showImageTray:Yt,requiresMaskImage:sa,requiresControlImage:na,requiresIPAdapterImage:la,supportsMask:ra,inpaintContract:ca,missingInstallTarget:pa}=Ws({form:A,hfCache:C,localModels:N,modelCacheDiagnostics:S,autoResourcePlan:_,backendCapabilities:$,backendCapabilitiesAuthoritative:M}),ja=Qt.modeRequirements?.[A.mode]?.requiredVideos??[],va=ja.includes("sourceVideo"),ya=ja.includes("referenceVideos"),ka=ja.includes("controlVideo"),wa=Ot.blockingIssues[0]?.message??"",Sa=!Ot.canRun,Ma=!It&&!Lt,{updateAndSync:Aa,handleCreateGraph:_a,handleModeChange:za,handleModelTypeChange:qa,handleResourceModeChange:Oa,handleRun:Ba,handleInstallMissingModel:Da,isInstallingMissingModel:Ea,openSetup:Ga}=je({sid:y,isConnected:k,missingInstallTarget:pa,installHfModel:P,setIsWorking:t}),La=(e,t=A)=>{let a=ut(t);s.request("run",a,e)};(0,Ta.useEffect)(()=>{!W||vt||K({sid:y,isConnected:k,includeStudio:!0,showDialog:!1})},[y,k,A,ct,ft,C,N,S,I,R,vt,W]),(0,Ta.useEffect)(()=>{!ct||!wt||"pending"===ft?.status||"template_graph_building"===jt?.type||me()},[jt?.type,me,ct,wt,St,ft?.status]),(0,Ta.useEffect)(()=>{if(!I)return;let e=Qe(I);e===A.device||Te(I,A.device)||A.device!==gt.device&&!A.device.toLowerCase().startsWith("cuda")||Aa({device:e})},[A.device,I,Aa]),(0,Ta.useCallback)(async()=>{if("auto"!==A.resourceMode)return null;let e=at();n(!0);try{let t=ia.getState().form,a=bt(ia.getState().autoResourcePlan,t)?ia.getState().autoResourcePlan:await ba(t);if($t(e),!a||!bt(a,t)){ia.getState().setAutoResourcePlan(a);let e=a?.blockingReason||a?.message||"Auto could not choose a runnable local plan yet.";return ia.getState().setLastError(e),Ee(e,{variant:"error",autoHideDuration:7e3}),null}let s=ia.getState().form,i=ee(s),n=ha(ua(a,s),s);ia.getState().applyAutoResourcePlan(a,n),Zt(e);let l=ia.getState().form;return Oe(l),i===ee(l)?await ga(l,e):await rt(l,e),$t(e),Ee("Auto plan refreshed.",{variant:"success",autoHideDuration:2200}),a}catch(e){if(ea(e))return null;let t=String(e);return ia.getState().setLastError(t),Ee(t,{variant:"error",autoHideDuration:7e3}),null}finally{n(!1)}},[A.resourceMode]);let Fa=(0,Ta.useCallback)(e=>{let t=ia.getState().form,a="low_vram"===e.id?{...e,values:{...e.values,...Je(t)}}:e;U(a);let s=ia.getState().form,i=$a(t,s,Object.keys(a.values)),n=t.quantizationMode!==s.quantizationMode||t.resourceMode!==s.resourceMode;queueMicrotask(()=>{n?rt(s):Oe(s)}),Ra(`${e.label} applied`,i,ia.getState().graphBinding?.managedNodeIds??[]),i.length>0&&Ee(`${e.label} updated ${i.map(e=>e.label).join(", ")}`,{variant:"success",autoHideDuration:2600})},[U]),Ha=(0,Ta.useCallback)(()=>{b(!0)},[b]),Va=(0,Ta.useCallback)(()=>{g(!0)},[g]),Wa=(0,Ta.useCallback)(async()=>{try{let e=await Me();Ee("string"==typeof e.message&&e.message.trim()?e.message:"Interrupt requested",{variant:"success",autoHideDuration:3e3})}catch(e){Ee(aa(e,"Could not stop the current execution."),{variant:"error",autoHideDuration:5e3})}},[]),Ka=(0,Ta.useCallback)(()=>{if(!y)return void Ee("Connect to the MoDiff server before exporting the API graph.",{variant:"error",autoHideDuration:3e3});let e=be(At.getState().exportGraph(y)),t=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(t),a.download="modiff-api-graph.json",a.click(),URL.revokeObjectURL(a.href)},[y]),Ua=(0,Ta.useCallback)(()=>{let e=ia.getState(),t=dt(e.outputs,e.activeWorkflowTabId,{includeUnscopedFallback:!1});t&&(ia.getState().restoreWorkflowFromOutput(t),p(!0),f("studio"),Ee("Latest Gallery workflow restored",{variant:"success",autoHideDuration:2e3}))},[p,f]),Qa=(0,Ta.useCallback)(async()=>{let e=ia.getState(),a=dt(e.outputs,e.activeWorkflowTabId,{includeUnscopedFallback:!1});if(a&&y&&k){t(!0);try{ia.getState().restoreWorkflowFromOutput(a);let e=at();if(!await fe(e))return;await Fe({sid:y,studioContext:{forceDeterministic:!a.formSnapshot.randomSeed},workflowContext:e})}catch(e){if(ea(e))return;Ee(String(e),{variant:"error",autoHideDuration:6e3})}finally{t(!1)}}else Ee("Connect to the MoDiff server and keep a Gallery output available before rerunning.",{variant:"error",autoHideDuration:4e3})},[k,y]),Ja=(0,Ta.useCallback)(()=>{let e=ia.getState(),[t,a]=qt(e.outputs,e.activeWorkflowTabId);!t||!a||x(it([a.url,t.url],1))},[x]),Za=(0,Ta.useCallback)(()=>{v(Ot.issues)},[Ot.issues,v]),Xa=(0,Ta.useCallback)(async e=>{if(!y||!k)return void Ee("Connect to the MoDiff server before running a sweep.",{variant:"error",autoHideDuration:4e3});if(wa)return void Ee(wa,{variant:"error",autoHideDuration:5e3});let a={...ia.getState().form,referenceImages:[...ia.getState().form.referenceImages]},s=at(),i=`sweep-${Date.now()}`;t(!0),ia.getState().addPromptHistory(a.prompt);try{for(let t of e){$t(s);let e={...t.values,randomSeed:!1};ia.getState().updateForm(e),Zt(s);let a=ia.getState().form;if(Oe(a),!await fe(s))break;await ga(ia.getState().form,s),$t(s);let{response:n}=await Fe({sid:y,workflowContext:s,studioContext:{forceDeterministic:!0,variation:{variationGroupId:i,variationLabel:t.label}}});if($t(s),n.error){ia.getState().setLastError(n.message||"MoDiff could not queue this sweep variation.");break}}Ee("Variation sweep queued",{variant:"success",autoHideDuration:2200})}catch(e){if(ea(e))return;let t=String(e);ia.getState().setLastError(t),Ee(t,{variant:"error",autoHideDuration:7e3})}finally{pt(s)&&(ia.getState().updateForm(a),Zt(s),Oe(a),await rt(a,s).catch(e=>{ea(e)||console.error("Could not restore the sweep graph",e)})),t(!1)}},[k,wa,y]);return(0,Ta.useLayoutEffect)(()=>{let e=d.current;if(!e)return;let t=()=>{let t=Math.ceil(e.getBoundingClientRect().height);m(e=>e===t?e:t)};if(t(),typeof ResizeObserver>"u")return;let a=0,s=new ResizeObserver(()=>{window.cancelAnimationFrame(a),a=window.requestAnimationFrame(t)});return s.observe(e),()=>{window.cancelAnimationFrame(a),s.disconnect()}},[]),(0,Pa.jsxs)("div",{className:"flex flex-col gap-3 p-3","data-testid":"studio-panel",children:[(0,Pa.jsxs)("div",{ref:d,className:"sticky top-0 z-20 -mx-3 -mt-3 grid gap-2 border-b border-modiff-border bg-modiff-bg p-3 shadow-modiff-node","data-testid":"studio-sticky-header",children:[!1,(0,Pa.jsx)(gs,{isWorking:e,runBlocked:Sa,expertMode:Le,hasManagedForm:Ma,hasGalleryItems:he.length>0,hasComparePair:he.length>1,onRun:()=>{La(Ba)},onInterrupt:()=>{Wa()},onUpdateGraph:()=>{_a()},onExport:Ka,onSavePrompt:X,onOpenGallery:Ha,onOpenTemplateGallery:Va,onOpenSetup:Ga,onRestoreLatest:Ua,onRerunLatest:()=>{La(Qa,he[0]?.formSnapshot)},onCompareLatest:Ja}),xt&&"complete"!==xt.status&&"idle"!==xt.status&&(0,Pa.jsxs)(Ae,{severity:"error"===xt.status?"error":"warning"===xt.status?"warning":"info",testId:"studio-graph-finalization",children:[(0,Pa.jsx)("p",{className:"text-xs font-semibold text-modiff-text",children:"pending"===xt.status?"Finalizing graph...":"warning"===xt.status?"Graph still finalizing":"Graph finalization failed"}),(0,Pa.jsx)("p",{className:"mt-1 text-xs text-modiff-subtle-text",children:xt.message||"Preparing dynamic fields and managed links in the background."})]}),F&&(0,Pa.jsx)(Ae,{severity:"error",children:(0,Pa.jsxs)("div",{className:"flex items-start gap-2",children:[(0,Pa.jsx)(Xe,{className:"min-w-0 flex-1",label:"Last error",buttonClassName:"min-h-0 justify-start p-0 text-xs text-modiff-invalid hover:bg-transparent",panelClassName:"mt-1",children:(0,Pa.jsx)("p",{className:"mt-1 max-h-28 overflow-auto break-words text-xs text-modiff-red",children:F})}),(0,Pa.jsx)(Z,{title:"Copy error",onClick:()=>function(e){navigator.clipboard.writeText(e),Ee("Error copied",{variant:"success",autoHideDuration:1800})}(F),children:(0,Pa.jsx)(L,{size:15})})]})})]}),(0,Pa.jsx)(xe,{id:"task",title:"Task",defaultOpen:!0,children:Lt?(0,Pa.jsxs)("div",{className:"grid gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"studio-empty-workflow",children:[(0,Pa.jsxs)("div",{children:[(0,Pa.jsx)("p",{className:"text-sm font-semibold text-modiff-text",children:"No task or model selected"}),(0,Pa.jsx)("p",{className:"mt-1 text-xs leading-5 text-modiff-subtle-text",children:"Choose a task in the canvas, browse templates, or add a node from the Nodes panel to build manually."})]}),(0,Pa.jsx)(le,{fullWidth:!0,tone:"secondary",icon:(0,Pa.jsx)(w,{size:15}),"data-testid":"studio-open-template-browser",onClick:Va,children:"Browse templates"})]}):(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"studio-task-model-summary",children:(0,Pa.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,Pa.jsxs)("div",{className:"min-w-0",children:[(0,Pa.jsx)("p",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:It?"Current graph":"Current task"}),(0,Pa.jsx)("h2",{className:"truncate text-base font-bold text-modiff-text",children:It?"Custom graph":Ye[A.mode]})]}),(0,Pa.jsxs)("div",{className:"min-w-0 text-right",children:[(0,Pa.jsx)("p",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:It?"Graph models":"Selected model"}),(0,Pa.jsxs)("div",{className:"flex items-center justify-end gap-1",children:[(0,Pa.jsx)("h3",{className:"truncate text-base font-bold text-hf-yellow",children:It?Mt:Ke}),!It&&(0,Pa.jsx)(Z,{title:ot,children:(0,Pa.jsx)(o,{size:13})})]})]})]})}),Ma&&(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(Ne,{"aria-label":"Task",value:A.mode,"data-testid":"studio-task-select",onValueChange:e=>{za(e)},options:Xt.map(e=>({value:e,label:Ye[e]}))}),(0,Pa.jsx)(Ne,{"aria-label":"Model",value:A.modelType,"data-testid":"studio-model-select",onValueChange:e=>{qa(e)},options:Jt.map(e=>({value:e,label:Nt[e]}))})]}),It?(0,Pa.jsx)(Vs,{issues:Ot.issues}):null,Ma&&pa?(0,Pa.jsx)(le,{fullWidth:!0,tone:"secondary","data-testid":"studio-install-missing-model",disabled:e||Ea,onClick:()=>{Da()},title:`${pa.label} | ${pa.repo}`,children:Ea?"Installing model...":`${pa.actionLabel??(pa.repair?"Repair":"Install")} ${pa.label}`}):null,vt?(0,Pa.jsx)(xa,{tone:"info",title:"Preparing graph",testId:"studio-run-readiness"}):(0,Pa.jsx)(xa,{tone:Dt.tone,title:Et?`${Dt.title} - ${Et}`:Dt.title,meta:Ot.primaryIssue?.message,testId:"studio-run-readiness",onClick:Ot.issues.length>0?Za:void 0}),Vt&&Gt.length>0?(0,Pa.jsx)(Hs,{candidates:Wt,nodes:Gt,pinnedInputs:Ut,pinnedIds:Kt,selectedNodes:Ht,workflowId:E,onTogglePin:J},E):null,0===Gt.length?(0,Pa.jsx)(le,{fullWidth:!0,tone:"secondary",icon:(0,Pa.jsx)(w,{size:15}),"data-testid":"studio-open-template-browser",onClick:Va,children:"Templates"}):null]})}),Ma&&(0,Pa.jsxs)(Pa.Fragment,{children:[De&&(0,Pa.jsxs)("div",{className:"relative grid gap-3",children:[(0,Pa.jsxs)(xe,{id:"prompt",title:"Prompt",defaultOpen:!0,testId:"studio-sticky-prompt",className:"sticky z-10 rounded-modiff-panel border border-modiff-border bg-modiff-bg px-2 pb-2 shadow-modiff-node before:absolute before:-inset-x-3 before:-top-3 before:h-3 before:bg-modiff-bg before:content-['']",stickyTop:c+12,children:[(0,Pa.jsx)("div",{"data-testid":"studio-prompt-input",children:(0,Pa.jsx)(Pt,{label:"Prompt",value:A.prompt,multiline:!0,onChange:e=>Aa({prompt:e})})}),!1!==Qt.supportsNegativePrompt&&(0,Pa.jsx)("div",{"data-testid":"studio-negative-prompt-input",children:(0,Pa.jsx)(Pt,{label:"Negative prompt",value:A.negativePrompt,multiline:!0,onChange:e=>Aa({negativePrompt:e})})}),(0,Pa.jsxs)("div",{className:"flex gap-2",children:[(0,Pa.jsx)(le,{tone:"ghost",icon:(0,Pa.jsx)(h,{size:15}),onClick:X,children:"Snippet"}),(0,Pa.jsx)(le,{tone:"ghost",icon:(0,Pa.jsx)(u,{size:15}),onClick:()=>Q(A.prompt),children:"History"})]})]}),(0,Pa.jsxs)(xe,{id:"prompt-tools",title:"Prompt tools",children:[(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Presets"}),(0,Pa.jsx)("div",{className:"flex flex-wrap gap-1.5",children:ve.map(e=>(0,Pa.jsx)(Ie,{title:e.description,onClick:()=>Fa(e),children:e.label},e.id))}),H&&(0,Pa.jsxs)("div",{className:"mt-2 rounded-modiff-compact border border-hf-yellow/60 bg-modiff-surface p-2 text-xs text-modiff-text","data-testid":"studio-recent-change",children:[(0,Pa.jsx)("p",{className:"font-semibold text-hf-yellow",children:H.label}),(0,Pa.jsx)("div",{className:"mt-1 flex flex-wrap gap-1",children:H.fields.slice(0,6).map(e=>(0,Pa.jsxs)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-modiff-subtle-text",children:[e.label,": ",Ia(e.before)," -> ",Ia(e.after)]},String(e.key)))})]})]}),(0,Pa.jsx)(_t,{}),(0,Pa.jsx)(Rs,{form:A,template:pe,onChange:Aa}),(0,Pa.jsx)(_t,{}),(0,Pa.jsx)(zs,{form:A,template:pe,onApply:Aa}),(q.length>0||O.length>0||B.length>0)&&(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(_t,{}),(0,Pa.jsxs)("div",{className:"grid gap-3",children:[O.length>0&&(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Saved snippets"}),(0,Pa.jsx)("div",{className:"grid gap-1",children:O.slice(0,4).map(e=>(0,Pa.jsxs)("div",{className:"flex gap-1",children:[(0,Pa.jsx)(le,{fullWidth:!0,tone:"ghost",align:"left",onClick:()=>{oe(e),queueMicrotask(()=>Oe())},children:e}),(0,Pa.jsx)(Z,{title:"Remove snippet",onClick:()=>Y(e),children:(0,Pa.jsx)(a,{size:15})})]},e))})]}),q.length>0&&(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Prompt history"}),(0,Pa.jsx)("div",{className:"grid gap-1",children:q.slice(0,4).map(e=>(0,Pa.jsx)(le,{tone:"ghost",align:"left",onClick:()=>Aa({prompt:e}),children:e},e))})]}),!1!==Qt.supportsNegativePrompt&&(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Negative presets"}),(0,Pa.jsx)("div",{className:"flex flex-wrap gap-1",children:B.map(e=>(0,Pa.jsx)(Ie,{onClick:()=>{ue(e),queueMicrotask(()=>Oe())},children:e},e))})]})]})]}),(0,Pa.jsx)(_t,{}),(0,Pa.jsx)(Ts,{currentPrompt:A.prompt,promptHistory:q,savedSnippets:O,onApplyPrompt:e=>Aa({prompt:e})})]})]}),(0,Pa.jsxs)(xe,{id:"advanced-generation",title:Le?"Advanced generation":"Generation",children:[(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:ye?"Video frame":Be?"Rendered orbit frame":Se?"Native sample size":$e?"Prediction map":qe?"Speech recognition":"Image size"}),Se?(0,Pa.jsxs)("p",{className:"text-xs text-modiff-subtle-text","data-testid":"studio-unconditional-native-size",children:["This checkpoint produces fixed ",A.width,"×",A.height," samples."]}):$e?(0,Pa.jsxs)("div",{className:"grid gap-2","data-testid":"studio-perception-controls",children:[(0,Pa.jsx)(Pt,{label:"Processing resolution",value:A.processingResolution,onChange:e=>Aa({processingResolution:Us(e,A.processingResolution)})}),(0,Pa.jsx)(ie,{checked:A.matchInputResolution,onChange:e=>Aa({matchInputResolution:e}),label:"Match output to source resolution"}),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Output is a normalized relative-depth map: near is 0, far is 1. Studio also previews it as grayscale."})]}):qe?(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text","data-testid":"studio-speech-summary",children:"Transcribe local audio into a normalized transcript with bounded chunking and timestamps."}):Qt.layerResolutions?.length?(0,Pa.jsx)(Ne,{"aria-label":"Layer resolution","data-testid":"studio-layer-resolution-select",value:String(A.width),onValueChange:e=>{let t=Us(e,A.width);Aa({width:t,height:t,aspectRatio:"1:1"})},options:Qt.layerResolutions.map(e=>({value:String(e),label:`${e} × ${e}`}))}):(0,Pa.jsxs)("div",{className:"flex gap-2",children:[(0,Pa.jsx)(Ne,{"aria-label":"Aspect ratio",value:A.aspectRatio,onValueChange:e=>(e=>{let t=Ks.find(t=>t.label===e);Aa(t?{aspectRatio:e,width:t.width,height:t.height}:{aspectRatio:e})})(e),options:[...Ks.map(e=>({value:e.label,label:e.label})),{value:"custom",label:"Custom"}],className:"flex-1"}),(0,Pa.jsx)(de,{"aria-label":"Width",value:A.width,"data-testid":"studio-width-input",onChange:e=>Aa({width:Us(e.target.value,A.width),aspectRatio:"custom"}),className:"w-[86px]"}),(0,Pa.jsx)(de,{"aria-label":"Height",value:A.height,"data-testid":"studio-height-input",onChange:e=>Aa({height:Us(e.target.value,A.height),aspectRatio:"custom"}),className:"w-[86px]"})]})]}),"outpaint"===A.mode&&(0,Pa.jsxs)("section",{"data-testid":"studio-outpaint-controls",children:[(0,Pa.jsx)(te,{title:"Outpaint canvas"}),(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Pa.jsx)(Pt,{label:"Left margin",value:A.outpaintLeft,onChange:e=>Aa({outpaintLeft:Us(e,A.outpaintLeft)})}),(0,Pa.jsx)(Pt,{label:"Right margin",value:A.outpaintRight,onChange:e=>Aa({outpaintRight:Us(e,A.outpaintRight)})}),(0,Pa.jsx)(Pt,{label:"Top margin",value:A.outpaintTop,onChange:e=>Aa({outpaintTop:Us(e,A.outpaintTop)})}),(0,Pa.jsx)(Pt,{label:"Bottom margin",value:A.outpaintBottom,onChange:e=>Aa({outpaintBottom:Us(e,A.outpaintBottom)})})]}),(0,Pa.jsx)(Pe,{label:`Seam overlap: ${A.outpaintOverlap}`,children:(0,Pa.jsx)(Re,{min:0,max:128,step:4,value:A.outpaintOverlap,onChange:e=>Aa({outpaintOverlap:e})})}),(0,Pa.jsx)(Pe,{label:`Mask feather: ${A.outpaintFeather}`,children:(0,Pa.jsx)(Re,{min:0,max:64,step:1,value:A.outpaintFeather,onChange:e=>Aa({outpaintFeather:e})})}),(0,Pa.jsx)(Pt,{label:"Fill color",value:A.outpaintFillColor,onChange:e=>Aa({outpaintFillColor:e})})]})]}),!qe&&(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Generation"}),(0,Pa.jsxs)("div",{className:"grid gap-2",children:[(0,Pa.jsx)("div",{"data-testid":"studio-seed-input",children:(0,Pa.jsx)(Pt,{label:"Seed",value:A.seed,onChange:e=>Aa({seed:Us(e,A.seed)}),disabled:A.randomSeed})}),(0,Pa.jsx)(ie,{checked:A.randomSeed,onChange:e=>Aa({randomSeed:e}),label:"Randomize seed on export"}),(0,Pa.jsx)(Pe,{label:`Steps: ${A.steps}`,children:(0,Pa.jsx)(Re,{min:1,max:Se?1e3:80,value:A.steps,onChange:e=>Aa({steps:e})})}),Se&&(0,Pa.jsx)(Pt,{label:"Batch size",value:A.batchSize,onChange:e=>Aa({batchSize:Us(e,A.batchSize)})}),"DDIMPipeline"===A.modelType&&(0,Pa.jsx)(Pe,{label:`Eta: ${A.eta}`,children:(0,Pa.jsx)(Re,{min:0,max:1,step:.01,value:A.eta,onChange:e=>Aa({eta:e})})}),"ConsistencyModelPipeline"===A.modelType&&(0,Pa.jsx)(Pt,{label:"Class label (-1 is unconditional)",value:A.classLabel,onChange:e=>Aa({classLabel:Us(e,A.classLabel)})}),De&&(0,Pa.jsx)(Pe,{label:`${Qt.guidanceLabel}: ${A.guidanceScale}`,children:(0,Pa.jsx)(Re,{min:0,max:10,step:.1,value:A.guidanceScale,onChange:e=>Aa({guidanceScale:e})})}),void 0!==Qt.recommendedPagScale&&(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(Pe,{label:`PAG scale: ${A.pagScale}`,children:(0,Pa.jsx)(Re,{min:0,max:10,step:.1,value:A.pagScale,onChange:e=>Aa({pagScale:e})})}),(0,Pa.jsx)(Pe,{label:`PAG adaptive scale: ${A.pagAdaptiveScale}`,children:(0,Pa.jsx)(Re,{min:0,max:10,step:.1,value:A.pagAdaptiveScale,onChange:e=>Aa({pagAdaptiveScale:e})})})]}),ye&&(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Pa.jsx)(Pt,{label:"Frames",value:A.numFrames,onChange:e=>Aa({numFrames:Us(e,A.numFrames)})}),(0,Pa.jsx)(Pt,{label:"FPS",value:A.fps,onChange:e=>Aa({fps:Us(e,A.fps)})})]}),(0,Pa.jsx)(Pe,{label:`Conditioning: ${A.conditioningScale}`,children:(0,Pa.jsx)(Re,{min:0,max:2,step:.05,value:A.conditioningScale,onChange:e=>Aa({conditioningScale:e})})}),Le?(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(Pe,{label:`Guidance 2: ${A.guidanceScale2}`,children:(0,Pa.jsx)(Re,{min:0,max:20,step:.1,value:A.guidanceScale2,onChange:e=>Aa({guidanceScale2:e})})}),(0,Pa.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Pa.jsx)(Ne,{"aria-label":"Output type",value:A.outputType,onValueChange:e=>Aa({outputType:e}),options:[{value:"pil",label:"Output: PIL"},{value:"np",label:"Output: NumPy"},{value:"pt",label:"Output: Torch"}]}),(0,Pa.jsx)(Pt,{label:"Max tokens",value:A.maxSequenceLength,onChange:e=>Aa({maxSequenceLength:Us(e,A.maxSequenceLength)})})]}),(0,Pa.jsx)(Pt,{label:"Attention kwargs JSON",value:A.attentionKwargsJson,multiline:!0,onChange:e=>Aa({attentionKwargsJson:e})})]}):null]}),Qt.supportsImageInput&&!ye&&!$e&&(0,Pa.jsx)(Pa.Fragment,{children:(0,Pa.jsx)(Pe,{label:`Strength: ${A.strength}`,children:(0,Pa.jsx)(Re,{min:0,max:1,step:.05,value:A.strength,onChange:e=>Aa({strength:e})})})}),(na||ka)&&(0,Pa.jsx)(Pe,{label:`Control conditioning: ${A.conditioningScale}`,children:(0,Pa.jsx)(Re,{min:0,max:2,step:.05,value:A.conditioningScale,onChange:e=>Aa({conditioningScale:e})})}),la&&(0,Pa.jsx)(Pe,{label:`IP-Adapter scale: ${A.ipAdapterScale}`,children:(0,Pa.jsx)(Re,{min:0,max:2,step:.05,value:A.ipAdapterScale,onChange:e=>Aa({ipAdapterScale:e})})}),Qt.supportsLayers&&(0,Pa.jsx)(Pt,{label:Qt.layerCount?`Layers (${Qt.layerCount.min}–${Qt.layerCount.max})`:"Layers",value:A.layers,onChange:e=>{let t=Us(e,A.layers);Aa({layers:Qt.layerCount?Math.min(Qt.layerCount.max,Math.max(Qt.layerCount.min,t)):t})}})]})]}),De&&(0,Pa.jsx)(Cs,{form:A}),De&&(0,Pa.jsx)(qs,{form:A,template:pe,onChange:Aa,onRunSweep:e=>{La(()=>Xa(e))},disabled:e||!!wa})]}),De&&(0,Pa.jsx)(xe,{id:"controlled-workflows",title:"Controlled workflows",children:(0,Pa.jsx)(vs,{form:A,onChange:Aa,disabled:e})}),ye&&(0,Pa.jsx)(xe,{id:"video-inputs",title:"Video inputs",defaultOpen:!0,children:(0,Pa.jsxs)("div",{className:"grid gap-2",children:[va&&(0,Pa.jsx)(Pt,{label:"Source video path",value:A.sourceVideo,onChange:e=>Aa({sourceVideo:e})}),ya&&(0,Pa.jsx)(Pt,{label:"Source video paths (one per line)",value:A.referenceVideos.join("\n"),onChange:e=>Aa({referenceVideos:e.split(/\r?\n/).map(e=>e.trim()).filter(Boolean)}),multiline:!0}),("video_inpaint"===A.mode||"video_outpaint"===A.mode)&&(0,Pa.jsx)(Pt,{label:"Mask video path",value:A.maskVideo,onChange:e=>Aa({maskVideo:e})}),ka&&(0,Pa.jsx)(Pt,{label:"Control video path",value:A.controlVideo,onChange:e=>Aa({controlVideo:e})}),"image_to_video"===A.mode&&(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Use Reference inputs below for the starting image."}),"reference_to_video"===A.mode&&(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Use Reference inputs below for subject and style images."})]})}),we&&(0,Pa.jsx)(xe,{id:"audio-inputs",title:"Audio",defaultOpen:!0,children:(0,Pa.jsxs)("div",{className:"grid gap-2",children:["text_to_audio"!==A.mode&&(0,Pa.jsx)(Pt,{label:"Source audio path",value:A.sourceAudio,onChange:e=>Aa({sourceAudio:e})}),"audio_variation"===A.mode&&(0,Pa.jsx)(Pt,{label:"Reference audio path",value:A.referenceAudio,onChange:e=>Aa({referenceAudio:e})}),(0,Pa.jsx)(Pt,{label:"Lyrics",value:A.lyrics,onChange:e=>Aa({lyrics:e}),multiline:!0}),(0,Pa.jsxs)("div",{className:_e("grid gap-2",Le&&"grid-cols-2"),children:[(0,Pa.jsx)(Pe,{label:`Duration ${A.audioDuration}s`,children:(0,Pa.jsx)(Re,{value:A.audioDuration,min:1,max:240,step:.5,onChange:e=>Aa({audioDuration:e})})}),Le?(0,Pa.jsx)(Pe,{label:`Shift ${A.shift}`,children:(0,Pa.jsx)(Re,{value:A.shift,min:0,max:10,step:.1,onChange:e=>Aa({shift:e})})}):null]}),"audio_continuation"===A.mode&&(0,Pa.jsx)(Pe,{label:`Continuation length ${A.extensionDuration}s`,children:(0,Pa.jsx)(Re,{value:A.extensionDuration,min:1,max:180,step:.5,onChange:e=>Aa({extensionDuration:e})})}),"audio_repaint"===A.mode&&(0,Pa.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Pa.jsx)(Pe,{label:`Repaint start ${A.repaintingStart}s`,children:(0,Pa.jsx)(Re,{value:A.repaintingStart,min:0,max:240,step:.1,onChange:e=>Aa({repaintingStart:e})})}),(0,Pa.jsx)(Pe,{label:`Repaint end ${A.repaintingEnd}s`,children:(0,Pa.jsx)(Re,{value:A.repaintingEnd,min:0,max:240,step:.1,onChange:e=>Aa({repaintingEnd:e})})})]}),"audio_variation"===A.mode&&(0,Pa.jsx)(Pe,{label:`Cover strength ${A.audioCoverStrength}`,children:(0,Pa.jsx)(Re,{value:A.audioCoverStrength,min:0,max:1,step:.01,onChange:e=>Aa({audioCoverStrength:e})})}),(0,Pa.jsxs)("div",{className:"grid grid-cols-3 gap-2",children:[(0,Pa.jsx)(de,{value:A.vocalLanguage,onChange:e=>Aa({vocalLanguage:e.target.value}),"aria-label":"Audio language"}),(0,Pa.jsx)(de,{value:A.keyscale,onChange:e=>Aa({keyscale:e.target.value}),"aria-label":"Audio key"}),(0,Pa.jsx)(de,{value:A.timesignature,onChange:e=>Aa({timesignature:e.target.value}),"aria-label":"Audio time signature"})]})]})}),qe&&(0,Pa.jsx)(xe,{id:"speech-inputs",title:"Speech recognition",defaultOpen:!0,children:(0,Pa.jsxs)("div",{className:"grid gap-2","data-testid":"studio-speech-controls",children:[(0,Pa.jsx)(Pt,{label:"Source audio path",value:A.sourceAudio,onChange:e=>Aa({sourceAudio:e})}),(0,Pa.jsx)(Pt,{label:"Language hint (optional)",value:A.speechLanguage,onChange:e=>Aa({speechLanguage:e})}),(0,Pa.jsx)(Ne,{"aria-label":"Speech timestamps",value:A.speechTimestamps,onValueChange:e=>Aa({speechTimestamps:e}),options:[{value:"none",label:"No timestamps"},{value:"segment",label:"Segment timestamps"},{value:"word",label:"Word timestamps"}]}),(0,Pa.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Pa.jsx)(Pt,{label:"Chunk length (seconds)",value:A.speechChunkSeconds,onChange:e=>Aa({speechChunkSeconds:Us(e,A.speechChunkSeconds)})}),(0,Pa.jsx)(Pt,{label:"Chunk stride (seconds)",value:A.speechStrideSeconds,onChange:e=>Aa({speechStrideSeconds:Us(e,A.speechStrideSeconds)})})]}),"speech_translation"===A.mode&&(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Translation output is normalized to English."})]})}),Le&&(0,Pa.jsx)(xe,{id:"runtime",title:"Runtime",children:(0,Pa.jsxs)("section",{className:"grid gap-2","data-testid":"studio-expert-runtime-controls",children:[(0,Pa.jsx)(te,{title:"Expert runtime"}),(0,Pa.jsxs)("div",{className:"flex gap-2",children:[(0,Pa.jsx)(Ne,{"aria-label":"Data type",value:A.dtype,onValueChange:e=>Aa({dtype:e}),options:[{value:"bfloat16",label:"bfloat16"},{value:"float16",label:"float16"},{value:"float32",label:"float32"}],className:"flex-1"}),(0,Pa.jsx)(de,{"aria-label":"Device",value:A.device,onChange:e=>Aa({device:e.target.value}),className:"w-24"})]}),(0,Pa.jsx)(ie,{checked:A.autoOffload,onChange:e=>Aa({autoOffload:e}),label:"Auto-offload model components"}),(0,Pa.jsxs)("div",{className:_e("grid gap-2",lt.length?"grid-cols-2":"grid-cols-1"),children:[lt.length>0&&(0,Pa.jsx)(Ne,{"aria-label":"Quantization","data-testid":"studio-quantization-select",value:A.quantizationMode,onValueChange:e=>{Aa({quantizationMode:e})},options:[{value:"none",label:"No quantization"},...lt.map(e=>({value:e,label:"bnb_4bit"===e?"4-bit BnB":"bnb_8bit"===e?"8-bit BnB":"quanto_float8"===e?"Quanto float8":"TorchAO float8"}))]}),(0,Pa.jsx)(Ne,{"aria-label":"Offload mode","data-testid":"studio-offload-mode-select",value:A.offloadMode,onValueChange:e=>Aa({offloadMode:e}),options:Qt.offloadSupport.modes.map(e=>({value:e,label:oa[e]}))})]})]})}),(Yt||"image_to_video"===A.mode||"reference_to_video"===A.mode)&&(0,Pa.jsx)(xe,{id:"reference-inputs",title:$e?"Source image":"Reference inputs",defaultOpen:!0,children:(0,Pa.jsx)(xs,{})}),(sa||na||la)&&(0,Pa.jsxs)(xe,{id:"mask-control",title:[sa,na,la].filter(Boolean).length>1?"Conditioning inputs":sa?"Mask draft":na?"Control image":"IP-Adapter image",defaultOpen:!0,children:[sa&&(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(Pt,{label:"Mask image path",value:A.maskImage,onChange:e=>Aa({maskImage:e})}),(0,Pa.jsx)("p",{className:_e("text-xs",ra?"text-modiff-subtle-text":"text-hf-orange"),children:ra?"The mask limits which source-image region can change.":ca?.reason??"Brush, erase, invert, feather, clear, and fill are frontend draft controls until backend support is confirmed."}),(0,Pa.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Pa.jsx)(le,{tone:"ghost",onClick:()=>r(!0),children:"Open mask editor"}),A.maskImage&&(0,Pa.jsx)(le,{tone:"ghost",onClick:()=>Aa({maskImage:""}),children:"Clear saved mask"})]}),l&&(0,Pa.jsx)(ws,{sourceImage:A.referenceImages[0],maskImage:A.maskImage,onSave:e=>{Aa({maskImage:e}),r(!1)},onCancel:()=>r(!1)})]}),na&&(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(Pt,{label:"Control image path",value:A.controlImage,onChange:e=>Aa({controlImage:e})}),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"The control image is a separate structural guide declared by the selected task contract."}),A.mode.includes("control_union")&&(0,Pa.jsx)(Pt,{label:"ControlNet Union mode (0–5)",value:A.controlMode,onChange:e=>Aa({controlMode:Us(e,A.controlMode)})})]}),la&&(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsx)(Pt,{label:"IP-Adapter image path",value:A.ipAdapterImage,onChange:e=>Aa({ipAdapterImage:e})}),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"This reference is encoded by the separately editable IP-Adapter block; it is not the source or ControlNet image."})]})]}),Le?(0,Pa.jsx)("p",{className:"border-t border-modiff-border pt-3 text-xs text-modiff-subtle-text",children:ct?"Studio is linked to graph nodes. Expert graph edits remain visible on the canvas.":"Custom graph mode: add nodes, connect an output, or start from a template."}):null]}),(0,Pa.jsx)(Na,{open:!!s.pending,policies:s.pending?.policies??[],action:"run",onCancel:s.cancel,onConfirm:s.confirm})]})}var Js=[{id:"studio:prompt",kind:"studio-form",label:"Prompt",formKey:"prompt"},{id:"studio:negativePrompt",kind:"studio-form",label:"Negative prompt",formKey:"negativePrompt"},{id:"studio:seed",kind:"studio-form",label:"Seed",formKey:"seed"},{id:"studio:steps",kind:"studio-form",label:"Steps",formKey:"steps"},{id:"studio:guidanceScale",kind:"studio-form",label:"Guidance",formKey:"guidanceScale"},{id:"studio:pagScale",kind:"studio-form",label:"PAG scale",formKey:"pagScale"},{id:"studio:pagAdaptiveScale",kind:"studio-form",label:"PAG adaptive scale",formKey:"pagAdaptiveScale"},{id:"studio:processingResolution",kind:"studio-form",label:"Processing resolution",formKey:"processingResolution"},{id:"studio:matchInputResolution",kind:"studio-form",label:"Match input resolution",formKey:"matchInputResolution"},{id:"studio:strength",kind:"studio-form",label:"Strength",formKey:"strength"},{id:"studio:width",kind:"studio-form",label:"Width",formKey:"width"},{id:"studio:height",kind:"studio-form",label:"Height",formKey:"height"},{id:"studio:referenceImages",kind:"studio-form",label:"Reference images",formKey:"referenceImages"},{id:"studio:controlImage",kind:"studio-form",label:"Control image",formKey:"controlImage"},{id:"studio:maskImage",kind:"studio-form",label:"Mask image",formKey:"maskImage"},{id:"studio:sourceAudio",kind:"studio-form",label:"Source audio",formKey:"sourceAudio"},{id:"studio:speechLanguage",kind:"studio-form",label:"Speech language",formKey:"speechLanguage"},{id:"studio:speechTimestamps",kind:"studio-form",label:"Speech timestamps",formKey:"speechTimestamps"},{id:"studio:speechChunkSeconds",kind:"studio-form",label:"Speech chunk length",formKey:"speechChunkSeconds"},{id:"studio:speechStrideSeconds",kind:"studio-form",label:"Speech chunk stride",formKey:"speechStrideSeconds"}];function Zs(e){return!(!e||"object"!=typeof e||!("id"in e)||"string"!=typeof e.id)}function Xs(e){return e.filter(Zs).flatMap(e=>Object.entries(e.data?.params??{}).filter(([e,t])=>!t.isInput&&!["image","output","images","latents"].includes(e)).slice(0,10).map(([t,a])=>({id:`graph:${e.id}:${t}`,kind:"graph-param",label:`${e.data?.label||e.id} / ${a.label||t}`,nodeId:e.id,paramKey:t})))}function Ys(e){return Array.isArray(e)?e.join("\n"):String(e??"")}function ei(e,t,a){let s=e[t];return Array.isArray(s)?a.split("\n").map(e=>e.trim()).filter(Boolean):"number"==typeof s?function(e,t){let a=Number(e);return Number.isFinite(a)?a:t}(a,s):"boolean"==typeof s?"true"===a:a}function ti(e,t){return e.exposedInputs.some(e=>e.id===t.id)}function ai(){let e=ia(e=>e.form),t=ia(e=>e.workflowTabs),s=ia(e=>e.activeWorkflowTabId),i=ia(e=>e.appModeConfigs),n=ia(e=>e.activeAppModeConfigId),l=ia(e=>e.createAppModeConfig),r=ia(e=>e.updateAppModeConfig),o=ia(e=>e.deleteAppModeConfig),d=ia(e=>e.setActiveAppModeConfig),c=ia(e=>e.switchWorkflowTab),u=ia(e=>e.updateForm),m=At(e=>e.nodes),p=Tt(e=>e.sid),f=Tt(e=>e.isConnected),h=i.find(e=>e.id===n)??i[0]??null,x=t.find(e=>e.id===h?.workflowTabId)??t.find(e=>e.id===s)??null,g=!(!h||h.workflowTabId!==s),b=(0,Ta.useMemo)(()=>g?m:x?.snapshot.nodes??[],[g,m,x]),j=(0,Ta.useMemo)(()=>[...Js,...Xs(b)],[b]),v=(0,Ta.useMemo)(()=>function(e){return e.filter(Zs).filter(e=>/preview|save|output|video|audio|image/i.test(`${e.data?.label??""} ${e.id}`)).map(e=>({id:`output:${e.id}`,label:e.data?.label||e.id,nodeId:e.id,fieldKey:"output"}))}(b),[b]),y=!!(s&&b.length>0&&v.length>0),k=!!(h&&h.exposedOutputs.length>0&&p&&f),w=(t,a)=>{"studio-form"!==t.kind||!t.formKey||(u({[t.formKey]:ei(e,t.formKey,a)}),queueMicrotask(()=>Oe()))};return(0,Pa.jsxs)("div",{className:"grid gap-3 p-3 text-sm text-modiff-text","data-testid":"app-mode-panel",children:[(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Run as app",action:(0,Pa.jsx)(le,{tone:"primary",icon:(0,Pa.jsx)(S,{size:15}),disabled:!y,onClick:()=>{y?l(s)&&Ee("Run as app created for this workflow",{variant:"success",autoHideDuration:2e3}):Ee("Use a graph with at least one output node before creating Run as app.",{variant:"error",autoHideDuration:3500})},"data-testid":"app-mode-create",children:"Create"})}),(0,Pa.jsx)(Be,{children:"Expose selected inputs and outputs from this graph."})]}),i.length>0&&(0,Pa.jsx)(Ne,{"aria-label":"Saved app configuration",value:h?.id??"",onValueChange:d,options:i.map(e=>({value:e.id,label:e.name}))}),h?(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsxs)("section",{className:"grid gap-2 border border-modiff-border bg-modiff-panel p-3",children:[(0,Pa.jsx)(Pt,{label:"App name",value:h.name,onChange:e=>r(h.id,{name:e})}),(0,Pa.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,Pa.jsx)(Ie,{active:g,children:g?"Workflow open":"Workflow saved"}),(0,Pa.jsx)(Ie,{children:x?.title??"Missing workflow"}),!g&&(0,Pa.jsx)(le,{tone:"ghost",onClick:()=>c(h.workflowTabId),children:"Open workflow"}),(0,Pa.jsx)(le,{tone:"danger",icon:(0,Pa.jsx)(a,{size:15}),onClick:()=>o(h.id),children:"Delete"})]})]}),(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Choose inputs"}),(0,Pa.jsx)("div",{className:"grid gap-1",children:j.slice(0,36).map(e=>(0,Pa.jsx)(ie,{checked:ti(h,e),label:e.label,onChange:()=>(e=>{if(!h)return;let t=ti(h,e)?h.exposedInputs.filter(t=>t.id!==e.id):[...h.exposedInputs,e];r(h.id,{exposedInputs:t})})(e)},e.id))})]}),(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Guided inputs"}),(0,Pa.jsxs)("div",{className:"grid gap-2",children:[0===h.exposedInputs.length&&(0,Pa.jsx)(Be,{children:"No inputs exposed yet."}),h.exposedInputs.map(t=>{if("studio-form"===t.kind&&t.formKey){let a=e[t.formKey];return"guidanceScale"===t.formKey||"pagScale"===t.formKey||"pagAdaptiveScale"===t.formKey||"strength"===t.formKey?(0,Pa.jsx)(Pe,{label:`${t.label}: ${String(a)}`,children:(0,Pa.jsx)(Re,{min:0,max:"strength"===t.formKey?1:12,step:.05,value:"number"==typeof a?a:0,onChange:e=>w(t,String(e))})},t.id):(0,Pa.jsx)(Pt,{label:t.label,disabled:!g,multiline:"prompt"===t.formKey||"negativePrompt"===t.formKey||"referenceImages"===t.formKey,value:Ys(a),onChange:e=>w(t,e)},t.id)}return(0,Pa.jsx)(Pt,{label:t.label,disabled:!g,value:String(t.nodeId&&t.paramKey?At.getState().getParam(t.nodeId,t.paramKey,"value")??"":""),onChange:e=>((e,t)=>{"graph-param"!==e.kind||!e.nodeId||!e.paramKey||At.getState().setParamWithHistory(e.nodeId,e.paramKey,t)})(t,e)},t.id)})]})]}),(0,Pa.jsxs)("section",{children:[(0,Pa.jsx)(te,{title:"Outputs"}),(0,Pa.jsxs)("div",{className:"grid gap-1",children:[0===v.length&&(0,Pa.jsx)(Be,{children:"No output nodes detected."}),v.map(e=>(0,Pa.jsx)(ie,{checked:h.exposedOutputs.some(t=>t.id===e.id),label:e.label,onChange:()=>(e=>{if(!h)return;let t=h.exposedOutputs.some(t=>t.id===e.id)?h.exposedOutputs.filter(t=>t.id!==e.id):[...h.exposedOutputs,e];r(h.id,{exposedOutputs:t})})(e)},e.id))]})]}),(0,Pa.jsx)(le,{tone:"primary",icon:(0,Pa.jsx)(P,{size:15}),disabled:!k,onClick:()=>{(async()=>{if(!h||!p||!f)return void Ee("Connect to the MoDiff server before running as app.",{variant:"error",autoHideDuration:4e3});if(0===h.exposedOutputs.length)return void Ee("Expose at least one output before running as app.",{variant:"error",autoHideDuration:3500});h.workflowTabId!==s&&c(h.workflowTabId);let e=at(),t=!!ia.getState().graphBinding;try{if(t&&!await fe(e)||!K({sid:p,isConnected:f,includeStudio:t,showDialog:!0}).canRun)return;await Fe({sid:p,studioContext:{applyRuntimeMetadata:t},workflowContext:e}),Ee("Run as app queued",{variant:"success",autoHideDuration:2200})}catch(e){if(ea(e))return;Ee(String(e),{variant:"error",autoHideDuration:7e3})}})()},"data-testid":"app-mode-run",children:"Run app"})]}):(0,Pa.jsx)("div",{className:"border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:"Create Run as app from the active graph."})]})}function si(){let e=(0,Ta.useMemo)(he,[]),t=At(t=>e(t.nodes)),a=At(e=>e.edges),s=ia(e=>e.activeWorkflowTabId),[i,n]=(0,Ta.useState)(""),[l,r]=(0,Ta.useState)(null),[o,c]=(0,Ta.useState)(""),[u,m]=(0,Ta.useState)(!1),[p,f]=(0,Ta.useState)(null),h=(0,Ta.useRef)(null),x=(0,Ta.useMemo)(()=>{try{return{request:va(t,a,i||void 0),error:""}}catch(e){return{request:null,error:e instanceof Error?e.message:String(e)}}},[t,a,i]),g=x.request?.key;(0,Ta.useEffect)(()=>(h.current?.abort(),m(!1),r(null),c(""),f(null),()=>h.current?.abort()),[g,i,s]);let b=x.request,j=l?.request.key===g?l:null;return(0,Pa.jsxs)("section",{className:"grid gap-3 p-3 text-sm text-modiff-text","data-testid":"workflow-resource-assessment",children:[(0,Pa.jsxs)("h2",{className:"flex items-center gap-2 font-semibold",children:[(0,Pa.jsx)(d,{size:17}),"Workflow resources"]}),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Auto chooses supported technical settings for the model and runtime. Expert keeps your configured settings. Switching modes preserves your nodes, links and creative controls."}),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"A loader’s Auto Offload controls where its weights reside. Repeat/Loop controls repeated runs. They are separate from Auto resource planning."}),(0,Pa.jsx)(Vt,{"aria-label":"Resource assessment scope",value:i,onValueChange:n,options:[{value:"",label:"Whole workflow"},...t.filter(e=>e.data.blockInstanceV2||e.data.blockProjectionContainer).map(e=>({value:e.id,label:`Only ${e.data.label??e.id}`}))]}),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:i?"Only contained nodes are assessed. Outside suppliers are excluded, as with Run Block.":"Includes connected outside nodes and every enabled Block."}),b?(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsxs)("p",{className:"text-xs",children:[b.nodeCount," executable nodes · ",b.loaderCount," model loaders · ",b.adapterCount," adapters"]}),(0,Pa.jsx)("p",{className:"rounded-modiff-compact border border-modiff-border p-2 text-xs","data-testid":"workflow-auto-reason",children:"Auto can plan custom workflows and multiple Blocks. Each model recipe and the combined memory requirement must pass the backend check."})]}):null,(0,Pa.jsxs)(Ge,{onClick:()=>{(async()=>{if(!x.request)return;h.current?.abort();let e=new AbortController;h.current=e,m(!0),c(""),r(null);try{let t=await ka(x.request,e.signal);if(!e.signal.aborted&&ia.getState().activeWorkflowTabId===s){let e=At.getState();if(va(e.nodes,e.edges,i||void 0).key!==t.request.key)throw Error("The workflow changed. Assess its current settings again.");r(t)}}catch(t){e.signal.aborted||c(t instanceof Error?t.message:String(t))}finally{e.signal.aborted||m(!1)}})()},disabled:u||!b||!b.nodeCount,"data-testid":"assess-workflow-resources",children:[(0,Pa.jsx)(C,{size:14}),u?"Assessing resources…":"Assess workflow resources"]}),(0,Pa.jsx)(Ge,{onClick:()=>{(async()=>{h.current?.abort();let e=new AbortController;h.current=e,m(!0),c(""),f(null);try{let t=await(await pe(async()=>{let{inspectWorkflowAutoPlanV2:e}=await import(`./block-composition-tools.js?v=5bcd315505f5670a`).then(e=>e.t);return{inspectWorkflowAutoPlanV2:e}},[])).inspectWorkflowAutoPlanV2(i||void 0,e.signal);!e.signal.aborted&&ia.getState().activeWorkflowTabId===s&&f(t)}catch(t){e.signal.aborted||c(t instanceof Error?t.message:String(t))}finally{e.signal.aborted||m(!1)}})()},disabled:u||!b?.nodeCount,"data-testid":"plan-workflow-auto",children:"Check Auto execution plan"}),p?(0,Pa.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border p-2 text-xs","data-testid":"workflow-auto-plan",children:[(0,Pa.jsx)("p",{className:"font-semibold",children:p.canAutoRun?p.requiresPreparation?"Check computed inputs when Run starts":"Ready for Auto execution":"Auto needs attention"}),(0,Pa.jsx)("p",{children:p.message}),(0,Pa.jsxs)("p",{children:[p.requiresPreparation?"Resolved so far: ":"Peak planned resources: ",p.loaders.length," model owners · RAM"," ",Gt(p.requirements.systemRamBytes??null)," · GPU"," ",Gt(p.requirements.vramBytes??null)]}),p.loaders.map(e=>(0,Pa.jsxs)("p",{children:[e.repository," · ",e.consumers.length," consumers"]},e.nodeId)),p.patches.length?(0,Pa.jsxs)("p",{children:["Run will apply ",p.patches.length," resource-setting changes to the visible workflow."]}):null,p.issues.map((e,t)=>(0,Pa.jsx)("p",{className:"break-words text-modiff-subtle-text",children:e},t)),(0,Pa.jsx)("p",{children:"Run checks the current graph and available memory again. Expert preserves your explicit settings."})]}):null,x.error||o?(0,Pa.jsx)("p",{role:"alert",className:"break-words text-xs text-modiff-red",children:x.error||o}):null,j?(0,Pa.jsxs)(Pa.Fragment,{children:[(0,Pa.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border p-2 text-xs",children:[(0,Pa.jsxs)("p",{children:["Available RAM: ",Gt(j.resources.system.ramAvailableBytes)]}),j.resources.accelerators.map(e=>(0,Pa.jsxs)("p",{children:[e.name,": ",Gt(e.memoryFreeBytes)," free · ",e.memoryKind," memory"]},e.device)),(0,Pa.jsx)("p",{className:"mt-1 text-modiff-subtle-text",children:"Shared GPU memory and RAM overlap; they are not added together."})]}),j.request.items.map(e=>{let t=j.plans[e.id],a=t?ua(t,e.form??void 0):null;return(0,Pa.jsxs)("div",{className:"grid gap-1 rounded-modiff-compact border border-modiff-border p-2 text-xs","data-testid":"workflow-resource-item",children:[(0,Pa.jsx)("p",{className:"break-words font-semibold",children:e.label}),(0,Pa.jsxs)("p",{children:[e.loaderIds.length," loader",1===e.loaderIds.length?"":"s"," · ",e.consumers," downstream consumers"]}),e.currentSettings.map((t,a)=>(0,Pa.jsxs)("p",{className:"break-words text-modiff-subtle-text",children:["Current: ",t]},e.loaderIds[a])),a?(0,Pa.jsxs)("p",{className:"break-words",children:["Source recipe recommendation: ",a.dtype," · ",a.offloadMode,"."," ",t?.statusLabel??a.reason]}):t?(0,Pa.jsx)("p",{children:t.blockingReason??t.message??"No supported local source recipe was selected."}):null,e.details.map(e=>(0,Pa.jsx)("p",{className:"text-modiff-subtle-text",children:e},e))]},e.id)}),j.request.issues.map(e=>(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:e},e)),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Assessment preserves the graph and current settings. Check Auto execution plan to validate the actual combined graph, connected adapters and available memory before running."})]}):null]})}var ii=[{value:"recommended",label:"Recommended"},{value:"best_quality",label:"Best quality"},{value:"faster",label:"Faster"},{value:"lowest_memory",label:"Lowest memory"}];function ni(e){return e.resolvedArtifact||e.artifact||e.modelRepo||""}function li(e){let t=e.artifactResolution?.resolved,a=t?.format||e.artifactFormat||e.quantizationMode||"native",s=t?.bits?` ${t.bits}-bit`:"";return`${ni(e)} · ${a}${s}`}function ri(e){let t=e.requirements?.minimum||e.requirements?.lowerMemory;if(!t)return[];let a=1024**3,s=[];return"number"==typeof t.vramBytes&&t.vramBytes>0&&s.push(`${Math.ceil(t.vramBytes/a)} GB VRAM`),"number"==typeof t.systemRamBytes&&s.push(`${Math.ceil(t.systemRamBytes/a)} GB RAM`),"number"==typeof t.diskFreeBytes&&s.push(`${Math.ceil(t.diskFreeBytes/a)} GB disk`),s}function oi(){let[e,t]=(0,Ta.useState)(null),a=ta(e=>e.setRightPanelTab),s=ta(e=>e.setRightPanelOpen),i=ta(e=>e.setAlertOpener),n=Tt(e=>e.sid),{installHfModel:l}=Ft(se(e=>({installHfModel:e.installHfModel}))),{form:r,graphBinding:o,plan:c,applyAutoResourcePlan:u,createWorkflowTab:p,updateForm:f,setAutoResourcePlan:h}=ia(se(e=>({form:e.form,graphBinding:e.graphBinding,plan:e.autoResourcePlan,applyAutoResourcePlan:e.applyAutoResourcePlan,createWorkflowTab:e.createWorkflowTab,updateForm:e.updateForm,setAutoResourcePlan:e.setAutoResourcePlan}))),x=ua(c,r)||null,g=da(x,o?.controlled?.contractIds),b=(0,Ta.useMemo)(()=>(c?.candidates||[]).filter(e=>e.id!==x?.id),[c?.candidates,x?.id]);if(!o)return(0,Pa.jsx)(si,{});if(!x)return(0,Pa.jsxs)("div",{className:"grid gap-3 p-4 text-sm text-modiff-text","data-testid":"compatibility-panel",children:[(0,Pa.jsxs)("div",{className:"flex items-center gap-2 font-semibold",children:[(0,Pa.jsx)(m,{size:17,className:"text-modiff-green"}),"No model substitution"]}),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Select a Studio model to inspect local compatibility."})]});let j=async e=>{let a=ni(e);if(!a)return;let s=at();t(a);try{await l(a,n,{repair:!!e.repairRequired}),$t(s),Ee(`${a} is ready. Refreshing compatibility.`,{variant:"success"});let t=await ba(ia.getState().form);$t(s),h(t)}catch(e){if(ea(e))return;Ee(e instanceof Error?e.message:`Could not install ${a}.`,{variant:"error"})}finally{t(null)}},v=e=>{let t=ni(e);i({title:e.repairRequired?"Repair artifact?":"Install artifact?",message:`${t} will be downloaded and managed by MoDiff.`,confirmText:e.repairRequired?"Repair":"Install",cancelText:"Cancel",onConfirm:()=>{i(null),j(e)},onCancel:()=>i(null)})},w=async(e,t="")=>{let a=at();try{let s={...ha(e,r),confirmedCommunityArtifact:t},i={...r,...s},n=await ba(i);$t(a);let l=ua(n,i)??e;u(n,{...ha(l,i),confirmedCommunityArtifact:t}),Zt(a),Ee("Next local option selected.",{variant:"info"})}catch(e){if(ea(e))return;Ee(String(e),{variant:"error"})}},C=x.compatibilityEvidence,N=g?.message||C?.message||x.reason||x.skipReason||c?.blockingReason||"Compatibility was estimated from the current runtime and model metadata.",S=x.artifactResolution?.base?.repo||x.baseArtifact||ni(x),I=!!(x.artifactResolution?.substituted||S&&S!==ni(x));return(0,Pa.jsx)(Pa.Fragment,{children:(0,Pa.jsxs)("div",{className:"grid gap-3 p-3 text-sm text-modiff-text","data-testid":"compatibility-panel",children:[(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Auto selects a supported local resource recipe. Expert keeps your configured technical settings. Switching preserves the workflow’s nodes, connections and creative controls."}),(0,Pa.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Auto Offload on a loader controls weight placement. Repeat/Loop controls repeated runs. These are separate from Auto resource planning."}),(0,Pa.jsxs)("div",{className:"flex items-start gap-2 rounded-modiff-compact border border-hf-yellow/50 bg-hf-yellow/10 p-3",children:[(0,Pa.jsx)(F,{size:17,className:"mt-0.5 shrink-0 text-hf-yellow"}),(0,Pa.jsxs)("div",{className:"min-w-0",children:[(0,Pa.jsx)("p",{className:"font-semibold",children:g?.label||C?.label||x.healthBadge||"This should work"}),(0,Pa.jsx)("p",{className:"mt-1 line-clamp-2 text-xs text-modiff-subtle-text",children:N})]})]}),(0,Pa.jsx)(Pe,{label:"Recommendation",children:(0,Pa.jsx)(Ne,{value:r.resourcePreference||"recommended",onValueChange:e=>{(async e=>{let t=at(),a={...r,resourcePreference:e};f({resourcePreference:e}),Zt(t);try{let e=await ba(a);$t(t),h(e)}catch(e){if(ea(e))return;Ee(String(e),{variant:"error"})}})(e)},options:ii.map(e=>({value:e.value,label:e.label})),"data-testid":"compatibility-preference"})}),(0,Pa.jsxs)("section",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[I?(0,Pa.jsxs)("p",{className:"truncate text-xs text-modiff-subtle-text",title:S,children:["Base: ",S]}):null,(0,Pa.jsx)("p",{className:"break-all font-semibold text-hf-yellow",children:li(x)}),(0,Pa.jsx)("div",{className:"flex flex-wrap gap-1.5",children:ri(x).map(e=>(0,Pa.jsx)("span",{className:"rounded bg-modiff-bg px-2 py-1 text-modiff-label text-modiff-subtle-text",children:e},e))}),!1===x.installed||x.repairRequired?(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(A,{size:15}),onClick:()=>v(x),disabled:e===ni(x),children:x.repairRequired?"Repair artifact":"Install in app"}):null]}),b.length?(0,Pa.jsxs)("section",{className:"grid gap-2",children:[(0,Pa.jsx)("h3",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:"Other options"}),b.map(e=>(0,Pa.jsxs)(Ge,{tone:"secondary",align:"left",fullWidth:!0,className:"h-auto p-2",onClick:()=>(e=>{if(e.requiresConfirmation&&"manual_only"===e.proof?.status){if(!e.installed||e.repairRequired)return void v(e);let t=ni(e);return void i({title:"Use community artifact?",message:`${t} is popularity-qualified but not quality-qualified by MoDiff. Use it for this workflow and machine?`,confirmText:"Use artifact",cancelText:"Cancel",onConfirm:()=>{i(null),w(e,t)},onCancel:()=>i(null)})}w(e)})(e),icon:"gguf"===e.artifactFormat?(0,Pa.jsx)(y,{size:15}):(0,Pa.jsx)(d,{size:15}),children:[(0,Pa.jsxs)("span",{className:"min-w-0 flex-1",children:[(0,Pa.jsx)("span",{className:"block truncate text-xs font-semibold",children:ni(e)}),(0,Pa.jsx)("span",{className:"block truncate text-modiff-label text-modiff-subtle-text",children:e.compatibilityEvidence?.label||e.healthBadge})]}),(0,Pa.jsx)(M,{size:14,className:"text-modiff-subtle-text"})]},e.id))]}):null,(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(k,{size:15}),onClick:()=>a("setup"),children:"Open setup"}),(0,Pa.jsx)(Ge,{icon:(0,Pa.jsx)(d,{size:15}),onClick:()=>{(async()=>{let e=at(),t="modules.ModelArtifact.QuantizeDiffusersComponents",a=Ft.getState().nodesRegistry;a[t]||(await Ft.getState().fetchNodes(),$t(e),a=Ft.getState().nodesRegistry);let i=De(t,a,{x:120,y:100});if(!i)return void Ee("The optimized-artifact node is unavailable in this backend.",{variant:"error"});let n=x.artifactResolution?.base?.repo||x.baseArtifact||ni(x);p(`Optimize ${n.split("/").pop()||"model"}`),At.getState().addNode(i),At.getState().setParam(i.id,"model_id",{source:"hub",value:n}),At.getState().setParam(i.id,"source_revision",x.artifactResolution?.base?.revision||"main"),x.pipelineClass&&At.getState().setParam(i.id,"pipeline_class",x.pipelineClass),ia.getState().saveActiveWorkflowTab(!0),s(!1),Ee("Optimized-copy workflow created. Review its admission and quality settings before running.",{variant:"success"})})()},children:"Create optimized copy"}),(0,Pa.jsxs)(Xe,{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs",label:"Evidence and requirements",buttonClassName:"min-h-0 p-0 text-xs",panelClassName:"mt-2 grid gap-2 break-words text-modiff-subtle-text",children:[(0,Pa.jsx)("p",{children:N}),x.requirementsMissing?.map(e=>(0,Pa.jsx)("p",{children:e},e)),C?.popularity?(0,Pa.jsxs)("p",{children:["Hub snapshot: ",C.popularity.downloads??0," downloads · ",C.popularity.likes??0," likes. Popularity is not compatibility proof."]}):null]})]})})}var di=(0,Ta.lazy)(()=>pe(()=>import(`./block-composition-tools.js?v=5bcd315505f5670a`).then(e=>e.l),[])),ci=(0,Ta.lazy)(()=>pe(()=>import(`./BlockInterfaceDialogContentV2.js?v=5bcd315505f5670a`),[]));function ui(){let e=$e(e=>e.target),t=$e(e=>e.close),a=ia(e=>e.activeWorkflowTabId),s=ia(e=>e.workflowCanvasEpoch),i=At(t=>{let a=t.nodes.find(t=>t.id===e?.snapshot.instanceId)?.data.blockInstanceV2;return!(!a||e?.subtreeId&&!a.effectiveGraph.nodes.some(t=>t.nodeId===e.subtreeId))})&&e?.context.workflowTabId===a&&e?.context.canvasEpoch===s;return(0,Ta.useEffect)(()=>{e&&!i&&t()},[e,i,t]),e&&i?(0,Pa.jsx)(Ta.Suspense,{fallback:(0,Pa.jsx)("p",{className:"p-3 text-sm",role:"status",children:"Loading Block editor…"}),children:"save"===e.mode?(0,Pa.jsx)(di,{nodeId:e.nodeId,context:e.context,onClose:t},e.requestId):(0,Pa.jsx)(ci,{...e,onClose:t},e.requestId)}):null}export{hs as a,ya as default,Qs as i,oi as n,os as o,ai as r,ui as t}; \ No newline at end of file +import{r as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{$ as t,An as a,B as s,Dn as i,E as l,Fn as n,Ft as r,Gt as o,Ht as d,J as c,K as u,Kt as m,Lt as p,M as f,Mt as h,Pn as x,Pt as g,Q as b,R as j,Rn as v,Rt as y,U as w,W as k,X as C,Xt as N,Yt as S,_n as I,bn as R,cr as $,dr as M,gt as T,jn as A,on as P,ot as _,pt as z,rt as q,sn as B,st as D,un as G,vn as E,wn as O,z as L,zn as F}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$a as H,$n as K,Aa as W,Ai as V,Ba as U,Bi as Q,Bt as J,C as Z,Ca as X,Ci as Y,Co as ee,Cr as te,Da as ae,Dn as se,Dr as ie,Ea as le,Ei as ne,En as re,Eo as oe,Fa as de,Fi as ce,Fn as ue,Fr as me,Gi as pe,Gt as fe,Ha as he,Hi as xe,Ht as ge,Ia as be,Ii as je,In as ve,Ir as ye,Jn as we,Jt as ke,Ka as Ce,Kt as Ne,La as Se,Li as Ie,Lr as Re,Lt as $e,Ma as Me,Mi as Te,Mo as Ae,Na as Pe,Ni as _e,Nr as ze,Ns as qe,Oa as Be,Oi as De,On as Ge,Oo as Ee,Pa as Oe,Pi as Le,Pr as Fe,Qa as He,Qt as Ke,Ra as We,Ri as Ve,Rt as Ue,S as Qe,Sa as Je,Si as Ze,Sr as Xe,St as Ye,Ti as et,Tn as tt,Tr as at,Ua as st,Ut as it,Vi as lt,Vn as nt,Vr as rt,Vt as ot,Wt as dt,Xt as ct,Yt as ut,Zi as mt,Zt as pt,_a as ft,bn as ht,c as xt,co as gt,dn as bt,dt as jt,eo as vt,er as yt,fa as wt,fn as kt,fr as Ct,hr as Nt,in as St,ir as It,ja as Rt,ji as $t,ka as Mt,ki as Tt,kn as At,kr as Pt,lo as _t,mo as zt,mr as qt,na as Bt,oc as Dt,pa as Gt,pn as Et,pr as Ot,q as Lt,qt as Ft,rn as Ht,ro as Kt,rr as Wt,so as Vt,tn as Ut,to as Qt,ua as Jt,uo as Zt,ur as Xt,vt as Yt,w as ea,wa as ta,wi as aa,wn as sa,wr as ia,xr as la,za as na,zi as ra,zo as oa,zt as da}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{i as ca,n as ua,r as ma}from"./block-composition-tools.js?v=cc046c26d6fe7aa4";import{n as pa,r as fa,t as ha}from"./presetDiff.js?v=cc046c26d6fe7aa4";import{t as xa}from"./WorkflowArtifactRequirementRow.js?v=cc046c26d6fe7aa4";import{n as ga,t as ba}from"./GraphNodeInputs.js?v=cc046c26d6fe7aa4";import{n as ja,t as va}from"./TemplateUsageTermsDialog.js?v=cc046c26d6fe7aa4";import{a as ya,t as wa}from"./controlledWorkflows.js?v=cc046c26d6fe7aa4";var ka=e(M(),1),Ca=$();function Na(e){navigator.clipboard?.writeText(e)}function Sa({error:e,status:t}){let a=t?.runtimeEnvironment,s=a?.devices.find(e=>e.device===a.defaultDevice),i=!(!t?.ready||!a?.executionReady),l="experimental"===a?.supportTier||"preview"===a?.supportTier,n=a?.installation,r=a?.issues.map(e=>e.message).join(" "),o=n?.steps?.filter(e=>"complete"!==e.status&&"skipped"!==e.status)??[];return(0,Ca.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"runtime-environment-card",children:[(0,Ca.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Ca.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Runtime environment"}),(0,Ca.jsx)(pt,{label:i?"Ready":"Needs setup",tone:i?"success":"error",title:e||r||a?.status})]}),(0,Ca.jsxs)("div",{className:"mt-2 flex flex-wrap gap-1.5",children:[(0,Ca.jsx)(pt,{label:`${a?.installedProfile??"Profile unverified"} · ${a?.supportTier??"unverified"}`,tone:l||!a?.profileVerified?"warning":"neutral",title:a?.profileVerified?`Requested: ${a.requestedProfile??"auto"}. Qualification: ${a.supportTier}.`:"Legacy backend: installation profile cannot be verified."}),(0,Ca.jsx)(pt,{label:s?`${s.backend} · ${s.device}`:"Detecting hardware",tone:s&&"cpu"!==s.backend?"success":"neutral",title:s?`${s.name} · ${s.vendor} · ${s.memoryKind} memory`:"Hardware has not been reported."}),(0,Ca.jsx)(pt,{label:`Torch ${t?.packages?.torch?.version??"unknown"}`,tone:"neutral",title:`Torch ${t?.packages?.torch?.version??"unknown"}`})]}),l&&(0,Ca.jsx)("p",{className:"mt-2 text-xs text-modiff-warning",children:"This profile passed local checks; model recipes still need physical qualification."}),!i&&a?.issues.length?(0,Ca.jsx)("ul",{className:"mt-2 grid gap-1 text-xs text-modiff-red","data-testid":"runtime-profile-issues",children:a.issues.map((e,t)=>(0,Ca.jsx)("li",{children:e.message},`${e.code??"runtime"}-${t}`))}):null,o.length>0&&(0,Ca.jsx)("ol",{className:"mt-3 grid gap-2","data-testid":"runtime-installation-checklist",children:o.map((e,t)=>(0,Ca.jsxs)("li",{className:"rounded-modiff-compact border border-modiff-border-subtle p-2 text-xs text-modiff-text",children:[(0,Ca.jsxs)("div",{className:"font-semibold",children:[t+1,". ",e.title??e.id]}),e.explanation&&(0,Ca.jsx)("p",{className:"mt-1 text-modiff-subtle-text",children:e.explanation}),e.requiresAdmin&&(0,Ca.jsx)("p",{className:"mt-1 text-modiff-warning",children:"Administrator approval required."}),e.requiresReboot&&(0,Ca.jsx)("p",{className:"mt-1 text-modiff-warning",children:"Resume after reboot or complete sign-out."}),e.command&&(0,Ca.jsxs)("div",{className:"mt-2 flex items-start gap-2",children:[(0,Ca.jsx)("code",{className:"min-w-0 flex-1 select-all break-all text-modiff-subtle-text",children:e.command}),(0,Ca.jsx)(Ht,{tone:"secondary",size:"compact",className:"h-auto px-2 py-1",onClick:()=>Na(e.command),children:"Copy"})]}),e.verification&&(0,Ca.jsxs)("p",{className:"mt-1 text-modiff-subtle-text",children:["Verify: ",e.verification]}),e.failureHelp&&(0,Ca.jsx)("p",{className:"mt-1 text-modiff-subtle-text",children:e.failureHelp})]},e.id??`${e.phase??"step"}-${t}`))}),n?.resumeCommand&&"complete"!==n.status&&(0,Ca.jsxs)("div",{className:"mt-2 flex items-start gap-2",children:[(0,Ca.jsx)("code",{className:"min-w-0 flex-1 select-all break-all text-xs text-modiff-subtle-text",children:n.resumeCommand}),(0,Ca.jsx)(Ht,{tone:"secondary",size:"compact",className:"h-auto px-2 py-1 text-xs",onClick:()=>Na(n.resumeCommand),children:"Copy resume"})]}),a?.repairCommand&&!i&&(!n?.resumeCommand||"complete"===n.status)&&(0,Ca.jsxs)("div",{className:"mt-2 flex items-start gap-2",children:[(0,Ca.jsx)("code",{className:"min-w-0 flex-1 select-all break-all text-xs text-modiff-subtle-text",children:a.repairCommand}),(0,Ca.jsx)(Ht,{tone:"secondary",size:"compact",className:"h-auto px-2 py-1 text-xs",onClick:()=>Na(a.repairCommand),children:"Copy repair"})]})]})}function Ia(e,t,a){return Ee(`${Dt.serverAddress}${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),timeoutMs:3e4,parse:a})}function Ra(e,t){let a="active"===t&&"active"===e.overlayStatus&&e.cutoverReady&&"qualified"===e.contractState,s="active"!==t&&"base"!==t?t:a?"active":"qualified"===e.contractState&&e.cutoverReady?e.overlayStatus:"unavailable";return`${s.replace(/_/g," ")}${"unavailable"===s?` (${e.contractState})`:""}.`}function $a(e){return`${"macos"===e.platform?"macOS":"windows"===e.platform?"Windows":"Linux"} ${"arm64"===e.machine?"ARM64":"x86-64"}`}function Ma(){let e=Ze(e=>e.optionalRuntimeCatalog),t=Ze(e=>e.discoveryRequests.optionalRuntimes),a=Ze(e=>e.fetchOptionalRuntimes),s=Je(e=>e.setAlertOpener),[i,l]=(0,ka.useState)(null),[n,r]=(0,ka.useState)([]),[o,d]=(0,ka.useState)(null),[c,u]=(0,ka.useState)(!0),[m,p]=(0,ka.useState)(null),[f,h]=(0,ka.useState)(null),x=(0,ka.useRef)(0),g=(0,ka.useRef)(!1),b=(0,ka.useRef)(0),j=(0,ka.useRef)(0),v=(0,ka.useCallback)(async()=>{let e=++x.current;u(!0);try{let[t,a]=await Promise.all([Ee(`${Dt.serverAddress}/runtime/optimizations`,{timeoutMs:15e3,parse:ae}),Ee(`${Dt.serverAddress}/runtime/optimizations/receipts`,{timeoutMs:15e3,parse:Mt})]);if(e!==x.current)return;l(t),r(a.receipts),p(null)}catch(t){throw e===x.current&&p(oe(t,"Could not load runtime optimizations.")),t}finally{e===x.current&&u(!1)}},[]);(0,ka.useEffect)(()=>{v().catch(()=>{})},[v]),(0,ka.useEffect)(()=>()=>{j.current+=1},[]);let y=(0,ka.useCallback)(async(e,t)=>{let s=++j.current,i=Date.now()+3e5;for(;s===j.current&&Date.now()window.setTimeout(e,1e3)),s!==j.current)return;await a();let i=Ze.getState().optionalRuntimeCatalog,l=i?.profiles.find(t=>t.id===e);if("active"===i?.processLoadStatus&&l?.specDigest===t&&"active"===l.overlayStatus){let e=Ze.getState();return void await Promise.all([e.fetchRuntimeStatus(),e.fetchStudioModelCapabilities()])}}s===j.current&&Ae("Runtime activation is still restarting. Refresh Setup before running this workflow.",{variant:"warning"})},[a]);(0,ka.useEffect)(()=>{if(!f||["cancelled","failed","ready"].includes(f.status))return;let e=new AbortController,t=window.setTimeout(async()=>{if(++b.current>2400)Ae("Runtime setup polling stopped. Refresh status to continue.",{variant:"warning"});else try{let t=await Ee(`${Dt.serverAddress}/runtime/optional-runtimes/jobs/${encodeURIComponent(f.id)}`,{signal:e.signal,parse:X});if(t.id!==f.id||t.profileId!==f.profileId||t.specDigest!==f.specDigest)throw Error("The optional-runtime job identity changed.");h(t),["cancelled","failed","ready"].includes(t.status)&&a()}catch{e.signal.aborted||h({...f})}},750);return()=>{e.abort(),window.clearTimeout(t)}},[a,f]);let w=async(e,t)=>{if(!g.current){g.current=!0,d(e);try{await t()}catch(e){Ae(oe(e,"Action failed."),{variant:"error"})}finally{g.current=!1,d(null)}}},k=(e,t,a)=>w(e,async()=>{Ae((await Ia(t,a,Be)).message??"Updated.",{variant:"success"}),v().catch(()=>{})}),C=(e,t,a,i)=>s({title:e,message:t,confirmText:a,cancelText:"Cancel",onConfirm:()=>{s(null),w(a.toLowerCase(),i)},onCancel:()=>s(null)}),N=(e,t)=>{let s=t?"Activate":"repair_required"===e.overlayStatus||"staged_unchecked"===e.overlayStatus?"Repair":"Install";C(`${s} optional runtime?`,t?`Activate validated ${e.label} and restart MoDiff.`:`Install ${e.label} from reviewed, locked artifacts. Activation is separate.`,s,t?async()=>{Ae((await Ia("/runtime/optional-runtimes/activate",{environmentId:t,profileId:e.id,specDigest:e.specDigest,consent:!0},ta)).message??"Activation selected.",{variant:"success"}),h(null),await a(),y(e.id,e.specDigest)}:async()=>{let t=await Ia("/runtime/optional-runtimes/install",{profileId:e.id,specDigest:e.specDigest,consent:!0},X);if(t.profileId!==e.id||t.specDigest!==e.specDigest)throw Error("Runtime job mismatch.");b.current=0,h(t),await a()})},S=()=>{f&&w("cancel",async()=>{let e=await Ia(`/runtime/optional-runtimes/jobs/${encodeURIComponent(f.id)}/cancel`,{},X);if(e.id!==f.id)throw Error("The cancellation response does not match the active job.");h(e),["cancelled","failed"].includes(e.status)&&await a()})},I=n.filter(e=>"workload"===e.kind&&"observed"===e.status),R=!(!e?.activeEnvironmentId||e.previousEnvironmentId||"repair_required"!==e.processLoadStatus),$=!(!e?.previousEnvironmentId&&!R);return(0,Ca.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[(0,Ca.jsxs)("div",{className:"grid gap-1 pb-2",children:[(0,Ca.jsx)("strong",{children:"Optional runtimes"}),t.error?(0,Ca.jsx)("p",{className:"text-xs text-modiff-warning",children:t.error}):"loading"===t.status?(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Loading optional runtimes..."}):"success"===t.status&&e?e.profiles.map(t=>{let a=le(e,t),s=f?.profileId===t.id&&f.specDigest===t.specDigest?f.result?.environmentId:void 0,i="qualified"===t.contractState&&t.cutoverReady,l=s??a,n=l?"Activate":"repair_required"===t.overlayStatus||"staged_unchecked"===t.overlayStatus?"Repair":"Install",r=i&&(l?t.activationAvailable:t.installActionAvailable&&"active"!==t.overlayStatus);return(0,Ca.jsxs)("div",{className:"grid gap-1 text-xs text-modiff-text","data-testid":`optional-runtime-${t.id}`,children:[(0,Ca.jsxs)("span",{children:[t.label," (",$a(t),"):"," ",Ra(t,e.processLoadStatus)]}),f?.profileId===t.id?(0,Ca.jsxs)("div",{className:"flex items-center gap-2","data-testid":"optional-runtime-progress",children:[(0,Ca.jsx)("span",{children:f.progress.message}),["queued","running"].includes(f.status)?(0,Ca.jsx)(Ht,{size:"compact",disabled:null!==o,onClick:S,children:"Cancel"}):null]}):null,(0,Ca.jsx)("div",{className:"flex gap-1.5",children:r?(0,Ca.jsx)(Ht,{size:"compact",disabled:null!==o||e.installBusy,loading:o===n.toLowerCase(),onClick:()=>N(t,l),children:n}):null})]},t.id)}):(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No optional runtimes."}),e&&$&&e.profiles.some(e=>e.activationAvailable&&e.cutoverReady&&"qualified"===e.contractState)?(0,Ca.jsx)(Ht,{size:"compact",disabled:null!==o||e.installBusy,loading:"rollback"===o,onClick:()=>C(R?"Roll back optional runtime to base?":"Roll back optional runtime?",R?"Remove the broken active optional environment and restart MoDiff with the validated base runtime. Active and queued runs must be stopped first.":"Select the previous validated optional environment and restart MoDiff. Active and queued runs must be stopped first.","Rollback",async()=>{Ae((await Ia("/runtime/optional-runtimes/rollback",{consent:!0},ta)).message??"Optional runtime rollback selected.",{variant:"success"}),await a()}),children:R?"Rollback to base":"Rollback"}):null]}),(0,Ca.jsxs)(bt,{label:"Optimizations",panelClassName:"grid gap-2 pt-2",children:[m?(0,Ca.jsxs)("div",{className:"flex items-center justify-between gap-2 rounded-modiff-compact border border-modiff-warning p-2",children:[(0,Ca.jsx)("span",{className:"text-xs text-modiff-warning",children:m}),(0,Ca.jsx)(Ht,{size:"compact",loading:c,onClick:()=>{v().catch(()=>{})},children:"Retry"})]}):c&&!i?(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Loading runtime optimizations…"}):null,i?.capabilities.map(e=>{let t=W(e.documentation);return(0,Ca.jsx)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:(0,Ca.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,Ca.jsxs)("div",{className:"min-w-0",children:[(0,Ca.jsx)("span",{className:"text-sm font-semibold text-modiff-text",children:e.label}),(0,Ca.jsx)("p",{className:"mt-1 text-xs text-modiff-subtle-text",children:e.summary}),e.disabledReason?(0,Ca.jsx)("p",{className:"mt-1 text-xs text-modiff-warning",children:e.disabledReason}):null]}),(0,Ca.jsxs)("div",{className:"flex shrink-0 flex-wrap gap-1.5",children:[(0,Ca.jsx)(Ht,{size:"compact",disabled:!t||null!==o,onClick:()=>{t&&window.open(t,"_blank","noopener,noreferrer")},children:"Docs"}),e.canEnable?(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(Ht,{size:"compact",disabled:null!==o,tone:e.enabled?"primary":"secondary",loading:o===`toggle:${e.id}`,onClick:()=>{k(`toggle:${e.id}`,"/runtime/optimizations/enable",{capabilityId:e.id,enabled:!e.enabled})},children:e.enabled?"Enabled":"Enable"}),(0,Ca.jsx)(Ht,{size:"compact",disabled:null!==o,loading:o===`probe:${e.id}`,onClick:()=>{k(`probe:${e.id}`,"/runtime/optimizations/probe",{capabilityId:e.id})},children:"Probe"})]}):null]})]})},e.id)}),I.map(e=>(0,Ca.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:[(0,Ca.jsxs)("span",{className:"text-xs text-modiff-text",children:["Review ",e.capabilityId," · ",e.modelType||e.artifact||"workload"]}),(0,Ca.jsx)(Ht,{size:"compact",loading:o===`qualify:${e.id}`,disabled:null!==o,onClick:()=>{k(`qualify:${e.id}`,"/runtime/optimizations/qualify",{receiptId:e.id,outputReviewed:!0})},children:"Reviewed"})]},e.id))]})]})}var Ta=/^[0-9a-f]{40}$/,Aa=/^sha256:canonical-json:[0-9a-f]{64}$/,Pa=/^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/,_a=new Set(["installing","missing","ready","repair_required","unavailable"]);function za(e,t){if(!e||"object"!=typeof e||Array.isArray(e))throw Error(`${t} must be an object.`);return e}function qa(e,t,a){if("string"!=typeof e||!e||e.length>512||a&&!a.test(e))throw Error(`${t} is invalid.`);return e}function Ba(e,t){if(!Number.isSafeInteger(e)||Number(e)<0)throw Error(`${t} is invalid.`);return Number(e)}function Da(e,t){if("boolean"!=typeof e)throw Error(`${t} is invalid.`);return e}function Ga(e){let t=za(e,"Template Gallery status");if(!1!==t.error||1!==t.schemaVersion)throw Error("Template Gallery status reports an unsupported contract.");let a=qa(t.status,"Template Gallery status");if(!_a.has(a))throw Error("Template Gallery status is unknown.");let s={status:a,installed:Da(t.installed,"Template Gallery installed state"),complete:Da(t.complete,"Template Gallery completion state"),repairRequired:Da(t.repairRequired,"Template Gallery repair state"),installing:Da(t.installing,"Template Gallery installation state"),...void 0===t.repoId?{}:{repoId:qa(t.repoId,"Template Gallery repository",Pa)},...void 0===t.revision?{}:{revision:qa(t.revision,"Template Gallery revision",Ta)},...void 0===t.assetSetId?{}:{assetSetId:qa(t.assetSetId,"Template Gallery asset set",Aa)},...void 0===t.assetCount?{}:{assetCount:Ba(t.assetCount,"Template Gallery asset count")},...void 0===t.totalBytes?{}:{totalBytes:Ba(t.totalBytes,"Template Gallery byte size")},...void 0===t.code?{}:{code:qa(t.code,"Template Gallery status code")},...void 0===t.message?{}:{message:qa(t.message,"Template Gallery status message")}};if("ready"===s.status&&(!s.installed||!s.complete||s.repairRequired||s.installing)||"missing"===s.status&&(s.installed||s.complete||s.repairRequired)||"repair_required"===s.status&&!s.repairRequired||"installing"===s.status&&!s.installing||"unavailable"!==s.status&&(!s.repoId||!s.revision||!s.assetSetId)||"ready"===s.status&&(void 0===s.assetCount||void 0===s.totalBytes))throw Error("Template Gallery status fields conflict.");return s}function Ea(e){let t=za(e,"Template Gallery plan");if(!1!==t.error||1!==t.schemaVersion)throw Error("Template Gallery plan reports an unsupported contract.");let a={repoId:qa(t.repoId,"Template Gallery repository",Pa),repoType:qa(t.repoType,"Template Gallery repository type"),revision:qa(t.revision,"Template Gallery revision",Ta),assetSetId:qa(t.assetSetId,"Template Gallery asset set",Aa),assetCount:Ba(t.assetCount,"Template Gallery asset count"),totalBytes:Ba(t.totalBytes,"Template Gallery total bytes"),downloadBytes:Ba(t.downloadBytes,"Template Gallery download bytes"),stagingBytes:Ba(t.stagingBytes,"Template Gallery staging bytes"),reservationBytes:Ba(t.reservationBytes,"Template Gallery reservation bytes"),queuedReservationBytes:Ba(t.queuedReservationBytes,"Model queue reservation bytes"),reserveBytes:Ba(t.reserveBytes,"Safety reserve bytes"),cacheFreeBytes:Ba(t.cacheFreeBytes,"Gallery cache free bytes"),destinationFreeBytes:Ba(t.destinationFreeBytes,"Gallery destination free bytes"),sameFilesystem:Da(t.sameFilesystem,"Template Gallery filesystem identity"),sizeKnown:Da(t.sizeKnown,"Template Gallery size state"),fitsWithQueue:Da(t.fitsWithQueue,"Template Gallery fit state"),installed:Da(t.installed,"Template Gallery installed state"),repairRequired:Da(t.repairRequired,"Template Gallery repair state"),repairReason:null===t.repairReason||void 0===t.repairReason?null:qa(t.repairReason,"Template Gallery repair reason"),installing:Da(t.installing,"Template Gallery installation state")};if("dataset"!==a.repoType||a.reservationBytes!==a.downloadBytes+a.stagingBytes||a.installed&&a.repairRequired||a.installed&&(0!==a.downloadBytes||0!==a.stagingBytes||0!==a.reservationBytes))throw Error("Template Gallery plan fields conflict.");return a}function Oa(e){let t=za(e,"Template Gallery install result");if(!1!==t.error||!0!==t.complete||"boolean"!=typeof t.alreadyInstalled)throw Error("Template Gallery install did not complete.");let a=Ea({...za(t.plan,"Template Gallery result plan"),error:!1});if(void 0===t.result){if(!t.alreadyInstalled)throw Error("Template Gallery install result is missing.");return{complete:!0,alreadyInstalled:!0,plan:a}}let s=za(t.result,"Installed Template Gallery receipt");if(!0!==s.installed||!0!==s.complete||!1!==s.repairRequired||s.assetSetId!==a.assetSetId||s.repoId!==a.repoId||s.revision!==a.revision||s.assetCount!==a.assetCount||s.totalBytes!==a.totalBytes)throw Error("Installed Template Gallery receipt does not match its plan.");return{complete:!0,alreadyInstalled:!1,plan:a,result:{installed:!0,complete:!0,repairRequired:!1,assetCount:Ba(s.assetCount,"Installed Template Gallery asset count"),totalBytes:Ba(s.totalBytes,"Installed Template Gallery byte size"),assetSetId:qa(s.assetSetId,"Installed Template Gallery asset set",Aa),repoId:qa(s.repoId,"Installed Template Gallery repository",Pa),revision:qa(s.revision,"Installed Template Gallery revision",Ta),restartRequired:Da(s.restartRequired,"Template Gallery restart requirement")}}}function La(e){if(!e||e<=0)return"0 B";let t=["B","KiB","MiB","GiB","TiB"],a=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),s=e/1024**a;return`${s.toFixed(s>=10||0===a?0:1)} ${t[a]}`}function Fa(e,t){return t&&!e?"Checking":e?"ready"===e.status?"Ready":"installing"===e.status?"Installing":"repair_required"===e.status?"Repair required":"missing"===e.status?"Not installed":"Unavailable":"Unavailable"}function Ha(){let e=Je(e=>e.setAlertOpener),[t,a]=(0,ka.useState)(null),[s,i]=(0,ka.useState)(!0),[l,n]=(0,ka.useState)(!1),[o,d]=(0,ka.useState)(null),c=(0,ka.useRef)(0),u=(0,ka.useCallback)(async()=>{let e=++c.current;i(!0);try{let t=await Ee(`${Dt.serverAddress}/template_gallery/status`,{timeoutMs:12e4,parse:Ga});if(e!==c.current)return;a(t),d(null)}catch(t){if(e!==c.current)return;d(oe(t,"Could not inspect Template Gallery assets."))}finally{e===c.current&&i(!1)}},[]);(0,ka.useEffect)(()=>{u()},[u]);let m="ready"===t?.status,p="missing"===t?.status||"repair_required"===t?.status;return(0,Ca.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"template-gallery-setup-card",children:[(0,Ca.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Ca.jsxs)("div",{className:"flex items-center gap-2",children:[(0,Ca.jsx)(r,{size:15,className:"text-hf-yellow"}),(0,Ca.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Template Gallery assets"})]}),(0,Ca.jsx)(pt,{label:Fa(t,s),tone:m?"success":o||t?.repairRequired?"error":"warning",title:o||t?.message||t?.assetSetId})]}),(0,Ca.jsx)("p",{className:"mt-2 text-xs leading-5 text-modiff-subtle-text",children:"Byte-pinned Gallery previews and default workflow inputs are installed from the app's immutable public Dataset. Model caches are separate and are never removed by this action."}),t?.complete&&(0,Ca.jsxs)("p",{className:"mt-1 text-xs text-modiff-green",children:[t.assetCount??0," files · ",La(t.totalBytes)," · ",t.revision?.slice(0,12)]}),o&&(0,Ca.jsx)("p",{className:"mt-1 break-words text-xs text-modiff-red",children:o}),(0,Ca.jsxs)("div",{className:"mt-2 flex flex-wrap gap-2",children:[p&&(0,Ca.jsx)(Ht,{size:"compact",loading:l||t?.installing,disabled:s||l||t?.installing,onClick:()=>{(async()=>{if(!l){n(!0);try{let t=await Ee(`${Dt.serverAddress}/template_gallery/plan`,{timeoutMs:12e4,parse:Ea});if(!t.sizeKnown||!t.fitsWithQueue)throw Error(`The verified ${La(t.reservationBytes)} Gallery reservation does not fit alongside ${La(t.queuedReservationBytes)} of active model reservations and the ${La(t.reserveBytes)} safety reserve.`);e({title:t.repairRequired?"Repair Template Gallery assets?":"Install Template Gallery assets?",message:`Download and hash-verify ${t.assetCount} files (${La(t.totalBytes)}) from ${t.repoId} at immutable revision ${t.revision.slice(0,12)}. Existing model downloads and cached models will not be removed.`,confirmText:t.repairRequired?"Repair":"Install",cancelText:"Cancel",onConfirm:()=>{e(null),(async()=>{try{Ae((await Ee(`${Dt.serverAddress}/template_gallery/install`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({}),timeoutMs:864e5,parse:Oa})).result?.restartRequired?"Template Gallery installed. Restart MoDiff after active downloads finish to serve it locally.":"Template Gallery is already installed.",{variant:"success"}),await u()}catch(e){let t=oe(e,"Template Gallery installation failed.");d(t),Ae(t,{variant:"error"})}finally{n(!1)}})()},onCancel:()=>{e(null),n(!1)}})}catch(e){let t=oe(e,"Could not plan the Template Gallery installation.");d(t),Ae(t,{variant:"error"}),n(!1)}}})()},"data-testid":"template-gallery-install",children:t?.repairRequired?"Check & repair":"Check & install"}),(0,Ca.jsx)(Ht,{size:"compact",tone:"secondary",icon:(0,Ca.jsx)(q,{size:14}),disabled:s||l,onClick:()=>{u()},"data-testid":"template-gallery-refresh",children:"Refresh"})]})]})}var Ka=(0,ka.lazy)(()=>Y(()=>import(`./CompositeMigrationCard.js?v=cc046c26d6fe7aa4`).then(e=>({default:e.CompositeMigrationCard})),[]));function Wa(e){if(!e||e<=0)return"0 B";let t=["B","KB","MB","GB","TB"],a=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),s=e/1024**a;return`${s.toFixed(s>=10||0===a?0:1)} ${t[a]}`}function Va(e){return e&&0!==Object.keys(e).length?Object.entries(e).slice(0,5).map(([e,t])=>`${e}: ${t}`).join(", "):"no files"}function Ua(e){return e?e.details?`${e.message} ${e.details}`:e.message:""}var Qa=["environment","package","backend","model_integrity","model","hardware_fit","asset","user_input","graph"],Ja={environment:"Environment",package:"Packages",backend:"Backend",model_integrity:"Model integrity",model:"Models",hardware_fit:"Hardware fit",asset:"Assets",user_input:"Inputs",graph:"Graph"};function Za(e){return"success"===e?"success":"warning"===e?"warning":"info"===e?"info":"error"}function Xa({children:e,tone:t="muted",testId:a}){return(0,Ca.jsx)("p",{"data-testid":a,className:ve("break-words text-xs leading-5","muted"===t&&"text-modiff-subtle-text","success"===t&&"text-modiff-green","error"===t&&"text-modiff-red","warning"===t&&"text-hf-orange"),children:e})}function Ya({children:e,disabled:t,onClick:a,progress:s,testId:i,title:l,tone:n}){return(0,Ca.jsx)(pt,{action:"Installing"===("string"==typeof e?e:void 0)?"installing":a?"install":"success"===n?"ready":"warning"===n||"error"===n?"missing":"details",className:"min-h-7 px-2 py-0.5 text-xs",disabled:t,label:e,onClick:a,progress:s,testId:i,title:l,tone:"default"===n?"neutral":n})}function es(){let e=Ze(e=>e.hfCache),t=Ze(e=>e.localModels),a=Ze(e=>e.modelCacheDiagnostics),s=Ze(e=>e.studioModelCapabilities),i=Ze(e=>e.runtimeStatus),l=Ze(e=>e.runtimeError),n=Ze(e=>e.hfDownloadProgress),r=Ze(e=>e.refreshModelIndexes),o=Ze(e=>e.fetchRuntimeStatus),d=Ze(e=>e.fetchStudioModelCapabilities),c=Ze(e=>e.installHfModel),m=$t(e=>e.form),p=$t(e=>e.activeTemplateId),f=$t(e=>e.graphBinding),h=$t(e=>e.sourceOutputId);rt(e=>[e.nodes.map(e=>{let t=Object.entries(e.data.params).filter(([e])=>/repo|model|checkpoint|ckpt|safetensors|lora|vae|controlnet|adapter/i.test(e)).map(([e,t])=>`${e}:${JSON.stringify(t.value??t.default??"")}`).join(",");return`${e.id}:${e.data.module}:${e.data.action}:${e.data.uiState?.disabled?"disabled":"enabled"}:${t}`}).join("|"),e.edges.map(e=>`${e.id}:${e.source}:${e.sourceHandle??""}->${e.target}:${e.targetHandle??""}`).join("|")].join("||"));let x=$t(e=>e.autoResourcePlans),b=$t(e=>e.setAutoResourcePlans),j=Je(e=>e.setModelManagerOpener),v=Je(e=>e.setRightPanelOpen),y=Je(e=>e.setRightPanelTab),w=Pt(e=>e.showIssues),k=we(e=>e.sid),C=we(e=>e.isConnected),I=Object.values(n).filter(_t).length,R=Object.entries(n).filter(([,e])=>_t(e)||gt(e)||Zt(e)),$=Ot(),M=!!(p||f||h),T=!(!f||!ye(f))||!M&&!f&&($.nodeCount>0||$.blockingIssues.length>0),A=Le(m),P=We[m.modelType],_=M&&P?[{profile:P,mode:m.mode,status:qe(P,e,t,a),backendCapability:s.find(e=>e.modelType===P.modelType),autoPlanKey:A,autoPlan:x[A]}]:[],z=M&&P?Xe({autoResourcePlan:x[A],form:m,hfCache:e,includePipeline:!1,localModels:t,modelCacheDiagnostics:a}):[],B=T?la({references:$.modelRefs,hfCache:e,localModels:t,modelCacheDiagnostics:a}).filter(e=>!e.status.runnable):[],D=$.blockingIssues[0]??null,E=function(e){return new Set(e.modelRefs.map(e=>`${e.kind}:${e.value}`)).size}($),O=ea({sid:k,isConnected:C,includeStudio:!T}),L=(0,ka.useMemo)(()=>Qa.map(e=>({category:e,label:Ja[e],issues:O.blockingIssues.filter(t=>t.category===e)})).filter(e=>e.issues.length>0),[O.blockingIssues]),F=async()=>{if(!M)return;let e=await lt([m],[A]),t={};e.forEach(e=>{let a=e.planKey??A;a&&(t[a]=e)}),b(t)},H=async()=>{await r(!0,{invalidateAutoPlans:!1}),await F()},K=()=>{v(!0),y("studio")},W=()=>{w(O.issues)},V=e=>"install_model"===e.action&&e.repoId?(0,Ca.jsx)(Ht,{tone:"primary",icon:(0,Ca.jsx)(G,{size:15}),onClick:()=>{(async e=>{try{await c(e,k),await H()}catch(e){console.error(e)}})(e.repoId)},children:"Install"}):"open_model_manager"===e.action?(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(u,{size:15}),onClick:()=>j({nodeId:null,fieldKey:null}),children:"Models"}):"refresh_cache"===e.action?(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(q,{size:15}),onClick:()=>{H()},children:"Refresh"}):"select_image"===e.action?(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(N,{size:15}),onClick:K,children:"Studio"}):(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(g,{size:15}),onClick:W,children:"Review"});(0,ka.useEffect)(()=>{o(),d()},[o,d]),(0,ka.useEffect)(()=>{if(!M)return;let e=!1;return lt([m],[A]).then(t=>{if(e)return;let a={};t.forEach(e=>{let t=e.planKey??A;t&&(a[t]=e)}),b(a)}).catch(e=>{console.error(e)}),()=>{e=!0}},[m,M,e.length,t.length,i?.ready,b,A]);let U=async e=>{try{await c(e.repo,k,{repair:e.repair,revision:e.revision,files:e.files}),await H()}catch(e){console.error(e)}},Q=async e=>{try{await Ve({modelType:e.modelType}),await F()}catch(e){console.error(e)}};return(0,Ca.jsxs)("div",{className:"grid gap-3 p-3 text-sm text-modiff-text","data-testid":"setup-panel",children:[(0,Ca.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Ca.jsx)("div",{children:(0,Ca.jsx)("h2",{className:"text-base font-bold text-modiff-text",children:"Model Health"})}),(0,Ca.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(u,{size:15}),"data-testid":"setup-models",onClick:()=>j({nodeId:null,fieldKey:null}),children:"Models"}),(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(q,{size:15}),"data-testid":"setup-refresh",onClick:()=>{H()},children:"Refresh"})]})]}),(0,Ca.jsx)(Sa,{error:l,status:i}),(0,Ca.jsx)(Ma,{}),(0,Ca.jsx)(Ha,{}),L.length>0&&(0,Ca.jsxs)("section",{className:"grid gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"setup-current-issues",children:[(0,Ca.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,Ca.jsx)("div",{children:(0,Ca.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Current issues"})}),(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(g,{size:15}),onClick:W,children:"Review all"})]}),L.map(e=>(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsxs)("h4",{className:"text-xs font-bold uppercase text-modiff-subtle-text",children:[e.label," (",e.issues.length,")"]}),(0,Ca.jsx)("div",{className:"grid gap-2",children:e.issues.map(e=>(0,Ca.jsx)(ue,{tone:Za(e.severity),title:e.message,action:V(e),testId:`setup-current-issue-${e.id}`},e.id))})]},e.category))]}),R.length>0&&(0,Ca.jsxs)("section",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"setup-downloads",children:[(0,Ca.jsxs)("div",{className:"mb-2 flex flex-wrap items-center gap-2",children:[(0,Ca.jsx)("h3",{className:"text-sm font-bold",children:"Downloads"}),(0,Ca.jsxs)(Ya,{tone:I>0?"default":"warning",title:`${I} active model download${1===I?"":"s"}`,children:[I," active"]})]}),(0,Ca.jsx)("div",{className:"grid gap-2",children:R.map(([e,t])=>(0,Ca.jsx)(Qe,{repoId:e,progress:t,testId:`setup-download-${e}`},e))})]}),(0,Ca.jsxs)(bt,{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"setup-advanced-diagnostics",label:(0,Ca.jsxs)("span",{className:"flex items-center gap-2",children:[(0,Ca.jsx)(S,{size:15,className:"text-hf-yellow"}),"Advanced diagnostics"]}),buttonClassName:"p-0",panelClassName:"mt-3 grid gap-3",children:[(0,Ca.jsx)(ka.Suspense,{fallback:(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Loading migration tools…"}),children:(0,Ca.jsx)(Ka,{})}),T?(0,Ca.jsxs)("section",{className:"grid gap-2","data-testid":"setup-workflow-model-health",children:[(0,Ca.jsxs)("div",{className:"flex items-center gap-2",children:[(0,Ca.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Current graph"}),(0,Ca.jsx)(Ya,{tone:"default",title:"Models referenced by the visible canvas graph are shown here.",children:"Contextual"})]}),(0,Ca.jsx)(Ge,{tone:D||B.length>0?"error":"success",title:D?"Graph needs attention":"Graph model checks",meta:Ua(D)||`${$.nodeCount} nodes, ${$.outputPathCount} connected output path${1===$.outputPathCount?"":"s"}.`,testId:"setup-current-graph-health",children:(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsxs)("div",{className:"flex flex-wrap gap-1.5",children:[(0,Ca.jsxs)(Ya,{tone:$.outputPathCount>0?"success":"error",title:`${$.outputPathCount} connected output path${1===$.outputPathCount?"":"s"}.`,children:[$.outputPathCount," outputs"]}),(0,Ca.jsxs)(Ya,{tone:B.length>0?"warning":"success",title:`${E} detected model reference${1===E?"":"s"}.`,children:[E," models"]})]}),B.length>0&&(0,Ca.jsx)("div",{className:"grid gap-2 border-t border-modiff-border pt-2",children:B.map(e=>(0,Ca.jsx)(xa,{activeInstallCount:I,actionTestId:`setup-install-graph-requirement-${e.id}`,installProgress:n,onInstall:e=>U(e),onUseLocal:e=>j({nodeId:null,fieldKey:null,focus:{label:e.label,repo:e.repo,requirementId:e.id,source:"setup"}}),requirement:e,testId:`setup-graph-requirement-${e.id}`},e.id))})]})})]}):M?(0,Ca.jsxs)("section",{className:"grid gap-2","data-testid":"setup-workflow-model-health",children:[(0,Ca.jsxs)("div",{className:"flex items-center gap-2",children:[(0,Ca.jsx)("h3",{className:"text-sm font-bold text-modiff-text",children:"Workflow model"}),(0,Ca.jsx)(Ya,{tone:"default",title:"Only models used by the current workflow are shown here.",children:"Contextual"})]}),_.map(({autoPlan:e,backendCapability:t,profile:a,status:s})=>(0,Ca.jsx)(ts,{activeInstallCount:I,autoPlan:e,form:m,backendCapability:t,hfDownloadProgress:n,onInstall:U,onUseLocal:e=>j({nodeId:null,fieldKey:null,focus:{label:e.label,repo:e.repo,requirementId:e.id,source:"setup"}}),onClearHistory:Q,profile:a,requirements:z,status:s},a.modelType))]}):(0,Ca.jsxs)("section",{className:"flex items-center gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3 text-xs text-modiff-subtle-text","data-testid":"setup-workflow-model-empty",children:[(0,Ca.jsx)(g,{size:15,className:"shrink-0 text-modiff-subtle-text"}),(0,Ca.jsx)("span",{children:"Workflow model checks appear after you create, update, or restore a workflow."})]}),a&&(0,Ca.jsxs)(bt,{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-3",label:(0,Ca.jsxs)("span",{className:"flex items-center gap-2",children:[(0,Ca.jsx)(S,{size:15,className:"text-hf-yellow"}),"Diagnostics and scanned locations"]}),buttonClassName:"p-0",panelClassName:"mt-3 grid gap-3",children:[(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)("h3",{className:"mb-1 text-sm font-bold",children:"Scanned model locations"}),(a.locations||[]).map(e=>{let t=e.repo_count??e.file_count??0,a=void 0===e.repo_count?`${t} files`:`${t} repos`,s=[e.external_package_count?`${e.external_package_count} external packages`:null,e.compatible_hf_repo_count?`${e.compatible_hf_repo_count} HF-compatible repos`:null,e.model_file_bytes?`${Wa(e.model_file_bytes)} model weights`:null].filter(Boolean).join(" | ");return(0,Ca.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2",children:[(0,Ca.jsxs)(Xa,{tone:e.exists?"muted":"warning",children:[e.label,": ",e.path||"default"," | ",e.exists?"exists":"missing"," |"," ",a," | ",!1===e.runnable?"discovered but not runnable":"runnable"]}),s&&(0,Ca.jsxs)(Xa,{children:[s," | ",Va(e.extension_counts)]}),e.reason&&!1===e.runnable&&(0,Ca.jsx)(Xa,{tone:"warning",children:e.reason}),e.error&&(0,Ca.jsx)(Xa,{tone:"error",children:e.error})]},`${e.label}-${e.path}`)})]}),(a.external_model_packages?.length??0)>0&&(0,Ca.jsxs)("section",{"data-testid":"setup-external-models",children:[(0,Ca.jsx)("h3",{className:"mb-1 text-sm font-bold",children:"Detected external packages"}),(0,Ca.jsx)("div",{className:"grid gap-2",children:a.external_model_packages?.slice(0,8).map(e=>(0,Ca.jsxs)(Ge,{tone:e.runnable?"success":"warning",title:e.label,meta:`${e.format} | ${e.file_count??0} files | ${Wa(e.model_file_bytes)}`,children:[(0,Ca.jsx)(Xa,{children:e.path}),(0,Ca.jsx)(Xa,{tone:e.runnable?"success":"warning",children:e.reason})]},`${e.source}-${e.path}`))})]}),(a.hf_compatible_external_repos?.length??0)>0&&(0,Ca.jsxs)("section",{"data-testid":"setup-external-hf-repos",children:[(0,Ca.jsx)("h3",{className:"mb-1 text-sm font-bold",children:"HF-compatible external repos"}),(0,Ca.jsx)("div",{className:"grid gap-2",children:a.hf_compatible_external_repos?.slice(0,8).map(e=>(0,Ca.jsxs)(Ge,{tone:"warning",title:e.repo_id,meta:e.source,children:[(0,Ca.jsx)(Xa,{children:e.path}),(0,Ca.jsx)(Xa,{tone:"warning",children:e.reason})]},`${e.source}-${e.path}`))})]})]})]})]})}function ts({activeInstallCount:e,autoPlan:t,form:a,backendCapability:s,hfDownloadProgress:i,onClearHistory:l,onInstall:n,onUseLocal:r,profile:o,requirements:d,status:c}){let u=Ie(t,a),m=pe(t,a),p=_e(t,a),f=t?p:c.runnable,h=u?.repo??o.defaultRepo,x=i[h],g=_t(x),b=s?.inpaintContract??o.inpaintContract,j=je(t,a),v=ce(t,a),y=f?"success":"unsuitable"===v.state?"error":"warning",w=f?"success":"error"===y?"error":"warning",k=m?.resolvedArtifact??m?.artifact??u?.repo??o.defaultRepo,C=f?j:g?"Installing":gt(x)?"Retry":u?.actionLabel??"Install",N=g||!f&&e>=2;return(0,Ca.jsx)(Ge,{testId:`setup-model-${o.modelType}`,tone:y,title:o.label,meta:k,action:(0,Ca.jsx)(Ya,{tone:w,title:f?j:u?.reason??u?.repo??j,disabled:N,progress:g?Vt(x):null,testId:`setup-install-${o.modelType}`,onClick:!f&&u?()=>{n(u)}:void 0,children:C}),children:(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsxs)("div",{className:"flex flex-wrap gap-1.5",children:[(0,Ca.jsx)(Ya,{tone:c.runnable?"success":"warning",title:c.reason,children:c.runnable?"Cached":"Missing"}),(0,Ca.jsx)(Ya,{tone:f?"success":"error"===y?"error":"warning",title:t?.blockingReason||m?.reason||u?.reason||t?.willNotWorkReason||j,children:"Auto"}),(0,Ca.jsx)(Ya,{tone:s?"success":"warning",title:s?`Backend modes: ${(s.modes||[]).join(", ")}`:"Backend capability metadata not reported for this profile.",children:"Backend"}),(0,Ca.jsx)(Ya,{tone:"success"===v.severity?"success":"error"===v.severity?"error":"info"===v.severity?"default":"warning",title:`${v.summary} ${v.detail}`,children:"Compatibility"}),b&&(0,Ca.jsx)(Ya,{tone:b.available?"success":"warning",title:b.available?"Native mask execution available.":b.reason,children:"Inpaint"})]}),c.matchingExternalPackages.length>0&&(0,Ca.jsx)(Ya,{tone:"warning",title:`Matching external package: ${c.matchingExternalPackages.slice(0,3).join(", ")}`,children:"External package"}),c.matchingExternalRepos.length>0&&(0,Ca.jsx)(Ya,{tone:"warning",title:`Matching HF-compatible external repo: ${c.matchingExternalRepos.slice(0,3).join(", ")}`,children:"External repo"}),x&&(0,Ca.jsx)(Qe,{testId:`setup-download-progress-${o.modelType}`,repoId:h,progress:x}),(t?.failureHistory?.length??0)>0&&(0,Ca.jsx)("div",{children:(0,Ca.jsx)(Ht,{tone:"secondary","data-testid":`setup-clear-auto-history-${o.modelType}`,onClick:()=>{l(o)},children:"Clear Auto history"})}),d.length>0&&(0,Ca.jsx)("div",{className:"grid gap-2 border-t border-modiff-border pt-2",children:d.map(t=>(0,Ca.jsx)(xa,{activeInstallCount:e,actionTestId:`setup-install-requirement-${t.id}`,installProgress:i,onInstall:e=>n(e),onUseLocal:r,requirement:t,testId:`setup-model-requirement-${t.id}`},t.id))})]})})}function as({children:e,disabled:t,icon:a,onClick:s,testId:i}){return(0,Ca.jsx)(Ht,{tone:"primary",icon:a,"data-testid":i,disabled:t,onClick:s,children:(0,Ca.jsx)("span",{children:e})})}function ss(e){if("number"!=typeof e||!Number.isFinite(e))return"-";if(e<1e3)return`${Math.max(0,Math.round(e))}ms`;let t=e/1e3;return t<60?`${t.toFixed(+(t<10))}s`:`${Math.floor(t/60)}:${Math.round(t%60).toString().padStart(2,"0")}`}function is(e){return"number"==typeof e&&Number.isFinite(e)?ss(1e3*e):null}function ls(e){return"number"==typeof e.durationMs?e.durationMs:e.completedAtMs&&e.startedAtMs?Math.max(0,e.completedAtMs-e.startedAtMs):void 0}function ns({children:e,fallbackStatus:t,focused:a=!1,pending:s=!1,severity:i="default",task:l,taskId:n,testId:r}){if(!l)return null;let o=re(l,n),d=ia(at(l))||l.error||l.message||`${o} · ${t}`;return(0,Ca.jsx)(sa,{content:d,placement:"top",children:c=>(0,Ca.jsx)(Ht,{...c,align:"left",fullWidth:!0,tone:"secondary",loading:s,"data-testid":r,"aria-label":`Open ${o} run details. ${d}`,onClick:()=>{tt(se(l,n,t))},className:ve("h-auto! min-w-0 rounded-none p-2","success"===i&&"border-modiff-green/60","error"===i&&"border-modiff-red/70 bg-modiff-red/10 hover:border-modiff-red",a&&"ring-2 ring-hf-yellow"),children:e})})}function rs(){let e=te(e=>e.currentTask),t=te(e=>e.queuedTasks),a=te(e=>e.failedTasks),i=te(e=>e.sessionRuns),l=te(e=>e.fetchTasks),n=te(e=>e.clearCompletedSessionRuns),r=te(e=>e.focusedTaskId),o=Je(e=>e.runActivityPendingTaskId),d=Object.entries(t),c=Object.entries(a).slice(0,8),u=i.filter(e=>"completed"===e.status||"failed"===e.status||"cancelled"===e.status).slice(0,10);return(0,ka.useEffect)(()=>{l()},[l]),(0,ka.useEffect)(()=>{r&&document.getElementById(`queue-task-${r}`)?.scrollIntoView({block:"nearest",behavior:"smooth"})},[r,e,t,i,a]),(0,Ca.jsxs)("div",{className:"p-3 text-sm text-modiff-text","data-testid":"queue-panel",children:[(0,Ca.jsxs)("div",{className:"mb-2 flex flex-wrap gap-2",children:[(0,Ca.jsx)(as,{icon:(0,Ca.jsx)(q,{size:15}),testId:"queue-refresh",onClick:()=>{l()},children:"Refresh"}),(0,Ca.jsx)(as,{icon:(0,Ca.jsx)(s,{size:15}),testId:"queue-stop",onClick:()=>{(async()=>{try{Ae((await ee()).message||"Stop requested",{variant:"success",autoHideDuration:3e3})}catch(e){Ae(oe(e,"Could not stop the current execution."),{variant:"error",autoHideDuration:5e3})}})()},disabled:!e,children:"Stop"})]}),(0,Ca.jsx)("h2",{className:"mb-2 text-sm font-bold",children:"Current run"}),e?(0,Ca.jsx)(ns,{fallbackStatus:"running",focused:r===e.task_id,pending:o===e.task_id,task:e,taskId:e.task_id||"current",testId:"queue-current-run",children:(0,Ca.jsxs)("span",{id:`queue-task-${e.task_id||"current"}`,className:"block min-w-0 flex-1",children:[(0,Ca.jsxs)("span",{className:"flex items-center justify-between gap-2",children:[(0,Ca.jsxs)("span",{className:"min-w-0",children:[(0,Ca.jsx)("span",{className:"block truncate text-sm",children:re(e,e.task_id||"Running task")}),(0,Ca.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:e.message||e.task_id||e.sid})]}),(0,Ca.jsx)(I,{size:16,className:"flex-none text-hf-yellow"})]}),(e.current_step||e.eta_seconds||e.average_step_seconds)&&(0,Ca.jsxs)("span",{className:"mt-2 flex flex-wrap gap-x-3 gap-y-1 text-xs text-modiff-subtle-text",children:[e.current_step&&e.total_steps?(0,Ca.jsx)("span",{children:`Step ${e.current_step}/${e.total_steps}`}):null,is(e.eta_seconds)?(0,Ca.jsx)("span",{children:`ETA ${is(e.eta_seconds)}`}):null,is(e.average_step_seconds)?(0,Ca.jsx)("span",{children:`${is(e.average_step_seconds)}/step`}):null]}),(0,Ca.jsx)(Ut,{value:e.progress,className:"mt-2"})]})}):(0,Ca.jsx)("p",{className:"mb-4 text-xs text-modiff-subtle-text",children:"No current run."}),(0,Ca.jsx)("h2",{className:"mb-2 text-sm font-bold",children:"Queued runs"}),0===d.length?(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No queued runs."}):(0,Ca.jsx)("div",{className:"grid gap-2",children:d.map(([e,t])=>(0,Ca.jsxs)("div",{id:`queue-task-${e}`,"data-testid":`queue-run-${e}`,className:ve("flex items-stretch border border-modiff-border bg-modiff-surface",r===e&&"ring-2 ring-hf-yellow"),children:[(0,Ca.jsx)(Ht,{align:"left",fullWidth:!0,tone:"ghost",loading:o===(t.task_id||e),"aria-label":`Open ${re(t,e)} run details`,onClick:()=>{tt(se(t,e,"queued"))},className:"h-auto! min-w-0 flex-1 rounded-none p-2",children:(0,Ca.jsxs)("span",{className:"min-w-0 flex-1",children:[(0,Ca.jsx)("span",{className:"block truncate text-sm",children:re(t,e)}),(0,Ca.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:t.task_id||t.sid})]})}),(0,Ca.jsx)(Ht,{tone:"ghost",size:"dense",title:"Cancel queued task",onClick:()=>{(async e=>{try{await zt(e),Ae("Queued task cancelled",{variant:"success",autoHideDuration:2200}),await l()}catch(e){Ae(oe(e,"Could not cancel the queued task."),{variant:"error",autoHideDuration:5e3})}})(e)},icon:(0,Ca.jsx)(R,{size:15}),className:"h-auto! flex-none rounded-none border-l border-modiff-border px-2 text-modiff-subtle-text",children:(0,Ca.jsx)("span",{children:"Cancel"})})]},e))}),(0,Ca.jsxs)("div",{className:"mb-2 mt-4 flex items-center justify-between gap-2",children:[(0,Ca.jsx)("h2",{className:"text-sm font-bold",children:"Session history"}),(0,Ca.jsx)(Ht,{tone:"ghost",size:"dense",disabled:0===u.length,onClick:n,icon:(0,Ca.jsx)(L,{size:14}),className:"px-2 text-xs",children:"Clear finished"})]}),0===u.length?(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Completed runs will appear here for this browser session."}):(0,Ca.jsx)("div",{className:"grid gap-2",children:u.map(e=>(0,Ca.jsx)(ns,{fallbackStatus:e.status??"completed",focused:r===(e.task_id||e.id),pending:o===(e.task_id||e.id),severity:"failed"===e.status?"error":"completed"===e.status?"success":"default",task:e,taskId:e.task_id||e.id,testId:`queue-session-run-${e.id}`,children:(0,Ca.jsx)("span",{id:`queue-task-${e.task_id||e.id}`,className:"block min-w-0 flex-1",children:(0,Ca.jsxs)("span",{className:"flex items-start gap-2",children:["completed"===e.status?(0,Ca.jsx)(O,{size:16,className:"mt-0.5 flex-none text-modiff-green"}):(0,Ca.jsx)(R,{size:16,className:"mt-0.5 flex-none text-modiff-red"}),(0,Ca.jsxs)("span",{className:"min-w-0 flex-1",children:[(0,Ca.jsx)("span",{className:"block truncate text-sm",children:re(e,e.task_id||e.id)}),(0,Ca.jsx)("span",{className:"block truncate text-xs text-modiff-subtle-text",children:e.error||e.message||e.task_id||e.sid})]}),(0,Ca.jsx)("span",{className:"flex-none text-xs font-semibold text-modiff-subtle-text",children:ss(ls(e))})]})})},e.id))}),(0,Ca.jsx)("h2",{className:"mb-2 mt-4 text-sm font-bold",children:"Failed runs"}),0===c.length?(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"No failed runs recorded in this session."}):(0,Ca.jsx)("div",{className:"grid gap-2",children:c.map(([e,t])=>(0,Ca.jsx)(ns,{fallbackStatus:"failed",focused:r===e,pending:o===(t.task_id||e),severity:"error",task:t,taskId:e,testId:`queue-failed-run-${e}`,children:(0,Ca.jsxs)("span",{id:`queue-task-${e}`,className:"block min-w-0 flex-1",children:[(0,Ca.jsx)("span",{className:"block truncate text-sm text-modiff-red",children:re(t,e)}),(0,Ca.jsx)("span",{className:"block break-all text-xs text-modiff-subtle-text",children:t.task_id||t.sid}),t.error&&(0,Ca.jsx)("span",{className:"block break-words text-xs text-modiff-red",children:t.error})]})},e))})]})}function os(){let e=(0,ka.useRef)(null),[t,a]=(0,ka.useState)(""),[s,i]=(0,ka.useState)(!1),{referenceImages:l,addReferenceImage:r,replaceReferenceImage:o,removeReferenceImage:d,moveReferenceImage:c,setReferenceImages:u,alphaMode:m,updateForm:p}=$t(nt(e=>({referenceImages:e.form.referenceImages,alphaMode:e.form.alphaMode,addReferenceImage:e.addReferenceImage,replaceReferenceImage:e.replaceReferenceImage,removeReferenceImage:e.removeReferenceImage,moveReferenceImage:e.moveReferenceImage,setReferenceImages:e.setReferenceImages,updateForm:e.updateForm})));return(0,Ca.jsxs)("div",{children:[(0,Ca.jsx)($e,{title:"Image input workspace",action:(0,Ca.jsx)(dt,{title:"Upload images",onClick:()=>e.current?.click(),disabled:s,children:s?(0,Ca.jsx)(Ue,{size:18}):(0,Ca.jsx)(N,{size:16})})}),(0,Ca.jsx)(Et,{ref:e,"aria-label":"Upload reference images",accept:Ye(["image"]),multiple:!0,hidden:!0,onChange:e=>{(async e=>{if(!e||0===e.length)return;let t=ne();i(!0);try{let a=[];for(let s of Array.from(e))a.push(...await Wt(s,"images")),et(t);u([...$t.getState().form.referenceImages,...a]),aa(t),Re()}catch(e){if(De(e))return;Ae(oe(e,"Could not upload the reference image."),{variant:"error",autoHideDuration:5e3})}finally{i(!1)}})(e.target.files)}}),(0,Ca.jsxs)("div",{className:"mb-2 flex gap-2",children:[(0,Ca.jsx)(ut,{"aria-label":"Reference image path or URL",value:t,onChange:e=>a(e.target.value),placeholder:"Image path or URL",className:"flex-1"}),(0,Ca.jsx)(dt,{title:"Add image",onClick:()=>{r(t),a(""),queueMicrotask(()=>Re())},disabled:!t.trim(),children:(0,Ca.jsx)(y,{size:17})})]}),(0,Ca.jsx)(Ft,{"aria-label":"Reference image alpha handling",value:m,onValueChange:e=>{p({alphaMode:e}),queueMicrotask(()=>Re())},options:[{value:"ignore",label:"Alpha: ignore"},{value:"add alpha",label:"Alpha: add alpha"},{value:"remove alpha",label:"Alpha: remove alpha"}],className:"mb-2 w-full"}),(0,Ca.jsx)("div",{className:"grid grid-cols-[repeat(auto-fit,minmax(86px,1fr))] gap-2",children:l.map((e,t)=>(0,Ca.jsxs)("div",{className:"border border-modiff-border bg-modiff-bg",children:[(0,Ca.jsx)(At,{src:Bt(e),alt:e,fit:"cover",aspectRatio:"1 / 1",bordered:!1}),(0,Ca.jsx)(ut,{"aria-label":`Reference image ${t+1} path`,value:e,onChange:e=>{o(t,e.target.value),queueMicrotask(()=>Re())},className:"h-7 w-full border-0 bg-transparent px-1 text-xs"}),(0,Ca.jsxs)("div",{className:"flex justify-between",children:[(0,Ca.jsx)(dt,{title:"Move up",disabled:0===t,onClick:()=>{c(t,-1),queueMicrotask(()=>Re())},children:(0,Ca.jsx)(n,{size:15})}),(0,Ca.jsx)(dt,{title:"Move down",disabled:t===l.length-1,onClick:()=>{c(t,1),queueMicrotask(()=>Re())},children:(0,Ca.jsx)(F,{size:15})}),(0,Ca.jsx)(dt,{title:"Remove",onClick:()=>{d(t),queueMicrotask(()=>Re())},children:(0,Ca.jsx)(L,{size:15})})]})]},`${e}-${t}`))})]})}function ds({isWorking:e,runBlocked:a,expertMode:s=!1,hasManagedForm:i=!0,hasComparePair:l,hasGalleryItems:n,onCompareLatest:o,onExport:d,onInterrupt:c,onRun:p,onUpdateGraph:f,onSavePrompt:h,onOpenGallery:x,onOpenTemplateGallery:g,onOpenSetup:b,onRestoreLatest:j,onRerunLatest:y}){let[w,k]=(0,ka.useState)(!1),[N,S]=(0,ka.useState)(""),I=(0,ka.useMemo)(()=>[{id:"run",label:"Run workflow",detail:"Queue the current Studio graph.",icon:(0,Ca.jsx)(D,{size:15}),disabled:e||a,run:p},...s&&i?[{id:"update",label:"Sync graph",detail:"Reconcile Studio fields with visible graph nodes.",icon:(0,Ca.jsx)(q,{size:15}),disabled:e,run:f}]:[],{id:"interrupt",label:"Interrupt run",detail:"Ask the backend to stop the active or queued execution.",icon:(0,Ca.jsx)(T,{size:15}),run:c},...i?[{id:"save-prompt",label:"Save prompt",detail:"Store the current prompt as a reusable snippet.",icon:(0,Ca.jsx)(C,{size:15}),run:h}]:[],...s?[{id:"export",label:"Export API graph",detail:"Download the current runnable graph JSON.",icon:(0,Ca.jsx)(G,{size:15}),run:d}]:[],{id:"gallery",label:"Open gallery",detail:"Inspect, compare, restore, rerun, and export outputs.",icon:(0,Ca.jsx)(r,{size:15}),run:x},{id:"compare",label:"Compare latest",detail:"Open the latest two Gallery outputs side by side.",icon:(0,Ca.jsx)(v,{size:15}),disabled:!l,run:o},{id:"restore-latest",label:"Restore latest",detail:"Restore the newest Gallery workflow into Studio.",icon:(0,Ca.jsx)(t,{size:15}),disabled:!n,run:j},{id:"rerun-latest",label:"Rerun latest",detail:"Restore and queue the newest Gallery workflow.",icon:(0,Ca.jsx)(D,{size:15}),disabled:!n||e||a,run:y},{id:"templates",label:"Browse templates",detail:"Search categorized workflow templates for the current task.",icon:(0,Ca.jsx)(m,{size:15}),run:g},{id:"setup",label:"Open setup",detail:"Repair missing models and runtime readiness.",icon:(0,Ca.jsx)(u,{size:15}),run:b}],[s,i,l,n,e,o,d,c,x,b,g,j,y,p,h,f,a]),R=(0,ka.useMemo)(()=>{let e=N.trim().toLowerCase();return e?I.filter(t=>`${t.label} ${t.detail}`.toLowerCase().includes(e)):I},[I,N]);return(0,ka.useEffect)(()=>{let t=t=>{let s=t.ctrlKey||t.metaKey;s&&"k"===t.key.toLowerCase()&&(t.preventDefault(),k(!0)),s&&"Enter"===t.key&&!e&&!a&&(t.preventDefault(),p())};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[e,p,a]),(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(J,{tone:"ghost",fullWidth:!0,icon:(0,Ca.jsx)(m,{size:15}),onClick:()=>k(!0),children:"Command palette"}),(0,Ca.jsxs)(St,{open:w,onClose:()=>{k(!1),S("")},title:"Command palette",panelClassName:"max-w-xl",bodyClassName:"!p-2",children:[(0,Ca.jsx)(Ne,{autoFocus:!0,"aria-label":"Search commands",value:N,onChange:e=>S(e.target.value),onClear:()=>S(""),placeholder:"Search commands",className:"mb-2"}),(0,Ca.jsxs)("div",{className:"grid gap-1",children:[R.map(e=>(0,Ca.jsxs)(J,{tone:"ghost",align:"left",fullWidth:!0,disabled:e.disabled,onClick:()=>(e=>{e.disabled||(e.run(),k(!1),S(""))})(e),className:"h-auto border border-transparent px-2 py-2 hover:border-hf-yellow hover:bg-modiff-bg",children:[(0,Ca.jsx)("span",{className:"grid size-7 shrink-0 place-items-center text-hf-yellow",children:e.icon}),(0,Ca.jsxs)("span",{className:"min-w-0",children:[(0,Ca.jsx)("span",{className:"block text-sm font-bold text-modiff-text",children:e.label}),(0,Ca.jsx)("span",{className:"block text-xs text-modiff-subtle-text",children:e.detail})]})]},e.id)),0===R.length&&(0,Ca.jsx)("p",{className:"p-2 text-xs text-modiff-subtle-text",children:"No matching commands."})]})]})]})}function cs(e){return"ready"===e?"success":"blocked"===e?"error":"default"}function us(e){return"ready"===e?"Ready":"draft"===e?"Draft":"blocked"===e?"Blocked":"Registry"}function ms({disabled:e=!1,form:t,onChange:a}){let[s,i]=(0,ka.useState)(null),l=Ze(e=>e.nodesRegistry),n=Object.keys(l).length>0,r=We[t.modelType],o=!!l[Jt.lora],d=!!l[Jt.upscaler],c=!!l[de],u=async(e,s)=>{let l={...t,...s};i(e);try{a(s),await ze(l),Ae("Controlled workflow prepared",{variant:"success",autoHideDuration:1800})}catch(e){Ae(String(e),{variant:"error",autoHideDuration:6500})}finally{i(null)}},m=async(e,t)=>{i(e);try{await t()}catch(e){Ae(String(e),{variant:"error",autoHideDuration:6500})}finally{i(null)}},f=[{id:"control",title:"ControlNet control image",detail:"Qwen Image plus Qwen ControlNet Union.",status:"ready",icon:(0,Ca.jsx)(y,{size:15}),button:"Use control",onClick:()=>u("control",{mode:"control_image",modelType:"QwenImageModularPipeline",controlImage:t.controlImage||t.referenceImages[0]||""})},{id:"lora",title:"LoRA adapter",detail:r.supportsLora?"Adds Lora -> ModelsLoader lora_list.":"Current model metadata does not support LoRA.",status:r.supportsLora?o||!n?"ready":"needs-registry":"blocked",icon:(0,Ca.jsx)(x,{size:15}),button:"Add block",onClick:r.supportsLora?()=>m("lora",()=>wa()):void 0},{id:"upscale",title:"Upscale finish",detail:"Adds Spandrel Upscaler and a second Preview.",status:d||!n?"ready":"needs-registry",icon:(0,Ca.jsx)(p,{size:15}),button:"Add block",onClick:()=>m("upscale",()=>ya())},{id:"inpaint",title:"Inpaint mask draft",detail:"Source image, mask image, Apply Mask, and image encode.",status:"draft",icon:(0,Ca.jsx)(z,{size:15}),button:"Prepare draft",onClick:()=>u("inpaint",{mode:"inpaint",modelType:"QwenImageEditModularPipeline",referenceImages:t.referenceImages,maskImage:t.maskImage})},{id:"strength",title:"Image-to-image strength sweep",detail:"Use edit mode, then run the sweep grid.",status:"ready",icon:(0,Ca.jsx)(w,{size:15}),button:"Edit sweep",onClick:()=>u("strength",{mode:"edit_image",modelType:"QwenImageEditModularPipeline",strength:t.strength||.65,randomSeed:!1})},{id:"multi-reference",title:"Multi-reference edit",detail:"Qwen Edit Plus with multiple source images.",status:"ready",icon:(0,Ca.jsx)(h,{size:15}),button:"Use refs",onClick:()=>u("multi-reference",{mode:"multi_image_reference_edit",modelType:"QwenImageEditPlusModularPipeline",referenceImages:t.referenceImages})},{id:"outpaint",title:"Outpaint canvas",detail:"Expands the source image, generates a boundary mask, and feeds Qwen inpaint.",status:c||!n?"ready":"needs-registry",icon:(0,Ca.jsx)(P,{size:15}),button:"Use outpaint",onClick:()=>u("outpaint",{mode:"outpaint",modelType:"QwenImageEditModularPipeline",referenceImages:t.referenceImages,aspectRatio:"16:9",width:1344,height:768,outpaintLeft:t.outpaintLeft||256,outpaintRight:t.outpaintRight||256,outpaintTop:t.outpaintTop||0,outpaintBottom:t.outpaintBottom||0,outpaintOverlap:t.outpaintOverlap||24,outpaintFeather:t.outpaintFeather||8})}];return(0,Ca.jsxs)("section",{"data-testid":"studio-controlled-workflows",children:[(0,Ca.jsx)($e,{title:"Controlled workflows"}),(0,Ca.jsx)("div",{className:"grid gap-2",children:f.map(t=>{let a=s===t.id,i=e||null!==s||"blocked"===t.status||!t.onClick;return(0,Ca.jsx)("article",{className:"border border-modiff-border bg-modiff-bg p-2",children:(0,Ca.jsxs)("div",{className:"flex items-start gap-2",children:[(0,Ca.jsx)("div",{className:"grid size-8 shrink-0 place-items-center rounded-modiff-compact bg-modiff-panel text-hf-yellow",children:t.icon}),(0,Ca.jsxs)("div",{className:"min-w-0 flex-1",children:[(0,Ca.jsxs)("div",{className:"flex flex-wrap items-center gap-1.5",children:[(0,Ca.jsx)("h4",{className:"text-xs font-bold text-modiff-text",children:t.title}),(0,Ca.jsx)(ge,{tone:cs(t.status),children:us(t.status)})]}),(0,Ca.jsx)("p",{className:ve("mt-1 text-xs","blocked"===t.status?"text-hf-orange":"text-modiff-subtle-text"),children:t.detail})]}),(0,Ca.jsx)(J,{tone:"blocked"===t.status?"ghost":"secondary",icon:a?(0,Ca.jsx)(Ue,{size:14}):t.icon,disabled:i,onClick:()=>{t.onClick?.()},children:t.button})]})},t.id)})})]})}var ps=512,fs={25:"opacity-25",50:"opacity-50",75:"opacity-75",100:"opacity-100"};function hs({sourceImage:e,maskImage:a,onCancel:s,onSave:i}){let n=(0,ka.useRef)(null),r=(0,ka.useRef)(!1),[o,d]=(0,ka.useState)("brush"),[c,u]=(0,ka.useState)(34),[m,p]=(0,ka.useState)(50),[f,h]=(0,ka.useState)([]),[x,g]=(0,ka.useState)([]),j=()=>n.current?.toDataURL("image/png")??"",v=()=>{let e=j();e&&(h(t=>[e,...t].slice(0,20)),g([]))},y=e=>{let t=n.current,a=t?.getContext("2d");if(!t||!a)return;let s=new Image;s.onload=()=>{a.clearRect(0,0,ps,ps),a.fillStyle="black",a.fillRect(0,0,ps,ps),a.drawImage(s,0,0,ps,ps)},s.src=e};(0,ka.useEffect)(()=>{let e=n.current,t=e?.getContext("2d");!e||!t||(t.fillStyle="black",t.fillRect(0,0,ps,ps),a&&y(a))},[a]);let w=e=>{let t=n.current,a=t?.getContext("2d");if(!t||!a)return;let s=t.getBoundingClientRect(),i=(e.clientX-s.left)/s.width*ps,l=(e.clientY-s.top)/s.height*ps;a.beginPath(),a.arc(i,l,c/2,0,2*Math.PI),a.fillStyle="brush"===o?"white":"black",a.fill()},k=e=>{r.current=!1,e.currentTarget.releasePointerCapture(e.pointerId)};return(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Mask editor"}),(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsxs)("div",{className:"relative aspect-square overflow-hidden border border-modiff-border bg-modiff-bg",children:[e&&(0,Ca.jsx)("img",{src:e,alt:"Source overlay",className:ve("absolute inset-0 h-full w-full object-contain",fs[m])}),(0,Ca.jsx)("canvas",{ref:n,width:ps,height:ps,className:"relative z-10 h-full w-full touch-none mix-blend-screen",onPointerDown:e=>{v(),r.current=!0,e.currentTarget.setPointerCapture(e.pointerId),w(e)},onPointerMove:e=>{r.current&&w(e)},onPointerUp:k,onPointerCancel:k})]}),(0,Ca.jsxs)("div",{className:"flex flex-wrap gap-1",children:[(0,Ca.jsxs)(ge,{active:"brush"===o,onClick:()=>d("brush"),children:[(0,Ca.jsx)(A,{size:13})," Brush"]}),(0,Ca.jsxs)(ge,{active:"erase"===o,onClick:()=>d("erase"),children:[(0,Ca.jsx)(B,{size:13})," Erase"]}),(0,Ca.jsx)(ge,{onClick:()=>{let e=n.current,t=e?.getContext("2d");if(!e||!t)return;v();let a=t.getImageData(0,0,ps,ps);for(let e=0;e{let e=n.current,t=e?.getContext("2d");!e||!t||(v(),t.fillStyle="black",t.fillRect(0,0,ps,ps))},children:[(0,Ca.jsx)(L,{size:13})," Clear"]}),(0,Ca.jsxs)(ge,{onClick:()=>{let e=f[0];if(!e)return;let t=j();g(e=>t?[t,...e].slice(0,20):e),h(e=>e.slice(1)),y(e)},disabled:0===f.length,children:[(0,Ca.jsx)(t,{size:13})," Undo"]}),(0,Ca.jsxs)(ge,{onClick:()=>{let e=x[0];if(!e)return;let t=j();h(e=>t?[t,...e].slice(0,20):e),g(e=>e.slice(1)),y(e)},disabled:0===x.length,children:[(0,Ca.jsx)(b,{size:13})," Redo"]})]}),(0,Ca.jsx)(kt,{label:`Brush size: ${c}`,children:(0,Ca.jsx)(ke,{min:4,max:120,value:c,onChange:u})}),(0,Ca.jsxs)("div",{children:[(0,Ca.jsxs)("p",{className:"mb-1 text-xs text-modiff-subtle-text",children:["Source overlay: ",m,"%"]}),(0,Ca.jsx)("div",{className:"flex flex-wrap gap-1",children:[25,50,75,100].map(e=>(0,Ca.jsxs)(ge,{active:m===e,onClick:()=>p(e),children:[e,"%"]},e))})]}),(0,Ca.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Ca.jsx)(J,{tone:"primary",icon:(0,Ca.jsx)(C,{size:15}),onClick:()=>i(j()),children:"Save mask"}),(0,Ca.jsx)(J,{tone:"ghost",icon:(0,Ca.jsx)(l,{size:15}),onClick:s,children:"Cancel"})]})]})]})}function xs({form:e}){let t=[["Seed",e.randomSeed?"Random on run, so exact examples cannot be promised.":`Locked to ${e.seed} for reruns.`],["Guidance",`${e.guidanceScale} balances prompt obedience against natural detail.`],["Steps",`${e.steps} sampling steps sets quality/runtime tradeoff.`],["Strength",`${e.strength} controls how much source images can change in edit workflows.`],["Size",`${e.width}x${e.height} changes composition and exactness hash.`],["Dtype",`${e.dtype} affects memory use and can affect reproducibility.`],["Model",Se[e.modelType]]];return(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Parameter notes"}),(0,Ca.jsx)("div",{className:"grid gap-1 border border-modiff-border bg-modiff-bg p-2",children:t.map(([e,t])=>(0,Ca.jsxs)("div",{className:"flex items-start gap-2 text-xs",children:[(0,Ca.jsx)(g,{size:13,className:"mt-0.5 shrink-0 text-modiff-subtle-text"}),(0,Ca.jsxs)("p",{className:"min-w-0 text-modiff-subtle-text",children:[(0,Ca.jsxs)("span",{className:"font-semibold text-modiff-text",children:[e,":"]})," ",t]})]},e))})]})}var gs=["Describe the subject first, then composition, style, lighting, camera, and constraints.","Keep exact text short and put it in quotes when the model needs to render letters."],bs=[["Subject",["one clear subject","premium product","same character identity"]],["Composition",["centered composition","negative space","preserve composition"]],["Style",["cinematic realism","editorial design","minimal studio look"]]],js=[["Subject","subject"],["Environment","environment"],["Preserve","preservation"],["Change","change"],["Composition","composition"],["Texture / materials","materials"],["Style","style"],["Lighting","lighting"],["Camera","camera"],["Motion","motion"],["Continuity","continuity"],["Arrangement","arrangement"],["Mix","mix"],["Parameters","parameters"],["Text","textRendering"]];function vs({form:e,template:t,onChange:a}){let s=t?.promptGuide,i=s?js.map(([e,t])=>[e,s[t]]).filter(e=>Array.isArray(e[1])&&e[1].length>0):bs,l=s?.negative??["low quality","blurry","warped text","distorted anatomy"],n=s?.modelHints??gs,r=t=>{let s=e.prompt.trim();a({prompt:s?`${s}, ${t}`:t})},o=t=>{let s=e.negativePrompt.trim();a({negativePrompt:s?`${s}, ${t}`:t})};return(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Prompt composer"}),(0,Ca.jsxs)("div",{className:"grid gap-2",children:[i.map(([e,t])=>(0,Ca.jsxs)("fieldset",{children:[(0,Ca.jsx)("legend",{className:"mb-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:e}),(0,Ca.jsx)("div",{className:"flex flex-wrap gap-1",children:t.map(t=>(0,Ca.jsx)(ge,{"aria-label":`Add ${e.toLowerCase()} detail: ${t}`,onClick:()=>r(t),children:t},`${e}-${t}`))})]},e)),l.length>0&&(0,Ca.jsxs)("fieldset",{children:[(0,Ca.jsx)("legend",{className:"mb-1 text-xs font-semibold uppercase text-modiff-subtle-text",children:"Avoid"}),(0,Ca.jsx)("div",{className:"flex flex-wrap gap-1",children:l.map(e=>(0,Ca.jsx)(ge,{"aria-label":`Add negative prompt detail: ${e}`,onClick:()=>o(e),children:e},`negative-${e}`))})]}),n.length>0&&(0,Ca.jsx)("div",{className:"grid gap-1 border border-modiff-border bg-modiff-bg p-2","aria-label":"Prompt guidance",children:n.map(e=>(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:e},e))}),t&&(0,Ca.jsx)(J,{tone:"ghost",icon:(0,Ca.jsx)(f,{size:15}),onClick:()=>a({prompt:t.prompt,negativePrompt:t.negativePrompt??e.negativePrompt}),children:"Restore template prompt"})]})]})}function ys(e){return e.match(/\S+/g)||[]}function ws(e,t){let a=ys(e),s=ys(t),i=Array.from({length:a.length+1},()=>Array(s.length+1).fill(0));for(let e=a.length-1;e>=0;--e)for(let t=s.length-1;t>=0;--t){let l=a[e]??"",n=s[t]??"",r=i[e];r&&(r[t]=l===n?(i[e+1]?.[t+1]??0)+1:Math.max(i[e+1]?.[t]??0,r[t+1]??0))}let l=[],n=0,r=0;for(;n=(i[n]?.[r+1]??0)?(l.push({text:e,kind:"removed"}),n+=1):(l.push({text:t,kind:"added"}),r+=1)}for(;n[...a.map((e,t)=>({id:`snippet-${t}`,label:`Snippet ${t+1}`,prompt:e})),...t.map((e,t)=>({id:`history-${t}`,label:`History ${t+1}`,prompt:e}))].filter(t=>t.prompt.trim()&&t.prompt!==e),[e,t,a]),r=n.find(e=>e.id===i)||n[0],o=(0,ka.useMemo)(()=>r?ws(r.prompt,e):[],[e,r]),d=o.filter(e=>"added"===e.kind).length,c=o.filter(e=>"removed"===e.kind).length;return 0===n.length?null:(0,Ca.jsxs)("div",{children:[(0,Ca.jsx)($e,{title:"Prompt diff",action:(0,Ca.jsxs)("div",{className:"flex gap-1",children:[(0,Ca.jsx)(ge,{tone:d?"success":"default",children:`+${d}`}),(0,Ca.jsx)(ge,{tone:c?"error":"default",children:`-${c}`})]})}),(0,Ca.jsx)(Ft,{"aria-label":"Prompt version to compare",value:r?.id||"",onValueChange:l,options:n.map(e=>({value:e.id,label:e.label})),className:"mb-2 w-full"}),(0,Ca.jsx)("div",{className:"max-h-40 overflow-y-auto border border-modiff-border bg-modiff-bg p-2",children:o.map((e,t)=>(0,Ca.jsx)("span",{className:ve("mb-1 mr-1 inline-block rounded-modiff-compact px-1 text-xs","same"===e.kind&&"text-modiff-subtle-text","added"===e.kind&&"bg-modiff-green/10 text-modiff-green","removed"===e.kind&&"bg-modiff-red/10 text-modiff-red line-through"),children:e.text},`${e.text}-${t}`))}),(0,Ca.jsxs)("div",{className:"mt-2 flex gap-2",children:[(0,Ca.jsx)(J,{tone:"ghost",icon:(0,Ca.jsx)(E,{size:15}),onClick:()=>r&&function(e){navigator.clipboard.writeText(e),Ae("Prompt diff copied",{variant:"success",autoHideDuration:1800})}(function(e,t,a){let s=a.filter(e=>"added"===e.kind).map(e=>e.text).join(" "),i=a.filter(e=>"removed"===e.kind).map(e=>e.text).join(" ");return["Prompt diff","","Base:",e,"","Current:",t,"","Added:",s||"None","","Removed:",i||"None"].join("\n")}(r.prompt,e,o)),children:"Copy diff"}),(0,Ca.jsx)(J,{tone:"ghost",onClick:()=>r&&s(r.prompt),children:"Use baseline"})]})]})}function Cs(e){return e.split(",").map(e=>e.trim()).filter(Boolean)}function Ns(e,t){let a=new Set(e.map(e=>e.toLowerCase()));return t.forEach(t=>{let s=t.toLowerCase();a.has(s)||(a.add(s),e.push(t))}),e}function Ss(e){return"edit_image"===e.mode?["preserve source identity","match original lighting","localized edit"]:"multi_image_reference_edit"===e.mode?["coherent blend of references","consistent perspective","preserve strongest design details"]:"control_image"===e.mode?["follow the control image structure","clean edges","stable composition"]:"control_edit_image"===e.mode?["preserve source identity","follow the control image structure","stable composition"]:"control_inpaint"===e.mode?["edit only inside the mask","follow the control image structure","preserve unmasked areas"]:"inpaint"===e.mode?["edit only inside the mask","preserve unmasked areas","consistent texture"]:"layer_decomposition"===e.mode?["clean layer boundaries","separated foreground and background","editable alpha"]:["one clear subject","balanced composition","professional finish"]}function Is({form:e,template:t,onApply:s}){let[i,n]=(0,ka.useState)(null),r=(0,ka.useMemo)(()=>i?ws(e.prompt,i.prompt):[],[e.prompt,i]),o=r.filter(e=>"added"===e.kind).length,d=r.filter(e=>"removed"===e.kind).length;return(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Prompt enhancer",action:i?(0,Ca.jsxs)("div",{className:"flex gap-1",children:[(0,Ca.jsx)(ge,{tone:o?"success":"default",children:`+${o}`}),(0,Ca.jsx)(ge,{tone:d?"error":"default",children:`-${d}`})]}):void 0}),(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Ca.jsx)(J,{tone:"ghost",icon:(0,Ca.jsx)(f,{size:15}),onClick:()=>n(function(e,t){let a=Cs(e.prompt||t?.prompt||""),s=t?.promptGuide,i=[...Ss(e),s?.lighting?.[0],s?.camera?.[0],s?.style?.[0]].filter(e=>!!e);return{prompt:Ns(a.length>0?a:["A polished image"],i).join(", "),negativePrompt:Ns(Cs(e.negativePrompt),s?.negative?.slice(0,3)??["low quality","blurry","artifacts"]).join(", ")}}(e,t)),children:"Suggest"}),i&&(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(J,{tone:"primary",icon:(0,Ca.jsx)(a,{size:15}),onClick:()=>{s(i),n(null)},children:"Apply"}),(0,Ca.jsx)(J,{tone:"ghost",icon:(0,Ca.jsx)(E,{size:15}),onClick:()=>function(e){navigator.clipboard.writeText(e),Ae("Enhanced prompt copied",{variant:"success",autoHideDuration:1800})}(`${i.prompt}\n\nNegative:\n${i.negativePrompt}`),children:"Copy"}),(0,Ca.jsx)(J,{tone:"ghost",icon:(0,Ca.jsx)(l,{size:15}),onClick:()=>n(null),children:"Dismiss"})]})]}),i&&(0,Ca.jsxs)("div",{className:"grid gap-2 border border-modiff-border bg-modiff-bg p-2","data-testid":"studio-prompt-enhancer-diff",children:[(0,Ca.jsx)("div",{className:"max-h-40 overflow-y-auto",children:r.map((e,t)=>(0,Ca.jsx)("span",{className:ve("mb-1 mr-1 inline-block rounded-modiff-compact px-1 text-xs","same"===e.kind&&"text-modiff-subtle-text","added"===e.kind&&"bg-modiff-green/10 text-modiff-green","removed"===e.kind&&"bg-modiff-red/10 text-modiff-red line-through"),children:e.text},`${e.text}-${t}`))}),(0,Ca.jsxs)("div",{className:"grid gap-1 border-t border-modiff-border pt-2",children:[(0,Ca.jsx)("p",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:"Negative"}),(0,Ca.jsx)("p",{className:"break-words text-xs text-modiff-subtle-text",children:i.negativePrompt||"None"})]})]})]})]})}function Rs({disabled:e=!1,form:t,template:a,onChange:s,onRunSweep:i}){let l=!!a&&mt(a,t),n=st.includes(t.mode),r="edit_image"===t.mode||"multi_image_reference_edit"===t.mode||"inpaint"===t.mode||"control_edit_image"===t.mode||"control_inpaint"===t.mode||"layer_decomposition"===t.mode?[{label:"Lower strength",detail:`Strength ${Math.max(0,Number((t.strength-.1).toFixed(2)))}`,values:{strength:Math.max(0,Number((t.strength-.1).toFixed(2)))}},{label:"Higher strength",detail:`Strength ${Math.min(1,Number((t.strength+.1).toFixed(2)))}`,values:{strength:Math.min(1,Number((t.strength+.1).toFixed(2)))}}]:[],o=n?[{label:"Lower conditioning",detail:`Conditioning ${Math.max(0,Number((t.conditioningScale-.15).toFixed(2)))}`,values:{conditioningScale:Math.max(0,Number((t.conditioningScale-.15).toFixed(2)))}},{label:"Higher conditioning",detail:`Conditioning ${Math.min(2,Number((t.conditioningScale+.15).toFixed(2)))}`,values:{conditioningScale:Math.min(2,Number((t.conditioningScale+.15).toFixed(2)))}},{label:"Shorter clip",detail:`${Math.max(17,t.numFrames-32)} frames`,values:{numFrames:Math.max(17,t.numFrames-32)}},{label:"Quality clip",detail:"50 steps, 81 frames",values:{steps:50,numFrames:81,randomSeed:!1}}]:[],d=[{label:"Next seed",detail:`Seed ${t.seed+1}`,values:{seed:t.seed+1,randomSeed:!1}},{label:"Lower guidance",detail:`Guidance ${Math.max(0,Number((t.guidanceScale-.5).toFixed(1)))}`,values:{guidanceScale:Math.max(0,Number((t.guidanceScale-.5).toFixed(1)))}},{label:"Higher guidance",detail:`Guidance ${Math.min(10,Number((t.guidanceScale+.5).toFixed(1)))}`,values:{guidanceScale:Math.min(10,Number((t.guidanceScale+.5).toFixed(1)))}},{label:"Quality pass",detail:`${Math.min(80,t.steps+12)} steps`,values:{steps:Math.min(80,t.steps+12)}},...r,...o];return(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Variation planner"}),(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:l?"Current settings match the locked template. Any sweep below will become Modified.":"Current settings already differ from the locked template or no active template is selected."}),(0,Ca.jsx)("div",{className:"grid grid-cols-2 gap-2",children:d.map((e,t)=>(0,Ca.jsx)(J,{tone:"ghost",align:"left",icon:0===t?(0,Ca.jsx)(k,{size:15}):(0,Ca.jsx)(w,{size:15}),onClick:()=>s(e.values),children:(0,Ca.jsxs)("span",{children:[(0,Ca.jsx)("span",{className:"block text-xs font-bold text-modiff-text",children:e.label}),(0,Ca.jsxs)("span",{className:"block text-xs text-hf-orange",children:[e.detail," | Modified"]})]})},e.label))}),(0,Ca.jsx)(J,{fullWidth:!0,tone:"secondary",icon:(0,Ca.jsx)(k,{size:15}),disabled:e,onClick:()=>i(d),children:"Run sweep"})]})]})}function $s({issues:e}){let{installHfModel:t,refreshModelIndexes:a,hfDownloadProgress:s}=Ze(nt(e=>({installHfModel:e.installHfModel,refreshModelIndexes:e.refreshModelIndexes,hfDownloadProgress:e.hfDownloadProgress}))),i=we(e=>e.sid);return e.filter((t,a)=>"install_model"===t.action&&t.repoId&&e.findIndex(e=>"install_model"===e.action&&e.repoId===t.repoId&&JSON.stringify(e.installOptions??{})===JSON.stringify(t.installOptions??{}))===a).map(e=>{let l=e.repoId,n=_t(s[l]);return(0,Ca.jsxs)(J,{fullWidth:!0,tone:"secondary",disabled:n||!i,"data-testid":`studio-install-graph-model-${l}`,title:`${l}: ${e.message}`,onClick:()=>{(async()=>{try{await t(l,i,e.installOptions),await a(!0)}catch(e){Ae(oe(e,`Could not install ${l}.`),{variant:"error"})}})()},children:[n?"Installing":e.installOptions?.repair?"Repair":"Install"," ",l.split("/").pop()]},e.id)})}function Ms({form:e,hfCache:t,localModels:a,modelCacheDiagnostics:s,autoResourcePlan:i,backendCapabilities:l=[],backendCapabilitiesAuthoritative:n=!1}){let r=(0,ka.useMemo)(()=>{let t=H(e),a=l.find(t=>t.modelType===e.modelType);return He(a?{...t,...a}:t,e.mode)},[l,e]),o=(0,ka.useMemo)(()=>n?l.filter(t=>Me(t).includes(e.mode)).map(e=>e.modelType):Ce(e.mode,{currentModelType:e.modelType}),[l,n,e.mode,e.modelType]),d=(0,ka.useMemo)(()=>{if(!n)return r.modes;let t=l.find(t=>t.modelType===e.modelType);return t?Me(t):[]},[l,n,r.modes,e.modelType]),c=(0,ka.useMemo)(()=>qe(r,t,a,s),[r,t,a,s]),u=(0,ka.useMemo)(()=>Xe({form:e,autoResourcePlan:i,hfCache:t,localModels:a,modelCacheDiagnostics:s,includePipeline:!1}),[i,e,t,a,s]),m=r.modeRequirements?.[e.mode]?.requiredImages??[],p=m.includes("referenceImages"),f=m.includes("maskImage"),h=m.includes("controlImage"),x=m.includes("ipAdapterImage"),g=p||"control_image"===e.mode||"inpaint"===e.mode||"layer_decomposition"===e.mode,b=r.supportsMask,j=r.inpaintContract,v=u.find(e=>!e.status.runnable),y=(0,ka.useMemo)(()=>"auto"===e.resourceMode?Ie(i,e):null,[i,e]),w=1===r.revisionCandidates?.length?r.revisionCandidates[0]:void 0;return{capability:r,compatibleModels:o,compatibleModes:d,showImageTray:g,requiresMaskImage:f,requiresControlImage:h,requiresIPAdapterImage:x,supportsMask:b,inpaintContract:j,missingInstallTarget:y??("auto"===e.resourceMode||!1===r.artifactInstallRequired||c.runnable?v?{repo:v.repo,label:v.label}:null:{repo:r.defaultRepo,label:r.label,revision:w,files:r.downloadFiles})}}var Ts=[{label:"1:1",width:1024,height:1024},{label:"4:3",width:1152,height:864},{label:"3:4",width:864,height:1152},{label:"16:9",width:1344,height:768},{label:"9:16",width:768,height:1344}];function As(e,t){let a=Number(e);return Number.isFinite(a)?a:t}function Ps(){let[e,t]=(0,ka.useState)(!1),a=ja(),[s,i]=(0,ka.useState)(!1),l=(0,ka.useRef)(null),[n,r]=(0,ka.useState)(0),o=Je(e=>e.setRightPanelOpen),d=Je(e=>e.setRightPanelTab),c=Je(e=>e.setLightboxOpener),u=Je(e=>e.setTemplateBrowserOpen),p=Je(e=>e.setGalleryLibraryOpen),h=Pt(e=>e.showIssues),{sid:x,isConnected:b}=we(nt(e=>({sid:e.sid,isConnected:e.isConnected}))),{hfCache:j,localModels:v,modelCacheDiagnostics:y,runtimeStatus:w,nodesRegistry:k,studioModelCapabilities:N,studioModelCapabilitiesAuthoritative:S,studioExecutionSpecInvalid:I,installHfModel:R}=Ze(nt(e=>({hfCache:e.hfCache,localModels:e.localModels,modelCacheDiagnostics:e.modelCacheDiagnostics,runtimeStatus:e.runtimeStatus,nodesRegistry:e.nodesRegistry,studioModelCapabilities:e.studioModelCapabilities,studioModelCapabilitiesAuthoritative:e.studioModelCapabilitiesAuthoritative,studioExecutionSpecInvalid:e.studioExecutionSpecInvalid,installHfModel:e.installHfModel}))),{form:$,autoResourcePlan:M,promptHistory:T,savedSnippets:A,negativePromptPresets:P,activeTemplateId:_,activeWorkflowTabId:z,outputs:q,lastError:B,recentChange:D,pinnedGraphInputIds:G,workflowCanvasHydrated:O,applyPreset:F,addPromptHistory:H,togglePinnedGraphInput:W,saveCurrentPromptAsSnippet:Q,removeSnippet:X,applySnippet:Y,applyNegativePreset:te,detachManagedGraph:ae}=$t(nt(e=>({form:e.form,promptHistory:e.promptHistory,savedSnippets:e.savedSnippets,negativePromptPresets:e.negativePromptPresets,autoResourcePlan:e.autoResourcePlan,activeTemplateId:e.activeTemplateId,activeWorkflowTabId:e.activeWorkflowTabId,outputs:e.outputs,lastError:e.lastError,recentChange:e.recentChange,pinnedGraphInputIds:e.pinnedGraphInputIds,workflowCanvasHydrated:e.workflowCanvasHydrated,applyPreset:e.applyPreset,addPromptHistory:e.addPromptHistory,togglePinnedGraphInput:e.togglePinnedGraphInput,saveCurrentPromptAsSnippet:e.saveCurrentPromptAsSnippet,removeSnippet:e.removeSnippet,applySnippet:e.applySnippet,applyNegativePreset:e.applyNegativePreset,detachManagedGraph:e.detachManagedGraph}))),se=(0,ka.useMemo)(()=>Gt.find(e=>e.id===_),[_]),ie=(0,ka.useMemo)(()=>V(q,z),[z,q]),le=(0,ka.useMemo)(()=>wt.filter(e=>{let t=!!e.compatibleModes?.some(e=>st.includes(e)),a=!!e.compatibleModes?.some(e=>Pe.includes(e));if(st.includes($.mode)&&!t||Pe.includes($.mode)&&!a)return!1;let s=!e.compatibleModes||e.compatibleModes.includes($.mode),i=!e.compatibleModelTypes||e.compatibleModelTypes.includes($.modelType);return s&&i}),[$.mode,$.modelType]),re=st.includes($.mode),de=Pe.includes($.mode),ce="unconditional_image"===$.mode,pe="depth_estimation"===$.mode,xe=be.includes($.mode),je=he.includes($.mode),Ce=!ce&&!pe&&!xe&&!1!==We[$.modelType].supportsPrompt,Ne=Qt(We[$.modelType]),Ie=Kt(We[$.modelType],$),Me=vt(We[$.modelType]),_e=Rt(N,I,$),qe=_e?.available_expert_quantization_modes??_e?.expert_quantization_modes??[],Be=`${Ie}. ${Ne} defaults to ${We[$.modelType].recommendedSteps} steps, ${We[$.modelType].guidanceLabel.toLowerCase()} ${We[$.modelType].recommendedGuidance}, ${We[$.modelType].defaultDtype}. ${Me}`,Ge=$t(e=>e.graphBinding),Ee=$t(e=>e.graphFinalization),Le=Ge?me($):null,He=Ee??(Le?{status:"warning",message:Le}:null),Ve=$t(e=>e.canvasTransition),Ue="template_graph_building"===Ve?.type&&Ve.workflowTabId===z;rt(nt(e=>({nodes:e.nodes.map(e=>{let t=Object.entries(e.data.params).filter(([e])=>/repo|model|checkpoint|ckpt|safetensors|lora|vae|controlnet|adapter/i.test(e)).map(([e,t])=>`${e}:${JSON.stringify(t.value??t.default??"")}`).join(",");return`${e.id}:${e.data.module}:${e.data.action}:${e.data.uiState?.disabled?"disabled":"enabled"}:${t}`}).join("|"),edges:e.edges.map(e=>`${e.id}:${e.source}:${e.sourceHandle??""}->${e.target}:${e.targetHandle??""}`).join("|")})));let Qe=Ot(),Xe=O&&!Ue&&Ge?ye(Ge):null,Ye=Xe?`${Xe.kind}:${Xe.details??Xe.message}`:"",tt=O&&!Ue&&(!Ge&&Qe.nodeCount>0||!!Xe),at=Qe.modelRefs.length>0?`${new Set(Qe.modelRefs.map(e=>`${e.kind}:${e.value}`)).size} model refs`:"Graph-defined",lt=ea({sid:x,isConnected:b,includeStudio:!tt}),mt=(0,ka.useMemo)(()=>Z(lt.issues),[lt.issues]),pt=lt.blockingIssues.length>0?`${lt.blockingIssues.length} issue${1===lt.blockingIssues.length?"":"s"}`:lt.warningIssues.length>0?`${lt.warningIssues.length} warning${1===lt.warningIssues.length?"":"s"}`:void 0,ht=rt(nt(e=>e.nodes)),gt=O&&!Ue&&!_&&0===ht.length,jt=(0,ka.useMemo)(()=>ht.filter(e=>e.selected),[ht]),St=(0,ka.useMemo)(()=>ga(ht,G),[ht,!0,G]),Mt=G.filter(e=>St.some(t=>t.id===e)),At=(0,ka.useMemo)(()=>{let e=new Set(Mt);return St.filter(t=>e.has(t.id))},[Mt,St]),{capability:_t,compatibleModels:zt,compatibleModes:Bt,showImageTray:Dt,requiresMaskImage:Et,requiresControlImage:Lt,requiresIPAdapterImage:Ht,supportsMask:Wt,inpaintContract:Vt,missingInstallTarget:Ut}=Ms({form:$,hfCache:j,localModels:v,modelCacheDiagnostics:y,autoResourcePlan:M,backendCapabilities:N,backendCapabilitiesAuthoritative:S}),Jt=_t.modeRequirements?.[$.mode]?.requiredVideos??[],Zt=Jt.includes("sourceVideo"),ta=Jt.includes("referenceVideos"),sa=Jt.includes("controlVideo"),ia=lt.blockingIssues[0]?.message??"",la=!lt.canRun,ra=!tt&&!gt,{updateAndSync:oa,handleCreateGraph:da,handleModeChange:ca,handleModelTypeChange:ua,handleRun:ma,handleInstallMissingModel:xa,isInstallingMissingModel:ya,openSetup:wa}=yt({sid:x,isConnected:b,missingInstallTarget:Ut,installHfModel:R,setIsWorking:t}),Na=(e,t=$)=>{let s=xt(t);a.request("run",s,e)};(0,ka.useEffect)(()=>{!O||Ue||Nt({sid:x,isConnected:b,includeStudio:!0,showDialog:!1})},[x,b,$,Ge,Ee,j,v,y,w,k,Ue,O]),(0,ka.useEffect)(()=>{!Ge||!Xe||"pending"===Ee?.status||"template_graph_building"===Ve?.type||ae()},[Ve?.type,ae,Ge,Xe,Ye,Ee?.status]),(0,ka.useEffect)(()=>{if(!w)return;let e=Ct(w);e===$.device||qt(w,$.device)||$.device!==Oe.device&&!$.device.toLowerCase().startsWith("cuda")||oa({device:e})},[$.device,w,oa]);let Sa=(0,ka.useCallback)(e=>{let t=$t.getState().form,a="low_vram"===e.id?{...e,values:{...e.values,...ft(t)}}:e;F(a);let s=$t.getState().form,i=ha(t,s,Object.keys(a.values)),l=t.quantizationMode!==s.quantizationMode||t.resourceMode!==s.resourceMode;queueMicrotask(()=>{l?ze(s):Re(s)}),fa(`${e.label} applied`,i,$t.getState().graphBinding?.managedNodeIds??[]),i.length>0&&Ae(`${e.label} updated ${i.map(e=>e.label).join(", ")}`,{variant:"success",autoHideDuration:2600})},[F]),Ia=(0,ka.useCallback)(()=>{p(!0)},[p]),Ra=(0,ka.useCallback)(()=>{u(!0)},[u]),$a=(0,ka.useCallback)(async()=>{try{let e=await ee();Ae("string"==typeof e.message&&e.message.trim()?e.message:"Interrupt requested",{variant:"success",autoHideDuration:3e3})}catch(e){Ae(oe(e,"Could not stop the current execution."),{variant:"error",autoHideDuration:5e3})}},[]),Ma=(0,ka.useCallback)(()=>{if(!x)return void Ae("Connect to the MoDiff server before exporting the API graph.",{variant:"error",autoHideDuration:3e3});let e=Xt(rt.getState().exportGraph(x)),t=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(t),a.download="modiff-api-graph.json",a.click(),URL.revokeObjectURL(a.href)},[x]),Ta=(0,ka.useCallback)(()=>{let e=$t.getState(),t=Tt(e.outputs,e.activeWorkflowTabId,{includeUnscopedFallback:!1});t&&($t.getState().restoreWorkflowFromOutput(t),o(!0),d("studio"),Ae("Latest Gallery workflow restored",{variant:"success",autoHideDuration:2e3}))},[o,d]),Aa=(0,ka.useCallback)(async()=>{let e=$t.getState(),a=Tt(e.outputs,e.activeWorkflowTabId,{includeUnscopedFallback:!1});if(a&&x&&b){t(!0);try{$t.getState().restoreWorkflowFromOutput(a);let e=ne();if(!await K(e))return;await It({sid:x,studioContext:{forceDeterministic:!a.formSnapshot.randomSeed},workflowContext:e})}catch(e){if(De(e))return;Ae(String(e),{variant:"error",autoHideDuration:6e3})}finally{t(!1)}}else Ae("Connect to the MoDiff server and keep a Gallery output available before rerunning.",{variant:"error",autoHideDuration:4e3})},[b,x]),Pa=(0,ka.useCallback)(()=>{let e=$t.getState(),[t,a]=V(e.outputs,e.activeWorkflowTabId);!t||!a||c(Yt([a.url,t.url],1))},[c]),_a=(0,ka.useCallback)(()=>{h(lt.issues)},[lt.issues,h]),za=(0,ka.useCallback)(async e=>{if(!x||!b)return void Ae("Connect to the MoDiff server before running a sweep.",{variant:"error",autoHideDuration:4e3});if(ia)return void Ae(ia,{variant:"error",autoHideDuration:5e3});let a={...$t.getState().form,referenceImages:[...$t.getState().form.referenceImages]},s=ne(),i=`sweep-${Date.now()}`;t(!0),$t.getState().addPromptHistory(a.prompt);try{for(let t of e){et(s);let e={...t.values,randomSeed:!1};$t.getState().updateForm(e),aa(s);let a=$t.getState().form;if(Re(a),!await K(s))break;await Fe($t.getState().form,s),et(s);let{response:l}=await It({sid:x,workflowContext:s,studioContext:{forceDeterministic:!0,variation:{variationGroupId:i,variationLabel:t.label}}});if(et(s),l.error){$t.getState().setLastError(l.message||"MoDiff could not queue this sweep variation.");break}}Ae("Variation sweep queued",{variant:"success",autoHideDuration:2200})}catch(e){if(De(e))return;let t=String(e);$t.getState().setLastError(t),Ae(t,{variant:"error",autoHideDuration:7e3})}finally{Te(s)&&($t.getState().updateForm(a),aa(s),Re(a),await ze(a,s).catch(e=>{De(e)||console.error("Could not restore the sweep graph",e)})),t(!1)}},[b,ia,x]);return(0,ka.useLayoutEffect)(()=>{let e=l.current;if(!e)return;let t=()=>{let t=Math.ceil(e.getBoundingClientRect().height);r(e=>e===t?e:t)};if(t(),typeof ResizeObserver>"u")return;let a=0,s=new ResizeObserver(()=>{window.cancelAnimationFrame(a),a=window.requestAnimationFrame(t)});return s.observe(e),()=>{window.cancelAnimationFrame(a),s.disconnect()}},[]),(0,Ca.jsxs)("div",{className:"flex flex-col gap-3 p-3","data-testid":"studio-panel",children:[(0,Ca.jsxs)("div",{ref:l,className:"sticky top-0 z-20 -mx-3 -mt-3 grid gap-2 border-b border-modiff-border bg-modiff-bg p-3 shadow-modiff-node","data-testid":"studio-sticky-header",children:[(0,Ca.jsx)(ds,{isWorking:e,runBlocked:la,expertMode:!0,hasManagedForm:ra,hasGalleryItems:ie.length>0,hasComparePair:ie.length>1,onRun:()=>{Na(ma)},onInterrupt:()=>{$a()},onUpdateGraph:()=>{da()},onExport:Ma,onSavePrompt:Q,onOpenGallery:Ia,onOpenTemplateGallery:Ra,onOpenSetup:wa,onRestoreLatest:Ta,onRerunLatest:()=>{Na(Aa,ie[0]?.formSnapshot)},onCompareLatest:Pa}),He&&"complete"!==He.status&&"idle"!==He.status&&(0,Ca.jsxs)(Ke,{severity:"error"===He.status?"error":"warning"===He.status?"warning":"info",testId:"studio-graph-finalization",children:[(0,Ca.jsx)("p",{className:"text-xs font-semibold text-modiff-text",children:"pending"===He.status?"Finalizing graph...":"warning"===He.status?"Graph still finalizing":"Graph finalization failed"}),(0,Ca.jsx)("p",{className:"mt-1 text-xs text-modiff-subtle-text",children:He.message||"Preparing dynamic fields and managed links in the background."})]}),B&&(0,Ca.jsx)(Ke,{severity:"error",children:(0,Ca.jsxs)("div",{className:"flex items-start gap-2",children:[(0,Ca.jsx)(bt,{className:"min-w-0 flex-1",label:"Last error",buttonClassName:"min-h-0 justify-start p-0 text-xs text-modiff-invalid hover:bg-transparent",panelClassName:"mt-1",children:(0,Ca.jsx)("p",{className:"mt-1 max-h-28 overflow-auto break-words text-xs text-modiff-red",children:B})}),(0,Ca.jsx)(dt,{title:"Copy error",onClick:()=>function(e){navigator.clipboard.writeText(e),Ae("Error copied",{variant:"success",autoHideDuration:1800})}(B),children:(0,Ca.jsx)(E,{size:15})})]})})]}),(0,Ca.jsx)(ct,{id:"task",title:"Task",defaultOpen:!0,children:gt?(0,Ca.jsxs)("div",{className:"grid gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"studio-empty-workflow",children:[(0,Ca.jsxs)("div",{children:[(0,Ca.jsx)("p",{className:"text-sm font-semibold text-modiff-text",children:"No task or model selected"}),(0,Ca.jsx)("p",{className:"mt-1 text-xs leading-5 text-modiff-subtle-text",children:"Choose a task in the canvas, browse templates, or add a node from the Nodes panel to build manually."})]}),(0,Ca.jsx)(J,{fullWidth:!0,tone:"secondary",icon:(0,Ca.jsx)(m,{size:15}),"data-testid":"studio-open-template-browser",onClick:Ra,children:"Browse templates"})]}):(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsx)("div",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3","data-testid":"studio-task-model-summary",children:(0,Ca.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,Ca.jsxs)("div",{className:"min-w-0",children:[(0,Ca.jsx)("p",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:tt?"Current graph":"Current task"}),(0,Ca.jsx)("h2",{className:"truncate text-base font-bold text-modiff-text",children:tt?"Custom graph":na[$.mode]})]}),(0,Ca.jsxs)("div",{className:"min-w-0 text-right",children:[(0,Ca.jsx)("p",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:tt?"Graph models":"Selected model"}),(0,Ca.jsxs)("div",{className:"flex items-center justify-end gap-1",children:[(0,Ca.jsx)("h3",{className:"truncate text-base font-bold text-hf-yellow",children:tt?at:Ne}),!tt&&(0,Ca.jsx)(dt,{title:Be,children:(0,Ca.jsx)(g,{size:13})})]})]})]})}),ra&&(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(Ft,{"aria-label":"Task",value:$.mode,"data-testid":"studio-task-select",onValueChange:e=>{ca(e)},options:Bt.map(e=>({value:e,label:na[e]}))}),(0,Ca.jsx)(Ft,{"aria-label":"Model",value:$.modelType,"data-testid":"studio-model-select",onValueChange:e=>{ua(e)},options:zt.map(e=>({value:e,label:Se[e]}))})]}),tt?(0,Ca.jsx)($s,{issues:lt.issues}):null,ra&&Ut?(0,Ca.jsx)(J,{fullWidth:!0,tone:"secondary","data-testid":"studio-install-missing-model",disabled:e||ya,onClick:()=>{xa()},title:`${Ut.label} | ${Ut.repo}`,children:ya?"Installing model...":`${Ut.actionLabel??(Ut.repair?"Repair":"Install")} ${Ut.label}`}):null,Ue?(0,Ca.jsx)(ue,{tone:"info",title:"Preparing graph",testId:"studio-run-readiness"}):(0,Ca.jsx)(ue,{tone:mt.tone,title:pt?`${mt.title} - ${pt}`:mt.title,meta:lt.primaryIssue?.message,testId:"studio-run-readiness",onClick:lt.issues.length>0?_a:void 0}),ht.length>0?(0,Ca.jsx)(ba,{candidates:St,nodes:ht,pinnedInputs:At,pinnedIds:Mt,selectedNodes:jt,workflowId:z,onTogglePin:W},z):null,0===ht.length?(0,Ca.jsx)(J,{fullWidth:!0,tone:"secondary",icon:(0,Ca.jsx)(m,{size:15}),"data-testid":"studio-open-template-browser",onClick:Ra,children:"Templates"}):null]})}),ra&&(0,Ca.jsxs)(Ca.Fragment,{children:[Ce&&(0,Ca.jsxs)("div",{className:"relative grid gap-3",children:[(0,Ca.jsxs)(ct,{id:"prompt",title:"Prompt",defaultOpen:!0,testId:"studio-sticky-prompt",className:"sticky z-10 rounded-modiff-panel border border-modiff-border bg-modiff-bg px-2 pb-2 shadow-modiff-node before:absolute before:-inset-x-3 before:-top-3 before:h-3 before:bg-modiff-bg before:content-['']",stickyTop:n+12,children:[(0,Ca.jsx)("div",{"data-testid":"studio-prompt-input",children:(0,Ca.jsx)(fe,{label:"Prompt",value:$.prompt,multiline:!0,onChange:e=>oa({prompt:e})})}),!1!==_t.supportsNegativePrompt&&(0,Ca.jsx)("div",{"data-testid":"studio-negative-prompt-input",children:(0,Ca.jsx)(fe,{label:"Negative prompt",value:$.negativePrompt,multiline:!0,onChange:e=>oa({negativePrompt:e})})}),(0,Ca.jsxs)("div",{className:"flex gap-2",children:[(0,Ca.jsx)(J,{tone:"ghost",icon:(0,Ca.jsx)(C,{size:15}),onClick:Q,children:"Snippet"}),(0,Ca.jsx)(J,{tone:"ghost",icon:(0,Ca.jsx)(f,{size:15}),onClick:()=>H($.prompt),children:"History"})]})]}),(0,Ca.jsxs)(ct,{id:"prompt-tools",title:"Prompt tools",children:[(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Presets"}),(0,Ca.jsx)("div",{className:"flex flex-wrap gap-1.5",children:le.map(e=>(0,Ca.jsx)(ge,{title:e.description,onClick:()=>Sa(e),children:e.label},e.id))}),D&&(0,Ca.jsxs)("div",{className:"mt-2 rounded-modiff-compact border border-hf-yellow/60 bg-modiff-surface p-2 text-xs text-modiff-text","data-testid":"studio-recent-change",children:[(0,Ca.jsx)("p",{className:"font-semibold text-hf-yellow",children:D.label}),(0,Ca.jsx)("div",{className:"mt-1 flex flex-wrap gap-1",children:D.fields.slice(0,6).map(e=>(0,Ca.jsxs)("span",{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg px-1.5 py-0.5 text-modiff-subtle-text",children:[e.label,": ",pa(e.before)," -> ",pa(e.after)]},String(e.key)))})]})]}),(0,Ca.jsx)(it,{}),(0,Ca.jsx)(vs,{form:$,template:se,onChange:oa}),(0,Ca.jsx)(it,{}),(0,Ca.jsx)(Is,{form:$,template:se,onApply:oa}),(T.length>0||A.length>0||P.length>0)&&(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(it,{}),(0,Ca.jsxs)("div",{className:"grid gap-3",children:[A.length>0&&(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Saved snippets"}),(0,Ca.jsx)("div",{className:"grid gap-1",children:A.slice(0,4).map(e=>(0,Ca.jsxs)("div",{className:"flex gap-1",children:[(0,Ca.jsx)(J,{fullWidth:!0,tone:"ghost",align:"left",onClick:()=>{Y(e),queueMicrotask(()=>Re())},children:e}),(0,Ca.jsx)(dt,{title:"Remove snippet",onClick:()=>X(e),children:(0,Ca.jsx)(L,{size:15})})]},e))})]}),T.length>0&&(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Prompt history"}),(0,Ca.jsx)("div",{className:"grid gap-1",children:T.slice(0,4).map(e=>(0,Ca.jsx)(J,{tone:"ghost",align:"left",onClick:()=>oa({prompt:e}),children:e},e))})]}),!1!==_t.supportsNegativePrompt&&(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Negative presets"}),(0,Ca.jsx)("div",{className:"flex flex-wrap gap-1",children:P.map(e=>(0,Ca.jsx)(ge,{onClick:()=>{te(e),queueMicrotask(()=>Re())},children:e},e))})]})]})]}),(0,Ca.jsx)(it,{}),(0,Ca.jsx)(ks,{currentPrompt:$.prompt,promptHistory:T,savedSnippets:A,onApplyPrompt:e=>oa({prompt:e})})]})]}),(0,Ca.jsxs)(ct,{id:"advanced-generation",title:"Advanced generation",children:[(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:re?"Video frame":je?"Rendered orbit frame":ce?"Native sample size":pe?"Prediction map":xe?"Speech recognition":"Image size"}),ce?(0,Ca.jsxs)("p",{className:"text-xs text-modiff-subtle-text","data-testid":"studio-unconditional-native-size",children:["This checkpoint produces fixed ",$.width,"×",$.height," samples."]}):pe?(0,Ca.jsxs)("div",{className:"grid gap-2","data-testid":"studio-perception-controls",children:[(0,Ca.jsx)(fe,{label:"Processing resolution",value:$.processingResolution,onChange:e=>oa({processingResolution:As(e,$.processingResolution)})}),(0,Ca.jsx)(ot,{checked:$.matchInputResolution,onChange:e=>oa({matchInputResolution:e}),label:"Match output to source resolution"}),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Output is a normalized relative-depth map: near is 0, far is 1. Studio also previews it as grayscale."})]}):xe?(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text","data-testid":"studio-speech-summary",children:"Transcribe local audio into a normalized transcript with bounded chunking and timestamps."}):_t.layerResolutions?.length?(0,Ca.jsx)(Ft,{"aria-label":"Layer resolution","data-testid":"studio-layer-resolution-select",value:String($.width),onValueChange:e=>{let t=As(e,$.width);oa({width:t,height:t,aspectRatio:"1:1"})},options:_t.layerResolutions.map(e=>({value:String(e),label:`${e} × ${e}`}))}):(0,Ca.jsxs)("div",{className:"flex gap-2",children:[(0,Ca.jsx)(Ft,{"aria-label":"Aspect ratio",value:$.aspectRatio,onValueChange:e=>(e=>{let t=Ts.find(t=>t.label===e);oa(t?{aspectRatio:e,width:t.width,height:t.height}:{aspectRatio:e})})(e),options:[...Ts.map(e=>({value:e.label,label:e.label})),{value:"custom",label:"Custom"}],className:"flex-1"}),(0,Ca.jsx)(ut,{"aria-label":"Width",value:$.width,"data-testid":"studio-width-input",onChange:e=>oa({width:As(e.target.value,$.width),aspectRatio:"custom"}),className:"w-[86px]"}),(0,Ca.jsx)(ut,{"aria-label":"Height",value:$.height,"data-testid":"studio-height-input",onChange:e=>oa({height:As(e.target.value,$.height),aspectRatio:"custom"}),className:"w-[86px]"})]})]}),"outpaint"===$.mode&&(0,Ca.jsxs)("section",{"data-testid":"studio-outpaint-controls",children:[(0,Ca.jsx)($e,{title:"Outpaint canvas"}),(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Ca.jsx)(fe,{label:"Left margin",value:$.outpaintLeft,onChange:e=>oa({outpaintLeft:As(e,$.outpaintLeft)})}),(0,Ca.jsx)(fe,{label:"Right margin",value:$.outpaintRight,onChange:e=>oa({outpaintRight:As(e,$.outpaintRight)})}),(0,Ca.jsx)(fe,{label:"Top margin",value:$.outpaintTop,onChange:e=>oa({outpaintTop:As(e,$.outpaintTop)})}),(0,Ca.jsx)(fe,{label:"Bottom margin",value:$.outpaintBottom,onChange:e=>oa({outpaintBottom:As(e,$.outpaintBottom)})})]}),(0,Ca.jsx)(kt,{label:`Seam overlap: ${$.outpaintOverlap}`,children:(0,Ca.jsx)(ke,{min:0,max:128,step:4,value:$.outpaintOverlap,onChange:e=>oa({outpaintOverlap:e})})}),(0,Ca.jsx)(kt,{label:`Mask feather: ${$.outpaintFeather}`,children:(0,Ca.jsx)(ke,{min:0,max:64,step:1,value:$.outpaintFeather,onChange:e=>oa({outpaintFeather:e})})}),(0,Ca.jsx)(fe,{label:"Fill color",value:$.outpaintFillColor,onChange:e=>oa({outpaintFillColor:e})})]})]}),!xe&&(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Generation"}),(0,Ca.jsxs)("div",{className:"grid gap-2",children:[(0,Ca.jsx)("div",{"data-testid":"studio-seed-input",children:(0,Ca.jsx)(fe,{label:"Seed",value:$.seed,onChange:e=>oa({seed:As(e,$.seed)}),disabled:$.randomSeed})}),(0,Ca.jsx)(ot,{checked:$.randomSeed,onChange:e=>oa({randomSeed:e}),label:"Randomize seed on export"}),(0,Ca.jsx)(kt,{label:`Steps: ${$.steps}`,children:(0,Ca.jsx)(ke,{min:1,max:ce?1e3:80,value:$.steps,onChange:e=>oa({steps:e})})}),ce&&(0,Ca.jsx)(fe,{label:"Batch size",value:$.batchSize,onChange:e=>oa({batchSize:As(e,$.batchSize)})}),"DDIMPipeline"===$.modelType&&(0,Ca.jsx)(kt,{label:`Eta: ${$.eta}`,children:(0,Ca.jsx)(ke,{min:0,max:1,step:.01,value:$.eta,onChange:e=>oa({eta:e})})}),"ConsistencyModelPipeline"===$.modelType&&(0,Ca.jsx)(fe,{label:"Class label (-1 is unconditional)",value:$.classLabel,onChange:e=>oa({classLabel:As(e,$.classLabel)})}),Ce&&(0,Ca.jsx)(kt,{label:`${_t.guidanceLabel}: ${$.guidanceScale}`,children:(0,Ca.jsx)(ke,{min:0,max:10,step:.1,value:$.guidanceScale,onChange:e=>oa({guidanceScale:e})})}),void 0!==_t.recommendedPagScale&&(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(kt,{label:`PAG scale: ${$.pagScale}`,children:(0,Ca.jsx)(ke,{min:0,max:10,step:.1,value:$.pagScale,onChange:e=>oa({pagScale:e})})}),(0,Ca.jsx)(kt,{label:`PAG adaptive scale: ${$.pagAdaptiveScale}`,children:(0,Ca.jsx)(ke,{min:0,max:10,step:.1,value:$.pagAdaptiveScale,onChange:e=>oa({pagAdaptiveScale:e})})})]}),re&&(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Ca.jsx)(fe,{label:"Frames",value:$.numFrames,onChange:e=>oa({numFrames:As(e,$.numFrames)})}),(0,Ca.jsx)(fe,{label:"FPS",value:$.fps,onChange:e=>oa({fps:As(e,$.fps)})})]}),(0,Ca.jsx)(kt,{label:`Conditioning: ${$.conditioningScale}`,children:(0,Ca.jsx)(ke,{min:0,max:2,step:.05,value:$.conditioningScale,onChange:e=>oa({conditioningScale:e})})}),(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(kt,{label:`Guidance 2: ${$.guidanceScale2}`,children:(0,Ca.jsx)(ke,{min:0,max:20,step:.1,value:$.guidanceScale2,onChange:e=>oa({guidanceScale2:e})})}),(0,Ca.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Ca.jsx)(Ft,{"aria-label":"Output type",value:$.outputType,onValueChange:e=>oa({outputType:e}),options:[{value:"pil",label:"Output: PIL"},{value:"np",label:"Output: NumPy"},{value:"pt",label:"Output: Torch"}]}),(0,Ca.jsx)(fe,{label:"Max tokens",value:$.maxSequenceLength,onChange:e=>oa({maxSequenceLength:As(e,$.maxSequenceLength)})})]}),(0,Ca.jsx)(fe,{label:"Attention kwargs JSON",value:$.attentionKwargsJson,multiline:!0,onChange:e=>oa({attentionKwargsJson:e})})]})]}),_t.supportsImageInput&&!re&&!pe&&(0,Ca.jsx)(Ca.Fragment,{children:(0,Ca.jsx)(kt,{label:`Strength: ${$.strength}`,children:(0,Ca.jsx)(ke,{min:0,max:1,step:.05,value:$.strength,onChange:e=>oa({strength:e})})})}),(Lt||sa)&&(0,Ca.jsx)(kt,{label:`Control conditioning: ${$.conditioningScale}`,children:(0,Ca.jsx)(ke,{min:0,max:2,step:.05,value:$.conditioningScale,onChange:e=>oa({conditioningScale:e})})}),Ht&&(0,Ca.jsx)(kt,{label:`IP-Adapter scale: ${$.ipAdapterScale}`,children:(0,Ca.jsx)(ke,{min:0,max:2,step:.05,value:$.ipAdapterScale,onChange:e=>oa({ipAdapterScale:e})})}),_t.supportsLayers&&(0,Ca.jsx)(fe,{label:_t.layerCount?`Layers (${_t.layerCount.min}–${_t.layerCount.max})`:"Layers",value:$.layers,onChange:e=>{let t=As(e,$.layers);oa({layers:_t.layerCount?Math.min(_t.layerCount.max,Math.max(_t.layerCount.min,t)):t})}})]})]}),Ce&&(0,Ca.jsx)(xs,{form:$}),Ce&&(0,Ca.jsx)(Rs,{form:$,template:se,onChange:oa,onRunSweep:e=>{Na(()=>za(e))},disabled:e||!!ia})]}),Ce&&(0,Ca.jsx)(ct,{id:"controlled-workflows",title:"Controlled workflows",children:(0,Ca.jsx)(ms,{form:$,onChange:oa,disabled:e})}),re&&(0,Ca.jsx)(ct,{id:"video-inputs",title:"Video inputs",defaultOpen:!0,children:(0,Ca.jsxs)("div",{className:"grid gap-2",children:[Zt&&(0,Ca.jsx)(fe,{label:"Source video path",value:$.sourceVideo,onChange:e=>oa({sourceVideo:e})}),ta&&(0,Ca.jsx)(fe,{label:"Source video paths (one per line)",value:$.referenceVideos.join("\n"),onChange:e=>oa({referenceVideos:e.split(/\r?\n/).map(e=>e.trim()).filter(Boolean)}),multiline:!0}),("video_inpaint"===$.mode||"video_outpaint"===$.mode)&&(0,Ca.jsx)(fe,{label:"Mask video path",value:$.maskVideo,onChange:e=>oa({maskVideo:e})}),sa&&(0,Ca.jsx)(fe,{label:"Control video path",value:$.controlVideo,onChange:e=>oa({controlVideo:e})}),"image_to_video"===$.mode&&(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Use Reference inputs below for the starting image."}),"reference_to_video"===$.mode&&(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Use Reference inputs below for subject and style images."})]})}),de&&(0,Ca.jsx)(ct,{id:"audio-inputs",title:"Audio",defaultOpen:!0,children:(0,Ca.jsxs)("div",{className:"grid gap-2",children:["text_to_audio"!==$.mode&&(0,Ca.jsx)(fe,{label:"Source audio path",value:$.sourceAudio,onChange:e=>oa({sourceAudio:e})}),"audio_variation"===$.mode&&(0,Ca.jsx)(fe,{label:"Reference audio path",value:$.referenceAudio,onChange:e=>oa({referenceAudio:e})}),(0,Ca.jsx)(fe,{label:"Lyrics",value:$.lyrics,onChange:e=>oa({lyrics:e}),multiline:!0}),(0,Ca.jsxs)("div",{className:ve("grid gap-2","grid-cols-2"),children:[(0,Ca.jsx)(kt,{label:`Duration ${$.audioDuration}s`,children:(0,Ca.jsx)(ke,{value:$.audioDuration,min:1,max:240,step:.5,onChange:e=>oa({audioDuration:e})})}),(0,Ca.jsx)(kt,{label:`Shift ${$.shift}`,children:(0,Ca.jsx)(ke,{value:$.shift,min:0,max:10,step:.1,onChange:e=>oa({shift:e})})})]}),"audio_continuation"===$.mode&&(0,Ca.jsx)(kt,{label:`Continuation length ${$.extensionDuration}s`,children:(0,Ca.jsx)(ke,{value:$.extensionDuration,min:1,max:180,step:.5,onChange:e=>oa({extensionDuration:e})})}),"audio_repaint"===$.mode&&(0,Ca.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Ca.jsx)(kt,{label:`Repaint start ${$.repaintingStart}s`,children:(0,Ca.jsx)(ke,{value:$.repaintingStart,min:0,max:240,step:.1,onChange:e=>oa({repaintingStart:e})})}),(0,Ca.jsx)(kt,{label:`Repaint end ${$.repaintingEnd}s`,children:(0,Ca.jsx)(ke,{value:$.repaintingEnd,min:0,max:240,step:.1,onChange:e=>oa({repaintingEnd:e})})})]}),"audio_variation"===$.mode&&(0,Ca.jsx)(kt,{label:`Cover strength ${$.audioCoverStrength}`,children:(0,Ca.jsx)(ke,{value:$.audioCoverStrength,min:0,max:1,step:.01,onChange:e=>oa({audioCoverStrength:e})})}),(0,Ca.jsxs)("div",{className:"grid grid-cols-3 gap-2",children:[(0,Ca.jsx)(ut,{value:$.vocalLanguage,onChange:e=>oa({vocalLanguage:e.target.value}),"aria-label":"Audio language"}),(0,Ca.jsx)(ut,{value:$.keyscale,onChange:e=>oa({keyscale:e.target.value}),"aria-label":"Audio key"}),(0,Ca.jsx)(ut,{value:$.timesignature,onChange:e=>oa({timesignature:e.target.value}),"aria-label":"Audio time signature"})]})]})}),xe&&(0,Ca.jsx)(ct,{id:"speech-inputs",title:"Speech recognition",defaultOpen:!0,children:(0,Ca.jsxs)("div",{className:"grid gap-2","data-testid":"studio-speech-controls",children:[(0,Ca.jsx)(fe,{label:"Source audio path",value:$.sourceAudio,onChange:e=>oa({sourceAudio:e})}),(0,Ca.jsx)(fe,{label:"Language hint (optional)",value:$.speechLanguage,onChange:e=>oa({speechLanguage:e})}),(0,Ca.jsx)(Ft,{"aria-label":"Speech timestamps",value:$.speechTimestamps,onValueChange:e=>oa({speechTimestamps:e}),options:[{value:"none",label:"No timestamps"},{value:"segment",label:"Segment timestamps"},{value:"word",label:"Word timestamps"}]}),(0,Ca.jsxs)("div",{className:"grid grid-cols-2 gap-2",children:[(0,Ca.jsx)(fe,{label:"Chunk length (seconds)",value:$.speechChunkSeconds,onChange:e=>oa({speechChunkSeconds:As(e,$.speechChunkSeconds)})}),(0,Ca.jsx)(fe,{label:"Chunk stride (seconds)",value:$.speechStrideSeconds,onChange:e=>oa({speechStrideSeconds:As(e,$.speechStrideSeconds)})})]}),"speech_translation"===$.mode&&(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Translation output is normalized to English."})]})}),(0,Ca.jsx)(ct,{id:"runtime",title:"Runtime",children:(0,Ca.jsxs)("section",{className:"grid gap-2","data-testid":"studio-expert-runtime-controls",children:[(0,Ca.jsx)($e,{title:"Custom runtime settings"}),(0,Ca.jsxs)("div",{className:"flex gap-2",children:[(0,Ca.jsx)(Ft,{"aria-label":"Data type",value:$.dtype,onValueChange:e=>oa({dtype:e}),options:[{value:"bfloat16",label:"bfloat16"},{value:"float16",label:"float16"},{value:"float32",label:"float32"}],className:"flex-1"}),(0,Ca.jsx)(ut,{"aria-label":"Device",value:$.device,onChange:e=>oa({device:e.target.value}),className:"w-24"})]}),(0,Ca.jsx)(ot,{checked:$.autoOffload,onChange:e=>oa({autoOffload:e}),label:"Auto-offload model components"}),(0,Ca.jsxs)("div",{className:ve("grid gap-2",qe.length?"grid-cols-2":"grid-cols-1"),children:[qe.length>0&&(0,Ca.jsx)(Ft,{"aria-label":"Quantization","data-testid":"studio-quantization-select",value:$.quantizationMode,onValueChange:e=>{oa({quantizationMode:e})},options:[{value:"none",label:"No quantization"},...qe.map(e=>({value:e,label:"bnb_4bit"===e?"4-bit BnB":"bnb_8bit"===e?"8-bit BnB":"quanto_float8"===e?"Quanto float8":"TorchAO float8"}))]}),(0,Ca.jsx)(Ft,{"aria-label":"Offload mode","data-testid":"studio-offload-mode-select",value:$.offloadMode,onValueChange:e=>oa({offloadMode:e}),options:_t.offloadSupport.modes.map(e=>({value:e,label:U[e]}))})]})]})}),(Dt||"image_to_video"===$.mode||"reference_to_video"===$.mode)&&(0,Ca.jsx)(ct,{id:"reference-inputs",title:pe?"Source image":"Reference inputs",defaultOpen:!0,children:(0,Ca.jsx)(os,{})}),(Et||Lt||Ht)&&(0,Ca.jsxs)(ct,{id:"mask-control",title:[Et,Lt,Ht].filter(Boolean).length>1?"Conditioning inputs":Et?"Mask draft":Lt?"Control image":"IP-Adapter image",defaultOpen:!0,children:[Et&&(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(fe,{label:"Mask image path",value:$.maskImage,onChange:e=>oa({maskImage:e})}),(0,Ca.jsx)("p",{className:ve("text-xs",Wt?"text-modiff-subtle-text":"text-hf-orange"),children:Wt?"The mask limits which source-image region can change.":Vt?.reason??"Brush, erase, invert, feather, clear, and fill are frontend draft controls until backend support is confirmed."}),(0,Ca.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,Ca.jsx)(J,{tone:"ghost",onClick:()=>i(!0),children:"Open mask editor"}),$.maskImage&&(0,Ca.jsx)(J,{tone:"ghost",onClick:()=>oa({maskImage:""}),children:"Clear saved mask"})]}),s&&(0,Ca.jsx)(hs,{sourceImage:$.referenceImages[0],maskImage:$.maskImage,onSave:e=>{oa({maskImage:e}),i(!1)},onCancel:()=>i(!1)})]}),Lt&&(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(fe,{label:"Control image path",value:$.controlImage,onChange:e=>oa({controlImage:e})}),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"The control image is a separate structural guide declared by the selected task contract."}),$.mode.includes("control_union")&&(0,Ca.jsx)(fe,{label:"ControlNet Union mode (0–5)",value:$.controlMode,onChange:e=>oa({controlMode:As(e,$.controlMode)})})]}),Ht&&(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsx)(fe,{label:"IP-Adapter image path",value:$.ipAdapterImage,onChange:e=>oa({ipAdapterImage:e})}),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"This reference is encoded by the separately editable IP-Adapter block; it is not the source or ControlNet image."})]})]}),(0,Ca.jsx)("p",{className:"border-t border-modiff-border pt-3 text-xs text-modiff-subtle-text",children:Ge?"Studio is linked to graph nodes. Workflow edits remain visible on the canvas.":"Custom graph mode: add nodes, connect an output, or start from a template."})]}),(0,Ca.jsx)(va,{open:!!a.pending,policies:a.pending?.policies??[],action:"run",onCancel:a.cancel,onConfirm:a.confirm})]})}var _s=[{id:"studio:prompt",kind:"studio-form",label:"Prompt",formKey:"prompt"},{id:"studio:negativePrompt",kind:"studio-form",label:"Negative prompt",formKey:"negativePrompt"},{id:"studio:seed",kind:"studio-form",label:"Seed",formKey:"seed"},{id:"studio:steps",kind:"studio-form",label:"Steps",formKey:"steps"},{id:"studio:guidanceScale",kind:"studio-form",label:"Guidance",formKey:"guidanceScale"},{id:"studio:pagScale",kind:"studio-form",label:"PAG scale",formKey:"pagScale"},{id:"studio:pagAdaptiveScale",kind:"studio-form",label:"PAG adaptive scale",formKey:"pagAdaptiveScale"},{id:"studio:processingResolution",kind:"studio-form",label:"Processing resolution",formKey:"processingResolution"},{id:"studio:matchInputResolution",kind:"studio-form",label:"Match input resolution",formKey:"matchInputResolution"},{id:"studio:strength",kind:"studio-form",label:"Strength",formKey:"strength"},{id:"studio:width",kind:"studio-form",label:"Width",formKey:"width"},{id:"studio:height",kind:"studio-form",label:"Height",formKey:"height"},{id:"studio:referenceImages",kind:"studio-form",label:"Reference images",formKey:"referenceImages"},{id:"studio:controlImage",kind:"studio-form",label:"Control image",formKey:"controlImage"},{id:"studio:maskImage",kind:"studio-form",label:"Mask image",formKey:"maskImage"},{id:"studio:sourceAudio",kind:"studio-form",label:"Source audio",formKey:"sourceAudio"},{id:"studio:speechLanguage",kind:"studio-form",label:"Speech language",formKey:"speechLanguage"},{id:"studio:speechTimestamps",kind:"studio-form",label:"Speech timestamps",formKey:"speechTimestamps"},{id:"studio:speechChunkSeconds",kind:"studio-form",label:"Speech chunk length",formKey:"speechChunkSeconds"},{id:"studio:speechStrideSeconds",kind:"studio-form",label:"Speech chunk stride",formKey:"speechStrideSeconds"}];function zs(e){return!(!e||"object"!=typeof e||!("id"in e)||"string"!=typeof e.id)}function qs(e){return e.filter(zs).flatMap(e=>Object.entries(e.data?.params??{}).filter(([e,t])=>!t.isInput&&!["image","output","images","latents"].includes(e)).slice(0,10).map(([t,a])=>({id:`graph:${e.id}:${t}`,kind:"graph-param",label:`${e.data?.label||e.id} / ${a.label||t}`,nodeId:e.id,paramKey:t})))}function Bs(e){return Array.isArray(e)?e.join("\n"):String(e??"")}function Ds(e,t,a){let s=e[t];return Array.isArray(s)?a.split("\n").map(e=>e.trim()).filter(Boolean):"number"==typeof s?function(e,t){let a=Number(e);return Number.isFinite(a)?a:t}(a,s):"boolean"==typeof s?"true"===a:a}function Gs(e,t){return e.exposedInputs.some(e=>e.id===t.id)}function Es(){let e=$t(e=>e.form),t=$t(e=>e.workflowTabs),a=$t(e=>e.activeWorkflowTabId),s=$t(e=>e.appModeConfigs),i=$t(e=>e.activeAppModeConfigId),l=$t(e=>e.createAppModeConfig),n=$t(e=>e.updateAppModeConfig),r=$t(e=>e.deleteAppModeConfig),o=$t(e=>e.setActiveAppModeConfig),d=$t(e=>e.switchWorkflowTab),c=$t(e=>e.updateForm),u=rt(e=>e.nodes),m=we(e=>e.sid),p=we(e=>e.isConnected),f=s.find(e=>e.id===i)??s[0]??null,h=t.find(e=>e.id===f?.workflowTabId)??t.find(e=>e.id===a)??null,x=!(!f||f.workflowTabId!==a),g=(0,ka.useMemo)(()=>x?u:h?.snapshot.nodes??[],[x,u,h]),b=(0,ka.useMemo)(()=>[..._s,...qs(g)],[g]),j=(0,ka.useMemo)(()=>function(e){return e.filter(zs).filter(e=>/preview|save|output|video|audio|image/i.test(`${e.data?.label??""} ${e.id}`)).map(e=>({id:`output:${e.id}`,label:e.data?.label||e.id,nodeId:e.id,fieldKey:"output"}))}(g),[g]),v=!!(a&&g.length>0&&j.length>0),y=!!(f&&f.exposedOutputs.length>0&&m&&p),w=(t,a)=>{"studio-form"!==t.kind||!t.formKey||(c({[t.formKey]:Ds(e,t.formKey,a)}),queueMicrotask(()=>Re()))};return(0,Ca.jsxs)("div",{className:"grid gap-3 p-3 text-sm text-modiff-text","data-testid":"app-mode-panel",children:[(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Run as app",action:(0,Ca.jsx)(J,{tone:"primary",icon:(0,Ca.jsx)(_,{size:15}),disabled:!v,onClick:()=>{v?l(a)&&Ae("Run as app created for this workflow",{variant:"success",autoHideDuration:2e3}):Ae("Use a graph with at least one output node before creating Run as app.",{variant:"error",autoHideDuration:3500})},"data-testid":"app-mode-create",children:"Create"})}),(0,Ca.jsx)(da,{children:"Expose selected inputs and outputs from this graph."})]}),s.length>0&&(0,Ca.jsx)(Ft,{"aria-label":"Saved app configuration",value:f?.id??"",onValueChange:o,options:s.map(e=>({value:e.id,label:e.name}))}),f?(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsxs)("section",{className:"grid gap-2 border border-modiff-border bg-modiff-panel p-3",children:[(0,Ca.jsx)(fe,{label:"App name",value:f.name,onChange:e=>n(f.id,{name:e})}),(0,Ca.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,Ca.jsx)(ge,{active:x,children:x?"Workflow open":"Workflow saved"}),(0,Ca.jsx)(ge,{children:h?.title??"Missing workflow"}),!x&&(0,Ca.jsx)(J,{tone:"ghost",onClick:()=>d(f.workflowTabId),children:"Open workflow"}),(0,Ca.jsx)(J,{tone:"danger",icon:(0,Ca.jsx)(L,{size:15}),onClick:()=>r(f.id),children:"Delete"})]})]}),(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Choose inputs"}),(0,Ca.jsx)("div",{className:"grid gap-1",children:b.slice(0,36).map(e=>(0,Ca.jsx)(ot,{checked:Gs(f,e),label:e.label,onChange:()=>(e=>{if(!f)return;let t=Gs(f,e)?f.exposedInputs.filter(t=>t.id!==e.id):[...f.exposedInputs,e];n(f.id,{exposedInputs:t})})(e)},e.id))})]}),(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Guided inputs"}),(0,Ca.jsxs)("div",{className:"grid gap-2",children:[0===f.exposedInputs.length&&(0,Ca.jsx)(da,{children:"No inputs exposed yet."}),f.exposedInputs.map(t=>{if("studio-form"===t.kind&&t.formKey){let a=e[t.formKey];return"guidanceScale"===t.formKey||"pagScale"===t.formKey||"pagAdaptiveScale"===t.formKey||"strength"===t.formKey?(0,Ca.jsx)(kt,{label:`${t.label}: ${String(a)}`,children:(0,Ca.jsx)(ke,{min:0,max:"strength"===t.formKey?1:12,step:.05,value:"number"==typeof a?a:0,onChange:e=>w(t,String(e))})},t.id):(0,Ca.jsx)(fe,{label:t.label,disabled:!x,multiline:"prompt"===t.formKey||"negativePrompt"===t.formKey||"referenceImages"===t.formKey,value:Bs(a),onChange:e=>w(t,e)},t.id)}return(0,Ca.jsx)(fe,{label:t.label,disabled:!x,value:String(t.nodeId&&t.paramKey?rt.getState().getParam(t.nodeId,t.paramKey,"value")??"":""),onChange:e=>((e,t)=>{"graph-param"!==e.kind||!e.nodeId||!e.paramKey||rt.getState().setParamWithHistory(e.nodeId,e.paramKey,t)})(t,e)},t.id)})]})]}),(0,Ca.jsxs)("section",{children:[(0,Ca.jsx)($e,{title:"Outputs"}),(0,Ca.jsxs)("div",{className:"grid gap-1",children:[0===j.length&&(0,Ca.jsx)(da,{children:"No output nodes detected."}),j.map(e=>(0,Ca.jsx)(ot,{checked:f.exposedOutputs.some(t=>t.id===e.id),label:e.label,onChange:()=>(e=>{if(!f)return;let t=f.exposedOutputs.some(t=>t.id===e.id)?f.exposedOutputs.filter(t=>t.id!==e.id):[...f.exposedOutputs,e];n(f.id,{exposedOutputs:t})})(e)},e.id))]})]}),(0,Ca.jsx)(J,{tone:"primary",icon:(0,Ca.jsx)(D,{size:15}),disabled:!y,onClick:()=>{(async()=>{if(!f||!m||!p)return void Ae("Connect to the MoDiff server before running as app.",{variant:"error",autoHideDuration:4e3});if(0===f.exposedOutputs.length)return void Ae("Expose at least one output before running as app.",{variant:"error",autoHideDuration:3500});f.workflowTabId!==a&&d(f.workflowTabId);let e=ne(),t=!!$t.getState().graphBinding;try{if(t&&!await K(e)||!Nt({sid:m,isConnected:p,includeStudio:t,showDialog:!0}).canRun)return;await It({sid:m,studioContext:{applyRuntimeMetadata:t},workflowContext:e}),Ae("Run as app queued",{variant:"success",autoHideDuration:2200})}catch(e){if(De(e))return;Ae(String(e),{variant:"error",autoHideDuration:7e3})}})()},"data-testid":"app-mode-run",children:"Run app"})]}):(0,Ca.jsx)("div",{className:"border border-modiff-border bg-modiff-bg p-3 text-xs text-modiff-subtle-text",children:"Create Run as app from the active graph."})]})}function Os(){let e=(0,ka.useMemo)(oa,[]),t=rt(t=>e(t.nodes)),a=rt(e=>e.edges),s=$t(e=>e.activeWorkflowTabId),[i,l]=(0,ka.useState)(""),[n,r]=(0,ka.useState)(null),[d,c]=(0,ka.useState)(""),[u,m]=(0,ka.useState)(!1),[p,f]=(0,ka.useState)(null),h=(0,ka.useRef)(null),x=(0,ka.useMemo)(()=>{try{return{request:ca(t,a,i||void 0),error:""}}catch(e){return{request:null,error:e instanceof Error?e.message:String(e)}}},[t,a,i]),g=x.request?.key;(0,ka.useEffect)(()=>(h.current?.abort(),m(!1),r(null),c(""),f(null),()=>h.current?.abort()),[g,i,s]);let b=x.request,j=n?.request.key===g?n:null;return(0,Ca.jsxs)("section",{className:"grid gap-3 p-3 text-sm text-modiff-text","data-testid":"workflow-resource-assessment",children:[(0,Ca.jsxs)("h2",{className:"flex items-center gap-2 font-semibold",children:[(0,Ca.jsx)(o,{size:17}),"Workflow resources"]}),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Memory → Automatic chooses supported technical settings. Custom keeps your configured settings. Creator and Developer workspaces can use either memory policy; changing workspace preserves this workflow’s settings."}),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"A loader’s Auto Offload controls where its weights reside. Repeat/Loop controls repeated runs. They are separate from Auto resource planning."}),(0,Ca.jsx)(ht,{"aria-label":"Resource assessment scope",value:i,onValueChange:l,options:[{value:"",label:"Whole workflow"},...t.filter(e=>e.data.blockInstanceV2||e.data.blockProjectionContainer).map(e=>({value:e.id,label:`Only ${e.data.label??e.id}`}))]}),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:i?"Only contained nodes are assessed. Outside suppliers are excluded, as with Run Block.":"Includes connected outside nodes and every enabled Block."}),b?(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsxs)("p",{className:"text-xs",children:[b.nodeCount," executable nodes · ",b.loaderCount," model loaders · ",b.adapterCount," adapters"]}),(0,Ca.jsx)("p",{className:"rounded-modiff-compact border border-modiff-border p-2 text-xs","data-testid":"workflow-auto-reason",children:"Auto can plan custom workflows and multiple Blocks. Each model recipe and the combined memory requirement must pass the backend check."})]}):null,(0,Ca.jsxs)(Ht,{onClick:()=>{(async()=>{if(!x.request)return;h.current?.abort();let e=new AbortController;h.current=e,m(!0),c(""),r(null);try{let t=await ma(x.request,e.signal);if(!e.signal.aborted&&$t.getState().activeWorkflowTabId===s){let e=rt.getState();if(ca(e.nodes,e.edges,i||void 0).key!==t.request.key)throw Error("The workflow changed. Assess its current settings again.");r(t)}}catch(t){e.signal.aborted||c(t instanceof Error?t.message:String(t))}finally{e.signal.aborted||m(!1)}})()},disabled:u||!b||!b.nodeCount,"data-testid":"assess-workflow-resources",children:[(0,Ca.jsx)(q,{size:14}),u?"Assessing resources…":"Assess workflow resources"]}),(0,Ca.jsx)(Ht,{onClick:()=>{(async()=>{h.current?.abort();let e=new AbortController;h.current=e,m(!0),c(""),f(null);try{let t=await(await Y(async()=>{let{inspectWorkflowAutoPlanV2:e}=await import(`./block-composition-tools.js?v=cc046c26d6fe7aa4`).then(e=>e.t);return{inspectWorkflowAutoPlanV2:e}},[])).inspectWorkflowAutoPlanV2(i||void 0,e.signal);!e.signal.aborted&&$t.getState().activeWorkflowTabId===s&&f(t)}catch(t){e.signal.aborted||c(t instanceof Error?t.message:String(t))}finally{e.signal.aborted||m(!1)}})()},disabled:u||!b?.nodeCount,"data-testid":"plan-workflow-auto",children:"Check Auto execution plan"}),p?(0,Ca.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border p-2 text-xs","data-testid":"workflow-auto-plan",children:[(0,Ca.jsx)("p",{className:"font-semibold",children:p.canAutoRun?p.requiresPreparation?"Check computed inputs when Run starts":"Ready for Auto execution":"Auto needs attention"}),(0,Ca.jsx)("p",{children:p.message}),(0,Ca.jsxs)("p",{children:[p.requiresPreparation?"Resolved so far: ":"Peak planned resources: ",p.loaders.length," model owners · RAM"," ",ie(p.requirements.systemRamBytes??null)," · GPU"," ",ie(p.requirements.vramBytes??null)]}),p.loaders.map(e=>(0,Ca.jsxs)("p",{children:[e.repository," · ",e.consumers.length," consumers"]},e.nodeId)),p.patches.length?(0,Ca.jsxs)("p",{children:["Run will apply ",p.patches.length," resource-setting changes to the visible workflow."]}):null,p.issues.map((e,t)=>(0,Ca.jsx)("p",{className:"break-words text-modiff-subtle-text",children:e},t)),(0,Ca.jsx)("p",{children:"Run checks the current graph and available memory again. Custom preserves your explicit settings."})]}):null,x.error||d?(0,Ca.jsx)("p",{role:"alert",className:"break-words text-xs text-modiff-red",children:x.error||d}):null,j?(0,Ca.jsxs)(Ca.Fragment,{children:[(0,Ca.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-border p-2 text-xs",children:[(0,Ca.jsxs)("p",{children:["Available RAM: ",ie(j.resources.system.ramAvailableBytes)]}),j.resources.accelerators.map(e=>(0,Ca.jsxs)("p",{children:[e.name,": ",ie(e.memoryFreeBytes)," free · ",e.memoryKind," memory"]},e.device)),(0,Ca.jsx)("p",{className:"mt-1 text-modiff-subtle-text",children:"Shared GPU memory and RAM overlap; they are not added together."})]}),j.request.items.map(e=>{let t=j.plans[e.id],a=t?pe(t,e.form??void 0):null;return(0,Ca.jsxs)("div",{className:"grid gap-1 rounded-modiff-compact border border-modiff-border p-2 text-xs","data-testid":"workflow-resource-item",children:[(0,Ca.jsx)("p",{className:"break-words font-semibold",children:e.label}),(0,Ca.jsxs)("p",{children:[e.loaderIds.length," loader",1===e.loaderIds.length?"":"s"," · ",e.consumers," downstream consumers"]}),e.currentSettings.map((t,a)=>(0,Ca.jsxs)("p",{className:"break-words text-modiff-subtle-text",children:["Current: ",t]},e.loaderIds[a])),a?(0,Ca.jsxs)("p",{className:"break-words",children:["Source recipe recommendation: ",a.dtype," · ",a.offloadMode,"."," ",t?.statusLabel??a.reason]}):t?(0,Ca.jsx)("p",{children:t.blockingReason??t.message??"No supported local source recipe was selected."}):null,e.details.map(e=>(0,Ca.jsx)("p",{className:"text-modiff-subtle-text",children:e},e))]},e.id)}),j.request.issues.map(e=>(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:e},e)),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Assessment preserves the graph and current settings. Check Auto execution plan to validate the actual combined graph, connected adapters and available memory before running."})]}):null]})}var Ls=[{value:"recommended",label:"Recommended"},{value:"best_quality",label:"Best quality"},{value:"faster",label:"Faster"},{value:"lowest_memory",label:"Lowest memory"}];function Fs(e){return e.resolvedArtifact||e.artifact||e.modelRepo||""}function Hs(e){let t=e.artifactResolution?.resolved,a=t?.format||e.artifactFormat||e.quantizationMode||"native",s=t?.bits?` ${t.bits}-bit`:"";return`${Fs(e)} · ${a}${s}`}function Ks(e){let t=e.requirements?.minimum||e.requirements?.lowerMemory;if(!t)return[];let a=1024**3,s=[];return"number"==typeof t.vramBytes&&t.vramBytes>0&&s.push(`${Math.ceil(t.vramBytes/a)} GB VRAM`),"number"==typeof t.systemRamBytes&&s.push(`${Math.ceil(t.systemRamBytes/a)} GB RAM`),"number"==typeof t.diskFreeBytes&&s.push(`${Math.ceil(t.diskFreeBytes/a)} GB disk`),s}function Ws(){let[e,t]=(0,ka.useState)(null),a=Je(e=>e.setRightPanelTab),s=Je(e=>e.setRightPanelOpen),l=Je(e=>e.setAlertOpener),n=we(e=>e.sid),{installHfModel:r}=Ze(nt(e=>({installHfModel:e.installHfModel}))),{form:u,graphBinding:m,plan:p,applyAutoResourcePlan:f,createWorkflowTab:h,updateForm:x,setAutoResourcePlan:g}=$t(nt(e=>({form:e.form,graphBinding:e.graphBinding,plan:e.autoResourcePlan,applyAutoResourcePlan:e.applyAutoResourcePlan,createWorkflowTab:e.createWorkflowTab,updateForm:e.updateForm,setAutoResourcePlan:e.setAutoResourcePlan}))),b=pe(p,u)||null,v=ra(b,m?.controlled?.contractIds),y=(0,ka.useMemo)(()=>(p?.candidates||[]).filter(e=>e.id!==b?.id),[p?.candidates,b?.id]);if(!m)return(0,Ca.jsx)(Os,{});if(!b)return(0,Ca.jsxs)("div",{className:"grid gap-3 p-4 text-sm text-modiff-text","data-testid":"compatibility-panel",children:[(0,Ca.jsxs)("div",{className:"flex items-center gap-2 font-semibold",children:[(0,Ca.jsx)(O,{size:17,className:"text-modiff-green"}),"No model substitution"]}),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Select a Studio model to inspect local compatibility."})]});let w=async e=>{let a=Fs(e);if(!a)return;let s=ne();t(a);try{await r(a,n,{repair:!!e.repairRequired}),et(s),Ae(`${a} is ready. Refreshing compatibility.`,{variant:"success"});let t=await Q($t.getState().form);et(s),g(t)}catch(e){if(De(e))return;Ae(e instanceof Error?e.message:`Could not install ${a}.`,{variant:"error"})}finally{t(null)}},k=e=>{let t=Fs(e);l({title:e.repairRequired?"Repair artifact?":"Install artifact?",message:`${t} will be downloaded and managed by MoDiff.`,confirmText:e.repairRequired?"Repair":"Install",cancelText:"Cancel",onConfirm:()=>{l(null),w(e)},onCancel:()=>l(null)})},C=async(e,t="")=>{let a=ne();try{let s={...xe(e,u),confirmedCommunityArtifact:t},i={...u,...s},l=await Q(i);et(a);let n=pe(l,i)??e;f(l,{...xe(n,i),confirmedCommunityArtifact:t}),aa(a),Ae("Next local option selected.",{variant:"info"})}catch(e){if(De(e))return;Ae(String(e),{variant:"error"})}},N=b.compatibilityEvidence,S=v?.message||N?.message||b.reason||b.skipReason||p?.blockingReason||"Compatibility was estimated from the current runtime and model metadata.",I=b.artifactResolution?.base?.repo||b.baseArtifact||Fs(b),R=!!(b.artifactResolution?.substituted||I&&I!==Fs(b));return(0,Ca.jsx)(Ca.Fragment,{children:(0,Ca.jsxs)("div",{className:"grid gap-3 p-3 text-sm text-modiff-text","data-testid":"compatibility-panel",children:[(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Automatic memory selects a supported local resource recipe. Custom keeps your configured technical settings. Switching preserves the workflow’s nodes, connections and creative controls."}),(0,Ca.jsx)("p",{className:"text-xs text-modiff-subtle-text",children:"Auto Offload on a loader controls weight placement. Repeat/Loop controls repeated runs. These are separate from Auto resource planning."}),(0,Ca.jsxs)("div",{className:"flex items-start gap-2 rounded-modiff-compact border border-hf-yellow/50 bg-hf-yellow/10 p-3",children:[(0,Ca.jsx)(j,{size:17,className:"mt-0.5 shrink-0 text-hf-yellow"}),(0,Ca.jsxs)("div",{className:"min-w-0",children:[(0,Ca.jsx)("p",{className:"font-semibold",children:v?.label||N?.label||b.healthBadge||"This should work"}),(0,Ca.jsx)("p",{className:"mt-1 line-clamp-2 text-xs text-modiff-subtle-text",children:S})]})]}),(0,Ca.jsx)(kt,{label:"Recommendation",children:(0,Ca.jsx)(Ft,{value:u.resourcePreference||"recommended",onValueChange:e=>{(async e=>{let t=ne(),a={...u,resourcePreference:e};x({resourcePreference:e}),aa(t);try{let e=await Q(a);et(t),g(e)}catch(e){if(De(e))return;Ae(String(e),{variant:"error"})}})(e)},options:Ls.map(e=>({value:e.value,label:e.label})),"data-testid":"compatibility-preference"})}),(0,Ca.jsxs)("section",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border bg-modiff-surface p-3",children:[R?(0,Ca.jsxs)("p",{className:"truncate text-xs text-modiff-subtle-text",title:I,children:["Base: ",I]}):null,(0,Ca.jsx)("p",{className:"break-all font-semibold text-hf-yellow",children:Hs(b)}),(0,Ca.jsx)("div",{className:"flex flex-wrap gap-1.5",children:Ks(b).map(e=>(0,Ca.jsx)("span",{className:"rounded bg-modiff-bg px-2 py-1 text-modiff-label text-modiff-subtle-text",children:e},e))}),!1===b.installed||b.repairRequired?(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(G,{size:15}),onClick:()=>k(b),disabled:e===Fs(b),children:b.repairRequired?"Repair artifact":"Install in app"}):null]}),y.length?(0,Ca.jsxs)("section",{className:"grid gap-2",children:[(0,Ca.jsx)("h3",{className:"text-xs font-semibold uppercase text-modiff-subtle-text",children:"Other options"}),y.map(e=>(0,Ca.jsxs)(Ht,{tone:"secondary",align:"left",fullWidth:!0,className:"h-auto p-2",onClick:()=>(e=>{if(e.requiresConfirmation&&"manual_only"===e.proof?.status){if(!e.installed||e.repairRequired)return void k(e);let t=Fs(e);return void l({title:"Use community artifact?",message:`${t} is popularity-qualified but not quality-qualified by MoDiff. Use it for this workflow and machine?`,confirmText:"Use artifact",cancelText:"Cancel",onConfirm:()=>{l(null),C(e,t)},onCancel:()=>l(null)})}C(e)})(e),icon:"gguf"===e.artifactFormat?(0,Ca.jsx)(d,{size:15}):(0,Ca.jsx)(o,{size:15}),children:[(0,Ca.jsxs)("span",{className:"min-w-0 flex-1",children:[(0,Ca.jsx)("span",{className:"block truncate text-xs font-semibold",children:Fs(e)}),(0,Ca.jsx)("span",{className:"block truncate text-modiff-label text-modiff-subtle-text",children:e.compatibilityEvidence?.label||e.healthBadge})]}),(0,Ca.jsx)(i,{size:14,className:"text-modiff-subtle-text"})]},e.id))]}):null,(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(c,{size:15}),onClick:()=>a("setup"),children:"Open setup"}),(0,Ca.jsx)(Ht,{icon:(0,Ca.jsx)(o,{size:15}),onClick:()=>{(async()=>{let e=ne(),t="modules.ModelArtifact.QuantizeDiffusersComponents",a=Ze.getState().nodesRegistry;a[t]||(await Ze.getState().fetchNodes(),et(e),a=Ze.getState().nodesRegistry);let i=Lt(t,a,{x:120,y:100});if(!i)return void Ae("The optimized-artifact node is unavailable in this backend.",{variant:"error"});let l=b.artifactResolution?.base?.repo||b.baseArtifact||Fs(b);h(`Optimize ${l.split("/").pop()||"model"}`),rt.getState().addNode(i),rt.getState().setParam(i.id,"model_id",{source:"hub",value:l}),rt.getState().setParam(i.id,"source_revision",b.artifactResolution?.base?.revision||"main"),b.pipelineClass&&rt.getState().setParam(i.id,"pipeline_class",b.pipelineClass),$t.getState().saveActiveWorkflowTab(!0),s(!1),Ae("Optimized-copy workflow created. Review its admission and quality settings before running.",{variant:"success"})})()},children:"Create optimized copy"}),(0,Ca.jsxs)(bt,{className:"rounded-modiff-compact border border-modiff-border bg-modiff-bg p-2 text-xs",label:"Evidence and requirements",buttonClassName:"min-h-0 p-0 text-xs",panelClassName:"mt-2 grid gap-2 break-words text-modiff-subtle-text",children:[(0,Ca.jsx)("p",{children:S}),b.requirementsMissing?.map(e=>(0,Ca.jsx)("p",{children:e},e)),N?.popularity?(0,Ca.jsxs)("p",{children:["Hub snapshot: ",N.popularity.downloads??0," downloads · ",N.popularity.likes??0," likes. Popularity is not compatibility proof."]}):null]})]})})}var Vs=(0,ka.lazy)(()=>Y(()=>import(`./block-composition-tools.js?v=cc046c26d6fe7aa4`).then(e=>e.l),[])),Us=(0,ka.lazy)(()=>Y(()=>import(`./BlockInterfaceDialogContentV2.js?v=cc046c26d6fe7aa4`),[]));function Qs(){let e=jt(e=>e.target),t=jt(e=>e.close),a=$t(e=>e.activeWorkflowTabId),s=$t(e=>e.workflowCanvasEpoch),i=rt(t=>{let a=t.nodes.find(t=>t.id===e?.snapshot.instanceId)?.data.blockInstanceV2;return!(!a||e?.subtreeId&&!a.effectiveGraph.nodes.some(t=>t.nodeId===e.subtreeId))})&&e?.context.workflowTabId===a&&e?.context.canvasEpoch===s;return(0,ka.useEffect)(()=>{e&&!i&&t()},[e,i,t]),e&&i?(0,Ca.jsx)(ka.Suspense,{fallback:(0,Ca.jsx)("p",{className:"p-3 text-sm",role:"status",children:"Loading Block editor…"}),children:"save"===e.mode?(0,Ca.jsx)(Vs,{nodeId:e.nodeId,context:e.context,onClose:t},e.requestId):(0,Ca.jsx)(Us,{...e,onClose:t},e.requestId)}):null}export{rs as a,ua as default,Ps as i,Ws as n,es as o,Es as r,Qs as t}; \ No newline at end of file diff --git a/web/assets/block-composition-tools.js b/web/assets/block-composition-tools.js index f95d249f..76ebd485 100644 --- a/web/assets/block-composition-tools.js +++ b/web/assets/block-composition-tools.js @@ -1 +1 @@ -import{n as e,r as t}from"./rolldown-runtime.js?v=5bcd315505f5670a";import{An as a,At as o,B as n,Ct as i,Dn as d,En as r,Et as s,J as l,M as c,Nn as u,Q as p,Wn as f,ct as m,fn as h,ir as g,nt as b,pr as x,rr as I,ur as k,z as v}from"./graph-vendor.js?v=5bcd315505f5670a";import{$ as y,$n as w,$r as j,Bi as N,Cn as $,D as C,Dn as S,En as P,Eo as _,Fr as T,G as M,Gt as O,Hn as A,Jr as E,Jt as H,K as R,L as B,Lo as V,Mo as z,Nr as L,Po as D,Pr as U,Q as q,Qr as F,Qt as G,S as W,Sn as K,U as J,Ua as Q,Ut as Y,Wt as X,X as Z,Xr as ee,Yr as te,Zt as ae,_ as oe,_o as ne,ao as ie,b as de,bi as re,co as se,en as le,er as ce,fo as ue,g as pe,go as fe,ho as me,ht as he,ii as ge,io as be,ir as xe,jn as Ie,kr as ke,lo as ve,mo as ye,ni as we,nr as je,oo as Ne,po as $e,pr as Ce,pt as Se,rn as Pe,ro as _e,rr as Te,sn as Me,so as Oe,ta as Ae,tr as Ee,ui as He,v as Re,vn as Be,w as Ve,x as ze,zi as Le}from"./studio-templates.js?v=5bcd315505f5670a";import{t as De}from"./EditorPanel.js?v=5bcd315505f5670a";import{a as Ue,i as qe,n as Fe,o as Ge,r as We,t as Ke}from"./BlockInterfaceDialogContentV2.js?v=5bcd315505f5670a";import{t as Je}from"./LegacyUserBlockNode.js?v=5bcd315505f5670a";import{a as Qe,i as Ye,n as Xe,o as Ze,r as et,t as tt}from"./WorkspacePanel.js?v=5bcd315505f5670a";var at=e({default:()=>it}),ot=t(x(),1),nt=k();function it(e){return"composition"===e.mode?(0,nt.jsx)(dt,{...e}):(0,nt.jsx)(X,{open:!0,onClose:()=>!e.busy&&e.onClose(),title:"Switch model route",testId:`switch-block-route-v1-${e.nodeId}`,panelClassName:"max-w-lg",footer:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Y,{disabled:e.busy,onClick:e.onClose,children:"Cancel"}),(0,nt.jsx)(Y,{disabled:e.busy,onClick:()=>e.onSwitch(!0),children:"Save active route and switch"}),(0,nt.jsx)(Y,{tone:"primary",disabled:e.busy,onClick:()=>e.onSwitch(!1),children:"Keep draft and switch"})]}),children:(0,nt.jsx)("p",{className:"text-sm text-modiff-subtle-text",children:"This route has workflow-local changes. MoDiff can keep its exact values, internal graph, interface, and layout as an inactive workflow draft, then restore them when you switch back. Previews and run authority are not copied between models."})})}function dt({nodeId:e,instance:t,onClose:a}){let o=T(e=>e.library),n=U(e=>e.snapshot),i=T(e=>e.error),d=U(e=>e.error),r=t.effectiveGraph.nodes.some(e=>"upstream_block"===e.modularDiffusers?.kind),s=!r||!(!o||!n),[l,c]=(0,ot.useState)(0),[u,p]=(0,ot.useState)(!1);(0,ot.useEffect)(()=>{if(!r)return;let e=!0;p(!0);let t=T.getState(),a=U.getState();return Promise.all([t.library?Promise.resolve():t.fetchLibrary(),a.snapshot?Promise.resolve():a.fetchSnapshot()]).finally(()=>{e&&p(!1)}),()=>{e=!1}},[r,l]);let[f,m]=(0,ot.useState)(!1),[h,g]=(0,ot.useState)(null),[b,x]=(0,ot.useState)(null),I=(0,ot.useRef)(null),k=(0,ot.useMemo)(()=>{if(!s)return{recipe:null,error:null};try{return{recipe:ze(t,o,n),error:null}}catch(e){return{recipe:null,error:e instanceof Error?e.message:"Could not derive the upstream Modular composition."}}},[t,o,n,s]);return(0,ot.useEffect)(()=>(g(null),x(null),m(!1),()=>I.current?.abort()),[D(k.recipe)]),(0,nt.jsx)(X,{open:!0,onClose:a,title:"Block composition",testId:`user-block-composition-dialog-${e}`,panelClassName:"max-w-lg",footer:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Y,{onClick:a,children:"Close"}),s?null:(0,nt.jsx)(Y,{disabled:u,onClick:()=>c(e=>e+1),children:u?"Loading pinned catalog…":"Retry pinned catalog"}),k.recipe?(0,nt.jsx)(Y,{tone:"primary",disabled:f,onClick:()=>{(async()=>{if(!k.recipe||f)return;let e=new AbortController;I.current=e,m(!0),g(null),x(null);try{let t=await de(k.recipe,e.signal);e.signal.aborted||g(t)}catch(t){e.signal.aborted||x(t instanceof Error?t.message:"The backend could not rebuild this Modular composition.")}finally{e.signal.aborted||m(!1)}})()},children:f?"Rebuilding…":"Validate upstream composition"}):null]}),children:(0,nt.jsxs)("div",{className:"grid gap-2 text-sm text-modiff-subtle-text",children:[s?null:(0,nt.jsx)("p",{role:u?"status":"alert",children:u?"Loading the reviewed block catalog and hierarchy. Your workflow is unchanged.":i??d??"The pinned catalog is unavailable. Retry before validating."}),(0,nt.jsxs)("p",{children:[t.effectiveGraph.nodes.length," ordinary internal nodes and ",t.effectiveGraph.edges.length," ","internal connections are owned by this workflow instance."]}),(0,nt.jsxs)("p",{children:["Source: ",t.definitionSnapshot.source.kind,". Customization state:"," ",t.customization.state.replace(/_/gu," "),"."]}),k.recipe?(0,nt.jsxs)("p",{"data-testid":`block-v2-composition-summary-${e}`,children:["Exact upstream recipe: ",k.recipe.operations.length," structural operation",1===k.recipe.operations.length?"":"s",". Parameter-only changes are stored directly on this workflow instance and do not rebuild sibling blocks."]}):null,k.error?(0,nt.jsx)("p",{className:"text-modiff-warning","data-testid":`block-v2-composition-error-${e}`,children:k.error}):null,h?(0,nt.jsxs)("p",{className:"text-modiff-green","data-testid":`block-v2-composition-receipt-${e}`,children:["Upstream init_pipeline() rebuild passed for ",h.composedPaths.length," paths. Receipt"," ",h.receiptHash.slice(0,20),"…"]}):null,b?(0,nt.jsx)("p",{className:"text-modiff-red",children:b}):null,"edited_unpruned_tree"===h?.inspectionScope?(0,nt.jsx)("p",{children:"This checks the edited tree, including inactive branches. It does not load models or qualify a run. Execution checks each connected step's required components and inputs."}):null]})})}function rt(e,t){return`${e}\0${t}`}function st(e,t){let a=e.fieldId??e.fieldOrPortId??"",o=t.fieldId??t.fieldOrPortId??"";return e.nodeId===t.nodeId?a===o?0:a=e.length)return[...e];let n=[...e];return[n[t],n[o]]=[n[o],n[t]],n}function ct({entryKey:e,valueType:t,primary:a,mirrors:o,options:i,selected:d,disabled:r=!1,onSelected:s,onAdd:l,onRemove:c}){let u=function(e,t,a,o){let n=new Set([rt(a.nodeId,a.fieldId),...o.map(({nodeId:e,fieldId:t})=>rt(e,t))]);return e.filter(e=>!n.has(e.key)&&z(t,e.valueType))}(i,t,a,o),p=new Map(i.map(e=>[e.key,e.label]));return(0,nt.jsxs)("div",{className:"ml-3 grid gap-2 border-l border-modiff-border-subtle pl-3","data-testid":`interface-mirrors-${e}`,children:[o.map(t=>{let a=rt(t.nodeId,t.fieldId);return(0,nt.jsxs)("div",{className:"flex min-w-0 items-center gap-2 text-xs text-modiff-subtle-text",children:[(0,nt.jsxs)("span",{className:"min-w-0 flex-1 break-words",children:["Additional consumer: ",p.get(a)??`${t.nodeId} / ${t.fieldId}`]}),(0,nt.jsx)(O,{label:`Remove additional consumer ${p.get(a)??t.fieldId}`,size:"compact",disabled:r,"data-testid":`interface-mirror-remove-${e}-${t.nodeId}-${t.fieldId}`,onClick:()=>c(t),children:(0,nt.jsx)(n,{size:13})})]},a)}),(0,nt.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2",children:[(0,nt.jsx)(Ge,{value:d,disabled:r,onValueChange:s,options:u.map(e=>({value:e.key,label:e.label})),placeholder:"Add another internal consumer","data-testid":`interface-mirror-select-${e}`}),(0,nt.jsx)(O,{label:"Add additional internal consumer",size:"compact",disabled:r||!d||!u.some(({key:e})=>e===d),"data-testid":`interface-mirror-add-${e}`,onClick:()=>{let e=u.find(({key:e})=>e===d);e&&l(e)},children:(0,nt.jsx)(m,{size:14})})]})]})}function ut(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function pt(e,t,a){let o=new Set([...a.boundary.inputs.map(({portId:e})=>e),...a.boundary.outputs.map(({portId:e})=>e),...a.controls.map(({controlId:e})=>e)]),n=`${e}:${t.nodeId}:${t.fieldId}`.replace(/[^A-Za-z0-9_.:/-]/gu,"-"),i=n,d=2;for(;o.has(i);)i=`${n}-${d++}`;return i}function ft({nodeId:e,snapshot:t,subtreeId:o,context:i,onClose:d}){let[r,s]=(0,ot.useState)(()=>Fe(t,o)),[l,c]=(0,ot.useState)({input:"",output:"",control:""}),[p,f]=(0,ot.useState)({}),h=(0,ot.useMemo)(()=>qe(t,o),[t,o]),g=(0,ot.useMemo)(()=>function(e){return e.effectiveGraph.nodes.flatMap(e=>{let t=ut(e.data.params)?e.data.params:{};return Object.entries(t).flatMap(([t,a])=>ut(a)&&"string"==typeof a.type&&a.type?[{key:`${e.nodeId}\0${t}`,nodeId:e.nodeId,fieldId:t,label:`${"string"==typeof e.data.label?e.data.label:e.nodeId} / ${"string"==typeof a.label?a.label:t}`,valueType:a.type,..."string"==typeof a.display?{display:a.display}:{}}]:[])})}(t).filter(({nodeId:e})=>h.has(e)),[t,h]),b=g.filter(({display:e})=>"output"!==e),x=g.filter(({display:e})=>"output"===e),I=b,k=ke(e=>e.edges),v=null,y=null;try{r&&(y=Ue(t,t,r,o))}catch(e){v=e instanceof Error?e.message:"Invalid interface draft."}let w=new Set(y?.boundary.inputs.map(({portId:e})=>e)),j=new Set(y?.boundary.outputs.map(({portId:e})=>e)),N=o&&y?L(t,o,{schemaVersion:1,...y}):o?[]:k.filter(e=>!((e.target!==t.instanceId||ve(e.targetHandle)||e.targetHandle&&w.has(e.targetHandle))&&(e.source!==t.instanceId||ve(e.sourceHandle)||e.sourceHandle&&j.has(e.sourceHandle))));return(0,nt.jsx)(De,{onClose:()=>d(),title:"Configure Block interface",testId:`configure-block-v2-${e}`,footer:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Y,{onClick:()=>d(),children:"Cancel"}),(0,nt.jsx)(Y,{tone:"primary",disabled:!r||N.length>0||!!v,onClick:()=>{if(r)try{(function(){ge(i,{includeForm:!1});let e=ke.getState().nodes.find(e=>e.id===t.instanceId)?.data.blockInstanceV2;if(!e||!r)throw Error("This Block is no longer available. Reopen Configure Interface.");let a=Ue(t,e,r,o);ke.getState().configureBlockInterfaceV2(t.instanceId,a,o)})(),d(),Q("The workflow instance interface was updated.",{variant:"success",autoHideDuration:3e3})}catch(e){Q(e instanceof Error?e.message:"Could not configure this interface.",{variant:"error",autoHideDuration:5200})}},children:"Apply interface"})]}),children:r?(0,nt.jsxs)("div",{className:"grid gap-5",children:[o?(0,nt.jsxs)("p",{className:"rounded-modiff-compact border border-modiff-border-subtle p-3 text-sm text-modiff-subtle-text","data-testid":"block-interface-scope",children:["Editing"," ",String(t.effectiveGraph.nodes.find(e=>e.nodeId===o)?.data.label||o),"'s own ports, controls, and previews. This interface is saved with the workflow and remains available after disconnecting links, collapsing, or reloading. The root Block interface is unchanged. Controls keep their existing values; shared controls continue to update their declared consumers together."]}):null,v?(0,nt.jsx)("p",{role:"alert",className:"text-modiff-red",children:v}):null,N.length?(0,nt.jsxs)("p",{className:"rounded-modiff-compact border border-modiff-warning/50 bg-modiff-warning/10 p-3 text-sm text-modiff-text",children:[N.length," connected edge",1===N.length?"":"s"," would lose a public port. Disconnect ",1===N.length?"it":"them"," before applying."]}):null,[["input","Inputs",b],["output","Outputs",x]].map(([e,t,o])=>{let i=r.boundary["input"===e?"inputs":"outputs"];return(0,nt.jsxs)("section",{className:"grid gap-2",children:[(0,nt.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:t}),i.map((t,d)=>{let r=`${e}:${t.portId}`,l=("input"===e?t.mirrorBindings:void 0)?.map(e=>({nodeId:e.nodeId,fieldId:e.fieldOrPortId}))??[];return(0,nt.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:[(0,nt.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2 [&>input]:col-span-2",children:[(0,nt.jsx)(le,{"aria-label":`${e} ${t.portId} label`,value:t.label,onChange:a=>{let o=a.currentTarget.value;s(a=>a&&{...a,boundary:{...a.boundary,["input"===e?"inputs":"outputs"]:i.map(e=>e.portId===t.portId?{...e,label:o}:e)}})}}),(0,nt.jsx)(Ge,{value:rt(t.binding.nodeId,t.binding.fieldOrPortId),onValueChange:a=>{let n=o.find(e=>e.key===a);n&&(s(a=>a&&{...a,boundary:{...a.boundary,["input"===e?"inputs":"outputs"]:i.map(e=>e.portId===t.portId?(()=>{let t={...e};return delete t.mirrorBindings,{...t,valueType:n.valueType,binding:{nodeId:n.nodeId,fieldOrPortId:n.fieldId}}})():e)}}),f(e=>({...e,[r]:""})))},options:o.map(e=>({value:e.key,label:e.label}))}),(0,nt.jsxs)("div",{className:"flex items-center gap-1",children:[(0,nt.jsx)(O,{label:`Move ${e} ${t.label} up`,size:"compact",disabled:0===d,onClick:()=>s(t=>t&&{...t,boundary:{...t.boundary,["input"===e?"inputs":"outputs"]:lt(i,d,-1)}}),children:(0,nt.jsx)(a,{size:14})}),(0,nt.jsx)(O,{label:`Move ${e} ${t.label} down`,size:"compact",disabled:d===i.length-1,onClick:()=>s(t=>t&&{...t,boundary:{...t.boundary,["input"===e?"inputs":"outputs"]:lt(i,d,1)}}),children:(0,nt.jsx)(u,{size:14})}),(0,nt.jsx)(O,{label:`Remove ${e} ${t.label}`,size:"compact",onClick:()=>s(a=>a&&{...a,boundary:{...a.boundary,["input"===e?"inputs":"outputs"]:i.filter(e=>e.portId!==t.portId)}}),children:(0,nt.jsx)(n,{size:14})})]})]}),"input"===e?(0,nt.jsx)(ct,{entryKey:r,valueType:t.valueType,primary:{nodeId:t.binding.nodeId,fieldId:t.binding.fieldOrPortId},mirrors:l,options:b,selected:p[r]??"",onSelected:e=>f(t=>({...t,[r]:e})),onAdd:e=>{s(a=>a&&{...a,boundary:{...a.boundary,inputs:a.boundary.inputs.map(a=>a.portId===t.portId?{...a,mirrorBindings:[...a.mirrorBindings??[],{nodeId:e.nodeId,fieldOrPortId:e.fieldId}].sort(st)}:a)}}),f(e=>({...e,[r]:""}))},onRemove:e=>s(a=>a&&{...a,boundary:{...a.boundary,inputs:a.boundary.inputs.map(a=>{if(a.portId!==t.portId)return a;let o=(a.mirrorBindings??[]).filter(t=>t.nodeId!==e.nodeId||t.fieldOrPortId!==e.fieldId),n={...a};return delete n.mirrorBindings,o.length?{...n,mirrorBindings:o}:n})}})}):null]},t.portId)}),(0,nt.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2",children:[(0,nt.jsx)(Ge,{value:l[e],onValueChange:t=>c(a=>({...a,[e]:t})),options:o.map(e=>({value:e.key,label:e.label})),placeholder:`Choose an internal ${e} field`}),(0,nt.jsx)(O,{label:`Add public ${e}`,size:"compact",disabled:!l[e],onClick:()=>{let t=o.find(t=>t.key===l[e]);if(!t)return;let a={portId:pt(e,t,r),label:t.label,valueType:t.valueType,required:!1,binding:{nodeId:t.nodeId,fieldOrPortId:t.fieldId}};s(t=>t&&{...t,boundary:{...t.boundary,["input"===e?"inputs":"outputs"]:[...i,a]}}),c(t=>({...t,[e]:""}))},children:(0,nt.jsx)(m,{size:14})})]})]},e)}),o?(0,nt.jsx)(Ke,{snapshot:t,subtreeId:o,previews:r.previews??[],onChange:e=>s(t=>t&&{...t,previews:e})}):null,(0,nt.jsxs)("section",{className:"grid gap-2",children:[(0,nt.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Editable controls"}),r.controls.map((e,t)=>{let o=`control:${e.controlId}`,i=e.mirrorBindings??[];return(0,nt.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:[(0,nt.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2 [&>input]:col-span-2",children:[(0,nt.jsx)(le,{"aria-label":`control ${e.controlId} label`,value:e.label,onChange:t=>{let a=t.currentTarget.value;s(t=>t&&{...t,controls:t.controls.map(t=>t.controlId===e.controlId?{...t,label:a}:t)})}}),(0,nt.jsx)(Ge,{value:rt(e.binding.nodeId,e.binding.fieldId),disabled:!!e.sealed,onValueChange:t=>{let a=I.find(e=>e.key===t);a&&(s(t=>t&&{...t,controls:t.controls.map(t=>t.controlId===e.controlId?(()=>{let e={...t};return delete e.mirrorBindings,{...e,valueType:a.valueType,binding:{nodeId:a.nodeId,fieldId:a.fieldId}}})():t)}),f(e=>({...e,[o]:""})))},options:I.map(e=>({value:e.key,label:e.label}))}),(0,nt.jsxs)("div",{className:"flex items-center gap-1",children:[(0,nt.jsx)(O,{label:`Move control ${e.label} up`,size:"compact",disabled:0===t,onClick:()=>s(e=>e&&{...e,controls:lt(e.controls,t,-1).map((e,t)=>({...e,order:t}))}),children:(0,nt.jsx)(a,{size:14})}),(0,nt.jsx)(O,{label:`Move control ${e.label} down`,size:"compact",disabled:t===r.controls.length-1,onClick:()=>s(e=>e&&{...e,controls:lt(e.controls,t,1).map((e,t)=>({...e,order:t}))}),children:(0,nt.jsx)(u,{size:14})}),(0,nt.jsx)(O,{label:`Remove control ${e.label}`,size:"compact",disabled:!!e.sealed,onClick:()=>s(t=>t&&{...t,controls:t.controls.filter(t=>t.controlId!==e.controlId).map((e,t)=>({...e,order:t}))}),children:(0,nt.jsx)(n,{size:14})})]})]}),(0,nt.jsx)(ct,{entryKey:o,valueType:e.valueType,primary:e.binding,mirrors:i,options:I,selected:p[o]??"",disabled:!!e.sealed,onSelected:e=>f(t=>({...t,[o]:e})),onAdd:t=>{s(a=>a&&{...a,controls:a.controls.map(a=>a.controlId===e.controlId?{...a,mirrorBindings:[...a.mirrorBindings??[],{nodeId:t.nodeId,fieldId:t.fieldId}].sort(st)}:a)}),f(e=>({...e,[o]:""}))},onRemove:t=>s(a=>a&&{...a,controls:a.controls.map(a=>{if(a.controlId!==e.controlId)return a;let o=(a.mirrorBindings??[]).filter(e=>e.nodeId!==t.nodeId||e.fieldId!==t.fieldId),n={...a};return delete n.mirrorBindings,o.length?{...n,mirrorBindings:o}:n})})})]},e.controlId)}),(0,nt.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2",children:[(0,nt.jsx)(Ge,{value:l.control,onValueChange:e=>c(t=>({...t,control:e})),options:I.map(e=>({value:e.key,label:e.label})),placeholder:"Choose an internal parameter"}),(0,nt.jsx)(O,{label:"Add editable control",size:"compact",disabled:!l.control,onClick:()=>{let e=I.find(e=>e.key===l.control);if(!e)return;let a=pt("control",e,r),o=We(t,e.nodeId,e.fieldId);s(t=>t&&{...t,controls:[...t.controls,{controlId:a,label:e.label,binding:{nodeId:e.nodeId,fieldId:e.fieldId},valueType:e.valueType,...void 0===o?{}:{defaultValue:o},order:t.controls.length}]}),c(e=>({...e,control:""}))},children:(0,nt.jsx)(m,{size:14})})]})]})]}):null})}var mt=e({default:()=>ht});function ht({nodeId:e,onClose:t,context:a}){let[o,n]=(0,ot.useState)(null),i=(0,ot.useRef)(!1),d=ke(t=>t.nodes.find(t=>t.id===e)),r=d?.data.blockInstanceV2?e:d?.data.blockProjectionOwnerId,s=ke(e=>e.nodes.find(e=>e.id===r)?.data.blockInstanceV2),l=!(!d||d.data.blockInstanceV2),[c,u]=(0,ot.useState)(()=>{let e=He.getState().workflowTabs.find(e=>e.id===a.workflowTabId)?.title||"Workflow";return M(d?.data.label||s?.definitionSnapshot.displayName||"User Node",e)}),p=!c.trim()||c.trim().length>512,f=s?Z(s.definitionSnapshot,s,{editWorkflow:!0,configureInterfaces:!0,createUserDefinitions:!0,updateUserDefinitions:!0}):null;async function m(o){if(!i.current){i.current=!0,n(o);try{if("workflow"!==o&&p)throw Error("Enter a User Node name (up to 512 characters).");let n=await R({nodeId:e,choice:o,context:a,displayName:c.trim()});Q("workflow"===o?"Changes kept only in this workflow.":`${"new"===o?"Saved new":"Updated"} User Node: ${n.definition?.displayName??"User Node"}`,{variant:"success",autoHideDuration:3200}),t()}catch(e){Q(e instanceof Error?e.message:"Could not save the Block changes.",{variant:"error",autoHideDuration:6e3})}finally{i.current=!1,n(null)}}}return(0,nt.jsx)(De,{onClose:()=>{i.current||t()},title:"Save block changes",testId:`save-user-block-choices-${e}`,footer:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Y,{disabled:!!o,onClick:t,children:"Cancel"}),(0,nt.jsx)(Y,{disabled:!!o||!f?.keepWorkflowOnly,loading:"workflow"===o,onClick:()=>{m("workflow")},children:"Keep only in this workflow"}),f?.saveAsNewUserNode?(0,nt.jsx)(Y,{tone:"primary",disabled:!!o||p,loading:"new"===o,onClick:()=>{m("new")},children:"Save as new User Node"}):null,!l&&f?.updateReusableDefinition?(0,nt.jsx)(Y,{disabled:!!o||p,loading:"update"===o,onClick:()=>{m("update")},children:"Update existing User Node"}):null]}),children:(0,nt.jsxs)("div",{className:"grid gap-2 text-sm text-modiff-subtle-text",children:[(0,nt.jsx)("p",{className:"font-semibold text-modiff-text",children:d?.data.label||"Block"}),(0,nt.jsx)(G,{label:"User Node name",required:!0,error:p?"Enter a name.":void 0,children:(0,nt.jsx)(le,{value:c,onChange:e=>u(e.currentTarget.value),maxLength:512,disabled:!!o,"aria-invalid":p,autoFocus:!0})}),(0,nt.jsx)("p",{children:"Save a reusable copy with these settings and the inputs and outputs you configured."}),(0,nt.jsx)("p",{children:l?"Only this Block and its contents are saved. Outside nodes and their connections stay in this workflow.":"Outside nodes and their connections stay in this workflow. Temporary connected sockets are not added to the saved User Node."})]})})}var gt=e({default:()=>bt});function bt({pendingBlock:e,setPendingBlock:t,handleConfirmBlockCreation:a}){return(0,nt.jsx)(X,{open:!!e,onClose:()=>t(null),title:"Create block",testId:"create-user-block-dialog",panelClassName:"max-w-xl",footer:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Y,{onClick:()=>t(null),children:"Cancel"}),(0,nt.jsx)(Y,{tone:"primary",disabled:!e?.name.trim(),onClick:()=>{a()},"data-testid":"confirm-create-user-block",children:"Create block"})]}),children:e?(0,nt.jsxs)("div",{className:"grid gap-4",children:[(0,nt.jsx)(G,{label:"Name",required:!0,children:(0,nt.jsx)(le,{autoFocus:!0,value:e.name,onChange:e=>{let a=e.currentTarget.value;t(e=>e&&{...e,name:a})}})}),e.result.block.inputs.length>0?(0,nt.jsxs)("section",{className:"grid gap-2",children:[(0,nt.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Inputs"}),e.result.block.inputs.map(a=>(0,nt.jsx)(G,{label:a.label,children:(0,nt.jsx)(le,{value:e.inputLabels[a.id]??a.label,onChange:e=>{let o=e.currentTarget.value;t(e=>e&&{...e,inputLabels:{...e.inputLabels,[a.id]:o}})}})},a.id))]}):null,e.result.block.outputs.length>0?(0,nt.jsxs)("section",{className:"grid gap-2",children:[(0,nt.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Outputs"}),e.result.block.outputs.map(a=>(0,nt.jsx)(G,{label:a.label,children:(0,nt.jsx)(le,{value:e.outputLabels[a.id]??a.label,onChange:e=>{let o=e.currentTarget.value;t(e=>e&&{...e,outputLabels:{...e.outputLabels,[a.id]:o}})}})},a.id))]}):null,e.result.block.exposedParams.length>0?(0,nt.jsxs)("section",{className:"grid gap-2",children:[(0,nt.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Editable parameters"}),e.result.block.exposedParams.map(a=>(0,nt.jsx)(H,{label:a.label,checked:e.exposedParamIds.has(a.id),onCheckedChange:e=>t(t=>{if(!t)return t;let o=new Set(t.exposedParamIds);return e?o.add(a.id):o.delete(a.id),{...t,exposedParamIds:o}})},a.id))]}):null]}):null})}var xt=e({buildGraphFixPlan:()=>zt,buildGraphFixPreview:()=>Dt,graphFixHighlightedSockets:()=>Ut,graphNodeIsOutputLike:()=>Pt,graphSocketTypesAreCompatible:()=>wt}),It=new Set(["image","video","audio"]),kt=new Set(["bool","float","int","number","str","string","text"]);function vt(e,t){return e.map(e=>{if(Ne(e))return e;let a=t[function(e){return`${e.data.module}.${e.data.action}`}(e)];if(!a)return e;let o=Object.fromEntries(Object.entries(e.data.params).map(([e,t])=>{let o=a.params[e];return o?[e,{...t,type:o.type??t.type,required:o.required??t.required}]:[e,t]}));return{...e,data:{...e.data,params:o}}})}function yt(e){let t=Array.isArray(e?.type)?e?.type:[e?.type??"any"];return Array.from(new Set(t.map(e=>String(e||"any").trim().toLowerCase()).filter(Boolean).map(e=>"integer"===e?"int":e)))}function wt(e,t){let a=yt(e),o=yt(t);return!(!a.includes("any")&&!o.includes("any"))||a.some(e=>o.includes(e))}function jt(e){return Object.entries(se(e)).filter(([,e])=>"input"===e.display||e.isInput)}function Nt(e){return Object.entries(se(e)).filter(([,e])=>"output"===e.display)}function $t(e){return null!=e&&("string"==typeof e?e.trim().length>0:Array.isArray(e)?e.length>0:"object"!=typeof e||("value"in e?$t(e.value):Object.keys(e).length>0))}function Ct(e,t,a=!1){if(t.disabled||t.spawn||$t(t.value??t.default)||!1===t.required)return!1;if(!0===t.required)return!0;if(a)return!1;let o=`${e} ${t.label??""} ${t.description??""}`.toLowerCase();return!/\boptional\b|\bif provided\b|\bwhen supplied\b/.test(o)}function St(e){return e.filter(e=>"group"!==e.data.type&&"loop"!==e.data.type&&!e.data.uiState?.disabled&&!!e.data.module&&!!e.data.action)}function Pt(e){let t=`${e.data.module} ${e.data.action} ${e.data.label} ${e.data.category}`.toLowerCase();return/\b(preview|export|save|display|output|gallery)\b|viewer\b/.test(t)}function _t(e){if(!Ne(e)||!e.data.blockInstanceV2)return!1;let t=e.data.blockInstanceV2.effectiveGraph,a=new Set(t.nodes.filter(({data:e})=>!e.uiState?.disabled).map(({nodeId:e})=>e));return t.nodes.some(e=>a.has(e.nodeId)&&Pt({data:e.data})&&t.edges.some(t=>t.targetNodeId===e.nodeId&&a.has(t.sourceNodeId)))}function Tt(e,t){return Oe(e,t)?.label||t.replace(/_/g," ")}function Mt(e){let t=e.split(".");return t[t.length-1]||e}function Ot(e,t,a){let o=new Map;e.forEach(e=>o.set(e.source,[...o.get(e.source)??[],e.target]));let n=[t],i=new Set;for(;n.length;){let e=n.shift();if(e&&!i.has(e)){if(e===a)return!0;i.add(e),n.push(...o.get(e)??[])}}return!1}function At(...e){return e.filter(Boolean).join(":").replace(/[^a-z0-9:_-]+/gi,"-").toLowerCase()}function Et(e){let t=yt(e);return t.some(e=>It.has(e))?80:t.some(e=>e.includes("pipeline"))?70:t.some(e=>kt.has(e))?10:t.includes("any")?0:40}function Ht(e,t){let a=yt(e),o=yt(t);return a.some(e=>"any"!==e&&o.includes(e))?100:a.includes("any")||o.includes("any")?5:0}function Rt(e,t){let a=0;return e.data.module===t.data.module&&(a+=24),e.data.category===t.data.category&&(a+=12),a}function Bt(e,t,a){return"output"===e&&t?{x:t.position.x+340,y:t.position.y}:"producer"===e&&a?{x:a.position.x-340,y:a.position.y}:t&&a?{x:Math.round((t.position.x+a.position.x)/2),y:Math.round((t.position.y+a.position.y)/2+140)}:{x:120,y:100}}function Vt(e,t){return(e.data.blockProjectionOwnerId??null)===(t.data.blockProjectionOwnerId??null)}function zt(e){e={...e,nodes:vt(e.nodes,e.registry)};let t=[],a=new Map(e.nodes.map(e=>[e.id,e])),o=new Set;for(let a of e.nodes){if(!Ne(a)||!a.data.blockInstanceV2)continue;for(let o of Ee(a.data.blockInstanceV2,e.modularBlockDefinitions??[])){let n=e.nodes.find(({data:e,hidden:t})=>!t&&e.blockProjectionOwnerId===a.id&&e.blockProjectionNodeId===o.nodeId)?.id??a.id,i=At("block-loop",a.id,o.nodeId,o.fieldId),d=oe(a.data.blockInstanceV2,o.nodeId,e.modularBlockDefinitions??[]);t.push({id:i,kind:"block_structure",title:"Invalid loop connection",description:o.message,targetNodeId:n,targetHandle:o.fieldId,candidates:d?[{id:At(i,"restore-one-edge"),issueId:i,title:"Reconnect the missing Loop Members link",description:`Restore only ${d.sourceNodeId} → ${d.targetNodeId}; keep all other edits.`,confidence:"choice",targetNodeId:n,operations:[{kind:"repair_block_loop",rootNodeId:a.id,targetNodeId:o.nodeId,graphHash:a.data.blockInstanceV2.effectiveGraph.graphHash}]}]:[]})}for(let o of je(a.data.blockInstanceV2,e.modularBlockDefinitions??[])){let n=e.nodes.find(({data:e,hidden:t})=>!t&&e.blockProjectionOwnerId===a.id&&e.blockProjectionNodeId===o.nodeId)?.id??a.id,i=At("block-state-input",a.id,o.nodeId);t.push({id:i,kind:"missing_input",title:"Missing upstream inputs",description:o.message,targetNodeId:n,candidates:o.reconnectFrom.map(e=>({id:At(i,e),issueId:i,title:`Reconnect Pipeline State from ${a.data.blockInstanceV2.effectiveGraph.nodes.find(t=>t.nodeId===e)?.data.label??e}`,description:"Reconnect one edge; keep all other edits.",confidence:"choice",targetNodeId:n,operations:[{kind:"repair_block_state",rootNodeId:a.id,sourceNodeId:e,targetNodeId:o.nodeId,graphHash:a.data.blockInstanceV2.effectiveGraph.graphHash}]}))})}let o=ce(a.data.blockInstanceV2,e.modularBlockDefinitions??[]).filter(e=>e.canRepair);if(o.length){let e=At("block-derived-control",a.id),n=[...new Set(o.map(({fieldId:e})=>e))].join(", "),i=`Repair ${o.length} bindings (${n}) to inherit derived values. Preserve prompts, requested values, layout and other wires.`;t.push({id:e,kind:"block_derived_control",title:"A mirrored control overwrites an upstream-derived value",description:i,targetNodeId:a.id,candidates:[{id:At(e,"inherit-derived-state"),issueId:e,title:"Use the upstream-derived values",description:i,confidence:"choice",targetNodeId:a.id,operations:o.map(({nodeId:e,fieldId:t})=>({kind:"repair_block_derived_control",rootNodeId:a.id,sourceNodeId:e,fieldId:t}))}]})}for(let o of Te(a.data.blockInstanceV2,e.modularBlockDefinitions??[])){if(!o.canRepair)continue;let n=e.nodes.find(({data:e,hidden:t})=>!t&&e.blockProjectionOwnerId===a.id&&e.blockProjectionNodeId===o.nodeId)?.id??a.id,i=At("block-seed",a.id,o.nodeId);t.push({id:i,kind:"block_seed",title:"A saved seed is attached to a step that does not consume it",description:o.reason,targetNodeId:n,candidates:[{id:At(i,"move-to-generator"),issueId:i,title:"Connect seed to its Generator consumer",description:o.reason,confidence:"choice",targetNodeId:n,operations:[{kind:"repair_block_seed",rootNodeId:a.id,sourceNodeId:o.nodeId}]}]})}}e.nodes.forEach(a=>{let o=function(e){if(!Ne(e))return null;try{return me([e],[]),null}catch(e){return e instanceof Error?e.message:String(e)}}(a);if(!o)return;let n=function(e,t,a){return a.filter(({data:t})=>t.blockProjectionOwnerId===e.id&&"string"==typeof t.blockProjectionNodeId).sort((e,t)=>String(t.data.blockProjectionNodeId).length-String(e.data.blockProjectionNodeId).length).find(({id:e,data:a})=>{let o=String(a.blockProjectionNodeId);return t.includes(e)||t.includes(`"${o}"`)||t.includes(`'${o}'`)||t.includes(` ${o}:`)||t.includes(` ${o}.`)||t.endsWith(` ${o}`)})?.id??e.id}(a,o,e.nodes),i=At("block-structure",a.id,o);t.push({id:i,kind:"block_structure",title:`${a.data.label||"Block"} has invalid internal structure`,description:o,targetNodeId:n,candidates:[{id:At(i,"restore-reviewed"),issueId:i,title:"Restore reviewed internal structure",description:"Restore the registered nodes and links for this workflow instance. Compatible custom additions are retained; incompatible structural edits are removed.",confidence:"choice",targetNodeId:n,operations:[{kind:"restore_block_structure",rootNodeId:a.id}]}]})}),e.edges.forEach(n=>{let i=a.get(n.source),d=a.get(n.target),r=n.sourceHandle??"",s=n.targetHandle??"",l=Oe(i,r),c=Oe(d,s);if(!(i&&d&&r&&s&&l&&c)){let e=At("broken-link",n.id);return void t.push({id:e,kind:"broken_link",title:"Remove a broken connection",description:"One connection points to a node or socket that no longer exists.",targetNodeId:d?.id,targetHandle:s||void 0,candidates:[{id:At(e,"remove"),issueId:e,title:"Remove broken link",description:"Delete only the invalid connection.",confidence:"safe",targetNodeId:d?.id,targetHandle:s||void 0,operations:[{kind:"remove_edges",edgeIds:[n.id]}]}]})}if(!wt(l,c)){let a=At("type-mismatch",n.id),o=function(e,t,a,o,n,i,d,r,s){return a.data.blockProjectionOwnerId||i.data.blockProjectionOwnerId?[]:Object.entries(t.registry).flatMap(([e,t])=>jt({data:t}).flatMap(([a,o])=>Nt({data:t}).map(([n,i])=>({key:e,data:t,bridgeInput:a,bridgeInputParam:o,bridgeOutput:n,bridgeOutputParam:i})))).filter(({data:e,bridgeInputParam:t,bridgeOutputParam:a})=>"group"!==e.type&&"loop"!==e.type&&wt(n,t)&&wt(a,r)).map(({key:t,data:l,bridgeInput:c,bridgeInputParam:u,bridgeOutput:p,bridgeOutputParam:f})=>{let m=`@${At(e,t,c,p)}`;return{score:Ht(n,u)+Ht(f,r)+Rt({data:l},i),candidate:{id:At(e,"bridge",t,c,p),issueId:e,title:`Insert ${l.label||Mt(t)}`,description:`Replace the incompatible link with a typed conversion through ${l.label||"this node"}.`,confidence:"choice",targetNodeId:i.id,targetHandle:d,operations:[{kind:"remove_edges",edgeIds:[s]},{kind:"add_node",ref:m,nodeKey:t,position:Bt("bridge",a,i)},{kind:"connect",source:{nodeId:a.id,handle:o},target:{nodeId:m,handle:c}},{kind:"connect",source:{nodeId:m,handle:p},target:{nodeId:i.id,handle:d}}]}}}).filter(e=>e.score>30).sort((e,t)=>t.score-e.score).slice(0,3).map(e=>e.candidate)}(a,e,i,r,l,d,s,c,n.id);return o.push({id:At(a,"remove"),issueId:a,title:"Remove incompatible link",description:"Disconnect the mismatched sockets and leave the input highlighted for another fix.",confidence:"safe",targetNodeId:d.id,targetHandle:s,operations:[{kind:"remove_edges",edgeIds:[n.id]}]}),void t.push({id:a,kind:"type_mismatch",title:`${Tt(i,r)} cannot connect to ${Tt(d,s)}`,description:`The sockets use incompatible types: ${yt(l).join(" or ")} and ${yt(c).join(" or ")}.`,targetNodeId:d.id,targetHandle:s,candidates:o.slice(0,3)})}o.add(`${d.id}:${s}`)});let n=St(e.nodes),i=n.filter(Pt),d=i.filter(t=>e.edges.some(e=>e.target===t.id&&a.has(e.source)));if(n.length>0&&0===d.length&&!n.some(_t)){let a="missing-output",o=function(e,t,a){let o=St(t.nodes),n=new Set(t.edges.map(e=>`${e.source}:${e.sourceHandle??""}`)),i=o.flatMap(e=>Nt(e).map(([t,a])=>({source:e,sourceHandle:t,sourceParam:a}))).filter(({source:e,sourceHandle:t})=>!n.has(`${e.id}:${t}`)).sort((e,t)=>Et(t.sourceParam)-Et(e.sourceParam)),d=a.flatMap(a=>jt(a).flatMap(([o,n])=>i.filter(({source:e,sourceParam:o})=>e.id!==a.id&&Vt(e,a)&&wt(o,n)&&!Ot(t.edges,a.id,e.id)).map(({source:t,sourceHandle:i,sourceParam:d})=>({score:Ht(d,n)+Et(d),candidate:{id:At(e,"connect-output",t.id,i,a.id,o),issueId:e,title:`Connect ${t.data.label||t.data.action} to ${a.data.label||a.data.action}`,description:`Send ${Tt(t,i)} to the existing output node.`,confidence:"safe",targetNodeId:a.id,targetHandle:o,operations:[{kind:"connect",source:{nodeId:t.id,handle:i},target:{nodeId:a.id,handle:o}}]}}))));return d.length?d.sort((e,t)=>t.score-e.score).slice(0,3).map(e=>e.candidate):Object.entries(t.registry).filter(([,e])=>Pt({data:e})).flatMap(([t,a])=>jt({data:a}).flatMap(([o,n])=>i.filter(({source:e,sourceParam:t})=>!e.data.blockProjectionOwnerId&&wt(t,n)).map(({source:i,sourceHandle:d,sourceParam:r})=>{let s=`@${At(e,t,i.id)}`;return{score:Ht(r,n)+Et(r),candidate:{id:At(e,"add-output",t,i.id,d,o),issueId:e,title:`Add ${a.label||Mt(t)}`,description:`Create a visible output for ${Tt(i,d)}.`,confidence:"choice",targetNodeId:i.id,targetHandle:d,operations:[{kind:"add_node",ref:s,nodeKey:t,position:Bt("output",i,void 0)},{kind:"connect",source:{nodeId:i.id,handle:d},target:{nodeId:s,handle:o}}]}}}))).sort((e,t)=>t.score-e.score||e.candidate.title.localeCompare(t.candidate.title)).slice(0,3).map(e=>e.candidate)}(a,e,i);o.length&&t.push({id:a,kind:"missing_output",title:i.length?"Connect the graph output":"Add a graph output",description:i.length?"An output node exists, but no generated result reaches it.":"The graph needs a Preview, Save, or Export node to produce a usable result.",targetNodeId:i[0]?.id,candidates:o})}let r=new Set(i.map(e=>e.id));n.forEach(a=>{jt(a).forEach(([n,i])=>{if(function(e,t){let a=e.data.blockInstanceV2;if(!Ne(e)||!a)return!1;let o=a.effectiveInterface.boundary.inputs.find(e=>e.portId===t);if(!o)return!1;let n=[o.binding,...o.mirrorBindings??[]];return a.effectiveInterface.controls.some(e=>[e.binding,...e.mirrorBindings??[]].some(e=>n.some(t=>t.nodeId===e.nodeId&&t.fieldOrPortId===e.fieldId)))}(a,n)||!Ct(n,i,!!a.data.skipParamsCheck)||o.has(`${a.id}:${n}`)||r.has(a.id)&&0===d.length)return;let s=At("missing-input",a.id,n),l=function(e,t,a,o,n){let i=function(e,t,a,o,n){return St(t.nodes).flatMap(e=>Nt(e).map(([t,a])=>({source:e,sourceHandle:t,sourceParam:a}))).filter(({source:e,sourceParam:o})=>e.id!==a.id&&Vt(e,a)&&wt(o,n)&&!Ot(t.edges,a.id,e.id)).map(({source:t,sourceHandle:i,sourceParam:d})=>({score:Ht(d,n)+Rt(t,a)-Math.min(40,Math.hypot(t.position.x-a.position.x,t.position.y-a.position.y)/50),candidate:{id:At(e,"connect",t.id,i),issueId:e,title:`Connect ${t.data.label||t.data.action}`,description:`Use its ${Tt(t,i)} output for ${Tt(a,o)}.`,confidence:"safe",targetNodeId:a.id,targetHandle:o,operations:[{kind:"connect",source:{nodeId:t.id,handle:i},target:{nodeId:a.id,handle:o}}]}})).sort((e,t)=>t.score-e.score).map(e=>e.candidate)}(e,t,a,o,n),d=function(e,t,a,o,n){return a.data.blockProjectionOwnerId?[]:Object.entries(t.registry).flatMap(([e,t])=>Nt({data:t}).map(([a,o])=>({key:e,data:t,sourceHandle:a,sourceParam:o}))).filter(({data:e,sourceParam:t})=>!("group"===e.type||"loop"===e.type||!wt(t,n)||Pt({data:e}))).map(({key:t,data:i,sourceHandle:d,sourceParam:r})=>{let s=`${t} ${i.label} ${i.category}`.toLowerCase(),l=yt(n),c=l.some(e=>e.includes("pipeline"))&&/load|pipeline/.test(s)?50:0,u=l.some(e=>It.has(e))&&/load|source|import/.test(s)?35:0,p=yt(r).includes("any")?45:0,f=`@${At(e,t)}`;return{score:Ht(r,n)+Rt({data:i},a)+c+u-p,candidate:{id:At(e,"add",t,d),issueId:e,title:`Add ${i.label||Mt(t)}`,description:`Add this compatible source and connect it to ${Tt(a,o)}.`,confidence:"choice",targetNodeId:a.id,targetHandle:o,operations:[{kind:"add_node",ref:f,nodeKey:t,position:Bt("producer",void 0,a)},{kind:"connect",source:{nodeId:f,handle:d},target:{nodeId:a.id,handle:o}}]}}}).filter(e=>e.score>15).sort((e,t)=>t.score-e.score||e.candidate.title.localeCompare(t.candidate.title)).map(e=>e.candidate)}(e,t,a,o,n);return[...i,...d].slice(0,3)}(s,e,a,n,i);l.length&&t.push({id:s,kind:"missing_input",title:`${a.data.label||a.data.action} needs ${Tt(a,n)}`,description:"Connect an existing compatible value or add a matching source node.",targetNodeId:a.id,targetHandle:n,candidates:l})})});let s=new Set;(e.readinessIssues??[]).forEach(e=>{if("block_media_input_missing"===e.code){let a=At(e.code,e.nodeId,e.fieldId);return void t.push({id:a,kind:"missing_media",title:e.message,description:e.details??e.message,targetNodeId:e.nodeId,targetHandle:e.fieldId,candidates:[{id:At(a,"inspect"),issueId:a,title:"Open required input",description:"Select the affected node and its controls. Choose the file yourself; no mask, prompt or connection is changed automatically.",confidence:"safe",targetNodeId:e.nodeId,targetHandle:e.fieldId,operations:[{kind:"external",action:"inspect_node",nodeId:e.nodeId}]}]})}if("modular_component_requirement"!==e.code&&"modular_runtime_failure"!==e.code)if(["environment","package","backend","hardware_fit"].includes(e.category)&&"open_setup"===e.action){let a=`${e.category}:${e.message}`;if(s.has(a))return;s.add(a);let o=At(e.category,e.message),n="package"===e.category?"Repair backend packages":"backend"===e.category?"Reconnect or update the backend":"hardware_fit"===e.category?"Review hardware compatibility":"Repair the runtime environment";t.push({id:o,kind:"environment",title:n,description:e.message,candidates:[{id:At(o,"setup"),issueId:o,title:"Open Setup",description:e.details||"Review the detected runtime mismatch and its exact repair command.",confidence:"safe",operations:[{kind:"external",action:"open_setup"}]}]})}else if("model"!==e.category&&"model_integrity"!==e.category||!e.repoId&&!e.modelPath){if("asset"===e.category&&"select_image"===e.action){let a=`missing_media:${e.nodeId??""}:${e.message}`;if(s.has(a))return;s.add(a);let o=At("missing-media",e.nodeId,e.message);t.push({id:o,kind:"missing_media",title:"Choose the required media",description:e.message,targetNodeId:e.nodeId,candidates:[{id:At(o,"assets"),issueId:o,title:"Open Gallery",description:"Choose an imported image, video, or audio file without changing the graph automatically.",confidence:"safe",targetNodeId:e.nodeId,operations:[{kind:"external",action:"open_assets",nodeId:e.nodeId}]}]})}}else{let a=`missing_model:${e.nodeId??""}:${e.repoId??e.modelPath}`;if(s.has(a))return;s.add(a);let o=At("missing-model",e.nodeId,e.repoId,e.modelPath);t.push({id:o,kind:"missing_model",title:"Choose or install the required model",description:e.message,targetNodeId:e.nodeId,candidates:[{id:At(o,"models"),issueId:o,title:"Open Models",description:"Review compatible installed models or explicitly install the required model.",confidence:"safe",targetNodeId:e.nodeId,operations:[{kind:"external",action:"open_model_manager",nodeId:e.nodeId,repoId:e.repoId}]}]})}else t.push({id:At(e.code,e.nodeId??"",e.message),kind:"block_structure",title:"modular_runtime_failure"===e.code?"Review the failed Modular block":"Review component compatibility",description:`${e.message} ${e.details??""}`,targetNodeId:e.nodeId,candidates:[]})});let l=t.filter(e=>e.candidates.length>0);return{issues:t,candidateCount:l.reduce((e,t)=>e+t.candidates.length,0),canFix:l.length>0}}function Lt(e,t){return t.get(e)??e}function Dt(e,t){if(!t)return{nodes:[],edges:[]};let a=new Map,o=[];t.operations.forEach(n=>{if("add_node"!==n.kind)return;let i=J(n.nodeKey,e.registry,n.position);if(!i)return;let d=`graph-fix-preview-${t.id}-${a.size}`,r={...i,id:d,className:"modiff-graph-fix-ghost",draggable:!1,selectable:!1,deletable:!1};r.data.label=`${r.data.label} preview`,a.set(n.ref,d),o.push(r)});let n=[...e.nodes,...o];return{nodes:o,edges:t.operations.flatMap((e,o)=>{if("connect"!==e.kind)return[];let i=Lt(e.source.nodeId,a),d=Lt(e.target.nodeId,a);return n.some(e=>e.id===i)&&n.some(e=>e.id===d)?[{id:`graph-fix-preview-edge-${t.id}-${o}`,source:i,sourceHandle:e.source.handle,target:d,targetHandle:e.target.handle,type:"default",selectable:!1,focusable:!1,className:"modiff-graph-fix-ghost"}]:[]})}}function Ut(e){return e?e.operations.flatMap(e=>"connect"===e.kind?[e.source,e.target]:[]):[]}var qt=e({default:()=>Gt});function Ft(e,t,a){return e.issues.find(e=>e.id===t)?.candidates.find(e=>e.id===a)}function Gt(){let e=P(e=>e.dialogOpen),t=P(e=>e.closeDialog),a=P(e=>e.setPreviewCandidate),o=P(e=>e.setIssueTargets),{nodes:n,edges:i}=ke(S(e=>({nodes:e.nodes,edges:e.edges}))),r=we(e=>e.nodesRegistry),s=T(e=>e.library?.blockDefinitions);(0,ot.useEffect)(()=>{e&&!T.getState().loaded&&T.getState().fetchLibrary()},[e]);let l=Ae(e=>e.edgeType),u=Ae(e=>e.setLeftPanelOpen),p=Ae(e=>e.setLeftPanelTabIndex),f=Ae(e=>e.setRightPanelOpen),m=Ae(e=>e.setRightPanelTab),h=Ae(e=>e.setModelManagerOpener),g=He(e=>e.saveActiveWorkflowTab),b=He(e=>e.graphBinding),x=Ie(e=>e.sid),I=Ie(e=>e.isConnected),k=C({sid:x,isConnected:I,includeStudio:!!b}),v=(0,ot.useMemo)(()=>zt({nodes:n,edges:i,registry:r,modularBlockDefinitions:s,readinessIssues:k.issues}),[i,n,k.issues,r,s]),y=v.issues.map(e=>`${e.id}:${e.candidates.map(e=>e.id).join(",")}`).join("|"),j=(0,ot.useRef)(v);j.current=v;let[N,$]=(0,ot.useState)({}),[_,M]=(0,ot.useState)(!1);(0,ot.useEffect)(()=>{if(!e)return;let t=j.current,n=function(e){let t=e.issues.map(e=>({issue:e,candidate:e.candidates.find(e=>"safe"===e.confidence&&e.operations.length>0&&e.operations.every(e=>"external"===e.kind))})).find(e=>e.candidate);return Object.fromEntries(e.issues.map(e=>[e.id,t?e.id===t.issue.id?t.candidate?.id??"":"":e.candidates.find(e=>"safe"===e.confidence)?.id??""]))}(t);$(n);let i=t.issues[0],d=i?Ft(t,i.id,n[i.id]??""):null;a(d??null),o(t.issues.flatMap(e=>e.targetNodeId?[{nodeId:e.targetNodeId,handle:e.targetHandle}]:[]))},[e,y,o,a]);let O=(e,t={})=>{if("inspect_node"===e){let e=Ae.getState();f(!0),m("studio"),e.setStudioViewMode("expert");let a=ke.getState();if(t.nodeId&&a.nodes.some(e=>e.id===t.nodeId)){a.onNodesChange(a.nodes.map(e=>({id:e.id,type:"select",selected:e.id===t.nodeId})));let o=He.getState().activeWorkflowTabId;o&&e.setWorkflowFocusRequest({workflowTabId:o,nodeId:t.nodeId,requestId:Date.now(),requestedAt:Date.now()})}return}if("open_setup"===e)return f(!0),void m("setup");u(!0),"open_assets"!==e?(p(3),h({nodeId:t.nodeId??null,fieldKey:null,focus:t.repoId?{repo:t.repoId,source:"graph"}:void 0})):p(2)},A=v.issues.map(e=>Ft(v,e.id,N[e.id]??"")).filter(e=>!!e),E=A.length>0&&A.every(e=>e.operations.every(e=>"external"===e.kind)),H=v.issues.filter(e=>!!N[e.id]).length,R=0===v.candidateCount;return(0,nt.jsx)(X,{open:e,onClose:t,title:"Fix graph",description:R?"Review the issue and correct the named node or connection.":"Review proposed repairs before applying them. Graph changes can be undone.",testId:"graph-fix-dialog",panelClassName:"max-w-2xl",footer:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Y,{onClick:t,children:"Cancel"}),!R&&(0,nt.jsx)(Y,{tone:"primary",icon:(0,nt.jsx)(c,{size:15}),disabled:0===H||_,loading:_,onClick:()=>{let e=A;if(e.length&&!_){M(!0);try{let a=e.filter(e=>e.operations.some(e=>"external"!==e.kind)),o=v.issues.filter(e=>a.some(t=>t.issueId===e.id)),n=ke.getState(),i=pe({nodes:n.nodes,edges:n.edges,registry:r,modularBlockDefinitions:s},e,l),d=a.length>0;d&&(ke.getState().replaceGraph({nodes:i.nodes,edges:i.edges},{historyLabel:"Fix graph",clearRemovedCache:!1}),g(!0));let c=w({sid:x,isConnected:I,includeStudio:!!b,showDialog:!1}),u=ke.getState(),p=zt({nodes:u.nodes,edges:u.edges,registry:r,modularBlockDefinitions:s,readinessIssues:c.issues}),f=d?o.filter(e=>p.issues.some(t=>function(e,t){return e.kind===t.kind&&(e.targetNodeId??null)===(t.targetNodeId??null)&&(e.targetHandle??null)===(t.targetHandle??null)}(e,t))):[];if(d&&f.length>0)throw Error(`The proposed change did not clear ${1===f.length?"the selected issue":`${f.length} selected issues`}. Review the updated graph before retrying.`);if(t(),i.externalActions.forEach(e=>O(e.action,{nodeId:e.nodeId,repoId:e.repoId})),d){let e=c.blocking.length;Q(`${1===a.length?"Fix":`${a.length} fixes`} applied and verified.${e>0?` ${e} other blocking ${1===e?"issue remains":"issues remain"}.`:""} Undo is available.`,{variant:"success",autoHideDuration:3600})}}catch(e){Q(e instanceof Error?e.message:"The graph changed before the fix could be applied.",{variant:"error",autoHideDuration:4200})}finally{M(!1)}}},"data-testid":"graph-fix-apply",children:E?A[0]?.title:"Apply "+(1===H?"fix":`${H} fixes`)})]}),children:0===v.issues.length?(0,nt.jsx)("div",{className:"rounded-modiff-panel border border-modiff-border bg-modiff-panel p-4 text-sm text-modiff-subtle-text",children:"No deterministic graph repair is currently available."}):(0,nt.jsxs)("div",{className:"grid gap-4",children:[(0,nt.jsx)("p",{className:"text-sm text-modiff-subtle-text",children:R?"No automatic repair is available. Your graph has not been changed.":E?"Review the detected issue and continue to the exact place where it can be resolved.":"Review the proposed connections. MoDiff will apply graph changes together as one undoable action."}),v.issues.map((e,t)=>(0,nt.jsxs)("section",{className:"rounded-modiff-panel border border-modiff-border bg-modiff-panel p-3","data-testid":`graph-fix-issue-${t}`,children:[(0,nt.jsxs)("div",{className:"flex items-start gap-2",children:[(0,nt.jsx)(d,{size:16,className:"mt-0.5 shrink-0 text-modiff-red"}),(0,nt.jsxs)("div",{className:"min-w-0",children:[(0,nt.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:e.title}),(0,nt.jsx)("p",{className:"mt-0.5 text-xs text-modiff-subtle-text",children:e.description})]}),N[e.id]&&(0,nt.jsx)(Y,{className:"ml-auto shrink-0","aria-label":`Skip ${e.title}`,onClick:()=>{$(t=>({...t,[e.id]:""})),a(null)},children:"Skip"})]}),0===e.candidates.length?(0,nt.jsx)("p",{className:"mt-3 text-xs text-modiff-subtle-text",children:"Manual correction required. No automatic graph change will be applied for this issue."}):(0,nt.jsx)(Pe,{className:"mt-3","aria-label":e.title,value:N[e.id]??"",options:e.candidates.map(e=>({value:e.id,label:e.title,description:e.description,meta:"safe"===e.confidence?"Direct":"Choice"})),onOptionPreview:t=>a(e.candidates.find(e=>e.id===t)??null),onValueChange:t=>{$(a=>({...a,[e.id]:t})),a(e.candidates.find(e=>e.id===t)??null)}})]},e.id))]})})}var Wt=(0,ot.memo)(e=>{let t=(0,ot.useRef)(null),a=(0,ot.useRef)(null),n=(0,ot.useRef)(null),d=ke(t=>ee(t,e.id)),r=e.data.userBlockSnapshot,c=ke(e=>e.toggleUserBlockExpanded),u=ke(e=>e.setParamWithHistory),f=ke(e=>e.replaceNodeParams),m=ke(e=>e.setNodeSize),h=ke(e=>e.beginHistoryTransaction),b=ke(e=>e.commitHistoryTransaction),x=ke(e=>e.setNodeUiState),k=ke(e=>e.configureUserBlock),v=He(e=>e.workflowTabs.find(t=>t.id===e.activeWorkflowTabId)?.title||"Workflow"),w=ke(e=>e.fitUserBlockToChildren),N=ke(t=>t.nodes.filter(t=>t.data.userBlockInstanceId===e.id).map(e=>`${e.id}:${e.position.x}:${e.position.y}:${e.measured?.width??e.width??0}:${e.measured?.height??e.height??0}`).sort().join("|")),C=g(),S=I(),P=$(e.id,t),_=(0,ot.useMemo)(()=>r?E(r,{x:0,y:0},e.id):null,[r,e.id]),M=_?.data.userBlockSnapshot??r,A=(0,ot.useMemo)(()=>{let t=_?.data.params;if(!t)return e.data.params;let a={...t};return Object.entries(e.data.params).forEach(([e,o])=>{let n=t[e];a[e]=n?{...n,...o,fieldOptions:{...n.fieldOptions??{},...o.fieldOptions??{}}}:o}),a},[_?.data.params,e.data.params]),R=(0,ot.useMemo)(()=>M?j(M,A):[],[M,A]),V=(0,ot.useMemo)(()=>M?F(M):[],[M]),[z,L]=(0,ot.useState)(null),[D,U]=(0,ot.useState)(!1),[q,J]=(0,ot.useState)(!1),[Z,oe]=(0,ot.useState)(!1),[ne,ie]=(0,ot.useState)(null),[re,se]=(0,ot.useState)(null),[ce,ue]=(0,ot.useState)(null),pe=T(e=>e.library),fe=(0,ot.useMemo)(()=>d?te(ke.getState(),e.id):{valid:!0,issues:[],insertionSuggestions:[]},[ke(t=>{if(!d)return"";let a=new Set(t.nodes.filter(t=>t.data.userBlockInstanceId===e.id).map(e=>e.id));return`${t.nodes.filter(e=>a.has(e.id)).map(e=>JSON.stringify([e.id,e.parentId,e.data.type,Object.entries(e.data.params??{}).map(([e,t])=>[e,t.type,t.display,t.isInput,t.hidden,"pipeline_class"===e||"workflow_id"===e?t.value??t.default:void 0])])).sort().join("|")}\n${t.edges.filter(e=>a.has(e.source)||a.has(e.target)).map(e=>`${e.id}:${e.source}:${e.sourceHandle??""}:${e.target}:${e.targetHandle??""}`).sort().join("|")}`}),d,e.id]),me=ke(e=>e.insertNodeInUserBlock),he=(0,ot.useMemo)(()=>W(M,pe),[M,pe]),ge=(0,ot.useCallback)((t,a,o)=>u(e.id,t,a,o),[e.id,u]),xe=(0,ot.useCallback)(()=>{c(e.id),He.getState().saveActiveWorkflowTab(!0)},[e.id,c]),Ie=(0,ot.useCallback)(async t=>{if(!Z){oe(!0);try{let a=await Je({instanceId:e.id,choice:t,workflowTitle:v});U(!1),Q("update"===t?"Reusable User Node updated. Existing workflow instances keep their embedded snapshots.":"new"===t?`Saved as new User Node: ${a.name}`:"Changes kept only in this workflow.",{variant:"success",autoHideDuration:3200})}catch(e){Q(e instanceof Error?e.message:"Could not save the User Node changes.",{variant:"error",autoHideDuration:4200})}finally{oe(!1)}}},[e.id,Z,v]),ve=(0,ot.useCallback)(()=>{let e=a.current?.offsetHeight??44,t=n.current?.offsetHeight??0;return Math.ceil(e+t+96+4)},[]),ye=(0,ot.useCallback)(()=>{if(d)return;let t=ve(),a=ke.getState().nodes.find(t=>t.id===e.id);!a||ee(ke.getState(),e.id)||(a.height??0)>=t||(ke.setState(a=>({nodes:a.nodes.map(a=>a.id===e.id?{...a,height:t,data:{...a.data,uiState:{...a.data.uiState,blockCollapsedHeight:t}}}:a)})),C(e.id),P())},[ve,d,e.id,P,C]);(0,ot.useEffect)(()=>{C(e.id)},[d,e.id,C]),(0,ot.useLayoutEffect)(()=>{if(d||(ye(),typeof ResizeObserver>"u"))return;let e=null,t=!0,o=new ResizeObserver(()=>{!t||null!==e||(e=window.requestAnimationFrame(()=>{e=null,t&&ye()}))});return a.current&&o.observe(a.current),n.current&&o.observe(n.current),()=>{t=!1,o.disconnect(),null!==e&&window.cancelAnimationFrame(e)}},[d,ye]),(0,ot.useEffect)(()=>{if(!d||!N)return;let t=window.requestAnimationFrame(()=>{w(e.id),P()});return()=>window.cancelAnimationFrame(t)},[N,d,w,e.id,P]),(0,ot.useEffect)(()=>{be(e.data.params,A)||f(e.id,A)},[A,e.data.params,e.id,f]);let we=(0,ot.useCallback)(a=>{a.preventDefault(),a.stopPropagation(),h("Resize block");let o=t.current?.clientWidth||e.width||340,n=t.current?.clientHeight||e.height||360,i=a.clientX,d=a.clientY,r=S.getState().transform[2]||1,s=ve(),l=o,c=n,u=t=>{l=Math.min(720,Math.max(280,o+(t.clientX-i)/r)),c=Math.max(s,n+(t.clientY-d)/r),m(e.id,Math.round(l),Math.round(c)),P()},p=()=>{document.removeEventListener("mousemove",u),document.removeEventListener("mouseup",p),x(e.id,{blockCollapsedWidth:Math.round(l),blockCollapsedHeight:Math.round(c)}),b(),P()};document.addEventListener("mousemove",u),document.addEventListener("mouseup",p)},[h,ve,b,e.height,e.id,e.width,S,P,m,x]);return(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Be,{nodeId:e.id,label:e.data.label||M?.name||"Block",expanded:d,nodeRef:t,headerRef:a,connectorRef:n,onResizeStart:we,actions:(0,nt.jsxs)(nt.Fragment,{children:[d?(0,nt.jsx)(O,{className:K("nodrag nowheel",fe.valid?"text-modiff-green":"text-modiff-red"),size:"compact",label:fe.valid?"Inspect User Node composition":`Inspect User Node composition: ${fe.issues.length} issue${1===fe.issues.length?"":"s"}`,onClick:()=>J(!0),"data-testid":`user-block-composition-${e.id}`,children:(0,nt.jsx)(o,{size:15})}):null,(0,nt.jsx)(O,{className:"nodrag nowheel",size:"compact",label:"Save User Node changes",onClick:()=>U(!0),"data-testid":`user-block-save-choices-${e.id}`,children:(0,nt.jsx)(p,{size:15})}),(0,nt.jsx)(O,{className:"nodrag nowheel",size:"compact",label:"Configure block instance",onClick:()=>{M&&L({name:M.name,inputLabels:Object.fromEntries(M.inputs.map(e=>[e.id,e.label])),outputLabels:Object.fromEntries(M.outputs.map(e=>[e.id,e.label])),exposedParamIds:new Set(M.exposedParams.map(e=>e.id))})},"data-testid":`user-block-configure-${e.id}`,children:(0,nt.jsx)(l,{size:15})}),(0,nt.jsx)(O,{className:"nodrag nowheel",size:"compact",label:d?"Collapse block":"Expand block",onClick:xe,"data-testid":`user-block-toggle-${e.id}`,children:d?(0,nt.jsx)(i,{size:16}):(0,nt.jsx)(s,{size:16})})]}),controls:(0,nt.jsx)("div",{className:"grid gap-2","data-testid":`user-block-disclosures-${e.id}`,children:R.map(t=>(0,nt.jsxs)("section",{"data-block-source-node":t.id,children:[Object.keys(t.controlParams).length>0?(0,nt.jsx)(ae,{label:t.label,"data-testid":`user-block-disclosure-${e.id}-${t.id}`,className:"rounded-modiff-compact border border-modiff-border-subtle bg-modiff-bg/60",buttonClassName:"min-h-8 text-xs",panelClassName:"grid gap-2 border-t border-modiff-border-subtle p-2",children:(0,nt.jsx)(y,{nodeId:e.id,params:t.controlParams,updateStore:ge,module:t.module,action:t.action,mode:"controls"})}):null,Object.keys(t.previewParams).length>0?(0,nt.jsxs)("div",{className:"mt-2 overflow-hidden rounded-modiff-compact border border-modiff-border-subtle bg-modiff-bg p-2","data-testid":`user-block-preview-${e.id}-${t.id}`,children:[(0,nt.jsx)("div",{className:"mb-2 truncate text-xs font-semibold text-modiff-subtle-text",children:t.label}),(0,nt.jsx)(y,{nodeId:e.id,params:t.previewParams,updateStore:ge,module:t.module,action:t.action,mode:"controls"})]}):null]},t.id))}),connectors:(0,nt.jsx)(y,{nodeId:e.id,params:A,updateStore:ge,module:e.data.module,action:e.data.action,mode:"connectors"})}),(0,nt.jsx)(X,{open:q,onClose:()=>J(!1),title:"User Node composition",testId:`user-block-composition-dialog-${e.id}`,panelClassName:"max-w-2xl",footer:(0,nt.jsx)(Y,{onClick:()=>J(!1),children:"Close"}),children:(0,nt.jsxs)("div",{className:"grid gap-4 text-sm",children:[(0,nt.jsxs)("section",{className:"grid gap-1",children:[(0,nt.jsx)("div",{className:"font-semibold text-modiff-text",children:"Execution contract"}),(0,nt.jsx)("p",{className:"text-modiff-subtle-text",children:"This editable instance executes as an ordinary MoDiff graph. Socket types, container ownership, and sealed Modular workflow-state identity are checked before execution."}),"diffusers"===M?.origin?.provider?(0,nt.jsxs)("p",{className:"text-modiff-subtle-text",children:["Upstream identity: ",M.origin.definitionId||"imported Diffusers definition"," at"," ",M.origin.libraryRevision||M.origin.revision||"its pinned revision",". Ordinary node edits do not claim upstream block-tree parity; reviewed Modular block edits are rebuilt through Diffusers ",(0,nt.jsx)("code",{children:"init_pipeline()"}),"."]}):null]}),(0,nt.jsxs)("section",{className:"grid gap-2","data-testid":`user-block-composition-issues-${e.id}`,children:[(0,nt.jsx)("div",{className:"font-semibold text-modiff-text",children:fe.valid?"No structural connection issues found":`${fe.issues.length} structural issue${1===fe.issues.length?"":"s"}`}),fe.issues.map(e=>(0,nt.jsx)("div",{className:"rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/10 px-3 py-2 text-modiff-text",children:e.message},e.id))]}),(0,nt.jsxs)("section",{className:"grid gap-2","data-testid":`user-block-insertion-suggestions-${e.id}`,children:[(0,nt.jsx)("div",{className:"font-semibold text-modiff-text",children:"Compatible insertion points"}),fe.insertionSuggestions.length?fe.insertionSuggestions.slice(0,12).map(t=>(0,nt.jsxs)("div",{className:"flex items-center justify-between gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-bg px-3 py-2",children:[(0,nt.jsx)("span",{className:"min-w-0 flex-1 text-modiff-subtle-text",children:t.label}),(0,nt.jsx)(Y,{tone:"primary",onClick:()=>{try{me(e.id,t),He.getState().saveActiveWorkflowTab(!0),Q("The node was inserted into the compatible execution path.",{variant:"success",autoHideDuration:2600})}catch(e){Q(e instanceof Error?e.message:"The compatible insertion point is unavailable.",{variant:"error",autoHideDuration:3600})}},children:"Insert"})]},t.id)):(0,nt.jsx)("p",{className:"text-modiff-subtle-text",children:"No disconnected node currently has both an input and output that can safely replace an existing typed connection."})]}),he?(0,nt.jsxs)("section",{className:"grid gap-2","data-testid":`reviewed-modular-composition-${e.id}`,children:[(0,nt.jsx)("div",{className:"font-semibold text-modiff-text",children:"Reviewed upstream Modular blocks"}),(0,nt.jsxs)("p",{className:"text-modiff-subtle-text",children:["These actions validate an upstream block-tree modification against the exact pinned Diffusers definition. The backend applies the edit to ",(0,nt.jsx)("code",{children:"sub_blocks"})," and rebuilds its interface through"," ",(0,nt.jsx)("code",{children:"init_pipeline()"}),". The receipt is structural and does not silently replace this ordinary User Node execution graph."]}),(0,nt.jsx)("div",{className:"max-h-52 overflow-y-auto rounded-modiff-compact border border-modiff-border",children:he.definition.blockPlacements.map(e=>{let t=e.path.join(".");return(0,nt.jsxs)("div",{className:"flex items-center justify-between gap-3 border-b border-modiff-border px-3 py-2 last:border-b-0",children:[(0,nt.jsx)("span",{className:"min-w-0 flex-1 truncate text-modiff-subtle-text",children:t}),(0,nt.jsx)(Y,{disabled:!!ne,onClick:()=>{ie(t),ue(null),de(Re(he,e.path)).then(e=>se(e)).catch(e=>ue(e instanceof Error?e.message:"Could not rebuild the Modular composition.")).finally(()=>ie(null))},children:ne===t?"Rebuilding…":"Validate duplicate"})]},t)})}),re?(0,nt.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-green/40 bg-modiff-green/10 px-3 py-2 text-modiff-text","data-testid":`reviewed-modular-composition-receipt-${e.id}`,children:["Upstream ",(0,nt.jsx)("code",{children:"init_pipeline()"})," rebuild passed: ",re.composedPaths.length," ","block paths, ",re.inputs.length," inputs,"," ",re.outputs.length," outputs. Receipt"," ",re.receiptHash.slice(0,20),"…"]}):null,ce?(0,nt.jsx)("div",{className:"rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/10 px-3 py-2 text-modiff-text",children:ce}):null]}):null]})}),(0,nt.jsx)(X,{open:D,onClose:()=>{Z||U(!1)},title:"Save User Node changes",testId:`save-user-block-choices-${e.id}`,panelClassName:"max-w-lg",footer:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Y,{disabled:Z,onClick:()=>{Ie("workflow")},children:"Keep only in this workflow"}),(0,nt.jsx)(Y,{disabled:Z,onClick:()=>{Ie("new")},children:"Save as new User Node"}),(0,nt.jsx)(Y,{tone:"primary",disabled:Z,onClick:()=>{Ie("update")},children:"Update existing User Node"})]}),children:(0,nt.jsxs)("div",{className:"grid gap-3 text-sm text-modiff-subtle-text",children:[(0,nt.jsx)("p",{children:"The current topology, parameters, exposed ports, and model revision have been captured."}),(0,nt.jsxs)("p",{children:["Updating changes the reusable library definition but does not silently rewrite other workflow instances. Saving as new defaults to"," ",(0,nt.jsx)("strong",{children:B(M?.name||"User Node",v)}),"."]})]})}),(0,nt.jsx)(X,{open:!!z,onClose:()=>L(null),title:"Configure block instance",testId:`configure-user-block-${e.id}`,panelClassName:"max-w-xl",footer:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(Y,{onClick:()=>L(null),children:"Cancel"}),(0,nt.jsx)(Y,{tone:"primary",disabled:!z?.name.trim(),onClick:()=>{z&&(k(e.id,z),L(null))},children:"Apply"})]}),children:z&&M?(0,nt.jsxs)("div",{className:"grid gap-4",children:[(0,nt.jsx)(G,{label:"Name",required:!0,children:(0,nt.jsx)(le,{value:z.name,onChange:e=>{let t=e.currentTarget.value;L(e=>e&&{...e,name:t})}})}),M.inputs.map(e=>(0,nt.jsx)(G,{label:`Input: ${e.label}`,children:(0,nt.jsx)(le,{value:z.inputLabels[e.id]??e.label,onChange:t=>{let a=t.currentTarget.value;L(t=>t&&{...t,inputLabels:{...t.inputLabels,[e.id]:a}})}})},`input-${e.id}`)),M.outputs.map(e=>(0,nt.jsx)(G,{label:`Output: ${e.label}`,children:(0,nt.jsx)(le,{value:z.outputLabels[e.id]??e.label,onChange:t=>{let a=t.currentTarget.value;L(t=>t&&{...t,outputLabels:{...t.outputLabels,[e.id]:a}})}})},`output-${e.id}`)),V.length>0?(0,nt.jsxs)("section",{className:"grid gap-2",children:[(0,nt.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Editable parameters"}),V.map(e=>(0,nt.jsx)(H,{label:e.label,checked:z.exposedParamIds.has(e.id),onCheckedChange:t=>L(a=>{if(!a)return a;let o=new Set(a.exposedParamIds);return t?o.add(e.id):o.delete(e.id),{...a,exposedParamIds:o}})},e.id))]}):null]}):null})]})});Wt.displayName="UserBlockNode";var Kt=/(?:ModelsLoader|LoadPipeline|LoadModel|LoadTokenizer|LoadTextEncoder)$/u;function Jt(e,t){let a=e.data.params[t];return void 0===a?.value?a?.default:a.value}function Qt(e){let t=Jt(e,"repo_id")??Jt(e,"model_id");return t&&"object"==typeof t&&"value"in t?"string"==typeof t.value?t.value:"":"string"==typeof t?t:""}function Yt(e){let t=Jt(e,"offload_mode")??(Jt(e,"auto_offload")?"Auto Offload enabled":"Auto Offload disabled");return`${Qt(e)||e.data.label||e.id} · ${Jt(e,"dtype")??"runtime dtype"} · ${Jt(e,"device")??"runtime device"} · ${t}`}function Xt(e,t,a){let o=e.find(e=>e.id===a);if(a&&!o?.data.blockInstanceV2&&!o?.data.blockProjectionContainer)throw Error("Select a Block to assess its contained graph.");let n=me(e,t,a),i=ie(n.nodes),d=new Set(i.map(e=>e.id)),r=n.edges.filter(e=>d.has(e.source)&&d.has(e.target)),s=$e(e,t).nodes.filter(e=>e.data.blockInstanceV2),l=i.filter(e=>Kt.test(e.data.action??"")),c=i.filter(e=>/lora|adapter/iu.test(`${e.data.action} ${e.data.module}`)),u=new Set,p=[],f=[],m=e=>{let t=new Set(e);for(let e=!0;e;){e=!1;for(let a of r)t.has(a.source)&&!t.has(a.target)&&(t.add(a.target),e=!0)}return t.size-e.length};for(let e of s){let t=e.data.blockInstanceV2,a=new Set(t.effectiveGraph.nodes.map(t=>ue(e.id,t.nodeId))),n=l.filter(e=>a.has(e.id));if(!n.length)continue;n.forEach(e=>u.add(e.id));let d=[],s=Le(t,"auto")??N(t),c=s?{...s.form,resourceMode:"auto"}:null;1!==n.length&&(c=null,d.push("This Block has multiple model loaders. Its combined recipe needs a workflow resource contract.")),o?.data.blockProjectionContainer&&(c=null,d.push("This nested scope differs from its full source recipe; the full Block estimate cannot be reused."));let p=n[0];if(c){let e=Qt(p),t=Jt(p,"model_type");(e&&e!==c.modelRepo||t&&t!==c.modelType)&&(c=null,d.push("The actual loader differs from the source recipe. Its resource plan must be reviewed separately."))}if(c)for(let[e,t]of Object.entries({device:"device",dtype:"dtype",offload_mode:"offloadMode",auto_offload:"autoOffload",quantization_mode:"quantizationMode"})){let a=Jt(p,e);void 0!==a&&typeof a==typeof c[t]&&(c[t]=a)}let h=r.filter(e=>a.has(e.target)&&!a.has(e.source));for(let e of h){let t=i.find(t=>t.id===e.source);d.push(`Connected input: ${t?.data.label??e.source} → ${e.targetHandle??"input"}. Its computed value is resolved when the graph runs.`)}"structure_changed"===t.customization.state&&d.push("Internal graph edits are present. The source model estimate does not qualify the edited recipe."),s||d.push("No exact resource planning form is available for this saved or custom Block."),f.push({id:e.id,label:e.data.label??t.definitionSnapshot.displayName,form:c,loaderIds:n.map(e=>e.id),consumers:m(n.map(e=>e.id)),details:[...new Set(d)],currentSettings:n.map(Yt)})}for(let e of l.filter(e=>!u.has(e.id)))f.push({id:e.id,label:e.data.label??e.id,form:null,loaderIds:[e.id],consumers:m([e.id]),details:["Use Check Auto execution plan for this standalone loader."],currentSettings:[Yt(e)]});(l.length>1||c.length)&&p.push("Check Auto execution plan for the combined model and adapter memory budget.");let h=xe(e,t,a);return{key:_({target:a??null,owners:s.flatMap(e=>{let t=e.data.blockInstanceV2,a=t.effectiveGraph.nodes.filter(t=>d.has(ue(e.id,t.nodeId)));return a.length?[{id:e.id,definition:t.definitionRef,members:a}]:[]}),nodes:i.map(e=>({id:e.id,module:e.data.module,action:e.data.action,params:Object.fromEntries(Object.entries(e.data.params).filter(([,e])=>"output"!==e.display).map(([e,t])=>[e,{value:void 0===t.value?t.default??null:t.value,type:t.type??null}]))})),edges:r.map(({source:e,target:t,sourceHandle:a,targetHandle:o})=>({source:e,target:t,sourceHandle:a??null,targetHandle:o??null}))}),scopeLabel:o?.data.label??"Whole workflow",items:f,issues:p,autoReason:h.reason,autoEligible:h.eligible,nodeCount:i.length,loaderCount:l.length,adapterCount:c.length}}async function Zt(e,t){let a=e.items.filter(e=>e.form),[o,n]=await Promise.all([a.length?re(a.map(e=>e.form),a.map(e=>e.id),t):Promise.resolve([]),Ce(t)]),i={};for(let e of o){let t=a.find(t=>t.id===e.planKey);if(!t||i[t.id])throw Error("Resource assessment returned an unmatched or duplicate Block plan.");i[t.id]=e}if(Object.keys(i).length!==a.length)throw Error("Resource assessment returned an incomplete set of Block plans.");return{request:e,plans:i,resources:n}}var ea=[{value:"studio",label:"Studio",Icon:b},{value:"block",label:"Block",Icon:l},{value:"compatibility",label:"Compatibility",Icon:v},{value:"queue",label:"Queue",Icon:r},{value:"setup",label:"Setup",Icon:l},{value:"app",label:"Run as app",Icon:f}];function ta(){let e=q(e=>e.target),t=Ae(e=>e.rightPanelTab),a=Ae(e=>e.setRightPanelTab),o=Ae(e=>e.studioViewMode),n=He(e=>e.appModeConfigs),i=ke(e=>e.nodes.some(e=>/preview|save|output|video|audio|image/i.test(`${e.data?.label??""} ${e.id}`))),d=ea.filter(a=>"studio"===a.value||"block"===a.value&&!!e||"queue"===a.value||"setup"===a.value||"compatibility"===a.value&&"compatibility"===t),r="expert"===o?ea.filter(e=>"app"===e.value&&(n.length>0||i)):[],s=r.find(e=>e.value===t),l=s?[...d,s]:d,c=l.some(e=>e.value===t)?t:"studio";return(0,ot.useEffect)(()=>{c!==t&&a(c)},[c,t,a]),(0,nt.jsxs)("div",{className:"flex h-full flex-col","data-testid":"workspace-panel",children:[(0,nt.jsxs)("div",{"aria-label":"Workspace panels",className:"flex min-h-[42px] flex-none overflow-x-auto overflow-y-hidden border-b border-modiff-border bg-modiff-bg",onWheel:Ve,children:[(0,nt.jsx)(Me,{"aria-label":"Workspace panels",className:"min-w-0 flex-1 flex-nowrap gap-0",value:c,onValueChange:a,options:l.map(e=>{let t=e.Icon;return{value:e.value,id:`workspace-tab-${e.value}`,controls:`workspace-panel-${e.value}`,label:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(t,{size:15}),(0,nt.jsx)("span",{children:e.label})]}),testId:`workspace-tab-${e.value}`,className:"h-[42px] min-w-[72px] flex-none justify-center gap-1.5 rounded-none px-2 text-xs"}})}),r.length>0&&(0,nt.jsx)("div",{className:"flex h-[42px] flex-none items-center px-1","data-testid":"workspace-tabs-more",children:(0,nt.jsx)(Se,{label:(0,nt.jsxs)(nt.Fragment,{children:[(0,nt.jsx)(h,{size:16}),(0,nt.jsx)("span",{className:"sr-only",children:"More panels"})]}),children:r.map(e=>{let t=e.Icon;return(0,nt.jsxs)(he,{active:c===e.value,onClick:()=>a(e.value),children:[(0,nt.jsx)(t,{size:15}),(0,nt.jsx)("span",{children:e.label})]},e.value)})})})]}),l.filter(e=>e.value!==c).map(e=>(0,nt.jsx)("div",{id:`workspace-panel-${e.value}`,role:"tabpanel","aria-labelledby":`workspace-tab-${e.value}`,hidden:!0},e.value)),(0,nt.jsxs)("div",{id:`workspace-panel-${c}`,role:"tabpanel","aria-labelledby":`workspace-tab-${c}`,className:"min-h-0 flex-1 overflow-y-auto overflow-x-hidden",children:["studio"===c&&(0,nt.jsx)(Ye,{}),"block"===c&&(0,nt.jsx)(tt,{}),"compatibility"===c&&(0,nt.jsx)(Xe,{}),"queue"===c&&(0,nt.jsx)(Qe,{}),"setup"===c&&(0,nt.jsx)(Ze,{}),"app"===c&&(0,nt.jsx)(et,{})]})]})}var aa=e({applyRuntimeWorkflowAutoSettingsV2:()=>pa,applyWorkflowAutoSettingsV2:()=>ua,fetchWorkflowAutoPlanV2:()=>da,inspectWorkflowAutoPlanV2:()=>ra,parseWorkflowAutoPlanV2:()=>ia,prepareWorkflowAutoExecutionV2:()=>ca}),oa=e=>!(!e||"object"!=typeof e||Array.isArray(e)),na=e=>Array.isArray(e)&&e.every(e=>"string"==typeof e);function ia(e){if(!oa(e)||1!==e.schemaVersion||"boolean"!=typeof e.canAutoRun||!na(e.issues)||"string"!=typeof e.message||![e.graphHash,e.plannedGraphHash].every(e=>"string"==typeof e&&/^sha256:workflow-auto-v1:[a-f0-9]{64}$/u.test(e))||!Array.isArray(e.patches)||!e.patches.every(e=>oa(e)&&"string"==typeof e.nodeId&&("auto_offload"===e.field&&"boolean"==typeof e.value||"offload_mode"===e.field&&"string"==typeof e.value))||!Array.isArray(e.loaders)||!e.loaders.every(e=>oa(e)&&["nodeId","modelType","repository","candidateId"].every(t=>"string"==typeof e[t])&&na(e.consumers))||!oa(e.requirements)||!Object.values(e.requirements).every(e=>"number"==typeof e&&Number.isFinite(e)&&e>=0)||!oa(e.available)||!Object.values(e.available).every(e=>null===e||"number"==typeof e&&Number.isFinite(e)&&e>=0)||"boolean"!=typeof e.sharedMemory||void 0!==e.requiresPreparation&&"boolean"!=typeof e.requiresPreparation||void 0!==e.preparationNodeIds&&!na(e.preparationNodeIds)||e.canAutoRun&&e.issues.length||!e.canAutoRun&&e.patches.length)throw Error("The backend returned an invalid workflow Auto plan.");return e}async function da(e,t){return _e(`${V.serverAddress}/auto_resource/workflow`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({schemaVersion:1,graph:{nodes:e.nodes,paths:e.paths,...e.loops?{loops:e.loops}:{}}}),timeoutMs:12e4,signal:t,parse:ia})}async function ra(e,t){let a=ke.getState(),{prepareLegacyGraphForAutoV2:o}=await A(async()=>{let{prepareLegacyGraphForAutoV2:e}=await import(`./studio-templates.js?v=5bcd315505f5670a`).then(e=>e.An);return{prepareLegacyGraphForAutoV2:e}},[]),n=o(a.nodes,a.edges,e),i=a.exportGraph("",e?n.remapped.get(e)??e:void 0,{randomizeSeeds:!1,sourceGraph:n});return n.nodes.some(e=>e.data.blockInstanceV2?.effectiveGraph.nodes.some(e=>"upstream_block"===e.modularDiffusers?.kind))?da((await(await A(async()=>{let{prepareModularCompositionExecutionV2:e}=await import(`./studio-templates.js?v=5bcd315505f5670a`).then(e=>e.y);return{prepareModularCompositionExecutionV2:e}},[])).prepareModularCompositionExecutionV2(a,n.nodes))(i),t):da(i,t)}function sa(){let e=ke.getState().toObject();return _({nodes:e.nodes.map(e=>({id:e.id,data:e.data})),edges:e.edges})}function la(e){let t=new Map(e.effectiveInterface.controls.map(e=>[e.controlId,[e.binding,...e.mirrorBindings??[]]]));for(let a of e.effectiveInterface.boundary.inputs)t.has(a.portId)||t.set(a.portId,[a.binding,...a.mirrorBindings??[]].map(({nodeId:e,fieldOrPortId:t})=>({nodeId:e,fieldId:t})));return[...t].map(([e,t])=>({logicalId:e,bindings:t}))}async function ca(e,t){let a=He.getState().activeWorkflowTabId,o=sa(),n=await da(e);if(a!==He.getState().activeWorkflowTabId||o!==sa()||"auto"!==He.getState().form.resourceMode)throw Error("The workflow or mode changed while Auto was planning. Run again with the current graph.");if(!n.canAutoRun)throw Error(`Auto cannot run this workflow: ${n.issues.join(" ")}`);let i=ua(e,n.patches,t),d=ke.getState().nodes.flatMap(e=>{let t=e.data.blockInstanceV2;return t?la(t).flatMap(({bindings:t})=>t.length<2||!t.every(e=>"offload_mode"===e.fieldId||"auto_offload"===e.fieldId)?[]:[t.map(t=>({nodeId:ue(e.id,t.nodeId),field:t.fieldId}))]):[]});return{...i,runtimeHints:{workflowAutoPlan:{schemaVersion:1,graphHash:n.plannedGraphHash,...d.length?{resourceControlGroups:d}:{}},resourceMode:"auto"}}}function ua(e,t,a){let o=structuredClone(e);ke.getState().beginHistoryTransaction("Apply workflow Auto resource settings");try{a&&ke.setState({nodes:a.nodes,edges:a.edges});let n=new Set;for(let a of t){let i=`${a.nodeId}.${a.field}`,d=o.nodes[a.nodeId]?.params[a.field];if(n.has(i)||!d||d.sourceId)throw Error("The Auto plan targets a missing, connected or repeated resource field.");n.add(i),d.value=a.value;let r=ke.getState(),s=r.nodes.find(e=>e.data.blockInstanceV2?.effectiveGraph.nodes.some(t=>ue(e.id,t.nodeId)===a.nodeId));if(!s?.data.blockInstanceV2){if(!r.nodes.some(e=>e.id===a.nodeId))throw Error("The loader is inside an older Block. Move it into a current Block before applying Auto settings.");r.setParamWithHistory(a.nodeId,a.field,a.value);continue}let l=s.data.blockInstanceV2,c=l.effectiveGraph.nodes.find(e=>ue(s.id,e.nodeId)===a.nodeId),u=la(l).filter(e=>e.bindings.some(e=>e.nodeId===c.nodeId&&e.fieldId===a.field));if(u.length){for(let o of u.flatMap(e=>e.bindings)){let n=ue(s.id,o.nodeId),i=e.nodes[n]?.params[o.fieldId],d=t.find(e=>e.nodeId===n&&e.field===o.fieldId);if(!i||i.sourceId||(d?.value??i.value)!==a.value)throw Error("A shared resource control would change a node outside this Auto plan. Configure that control separately.")}for(let e of u)r.setBlockInstanceValueV2(s.id,e.logicalId,a.value)}else{let e=structuredClone(l.effectiveGraph),t=e.nodes.find(e=>e.nodeId===c.nodeId).data.params;t[a.field]={...t[a.field],value:a.value};let o=fe(ye(ne(l,e),{selected:s.selected}));ke.setState({nodes:[...r.nodes.filter(e=>e.id!==s.id&&e.data.blockProjectionOwnerId!==s.id),...o.nodes],edges:[...r.edges.filter(e=>e.data?.blockProjectionOwnerId!==s.id),...o.edges]})}}ke.getState().commitHistoryTransaction()}catch(e){throw ke.getState().cancelHistoryTransaction(),e}return o}function pa(e){let t=ke.getState().exportGraph("",void 0,{randomizeSeeds:!1});for(let a of e){let e=t.nodes[a.nodeId]?.params[a.field];if(!e||e.sourceId||(e.value??null)!==(a.previousValue??null))throw Error("The run changed resource settings, but your newer workflow edits were preserved. The run details show the settings used.")}ua(t,e)}export{Wt as a,gt as c,at as d,Xt as i,mt as l,ta as n,qt as o,Zt as r,xt as s,aa as t,ft as u}; \ No newline at end of file +import{n as e,r as t}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{Bn as a,Cn as o,En as n,M as i,Ot as d,R as r,Sn as s,X as l,cr as c,dr as u,et as p,kn as f,ln as m,nr as h,ot as g,q as b,tr as x,wt as I,xt as k,z as v}from"./graph-vendor.js?v=cc046c26d6fe7aa4";import{$s as y,Bn as w,Bs as j,Ci as $,Dt as N,Eo as C,Er as S,Fo as _,In as P,Jn as T,Js as O,Ks as M,Ln as E,Mn as H,Mo as R,No as B,Oo as A,Po as V,Sa as z,Si as L,Ti as D,Ts as q,V as F,Vi as G,Vn as W,Vr as U,Xr as K,Xs as Y,Yr as X,Ys as J,_ as Q,_n as Z,_r as ee,_s as te,an as ae,as as oe,br as ne,ca as ie,cn as de,cs as re,ct as se,dn as le,dt as ce,fi as ue,fn as pe,ft as fe,gi as me,gr as he,h as ge,hi as be,hr as xe,in as Ie,is as ke,it as ve,ji as ye,jo as we,ko as je,kr as $e,kt as Ne,m as Ce,mn as Se,oc as _e,os as Pe,p as Te,pi as Oe,q as Me,qo as Ee,rn as He,rs as Re,rt as Be,sa as Ae,ts as Ve,ui as ze,v as Le,vr as De,w as qe,x as Fe,xn as Ge,y as We,yr as Ue}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{t as Ke}from"./EditorPanel.js?v=cc046c26d6fe7aa4";import{a as Ye,i as Xe,n as Je,o as Qe,r as Ze,t as et}from"./BlockInterfaceDialogContentV2.js?v=cc046c26d6fe7aa4";import{t as tt}from"./LegacyUserBlockNode.js?v=cc046c26d6fe7aa4";import{a as at,i as ot,n as nt,o as it,r as dt,t as rt}from"./WorkspacePanel.js?v=cc046c26d6fe7aa4";var st=e({default:()=>ut}),lt=t(u(),1),ct=c();function ut(e){return"composition"===e.mode?(0,ct.jsx)(pt,{...e}):(0,ct.jsx)(Ie,{open:!0,onClose:e.onClose,title:"Switch model route",testId:`switch-block-route-v1-${e.nodeId}`,panelClassName:"max-w-lg",footer:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(He,{onClick:e.onClose,children:"Cancel"}),(0,ct.jsx)(He,{disabled:e.busy,onClick:()=>e.onSwitch(!0),children:"Save active route and switch"}),(0,ct.jsx)(He,{tone:"primary",disabled:e.busy,onClick:()=>e.onSwitch(!1),children:"Keep draft and switch"})]}),children:(0,ct.jsx)("p",{className:"text-sm text-modiff-subtle-text",children:"This route has workflow-local changes. MoDiff can keep its exact values, internal graph, interface, and layout as an inactive workflow draft, then restore them when you switch back. Previews and run authority are not copied between models."})})}function pt({nodeId:e,instance:t,onClose:a}){let o=K(e=>e.library),n=X(e=>e.snapshot),i=K(e=>e.error),d=X(e=>e.error),r=t.effectiveGraph.nodes.some(e=>"upstream_block"===e.modularDiffusers?.kind),s=!r||!(!o||!n),[l,c]=(0,lt.useState)(0),[u,p]=(0,lt.useState)(!1);(0,lt.useEffect)(()=>{if(!r)return;let e=!0;p(!0);let t=K.getState(),a=X.getState();return Promise.all([t.library?Promise.resolve():t.fetchLibrary(),a.snapshot?Promise.resolve():a.fetchSnapshot()]).finally(()=>{e&&p(!1)}),()=>{e=!1}},[r,l]);let[f,m]=(0,lt.useState)(!1),[h,g]=(0,lt.useState)(null),[b,x]=(0,lt.useState)(null),I=(0,lt.useRef)(null),k=(0,lt.useMemo)(()=>{if(!s)return{recipe:null,error:null};try{return{recipe:Le(t,o,n),error:null}}catch(e){return{recipe:null,error:e instanceof Error?e.message:"Could not derive the upstream Modular composition."}}},[t,o,n,s]);return(0,lt.useEffect)(()=>(g(null),x(null),m(!1),()=>I.current?.abort()),[Y(k.recipe)]),(0,ct.jsx)(Ie,{open:!0,onClose:a,title:"Block composition",testId:`user-block-composition-dialog-${e}`,panelClassName:"max-w-lg",footer:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(He,{onClick:a,children:"Close"}),s?null:(0,ct.jsx)(He,{disabled:u,onClick:()=>c(e=>e+1),children:u?"Loading pinned catalog…":"Retry pinned catalog"}),k.recipe?(0,ct.jsx)(He,{tone:"primary",disabled:f,onClick:()=>{(async()=>{if(!k.recipe||f)return;let e=new AbortController;I.current=e,m(!0),g(null),x(null);try{let t=await Q(k.recipe,e.signal);e.signal.aborted||g(t)}catch(t){e.signal.aborted||x(t instanceof Error?t.message:"The backend could not rebuild this Modular composition.")}finally{e.signal.aborted||m(!1)}})()},children:f?"Rebuilding…":"Validate upstream composition"}):null]}),children:(0,ct.jsxs)("div",{className:"grid gap-2 text-sm text-modiff-subtle-text",children:[s?null:(0,ct.jsx)("p",{role:u?"status":"alert",children:u?"Loading the reviewed block catalog and hierarchy. Your workflow is unchanged.":i??d??"The pinned catalog is unavailable. Retry before validating."}),(0,ct.jsxs)("p",{children:[t.effectiveGraph.nodes.length," ordinary internal nodes and ",t.effectiveGraph.edges.length," ","internal connections are owned by this workflow instance."]}),(0,ct.jsxs)("p",{children:["Source: ",t.definitionSnapshot.source.kind,". Customization state:"," ",t.customization.state.replace(/_/gu," "),"."]}),k.recipe?(0,ct.jsxs)("p",{"data-testid":`block-v2-composition-summary-${e}`,children:["Exact upstream recipe: ",k.recipe.operations.length," structural operation",1===k.recipe.operations.length?"":"s",". Parameter-only changes are stored directly on this workflow instance and do not rebuild sibling blocks."]}):null,k.error?(0,ct.jsx)("p",{className:"text-modiff-warning","data-testid":`block-v2-composition-error-${e}`,children:k.error}):null,h?(0,ct.jsxs)("p",{className:"text-modiff-green","data-testid":`block-v2-composition-receipt-${e}`,children:["Upstream init_pipeline() rebuild passed for ",h.composedPaths.length," paths. Receipt"," ",h.receiptHash.slice(0,20),"…"]}):null,b?(0,ct.jsx)("p",{className:"text-modiff-red",children:b}):null,"edited_unpruned_tree"===h?.inspectionScope?(0,ct.jsx)("p",{children:"This checks the edited tree, including inactive branches. It does not load models or qualify a run. Execution checks each connected step's required components and inputs."}):null]})})}function ft(e,t){return`${e}\0${t}`}function mt(e,t){let a=e.fieldId??e.fieldOrPortId??"",o=t.fieldId??t.fieldOrPortId??"";return e.nodeId===t.nodeId?a===o?0:a=e.length)return[...e];let n=[...e];return[n[t],n[o]]=[n[o],n[t]],n}function gt({entryKey:e,valueType:t,primary:a,mirrors:o,options:n,selected:i,disabled:d=!1,onSelected:r,onAdd:s,onRemove:l}){let c=function(e,t,a,o){let n=new Set([ft(a.nodeId,a.fieldId),...o.map(({nodeId:e,fieldId:t})=>ft(e,t))]);return e.filter(e=>!n.has(e.key)&&M(t,e.valueType))}(n,t,a,o),u=new Map(n.map(e=>[e.key,e.label]));return(0,ct.jsxs)("div",{className:"ml-3 grid gap-2 border-l border-modiff-border-subtle pl-3","data-testid":`interface-mirrors-${e}`,children:[o.map(t=>{let a=ft(t.nodeId,t.fieldId);return(0,ct.jsxs)("div",{className:"flex min-w-0 items-center gap-2 text-xs text-modiff-subtle-text",children:[(0,ct.jsxs)("span",{className:"min-w-0 flex-1 break-words",children:["Additional consumer: ",u.get(a)??`${t.nodeId} / ${t.fieldId}`]}),(0,ct.jsx)(ae,{label:`Remove additional consumer ${u.get(a)??t.fieldId}`,size:"compact",disabled:d,"data-testid":`interface-mirror-remove-${e}-${t.nodeId}-${t.fieldId}`,onClick:()=>l(t),children:(0,ct.jsx)(v,{size:13})})]},a)}),(0,ct.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2",children:[(0,ct.jsx)(Qe,{value:i,disabled:d,onValueChange:r,options:c.map(e=>({value:e.key,label:e.label})),placeholder:"Add another internal consumer","data-testid":`interface-mirror-select-${e}`}),(0,ct.jsx)(ae,{label:"Add additional internal consumer",size:"compact",disabled:d||!i||!c.some(({key:e})=>e===i),"data-testid":`interface-mirror-add-${e}`,onClick:()=>{let e=c.find(({key:e})=>e===i);e&&s(e)},children:(0,ct.jsx)(g,{size:14})})]})]})}function bt(e){return!(!e||"object"!=typeof e||Array.isArray(e))}function xt(e,t,a){let o=new Set([...a.boundary.inputs.map(({portId:e})=>e),...a.boundary.outputs.map(({portId:e})=>e),...a.controls.map(({controlId:e})=>e)]),n=`${e}:${t.nodeId}:${t.fieldId}`.replace(/[^A-Za-z0-9_.:/-]/gu,"-"),i=n,d=2;for(;o.has(i);)i=`${n}-${d++}`;return i}function It({nodeId:e,snapshot:t,subtreeId:a,context:o,onClose:i}){let[d,r]=(0,lt.useState)(()=>Je(t,a)),[s,l]=(0,lt.useState)({input:"",output:"",control:""}),[c,u]=(0,lt.useState)({}),p=(0,lt.useMemo)(()=>Xe(t,a),[t,a]),m=(0,lt.useMemo)(()=>function(e){return e.effectiveGraph.nodes.flatMap(e=>{let t=bt(e.data.params)?e.data.params:{};return Object.entries(t).flatMap(([t,a])=>bt(a)&&"string"==typeof a.type&&a.type?[{key:`${e.nodeId}\0${t}`,nodeId:e.nodeId,fieldId:t,label:`${"string"==typeof e.data.label?e.data.label:e.nodeId} / ${"string"==typeof a.label?a.label:t}`,valueType:a.type,..."string"==typeof a.display?{display:a.display}:{}}]:[])})}(t).filter(({nodeId:e})=>p.has(e)),[t,p]),h=m.filter(({display:e})=>"output"!==e),b=m.filter(({display:e})=>"output"===e),x=h,I=U(e=>e.edges),k=null,y=null;try{d&&(y=Ye(t,t,d,a))}catch(e){k=e instanceof Error?e.message:"Invalid interface draft."}let w=new Set(y?.boundary.inputs.map(({portId:e})=>e)),j=new Set(y?.boundary.outputs.map(({portId:e})=>e)),$=a&&y?Ee(t,a,{schemaVersion:1,...y}):a?[]:I.filter(e=>!((e.target!==t.instanceId||te(e.targetHandle)||e.targetHandle&&w.has(e.targetHandle))&&(e.source!==t.instanceId||te(e.sourceHandle)||e.sourceHandle&&j.has(e.sourceHandle))));return(0,ct.jsx)(Ke,{onClose:()=>i(),title:q(t)&&!a?"Configure node interface":"Configure Block interface",testId:`configure-block-v2-${e}`,footer:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(He,{onClick:()=>i(),children:"Cancel"}),(0,ct.jsx)(He,{tone:"primary",disabled:!d||$.length>0||!!k,onClick:()=>{if(d)try{(function(){D(o,{includeForm:!1});let e=U.getState().nodes.find(e=>e.id===t.instanceId)?.data.blockInstanceV2;if(!e||!d)throw Error("This Block is no longer available. Reopen Configure Interface.");let n=Ye(t,e,d,a);U.getState().configureBlockInterfaceV2(t.instanceId,n,a)})(),i(),R("The workflow instance interface was updated.",{variant:"success",autoHideDuration:3e3})}catch(e){R(e instanceof Error?e.message:"Could not configure this interface.",{variant:"error",autoHideDuration:5200})}},children:"Apply interface"})]}),children:d?(0,ct.jsxs)("div",{className:"grid gap-5",children:[a?(0,ct.jsxs)("p",{className:"rounded-modiff-compact border border-modiff-border-subtle p-3 text-sm text-modiff-subtle-text","data-testid":"block-interface-scope",children:["Editing"," ",String(t.effectiveGraph.nodes.find(e=>e.nodeId===a)?.data.label||a),"'s own ports, controls, and previews. This interface is saved with the workflow and remains available after disconnecting links, collapsing, or reloading. The root Block interface is unchanged. Controls keep their existing values; shared controls continue to update their declared consumers together."]}):null,k?(0,ct.jsx)("p",{role:"alert",className:"text-modiff-red",children:k}):null,$.length?(0,ct.jsxs)("p",{className:"rounded-modiff-compact border border-modiff-warning/50 bg-modiff-warning/10 p-3 text-sm text-modiff-text",children:[$.length," connected edge",1===$.length?"":"s"," would lose a public port. Disconnect ",1===$.length?"it":"them"," before applying."]}):null,[["input","Inputs",h],["output","Outputs",b]].map(([e,t,a])=>{let o=d.boundary["input"===e?"inputs":"outputs"];return(0,ct.jsxs)("section",{className:"grid gap-2",children:[(0,ct.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:t}),o.map((t,i)=>{let d=`${e}:${t.portId}`,s=("input"===e?t.mirrorBindings:void 0)?.map(e=>({nodeId:e.nodeId,fieldId:e.fieldOrPortId}))??[];return(0,ct.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:[(0,ct.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2 [&>input]:col-span-2",children:[(0,ct.jsx)(Se,{"aria-label":`${e} ${t.portId} label`,value:t.label,onChange:a=>{let n=a.currentTarget.value;r(a=>a&&{...a,boundary:{...a.boundary,["input"===e?"inputs":"outputs"]:o.map(e=>e.portId===t.portId?{...e,label:n}:e)}})}}),(0,ct.jsx)(Qe,{value:ft(t.binding.nodeId,t.binding.fieldOrPortId),onValueChange:n=>{let i=a.find(e=>e.key===n);i&&(r(a=>a&&{...a,boundary:{...a.boundary,["input"===e?"inputs":"outputs"]:o.map(e=>e.portId===t.portId?(()=>{let t={...e};return delete t.mirrorBindings,{...t,valueType:i.valueType,binding:{nodeId:i.nodeId,fieldOrPortId:i.fieldId}}})():e)}}),u(e=>({...e,[d]:""})))},options:a.map(e=>({value:e.key,label:e.label}))}),(0,ct.jsxs)("div",{className:"flex items-center gap-1",children:[(0,ct.jsx)(ae,{label:`Move ${e} ${t.label} up`,size:"compact",disabled:0===i,onClick:()=>r(t=>t&&{...t,boundary:{...t.boundary,["input"===e?"inputs":"outputs"]:ht(o,i,-1)}}),children:(0,ct.jsx)(n,{size:14})}),(0,ct.jsx)(ae,{label:`Move ${e} ${t.label} down`,size:"compact",disabled:i===o.length-1,onClick:()=>r(t=>t&&{...t,boundary:{...t.boundary,["input"===e?"inputs":"outputs"]:ht(o,i,1)}}),children:(0,ct.jsx)(f,{size:14})}),(0,ct.jsx)(ae,{label:`Remove ${e} ${t.label}`,size:"compact",onClick:()=>r(a=>a&&{...a,boundary:{...a.boundary,["input"===e?"inputs":"outputs"]:o.filter(e=>e.portId!==t.portId)}}),children:(0,ct.jsx)(v,{size:14})})]})]}),"input"===e?(0,ct.jsx)(gt,{entryKey:d,valueType:t.valueType,primary:{nodeId:t.binding.nodeId,fieldId:t.binding.fieldOrPortId},mirrors:s,options:h,selected:c[d]??"",onSelected:e=>u(t=>({...t,[d]:e})),onAdd:e=>{r(a=>a&&{...a,boundary:{...a.boundary,inputs:a.boundary.inputs.map(a=>a.portId===t.portId?{...a,mirrorBindings:[...a.mirrorBindings??[],{nodeId:e.nodeId,fieldOrPortId:e.fieldId}].sort(mt)}:a)}}),u(e=>({...e,[d]:""}))},onRemove:e=>r(a=>a&&{...a,boundary:{...a.boundary,inputs:a.boundary.inputs.map(a=>{if(a.portId!==t.portId)return a;let o=(a.mirrorBindings??[]).filter(t=>t.nodeId!==e.nodeId||t.fieldOrPortId!==e.fieldId),n={...a};return delete n.mirrorBindings,o.length?{...n,mirrorBindings:o}:n})}})}):null]},t.portId)}),(0,ct.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2",children:[(0,ct.jsx)(Qe,{value:s[e],onValueChange:t=>l(a=>({...a,[e]:t})),options:a.map(e=>({value:e.key,label:e.label})),placeholder:`Choose an internal ${e} field`}),(0,ct.jsx)(ae,{label:`Add public ${e}`,size:"compact",disabled:!s[e],onClick:()=>{let t=a.find(t=>t.key===s[e]);if(!t)return;let n={portId:xt(e,t,d),label:t.label,valueType:t.valueType,required:!1,binding:{nodeId:t.nodeId,fieldOrPortId:t.fieldId}};r(t=>t&&{...t,boundary:{...t.boundary,["input"===e?"inputs":"outputs"]:[...o,n]}}),l(t=>({...t,[e]:""}))},children:(0,ct.jsx)(g,{size:14})})]})]},e)}),a?(0,ct.jsx)(et,{snapshot:t,subtreeId:a,previews:d.previews??[],onChange:e=>r(t=>t&&{...t,previews:e})}):null,(0,ct.jsxs)("section",{className:"grid gap-2",children:[(0,ct.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Editable controls"}),d.controls.map((e,t)=>{let a=`control:${e.controlId}`,o=e.mirrorBindings??[];return(0,ct.jsxs)("div",{className:"grid gap-2 rounded-modiff-compact border border-modiff-border-subtle p-2",children:[(0,ct.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2 [&>input]:col-span-2",children:[(0,ct.jsx)(Se,{"aria-label":`control ${e.controlId} label`,value:e.label,onChange:t=>{let a=t.currentTarget.value;r(t=>t&&{...t,controls:t.controls.map(t=>t.controlId===e.controlId?{...t,label:a}:t)})}}),(0,ct.jsx)(Qe,{value:ft(e.binding.nodeId,e.binding.fieldId),disabled:!!e.sealed,onValueChange:t=>{let o=x.find(e=>e.key===t);o&&(r(t=>t&&{...t,controls:t.controls.map(t=>t.controlId===e.controlId?(()=>{let e={...t};return delete e.mirrorBindings,{...e,valueType:o.valueType,binding:{nodeId:o.nodeId,fieldId:o.fieldId}}})():t)}),u(e=>({...e,[a]:""})))},options:x.map(e=>({value:e.key,label:e.label}))}),(0,ct.jsxs)("div",{className:"flex items-center gap-1",children:[(0,ct.jsx)(ae,{label:`Move control ${e.label} up`,size:"compact",disabled:0===t,onClick:()=>r(e=>e&&{...e,controls:ht(e.controls,t,-1).map((e,t)=>({...e,order:t}))}),children:(0,ct.jsx)(n,{size:14})}),(0,ct.jsx)(ae,{label:`Move control ${e.label} down`,size:"compact",disabled:t===d.controls.length-1,onClick:()=>r(e=>e&&{...e,controls:ht(e.controls,t,1).map((e,t)=>({...e,order:t}))}),children:(0,ct.jsx)(f,{size:14})}),(0,ct.jsx)(ae,{label:`Remove control ${e.label}`,size:"compact",disabled:!!e.sealed,onClick:()=>r(t=>t&&{...t,controls:t.controls.filter(t=>t.controlId!==e.controlId).map((e,t)=>({...e,order:t}))}),children:(0,ct.jsx)(v,{size:14})})]})]}),(0,ct.jsx)(gt,{entryKey:a,valueType:e.valueType,primary:e.binding,mirrors:o,options:x,selected:c[a]??"",disabled:!!e.sealed,onSelected:e=>u(t=>({...t,[a]:e})),onAdd:t=>{r(a=>a&&{...a,controls:a.controls.map(a=>a.controlId===e.controlId?{...a,mirrorBindings:[...a.mirrorBindings??[],{nodeId:t.nodeId,fieldId:t.fieldId}].sort(mt)}:a)}),u(e=>({...e,[a]:""}))},onRemove:t=>r(a=>a&&{...a,controls:a.controls.map(a=>{if(a.controlId!==e.controlId)return a;let o=(a.mirrorBindings??[]).filter(e=>e.nodeId!==t.nodeId||e.fieldId!==t.fieldId),n={...a};return delete n.mirrorBindings,o.length?{...n,mirrorBindings:o}:n})})})]},e.controlId)}),(0,ct.jsxs)("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] gap-2",children:[(0,ct.jsx)(Qe,{value:s.control,onValueChange:e=>l(t=>({...t,control:e})),options:x.map(e=>({value:e.key,label:e.label})),placeholder:"Choose an internal parameter"}),(0,ct.jsx)(ae,{label:"Add editable control",size:"compact",disabled:!s.control,onClick:()=>{let e=x.find(e=>e.key===s.control);if(!e)return;let a=xt("control",e,d),o=Ze(t,e.nodeId,e.fieldId);r(t=>t&&{...t,controls:[...t.controls,{controlId:a,label:e.label,binding:{nodeId:e.nodeId,fieldId:e.fieldId},valueType:e.valueType,...void 0===o?{}:{defaultValue:o},order:t.controls.length}]}),l(e=>({...e,control:""}))},children:(0,ct.jsx)(g,{size:14})})]})]})]}):null})}var kt=e({default:()=>vt});function vt({nodeId:e,onClose:t,context:a}){let[o,n]=(0,lt.useState)(null),i=(0,lt.useRef)(!1),d=U(t=>t.nodes.find(t=>t.id===e)),r=d?.data.blockInstanceV2?e:d?.data.blockProjectionOwnerId,s=U(e=>e.nodes.find(e=>e.id===r)?.data.blockInstanceV2),l=!(!d||d.data.blockInstanceV2),c=!l&&q(s)?"node":"Block",[u,p]=(0,lt.useState)(()=>{let e=ye.getState().workflowTabs.find(e=>e.id===a.workflowTabId)?.title||"Workflow";return Be(d?.data.label||s?.definitionSnapshot.displayName||"Block",e)}),f=!u.trim()||u.trim().length>512,m=s?se(s.definitionSnapshot,s,{editWorkflow:!0,configureInterfaces:!0,createUserDefinitions:!0,updateUserDefinitions:!0}):null;async function h(o){if(!i.current){i.current=!0,n(o);try{if("workflow"!==o&&f)throw Error(`Enter a ${c} name (up to 512 characters).`);let n=await ve({nodeId:e,choice:o,context:a,displayName:u.trim()});R("workflow"===o?"Changes kept only in this workflow.":`${"new"===o?"Saved new":"Updated"} ${c}: ${n.definition?.displayName??c}`,{variant:"success",autoHideDuration:3200}),t()}catch(e){R(e instanceof Error?e.message:`Could not save the ${c} changes.`,{variant:"error",autoHideDuration:6e3})}finally{i.current=!1,n(null)}}}return(0,ct.jsx)(Ke,{onClose:()=>{i.current||t()},title:`Save ${c.toLowerCase()} changes`,testId:`save-user-block-choices-${e}`,footer:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(He,{disabled:!!o,onClick:t,children:"Cancel"}),(0,ct.jsx)(He,{disabled:!!o||!m?.keepWorkflowOnly,loading:"workflow"===o,onClick:()=>{h("workflow")},children:"Keep only in this workflow"}),m?.saveAsNewUserNode?(0,ct.jsxs)(He,{tone:"primary",disabled:!!o||f,loading:"new"===o,onClick:()=>{h("new")},children:["Save as new ",c]}):null,!l&&m?.updateReusableDefinition?(0,ct.jsxs)(He,{disabled:!!o||f,loading:"update"===o,onClick:()=>{h("update")},children:["Update existing ",c]}):null]}),children:(0,ct.jsxs)("div",{className:"grid gap-2 text-sm text-modiff-subtle-text",children:[(0,ct.jsx)("p",{className:"font-semibold text-modiff-text",children:d?.data.label||"Block"}),(0,ct.jsx)(pe,{label:"node"===c?"Node name":"Block name",required:!0,error:f?"Enter a name.":void 0,children:(0,ct.jsx)(Se,{value:u,onChange:e=>p(e.currentTarget.value),maxLength:512,disabled:!!o,"aria-invalid":f,autoFocus:!0})}),(0,ct.jsx)("p",{children:"Save a reusable copy with these settings and the inputs and outputs you configured."}),(0,ct.jsx)("p",{children:l?"Only this Block and its contents are saved. Outside nodes and their connections stay in this workflow.":`Outside nodes and their connections stay in this workflow. Temporary connected sockets are not added to the saved ${c}.`})]})})}var yt=e({default:()=>wt});function wt({pendingBlock:e,setPendingBlock:t,handleConfirmBlockCreation:a}){return(0,ct.jsx)(Ie,{open:!!e,onClose:()=>t(null),title:"Create block",testId:"create-user-block-dialog",panelClassName:"max-w-xl",footer:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(He,{onClick:()=>t(null),children:"Cancel"}),(0,ct.jsx)(He,{tone:"primary",disabled:!e?.name.trim(),onClick:()=>{a()},"data-testid":"confirm-create-user-block",children:"Create block"})]}),children:e?(0,ct.jsxs)("div",{className:"grid gap-4",children:[(0,ct.jsx)(pe,{label:"Name",required:!0,children:(0,ct.jsx)(Se,{autoFocus:!0,value:e.name,onChange:e=>{let a=e.currentTarget.value;t(e=>e&&{...e,name:a})}})}),e.result.block.inputs.length>0?(0,ct.jsxs)("section",{className:"grid gap-2",children:[(0,ct.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Inputs"}),e.result.block.inputs.map(a=>(0,ct.jsx)(pe,{label:a.label,children:(0,ct.jsx)(Se,{value:e.inputLabels[a.id]??a.label,onChange:e=>{let o=e.currentTarget.value;t(e=>e&&{...e,inputLabels:{...e.inputLabels,[a.id]:o}})}})},a.id))]}):null,e.result.block.outputs.length>0?(0,ct.jsxs)("section",{className:"grid gap-2",children:[(0,ct.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Outputs"}),e.result.block.outputs.map(a=>(0,ct.jsx)(pe,{label:a.label,children:(0,ct.jsx)(Se,{value:e.outputLabels[a.id]??a.label,onChange:e=>{let o=e.currentTarget.value;t(e=>e&&{...e,outputLabels:{...e.outputLabels,[a.id]:o}})}})},a.id))]}):null,e.result.block.exposedParams.length>0?(0,ct.jsxs)("section",{className:"grid gap-2",children:[(0,ct.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Editable parameters"}),e.result.block.exposedParams.map(a=>(0,ct.jsx)(de,{label:a.label,checked:e.exposedParamIds.has(a.id),onCheckedChange:e=>t(t=>{if(!t)return t;let o=new Set(t.exposedParamIds);return e?o.add(a.id):o.delete(a.id),{...t,exposedParamIds:o}})},a.id))]}):null]}):null})}var jt=e({buildGraphFixPlan:()=>Wt,buildGraphFixPreview:()=>Kt,graphFixHighlightedSockets:()=>Yt,graphNodeIsOutputLike:()=>Ht,graphSocketTypesAreCompatible:()=>_t}),$t=new Set(["image","video","audio"]),Nt=new Set(["bool","float","int","number","str","string","text"]);function Ct(e,t){return e.map(e=>{if(B(e))return e;let a=t[function(e){return`${e.data.module}.${e.data.action}`}(e)];if(!a)return e;let o=Object.fromEntries(Object.entries(e.data.params).map(([e,t])=>{let o=a.params[e];return o?[e,{...t,type:o.type??t.type,required:o.required??t.required}]:[e,t]}));return{...e,data:{...e.data,params:o}}})}function St(e){let t=O(e?.type);return t.length?t:["any"]}function _t(e,t){return J(e?.type,t?.type)}function Pt(e){return Object.entries(_(e)).filter(([,e])=>"input"===e.display||e.isInput)}function Tt(e){return Object.entries(_(e)).filter(([,e])=>"output"===e.display)}function Ot(e){return null!=e&&("string"==typeof e?e.trim().length>0:Array.isArray(e)?e.length>0:"object"!=typeof e||("value"in e?Ot(e.value):Object.keys(e).length>0))}function Mt(e,t,a=!1){if(t.hidden||t.disabled||t.spawn||Ot(t.value??t.default)||!1===t.required)return!1;if(!0===t.required)return!0;if(a)return!1;let o=`${e} ${t.label??""} ${t.description??""}`.toLowerCase();return!/\boptional\b|\bif provided\b|\bwhen supplied\b/.test(o)}function Et(e){return e.filter(e=>"group"!==e.data.type&&"loop"!==e.data.type&&!e.data.uiState?.disabled&&!!e.data.module&&!!e.data.action)}function Ht(e){let t=`${e.data.module} ${e.data.action} ${e.data.label} ${e.data.category}`.toLowerCase();return/\b(preview|export|save|display|output|gallery)\b|viewer\b/.test(t)}function Rt(e){if(!B(e)||!e.data.blockInstanceV2)return!1;let t=e.data.blockInstanceV2.effectiveGraph,a=new Set(t.nodes.filter(({data:e})=>!e.uiState?.disabled).map(({nodeId:e})=>e));return t.nodes.some(e=>a.has(e.nodeId)&&Ht({data:e.data})&&t.edges.some(t=>t.targetNodeId===e.nodeId&&a.has(t.sourceNodeId)))}function Bt(e,t){return V(e,t)?.label||t.replace(/_/g," ")}function At(e){let t=e.split(".");return t[t.length-1]||e}function Vt(e,t,a){let o=new Map;e.forEach(e=>o.set(e.source,[...o.get(e.source)??[],e.target]));let n=[t],i=new Set;for(;n.length;){let e=n.shift();if(e&&!i.has(e)){if(e===a)return!0;i.add(e),n.push(...o.get(e)??[])}}return!1}function zt(...e){return e.filter(Boolean).join(":").replace(/[^a-z0-9:_-]+/gi,"-").toLowerCase()}function Lt(e){let t=St(e);return t.some(e=>$t.has(e))?80:t.some(e=>e.includes("pipeline"))?70:t.some(e=>Nt.has(e))?10:t.includes("any")?0:40}function Dt(e,t){let a=St(e),o=St(t);return a.some(e=>"any"!==e&&o.includes(e))?100:a.includes("any")||o.includes("any")?5:0}function qt(e,t){let a=0;return e.data.module===t.data.module&&(a+=24),e.data.category===t.data.category&&(a+=12),a}function Ft(e,t,a){return"output"===e&&t?{x:t.position.x+340,y:t.position.y}:"producer"===e&&a?{x:a.position.x-340,y:a.position.y}:t&&a?{x:Math.round((t.position.x+a.position.x)/2),y:Math.round((t.position.y+a.position.y)/2+140)}:{x:120,y:100}}function Gt(e,t){return(e.data.blockProjectionOwnerId??null)===(t.data.blockProjectionOwnerId??null)}function Wt(e){e={...e,nodes:Ct(e.nodes,e.registry)};let t=[],a=new Map(e.nodes.map(e=>[e.id,e])),o=new Set;for(let a of e.nodes){if(!B(a)||!a.data.blockInstanceV2)continue;for(let o of ee(a.data.blockInstanceV2,e.modularBlockDefinitions??[])){let n=e.nodes.find(({data:e,hidden:t})=>!t&&e.blockProjectionOwnerId===a.id&&e.blockProjectionNodeId===o.nodeId)?.id??a.id,i=zt("block-loop",a.id,o.nodeId,o.fieldId),d=Ce(a.data.blockInstanceV2,o.nodeId,e.modularBlockDefinitions??[]);t.push({id:i,kind:"block_structure",title:"Invalid loop connection",description:o.message,targetNodeId:n,targetHandle:o.fieldId,candidates:d?[{id:zt(i,"restore-one-edge"),issueId:i,title:"Reconnect the missing Loop Members link",description:`Restore only ${d.sourceNodeId} → ${d.targetNodeId}; keep all other edits.`,confidence:"choice",targetNodeId:n,operations:[{kind:"repair_block_loop",rootNodeId:a.id,targetNodeId:o.nodeId,graphHash:a.data.blockInstanceV2.effectiveGraph.graphHash}]}]:[]})}for(let o of De(a.data.blockInstanceV2,e.modularBlockDefinitions??[])){let n=e.nodes.find(({data:e,hidden:t})=>!t&&e.blockProjectionOwnerId===a.id&&e.blockProjectionNodeId===o.nodeId)?.id??a.id,i=zt("block-state-input",a.id,o.nodeId);t.push({id:i,kind:"missing_input",title:"Missing upstream inputs",description:o.message,targetNodeId:n,candidates:o.reconnectFrom.map(e=>({id:zt(i,e),issueId:i,title:`Reconnect Pipeline State from ${a.data.blockInstanceV2.effectiveGraph.nodes.find(t=>t.nodeId===e)?.data.label??e}`,description:"Reconnect one edge; keep all other edits.",confidence:"choice",targetNodeId:n,operations:[{kind:"repair_block_state",rootNodeId:a.id,sourceNodeId:e,targetNodeId:o.nodeId,graphHash:a.data.blockInstanceV2.effectiveGraph.graphHash}]}))})}let o=he(a.data.blockInstanceV2,e.modularBlockDefinitions??[]).filter(e=>e.canRepair);if(o.length){let e=zt("block-derived-control",a.id),n=[...new Set(o.map(({fieldId:e})=>e))].join(", "),i=`Repair ${o.length} bindings (${n}) to inherit derived values. Preserve prompts, requested values, layout and other wires.`;t.push({id:e,kind:"block_derived_control",title:"A mirrored control overwrites an upstream-derived value",description:i,targetNodeId:a.id,candidates:[{id:zt(e,"inherit-derived-state"),issueId:e,title:"Use the upstream-derived values",description:i,confidence:"choice",targetNodeId:a.id,operations:o.map(({nodeId:e,fieldId:t})=>({kind:"repair_block_derived_control",rootNodeId:a.id,sourceNodeId:e,fieldId:t}))}]})}for(let o of Ue(a.data.blockInstanceV2,e.modularBlockDefinitions??[])){if(!o.canRepair)continue;let n=e.nodes.find(({data:e,hidden:t})=>!t&&e.blockProjectionOwnerId===a.id&&e.blockProjectionNodeId===o.nodeId)?.id??a.id,i=zt("block-seed",a.id,o.nodeId);t.push({id:i,kind:"block_seed",title:"A saved seed is attached to a step that does not consume it",description:o.reason,targetNodeId:n,candidates:[{id:zt(i,"move-to-generator"),issueId:i,title:"Connect seed to its Generator consumer",description:o.reason,confidence:"choice",targetNodeId:n,operations:[{kind:"repair_block_seed",rootNodeId:a.id,sourceNodeId:o.nodeId}]}]})}}e.nodes.forEach(a=>{let o=function(e){if(!B(e))return null;try{return oe([e],[]),null}catch(e){return e instanceof Error?e.message:String(e)}}(a);if(!o)return;let n=function(e,t,a){return a.filter(({data:t})=>t.blockProjectionOwnerId===e.id&&"string"==typeof t.blockProjectionNodeId).sort((e,t)=>String(t.data.blockProjectionNodeId).length-String(e.data.blockProjectionNodeId).length).find(({id:e,data:a})=>{let o=String(a.blockProjectionNodeId);return t.includes(e)||t.includes(`"${o}"`)||t.includes(`'${o}'`)||t.includes(` ${o}:`)||t.includes(` ${o}.`)||t.endsWith(` ${o}`)})?.id??e.id}(a,o,e.nodes),i=zt("block-structure",a.id,o);t.push({id:i,kind:"block_structure",title:`${a.data.label||"Block"} has invalid internal structure`,description:o,targetNodeId:n,candidates:[{id:zt(i,"restore-reviewed"),issueId:i,title:"Restore reviewed internal structure",description:"Restore the registered nodes and links for this workflow instance. Compatible custom additions are retained; incompatible structural edits are removed.",confidence:"choice",targetNodeId:n,operations:[{kind:"restore_block_structure",rootNodeId:a.id}]}]})}),e.edges.forEach(n=>{let i=a.get(n.source),d=a.get(n.target),r=n.sourceHandle??"",s=n.targetHandle??"",l=V(i,r),c=V(d,s);if(!(i&&d&&r&&s&&l&&c)){let e=zt("broken-link",n.id);return void t.push({id:e,kind:"broken_link",title:"Remove a broken connection",description:"One connection points to a node or socket that no longer exists.",targetNodeId:d?.id,targetHandle:s||void 0,candidates:[{id:zt(e,"remove"),issueId:e,title:"Remove broken link",description:"Delete only the invalid connection.",confidence:"safe",targetNodeId:d?.id,targetHandle:s||void 0,operations:[{kind:"remove_edges",edgeIds:[n.id]}]}]})}if(!_t(l,c)||!we(i.data,r,d.data,s)){let a=zt("type-mismatch",n.id),o=function(e,t,a,o,n,i,d,r,s){return a.data.blockProjectionOwnerId||i.data.blockProjectionOwnerId?[]:Object.entries(t.registry).flatMap(([e,t])=>{if("group"===t.type||"loop"===t.type)return[];let s=Pt({data:t}).filter(([e,i])=>_t(n,i)&&we(a.data,o,t,e)).map(([e,t],a)=>({handle:e,param:t,order:a,score:Dt(n,t)})).sort((e,t)=>t.score-e.score).slice(0,3);if(!s.length)return[];let l=Tt({data:t}).filter(([e,a])=>_t(a,r)&&we(t,e,i.data,d)).map(([e,t],a)=>({handle:e,param:t,order:a,score:Dt(t,r)})).sort((e,t)=>t.score-e.score).slice(0,3);return s.sort((e,t)=>e.order-t.order),l.sort((e,t)=>e.order-t.order),s.flatMap(({handle:a,param:o})=>l.map(({handle:n,param:i})=>({key:e,data:t,bridgeInput:a,bridgeInputParam:o,bridgeOutput:n,bridgeOutputParam:i})))}).map(({key:t,data:l,bridgeInput:c,bridgeInputParam:u,bridgeOutput:p,bridgeOutputParam:f})=>{let m=`@${zt(e,t,c,p)}`;return{score:Dt(n,u)+Dt(f,r)+qt({data:l},i),candidate:{id:zt(e,"bridge",t,c,p),issueId:e,title:`Insert ${l.label||At(t)}`,description:`Replace the incompatible link with a typed conversion through ${l.label||"this node"}.`,confidence:"choice",targetNodeId:i.id,targetHandle:d,operations:[{kind:"remove_edges",edgeIds:[s]},{kind:"add_node",ref:m,nodeKey:t,position:Ft("bridge",a,i)},{kind:"connect",source:{nodeId:a.id,handle:o},target:{nodeId:m,handle:c}},{kind:"connect",source:{nodeId:m,handle:p},target:{nodeId:i.id,handle:d}}]}}}).filter(e=>e.score>30).sort((e,t)=>t.score-e.score).slice(0,3).map(e=>e.candidate)}(a,e,i,r,l,d,s,c,n.id);return o.push({id:zt(a,"remove"),issueId:a,title:"Remove incompatible link",description:"Disconnect the mismatched sockets and leave the input highlighted for another fix.",confidence:"safe",targetNodeId:d.id,targetHandle:s,operations:[{kind:"remove_edges",edgeIds:[n.id]}]}),void t.push({id:a,kind:"type_mismatch",title:`${Bt(i,r)} cannot connect to ${Bt(d,s)}`,description:_t(l,c)?"The selected model or component does not satisfy the receiving socket’s declared capabilities.":`The sockets use incompatible types: ${St(l).join(" or ")} and ${St(c).join(" or ")}.`,targetNodeId:d.id,targetHandle:s,candidates:o.slice(0,3)})}o.add(`${d.id}:${s}`)});let n=Et(e.nodes),i=n.filter(Ht),d=i.filter(t=>e.edges.some(e=>e.target===t.id&&a.has(e.source)));if(n.length>0&&0===d.length&&!n.some(Rt)){let a="missing-output",o=function(e,t,a){let o=Et(t.nodes),n=new Set(t.edges.map(e=>`${e.source}:${e.sourceHandle??""}`)),i=o.flatMap(e=>Tt(e).map(([t,a])=>({source:e,sourceHandle:t,sourceParam:a}))).filter(({source:e,sourceHandle:t})=>!n.has(`${e.id}:${t}`)).sort((e,t)=>Lt(t.sourceParam)-Lt(e.sourceParam)),d=a.flatMap(a=>Pt(a).flatMap(([o,n])=>i.filter(({source:e,sourceHandle:i,sourceParam:d})=>e.id!==a.id&&Gt(e,a)&&_t(d,n)&&we(e.data,i,a.data,o)&&!Vt(t.edges,a.id,e.id)).map(({source:t,sourceHandle:i,sourceParam:d})=>({score:Dt(d,n)+Lt(d),candidate:{id:zt(e,"connect-output",t.id,i,a.id,o),issueId:e,title:`Connect ${t.data.label||t.data.action} to ${a.data.label||a.data.action}`,description:`Send ${Bt(t,i)} to the existing output node.`,confidence:"safe",targetNodeId:a.id,targetHandle:o,operations:[{kind:"connect",source:{nodeId:t.id,handle:i},target:{nodeId:a.id,handle:o}}]}}))));return d.length?d.sort((e,t)=>t.score-e.score).slice(0,3).map(e=>e.candidate):Object.entries(t.registry).filter(([,e])=>Ht({data:e})).flatMap(([t,a])=>Pt({data:a}).flatMap(([o,n])=>i.filter(({source:e,sourceHandle:t,sourceParam:i})=>!e.data.blockProjectionOwnerId&&_t(i,n)&&we(e.data,t,a,o)).map(({source:i,sourceHandle:d,sourceParam:r})=>{let s=`@${zt(e,t,i.id)}`;return{score:Dt(r,n)+Lt(r),candidate:{id:zt(e,"add-output",t,i.id,d,o),issueId:e,title:`Add ${a.label||At(t)}`,description:`Create a visible output for ${Bt(i,d)}.`,confidence:"choice",targetNodeId:i.id,targetHandle:d,operations:[{kind:"add_node",ref:s,nodeKey:t,position:Ft("output",i,void 0)},{kind:"connect",source:{nodeId:i.id,handle:d},target:{nodeId:s,handle:o}}]}}}))).sort((e,t)=>t.score-e.score||e.candidate.title.localeCompare(t.candidate.title)).slice(0,3).map(e=>e.candidate)}(a,e,i);o.length&&t.push({id:a,kind:"missing_output",title:i.length?"Connect the graph output":"Add a graph output",description:i.length?"An output node exists, but no generated result reaches it.":"The graph needs a Preview, Save, or Export node to produce a usable result.",targetNodeId:i[0]?.id,candidates:o})}let r=new Set(i.map(e=>e.id));n.forEach(a=>{Pt(a).forEach(([n,i])=>{if(function(e,t){let a=e.data.blockInstanceV2;if(!B(e)||!a)return!1;let o=a.effectiveInterface.boundary.inputs.find(e=>e.portId===t);if(!o)return!1;let n=[o.binding,...o.mirrorBindings??[]];return a.effectiveInterface.controls.some(e=>[e.binding,...e.mirrorBindings??[]].some(e=>n.some(t=>t.nodeId===e.nodeId&&t.fieldOrPortId===e.fieldId)))}(a,n)||!Mt(n,i,!!a.data.skipParamsCheck)||o.has(`${a.id}:${n}`)||r.has(a.id)&&0===d.length)return;let s=zt("missing-input",a.id,n),l=function(e,t,a,o,n){let i=function(e,t,a,o,n){return Et(t.nodes).flatMap(e=>Tt(e).map(([t,a])=>({source:e,sourceHandle:t,sourceParam:a}))).filter(({source:e,sourceHandle:i,sourceParam:d})=>e.id!==a.id&&Gt(e,a)&&_t(d,n)&&we(e.data,i,a.data,o)&&!Vt(t.edges,a.id,e.id)).map(({source:t,sourceHandle:i,sourceParam:d})=>({score:Dt(d,n)+qt(t,a)-Math.min(40,Math.hypot(t.position.x-a.position.x,t.position.y-a.position.y)/50),candidate:{id:zt(e,"connect",t.id,i),issueId:e,title:`Connect ${t.data.label||t.data.action}`,description:`Use its ${Bt(t,i)} output for ${Bt(a,o)}.`,confidence:100===Dt(d,n)?"safe":"choice",targetNodeId:a.id,targetHandle:o,operations:[{kind:"connect",source:{nodeId:t.id,handle:i},target:{nodeId:a.id,handle:o}}]}})).sort((e,t)=>t.score-e.score).map(e=>e.candidate)}(e,t,a,o,n),d=function(e,t,a,o,n){return a.data.blockProjectionOwnerId?[]:Object.entries(t.registry).flatMap(([e,t])=>Tt({data:t}).map(([a,o])=>({key:e,data:t,sourceHandle:a,sourceParam:o}))).filter(({data:e,sourceHandle:t,sourceParam:i})=>!("group"===e.type||"loop"===e.type||!_t(i,n)||!we(e,t,a.data,o)||Ht({data:e}))).map(({key:t,data:i,sourceHandle:d,sourceParam:r})=>{let s=`${t} ${i.label} ${i.category}`.toLowerCase(),l=St(n),c=l.some(e=>e.includes("pipeline"))&&/load|pipeline/.test(s)?50:0,u=l.some(e=>$t.has(e))&&/load|source|import/.test(s)?35:0,p=St(r).includes("any")?45:0,f=`@${zt(e,t)}`;return{score:Dt(r,n)+qt({data:i},a)+c+u-p,candidate:{id:zt(e,"add",t,d),issueId:e,title:`Add ${i.label||At(t)}`,description:`Add this compatible source and connect it to ${Bt(a,o)}.`,confidence:"choice",targetNodeId:a.id,targetHandle:o,operations:[{kind:"add_node",ref:f,nodeKey:t,position:Ft("producer",void 0,a)},{kind:"connect",source:{nodeId:f,handle:d},target:{nodeId:a.id,handle:o}}]}}}).filter(e=>e.score>15).sort((e,t)=>t.score-e.score||e.candidate.title.localeCompare(t.candidate.title)).map(e=>e.candidate)}(e,t,a,o,n);return[...i,...d].slice(0,3)}(s,e,a,n,i);l.length&&t.push({id:s,kind:"missing_input",title:`${a.data.label||a.data.action} needs ${Bt(a,n)}`,description:"Connect an existing compatible value or add a matching source node.",targetNodeId:a.id,targetHandle:n,candidates:l})})});let s=new Set;(e.readinessIssues??[]).forEach(e=>{if("block_media_input_missing"===e.code||"operation_media_input_missing"===e.code){let a=zt(e.code,e.id);return void t.push({id:a,kind:"missing_media",title:e.message,description:e.details??e.message,targetNodeId:e.nodeId,targetHandle:e.fieldId,candidates:[{id:zt(a,"inspect"),issueId:a,title:"Open required input",description:"Select the affected node and its controls. Choose the file yourself; no mask, prompt or connection is changed automatically.",confidence:"safe",targetNodeId:e.nodeId,targetHandle:e.fieldId,operations:[{kind:"external",action:"inspect_node",nodeId:e.nodeId}]}]})}if("modular_component_requirement"!==e.code&&"modular_runtime_failure"!==e.code)if(["environment","package","backend","hardware_fit"].includes(e.category)&&"open_setup"===e.action){let a=`${e.category}:${e.message}`;if(s.has(a))return;s.add(a);let o=zt(e.category,e.message),n="package"===e.category?"Repair backend packages":"backend"===e.category?"Reconnect or update the backend":"hardware_fit"===e.category?"Review hardware compatibility":"Repair the runtime environment";t.push({id:o,kind:"environment",title:n,description:e.message,candidates:[{id:zt(o,"setup"),issueId:o,title:"Open Setup",description:e.details||"Review the detected runtime mismatch and its exact repair command.",confidence:"safe",operations:[{kind:"external",action:"open_setup"}]}]})}else if("model"!==e.category&&"model_integrity"!==e.category||!e.repoId&&!e.modelPath){if("asset"===e.category&&"select_image"===e.action){let a=`missing_media:${e.nodeId??""}:${e.message}`;if(s.has(a))return;s.add(a);let o=zt("missing-media",e.nodeId,e.message);t.push({id:o,kind:"missing_media",title:"Choose the required media",description:e.message,targetNodeId:e.nodeId,candidates:[{id:zt(o,"assets"),issueId:o,title:"Open Gallery",description:"Choose an imported image, video, or audio file without changing the graph automatically.",confidence:"safe",targetNodeId:e.nodeId,operations:[{kind:"external",action:"open_assets",nodeId:e.nodeId}]}]})}}else{let a=`missing_model:${e.nodeId??""}:${e.repoId??e.modelPath}`;if(s.has(a))return;s.add(a);let o=zt("missing-model",e.nodeId,e.repoId,e.modelPath);t.push({id:o,kind:"missing_model",title:"Choose or install the required model",description:e.message,targetNodeId:e.nodeId,candidates:[{id:zt(o,"models"),issueId:o,title:"Open Models",description:"Review compatible installed models or explicitly install the required model.",confidence:"safe",targetNodeId:e.nodeId,operations:[{kind:"external",action:"open_model_manager",nodeId:e.nodeId,repoId:e.repoId}]}]})}else t.push({id:zt(e.code,e.nodeId??"",e.message),kind:"block_structure",title:"modular_runtime_failure"===e.code?"Review the failed Modular block":"Review component compatibility",description:`${e.message} ${e.details??""}`,targetNodeId:e.nodeId,candidates:[]})});let l=t.filter(e=>e.candidates.length>0);return{issues:t,candidateCount:l.reduce((e,t)=>e+t.candidates.length,0),canFix:l.length>0}}function Ut(e,t){return t.get(e)??e}function Kt(e,t){if(!t)return{nodes:[],edges:[]};let a=new Map,o=[];t.operations.forEach(n=>{if("add_node"!==n.kind)return;let i=Me(n.nodeKey,e.registry,n.position);if(!i)return;let d=`graph-fix-preview-${t.id}-${a.size}`,r={...i,id:d,className:"modiff-graph-fix-ghost",draggable:!1,selectable:!1,deletable:!1};r.data.label=`${r.data.label} preview`,a.set(n.ref,d),o.push(r)});let n=[...e.nodes,...o];return{nodes:o,edges:t.operations.flatMap((e,o)=>{if("connect"!==e.kind)return[];let i=Ut(e.source.nodeId,a),d=Ut(e.target.nodeId,a);return n.some(e=>e.id===i)&&n.some(e=>e.id===d)?[{id:`graph-fix-preview-edge-${t.id}-${o}`,source:i,sourceHandle:e.source.handle,target:d,targetHandle:e.target.handle,type:"default",selectable:!1,focusable:!1,className:"modiff-graph-fix-ghost"}]:[]})}}function Yt(e){return e?e.operations.flatMap(e=>"connect"===e.kind?[e.source,e.target]:[]):[]}var Xt=e({default:()=>Qt});function Jt(e,t,a){return e.issues.find(e=>e.id===t)?.candidates.find(e=>e.id===a)}function Qt(){let e=w(e=>e.dialogOpen),t=w(e=>e.closeDialog),a=w(e=>e.setPreviewCandidate),n=w(e=>e.setIssueTargets),{nodes:d,edges:r}=U(W(e=>({nodes:e.nodes,edges:e.edges}))),s=L(e=>e.nodesRegistry),l=K(e=>e.library?.blockDefinitions);(0,lt.useEffect)(()=>{e&&!K.getState().loaded&&K.getState().fetchLibrary()},[e]);let c=z(e=>e.edgeType),u=z(e=>e.setLeftPanelOpen),p=z(e=>e.setLeftPanelTabIndex),f=z(e=>e.setRightPanelOpen),m=z(e=>e.setRightPanelTab),h=z(e=>e.setModelManagerOpener),g=ye(e=>e.saveActiveWorkflowTab),b=ye(e=>e.graphBinding),x=T(e=>e.sid),I=T(e=>e.isConnected),k=qe({sid:x,isConnected:I,includeStudio:!!b}),v=(0,lt.useMemo)(()=>Wt({nodes:d,edges:r,registry:s,modularBlockDefinitions:l,readinessIssues:k.issues}),[r,d,k.issues,s,l]),y=v.issues.map(e=>`${e.id}:${e.candidates.map(e=>e.id).join(",")}`).join("|"),j=(0,lt.useRef)(v);j.current=v;let[$,N]=(0,lt.useState)({}),[C,S]=(0,lt.useState)(!1);(0,lt.useEffect)(()=>{if(!e)return;let t=j.current,o=function(e){let t=e.issues.map(e=>({issue:e,candidate:e.candidates.find(e=>"safe"===e.confidence&&e.operations.length>0&&e.operations.every(e=>"external"===e.kind))})).find(e=>e.candidate);return Object.fromEntries(e.issues.map(e=>[e.id,t?e.id===t.issue.id?t.candidate?.id??"":"":e.candidates.find(e=>"safe"===e.confidence)?.id??""]))}(t);N(o);let i=t.issues[0],d=i?Jt(t,i.id,o[i.id]??""):null;a(d??null),n(t.issues.flatMap(e=>e.targetNodeId?[{nodeId:e.targetNodeId,handle:e.targetHandle}]:[]))},[e,y,n,a]);let _=(e,t={})=>{if("inspect_node"===e){let e=z.getState();f(!0),m("studio");let a=U.getState();if(t.nodeId&&a.nodes.some(e=>e.id===t.nodeId)){a.onNodesChange(a.nodes.map(e=>({id:e.id,type:"select",selected:e.id===t.nodeId})));let o=ye.getState().activeWorkflowTabId;o&&e.setWorkflowFocusRequest({workflowTabId:o,nodeId:t.nodeId,requestId:Date.now(),requestedAt:Date.now()})}return}if("open_setup"===e)return f(!0),void m("setup");u(!0),"open_assets"!==e?(p(3),h({nodeId:t.nodeId??null,fieldKey:null,focus:t.repoId?{repo:t.repoId,source:"graph"}:void 0})):p(2)},P=v.issues.map(e=>Jt(v,e.id,$[e.id]??"")).filter(e=>!!e),O=P.length>0&&P.every(e=>e.operations.every(e=>"external"===e.kind)),M=v.issues.filter(e=>!!$[e.id]).length,E=0===v.candidateCount;return(0,ct.jsx)(Ie,{open:e,onClose:t,title:"Fix graph",description:E?"Review the issue and correct the named node or connection.":"Review proposed repairs before applying them. Graph changes can be undone.",testId:"graph-fix-dialog",panelClassName:"max-w-2xl",footer:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(He,{onClick:t,children:"Cancel"}),!E&&(0,ct.jsx)(He,{tone:"primary",icon:(0,ct.jsx)(i,{size:15}),disabled:0===M||C,loading:C,onClick:()=>{let e=P;if(e.length&&!C){S(!0);try{let a=e.filter(e=>e.operations.some(e=>"external"!==e.kind)),o=v.issues.filter(e=>a.some(t=>t.issueId===e.id)),n=U.getState(),i=Te({nodes:n.nodes,edges:n.edges,registry:s,modularBlockDefinitions:l},e,c),d=a.length>0;d&&(U.getState().replaceGraph({nodes:i.nodes,edges:i.edges},{historyLabel:"Fix graph",clearRemovedCache:!1}),g(!0));let r=xe({sid:x,isConnected:I,includeStudio:!!b,showDialog:!1}),u=U.getState(),p=Wt({nodes:u.nodes,edges:u.edges,registry:s,modularBlockDefinitions:l,readinessIssues:r.issues}),f=d?o.filter(e=>p.issues.some(t=>function(e,t){return e.kind===t.kind&&(e.targetNodeId??null)===(t.targetNodeId??null)&&(e.targetHandle??null)===(t.targetHandle??null)}(e,t))):[];if(d&&f.length>0)throw Error(`The proposed change did not clear ${1===f.length?"the selected issue":`${f.length} selected issues`}. Review the updated graph before retrying.`);if(t(),i.externalActions.forEach(e=>_(e.action,{nodeId:e.nodeId,repoId:e.repoId})),d){let e=r.blocking.length;R(`${1===a.length?"Fix":`${a.length} fixes`} applied and verified.${e>0?` ${e} other blocking ${1===e?"issue remains":"issues remain"}.`:""} Undo is available.`,{variant:"success",autoHideDuration:3600})}}catch(e){R(e instanceof Error?e.message:"The graph changed before the fix could be applied.",{variant:"error",autoHideDuration:4200})}finally{S(!1)}}},"data-testid":"graph-fix-apply",children:O?P[0]?.title:"Apply "+(1===M?"fix":`${M} fixes`)})]}),children:0===v.issues.length?(0,ct.jsx)("div",{className:"rounded-modiff-panel border border-modiff-border bg-modiff-panel p-4 text-sm text-modiff-subtle-text",children:"No deterministic graph repair is currently available."}):(0,ct.jsxs)("div",{className:"grid gap-4",children:[(0,ct.jsx)("p",{className:"text-sm text-modiff-subtle-text",children:E?"No automatic repair is available. Your graph has not been changed.":O?"Review the detected issue and continue to the exact place where it can be resolved.":"Review the proposed connections. MoDiff will apply graph changes together as one undoable action."}),v.issues.map((e,t)=>(0,ct.jsxs)("section",{className:"rounded-modiff-panel border border-modiff-border bg-modiff-panel p-3","data-testid":`graph-fix-issue-${t}`,children:[(0,ct.jsxs)("div",{className:"flex items-start gap-2",children:[(0,ct.jsx)(o,{size:16,className:"mt-0.5 shrink-0 text-modiff-red"}),(0,ct.jsxs)("div",{className:"min-w-0",children:[(0,ct.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:e.title}),(0,ct.jsx)("p",{className:"mt-0.5 text-xs text-modiff-subtle-text",children:e.description})]}),$[e.id]&&(0,ct.jsx)(He,{className:"ml-auto shrink-0","aria-label":`Skip ${e.title}`,onClick:()=>{N(t=>({...t,[e.id]:""})),a(null)},children:"Skip"})]}),0===e.candidates.length?(0,ct.jsx)("p",{className:"mt-3 text-xs text-modiff-subtle-text",children:"Manual correction required. No automatic graph change will be applied for this issue."}):(0,ct.jsx)(Z,{className:"mt-3","aria-label":e.title,value:$[e.id]??"",options:e.candidates.map(e=>({value:e.id,label:e.title,description:e.description,meta:"safe"===e.confidence?"Direct":"Choice"})),onOptionPreview:t=>a(e.candidates.find(e=>e.id===t)??null),onValueChange:t=>{N(a=>({...a,[e.id]:t})),a(e.candidates.find(e=>e.id===t)??null)}})]},e.id))]})})}var Zt=(0,lt.lazy)(()=>$(()=>import(`./OperationOwnerControls.js?v=cc046c26d6fe7aa4`),[])),ea=(0,lt.memo)(e=>{let t=U(t=>t.nodes.find(t=>t.id===e.id)),a=(0,lt.useRef)(null),o=(0,lt.useRef)(null),n=(0,lt.useRef)(null),i=U(t=>Oe(t,e.id)),r=e.data.userBlockSnapshot,s=U(e=>e.toggleUserBlockExpanded),c=U(e=>e.setParamWithHistory),u=U(e=>e.replaceNodeParams),p=U(e=>e.setNodeSize),f=U(e=>e.beginHistoryTransaction),m=U(e=>e.commitHistoryTransaction),g=U(e=>e.setNodeUiState),v=U(e=>e.configureUserBlock),w=ye(e=>e.workflowTabs.find(t=>t.id===e.activeWorkflowTabId)?.title||"Workflow"),j=U(e=>e.fitUserBlockToChildren),$=U(t=>t.nodes.filter(t=>t.data.userBlockInstanceId===e.id).map(e=>`${e.id}:${e.position.x}:${e.position.y}:${e.measured?.width??e.width??0}:${e.measured?.height??e.height??0}`).sort().join("|")),N=h(),C=x(),S=E(e.id,a),_=(0,lt.useMemo)(()=>r?ze(r,{x:0,y:0},e.id):null,[r,e.id]),T=_?.data.userBlockSnapshot??r,O=(0,lt.useMemo)(()=>{let t=_?.data.params;if(!t)return e.data.params;let a={...t};return Object.entries(e.data.params).forEach(([e,o])=>{let n=t[e];a[e]=n?{...n,...o,fieldOptions:{...n.fieldOptions??{},...o.fieldOptions??{}}}:o}),a},[_?.data.params,e.data.params]),M=(0,lt.useMemo)(()=>T?me(T,O):[],[T,O]),B=(0,lt.useMemo)(()=>T?be(T):[],[T]),[A,V]=(0,lt.useState)(null),[z,L]=(0,lt.useState)(!1),[D,q]=(0,lt.useState)(!1),[G,W]=(0,lt.useState)(!1),[Y,X]=(0,lt.useState)(null),[J,Z]=(0,lt.useState)(null),[ee,te]=(0,lt.useState)(null),oe=K(e=>e.library),ne=(0,lt.useMemo)(()=>i?ue(U.getState(),e.id):{valid:!0,issues:[],insertionSuggestions:[]},[U(t=>{if(!i)return"";let a=new Set(t.nodes.filter(t=>t.data.userBlockInstanceId===e.id).map(e=>e.id));return`${t.nodes.filter(e=>a.has(e.id)).map(e=>JSON.stringify([e.id,e.parentId,e.data.type,Object.entries(e.data.params??{}).map(([e,t])=>[e,t.type,t.display,t.isInput,t.hidden,"pipeline_class"===e||"workflow_id"===e?t.value??t.default:void 0])])).sort().join("|")}\n${t.edges.filter(e=>a.has(e.source)||a.has(e.target)).map(e=>`${e.id}:${e.source}:${e.sourceHandle??""}:${e.target}:${e.targetHandle??""}`).sort().join("|")}`}),i,e.id]),ie=U(e=>e.insertNodeInUserBlock),re=(0,lt.useMemo)(()=>We(T,oe),[T,oe]),se=(0,lt.useCallback)((t,a,o)=>c(e.id,t,a,o),[e.id,c]),ce=(0,lt.useCallback)(()=>{s(e.id),ye.getState().saveActiveWorkflowTab(!0)},[e.id,s]),he=(0,lt.useCallback)(async t=>{if(!G){W(!0);try{let a=await tt({instanceId:e.id,choice:t,workflowTitle:w});L(!1),R("update"===t?"Reusable Block updated. Existing workflow instances keep their embedded snapshots.":"new"===t?`Saved as new Block: ${a.name}`:"Changes kept only in this workflow.",{variant:"success",autoHideDuration:3200})}catch(e){R(e instanceof Error?e.message:"Could not save the Block changes.",{variant:"error",autoHideDuration:4200})}finally{W(!1)}}},[e.id,G,w]),xe=(0,lt.useCallback)(()=>{let e=o.current?.offsetHeight??44,t=n.current?.offsetHeight??0;return Math.ceil(e+t+96+4)},[]),ke=(0,lt.useCallback)(()=>{if(i)return;let t=xe(),a=U.getState().nodes.find(t=>t.id===e.id);!a||Oe(U.getState(),e.id)||(a.height??0)>=t||(U.setState(a=>({nodes:a.nodes.map(a=>a.id===e.id?{...a,height:t,data:{...a.data,uiState:{...a.data.uiState,blockCollapsedHeight:t}}}:a)})),N(e.id),S())},[xe,i,e.id,S,N]);(0,lt.useEffect)(()=>{N(e.id)},[i,e.id,N]),(0,lt.useLayoutEffect)(()=>{if(i||(ke(),typeof ResizeObserver>"u"))return;let e=null,t=!0,a=new ResizeObserver(()=>{!t||null!==e||(e=window.requestAnimationFrame(()=>{e=null,t&&ke()}))});return o.current&&a.observe(o.current),n.current&&a.observe(n.current),()=>{t=!1,a.disconnect(),null!==e&&window.cancelAnimationFrame(e)}},[i,ke]),(0,lt.useEffect)(()=>{if(!i||!$)return;let t=window.requestAnimationFrame(()=>{j(e.id),S()});return()=>window.cancelAnimationFrame(t)},[$,i,j,e.id,S]),(0,lt.useEffect)(()=>{y(e.data.params,O)||u(e.id,O)},[O,e.data.params,e.id,u]);let ve=(0,lt.useCallback)(t=>{t.preventDefault(),t.stopPropagation(),f("Resize block");let o=a.current?.clientWidth||e.width||340,n=a.current?.clientHeight||e.height||360,i=t.clientX,d=t.clientY,r=C.getState().transform[2]||1,s=xe(),l=o,c=n,u=t=>{l=Math.min(720,Math.max(280,o+(t.clientX-i)/r)),c=Math.max(s,n+(t.clientY-d)/r),p(e.id,Math.round(l),Math.round(c)),S()},h=()=>{document.removeEventListener("mousemove",u),document.removeEventListener("mouseup",h),g(e.id,{blockCollapsedWidth:Math.round(l),blockCollapsedHeight:Math.round(c)}),m(),S()};document.addEventListener("mousemove",u),document.addEventListener("mouseup",h)},[f,xe,m,e.height,e.id,e.width,C,S,p,g]);return(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(H,{nodeId:e.id,label:e.data.label||T?.name||"Block",expanded:i,nodeRef:a,headerRef:o,connectorRef:n,onResizeStart:ve,actions:(0,ct.jsxs)(ct.Fragment,{children:[i?(0,ct.jsx)(ae,{className:P("nodrag nowheel",ne.valid?"text-modiff-green":"text-modiff-red"),size:"compact",label:ne.valid?"Inspect Block composition":`Inspect Block composition: ${ne.issues.length} issue${1===ne.issues.length?"":"s"}`,onClick:()=>q(!0),"data-testid":`user-block-composition-${e.id}`,children:(0,ct.jsx)(d,{size:15})}):null,(0,ct.jsx)(ae,{className:"nodrag nowheel",size:"compact",label:"Save Block changes",onClick:()=>L(!0),"data-testid":`user-block-save-choices-${e.id}`,children:(0,ct.jsx)(l,{size:15})}),(0,ct.jsx)(ae,{className:"nodrag nowheel",size:"compact",label:"Configure block instance",onClick:()=>{T&&V({name:T.name,inputLabels:Object.fromEntries(T.inputs.map(e=>[e.id,e.label])),outputLabels:Object.fromEntries(T.outputs.map(e=>[e.id,e.label])),exposedParamIds:new Set(T.exposedParams.map(e=>e.id))})},"data-testid":`user-block-configure-${e.id}`,children:(0,ct.jsx)(b,{size:15})}),(0,ct.jsx)(ae,{className:"nodrag nowheel",size:"compact",label:i?"Collapse block":"Expand block",onClick:ce,"data-testid":`user-block-toggle-${e.id}`,children:i?(0,ct.jsx)(k,{size:16}):(0,ct.jsx)(I,{size:16})})]}),controls:(0,ct.jsxs)("div",{className:"grid gap-2","data-testid":`user-block-disclosures-${e.id}`,children:[(0,ct.jsx)(lt.Suspense,{fallback:(0,ct.jsx)("p",{role:"status",children:"Loading model controls…"}),children:t?(0,ct.jsx)(Zt,{node:t}):null}),M.map(t=>(0,ct.jsxs)("section",{"data-block-source-node":t.id,children:[Object.keys(t.controlParams).length>0?(0,ct.jsx)(le,{label:t.label,"data-testid":`user-block-disclosure-${e.id}-${t.id}`,className:"rounded-modiff-compact border border-modiff-border-subtle bg-modiff-bg/60",buttonClassName:"min-h-8 text-xs",panelClassName:"grid gap-2 border-t border-modiff-border-subtle p-2",children:(0,ct.jsx)(fe,{nodeId:e.id,params:t.controlParams,updateStore:se,module:t.module,action:t.action,mode:"controls"})}):null,Object.keys(t.previewParams).length>0?(0,ct.jsxs)("div",{className:"mt-2 overflow-hidden rounded-modiff-compact border border-modiff-border-subtle bg-modiff-bg p-2","data-testid":`user-block-preview-${e.id}-${t.id}`,children:[(0,ct.jsx)("div",{className:"mb-2 truncate text-xs font-semibold text-modiff-subtle-text",children:t.label}),(0,ct.jsx)(fe,{nodeId:e.id,params:t.previewParams,updateStore:se,module:t.module,action:t.action,mode:"controls"})]}):null]},t.id))]}),connectors:(0,ct.jsx)(fe,{nodeId:e.id,params:O,updateStore:se,module:e.data.module,action:e.data.action,mode:"connectors"})}),(0,ct.jsx)(Ie,{open:D,onClose:()=>q(!1),title:"Block composition",testId:`user-block-composition-dialog-${e.id}`,panelClassName:"max-w-2xl",footer:(0,ct.jsx)(He,{onClick:()=>q(!1),children:"Close"}),children:(0,ct.jsxs)("div",{className:"grid gap-4 text-sm",children:[(0,ct.jsxs)("section",{className:"grid gap-1",children:[(0,ct.jsx)("div",{className:"font-semibold text-modiff-text",children:"Execution contract"}),(0,ct.jsx)("p",{className:"text-modiff-subtle-text",children:"This editable instance executes as an ordinary MoDiff graph. Socket types, container ownership, and sealed Modular workflow-state identity are checked before execution."}),"diffusers"===T?.origin?.provider?(0,ct.jsxs)("p",{className:"text-modiff-subtle-text",children:["Upstream identity: ",T.origin.definitionId||"imported Diffusers definition"," at"," ",T.origin.libraryRevision||T.origin.revision||"its pinned revision",". Ordinary node edits do not claim upstream block-tree parity; reviewed Modular block edits are rebuilt through Diffusers ",(0,ct.jsx)("code",{children:"init_pipeline()"}),"."]}):null]}),(0,ct.jsxs)("section",{className:"grid gap-2","data-testid":`user-block-composition-issues-${e.id}`,children:[(0,ct.jsx)("div",{className:"font-semibold text-modiff-text",children:ne.valid?"No structural connection issues found":`${ne.issues.length} structural issue${1===ne.issues.length?"":"s"}`}),ne.issues.map(e=>(0,ct.jsx)("div",{className:"rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/10 px-3 py-2 text-modiff-text",children:e.message},e.id))]}),(0,ct.jsxs)("section",{className:"grid gap-2","data-testid":`user-block-insertion-suggestions-${e.id}`,children:[(0,ct.jsx)("div",{className:"font-semibold text-modiff-text",children:"Compatible insertion points"}),ne.insertionSuggestions.length?ne.insertionSuggestions.slice(0,12).map(t=>(0,ct.jsxs)("div",{className:"flex items-center justify-between gap-3 rounded-modiff-compact border border-modiff-border bg-modiff-bg px-3 py-2",children:[(0,ct.jsx)("span",{className:"min-w-0 flex-1 text-modiff-subtle-text",children:t.label}),(0,ct.jsx)(He,{tone:"primary",onClick:()=>{try{ie(e.id,t),ye.getState().saveActiveWorkflowTab(!0),R("The node was inserted into the compatible execution path.",{variant:"success",autoHideDuration:2600})}catch(e){R(e instanceof Error?e.message:"The compatible insertion point is unavailable.",{variant:"error",autoHideDuration:3600})}},children:"Insert"})]},t.id)):(0,ct.jsx)("p",{className:"text-modiff-subtle-text",children:"No disconnected node currently has both an input and output that can safely replace an existing typed connection."})]}),re?(0,ct.jsxs)("section",{className:"grid gap-2","data-testid":`reviewed-modular-composition-${e.id}`,children:[(0,ct.jsx)("div",{className:"font-semibold text-modiff-text",children:"Reviewed upstream Modular blocks"}),(0,ct.jsxs)("p",{className:"text-modiff-subtle-text",children:["These actions validate an upstream block-tree modification against the exact pinned Diffusers definition. The backend applies the edit to ",(0,ct.jsx)("code",{children:"sub_blocks"})," and rebuilds its interface through"," ",(0,ct.jsx)("code",{children:"init_pipeline()"}),". The receipt is structural and does not silently replace this ordinary User Node execution graph."]}),(0,ct.jsx)("div",{className:"max-h-52 overflow-y-auto rounded-modiff-compact border border-modiff-border",children:re.definition.blockPlacements.map(e=>{let t=e.path.join(".");return(0,ct.jsxs)("div",{className:"flex items-center justify-between gap-3 border-b border-modiff-border px-3 py-2 last:border-b-0",children:[(0,ct.jsx)("span",{className:"min-w-0 flex-1 truncate text-modiff-subtle-text",children:t}),(0,ct.jsx)(He,{disabled:!!Y,onClick:()=>{X(t),te(null),Q(ge(re,e.path)).then(e=>Z(e)).catch(e=>te(e instanceof Error?e.message:"Could not rebuild the Modular composition.")).finally(()=>X(null))},children:Y===t?"Rebuilding…":"Validate duplicate"})]},t)})}),J?(0,ct.jsxs)("div",{className:"rounded-modiff-compact border border-modiff-green/40 bg-modiff-green/10 px-3 py-2 text-modiff-text","data-testid":`reviewed-modular-composition-receipt-${e.id}`,children:["Upstream ",(0,ct.jsx)("code",{children:"init_pipeline()"})," rebuild passed: ",J.composedPaths.length," ","block paths, ",J.inputs.length," inputs,"," ",J.outputs.length," outputs. Receipt"," ",J.receiptHash.slice(0,20),"…"]}):null,ee?(0,ct.jsx)("div",{className:"rounded-modiff-compact border border-modiff-red/40 bg-modiff-red/10 px-3 py-2 text-modiff-text",children:ee}):null]}):null]})}),(0,ct.jsx)(Ie,{open:z,onClose:()=>{G||L(!1)},title:"Save Block changes",testId:`save-user-block-choices-${e.id}`,panelClassName:"max-w-lg",footer:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(He,{disabled:G,onClick:()=>{he("workflow")},children:"Keep only in this workflow"}),(0,ct.jsx)(He,{disabled:G,onClick:()=>{he("new")},children:"Save as new Block"}),(0,ct.jsx)(He,{tone:"primary",disabled:G,onClick:()=>{he("update")},children:"Update existing Block"})]}),children:(0,ct.jsxs)("div",{className:"grid gap-3 text-sm text-modiff-subtle-text",children:[(0,ct.jsx)("p",{children:"The current topology, parameters, exposed ports, and model revision have been captured."}),(0,ct.jsxs)("p",{children:["Updating changes the reusable library definition but does not silently rewrite other workflow instances. Saving as new defaults to"," ",(0,ct.jsx)("strong",{children:F(T?.name||"Block",w)}),"."]})]})}),(0,ct.jsx)(Ie,{open:!!A,onClose:()=>V(null),title:"Configure block instance",testId:`configure-user-block-${e.id}`,panelClassName:"max-w-xl",footer:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(He,{onClick:()=>V(null),children:"Cancel"}),(0,ct.jsx)(He,{tone:"primary",disabled:!A?.name.trim(),onClick:()=>{A&&(v(e.id,A),V(null))},children:"Apply"})]}),children:A&&T?(0,ct.jsxs)("div",{className:"grid gap-4",children:[(0,ct.jsx)(pe,{label:"Name",required:!0,children:(0,ct.jsx)(Se,{value:A.name,onChange:e=>{let t=e.currentTarget.value;V(e=>e&&{...e,name:t})}})}),T.inputs.map(e=>(0,ct.jsx)(pe,{label:`Input: ${e.label}`,children:(0,ct.jsx)(Se,{value:A.inputLabels[e.id]??e.label,onChange:t=>{let a=t.currentTarget.value;V(t=>t&&{...t,inputLabels:{...t.inputLabels,[e.id]:a}})}})},`input-${e.id}`)),T.outputs.map(e=>(0,ct.jsx)(pe,{label:`Output: ${e.label}`,children:(0,ct.jsx)(Se,{value:A.outputLabels[e.id]??e.label,onChange:t=>{let a=t.currentTarget.value;V(t=>t&&{...t,outputLabels:{...t.outputLabels,[e.id]:a}})}})},`output-${e.id}`)),B.length>0?(0,ct.jsxs)("section",{className:"grid gap-2",children:[(0,ct.jsx)("h3",{className:"text-sm font-semibold text-modiff-text",children:"Editable parameters"}),B.map(e=>(0,ct.jsx)(de,{label:e.label,checked:A.exposedParamIds.has(e.id),onCheckedChange:t=>V(a=>{if(!a)return a;let o=new Set(a.exposedParamIds);return t?o.add(e.id):o.delete(e.id),{...a,exposedParamIds:o}})},e.id))]}):null]}):null})]})});ea.displayName="UserBlockNode";var ta=/(?:ModelsLoader|LoadPipeline|LoadModel|LoadTokenizer|LoadTextEncoder)$/u;function aa(e,t){let a=e.data.params[t];return void 0===a?.value?a?.default:a.value}function oa(e){let t=aa(e,"repo_id")??aa(e,"model_id");return t&&"object"==typeof t&&"value"in t?"string"==typeof t.value?t.value:"":"string"==typeof t?t:""}function na(e){let t=aa(e,"offload_mode")??(aa(e,"auto_offload")?"Auto Offload enabled":"Auto Offload disabled");return`${oa(e)||e.data.label||e.id} · ${aa(e,"dtype")??"runtime dtype"} · ${aa(e,"device")??"runtime device"} · ${t}`}function ia(e,t,a){let o=e.find(e=>e.id===a);if(a&&!o?.data.blockInstanceV2&&!o?.data.blockProjectionContainer)throw Error("Select a Block to assess its contained graph.");let n=oe(e,t,a),i=je(n.nodes),d=new Set(i.map(e=>e.id)),r=n.edges.filter(e=>d.has(e.source)&&d.has(e.target)),s=Re(e,t).nodes.filter(e=>e.data.blockInstanceV2),l=i.filter(e=>ta.test(e.data.action??"")),c=i.filter(e=>/lora|adapter/iu.test(`${e.data.action} ${e.data.module}`)),u=new Set,p=[],f=[],m=e=>{let t=new Set(e);for(let e=!0;e;){e=!1;for(let a of r)t.has(a.source)&&!t.has(a.target)&&(t.add(a.target),e=!0)}return t.size-e.length};for(let e of s){let t=e.data.blockInstanceV2,a=new Set(t.effectiveGraph.nodes.map(t=>Ve(e.id,t.nodeId))),n=l.filter(e=>a.has(e.id));if(!n.length)continue;n.forEach(e=>u.add(e.id));let d=[],s=Ae(t,"auto")??ie(t),c=s?{...s.form,resourceMode:"auto"}:null;1!==n.length&&(c=null,d.push("This Block has multiple model loaders. Its combined recipe needs a workflow resource contract.")),o?.data.blockProjectionContainer&&(c=null,d.push("This nested scope differs from its full source recipe; the full Block estimate cannot be reused."));let p=n[0];if(c){let e=oa(p),t=aa(p,"model_type");(e&&e!==c.modelRepo||t&&t!==c.modelType)&&(c=null,d.push("The actual loader differs from the source recipe. Its resource plan must be reviewed separately."))}if(c)for(let[e,t]of Object.entries({device:"device",dtype:"dtype",offload_mode:"offloadMode",auto_offload:"autoOffload",quantization_mode:"quantizationMode"})){let a=aa(p,e);void 0!==a&&typeof a==typeof c[t]&&(c[t]=a)}let h=r.filter(e=>a.has(e.target)&&!a.has(e.source));for(let e of h){let t=i.find(t=>t.id===e.source);d.push(`Connected input: ${t?.data.label??e.source} → ${e.targetHandle??"input"}. Its computed value is resolved when the graph runs.`)}"structure_changed"===t.customization.state&&d.push("Internal graph edits are present. The source model estimate does not qualify the edited recipe."),s||d.push("No exact resource planning form is available for this saved or custom Block."),f.push({id:e.id,label:e.data.label??t.definitionSnapshot.displayName,form:c,loaderIds:n.map(e=>e.id),consumers:m(n.map(e=>e.id)),details:[...new Set(d)],currentSettings:n.map(na)})}for(let e of l.filter(e=>!u.has(e.id)))f.push({id:e.id,label:e.data.label??e.id,form:null,loaderIds:[e.id],consumers:m([e.id]),details:["Use Check Auto execution plan for this standalone loader."],currentSettings:[na(e)]});(l.length>1||c.length)&&p.push("Check Auto execution plan for the combined model and adapter memory budget.");let h=ne(e,t,a);return{key:j({target:a??null,owners:s.flatMap(e=>{let t=e.data.blockInstanceV2,a=t.effectiveGraph.nodes.filter(t=>d.has(Ve(e.id,t.nodeId)));return a.length?[{id:e.id,definition:t.definitionRef,members:a}]:[]}),nodes:i.map(e=>({id:e.id,module:e.data.module,action:e.data.action,params:Object.fromEntries(Object.entries(e.data.params).filter(([,e])=>"output"!==e.display).map(([e,t])=>[e,{value:void 0===t.value?t.default??null:t.value,type:t.type??null}]))})),edges:r.map(({source:e,target:t,sourceHandle:a,targetHandle:o})=>({source:e,target:t,sourceHandle:a??null,targetHandle:o??null}))}),scopeLabel:o?.data.label??"Whole workflow",items:f,issues:p,autoReason:h.reason,autoEligible:h.eligible,nodeCount:i.length,loaderCount:l.length,adapterCount:c.length}}async function da(e,t){let a=e.items.filter(e=>e.form),[o,n]=await Promise.all([a.length?G(a.map(e=>e.form),a.map(e=>e.id),t):Promise.resolve([]),S(t)]),i={};for(let e of o){let t=a.find(t=>t.id===e.planKey);if(!t||i[t.id])throw Error("Resource assessment returned an unmatched or duplicate Block plan.");i[t.id]=e}if(Object.keys(i).length!==a.length)throw Error("Resource assessment returned an incomplete set of Block plans.");return{request:e,plans:i,resources:n}}var ra=[{value:"studio",label:"Studio",Icon:p},{value:"block",label:"Block",Icon:b},{value:"compatibility",label:"Compatibility",Icon:r},{value:"queue",label:"Queue",Icon:s},{value:"setup",label:"Setup",Icon:b},{value:"app",label:"Run as app",Icon:a}];function sa(){let e=ce(e=>e.target),t=z(e=>e.rightPanelTab),a=z(e=>e.setRightPanelTab),o=ye(e=>e.appModeConfigs),n=U(e=>e.nodes.some(e=>/preview|save|output|video|audio|image/i.test(`${e.data?.label??""} ${e.id}`))),i=ra.filter(a=>"studio"===a.value||"block"===a.value&&!!e||"queue"===a.value||"setup"===a.value||"compatibility"===a.value&&"compatibility"===t),d=ra.filter(e=>"app"===e.value&&(o.length>0||n)),r=d.find(e=>e.value===t),s=r?[...i,r]:i,l=s.some(e=>e.value===t)?t:"studio";return(0,lt.useEffect)(()=>{l!==t&&a(l)},[l,t,a]),(0,ct.jsxs)("div",{className:"flex h-full flex-col","data-testid":"workspace-panel",children:[(0,ct.jsxs)("div",{"aria-label":"Workspace panels",className:"flex min-h-[42px] flex-none overflow-x-auto overflow-y-hidden border-b border-modiff-border bg-modiff-bg",onWheel:Fe,children:[(0,ct.jsx)(Ge,{"aria-label":"Workspace panels",className:"min-w-0 flex-1 flex-nowrap gap-0",value:l,onValueChange:a,options:s.map(e=>{let t=e.Icon;return{value:e.value,id:`workspace-tab-${e.value}`,controls:`workspace-panel-${e.value}`,label:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(t,{size:15}),(0,ct.jsx)("span",{children:e.label})]}),testId:`workspace-tab-${e.value}`,className:"h-[42px] min-w-[72px] flex-none justify-center gap-1.5 rounded-none px-2 text-xs"}})}),d.length>0&&(0,ct.jsx)("div",{className:"flex h-[42px] flex-none items-center px-1","data-testid":"workspace-tabs-more",children:(0,ct.jsx)(N,{label:(0,ct.jsxs)(ct.Fragment,{children:[(0,ct.jsx)(m,{size:16}),(0,ct.jsx)("span",{className:"sr-only",children:"More panels"})]}),children:d.map(e=>{let t=e.Icon;return(0,ct.jsxs)(Ne,{active:l===e.value,onClick:()=>a(e.value),children:[(0,ct.jsx)(t,{size:15}),(0,ct.jsx)("span",{children:e.label})]},e.value)})})})]}),s.filter(e=>e.value!==l).map(e=>(0,ct.jsx)("div",{id:`workspace-panel-${e.value}`,role:"tabpanel","aria-labelledby":`workspace-tab-${e.value}`,hidden:!0},e.value)),(0,ct.jsxs)("div",{id:`workspace-panel-${l}`,role:"tabpanel","aria-labelledby":`workspace-tab-${l}`,className:"min-h-0 flex-1 overflow-y-auto overflow-x-hidden",children:["studio"===l&&(0,ct.jsx)(ot,{}),"block"===l&&(0,ct.jsx)(rt,{}),"compatibility"===l&&(0,ct.jsx)(nt,{}),"queue"===l&&(0,ct.jsx)(at,{}),"setup"===l&&(0,ct.jsx)(it,{}),"app"===l&&(0,ct.jsx)(dt,{})]})]})}var la=e({applyRuntimeWorkflowAutoSettingsV2:()=>Ia,applyWorkflowAutoSettingsV2:()=>xa,fetchWorkflowAutoPlanV2:()=>fa,inspectWorkflowAutoPlanV2:()=>ma,parseWorkflowAutoPlanV2:()=>pa,prepareWorkflowAutoExecutionV2:()=>ba}),ca=e=>!(!e||"object"!=typeof e||Array.isArray(e)),ua=e=>Array.isArray(e)&&e.every(e=>"string"==typeof e);function pa(e){if(!ca(e)||1!==e.schemaVersion||"boolean"!=typeof e.canAutoRun||!ua(e.issues)||"string"!=typeof e.message||![e.graphHash,e.plannedGraphHash].every(e=>"string"==typeof e&&/^sha256:workflow-auto-v1:[a-f0-9]{64}$/u.test(e))||!Array.isArray(e.patches)||!e.patches.every(e=>ca(e)&&"string"==typeof e.nodeId&&("auto_offload"===e.field&&"boolean"==typeof e.value||"offload_mode"===e.field&&"string"==typeof e.value))||!Array.isArray(e.loaders)||!e.loaders.every(e=>ca(e)&&["nodeId","modelType","repository","candidateId"].every(t=>"string"==typeof e[t])&&ua(e.consumers))||!ca(e.requirements)||!Object.values(e.requirements).every(e=>"number"==typeof e&&Number.isFinite(e)&&e>=0)||!ca(e.available)||!Object.values(e.available).every(e=>null===e||"number"==typeof e&&Number.isFinite(e)&&e>=0)||"boolean"!=typeof e.sharedMemory||void 0!==e.requiresPreparation&&"boolean"!=typeof e.requiresPreparation||void 0!==e.preparationNodeIds&&!ua(e.preparationNodeIds)||e.canAutoRun&&e.issues.length||!e.canAutoRun&&e.patches.length)throw Error("The backend returned an invalid workflow Auto plan.");return e}async function fa(e,t){return A(`${_e.serverAddress}/auto_resource/workflow`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({schemaVersion:1,graph:{nodes:e.nodes,paths:e.paths,...e.loops?{loops:e.loops}:{}}}),timeoutMs:12e4,signal:t,parse:pa})}async function ma(e,t){let a=U.getState(),{prepareLegacyGraphForAutoV2:o}=await $(async()=>{let{prepareLegacyGraphForAutoV2:e}=await import(`./studio-templates.js?v=cc046c26d6fe7aa4`).then(e=>e.Gn);return{prepareLegacyGraphForAutoV2:e}},[]),n=o(a.nodes,a.edges,e),i=a.exportGraph("",e?n.remapped.get(e)??e:void 0,{randomizeSeeds:!1,sourceGraph:n});return n.nodes.some(e=>e.data.blockInstanceV2?.effectiveGraph.nodes.some(e=>"upstream_block"===e.modularDiffusers?.kind))?fa((await(await $(async()=>{let{prepareModularCompositionExecutionV2:e}=await import(`./studio-templates.js?v=cc046c26d6fe7aa4`).then(e=>e.g);return{prepareModularCompositionExecutionV2:e}},[])).prepareModularCompositionExecutionV2(a,n.nodes))(i),t):fa(i,t)}function ha(){let e=U.getState().toObject();return j({nodes:e.nodes.map(e=>({id:e.id,data:e.data})),edges:e.edges})}function ga(e){let t=new Map(e.effectiveInterface.controls.map(e=>[e.controlId,[e.binding,...e.mirrorBindings??[]]]));for(let a of e.effectiveInterface.boundary.inputs)t.has(a.portId)||t.set(a.portId,[a.binding,...a.mirrorBindings??[]].map(({nodeId:e,fieldOrPortId:t})=>({nodeId:e,fieldId:t})));return[...t].map(([e,t])=>({logicalId:e,bindings:t}))}async function ba(e,t){let a,o=ye.getState().activeWorkflowTabId,n=ha(),i=()=>o===ye.getState().activeWorkflowTabId&&n===ha()&&"auto"===ye.getState().form.resourceMode;try{if(a=await fa(e),!i())throw Error("The workflow or mode changed while Auto was planning. Run again with the current graph.");if(!a.canAutoRun)throw Error(`Auto cannot run this workflow: ${a.issues.join(" ")}`)}catch(e){throw i()&&$e.getState().showIssues([{id:"workflow-auto-planning",category:"hardware_fit",severity:"error",blocking:!0,message:C(e,"Workflow memory planning failed."),details:"No run was submitted. Review the requirements and model/memory settings, then Run again for a fresh plan."}]),e}let d=xa(e,a.patches,t),r=U.getState().nodes.flatMap(e=>{let t=e.data.blockInstanceV2;return t?ga(t).flatMap(({bindings:t})=>t.length<2||!t.every(e=>"offload_mode"===e.fieldId||"auto_offload"===e.fieldId)?[]:[t.map(t=>({nodeId:Ve(e.id,t.nodeId),field:t.fieldId}))]):[]});return{...d,runtimeHints:{workflowAutoPlan:{schemaVersion:1,graphHash:a.plannedGraphHash,...r.length?{resourceControlGroups:r}:{}},resourceMode:"auto"}}}function xa(e,t,a){let o=structuredClone(e);U.getState().beginHistoryTransaction("Apply workflow Auto resource settings");try{a&&U.setState({nodes:a.nodes,edges:a.edges});let n=new Set;for(let a of t){let i=`${a.nodeId}.${a.field}`,d=o.nodes[a.nodeId]?.params[a.field];if(n.has(i)||!d||d.sourceId)throw Error("The Auto plan targets a missing, connected or repeated resource field.");n.add(i),d.value=a.value;let r=U.getState(),s=r.nodes.find(e=>e.data.blockInstanceV2?.effectiveGraph.nodes.some(t=>Ve(e.id,t.nodeId)===a.nodeId));if(!s?.data.blockInstanceV2){if(!r.nodes.some(e=>e.id===a.nodeId))throw Error("The loader is inside an older Block. Move it into a current Block before applying Auto settings.");r.setParamWithHistory(a.nodeId,a.field,a.value);continue}let l=s.data.blockInstanceV2,c=l.effectiveGraph.nodes.find(e=>Ve(s.id,e.nodeId)===a.nodeId),u=ga(l).filter(e=>e.bindings.some(e=>e.nodeId===c.nodeId&&e.fieldId===a.field));if(u.length){for(let o of u.flatMap(e=>e.bindings)){let n=Ve(s.id,o.nodeId),i=e.nodes[n]?.params[o.fieldId],d=t.find(e=>e.nodeId===n&&e.field===o.fieldId);if(!i||i.sourceId||(d?.value??i.value)!==a.value)throw Error("A shared resource control would change a node outside this Auto plan. Configure that control separately.")}for(let e of u)r.setBlockInstanceValueV2(s.id,e.logicalId,a.value)}else{let e=structuredClone(l.effectiveGraph),t=e.nodes.find(e=>e.nodeId===c.nodeId).data.params;t[a.field]={...t[a.field],value:a.value};let o=Pe(ke(re(l,e),{selected:s.selected}));U.setState({nodes:[...r.nodes.filter(e=>e.id!==s.id&&e.data.blockProjectionOwnerId!==s.id),...o.nodes],edges:[...r.edges.filter(e=>e.data?.blockProjectionOwnerId!==s.id),...o.edges]})}}U.getState().commitHistoryTransaction()}catch(e){throw U.getState().cancelHistoryTransaction(),e}return o}function Ia(e){let t=U.getState().exportGraph("",void 0,{randomizeSeeds:!1});for(let a of e){let e=t.nodes[a.nodeId]?.params[a.field];if(!e||e.sourceId||(e.value??null)!==(a.previousValue??null))throw Error("The run changed resource settings, but your newer workflow edits were preserved. The run details show the settings used.")}xa(t,e)}export{ea as a,yt as c,st as d,ia as i,kt as l,sa as n,Xt as o,da as r,jt as s,la as t,It as u}; \ No newline at end of file diff --git a/web/assets/connectEncodingNode.js b/web/assets/connectEncodingNode.js new file mode 100644 index 00000000..b2ca5365 --- /dev/null +++ b/web/assets/connectEncodingNode.js @@ -0,0 +1 @@ +import{Ei as e,Io as t,Jo as o,Po as n,Si as r,Ti as i,Vr as a,Wo as d,Ys as s,sc as c}from"./studio-templates.js?v=cc046c26d6fe7aa4";import{n as p,t as l}from"./operationGraphTransaction.js?v=cc046c26d6fe7aa4";import{t as g}from"./operationStarterRequest.js?v=cc046c26d6fe7aa4";import{r as h}from"./operationAuthoring.js?v=cc046c26d6fe7aa4";import{n as u}from"./encodingImageRoute.js?v=cc046c26d6fe7aa4";function f(e,o,n,r){let i=t(o.targetHandle)?o.target:o.source,a=u(e.nodes.find(e=>e.id===i)?.data.blockInstanceV2,n,r);if(!a)throw Error("This model has no unambiguous supported image encoder for this connection. Nothing changed.");let s=d(e,i);if(1!==s.length)throw Error("Connect Encode Inputs to one model loader before connecting its image encoding ports. Nothing changed.");return{encodingId:i,operation:a,owner:s[0]}}function m(e,o,r,i){if(i.pipelineClass!==r.operation.pipelineClass||i.task!==r.operation.task)throw Error("The image encoding route changed. Nothing changed.");let a=h(e,r.owner.id,i,{preserveValues:!0});if(a.review.required)throw Error(`Cannot connect without changing existing work: ${a.review.attention.join(" ")} Nothing changed.`);let d=a.graph;function p(e,o,n){if(!o)throw Error("The connection is missing a socket.");if(!t(o))return{id:a.replacements[e]??e,handle:o};let i=d.nodes.find(t=>t.id===e)?.data.blockInstanceV2,s=o.slice(o.indexOf(":")+1),c=i?.effectiveInterface.boundary["input"===n?"inputs":"outputs"].filter(e=>e.binding.fieldOrPortId===s&&i.effectiveGraph.nodes.some(t=>t.nodeId===e.binding.nodeId&&t.data.operationAuthoring?.operation?.operationId===r.operation.operationId));if(1!==c?.length)throw Error("The prepared encoder does not expose the selected socket. Nothing changed.");return{id:e,handle:c[0].portId}}let l=p(o.source,o.sourceHandle,"output"),g=p(o.target,o.targetHandle,"input"),u=n(d.nodes.find(e=>e.id===l.id),l.handle),f=n(d.nodes.find(e=>e.id===g.id),g.handle);if(!u||!f||"output"!==u.display||"input"!==f.display&&!f.isInput||!s(u.type,f.type))throw Error("These sockets are no longer compatible. Nothing changed.");d.edges=d.edges.filter(e=>e.target!==g.id||e.targetHandle!==g.handle),d.edges.push({id:`edge-${c()}`,source:l.id,sourceHandle:l.handle,target:g.id,targetHandle:g.handle,type:"default"});let m=new Set,w=[g.id];for(;w.length;){let e=w.pop();if(e===l.id)throw Error("That connection would create a cycle. Nothing changed.");if(!m.has(e)){m.add(e);for(let t of d.edges)t.source===e&&w.push(t.target)}}return d}function w(e){let t=e.data.params.execution_profile_id?.value??e.data.params.execution_profile_id?.default;if(!o(e))throw Error("The model loader no longer exists.");return"string"==typeof t&&t?t:void 0}async function I(t,o,n){let d=e(),s=a.getState().toObject(),c=p(s);o&&s.nodes.push(o);let h=r.getState(),u=f(s,t,h.operationContracts,h.pipelineSupport),I=await g(u.operation.pipelineClass,u.operation.task,h.operationContracts,n,w(u.owner));if(!n?.aborted){if(i(d,{includeForm:!1}),p(a.getState().toObject())!==c)throw Error("The workflow changed while preparing this connection. Connect again. Nothing changed.");l(m(s,t,u,I),d,c,"Connect encoding input/output")}}export{I as connectEncodingNode}; \ No newline at end of file diff --git a/web/assets/controlledWorkflows.js b/web/assets/controlledWorkflows.js index 6c07b474..ec4b73fd 100644 --- a/web/assets/controlledWorkflows.js +++ b/web/assets/controlledWorkflows.js @@ -1 +1 @@ -import{Er as e,Hi as t,Ro as o,Ua as i,Ya as a,Yi as n,_r as d,ai as r,br as s,ii as l,io as u,kr as c,ni as p,ta as g,ui as f,vr as m,yr as h}from"./studio-templates.js?v=5bcd315505f5670a";var y={aspectRatio:"Aspect",attentionKwargsJson:"Attention kwargs",autoOffload:"Auto-offload",conditioningScale:"Conditioning",controlImage:"Control image",controlVideo:"Control video",device:"Device",dtype:"Precision",fps:"FPS",guidanceScale:"Guidance",guidanceScale2:"Guidance 2",height:"Height",maskImage:"Mask image",maskVideo:"Mask video",maxSequenceLength:"Max tokens",modelType:"Model",mode:"Task",negativePrompt:"Negative prompt",numFrames:"Frames",outputType:"Output type",offloadMode:"Offload mode",processingResolution:"Processing resolution",matchInputResolution:"Match input resolution",speechLanguage:"Speech language",speechTimestamps:"Speech timestamps",speechChunkSeconds:"Speech chunk length",speechStrideSeconds:"Speech chunk stride",outpaintBottom:"Outpaint bottom",outpaintFeather:"Outpaint feather",outpaintFillColor:"Outpaint fill",outpaintLeft:"Outpaint left",outpaintOverlap:"Outpaint overlap",outpaintRight:"Outpaint right",outpaintTop:"Outpaint top",prompt:"Prompt",quantizationMode:"Quantization",randomSeed:"Random seed",referenceImages:"References",resourceMode:"Resource mode",seed:"Seed",sourceVideo:"Source video",steps:"Steps",strength:"Strength",width:"Width"};function v(e){return y[e]??String(e)}function _(e){return Array.isArray(e)?e.length>0?e.join(", "):"empty":"boolean"==typeof e?e?"on":"off":null==e||""===e?"empty":"object"==typeof e?JSON.stringify(e):String(e)}function S(e,t,o){return o.filter(o=>!u(e[o],t[o])).map(o=>({key:o,label:v(o),before:e[o],after:t[o]}))}function w(e,t,i){if(0===t.length)return;let a=Date.now(),n={id:o(),label:e,fields:t,createdAt:a};f.getState().setRecentChange(n);let d=`${e}: ${t.map(e=>e.label).slice(0,4).join(", ")}`;Array.from(new Set(i)).forEach(e=>{c.getState().setNodeUiState(e,{recentChangeLabel:d,recentChangeAt:a})}),window.setTimeout(()=>{let e=f.getState();e.recentChange?.createdAt===a&&e.setRecentChange(null),c.getState().nodes.forEach(e=>{e.data.uiState?.recentChangeAt===a&&c.getState().setNodeUiState(e.id,{recentChangeLabel:void 0,recentChangeAt:void 0})})},4500)}var x={source:"hub",value:"nateraw/real-esrgan/RealESRGAN_x2plus.pth",revision:"42efb9c3eeed1f5c0c8a626cf5f7f4481dfbb094",sha256:"49fafd45f8fd7aa8d31ab2a22d14d91b536c34494a5cfe31eb5d89c2fa266abb",byteSize:67061725};function b(e){return JSON.parse(JSON.stringify(e))}function q(e){return`${e.data.module}.${e.data.action}`}function k(e){if(e)return c.getState().nodes.find(t=>t.id===e)}function R(e,t){let o=k(e);if(o)return t.find(e=>o.data.params[e])}function V(e,t,o,i="value"){let a=R(e,t);return!(!e||!a||(c.getState().setParam(e,a,o,i),0))}function E(e,t){V(e,["source_start_seconds"],t.sourceStartSeconds),V(e,["source_duration_seconds"],t.sourceDurationSeconds),V(e,["target_duration_seconds"],t.targetDurationSeconds),V(e,["delay_seconds"],t.delaySeconds??0),V(e,["target_sample_rate"],t.targetSampleRate??48e3),V(e,["fade_in_seconds"],t.fadeInSeconds??0),V(e,["fade_out_seconds"],t.fadeOutSeconds??0)}async function C(e){let t=p.getState(),o=e.filter(e=>!t.nodesRegistry[e]);return(0===Object.keys(t.nodesRegistry).length||o.length>0)&&await t.fetchNodes(),e.filter(e=>!p.getState().nodesRegistry[e])}function A(e,t){return c.getState().nodes.find(o=>q(o)===e&&o.data.studioRole===t)}function P(e){if(0===e.length)return;let t=new Set(e);c.setState(e=>({nodes:e.nodes.filter(e=>!t.has(e.id)),edges:e.edges.filter(e=>!t.has(e.source)&&!t.has(e.target))})),c.getState().updateHandleConnectionStatus()}function L(e,t,i){let a=c.getState().nodes.filter(o=>o.data.studioRole===t&&q(o)!==e).map(e=>e.id);a.length>0&&P(a);let n=A(e,t);if(n)return n.data.studioOwned||c.setState(e=>({nodes:e.nodes.map(e=>e.id===n.id?{...e,data:{...e.data,studioOwned:!0}}:e)})),n.id;let d=p.getState().nodesRegistry[e];if(!d)throw Error(`Missing MoDiff node registry entry: ${e}`);let r=function(e){let t=e.lastIndexOf(".");return{module:e.slice(0,t),action:e.slice(t+1)}}(e),s={id:o(),type:d.type,position:i,data:{...b(d),module:d.module||r.module,action:d.action||r.action,studioRole:t,studioOwned:!0}};return c.getState().addNode(s),s.id}function I(e,t,o,i){if(!e||!o)return!1;let a=R(e,t),n=R(o,i);if(!a||!n)return!1;let d=c.getState();return!!d.edges.some(t=>t.source===e&&t.target===o&&t.sourceHandle===a&&t.targetHandle===n)||(d.onConnect({source:e,sourceHandle:a,target:o,targetHandle:n,edgeType:g.getState().edgeType}),c.getState().edges.some(t=>t.source===e&&t.target===o&&t.sourceHandle===a&&t.targetHandle===n))}function O(e,t,o){let i=k(e);return i?{x:i.position.x+o.x,y:i.position.y+o.y}:t}function M(e,t,o){let i=new Set(c.getState().nodes.map(e=>e.id)),n=m(e,t);try{let e=o();h(n);let t=[...i].filter(e=>!c.getState().nodes.some(t=>t.id===e));return t.length>0&&a(t).catch(e=>console.error("Failed to delete replaced node cache",e)),e}catch(e){throw d(n),e}}async function z(o=f.getState().form,a,n={}){let d=n.workflowContext??r();if(l(d),n.graphPrepared||await s(o,d),l(d),!await e(750,5e3,d))throw Error("The Studio graph schema did not settle before adding the LoRA adapter block.");l(d);let u=f.getState().graphBinding,p=!!u?.nodes.diffusersImagePipeline,g=!!u?.nodes.audioPipeline,m=g?t.audioLora:p?t.directLora:t.lora,h=await C([m]);if(l(d),h.length>0)throw Error(`LoRA block is unavailable because the backend registry is missing ${h.join(", ")}.`);let y=u?.nodes.models,v=u?.nodes.diffusersImagePipeline,_=u?.nodes.diffusersImageInpaint??u?.nodes.diffusersImageControl??u?.nodes.diffusersImageEdit??u?.nodes.diffusersImageGenerate,S=u?.nodes.audioPipeline,w=u?.nodes.audioGenerate;if(!(y||v&&_||S&&w))throw Error("Create a Studio graph before adding a LoRA adapter block.");let x=a?[a,...a.additionalAdapters??[]]:[];if(x.length>1&&!p)throw Error("Multiple LoRA adapters currently require the Diffusers image pipeline.");let b=x.length>0?x:[void 0],q=O(y??v??S,{x:-520,y:180},{x:0,y:270}),k=M(d,g?"lora.diffusers-audio.v1":p?"lora.diffusers-image.v1":"lora.modular.v1",()=>{let e=new Set(b.map((e,t)=>0===t?"loraAdapter":`loraAdapter:${t}`)),t=c.getState().nodes.filter(t=>"string"==typeof t.data.studioRole&&t.data.studioRole.startsWith("loraAdapter:")&&!e.has(t.data.studioRole)).map(e=>e.id);t.length>0&&P(t);let o=b.map((e,t)=>{let o=L(m,0===t?"loraAdapter":`loraAdapter:${t}`,{x:q.x+310*t,y:q.y});return V(o,["scale"],e?.scale??1),V(o,["replace_existing"],0===t),e?.model&&(V(o,["model","adapter_path"],{source:e.model.source,value:e.model.value}),V(o,["revision"],e.model.revision??""),V(o,["expected_sha256"],e.model.sha256??"")),e?.weightName&&V(o,["weight_name"],e.weightName),e?.adapterName&&V(o,["adapter_name"],e.adapterName),o}),i=o[0];g&&a?.baseModel&&(V(S,["model_id","model"],{source:a.baseModel.source,value:a.baseModel.value}),V(S,["revision"],a.baseModel.revision??"")),a?.schedulerClass&&V(i,["scheduler_class"],a.schedulerClass),a?.schedulerConfig&&V(i,["scheduler_config"],JSON.stringify(a.schedulerConfig));let n=!1;if(p||g){let e=c.getState(),t=v??S,i=_??w,a=e.edges.filter(e=>e.source===t&&(e.target===i||o.includes(e.target))||o.includes(e.source)&&(e.target===i||o.includes(e.target))).map(e=>e.id);a.length>0&&e.removeEdges(a),n=I(t,["pipeline"],o[0],["pipeline"]);for(let e=1;e0)throw Error(`Upscale block is unavailable because the backend registry is missing ${d.join(", ")}.`);a.graphPrepared||await s(e,n),l(n);let u=f.getState().graphBinding,p=u?.nodes.decode,g=u?.nodes.diffusersImageGenerate??u?.nodes.diffusersImageEdit??u?.nodes.diffusersImageInpaint??u?.nodes.diffusersImageControl,m=u?.nodes.wanGenerate,h=u?.nodes.videoExport,y=A(t.qualityVideoGenerate,"qualityVideoGenerate"),v=A(t.qualityVideoRetain,"qualityVideoRetain"),_=A(t.videoCompose,"videoCompose")?.id,S=A(t.exportWithAudio,"exportWithAudio")?.id,w=_??y?.id??m;if(!(p||g||y&&v||m&&h))throw Error("Create a Studio graph before adding an Upscale block.");let x=M(n,y?"upscale.quality-loop.v1":h?"upscale.video.v1":"upscale.image.v1",()=>{let i=L(t.upscaler,"upscaler",O(p??g??w,{x:930,y:170},{x:330,y:250}));if(V(i,["device"],e.device),o?.model&&V(i,["model_id"],o.model),void 0!==o?.downscale&&V(i,["downscale"],o.downscale),y&&v){G(y.id,v.id,["video_out"],["video"]);let e=I(y.id,["video_out"],i,["image"]),t=I(i,["output","image"],v.id,["video"]);if(!e||!t)throw Error("Quality-video upscale nodes were created, but their frame handles are not ready yet.");return y.parentId&&c.getState().setNodeLoopParent(i,y.parentId),{nodeId:i,message:"Upscale block added inside the per-shot quality loop"}}if(w&&h){let e=S??h,t=c.getState().edges.filter(t=>(t.source===m||t.source===_)&&t.target===e).map(e=>e.id);t.length>0&&c.getState().removeEdges(t);let o=I(w,_?["video"]:["video_out"],i,["image"]),a=I(i,["output","image"],e,["video"]);if(!o||!a)throw Error("Video upscale nodes were created, but video/image handles are not ready yet.");return{nodeId:i,message:"Upscale block added before the Studio video export"}}let a=L(t.preview,"upscalePreview",O(i,{x:1260,y:170},{x:330,y:0})),n=I(p??g,["images","image","output"],i,["image"]),d=I(i,["output","image"],a,["image"]);if(!n||!d)throw Error("Upscale nodes were created, but image handles are not ready yet. Try Add Upscale again after the registry finishes refreshing.");return{nodeId:i,message:"Upscale block added after Studio image generation"}});return!1!==a.notify&&i(x.message,{variant:"success",autoHideDuration:2200}),x.nodeId}function G(e,t,o,i){if(!e||!t)return;let a=c.getState().edges.filter(o=>o.source===e&&o.target===t).filter(e=>!o||o.includes(e.sourceHandle??"")).filter(e=>!i||i.includes(e.targetHandle??"")).map(e=>e.id);a.length>0&&c.getState().removeEdges(a)}function F(e){if(!e)return;let t=c.getState().edges.filter(t=>t.source===e||t.target===e).map(e=>e.id);t.length>0&&c.getState().removeEdges(t)}function Q(e,t,o){V(e,["mode"],"text_to_video"),V(e,["prompts_json"],o),V(e,["negative_prompt"],t.negativePrompt),V(e,["width"],t.width),V(e,["height"],t.height),V(e,["num_frames"],t.numFrames),V(e,["frame_rate"],t.fps),V(e,["num_inference_steps"],t.steps),V(e,["guidance_scale"],t.guidanceScale),V(e,["seed"],t.seed),V(e,["output_type"],t.outputType),V(e,["max_sequence_length"],t.maxSequenceLength)}async function j(e,o,i={}){let a=i.workflowContext??r();l(a);let n=await C([t.videoSequence,t.videoCompose]);if(l(a),n.length>0)throw Error(`Video sequence block is missing ${n.join(", ")}.`);i.graphPrepared||await s(e,a),l(a);let d=f.getState().graphBinding,u=d?.nodes.wanPipeline,p=d?.nodes.wanGenerate,g=d?.nodes.videoExport,m=A(t.upscaler,"upscaler")?.id,h=A(t.exportWithAudio,"exportWithAudio")?.id;if(!u||!g)throw Error("Video sequence requires a Studio video graph.");return M(a,"video-sequence.v1",()=>{let i=L(t.videoSequence,"videoSequence",O(u,{x:-100,y:-80},{x:390,y:0})),a=L(t.videoCompose,"videoCompose",O(i,{x:300,y:-80},{x:390,y:0}));Q(i,e,o?.promptsJson??"[]"),p&&c.getState().setNodeUiState(p,{disabled:!0}),V(a,["fps"],e.fps),V(a,["transition_seconds"],o?.transitionSeconds??.35),F(p);let n=h??g;if(!I(u,["pipeline"],i,["pipeline"])||!I(i,["clips"],a,["clip_1"])||!(m?I(a,["video"],m,["image"])&&I(m,["output","image"],n,["video"]):I(a,["video"],n,["video"])))throw Error("Video sequence nodes were created, but their media handles are unavailable.");return a})}async function J(e,o,i={}){let a=i.workflowContext??r();if(l(a),!o)throw Error("Quality video sequence settings are required.");let d=n(e),u="expert"===d.resourceMode?d.quantizationMode:"none",p=await C([t.qualityVideoQuantization,t.qualityVideoRecipe,t.qualityVideoShots,t.qualityVideoJobs,t.qualityVideoLoopItems,t.qualityVideoGenerate,t.qualityVideoRetain,t.qualityVideoLoopResult,t.qualityVideoJoin]);if(l(a),p.length>0)throw Error(`Quality video sequence is missing ${p.join(", ")}.`);i.graphPrepared||await s(d,a),l(a);let g=f.getState().graphBinding,m=g?.nodes.wanPipeline,h=g?.nodes.loadImage,y=g?.nodes.wanGenerate,v=g?.nodes.videoExport,_=o.mode??"image_to_video",S=d.device,w="text_to_video"===_?"none":d.offloadMode;if(!m||"image_to_video"===_&&!h)throw Error("Quality video sequence requires a video pipeline and image-to-video also needs keyframe input.");return M(a,"image_to_video"===_?"quality-video.i2v.v1":"quality-video.t2v.v1",()=>{let i=L(t.qualityVideoQuantization,"qualityVideoQuantization",{x:-930,y:-390}),a=L(t.qualityVideoRecipe,"qualityVideoRecipe",{x:-550,y:-390}),n=L(t.qualityVideoShots,"qualityVideoShots",{x:-930,y:310}),d=L(t.qualityVideoJobs,"qualityVideoJobs",{x:-500,y:310}),r=L(t.qualityVideoLoopItems,"qualityVideoLoopItems",{x:10,y:260}),s=L(t.qualityVideoGenerate,"qualityVideoGenerate",{x:310,y:260}),l=L(t.qualityVideoRetain,"qualityVideoRetain",{x:610,y:260}),p=L(t.qualityVideoLoopResult,"qualityVideoLoopResult",{x:910,y:260}),g=L(t.qualityVideoJoin,"qualityVideoJoin",{x:1270,y:120});V(i,["backend"],u),V(i,["components"],"image_to_video"===_?["transformer","transformer_2"]:["transformer"]),V(i,["dtype"],"bfloat16"),V(a,["device_map"],"none"),V(a,["offload_mode"],w),V(a,["device"],S);let f=S.startsWith("cuda");V(a,["attention_backend"],f?"_native_flash":"auto"),V(a,["attention_components"],f?"text_to_video"===_?"transformer":"transformer,transformer_2":""),V(a,["vae_slicing"],!0),V(a,["vae_tiling"],!0),V(m,["device"],S),V(m,["auto_offload"],"none"!==w),V(m,["offload_mode"],w),V(n,["shots_json"],o.shotsJson),V(n,["maximum_shots"],6),V(d,["mode"],_),V(d,["reference_policy"],"text_to_video"===_?"none":"one_per_shot"),V(d,["base_seed"],e.seed),V(d,["fps"],o.fps??("text_to_video"===_?24:16)),V(d,["minimum_seconds"],5),V(d,["width"],o.width??("text_to_video"===_?1280:832)),V(d,["height"],o.height??("text_to_video"===_?704:480)),V(d,["steps"],o.steps??("text_to_video"===_?50:40)),V(d,["guidance_scale"],o.guidanceScale??("text_to_video"===_?5:3.5)),V(d,["secondary_guidance_scale"],o.secondaryGuidanceScale??3.5),V(d,["conditioning_strength"],o.conditioningStrength??e.strength),V(d,["negative_prompt"],e.negativePrompt),V(l,["quality"],10),V(l,["pin"],!0),V(g,["transition_seconds"],o.transitionSeconds),V(g,["pin"],!0),F(y),F(v),y&&c.getState().setNodeUiState(y,{disabled:!0}),v&&c.getState().setNodeUiState(v,{disabled:!0});let x=A(t.upscaler,"upscaler")?.id;if(!(I(i,["quantization_config"],a,["quantization_config"])&&I(a,["execution_recipe"],m,["execution_recipe"])&&I(n,["shots"],d,["shots"])&&("image_to_video"!==_||I(h,["image"],d,["opening_images"]))&&I(d,["jobs"],r,["collection"])&&I(m,["pipeline"],s,["pipeline"])&&I(r,["item"],s,["job"])&&(x?I(s,["video_out"],x,["image"])&&I(x,["output","image"],l,["video"]):I(s,["video_out"],l,["video"]))&&I(s,["fps_out"],l,["fps"])&&I(l,["asset"],p,["value_input"])&&I(p,["collection"],g,["clips"])))throw Error("Quality video sequence nodes were created, but one or more media handles are unavailable.");let b=[r,s,l,p],q=c.getState(),R=q.nodes.find(e=>"qualityVideoLoop"===e.data.studioRole);if(!R){let e=new Set(q.nodes.filter(e=>"loop"===e.data.type).map(e=>e.id));if(q.loopNodes(b),R=c.getState().nodes.find(t=>"loop"===t.data.type&&!e.has(t.id)),R){let e=R.id;c.setState(t=>({nodes:t.nodes.map(t=>t.id===e?{...t,data:{...t.data,studioRole:"qualityVideoLoop",studioOwned:!0}}:t)})),R=k(e)}}if(!R)throw Error("Quality video sequence could not create its visible collection loop.");return b.forEach(e=>c.getState().setNodeLoopParent(e,R.id)),x&&c.getState().setNodeLoopParent(x,R.id),V(R.id,["iteration_mode"],"collection"),V(R.id,["iterations"],6),V(R.id,["max_iterations"],6),V(R.id,["carry"],!1),V(R.id,["collect"],!0),V(R.id,["durable"],!0),V(R.id,["max_retries"],1),g})}async function U(e,o,i={}){let a=i.workflowContext??r();if(l(a),!o)throw Error("Soundtrack settings are required.");let d=n(e),u=await C([t.soundtrackQuantization,t.soundtrackRecipe,t.soundtrackPipeline,t.soundtrackGenerate,t.audioFit,t.exportWithAudio]);if(l(a),u.length>0)throw Error(`Soundtrack block is missing ${u.join(", ")}.`);i.graphPrepared||await s(d,a),l(a);let p=f.getState().graphBinding,g=p?.nodes.wanGenerate,m=p?.nodes.videoExport,h=A(t.upscaler,"upscaler")?.id,y=A(t.videoCompose,"videoCompose")?.id,v=h??y??g;if(!v||!m)throw Error("Soundtrack requires a Studio video graph.");return M(a,"soundtrack.v1",()=>{let i=L(t.soundtrackQuantization,"soundtrackQuantization",{x:-930,y:420}),a=L(t.soundtrackRecipe,"soundtrackRecipe",{x:-550,y:420}),n=L(t.soundtrackPipeline,"soundtrackPipeline",{x:-170,y:420}),r=L(t.soundtrackGenerate,"soundtrackGenerate",{x:220,y:420}),s=L(t.audioFit,"soundtrackAudioFit",{x:620,y:420}),l=L(t.exportWithAudio,"exportWithAudio",{x:1050,y:140});V(i,["backend"],"none"),V(i,["components"],[]),V(i,["dtype"],d.dtype),V(a,["device_map"],"none"),V(a,["offload_mode"],d.offloadMode),V(a,["device"],d.device),V(a,["attention_backend"],"_native_math"),V(a,["attention_components"],""),V(a,["vae_slicing"],!0),V(a,["vae_tiling"],!0),V(n,["model_id"],o.model),o.model.revision&&V(n,["revision"],o.model.revision),V(n,["pipeline_class"],o.pipelineClass),V(n,["mode"],"text_to_audio"),V(n,["dtype"],d.dtype),V(n,["device"],d.device),V(n,["auto_offload"],d.autoOffload),V(n,["offload_mode"],d.offloadMode),V(r,["task_type"],"text2music"),V(r,["prompt"],o.prompt),V(r,["negative_prompt"],o.negativePrompt??""),V(r,["lyrics"],""),V(r,["audio_duration"],o.durationSeconds),V(r,["num_inference_steps"],o.steps),V(r,["guidance_scale"],o.guidanceScale),V(r,["stable_audio_steps"],o.steps),V(r,["stable_audio_guidance"],o.guidanceScale),V(r,["seed"],o.seed),V(r,["bpm"],o.bpm??0),V(r,["keyscale"],o.keyscale??""),V(r,["timesignature"],o.timesignature??"4/4"),E(s,o.audioFit),V(l,["fps"],e.fps),V(l,["quality"],10),G(v,m),c.getState().setNodeUiState(m,{disabled:!0});let u=h?["output","image"]:y?["video"]:["video_out"];if(!(I(i,["quantization_config"],a,["quantization_config"])&&I(a,["execution_recipe"],n,["execution_recipe"])&&I(n,["pipeline"],r,["pipeline"])&&I(r,["audio"],s,["audio"])&&I(s,["output"],l,["audio"])&&I(v,u,l,["video"])))throw Error("Soundtrack nodes were created, but their audio/video handles are unavailable.");return l})}async function H(e,o,i={}){let a=i.workflowContext??r();if(l(a),!o)throw Error("Lyric video settings are required.");let d=n(e),u="expert"===d.resourceMode?d.quantizationMode:"none",c=await C([t.lyricVideoQuantization,t.lyricVideoRecipe,t.videoPipeline,t.videoSequence,t.videoCompose,t.upscaler,t.lyricOverlay,t.audioFit,t.exportWithAudio]);if(l(a),c.length>0)throw Error(`Lyric video block is missing ${c.join(", ")}.`);i.graphPrepared||await s(d,a),l(a);let p=f.getState().graphBinding?.nodes.audioGenerate;if(!p)throw Error("Lyric video requires the ACE-Step audio graph.");return M(a,"lyric-video.v1",()=>{let i=L(t.lyricVideoQuantization,"lyricVideoQuantization",{x:-1280,y:340}),a=L(t.lyricVideoRecipe,"lyricVideoRecipe",{x:-900,y:340}),n=L(t.videoPipeline,"lyricVideoPipeline",{x:-500,y:340}),r=L(t.videoSequence,"videoSequence",{x:-100,y:340}),s=L(t.videoCompose,"videoCompose",{x:300,y:340}),l=L(t.upscaler,"upscaler",{x:680,y:340}),c=L(t.lyricOverlay,"lyricOverlay",{x:1060,y:340}),g=L(t.audioFit,"lyricAudioFit",{x:1060,y:720}),f=L(t.exportWithAudio,"exportWithAudio",{x:1440,y:180});if(V(i,["backend"],u),V(i,["components"],["transformer"]),V(i,["dtype"],d.dtype),V(a,["device_map"],"none"),V(a,["offload_mode"],d.offloadMode),V(a,["device"],d.device),V(a,["attention_backend"],"auto"),V(a,["attention_components"],""),V(a,["vae_slicing"],!0),V(a,["vae_tiling"],!0),V(n,["model_id"],o.visualModel),V(n,["pipeline_class"],"LTXConditionPipeline"),V(n,["dtype"],d.dtype),V(n,["device"],d.device),V(n,["auto_offload"],d.autoOffload),V(n,["offload_mode"],d.offloadMode),Q(r,{...e,width:768,height:512,numFrames:81,steps:8,guidanceScale:1},o.promptsJson),V(s,["fps"],16),V(s,["transition_seconds"],o.transitionSeconds),V(l,["model_id"],x),V(l,["downscale"],1),V(c,["lrc"],o.lrc),V(c,["fps"],16),V(c,["font_size"],o.fontSize??58),V(c,["bottom_margin"],o.bottomMargin??70),V(p,["task_type"],"text2music"),V(p,["prompt"],o.audio.prompt),V(p,["negative_prompt"],""),V(p,["lyrics"],o.audio.lyrics),V(p,["audio_duration"],o.audio.durationSeconds),V(p,["num_inference_steps"],o.audio.steps),V(p,["guidance_scale"],o.audio.guidanceScale),V(p,["shift"],o.audio.shift??3),V(p,["seed"],o.audio.seed),V(p,["bpm"],o.audio.bpm??0),V(p,["keyscale"],o.audio.keyscale??""),V(p,["timesignature"],o.audio.timesignature??"4"),V(p,["vocal_language"],o.audio.vocalLanguage??"en"),V(p,["sample_rate"],o.audioFit.targetSampleRate??48e3),E(g,o.audioFit),V(f,["fps"],16),!(I(i,["quantization_config"],a,["quantization_config"])&&I(a,["execution_recipe"],n,["execution_recipe"])&&I(n,["pipeline"],r,["pipeline"])&&I(r,["clips"],s,["clip_1"])&&I(s,["video"],l,["image"])&&I(l,["output"],c,["video"])&&I(c,["output"],f,["video"])&&I(p,["audio"],g,["audio"])&&I(g,["output"],f,["audio"])))throw Error("Lyric video nodes were created, but their media handles are unavailable.");return f})}export{N as a,_ as c,U as i,w as l,H as n,j as o,J as r,S as s,z as t}; \ No newline at end of file +import{Ar as e,Ei as t,Mo as i,Mr as o,Nr as a,Rr as d,Sa as n,Si as r,Ti as s,Vr as l,ga as u,ji as c,jr as p,sc as g,ua as f,yo as m}from"./studio-templates.js?v=cc046c26d6fe7aa4";var y={source:"hub",value:"nateraw/real-esrgan/RealESRGAN_x2plus.pth",revision:"42efb9c3eeed1f5c0c8a626cf5f7f4481dfbb094",sha256:"49fafd45f8fd7aa8d31ab2a22d14d91b536c34494a5cfe31eb5d89c2fa266abb",byteSize:67061725};function v(e){return JSON.parse(JSON.stringify(e))}function h(e){return`${e.data.module}.${e.data.action}`}function _(e){if(e)return l.getState().nodes.find(t=>t.id===e)}function S(e,t){let i=_(e);if(i)return t.find(e=>i.data.params[e])}function w(e,t,i,o="value"){let a=S(e,t);return!(!e||!a||(l.getState().setParam(e,a,i,o),0))}function x(e,t){w(e,["source_start_seconds"],t.sourceStartSeconds),w(e,["source_duration_seconds"],t.sourceDurationSeconds),w(e,["target_duration_seconds"],t.targetDurationSeconds),w(e,["delay_seconds"],t.delaySeconds??0),w(e,["target_sample_rate"],t.targetSampleRate??48e3),w(e,["fade_in_seconds"],t.fadeInSeconds??0),w(e,["fade_out_seconds"],t.fadeOutSeconds??0)}async function b(e){let t=r.getState(),i=e.filter(e=>!t.nodesRegistry[e]);return(0===Object.keys(t.nodesRegistry).length||i.length>0)&&await t.fetchNodes(),e.filter(e=>!r.getState().nodesRegistry[e])}function q(e,t){return l.getState().nodes.find(i=>h(i)===e&&i.data.studioRole===t)}function k(e){if(0===e.length)return;let t=new Set(e);l.setState(e=>({nodes:e.nodes.filter(e=>!t.has(e.id)),edges:e.edges.filter(e=>!t.has(e.source)&&!t.has(e.target))})),l.getState().updateHandleConnectionStatus()}function V(e,t,i){let o=l.getState().nodes.filter(i=>i.data.studioRole===t&&h(i)!==e).map(e=>e.id);o.length>0&&k(o);let a=q(e,t);if(a)return a.data.studioOwned||l.setState(e=>({nodes:e.nodes.map(e=>e.id===a.id?{...e,data:{...e.data,studioOwned:!0}}:e)})),a.id;let d=r.getState().nodesRegistry[e];if(!d)throw Error(`Missing MoDiff node registry entry: ${e}`);let n=function(e){let t=e.lastIndexOf(".");return{module:e.slice(0,t),action:e.slice(t+1)}}(e),s={id:g(),type:d.type,position:i,data:{...v(d),module:d.module||n.module,action:d.action||n.action,studioRole:t,studioOwned:!0}};return l.getState().addNode(s),s.id}function E(e,t,i,o){if(!e||!i)return!1;let a=S(e,t),d=S(i,o);if(!a||!d)return!1;let r=l.getState();return!!r.edges.some(t=>t.source===e&&t.target===i&&t.sourceHandle===a&&t.targetHandle===d)||(r.onConnect({source:e,sourceHandle:a,target:i,targetHandle:d,edgeType:n.getState().edgeType}),l.getState().edges.some(t=>t.source===e&&t.target===i&&t.sourceHandle===a&&t.targetHandle===d))}function R(e,t,i){let o=_(e);return o?{x:o.position.x+i.x,y:o.position.y+i.y}:t}function C(t,i,a){let d=new Set(l.getState().nodes.map(e=>e.id)),n=p(t,i);try{let e=a();o(n);let t=[...d].filter(e=>!l.getState().nodes.some(t=>t.id===e));return t.length>0&&m(t).catch(e=>console.error("Failed to delete replaced node cache",e)),e}catch(t){throw e(n),t}}async function A(e=c.getState().form,o,n={}){let r=n.workflowContext??t();if(s(r),n.graphPrepared||await a(e,r),s(r),!await d(750,5e3,r))throw Error("The Studio graph schema did not settle before adding the LoRA adapter block.");s(r);let u=c.getState().graphBinding,p=!!u?.nodes.diffusersImagePipeline,g=!!u?.nodes.audioPipeline,m=g?f.audioLora:p?f.directLora:f.lora,y=await b([m]);if(s(r),y.length>0)throw Error(`LoRA block is unavailable because the backend registry is missing ${y.join(", ")}.`);let v=u?.nodes.models,h=u?.nodes.diffusersImagePipeline,_=u?.nodes.diffusersImageInpaint??u?.nodes.diffusersImageControl??u?.nodes.diffusersImageEdit??u?.nodes.diffusersImageGenerate,S=u?.nodes.audioPipeline,x=u?.nodes.audioGenerate;if(!(v||h&&_||S&&x))throw Error("Create a Studio graph before adding a LoRA adapter block.");let q=o?[o,...o.additionalAdapters??[]]:[];if(q.length>1&&!p)throw Error("Multiple LoRA adapters currently require the Diffusers image pipeline.");let A=q.length>0?q:[void 0],P=R(v??h??S,{x:-520,y:180},{x:0,y:270}),L=C(r,g?"lora.diffusers-audio.v1":p?"lora.diffusers-image.v1":"lora.modular.v1",()=>{let e=new Set(A.map((e,t)=>0===t?"loraAdapter":`loraAdapter:${t}`)),t=l.getState().nodes.filter(t=>"string"==typeof t.data.studioRole&&t.data.studioRole.startsWith("loraAdapter:")&&!e.has(t.data.studioRole)).map(e=>e.id);t.length>0&&k(t);let i=A.map((e,t)=>{let i=V(m,0===t?"loraAdapter":`loraAdapter:${t}`,{x:P.x+310*t,y:P.y});return w(i,["scale"],e?.scale??1),w(i,["replace_existing"],0===t),e?.model&&(w(i,["model","adapter_path"],{source:e.model.source,value:e.model.value}),w(i,["revision"],e.model.revision??""),w(i,["expected_sha256"],e.model.sha256??"")),e?.weightName&&w(i,["weight_name"],e.weightName),e?.adapterName&&w(i,["adapter_name"],e.adapterName),i}),a=i[0];g&&o?.baseModel&&(w(S,["model_id","model"],{source:o.baseModel.source,value:o.baseModel.value}),w(S,["revision"],o.baseModel.revision??"")),o?.schedulerClass&&w(a,["scheduler_class"],o.schedulerClass),o?.schedulerConfig&&w(a,["scheduler_config"],JSON.stringify(o.schedulerConfig));let d=!1;if(p||g){let e=l.getState(),t=h??S,o=_??x,a=e.edges.filter(e=>e.source===t&&(e.target===o||i.includes(e.target))||i.includes(e.source)&&(e.target===o||i.includes(e.target))).map(e=>e.id);a.length>0&&e.removeEdges(a),d=E(t,["pipeline"],i[0],["pipeline"]);for(let e=1;e0)throw Error(`Upscale block is unavailable because the backend registry is missing ${r.join(", ")}.`);d.graphPrepared||await a(e,n),s(n);let u=c.getState().graphBinding,p=u?.nodes.decode,g=u?.nodes.diffusersImageGenerate??u?.nodes.diffusersImageEdit??u?.nodes.diffusersImageInpaint??u?.nodes.diffusersImageControl,m=u?.nodes.wanGenerate,y=u?.nodes.videoExport,v=q(f.qualityVideoGenerate,"qualityVideoGenerate"),h=q(f.qualityVideoRetain,"qualityVideoRetain"),_=q(f.videoCompose,"videoCompose")?.id,S=q(f.exportWithAudio,"exportWithAudio")?.id,x=_??v?.id??m;if(!(p||g||v&&h||m&&y))throw Error("Create a Studio graph before adding an Upscale block.");let k=C(n,v?"upscale.quality-loop.v1":y?"upscale.video.v1":"upscale.image.v1",()=>{let t=V(f.upscaler,"upscaler",R(p??g??x,{x:930,y:170},{x:330,y:250}));if(w(t,["device"],e.device),o?.model&&w(t,["model_id"],o.model),void 0!==o?.downscale&&w(t,["downscale"],o.downscale),v&&h){L(v.id,h.id,["video_out"],["video"]);let e=E(v.id,["video_out"],t,["image"]),i=E(t,["output","image"],h.id,["video"]);if(!e||!i)throw Error("Quality-video upscale nodes were created, but their frame handles are not ready yet.");return v.parentId&&l.getState().setNodeLoopParent(t,v.parentId),{nodeId:t,message:"Upscale block added inside the per-shot quality loop"}}if(x&&y){let e=S??y,i=l.getState().edges.filter(t=>(t.source===m||t.source===_)&&t.target===e).map(e=>e.id);i.length>0&&l.getState().removeEdges(i);let o=E(x,_?["video"]:["video_out"],t,["image"]),a=E(t,["output","image"],e,["video"]);if(!o||!a)throw Error("Video upscale nodes were created, but video/image handles are not ready yet.");return{nodeId:t,message:"Upscale block added before the Studio video export"}}let i=V(f.preview,"upscalePreview",R(t,{x:1260,y:170},{x:330,y:0})),a=E(p??g,["images","image","output"],t,["image"]),d=E(t,["output","image"],i,["image"]);if(!a||!d)throw Error("Upscale nodes were created, but image handles are not ready yet. Try Add Upscale again after the registry finishes refreshing.");return{nodeId:t,message:"Upscale block added after Studio image generation"}});return!1!==d.notify&&i(k.message,{variant:"success",autoHideDuration:2200}),k.nodeId}function L(e,t,i,o){if(!e||!t)return;let a=l.getState().edges.filter(i=>i.source===e&&i.target===t).filter(e=>!i||i.includes(e.sourceHandle??"")).filter(e=>!o||o.includes(e.targetHandle??"")).map(e=>e.id);a.length>0&&l.getState().removeEdges(a)}function I(e){if(!e)return;let t=l.getState().edges.filter(t=>t.source===e||t.target===e).map(e=>e.id);t.length>0&&l.getState().removeEdges(t)}function z(e,t,i){w(e,["mode"],"text_to_video"),w(e,["prompts_json"],i),w(e,["negative_prompt"],t.negativePrompt),w(e,["width"],t.width),w(e,["height"],t.height),w(e,["num_frames"],t.numFrames),w(e,["frame_rate"],t.fps),w(e,["num_inference_steps"],t.steps),w(e,["guidance_scale"],t.guidanceScale),w(e,["seed"],t.seed),w(e,["output_type"],t.outputType),w(e,["max_sequence_length"],t.maxSequenceLength)}async function M(e,i,o={}){let d=o.workflowContext??t();s(d);let n=await b([f.videoSequence,f.videoCompose]);if(s(d),n.length>0)throw Error(`Video sequence block is missing ${n.join(", ")}.`);o.graphPrepared||await a(e,d),s(d);let r=c.getState().graphBinding,u=r?.nodes.wanPipeline,p=r?.nodes.wanGenerate,g=r?.nodes.videoExport,m=q(f.upscaler,"upscaler")?.id,y=q(f.exportWithAudio,"exportWithAudio")?.id;if(!u||!g)throw Error("Video sequence requires a Studio video graph.");return C(d,"video-sequence.v1",()=>{let t=V(f.videoSequence,"videoSequence",R(u,{x:-100,y:-80},{x:390,y:0})),o=V(f.videoCompose,"videoCompose",R(t,{x:300,y:-80},{x:390,y:0}));z(t,e,i?.promptsJson??"[]"),p&&l.getState().setNodeUiState(p,{disabled:!0}),w(o,["fps"],e.fps),w(o,["transition_seconds"],i?.transitionSeconds??.35),I(p);let a=y??g;if(!E(u,["pipeline"],t,["pipeline"])||!E(t,["clips"],o,["clip_1"])||!(m?E(o,["video"],m,["image"])&&E(m,["output","image"],a,["video"]):E(o,["video"],a,["video"])))throw Error("Video sequence nodes were created, but their media handles are unavailable.");return o})}async function N(e,i,o={}){let d=o.workflowContext??t();if(s(d),!i)throw Error("Quality video sequence settings are required.");let n=u(e),r="expert"===n.resourceMode?n.quantizationMode:"none",p=await b([f.qualityVideoQuantization,f.qualityVideoRecipe,f.qualityVideoShots,f.qualityVideoJobs,f.qualityVideoLoopItems,f.qualityVideoGenerate,f.qualityVideoRetain,f.qualityVideoLoopResult,f.qualityVideoJoin]);if(s(d),p.length>0)throw Error(`Quality video sequence is missing ${p.join(", ")}.`);o.graphPrepared||await a(n,d),s(d);let g=c.getState().graphBinding,m=g?.nodes.wanPipeline,y=g?.nodes.loadImage,v=g?.nodes.wanGenerate,h=g?.nodes.videoExport,S=i.mode??"image_to_video",x=n.device,k="text_to_video"===S?"none":n.offloadMode;if(!m||"image_to_video"===S&&!y)throw Error("Quality video sequence requires a video pipeline and image-to-video also needs keyframe input.");return C(d,"image_to_video"===S?"quality-video.i2v.v1":"quality-video.t2v.v1",()=>{let t=V(f.qualityVideoQuantization,"qualityVideoQuantization",{x:-930,y:-390}),o=V(f.qualityVideoRecipe,"qualityVideoRecipe",{x:-550,y:-390}),a=V(f.qualityVideoShots,"qualityVideoShots",{x:-930,y:310}),d=V(f.qualityVideoJobs,"qualityVideoJobs",{x:-500,y:310}),n=V(f.qualityVideoLoopItems,"qualityVideoLoopItems",{x:10,y:260}),s=V(f.qualityVideoGenerate,"qualityVideoGenerate",{x:310,y:260}),u=V(f.qualityVideoRetain,"qualityVideoRetain",{x:610,y:260}),c=V(f.qualityVideoLoopResult,"qualityVideoLoopResult",{x:910,y:260}),p=V(f.qualityVideoJoin,"qualityVideoJoin",{x:1270,y:120});w(t,["backend"],r),w(t,["components"],"image_to_video"===S?["transformer","transformer_2"]:["transformer"]),w(t,["dtype"],"bfloat16"),w(o,["device_map"],"none"),w(o,["offload_mode"],k),w(o,["device"],x);let g=x.startsWith("cuda");w(o,["attention_backend"],g?"_native_flash":"auto"),w(o,["attention_components"],g?"text_to_video"===S?"transformer":"transformer,transformer_2":""),w(o,["vae_slicing"],!0),w(o,["vae_tiling"],!0),w(m,["device"],x),w(m,["auto_offload"],"none"!==k),w(m,["offload_mode"],k),w(a,["shots_json"],i.shotsJson),w(a,["maximum_shots"],6),w(d,["mode"],S),w(d,["reference_policy"],"text_to_video"===S?"none":"one_per_shot"),w(d,["base_seed"],e.seed),w(d,["fps"],i.fps??("text_to_video"===S?24:16)),w(d,["minimum_seconds"],5),w(d,["width"],i.width??("text_to_video"===S?1280:832)),w(d,["height"],i.height??("text_to_video"===S?704:480)),w(d,["steps"],i.steps??("text_to_video"===S?50:40)),w(d,["guidance_scale"],i.guidanceScale??("text_to_video"===S?5:3.5)),w(d,["secondary_guidance_scale"],i.secondaryGuidanceScale??3.5),w(d,["conditioning_strength"],i.conditioningStrength??e.strength),w(d,["negative_prompt"],e.negativePrompt),w(u,["quality"],10),w(u,["pin"],!0),w(p,["transition_seconds"],i.transitionSeconds),w(p,["pin"],!0),I(v),I(h),v&&l.getState().setNodeUiState(v,{disabled:!0}),h&&l.getState().setNodeUiState(h,{disabled:!0});let b=q(f.upscaler,"upscaler")?.id;if(!(E(t,["quantization_config"],o,["quantization_config"])&&E(o,["execution_recipe"],m,["execution_recipe"])&&E(a,["shots"],d,["shots"])&&("image_to_video"!==S||E(y,["image"],d,["opening_images"]))&&E(d,["jobs"],n,["collection"])&&E(m,["pipeline"],s,["pipeline"])&&E(n,["item"],s,["job"])&&(b?E(s,["video_out"],b,["image"])&&E(b,["output","image"],u,["video"]):E(s,["video_out"],u,["video"]))&&E(s,["fps_out"],u,["fps"])&&E(u,["asset"],c,["value_input"])&&E(c,["collection"],p,["clips"])))throw Error("Quality video sequence nodes were created, but one or more media handles are unavailable.");let R=[n,s,u,c],C=l.getState(),A=C.nodes.find(e=>"qualityVideoLoop"===e.data.studioRole);if(!A){let e=new Set(C.nodes.filter(e=>"loop"===e.data.type).map(e=>e.id));if(C.loopNodes(R),A=l.getState().nodes.find(t=>"loop"===t.data.type&&!e.has(t.id)),A){let e=A.id;l.setState(t=>({nodes:t.nodes.map(t=>t.id===e?{...t,data:{...t.data,studioRole:"qualityVideoLoop",studioOwned:!0}}:t)})),A=_(e)}}if(!A)throw Error("Quality video sequence could not create its visible collection loop.");return R.forEach(e=>l.getState().setNodeLoopParent(e,A.id)),b&&l.getState().setNodeLoopParent(b,A.id),w(A.id,["iteration_mode"],"collection"),w(A.id,["iterations"],6),w(A.id,["max_iterations"],6),w(A.id,["carry"],!1),w(A.id,["collect"],!0),w(A.id,["durable"],!0),w(A.id,["max_retries"],1),p})}async function G(e,i,o={}){let d=o.workflowContext??t();if(s(d),!i)throw Error("Soundtrack settings are required.");let n=u(e),r=await b([f.soundtrackQuantization,f.soundtrackRecipe,f.soundtrackPipeline,f.soundtrackGenerate,f.audioFit,f.exportWithAudio]);if(s(d),r.length>0)throw Error(`Soundtrack block is missing ${r.join(", ")}.`);o.graphPrepared||await a(n,d),s(d);let p=c.getState().graphBinding,g=p?.nodes.wanGenerate,m=p?.nodes.videoExport,y=q(f.upscaler,"upscaler")?.id,v=q(f.videoCompose,"videoCompose")?.id,h=y??v??g;if(!h||!m)throw Error("Soundtrack requires a Studio video graph.");return C(d,"soundtrack.v1",()=>{let t=V(f.soundtrackQuantization,"soundtrackQuantization",{x:-930,y:420}),o=V(f.soundtrackRecipe,"soundtrackRecipe",{x:-550,y:420}),a=V(f.soundtrackPipeline,"soundtrackPipeline",{x:-170,y:420}),d=V(f.soundtrackGenerate,"soundtrackGenerate",{x:220,y:420}),r=V(f.audioFit,"soundtrackAudioFit",{x:620,y:420}),s=V(f.exportWithAudio,"exportWithAudio",{x:1050,y:140});w(t,["backend"],"none"),w(t,["components"],[]),w(t,["dtype"],n.dtype),w(o,["device_map"],"none"),w(o,["offload_mode"],n.offloadMode),w(o,["device"],n.device),w(o,["attention_backend"],"_native_math"),w(o,["attention_components"],""),w(o,["vae_slicing"],!0),w(o,["vae_tiling"],!0),w(a,["model_id"],i.model),i.model.revision&&w(a,["revision"],i.model.revision),w(a,["pipeline_class"],i.pipelineClass),w(a,["mode"],"text_to_audio"),w(a,["dtype"],n.dtype),w(a,["device"],n.device),w(a,["auto_offload"],n.autoOffload),w(a,["offload_mode"],n.offloadMode),w(d,["task_type"],"text2music"),w(d,["prompt"],i.prompt),w(d,["negative_prompt"],i.negativePrompt??""),w(d,["lyrics"],""),w(d,["audio_duration"],i.durationSeconds),w(d,["num_inference_steps"],i.steps),w(d,["guidance_scale"],i.guidanceScale),w(d,["stable_audio_steps"],i.steps),w(d,["stable_audio_guidance"],i.guidanceScale),w(d,["seed"],i.seed),w(d,["bpm"],i.bpm??0),w(d,["keyscale"],i.keyscale??""),w(d,["timesignature"],i.timesignature??"4/4"),x(r,i.audioFit),w(s,["fps"],e.fps),w(s,["quality"],10),L(h,m),l.getState().setNodeUiState(m,{disabled:!0});let u=y?["output","image"]:v?["video"]:["video_out"];if(!(E(t,["quantization_config"],o,["quantization_config"])&&E(o,["execution_recipe"],a,["execution_recipe"])&&E(a,["pipeline"],d,["pipeline"])&&E(d,["audio"],r,["audio"])&&E(r,["output"],s,["audio"])&&E(h,u,s,["video"])))throw Error("Soundtrack nodes were created, but their audio/video handles are unavailable.");return s})}async function O(e,i,o={}){let d=o.workflowContext??t();if(s(d),!i)throw Error("Lyric video settings are required.");let n=u(e),r="expert"===n.resourceMode?n.quantizationMode:"none",l=await b([f.lyricVideoQuantization,f.lyricVideoRecipe,f.videoPipeline,f.videoSequence,f.videoCompose,f.upscaler,f.lyricOverlay,f.audioFit,f.exportWithAudio]);if(s(d),l.length>0)throw Error(`Lyric video block is missing ${l.join(", ")}.`);o.graphPrepared||await a(n,d),s(d);let p=c.getState().graphBinding?.nodes.audioGenerate;if(!p)throw Error("Lyric video requires the ACE-Step audio graph.");return C(d,"lyric-video.v1",()=>{let t=V(f.lyricVideoQuantization,"lyricVideoQuantization",{x:-1280,y:340}),o=V(f.lyricVideoRecipe,"lyricVideoRecipe",{x:-900,y:340}),a=V(f.videoPipeline,"lyricVideoPipeline",{x:-500,y:340}),d=V(f.videoSequence,"videoSequence",{x:-100,y:340}),s=V(f.videoCompose,"videoCompose",{x:300,y:340}),l=V(f.upscaler,"upscaler",{x:680,y:340}),u=V(f.lyricOverlay,"lyricOverlay",{x:1060,y:340}),c=V(f.audioFit,"lyricAudioFit",{x:1060,y:720}),g=V(f.exportWithAudio,"exportWithAudio",{x:1440,y:180});if(w(t,["backend"],r),w(t,["components"],["transformer"]),w(t,["dtype"],n.dtype),w(o,["device_map"],"none"),w(o,["offload_mode"],n.offloadMode),w(o,["device"],n.device),w(o,["attention_backend"],"auto"),w(o,["attention_components"],""),w(o,["vae_slicing"],!0),w(o,["vae_tiling"],!0),w(a,["model_id"],i.visualModel),w(a,["pipeline_class"],"LTXConditionPipeline"),w(a,["dtype"],n.dtype),w(a,["device"],n.device),w(a,["auto_offload"],n.autoOffload),w(a,["offload_mode"],n.offloadMode),z(d,{...e,width:768,height:512,numFrames:81,steps:8,guidanceScale:1},i.promptsJson),w(s,["fps"],16),w(s,["transition_seconds"],i.transitionSeconds),w(l,["model_id"],y),w(l,["downscale"],1),w(u,["lrc"],i.lrc),w(u,["fps"],16),w(u,["font_size"],i.fontSize??58),w(u,["bottom_margin"],i.bottomMargin??70),w(p,["task_type"],"text2music"),w(p,["prompt"],i.audio.prompt),w(p,["negative_prompt"],""),w(p,["lyrics"],i.audio.lyrics),w(p,["audio_duration"],i.audio.durationSeconds),w(p,["num_inference_steps"],i.audio.steps),w(p,["guidance_scale"],i.audio.guidanceScale),w(p,["shift"],i.audio.shift??3),w(p,["seed"],i.audio.seed),w(p,["bpm"],i.audio.bpm??0),w(p,["keyscale"],i.audio.keyscale??""),w(p,["timesignature"],i.audio.timesignature??"4"),w(p,["vocal_language"],i.audio.vocalLanguage??"en"),w(p,["sample_rate"],i.audioFit.targetSampleRate??48e3),x(c,i.audioFit),w(g,["fps"],16),!(E(t,["quantization_config"],o,["quantization_config"])&&E(o,["execution_recipe"],a,["execution_recipe"])&&E(a,["pipeline"],d,["pipeline"])&&E(d,["clips"],s,["clip_1"])&&E(s,["video"],l,["image"])&&E(l,["output"],u,["video"])&&E(u,["output"],g,["video"])&&E(p,["audio"],c,["audio"])&&E(c,["output"],g,["audio"])))throw Error("Lyric video nodes were created, but their media handles are unavailable.");return g})}export{P as a,G as i,O as n,M as o,N as r,A as t}; \ No newline at end of file diff --git a/web/assets/encodingImageRoute.js b/web/assets/encodingImageRoute.js new file mode 100644 index 00000000..173129cb --- /dev/null +++ b/web/assets/encodingImageRoute.js @@ -0,0 +1 @@ +import{n as e}from"./rolldown-runtime.js?v=cc046c26d6fe7aa4";import{ws as t}from"./studio-templates.js?v=cc046c26d6fe7aa4";var i=e({optionalEncodingRoute:()=>o});function o(e,i,o){if(!t(e)||!e||e.effectiveInterface.boundary.inputs.some(e=>"image"===e.valueType))return null;let n=e.effectiveGraph.nodes.flatMap(e=>{let t=e.data.operationAuthoring;return t?.operation?[t.operation]:[]}),a=n[0]?.pipelineClass;if(!a||n.some(e=>e.pipelineClass!==a))return null;let s=i.filter(e=>e.pipelineClass===a&&e.task===n[0]?.task),r=i.flatMap(e=>{if(e.pipelineClass!==a||!e.task||e.task.includes("video")||!["vae_encoder","image_encoder"].includes(e.nodeType)||!e.ports.some(e=>"input"===e.direction&&!e.hidden&&"image"===e.semanticName&&e.types.includes("image"))||!o.some(t=>t.pipelineClass===a&&t.tasks.some(t=>t.task===e.task&&"adapter"===t.execution&&t.operationIds.includes(e.operationId))))return[];let t=i.filter(t=>t.pipelineClass===a&&t.task===e.task);return s.some(e=>!t.some(t=>t.operationId===e.operationId))||t.some(e=>e.ports.some(t=>"input"===t.direction&&t.required&&!t.hidden&&t.types.includes("image")&&"image"!==t.semanticName&&!s.some(i=>i.operationId===e.operationId&&i.ports.some(e=>"input"===e.direction&&e.required&&e.semanticName===t.semanticName))))?[]:[{operation:e,cost:t.filter(e=>!s.some(t=>t.operationId===e.operationId)).length}]}),p=Math.min(...r.map(e=>e.cost)),l=r.filter(e=>e.cost===p);return 1===l.length?l[0].operation:null}export{o as n,i as t}; \ No newline at end of file diff --git a/web/assets/graph-vendor.js b/web/assets/graph-vendor.js index f3f23f2d..95992be9 100644 --- a/web/assets/graph-vendor.js +++ b/web/assets/graph-vendor.js @@ -1 +1 @@ -import{r as e,t}from"./rolldown-runtime.js?v=5bcd315505f5670a";var n=t(e=>{var t=Symbol.for("react.transitional.element"),n=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),a=Symbol.for("react.consumer"),l=Symbol.for("react.context"),s=Symbol.for("react.forward_ref"),u=Symbol.for("react.suspense"),c=Symbol.for("react.memo"),d=Symbol.for("react.lazy"),f=Symbol.for("react.activity"),p=Symbol.iterator,h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},m=Object.assign,g={};function y(e,t,n){this.props=e,this.context=t,this.refs=g,this.updater=n||h}function v(){}function b(e,t,n){this.props=e,this.context=t,this.refs=g,this.updater=n||h}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},v.prototype=y.prototype;var w=b.prototype=new v;w.constructor=b,m(w,y.prototype),w.isPureReactComponent=!0;var x=Array.isArray;function k(){}var S={H:null,A:null,T:null,S:null},E=Object.prototype.hasOwnProperty;function C(e,n,r){var o=r.ref;return{$$typeof:t,type:e,key:n,ref:void 0===o?null:o,props:r}}function M(e){return"object"==typeof e&&!!e&&e.$$typeof===t}var P=/\/+/g;function O(e,t){return"object"==typeof e&&e&&null!=e.key?function(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,function(e){return t[e]})}(""+e.key):t.toString(36)}function T(e,r,o,i,a){var l=typeof e;("undefined"===l||"boolean"===l)&&(e=null);var s=!1;if(null===e)s=!0;else switch(l){case"bigint":case"string":case"number":s=!0;break;case"object":switch(e.$$typeof){case t:case n:s=!0;break;case d:return T((s=e._init)(e._payload),r,o,i,a)}}if(s)return a=a(e),s=""===i?"."+O(e,0):i,x(a)?(o="",null!=s&&(o=s.replace(P,"$&/")+"/"),T(a,r,o,"",function(e){return e})):null!=a&&(M(a)&&(a=function(e,t){return C(e.type,t,e.props)}(a,o+(null==a.key||e&&e.key===a.key?"":(""+a.key).replace(P,"$&/")+"/")+s)),r.push(a)),1;s=0;var u=""===i?".":i+":";if(x(e))for(var c=0;c{t.exports=n()}),o=t(e=>{function t(e,t){var n=e.length;e.push(t);e:for(;0>>1,i=e[r];if(!(0>>1;ro(s,n))uo(c,s)?(e[r]=c,e[u]=n,r=u):(e[r]=s,e[l]=n,r=l);else{if(!(uo(c,n)))break e;e[r]=c,e[u]=n,r=u}}}return t}function o(e,t){var n=e.sortIndex-t.sortIndex;return 0===n?e.id-t.id:n}if(e.unstable_now=void 0,"object"==typeof performance&&"function"==typeof performance.now){var i=performance;e.unstable_now=function(){return i.now()}}else{var a=Date,l=a.now();e.unstable_now=function(){return a.now()-l}}var s=[],u=[],c=1,d=null,f=3,p=!1,h=!1,m=!1,g=!1,y="function"==typeof setTimeout?setTimeout:null,v="function"==typeof clearTimeout?clearTimeout:null,b=typeof setImmediate<"u"?setImmediate:null;function w(e){for(var o=n(u);null!==o;){if(null===o.callback)r(u);else{if(!(o.startTime<=e))break;r(u),o.sortIndex=o.expirationTime,t(s,o)}o=n(u)}}function x(e){if(m=!1,w(e),!h)if(null!==n(s))h=!0,S||(S=!0,k());else{var t=n(u);null!==t&&N(x,t.startTime-e)}}var k,S=!1,E=-1,C=5,M=-1;function P(){return!(!g&&e.unstable_now()-Mt&&P());){var a=d.callback;if("function"==typeof a){d.callback=null,f=d.priorityLevel;var l=a(d.expirationTime<=t);if(t=e.unstable_now(),"function"==typeof l){d.callback=l,w(t),o=!0;break t}d===n(s)&&r(s),w(t)}else r(s);d=n(s)}if(null!==d)o=!0;else{var c=n(u);null!==c&&N(x,c.startTime-t),o=!1}}break e}finally{d=null,f=i,p=!1}o=void 0}}finally{o?k():S=!1}}}if("function"==typeof b)k=function(){b(O)};else if(typeof MessageChannel<"u"){var T=new MessageChannel,_=T.port2;T.port1.onmessage=O,k=function(){_.postMessage(null)}}else k=function(){y(O,0)};function N(t,n){E=y(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125a?(r.sortIndex=i,t(u,r),null===n(s)&&r===n(u)&&(m?(v(E),E=-1):m=!0,N(x,i-a))):(r.sortIndex=l,t(s,r),h||p||(h=!0,S||(S=!0,k()))),r},e.unstable_shouldYield=P,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}}),i=t((e,t)=>{t.exports=o()}),a=t(e=>{var t=r();function n(e){var t="https://react.dev/errors/"+e;if(1{(function e(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||"function"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}})(),t.exports=a()}),s=t(e=>{var t=i(),n=r(),o=l();function a(e){var t="https://react.dev/errors/"+e;if(1F||(e.current=D[F],D[F]=null,F--)}function V(e,t){F++,D[F]=e.current,e.current=t}var $,B,W=j(null),q=j(null),U=j(null),K=j(null);function Y(e,t){switch(V(U,t),V(q,e),V(W,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?yd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)e=vd(t=yd(t),e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}H(W),V(W,e)}function X(){H(W),H(q),H(U)}function Z(e){null!==e.memoizedState&&V(K,e);var t=W.current,n=vd(t,e.type);t!==n&&(V(q,e),V(W,n))}function G(e){q.current===e&&(H(W),H(q)),K.current===e&&(H(K),df._currentValue=A)}function Q(e){if(void 0===$)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);$=t&&t[1]||"",B=-1)":-1--o||s[r]!==u[o]){var c="\n"+s[r].replace(" at new "," at ");return e.displayName&&c.includes("")&&(c=c.replace("",e.displayName)),c}}while(1<=r&&0<=o);break}}}finally{J=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:"")?Q(n):""}function te(e,t){switch(e.tag){case 26:case 27:case 5:return Q(e.type);case 16:return Q("Lazy");case 13:return e.child!==t&&null!==t?Q("Suspense Fallback"):Q("Suspense");case 19:return Q("SuspenseList");case 0:case 15:return ee(e.type,!1);case 11:return ee(e.type.render,!1);case 1:return ee(e.type,!0);case 31:return Q("Activity");default:return""}}function ne(e){try{var t="",n=null;do{t+=te(e,n),n=e,e=e.return}while(e);return t}catch(e){return"\nError generating stack: "+e.message+"\n"+e.stack}}var re=Object.prototype.hasOwnProperty,oe=t.unstable_scheduleCallback,ie=t.unstable_cancelCallback,ae=t.unstable_shouldYield,le=t.unstable_requestPaint,se=t.unstable_now,ue=t.unstable_getCurrentPriorityLevel,ce=t.unstable_ImmediatePriority,de=t.unstable_UserBlockingPriority,fe=t.unstable_NormalPriority,pe=t.unstable_LowPriority,he=t.unstable_IdlePriority,me=t.log,ge=t.unstable_setDisableYieldValue,ye=null,ve=null;function be(e){if("function"==typeof me&&ge(e),ve&&"function"==typeof ve.setStrictMode)try{ve.setStrictMode(ye,e)}catch{}}var we=Math.clz32?Math.clz32:function(e){return 0==(e>>>=0)?32:31-(xe(e)/ke|0)|0},xe=Math.log,ke=Math.LN2,Se=256,Ee=262144,Ce=4194304;function Me(e){var t=42&e;if(0!==t)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return 261888&e;case 262144:case 524288:case 1048576:case 2097152:return 3932160&e;case 4194304:case 8388608:case 16777216:case 33554432:return 62914560&e;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Pe(e,t,n){var r=e.pendingLanes;if(0===r)return 0;var o=0,i=e.suspendedLanes,a=e.pingedLanes;e=e.warmLanes;var l=134217727&r;return 0===l?0===(l=r&~i)?0===a?n||0!==(n=r&~e)&&(o=Me(n)):o=Me(a):o=Me(l):0===(r=l&~i)?0===(a&=l)?n||0!==(n=l&~e)&&(o=Me(n)):o=Me(a):o=Me(r),0===o?0:0!==t&&t!==o&&0===(t&i)&&((i=o&-o)>=(n=t&-t)||32===i&&4194048&n)?t:o}function Oe(e,t){return 0===(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)}function Te(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;default:return-1}}function _e(){var e=Ce;return!(62914560&(Ce<<=1))&&(Ce=4194304),e}function Ne(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Ie(e,t){e.pendingLanes|=t,268435456!==t&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function ze(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-we(t);e.entangledLanes|=t,e.entanglements[r]=1073741824|e.entanglements[r]|261930&n}function Re(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-we(n),o=1<"u"||void 0===window.document||void 0===window.document.createElement),$t=!1;if(Vt)try{var Bt={};Object.defineProperty(Bt,"passive",{get:function(){$t=!0}}),window.addEventListener("test",Bt,Bt),window.removeEventListener("test",Bt,Bt)}catch{$t=!1}var Wt=null,qt=null,Ut=null;function Kt(){if(Ut)return Ut;var e,t,n=qt,r=n.length,o="value"in Wt?Wt.value:Wt.textContent,i=o.length;for(e=0;e=Mn),Tn=" ",_n=!1;function Nn(e,t){switch(e){case"keyup":return-1!==En.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function In(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var zn=!1,Rn={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function Ln(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!Rn[e.type]:"textarea"===t}function An(e,t,n,r){Lt?At?At.push(r):At=[r]:Lt=r,0<(t=rd(t,"onChange")).length&&(n=new nn("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var Dn=null,Fn=null;function jn(e){Xc(e,0)}function Hn(e){if(ht(Qe(e)))return e}function Vn(e,t){if("change"===e)return t}var $n=!1;if(Vt){var Bn;if(Vt){var Wn="oninput"in document;if(!Wn){var qn=document.createElement("div");qn.setAttribute("oninput","return;"),Wn="function"==typeof qn.oninput}Bn=Wn}else Bn=!1;$n=Bn&&(!document.documentMode||9=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=er(r)}}function nr(e,t){return!(!e||!t)&&(e===t||(!e||3!==e.nodeType)&&(t&&3===t.nodeType?nr(e,t.parentNode):"contains"in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}function rr(e){for(var t=mt((e=null!=e&&null!=e.ownerDocument&&null!=e.ownerDocument.defaultView?e.ownerDocument.defaultView:window).document);t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch{n=!1}if(!n)break;t=mt((e=t.contentWindow).document)}return t}function or(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var ir=Vt&&"documentMode"in document&&11>=document.documentMode,ar=null,lr=null,sr=null,ur=!1;function cr(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;ur||null==ar||ar!==mt(r)||(r="selectionStart"in(r=ar)&&or(r)?{start:r.selectionStart,end:r.selectionEnd}:{anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},sr&&Jn(sr,r)||(sr=r,0<(r=rd(lr,"onSelect")).length&&(t=new nn("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=ar)))}function dr(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var fr={animationend:dr("Animation","AnimationEnd"),animationiteration:dr("Animation","AnimationIteration"),animationstart:dr("Animation","AnimationStart"),transitionrun:dr("Transition","TransitionRun"),transitionstart:dr("Transition","TransitionStart"),transitioncancel:dr("Transition","TransitionCancel"),transitionend:dr("Transition","TransitionEnd")},pr={},hr={};function mr(e){if(pr[e])return pr[e];if(!fr[e])return e;var t,n=fr[e];for(t in n)if(n.hasOwnProperty(t)&&t in hr)return pr[e]=n[t];return e}Vt&&(hr=document.createElement("div").style,"AnimationEvent"in window||(delete fr.animationend.animation,delete fr.animationiteration.animation,delete fr.animationstart.animation),"TransitionEvent"in window||delete fr.transitionend.transition);var gr=mr("animationend"),yr=mr("animationiteration"),vr=mr("animationstart"),br=mr("transitionrun"),wr=mr("transitionstart"),xr=mr("transitioncancel"),kr=mr("transitionend"),Sr=new Map,Er="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function Cr(e,t){Sr.set(e,t),rt(t,[e])}Er.push("scrollEnd");var Mr="function"==typeof reportError?reportError:function(e){if("object"==typeof window&&"function"==typeof window.ErrorEvent){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:"object"==typeof e&&e&&"string"==typeof e.message?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if("object"==typeof process&&"function"==typeof process.emit)return void process.emit("uncaughtException",e);console.error(e)},Pr=[],Or=0,Tr=0;function _r(){for(var e=Or,t=Tr=Or=0;t>=a,o-=a,no=1<<32-we(t)+o|n<m?(g=d,d=null):g=d.sibling;var y=p(o,d,l[m],s);if(null===y){null===d&&(d=g);break}e&&d&&null===y.alternate&&t(o,d),a=i(y,a,m),null===c?u=y:c.sibling=y,c=y,d=g}if(m===l.length)return n(o,d),fo&&oo(o,m),u;if(null===d){for(;mg?(y=m,m=null):y=m.sibling;var b=p(o,m,v.value,u);if(null===b){null===m&&(m=y);break}e&&m&&null===b.alternate&&t(o,m),l=i(b,l,g),null===d?c=b:d.sibling=b,d=b,m=y}if(v.done)return n(o,m),fo&&oo(o,g),c;if(null===m){for(;!v.done;g++,v=s.next())null!==(v=f(o,v.value,u))&&(l=i(v,l,g),null===d?c=v:d.sibling=v,d=v);return fo&&oo(o,g),c}for(m=r(m);!v.done;g++,v=s.next())null!==(v=h(m,o,g,v.value,u))&&(e&&null!==v.alternate&&m.delete(null===v.key?g:v.key),l=i(v,l,g),null===d?c=v:d.sibling=v,d=v);return e&&m.forEach(function(e){return t(o,e)}),fo&&oo(o,g),c}function w(e,r,i,s){if("object"==typeof i&&i&&i.type===y&&null===i.key&&(i=i.props.children),"object"==typeof i&&i){switch(i.$$typeof){case m:e:{for(var u=i.key;null!==r;){if(r.key===u){if((u=i.type)===y){if(7===r.tag){n(e,r.sibling),(s=o(r,i.props.children)).return=e,e=s;break e}}else if(r.elementType===u||"object"==typeof u&&u&&u.$$typeof===M&&oi(u)===r.type){n(e,r.sibling),di(s=o(r,i.props),i),s.return=e,e=s;break e}n(e,r);break}t(e,r),r=r.sibling}i.type===y?((s=Br(i.props.children,e.mode,s,i.key)).return=e,e=s):(di(s=$r(i.type,i.key,i.props,null,e.mode,s),i),s.return=e,e=s)}return l(e);case g:e:{for(u=i.key;null!==r;){if(r.key===u){if(4===r.tag&&r.stateNode.containerInfo===i.containerInfo&&r.stateNode.implementation===i.implementation){n(e,r.sibling),(s=o(r,i.children||[])).return=e,e=s;break e}n(e,r);break}t(e,r),r=r.sibling}(s=Ur(i,e.mode,s)).return=e,e=s}return l(e);case M:return w(e,r,i=oi(i),s)}if(z(i))return v(e,r,i,s);if(_(i)){if("function"!=typeof(u=_(i)))throw Error(a(150));return b(e,r,i=u.call(i),s)}if("function"==typeof i.then)return w(e,r,ci(i),s);if(i.$$typeof===x)return w(e,r,Ro(e,i),s);fi(e,i)}return"string"==typeof i&&""!==i||"number"==typeof i||"bigint"==typeof i?(i=""+i,null!==r&&6===r.tag?(n(e,r.sibling),(s=o(r,i)).return=e,e=s):(n(e,r),(s=Wr(i,e.mode,s)).return=e,e=s),l(e)):n(e,r)}return function(e,t,n,r){try{ui=0;var o=w(e,t,n,r);return si=null,o}catch(t){if(t===Qo||t===ei)throw t;var i=Fr(29,t,null,e.mode);return i.lanes=r,i.return=e,i}}}var hi=pi(!0),mi=pi(!1),gi=!1;function yi(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function vi(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function bi(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function wi(e,t,n){var r=e.updateQueue;if(null===r)return null;if(r=r.shared,2&pu){var o=r.pending;return null===o?t.next=t:(t.next=o.next,o.next=t),r.pending=t,t=Lr(e),Rr(e,null,n),t}return Nr(e,r,t,n),Lr(e)}function xi(e,t,n){if(null!==(t=t.updateQueue)&&(t=t.shared,4194048&n)){var r=t.lanes;n|=r&=e.pendingLanes,t.lanes=n,Re(e,n)}}function ki(e,t){var n=e.updateQueue,r=e.alternate;if(null!==r&&n===(r=r.updateQueue)){var o=null,i=null;if(null!==(n=n.firstBaseUpdate)){do{var a={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};null===i?o=i=a:i=i.next=a,n=n.next}while(null!==n);null===i?o=i=t:i=i.next=t}else o=i=t;return n={baseState:r.baseState,firstBaseUpdate:o,lastBaseUpdate:i,shared:r.shared,callbacks:r.callbacks},void(e.updateQueue=n)}null===(e=n.lastBaseUpdate)?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Si=!1;function Ei(){if(Si&&null!==qo)throw qo}function Ci(e,t,n,r){Si=!1;var o=e.updateQueue;gi=!1;var i=o.firstBaseUpdate,a=o.lastBaseUpdate,l=o.shared.pending;if(null!==l){o.shared.pending=null;var s=l,u=s.next;s.next=null,null===a?i=u:a.next=u,a=s;var c=e.alternate;null!==c&&(l=(c=c.updateQueue).lastBaseUpdate)!==a&&(null===l?c.firstBaseUpdate=u:l.next=u,c.lastBaseUpdate=s)}if(null!==i){var d=o.baseState;for(a=0,c=u=s=null,l=i;;){var f=-536870913&l.lane,h=f!==l.lane;if(h?(gu&f)===f:(r&f)===f){0!==f&&f===Wo&&(Si=!0),null!==c&&(c=c.next={lane:0,tag:l.tag,payload:l.payload,callback:null,next:null});e:{var m=e,g=l;f=t;var y=n;switch(g.tag){case 1:if("function"==typeof(m=g.payload)){d=m.call(y,d,f);break e}d=m;break e;case 3:m.flags=-65537&m.flags|128;case 0:if(null==(f="function"==typeof(m=g.payload)?m.call(y,d,f):m))break e;d=p({},d,f);break e;case 2:gi=!0}}null!==(f=l.callback)&&(e.flags|=64,h&&(e.flags|=8192),null===(h=o.callbacks)?o.callbacks=[f]:h.push(f))}else h={lane:f,tag:l.tag,payload:l.payload,callback:l.callback,next:null},null===c?(u=c=h,s=d):c=c.next=h,a|=f;if(null===(l=l.next)){if(null===(l=o.shared.pending))break;l=(h=l).next,h.next=null,o.lastBaseUpdate=h,o.shared.pending=null}}null===c&&(s=d),o.baseState=s,o.firstBaseUpdate=u,o.lastBaseUpdate=c,null===i&&(o.shared.lanes=0),Eu|=a,e.lanes=a,e.memoizedState=d}}function Mi(e,t){if("function"!=typeof e)throw Error(a(191,e));e.call(t)}function Pi(e,t){var n=e.callbacks;if(null!==n)for(e.callbacks=null,e=0;ei?i:8;var a=R.T,l={};R.T=l,dl(e,!1,t,n);try{var s=o(),u=R.S;null!==u&&u(l,s),"object"==typeof s&&s&&"function"==typeof s.then?cl(e,t,function(e,t){var n=[],r={status:"pending",value:null,reason:null,then:function(e){n.push(e)}};return e.then(function(){r.status="fulfilled",r.value=t;for(var e=0;e<\/script>",i=i.removeChild(i.firstChild);break;case"select":i="string"==typeof r.is?l.createElement("select",{is:r.is}):l.createElement("select"),r.multiple?i.multiple=!0:r.size&&(i.size=r.size);break;default:i="string"==typeof r.is?l.createElement(o,{is:r.is}):l.createElement(o)}}i[Ve]=t,i[$e]=r;e:for(l=t.child;null!==l;){if(5===l.tag||6===l.tag)i.appendChild(l.stateNode);else if(4!==l.tag&&27!==l.tag&&null!==l.child){l.child.return=l,l=l.child;continue}if(l===t)break e;for(;null===l.sibling;){if(null===l.return||l.return===t)break e;l=l.return}l.sibling.return=l.return,l=l.sibling}t.stateNode=i;e:switch(fd(i,o,r),o){case"button":case"input":case"select":case"textarea":r=!!r.autoFocus;break e;case"img":r=!0;break e;default:r=!1}r&&ls(t)}}return fs(t),ss(t,t.type,null===e||e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&null!=t.stateNode)e.memoizedProps!==r&&ls(t);else{if("string"!=typeof r&&null===t.stateNode)throw Error(a(166));if(e=U.current,bo(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,null!==(o=uo))switch(o.tag){case 27:case 5:r=o.memoizedProps}e[Ve]=t,(e=!!(e.nodeValue===n||null!==r&&!0===r.suppressHydrationWarning||ud(e.nodeValue,n)))||go(t,!0)}else(e=gd(e).createTextNode(r))[Ve]=t,t.stateNode=e}return fs(t),null;case 31:if(n=t.memoizedState,null===e||null!==e.memoizedState){if(r=bo(t),null!==n){if(null===e){if(!r)throw Error(a(318));if(!(e=null===(e=t.memoizedState)?null:e.dehydrated))throw Error(a(557));e[Ve]=t}else wo(),!(128&t.flags)&&(t.memoizedState=null),t.flags|=4;fs(t),e=!1}else n=xo(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return 256&t.flags?(ji(t),t):(ji(t),null);if(128&t.flags)throw Error(a(558))}return fs(t),null;case 13:if(r=t.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(o=bo(t),null!==r&&null!==r.dehydrated){if(null===e){if(!o)throw Error(a(318));if(!(o=null===(o=t.memoizedState)?null:o.dehydrated))throw Error(a(317));o[Ve]=t}else wo(),!(128&t.flags)&&(t.memoizedState=null),t.flags|=4;fs(t),o=!1}else o=xo(),null!==e&&null!==e.memoizedState&&(e.memoizedState.hydrationErrors=o),o=!0;if(!o)return 256&t.flags?(ji(t),t):(ji(t),null)}return ji(t),128&t.flags?(t.lanes=n,t):(n=null!==r,e=null!==e&&null!==e.memoizedState,n&&(o=null,null!==(r=t.child).alternate&&null!==r.alternate.memoizedState&&null!==r.alternate.memoizedState.cachePool&&(o=r.alternate.memoizedState.cachePool.pool),i=null,null!==r.memoizedState&&null!==r.memoizedState.cachePool&&(i=r.memoizedState.cachePool.pool),i!==o&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),cs(t,t.updateQueue),fs(t),null);case 4:return X(),null===e&&Jc(t.stateNode.containerInfo),fs(t),null;case 10:return Po(t.type),fs(t),null;case 19:if(H(Hi),null===(r=t.memoizedState))return fs(t),null;if(o=!!(128&t.flags),null===(i=r.rendering))if(o)ds(r,!1);else{if(0!==Su||null!==e&&128&e.flags)for(e=t.child;null!==e;){if(null!==(i=Vi(e))){for(t.flags|=128,ds(r,!1),e=i.updateQueue,t.updateQueue=e,cs(t,e),t.subtreeFlags=0,e=n,n=t.child;null!==n;)Vr(n,e),n=n.sibling;return V(Hi,1&Hi.current|2),fo&&oo(t,r.treeForkCount),t.child}e=e.sibling}null!==r.tail&&se()>Ru&&(t.flags|=128,o=!0,ds(r,!1),t.lanes=4194304)}else{if(!o)if(null!==(e=Vi(i))){if(t.flags|=128,o=!0,e=e.updateQueue,t.updateQueue=e,cs(t,e),ds(r,!0),null===r.tail&&"hidden"===r.tailMode&&!i.alternate&&!fo)return fs(t),null}else 2*se()-r.renderingStartTime>Ru&&536870912!==n&&(t.flags|=128,o=!0,ds(r,!1),t.lanes=4194304);r.isBackwards?(i.sibling=t.child,t.child=i):(null===(e=r.last)?t.child=i:e.sibling=i,r.last=i)}return null===r.tail?(fs(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=se(),e.sibling=null,n=Hi.current,V(Hi,o?1&n|2:1&n),fo&&oo(t,r.treeForkCount),e);case 22:case 23:return ji(t),Ii(),r=null!==t.memoizedState,null===e?r&&(t.flags|=8192):null!==e.memoizedState!==r&&(t.flags|=8192),r?536870912&n&&!(128&t.flags)&&(fs(t),6&t.subtreeFlags&&(t.flags|=8192)):fs(t),null!==(n=t.updateQueue)&&cs(t,n.retryQueue),n=null,null!==e&&null!==e.memoizedState&&null!==e.memoizedState.cachePool&&(n=e.memoizedState.cachePool.pool),r=null,null!==t.memoizedState&&null!==t.memoizedState.cachePool&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),null!==e&&H(Yo),null;case 24:return n=null,null!==e&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Po(jo),fs(t),null;case 25:case 30:return null}throw Error(a(156,t.tag))}function hs(e,t){switch(lo(t),t.tag){case 1:return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 3:return Po(jo),X(),65536&(e=t.flags)&&!(128&e)?(t.flags=-65537&e|128,t):null;case 26:case 27:case 5:return G(t),null;case 31:if(null!==t.memoizedState){if(ji(t),null===t.alternate)throw Error(a(340));wo()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 13:if(ji(t),null!==(e=t.memoizedState)&&null!==e.dehydrated){if(null===t.alternate)throw Error(a(340));wo()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 19:return H(Hi),null;case 4:return X(),null;case 10:return Po(t.type),null;case 22:case 23:return ji(t),Ii(),null!==e&&H(Yo),65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 24:return Po(jo),null;default:return null}}function ms(e,t){switch(lo(t),t.tag){case 3:Po(jo),X();break;case 26:case 27:case 5:G(t);break;case 4:X();break;case 31:null!==t.memoizedState&&ji(t);break;case 13:ji(t);break;case 19:H(Hi);break;case 10:Po(t.type);break;case 22:case 23:ji(t),Ii(),null!==e&&H(Yo);break;case 24:Po(jo)}}function gs(e,t){try{var n=t.updateQueue,r=null===n?null:n.lastEffect;if(null!==r){var o=r.next;n=o;do{if((n.tag&e)===e){r=void 0;var i=n.create,a=n.inst;r=i(),a.destroy=r}n=n.next}while(n!==o)}}catch(e){Sc(t,t.return,e)}}function ys(e,t,n){try{var r=t.updateQueue,o=null===r?null:r.lastEffect;if(null!==o){var i=o.next;r=i;do{if((r.tag&e)===e){var a=r.inst,l=a.destroy;if(void 0!==l){a.destroy=void 0,o=t;var s=n,u=l;try{u()}catch(e){Sc(o,s,e)}}}r=r.next}while(r!==i)}}catch(e){Sc(t,t.return,e)}}function vs(e){var t=e.updateQueue;if(null!==t){var n=e.stateNode;try{Pi(t,n)}catch(t){Sc(e,e.return,t)}}}function bs(e,t,n){n.props=Sl(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Sc(e,t,n)}}function ws(e,t){try{var n=e.ref;if(null!==n){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;default:r=e.stateNode}"function"==typeof n?e.refCleanup=n(r):n.current=r}}catch(n){Sc(e,t,n)}}function xs(e,t){var n=e.ref,r=e.refCleanup;if(null!==n)if("function"==typeof r)try{r()}catch(n){Sc(e,t,n)}finally{e.refCleanup=null,null!=(e=e.alternate)&&(e.refCleanup=null)}else if("function"==typeof n)try{n(null)}catch(n){Sc(e,t,n)}else n.current=null}function ks(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":n.autoFocus&&r.focus();break e;case"img":n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Sc(e,e.return,t)}}function Ss(e,t,n){try{var r=e.stateNode;(function(e,t,n,r){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var o=null,i=null,l=null,s=null,u=null,c=null,d=null;for(h in n){var f=n[h];if(n.hasOwnProperty(h)&&null!=f)switch(h){case"checked":case"value":break;case"defaultValue":u=f;default:r.hasOwnProperty(h)||cd(e,t,h,null,r,f)}}for(var p in r){var h=r[p];if(f=n[p],r.hasOwnProperty(p)&&(null!=h||null!=f))switch(p){case"type":i=h;break;case"name":o=h;break;case"checked":c=h;break;case"defaultChecked":d=h;break;case"value":l=h;break;case"defaultValue":s=h;break;case"children":case"dangerouslySetInnerHTML":if(null!=h)throw Error(a(137,t));break;default:h!==f&&cd(e,t,p,h,r,f)}}return void vt(e,l,s,u,c,d,i,o);case"select":for(i in h=l=s=p=null,n)if(u=n[i],n.hasOwnProperty(i)&&null!=u)switch(i){case"value":break;case"multiple":h=u;default:r.hasOwnProperty(i)||cd(e,t,i,null,r,u)}for(o in r)if(i=r[o],u=n[o],r.hasOwnProperty(o)&&(null!=i||null!=u))switch(o){case"value":p=i;break;case"defaultValue":s=i;break;case"multiple":l=i;default:i!==u&&cd(e,t,o,i,r,u)}return t=s,n=l,r=h,void(null==p?!!r!=!!n&&(null==t?xt(e,!!n,n?[]:"",!1):xt(e,!!n,t,!0)):xt(e,!!n,p,!1));case"textarea":for(s in h=p=null,n)if(o=n[s],n.hasOwnProperty(s)&&null!=o&&!r.hasOwnProperty(s))switch(s){case"value":case"children":break;default:cd(e,t,s,null,r,o)}for(l in r)if(o=r[l],i=n[l],r.hasOwnProperty(l)&&(null!=o||null!=i))switch(l){case"value":p=o;break;case"defaultValue":h=o;break;case"children":break;case"dangerouslySetInnerHTML":if(null!=o)throw Error(a(91));break;default:o!==i&&cd(e,t,l,o,r,i)}return void kt(e,p,h);case"option":for(var m in n)p=n[m],n.hasOwnProperty(m)&&null!=p&&!r.hasOwnProperty(m)&&("selected"===m?e.selected=!1:cd(e,t,m,null,r,p));for(u in r)p=r[u],h=n[u],!r.hasOwnProperty(u)||p===h||null==p&&null==h||("selected"===u?e.selected=p&&"function"!=typeof p&&"symbol"!=typeof p:cd(e,t,u,p,r,h));return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var g in n)p=n[g],n.hasOwnProperty(g)&&null!=p&&!r.hasOwnProperty(g)&&cd(e,t,g,null,r,p);for(c in r)if(p=r[c],h=n[c],r.hasOwnProperty(c)&&p!==h&&(null!=p||null!=h))switch(c){case"children":case"dangerouslySetInnerHTML":if(null!=p)throw Error(a(137,t));break;default:cd(e,t,c,p,r,h)}return;default:if(Ot(t)){for(var y in n)p=n[y],n.hasOwnProperty(y)&&void 0!==p&&!r.hasOwnProperty(y)&&dd(e,t,y,void 0,r,p);for(d in r)p=r[d],h=n[d],!r.hasOwnProperty(d)||p===h||void 0===p&&void 0===h||dd(e,t,d,p,r,h);return}}for(var v in n)p=n[v],n.hasOwnProperty(v)&&null!=p&&!r.hasOwnProperty(v)&&cd(e,t,v,null,r,p);for(f in r)p=r[f],h=n[f],!r.hasOwnProperty(f)||p===h||null==p&&null==h||cd(e,t,f,p,r,h)})(r,e.type,n,t),r[$e]=t}catch(t){Sc(e,e.return,t)}}function Es(e){return 5===e.tag||3===e.tag||26===e.tag||27===e.tag&&Md(e.type)||4===e.tag}function Cs(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||Es(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;5!==e.tag&&6!==e.tag&&18!==e.tag;){if(27===e.tag&&Md(e.type)||2&e.flags||null===e.child||4===e.tag)continue e;e.child.return=e,e=e.child}if(!(2&e.flags))return e.stateNode}}function Ms(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?(9===n.nodeType?n.body:"HTML"===n.nodeName?n.ownerDocument.body:n).insertBefore(e,t):((t=9===n.nodeType?n.body:"HTML"===n.nodeName?n.ownerDocument.body:n).appendChild(e),null!=(n=n._reactRootContainer)||null!==t.onclick||(t.onclick=It));else if(4!==r&&(27===r&&Md(e.type)&&(n=e.stateNode,t=null),null!==(e=e.child)))for(Ms(e,t,n),e=e.sibling;null!==e;)Ms(e,t,n),e=e.sibling}function Ps(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&(27===r&&Md(e.type)&&(n=e.stateNode),null!==(e=e.child)))for(Ps(e,t,n),e=e.sibling;null!==e;)Ps(e,t,n),e=e.sibling}function Os(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,o=t.attributes;o.length;)t.removeAttributeNode(o[0]);fd(t,r,n),t[Ve]=e,t[$e]=n}catch(t){Sc(e,e.return,t)}}var Ts=!1,_s=!1,Ns=!1,Is="function"==typeof WeakSet?WeakSet:Set,zs=null;function Rs(e,t,n){var r=n.flags;switch(n.tag){case 0:case 11:case 15:Ys(e,n),4&r&&gs(5,n);break;case 1:if(Ys(e,n),4&r)if(e=n.stateNode,null===t)try{e.componentDidMount()}catch(e){Sc(n,n.return,e)}else{var o=Sl(n.type,t.memoizedProps);t=t.memoizedState;try{e.componentDidUpdate(o,t,e.__reactInternalSnapshotBeforeUpdate)}catch(e){Sc(n,n.return,e)}}64&r&&vs(n),512&r&&ws(n,n.return);break;case 3:if(Ys(e,n),64&r&&null!==(e=n.updateQueue)){if(t=null,null!==n.child)switch(n.child.tag){case 27:case 5:case 1:t=n.child.stateNode}try{Pi(e,t)}catch(e){Sc(n,n.return,e)}}break;case 27:null===t&&4&r&&Os(n);case 26:case 5:Ys(e,n),null===t&&4&r&&ks(n),512&r&&ws(n,n.return);break;case 12:Ys(e,n);break;case 31:Ys(e,n),4&r&&Hs(e,n);break;case 13:Ys(e,n),4&r&&Vs(e,n),64&r&&null!==(e=n.memoizedState)&&null!==(e=e.dehydrated)&&function(e,t){var n=e.ownerDocument;if("$~"===e.data)e._reactRetry=t;else if("$?"!==e.data||"loading"!==n.readyState)t();else{var r=function(){t(),n.removeEventListener("DOMContentLoaded",r)};n.addEventListener("DOMContentLoaded",r),e._reactRetry=r}}(e,n=Pc.bind(null,n));break;case 22:if(!(r=null!==n.memoizedState||Ts)){t=null!==t&&null!==t.memoizedState||_s,o=Ts;var i=_s;Ts=r,(_s=t)&&!i?Zs(e,n,!!(8772&n.subtreeFlags)):Ys(e,n),Ts=o,_s=i}break;case 30:break;default:Ys(e,n)}}function Ls(e){var t=e.alternate;null!==t&&(e.alternate=null,Ls(t)),e.child=null,e.deletions=null,e.sibling=null,5===e.tag&&null!==(t=e.stateNode)&&Xe(t),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}var As=null,Ds=!1;function Fs(e,t,n){for(n=n.child;null!==n;)js(e,t,n),n=n.sibling}function js(e,t,n){if(ve&&"function"==typeof ve.onCommitFiberUnmount)try{ve.onCommitFiberUnmount(ye,n)}catch{}switch(n.tag){case 26:_s||xs(n,t),Fs(e,t,n),n.memoizedState?n.memoizedState.count--:n.stateNode&&(n=n.stateNode).parentNode.removeChild(n);break;case 27:_s||xs(n,t);var r=As,o=Ds;Md(n.type)&&(As=n.stateNode,Ds=!1),Fs(e,t,n),Fd(n.stateNode),As=r,Ds=o;break;case 5:_s||xs(n,t);case 6:if(r=As,o=Ds,As=null,Fs(e,t,n),Ds=o,null!==(As=r))if(Ds)try{(9===As.nodeType?As.body:"HTML"===As.nodeName?As.ownerDocument.body:As).removeChild(n.stateNode)}catch(e){Sc(n,t,e)}else try{As.removeChild(n.stateNode)}catch(e){Sc(n,t,e)}break;case 18:null!==As&&(Ds?(Pd(9===(e=As).nodeType?e.body:"HTML"===e.nodeName?e.ownerDocument.body:e,n.stateNode),Bf(e)):Pd(As,n.stateNode));break;case 4:r=As,o=Ds,As=n.stateNode.containerInfo,Ds=!0,Fs(e,t,n),As=r,Ds=o;break;case 0:case 11:case 14:case 15:ys(2,n,t),_s||ys(4,n,t),Fs(e,t,n);break;case 1:_s||(xs(n,t),"function"==typeof(r=n.stateNode).componentWillUnmount&&bs(n,t,r)),Fs(e,t,n);break;case 21:Fs(e,t,n);break;case 22:_s=(r=_s)||null!==n.memoizedState,Fs(e,t,n),_s=r;break;default:Fs(e,t,n)}}function Hs(e,t){if(null===t.memoizedState&&null!==(e=t.alternate)&&null!==(e=e.memoizedState)){e=e.dehydrated;try{Bf(e)}catch(e){Sc(t,t.return,e)}}}function Vs(e,t){if(null===t.memoizedState&&null!==(e=t.alternate)&&null!==(e=e.memoizedState)&&null!==(e=e.dehydrated))try{Bf(e)}catch(e){Sc(t,t.return,e)}}function $s(e,t){var n=function(e){switch(e.tag){case 31:case 13:case 19:var t=e.stateNode;return null===t&&(t=e.stateNode=new Is),t;case 22:return null===(t=(e=e.stateNode)._retryCache)&&(t=e._retryCache=new Is),t;default:throw Error(a(435,e.tag))}}(e);t.forEach(function(t){if(!n.has(t)){n.add(t);var r=Oc.bind(null,e,t);t.then(r,r)}})}function Bs(e,t){var n=t.deletions;if(null!==n)for(var r=0;r title"))),fd(i,r,n),i[Ve]=e,et(i),r=i;break e;case"link":var l=nf("link","href",o).get(r+(n.href||""));if(l)for(var s=0;sl)break;var c=s.transferSize,d=s.initiatorType;c&&pd(d)&&(a+=c*((s=s.responseEnd)af?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(o)}}:null}(d,h),null!==h)return Hu=i,e.cancelPendingCommit=h(mc.bind(null,e,t,i,n,r,o,a,l,s,c,d,null,f,p)),void Qu(e,i,a,!u)}mc(e,t,i,n,r,o,a,l,s)}function Gu(e){for(var t=e;;){var n=t.tag;if((0===n||11===n||15===n)&&16384&t.flags&&null!==(n=t.updateQueue)&&null!==(n=n.stores))for(var r=0;rg&&(a=g,g=m,m=a);var y=tr(l,m),v=tr(l,g);if(y&&v&&(1!==p.rangeCount||p.anchorNode!==y.node||p.anchorOffset!==y.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var b=d.createRange();b.setStart(y.node,y.offset),p.removeAllRanges(),m>g?(p.addRange(b),p.extend(v.node,v.offset)):(b.setEnd(v.node,v.offset),p.addRange(b))}}}}for(d=[],p=l;p=p.parentNode;)1===p.nodeType&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for("function"==typeof l.focus&&l.focus(),l=0;ln?32:n,R.T=null,n=$u,$u=null;var i=Fu,l=Hu;if(Du=0,ju=Fu=null,Hu=0,6&pu)throw Error(a(331));var s=pu;if(pu|=4,su(i.current),eu(i,i.current,l,n),pu=s,Ac(0,!1),ve&&"function"==typeof ve.onPostCommitFiberRoot)try{ve.onPostCommitFiberRoot(ye,i)}catch{}return!0}finally{L.p=o,R.T=r,bc(e,t)}}function kc(e,t,n){t=Yr(n,t),null!==(e=wi(e,t=Tl(e.stateNode,t,2),2))&&(Ie(e,2),Lc(e))}function Sc(e,t,n){if(3===e.tag)kc(e,e,n);else for(;null!==t;){if(3===t.tag){kc(t,e,n);break}if(1===t.tag){var r=t.stateNode;if("function"==typeof t.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===Au||!Au.has(r))){e=Yr(n,e),null!==(r=wi(t,n=_l(2),2))&&(Nl(n,r,t,e),Ie(r,2),Lc(r));break}}t=t.return}}function Ec(e,t,n){var r=e.pingCache;if(null===r){r=e.pingCache=new fu;var o=new Set;r.set(t,o)}else void 0===(o=r.get(t))&&(o=new Set,r.set(t,o));o.has(n)||(xu=!0,o.add(n),e=Cc.bind(null,e,t,n),t.then(e,e))}function Cc(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,hu===e&&(gu&n)===n&&(4===Su||3===Su&&(62914560&gu)===gu&&300>se()-Iu?!(2&pu)&&tc(e,0):Mu|=n,Ou===gu&&(Ou=0)),Lc(e)}function Mc(e,t){0===t&&(t=_e()),null!==(e=zr(e,t))&&(Ie(e,t),Lc(e))}function Pc(e){var t=e.memoizedState,n=0;null!==t&&(n=t.retryLane),Mc(e,n)}function Oc(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,o=e.memoizedState;null!==o&&(n=o.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(a(314))}null!==r&&r.delete(t),Mc(e,n)}var Tc=null,_c=null,Nc=!1,Ic=!1,zc=!1,Rc=0;function Lc(e){e!==_c&&null===e.next&&(null===_c?Tc=_c=e:_c=_c.next=e),Ic=!0,Nc||(Nc=!0,Ed(function(){6&pu?oe(ce,Dc):Fc()}))}function Ac(e,t){if(!zc&&Ic){zc=!0;do{for(var n=!1,r=Tc;null!==r;){if(!t)if(0!==e){var o=r.pendingLanes;if(0===o)var i=0;else{var a=r.suspendedLanes,l=r.pingedLanes;i=(1<<31-we(42|e)+1)-1,i=201326741&(i&=o&~(a&~l))?201326741&i|1:i?2|i:0}0!==i&&(n=!0,Vc(r,i))}else i=gu,!(3&(i=Pe(r,r===hu?i:0,null!==r.cancelPendingCommit||-1!==r.timeoutHandle)))||Oe(r,i)||(n=!0,Vc(r,i));r=r.next}}while(n);zc=!1}}function Dc(){Fc()}function Fc(){Ic=Nc=!1;var e=0;0!==Rc&&function(){var e=window.event;return e&&"popstate"===e.type?e!==wd&&(wd=e,!0):(wd=null,!1)}()&&(e=Rc);for(var t=se(),n=null,r=Tc;null!==r;){var o=r.next,i=jc(r,t);0===i?(r.next=null,null===n?Tc=o:n.next=o,null===o&&(_c=n)):(n=r,(0!==e||3&i)&&(Ic=!0)),r=o}0!==Du&&5!==Du||Ac(e,!1),0!==Rc&&(Rc=0)}function jc(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,o=e.expirationTimes,i=-62914561&e.pendingLanes;0"u"?null:document;function Wd(e,t,n){var r=Bd;if(r&&"string"==typeof t&&t){var o=yt(t);o='link[rel="'+e+'"][href="'+o+'"]',"string"==typeof n&&(o+='[crossorigin="'+n+'"]'),Hd.has(o)||(Hd.add(o),e={rel:e,crossOrigin:n,href:t},null===r.querySelector(o)&&(fd(t=r.createElement("link"),"link",e),et(t),r.head.appendChild(t)))}}function qd(e,t,n,r){var o=(o=U.current)?Vd(o):null;if(!o)throw Error(a(446));switch(e){case"meta":case"title":return null;case"style":return"string"==typeof n.precedence&&"string"==typeof n.href?(t=Ud(n.href),(r=(n=Je(o).hoistableStyles).get(t))||(r={type:"style",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};case"link":if("stylesheet"===n.rel&&"string"==typeof n.href&&"string"==typeof n.precedence){e=Ud(n.href);var i=Je(o).hoistableStyles,l=i.get(e);if(l||(o=o.ownerDocument||o,l={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},i.set(e,l),(i=o.querySelector(Kd(e)))&&!i._p&&(l.instance=i,l.state.loading=5),jd.has(e)||(n={rel:"preload",as:"style",href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},jd.set(e,n),i||function(e,t,n,r){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?r.loading=1:(t=e.createElement("link"),r.preload=t,t.addEventListener("load",function(){return r.loading|=1}),t.addEventListener("error",function(){return r.loading|=2}),fd(t,"link",n),et(t),e.head.appendChild(t))}(o,e,n,l.state))),t&&null===r)throw Error(a(528,""));return l}if(t&&null!==r)throw Error(a(529,""));return null;case"script":return t=n.async,"string"==typeof(n=n.src)&&t&&"function"!=typeof t&&"symbol"!=typeof t?(t=Xd(n),(r=(n=Je(o).hoistableScripts).get(t))||(r={type:"script",instance:null,count:0,state:null},n.set(t,r)),r):{type:"void",instance:null,count:0,state:null};default:throw Error(a(444,e))}}function Ud(e){return'href="'+yt(e)+'"'}function Kd(e){return'link[rel="stylesheet"]['+e+"]"}function Yd(e){return p({},e,{"data-precedence":e.precedence,precedence:null})}function Xd(e){return'[src="'+yt(e)+'"]'}function Zd(e){return"script[async]"+e}function Gd(e,t,n){if(t.count++,null===t.instance)switch(t.type){case"style":var r=e.querySelector('style[data-href~="'+yt(n.href)+'"]');if(r)return t.instance=r,et(r),r;var o=p({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return et(r=(e.ownerDocument||e).createElement("style")),fd(r,"style",o),Qd(r,n.precedence,e),t.instance=r;case"stylesheet":o=Ud(n.href);var i=e.querySelector(Kd(o));if(i)return t.state.loading|=4,t.instance=i,et(i),i;r=Yd(n),(o=jd.get(o))&&Jd(r,o),et(i=(e.ownerDocument||e).createElement("link"));var l=i;return l._p=new Promise(function(e,t){l.onload=e,l.onerror=t}),fd(i,"link",r),t.state.loading|=4,Qd(i,n.precedence,e),t.instance=i;case"script":return i=Xd(n.src),(o=e.querySelector(Zd(i)))?(t.instance=o,et(o),o):(r=n,(o=jd.get(i))&&ef(r=p({},n),o),et(o=(e=e.ownerDocument||e).createElement("script")),fd(o,"link",r),e.head.appendChild(o),t.instance=o);case"void":return null;default:throw Error(a(443,t.type))}else"stylesheet"===t.type&&!(4&t.state.loading)&&(r=t.instance,t.state.loading|=4,Qd(r,n.precedence,e));return t.instance}function Qd(e,t,n){for(var r=n.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),o=r.length?r[r.length-1]:null,i=o,a=0;a title"):null)}function of(e){return!("stylesheet"===e.type&&!(3&e.state.loading))}var af=0;function lf(){if(this.count--,0===this.count&&(0===this.imgCount||!this.waitingForImages))if(this.stylesheets)uf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}var sf=null;function uf(e,t){e.stylesheets=null,null!==e.unsuspend&&(e.count++,sf=new Map,t.forEach(cf,e),sf=null,lf.call(e))}function cf(e,t){if(!(4&t.state.loading)){var n=sf.get(e);if(n)var r=n.get(null);else{n=new Map,sf.set(e,n);for(var o=e.querySelectorAll("link[data-precedence],style[data-precedence]"),i=0;i{(function e(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||"function"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}})(),t.exports=s()}),c=t(e=>{var t=Symbol.for("react.transitional.element"),n=Symbol.for("react.fragment");function r(e,n,r){var o=null;if(void 0!==r&&(o=""+r),void 0!==n.key&&(o=""+n.key),"key"in n)for(var i in r={},n)"key"!==i&&(r[i]=n[i]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:o,ref:void 0===n?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r}),d=t((e,t)=>{t.exports=c()}),f=e(r(),1),p=d();function h(e){if("string"==typeof e||"number"==typeof e)return""+e;let t="";if(Array.isArray(e))for(let n,r=0;r{}};function g(){for(var e,t=0,n=arguments.length,r={};t=0&&(n=e.slice(r+1),e=e.slice(0,r)),e&&!t.hasOwnProperty(e))throw Error("unknown type: "+e);return{type:e,name:n}})}(e+"",r),i=-1,a=o.length;if(!(arguments.length<2)){if(null!=t&&"function"!=typeof t)throw Error("invalid callback: "+t);for(;++i0)for(var n,r,o=Array(n),i=0;i=0&&"xmlns"!==(t=e.slice(0,n))&&(e=e.slice(n+1)),w.hasOwnProperty(t)?{space:w[t],local:e}:e}function k(e){return function(){var t=this.ownerDocument,n=this.namespaceURI;return"http://www.w3.org/1999/xhtml"===n&&"http://www.w3.org/1999/xhtml"===t.documentElement.namespaceURI?t.createElement(e):t.createElementNS(n,e)}}function S(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function E(e){var t=x(e);return(t.local?S:k)(t)}function C(){}function M(e){return null==e?C:function(){return this.querySelector(e)}}function P(){return[]}function O(e){return null==e?P:function(){return this.querySelectorAll(e)}}function T(e){return function(){return this.matches(e)}}function _(e){return function(t){return t.matches(e)}}var N=Array.prototype.find;function I(){return this.firstElementChild}var z=Array.prototype.filter;function R(){return Array.from(this.children)}function L(e){return Array(e.length)}function A(e,t){this.ownerDocument=e.ownerDocument,this.namespaceURI=e.namespaceURI,this._next=null,this._parent=e,this.__data__=t}function D(e,t,n,r,o,i){for(var a,l=0,s=t.length,u=i.length;lt?1:e>=t?0:NaN}function $(e){return function(){this.removeAttribute(e)}}function B(e){return function(){this.removeAttributeNS(e.space,e.local)}}function W(e,t){return function(){this.setAttribute(e,t)}}function q(e,t){return function(){this.setAttributeNS(e.space,e.local,t)}}function U(e,t){return function(){var n=t.apply(this,arguments);null==n?this.removeAttribute(e):this.setAttribute(e,n)}}function K(e,t){return function(){var n=t.apply(this,arguments);null==n?this.removeAttributeNS(e.space,e.local):this.setAttributeNS(e.space,e.local,n)}}function Y(e){return e.ownerDocument&&e.ownerDocument.defaultView||e.document&&e||e.defaultView}function X(e){return function(){this.style.removeProperty(e)}}function Z(e,t,n){return function(){this.style.setProperty(e,t,n)}}function G(e,t,n){return function(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(e):this.style.setProperty(e,r,n)}}function Q(e,t){return e.style.getPropertyValue(t)||Y(e).getComputedStyle(e,null).getPropertyValue(t)}function J(e){return function(){delete this[e]}}function ee(e,t){return function(){this[e]=t}}function te(e,t){return function(){var n=t.apply(this,arguments);null==n?delete this[e]:this[e]=n}}function ne(e){return e.trim().split(/^|\s+/)}function re(e){return e.classList||new oe(e)}function oe(e){this._node=e,this._names=ne(e.getAttribute("class")||"")}function ie(e,t){for(var n=re(e),r=-1,o=t.length;++r=0&&(this._names.splice(t,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};var Pe=[null];function Oe(e,t){this._groups=e,this._parents=t}function Te(){return new Oe([[document.documentElement]],Pe)}function _e(e){return"string"==typeof e?new Oe([[document.querySelector(e)]],[document.documentElement]):new Oe([[e]],Pe)}function Ne(e,t){if(e=function(e){let t;for(;t=e.sourceEvent;)e=t;return e}(e),void 0===t&&(t=e.currentTarget),t){var n=t.ownerSVGElement||t;if(n.createSVGPoint){var r=n.createSVGPoint();return r.x=e.clientX,r.y=e.clientY,[(r=r.matrixTransform(t.getScreenCTM().inverse())).x,r.y]}if(t.getBoundingClientRect){var o=t.getBoundingClientRect();return[e.clientX-o.left-t.clientLeft,e.clientY-o.top-t.clientTop]}}return[e.pageX,e.pageY]}Oe.prototype=Te.prototype={constructor:Oe,select:function(e){"function"!=typeof e&&(e=M(e));for(var t=this._groups,n=t.length,r=Array(n),o=0;o=w&&(w=b+1);!(v=g[w])&&++w=0;)(r=o[i])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(e){function t(t,n){return t&&n?e(t.__data__,n.__data__):!t-!n}e||=V;for(var n=this._groups,r=n.length,o=Array(r),i=0;i1?this.each((null==t?X:"function"==typeof t?G:Z)(e,t,n??"")):Q(this.node(),e)},property:function(e,t){return arguments.length>1?this.each((null==t?J:"function"==typeof t?te:ee)(e,t)):this.node()[e]},classed:function(e,t){var n=ne(e+"");if(arguments.length<2){for(var r=re(this.node()),o=-1,i=n.length;++o=0&&(t=e.slice(n+1),e=e.slice(0,n)),{type:e,name:t}})}(e+""),a=i.length;if(!(arguments.length<2)){for(l=t?Se:ke,r=0;r()=>e;function je(e,{sourceEvent:t,subject:n,target:r,identifier:o,active:i,x:a,y:l,dx:s,dy:u,dispatch:c}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},subject:{value:n,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:o,enumerable:!0,configurable:!0},active:{value:i,enumerable:!0,configurable:!0},x:{value:a,enumerable:!0,configurable:!0},y:{value:l,enumerable:!0,configurable:!0},dx:{value:s,enumerable:!0,configurable:!0},dy:{value:u,enumerable:!0,configurable:!0},_:{value:c}})}function He(e){return!e.ctrlKey&&!e.button}function Ve(){return this.parentNode}function $e(e,t){return t??{x:e.x,y:e.y}}function Be(){return navigator.maxTouchPoints||"ontouchstart"in this}function We(){var e,t,n,r,o=He,i=Ve,a=$e,l=Be,s={},u=g("start","drag","end"),c=0,d=0;function f(e){e.on("mousedown.drag",p).filter(l).on("touchstart.drag",y).on("touchmove.drag",v,Ie).on("touchend.drag touchcancel.drag",b).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function p(a,l){if(!r&&o.call(this,a,l)){var s=w(this,i.call(this,a,l),a,l,"mouse");s&&(_e(a.view).on("mousemove.drag",h,ze).on("mouseup.drag",m,ze),Ae(a.view),Re(a),n=!1,e=a.clientX,t=a.clientY,s("start",a))}}function h(r){if(Le(r),!n){var o=r.clientX-e,i=r.clientY-t;n=o*o+i*i>d}s.mouse("drag",r)}function m(e){_e(e.view).on("mousemove.drag mouseup.drag",null),De(e.view,n),Le(e),s.mouse("end",e)}function y(e,t){if(o.call(this,e,t)){var n,r,a=e.changedTouches,l=i.call(this,e,t),s=a.length;for(n=0;n>8&15|t>>4&240,t>>4&15|240&t,(15&t)<<4|15&t,1):8===n?dt(t>>24&255,t>>16&255,t>>8&255,(255&t)/255):4===n?dt(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|240&t,((15&t)<<4|15&t)/255):null):(t=et.exec(e))?new pt(t[1],t[2],t[3],1):(t=tt.exec(e))?new pt(255*t[1]/100,255*t[2]/100,255*t[3]/100,1):(t=nt.exec(e))?dt(t[1],t[2],t[3],t[4]):(t=rt.exec(e))?dt(255*t[1]/100,255*t[2]/100,255*t[3]/100,t[4]):(t=ot.exec(e))?bt(t[1],t[2]/100,t[3]/100,1):(t=it.exec(e))?bt(t[1],t[2]/100,t[3]/100,t[4]):at.hasOwnProperty(e)?ct(at[e]):"transparent"===e?new pt(NaN,NaN,NaN,0):null}function ct(e){return new pt(e>>16&255,e>>8&255,255&e,1)}function dt(e,t,n,r){return r<=0&&(e=t=n=NaN),new pt(e,t,n,r)}function ft(e,t,n,r){return 1===arguments.length?function(e){return e instanceof Ke||(e=ut(e)),e?new pt((e=e.rgb()).r,e.g,e.b,e.opacity):new pt}(e):new pt(e,t,n,r??1)}function pt(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}function ht(){return`#${vt(this.r)}${vt(this.g)}${vt(this.b)}`}function mt(){let e=gt(this.opacity);return`${1===e?"rgb(":"rgba("}${yt(this.r)}, ${yt(this.g)}, ${yt(this.b)}${1===e?")":`, ${e})`}`}function gt(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function yt(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function vt(e){return((e=yt(e))<16?"0":"")+e.toString(16)}function bt(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new xt(e,t,n,r)}function wt(e){if(e instanceof xt)return new xt(e.h,e.s,e.l,e.opacity);if(e instanceof Ke||(e=ut(e)),!e)return new xt;if(e instanceof xt)return e;var t=(e=e.rgb()).r/255,n=e.g/255,r=e.b/255,o=Math.min(t,n,r),i=Math.max(t,n,r),a=NaN,l=i-o,s=(i+o)/2;return l?(a=t===i?(n-r)/l+6*(n0&&s<1?0:a,new xt(a,l,s,e.opacity)}function xt(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}function kt(e){return(e=(e||0)%360)<0?e+360:e}function St(e){return Math.max(0,Math.min(1,e||0))}function Et(e,t,n){return 255*(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)}qe(Ke,ut,{copy(e){return Object.assign(new this.constructor,this,e)},displayable(){return this.rgb().displayable()},hex:lt,formatHex:lt,formatHex8:function(){return this.rgb().formatHex8()},formatHsl:function(){return wt(this).formatHsl()},formatRgb:st,toString:st}),qe(pt,ft,Ue(Ke,{brighter(e){return e=null==e?Xe:Xe**+e,new pt(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=null==e?Ye:Ye**+e,new pt(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new pt(yt(this.r),yt(this.g),yt(this.b),gt(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:ht,formatHex:ht,formatHex8:function(){return`#${vt(this.r)}${vt(this.g)}${vt(this.b)}${vt(255*(isNaN(this.opacity)?1:this.opacity))}`},formatRgb:mt,toString:mt})),qe(xt,function(e,t,n,r){return 1===arguments.length?wt(e):new xt(e,t,n,r??1)},Ue(Ke,{brighter(e){return e=null==e?Xe:Xe**+e,new xt(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=null==e?Ye:Ye**+e,new xt(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+360*(this.h<0),t=isNaN(e)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*t,o=2*n-r;return new pt(Et(e>=240?e-240:e+120,o,r),Et(e,o,r),Et(e<120?e+240:e-120,o,r),this.opacity)},clamp(){return new xt(kt(this.h),St(this.s),St(this.l),gt(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let e=gt(this.opacity);return`${1===e?"hsl(":"hsla("}${kt(this.h)}, ${100*St(this.s)}%, ${100*St(this.l)}%${1===e?")":`, ${e})`}`}}));var Ct=e=>()=>e;function Mt(e,t){var n=t-e;return n?function(e,t){return function(n){return e+n*t}}(e,n):Ct(isNaN(e)?t:e)}var Pt=function e(t){var n=function(e){return 1==(e=+e)?Mt:function(t,n){return n-t?function(e,t,n){return t=t**+n-(e**=+n),n=1/n,function(r){return(e+r*t)**+n}}(t,n,e):Ct(isNaN(t)?n:t)}}(t);function r(e,t){var r=n((e=ft(e)).r,(t=ft(t)).r),o=n(e.g,t.g),i=n(e.b,t.b),a=Mt(e.opacity,t.opacity);return function(t){return e.r=r(t),e.g=o(t),e.b=i(t),e.opacity=a(t),e+""}}return r.gamma=e,r}(1);function Ot(e,t){t||=[];var n,r=e?Math.min(t.length,e.length):0,o=t.slice();return function(i){for(n=0;ni&&(o=t.slice(i,o),l[a]?l[a]+=o:l[++a]=o),(n=n[0])===(r=r[0])?l[a]?l[a]+=r:l[++a]=r:(l[++a]=null,s.push({i:a,x:Nt(n,r)})),i=Rt.lastIndex;return i180?t+=360:t-e>180&&(e+=360),i.push({i:n.push(o(n)+"rotate(",null,r)-2,x:Nt(e,t)}))}function l(e,t,n,i){e===t?t&&n.push(o(n)+"skewX("+t+r):i.push({i:n.push(o(n)+"skewX(",null,r)-2,x:Nt(e,t)})}function s(e,t,n,r,i,a){if(e!==n||t!==r){var l=i.push(o(i)+"scale(",null,",",null,")");a.push({i:l-4,x:Nt(e,n)},{i:l-2,x:Nt(t,r)})}else(1!==n||1!==r)&&i.push(o(i)+"scale("+n+","+r+")")}return function(t,n){var r=[],o=[];return t=e(t),n=e(n),i(t.translateX,t.translateY,n.translateX,n.translateY,r,o),a(t.rotate,n.rotate,r,o),l(t.skewX,n.skewX,r,o),s(t.scaleX,t.scaleY,n.scaleX,n.scaleY,r,o),t=n=null,function(e){for(var t,n=-1,i=o.length;++n=0&&t._call.call(void 0,e),t=t._next;--Yt}()}finally{Yt=0,function(){for(var e,t,n=qt,r=1/0;n;)n._call?(r>n._time&&(r=n._time),e=n,n=n._next):(t=n._next,n._next=null,n=e?e._next=t:qt=t);Ut=e,un(r)}(),Qt=0}}function sn(){var e=en.now(),t=e-Gt;t>1e3&&(Jt-=t,Gt=e)}function un(e){Yt||(Xt&&=clearTimeout(Xt),e-Qt>24?(e<1/0&&(Xt=setTimeout(ln,e-en.now()-Jt)),Zt&&=clearInterval(Zt)):(Zt||=(Gt=en.now(),setInterval(sn,1e3)),Yt=1,tn(ln)))}function cn(e,t,n){var r=new on;return t=null==t?0:+t,r.restart(n=>{r.stop(),e(n+t)},t,n),r}on.prototype=an.prototype={constructor:on,restart:function(e,t,n){if("function"!=typeof e)throw TypeError("callback is not a function");n=(null==n?nn():+n)+(null==t?0:+t),!this._next&&Ut!==this&&(Ut?Ut._next=this:qt=this,Ut=this),this._call=e,this._time=n,un()},stop:function(){this._call&&(this._call=null,this._time=1/0,un())}};var dn=g("start","end","cancel","interrupt"),fn=[];function pn(e,t,n,r,o,i){var a=e.__transition;if(a){if(n in a)return}else e.__transition={};!function(e,t,n){var r,o=e.__transition;function i(s){var u,c,d,f;if(1!==n.state)return l();for(u in o)if((f=o[u]).name===n.name){if(3===f.state)return cn(i);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",e,e.__data__,f.index,f.group),delete o[u]):+u0)throw Error("too late; already scheduled");return n}function mn(e,t){var n=gn(e,t);if(n.state>3)throw Error("too late; already running");return n}function gn(e,t){var n=e.__transition;if(!n||!(n=n[t]))throw Error("transition not found");return n}function yn(e,t){var n,r,o,i=e.__transition,a=!0;if(i){for(o in t=null==t?null:t+"",i)(n=i[o]).name===t?(r=n.state>2&&n.state<5,n.state=6,n.timer.stop(),n.on.call(r?"interrupt":"cancel",e,e.__data__,n.index,n.group),delete i[o]):a=!1;a&&delete e.__transition}}function vn(e,t){var n,r;return function(){var o=mn(this,e),i=o.tween;if(i!==n)for(var a=0,l=(r=n=i).length;a=0&&(e=e.slice(0,t)),!e||"start"===e})}(t)?hn:mn;return function(){var a=i(this,e),l=a.on;l!==r&&(o=(r=l).copy()).on(t,n),a.on=o}}(n,e,t))},attr:function(e,t){var n=x(e),r="transform"===n?Bt:xn;return this.attrTween(e,"function"==typeof t?(n.local?Pn:Mn)(n,r,wn(this,"attr."+e,t)):null==t?(n.local?Sn:kn)(n):(n.local?Cn:En)(n,r,t))},attrTween:function(e,t){var n="attr."+e;if(arguments.length<2)return(n=this.tween(n))&&n._value;if(null==t)return this.tween(n,null);if("function"!=typeof t)throw Error();var r=x(e);return this.tween(n,(r.local?On:Tn)(r,t))},style:function(e,t,n){var r="transform"==(e+="")?$t:xn;return null==t?this.styleTween(e,function(e,t){var n,r,o;return function(){var i=Q(this,e),a=(this.style.removeProperty(e),Q(this,e));return i===a?null:i===n&&a===r?o:o=t(n=i,r=a)}}(e,r)).on("end.style."+e,Ln(e)):"function"==typeof t?this.styleTween(e,function(e,t,n){var r,o,i;return function(){var a=Q(this,e),l=n(this),s=l+"";return l??(this.style.removeProperty(e),s=l=Q(this,e)),a===s?null:a===r&&s===o?i:(o=s,i=t(r=a,l))}}(e,r,wn(this,"style."+e,t))).each(function(e,t){var n,r,o,i,a="style."+t,l="end."+a;return function(){var s=mn(this,e),u=s.on,c=null==s.value[a]?i||=Ln(t):void 0;(u!==n||o!==c)&&(r=(n=u).copy()).on(l,o=c),s.on=r}}(this._id,e)):this.styleTween(e,function(e,t,n){var r,o,i=n+"";return function(){var a=Q(this,e);return a===i?null:a===r?o:o=t(r=a,n)}}(e,r,t),n).on("end.style."+e,null)},styleTween:function(e,t,n){var r="style."+(e+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==t)return this.tween(r,null);if("function"!=typeof t)throw Error();return this.tween(r,function(e,t,n){var r,o;function i(){var i=t.apply(this,arguments);return i!==o&&(r=(o=i)&&function(e,t,n){return function(r){this.style.setProperty(e,t.call(this,r),n)}}(e,i,n)),r}return i._value=t,i}(e,t,n??""))},text:function(e){return this.tween("text","function"==typeof e?function(e){return function(){var t=e(this);this.textContent=t??""}}(wn(this,"text",e)):function(e){return function(){this.textContent=e}}(null==e?"":e+""))},textTween:function(e){var t="text";if(arguments.length<1)return(t=this.tween(t))&&t._value;if(null==e)return this.tween(t,null);if("function"!=typeof e)throw Error();return this.tween(t,function(e){var t,n;function r(){var r=e.apply(this,arguments);return r!==n&&(t=(n=r)&&function(e){return function(t){this.textContent=e.call(this,t)}}(r)),t}return r._value=e,r}(e))},remove:function(){return this.on("end.remove",function(e){return function(){var t=this.parentNode;for(var n in this.__transition)if(+n!==e)return;t&&t.removeChild(this)}}(this._id))},tween:function(e,t){var n=this._id;if(e+="",arguments.length<2){for(var r,o=gn(this.node(),n).tween,i=0,a=o.length;i()=>e;function Bn(e,{sourceEvent:t,target:n,transform:r,dispatch:o}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:o}})}function Wn(e,t,n){this.k=e,this.x=t,this.y=n}Wn.prototype={constructor:Wn,scale:function(e){return 1===e?this:new Wn(this.k*e,this.x,this.y)},translate:function(e,t){return 0===e&0===t?this:new Wn(this.k,this.x+this.k*e,this.y+this.k*t)},apply:function(e){return[e[0]*this.k+this.x,e[1]*this.k+this.y]},applyX:function(e){return e*this.k+this.x},applyY:function(e){return e*this.k+this.y},invert:function(e){return[(e[0]-this.x)/this.k,(e[1]-this.y)/this.k]},invertX:function(e){return(e-this.x)/this.k},invertY:function(e){return(e-this.y)/this.k},rescaleX:function(e){return e.copy().domain(e.range().map(this.invertX,this).map(e.invert,e))},rescaleY:function(e){return e.copy().domain(e.range().map(this.invertY,this).map(e.invert,e))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var qn=new Wn(1,0,0);function Un(e){for(;!e.__zoom;)if(!(e=e.parentNode))return qn;return e.__zoom}function Kn(e){e.stopImmediatePropagation()}function Yn(e){e.preventDefault(),e.stopImmediatePropagation()}function Xn(e){return!(e.ctrlKey&&"wheel"!==e.type||e.button)}function Zn(){var e=this;return e instanceof SVGElement?(e=e.ownerSVGElement||e).hasAttribute("viewBox")?[[(e=e.viewBox.baseVal).x,e.y],[e.x+e.width,e.y+e.height]]:[[0,0],[e.width.baseVal.value,e.height.baseVal.value]]:[[0,0],[e.clientWidth,e.clientHeight]]}function Gn(){return this.__zoom||qn}function Qn(e){return-e.deltaY*(1===e.deltaMode?.05:e.deltaMode?1:.002)*(e.ctrlKey?10:1)}function Jn(){return navigator.maxTouchPoints||"ontouchstart"in this}function er(e,t,n){var r=e.invertX(t[0][0])-n[0][0],o=e.invertX(t[1][0])-n[1][0],i=e.invertY(t[0][1])-n[0][1],a=e.invertY(t[1][1])-n[1][1];return e.translate(o>r?(r+o)/2:Math.min(0,r)||Math.max(0,o),a>i?(i+a)/2:Math.min(0,i)||Math.max(0,a))}function tr(){var e,t,n,r=Xn,o=Zn,i=er,a=Qn,l=Jn,s=[0,1/0],u=[[-1/0,-1/0],[1/0,1/0]],c=250,d=Kt,f=g("start","zoom","end"),p=0,h=10;function m(e){e.property("__zoom",Gn).on("wheel.zoom",S,{passive:!1}).on("mousedown.zoom",E).on("dblclick.zoom",C).filter(l).on("touchstart.zoom",M).on("touchmove.zoom",P).on("touchend.zoom touchcancel.zoom",O).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function y(e,t){return(t=Math.max(s[0],Math.min(s[1],t)))===e.k?e:new Wn(t,e.x,e.y)}function v(e,t,n){var r=t[0]-n[0]*e.k,o=t[1]-n[1]*e.k;return r===e.x&&o===e.y?e:new Wn(e.k,r,o)}function b(e){return[(+e[0][0]+ +e[1][0])/2,(+e[0][1]+ +e[1][1])/2]}function w(e,t,n,r){e.on("start.zoom",function(){x(this,arguments).event(r).start()}).on("interrupt.zoom end.zoom",function(){x(this,arguments).event(r).end()}).tween("zoom",function(){var e=this,i=arguments,a=x(e,i).event(r),l=o.apply(e,i),s=null==n?b(l):"function"==typeof n?n.apply(e,i):n,u=Math.max(l[1][0]-l[0][0],l[1][1]-l[0][1]),c=e.__zoom,f="function"==typeof t?t.apply(e,i):t,p=d(c.invert(s).concat(u/c.k),f.invert(s).concat(u/f.k));return function(e){if(1===e)e=f;else{var t=p(e),n=u/t[2];e=new Wn(n,s[0]-t[0]*n,s[1]-t[1]*n)}a.zoom(null,e)}})}function x(e,t,n){return!n&&e.__zooming||new k(e,t)}function k(e,t){this.that=e,this.args=t,this.active=0,this.sourceEvent=null,this.extent=o.apply(e,t),this.taps=0}function S(e,...t){if(r.apply(this,arguments)){var n=x(this,t).event(e),o=this.__zoom,l=Math.max(s[0],Math.min(s[1],o.k*2**a.apply(this,arguments))),c=Ne(e);if(n.wheel)(n.mouse[0][0]!==c[0]||n.mouse[0][1]!==c[1])&&(n.mouse[1]=o.invert(n.mouse[0]=c)),clearTimeout(n.wheel);else{if(o.k===l)return;n.mouse=[c,o.invert(c)],yn(this),n.start()}Yn(e),n.wheel=setTimeout(function(){n.wheel=null,n.end()},150),n.zoom("mouse",i(v(y(o,l),n.mouse[0],n.mouse[1]),n.extent,u))}}function E(e,...t){if(!n&&r.apply(this,arguments)){var o=e.currentTarget,a=x(this,t,!0).event(e),l=_e(e.view).on("mousemove.zoom",function(e){if(Yn(e),!a.moved){var t=e.clientX-c,n=e.clientY-d;a.moved=t*t+n*n>p}a.event(e).zoom("mouse",i(v(a.that.__zoom,a.mouse[0]=Ne(e,o),a.mouse[1]),a.extent,u))},!0).on("mouseup.zoom",function(e){l.on("mousemove.zoom mouseup.zoom",null),De(e.view,a.moved),Yn(e),a.event(e).end()},!0),s=Ne(e,o),c=e.clientX,d=e.clientY;Ae(e.view),Kn(e),a.mouse=[s,this.__zoom.invert(s)],yn(this),a.start()}}function C(e,...t){if(r.apply(this,arguments)){var n=this.__zoom,a=Ne(e.changedTouches?e.changedTouches[0]:e,this),l=n.invert(a),s=n.k*(e.shiftKey?.5:2),d=i(v(y(n,s),a,l),o.apply(this,t),u);Yn(e),c>0?_e(this).transition().duration(c).call(w,d,a,e):_e(this).call(m.transform,d,a,e)}}function M(n,...o){if(r.apply(this,arguments)){var i,a,l,s,u=n.touches,c=u.length,d=x(this,o,n.changedTouches.length===c).event(n);for(Kn(n),a=0;a`Moved selected node ${e}. New position, x: ${t}, y: ${n}`,"edge.a11yDescription.default":"Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.","controls.ariaLabel":"Control Panel","controls.zoomIn.ariaLabel":"Zoom In","controls.zoomOut.ariaLabel":"Zoom Out","controls.fitView.ariaLabel":"Fit View","controls.interactive.ariaLabel":"Toggle Interactivity","minimap.ariaLabel":"Mini Map","handle.ariaLabel":"Handle"};!function(e){e.Strict="strict",e.Loose="loose"}(nr||={}),function(e){e.Free="free",e.Vertical="vertical",e.Horizontal="horizontal"}(rr||={}),function(e){e.Partial="partial",e.Full="full"}(or||={});var sr,ur,cr,dr={inProgress:!1,isValid:null,from:null,fromHandle:null,fromPosition:null,fromNode:null,to:null,toHandle:null,toPosition:null,toNode:null,pointer:null};!function(e){e.Bezier="default",e.Straight="straight",e.Step="step",e.SmoothStep="smoothstep",e.SimpleBezier="simplebezier"}(sr||={}),function(e){e.Arrow="arrow",e.ArrowClosed="arrowclosed"}(ur||={}),function(e){e.Left="left",e.Top="top",e.Right="right",e.Bottom="bottom"}(cr||={});var fr={[cr.Left]:cr.Right,[cr.Right]:cr.Left,[cr.Top]:cr.Bottom,[cr.Bottom]:cr.Top};function pr(e){return null===e?null:e?"valid":"invalid"}var hr=e=>"id"in e&&"source"in e&&"target"in e,mr=e=>"id"in e&&"internals"in e&&!("source"in e)&&!("target"in e),gr=(e,t,n)=>{if(!e.id)return[];let r=new Set;return n.forEach(t=>{t.source===e.id&&r.add(t.target)}),t.filter(e=>r.has(e.id))},yr=(e,t,n)=>{if(!e.id)return[];let r=new Set;return n.forEach(t=>{t.target===e.id&&r.add(t.source)}),t.filter(e=>r.has(e.id))},vr=(e,t=[0,0])=>{let{width:n,height:r}=Br(e),o=e.origin??t,i=n*o[0],a=r*o[1];return{x:e.position.x-i,y:e.position.y-a}},br=(e,t={})=>{let n={x:1/0,y:1/0,x2:-1/0,y2:-1/0},r=!1;return e.forEach(e=>{(void 0===t.filter||t.filter(e))&&(n=Pr(n,Nr(e)),r=!0)}),r?Tr(n):{x:0,y:0,width:0,height:0}},wr=(e,t,[n,r,o]=[0,0,1],i=!1,a=!1)=>{let l={...Dr(t,[n,r,o]),width:t.width/o,height:t.height/o},s=[];for(let t of e.values()){let{measured:e,selectable:n=!0,hidden:r=!1}=t;if(a&&!n||r)continue;let o=e.width??t.width??t.initialWidth??null,u=e.height??t.height??t.initialHeight??null,c=zr(l,_r(t)),d=(o??0)*(u??0),f=i&&c>0;(!t.internals.handleBounds||f||c>=d||t.dragging)&&s.push(t)}return s};function xr({nodeId:e,nextPosition:t,nodeLookup:n,nodeOrigin:r=[0,0],nodeExtent:o,onError:i}){let a=n.get(e),l=a.parentId?n.get(a.parentId):void 0,{x:s,y:u}=l?l.internals.positionAbsolute:{x:0,y:0},c=a.origin??r,d=a.extent||o;if("parent"!==a.extent||a.expandParent)l&&$r(a.extent)&&(d=[[a.extent[0][0]+s,a.extent[0][1]+u],[a.extent[1][0]+s,a.extent[1][1]+u]]);else if(l){let e=l.measured.width,t=l.measured.height;e&&t&&(d=[[s,u],[s+e,u+t]])}else i?.("005","Only child nodes can use a parent extent.");let f=$r(d)?Sr(t,d,a.measured):t;return(void 0===a.measured.width||void 0===a.measured.height)&&i?.("015","It seems that you are trying to drag a node that is not initialized. Please use onNodesChange as explained in the docs."),{position:{x:f.x-s+(a.measured.width??0)*c[0],y:f.y-u+(a.measured.height??0)*c[1]},positionAbsolute:f}}var kr=(e,t=0,n=1)=>Math.min(Math.max(e,t),n),Sr=(e={x:0,y:0},t,n)=>({x:kr(e.x,t[0][0],t[1][0]-(n?.width??0)),y:kr(e.y,t[0][1],t[1][1]-(n?.height??0))});function Er(e,t,n){let{width:r,height:o}=Br(n),{x:i,y:a}=n.internals.positionAbsolute;return Sr(e,[[i,a],[i+r,a+o]],t)}var Cr=(e,t,n)=>en?-kr(Math.abs(e-n),1,t)/t:0,Mr=(e,t,n=15,r=40)=>[Cr(e.x,r,t.width-r)*n,Cr(e.y,r,t.height-r)*n],Pr=(e,t)=>({x:Math.min(e.x,t.x),y:Math.min(e.y,t.y),x2:Math.max(e.x2,t.x2),y2:Math.max(e.y2,t.y2)}),Or=({x:e,y:t,width:n,height:r})=>({x:e,y:t,x2:e+n,y2:t+r}),Tr=({x:e,y:t,x2:n,y2:r})=>({x:e,y:t,width:n-e,height:r-t}),_r=(e,t=[0,0])=>{let{x:n,y:r}=mr(e)?e.internals.positionAbsolute:vr(e,t);return{x:n,y:r,width:e.measured?.width??e.width??e.initialWidth??0,height:e.measured?.height??e.height??e.initialHeight??0}},Nr=(e,t=[0,0])=>{let{x:n,y:r}=mr(e)?e.internals.positionAbsolute:vr(e,t);return{x:n,y:r,x2:n+(e.measured?.width??e.width??e.initialWidth??0),y2:r+(e.measured?.height??e.height??e.initialHeight??0)}},Ir=(e,t)=>Tr(Pr(Or(e),Or(t))),zr=(e,t)=>{let n=Math.max(0,Math.min(e.x+e.width,t.x+t.width)-Math.max(e.x,t.x)),r=Math.max(0,Math.min(e.y+e.height,t.y+t.height)-Math.max(e.y,t.y));return Math.ceil(n*r)},Rr=e=>Lr(e.width)&&Lr(e.height)&&Lr(e.x)&&Lr(e.y),Lr=e=>!isNaN(e)&&isFinite(e),Ar=(e,t=[1,1])=>({x:t[0]*Math.round(e.x/t[0]),y:t[1]*Math.round(e.y/t[1])}),Dr=({x:e,y:t},[n,r,o],i=!1,a=[1,1])=>{let l={x:(e-n)/o,y:(t-r)/o};return i?Ar(l,a):l},Fr=({x:e,y:t},[n,r,o])=>({x:e*o+n,y:t*o+r});function jr(e,t){if("number"==typeof e)return Math.floor(.5*(t-t/(1+e)));if("string"==typeof e&&e.endsWith("px")){let t=parseFloat(e);if(!Number.isNaN(t))return Math.floor(t)}if("string"==typeof e&&e.endsWith("%")){let n=parseFloat(e);if(!Number.isNaN(n))return Math.floor(t*n*.01)}return console.error(`The padding value "${e}" is invalid. Please provide a number or a string with a valid unit (px or %).`),0}var Hr=(e,t,n,r,o,i)=>{let a=function(e,t,n){if("string"==typeof e||"number"==typeof e){let r=jr(e,n),o=jr(e,t);return{top:r,right:o,bottom:r,left:o,x:2*o,y:2*r}}if("object"==typeof e){let r=jr(e.top??e.y??0,n),o=jr(e.bottom??e.y??0,n),i=jr(e.left??e.x??0,t),a=jr(e.right??e.x??0,t);return{top:r,right:a,bottom:o,left:i,x:i+a,y:r+o}}return{top:0,right:0,bottom:0,left:0,x:0,y:0}}(i,t,n),l=(t-a.x)/e.width,s=(n-a.y)/e.height,u=kr(Math.min(l,s),r,o),c=t/2-(e.x+e.width/2)*u,d=n/2-(e.y+e.height/2)*u,f=function(e,t,n,r,o,i){let{x:a,y:l}=Fr(e,[t,n,r]),{x:s,y:u}=Fr({x:e.x+e.width,y:e.y+e.height},[t,n,r]),c=o-s,d=i-u;return{left:Math.floor(a),top:Math.floor(l),right:Math.floor(c),bottom:Math.floor(d)}}(e,c,d,u,t,n),p=Math.min(f.left-a.left,0),h=Math.min(f.top-a.top,0);return{x:c-p+Math.min(f.right-a.right,0),y:d-h+Math.min(f.bottom-a.bottom,0),zoom:u}},Vr=()=>typeof navigator<"u"&&navigator?.userAgent?.indexOf("Mac")>=0;function $r(e){return null!=e&&"parent"!==e}function Br(e){return{width:e.measured?.width??e.width??e.initialWidth??0,height:e.measured?.height??e.height??e.initialHeight??0}}function Wr(e){return void 0!==(e.measured?.width??e.width??e.initialWidth)&&void 0!==(e.measured?.height??e.height??e.initialHeight)}function qr(e,t={width:0,height:0},n,r,o){let i={...e},a=r.get(n);if(a){let e=a.origin||o;i.x+=a.internals.positionAbsolute.x-(t.width??0)*e[0],i.y+=a.internals.positionAbsolute.y-(t.height??0)*e[1]}return i}function Ur(e,t){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}function Kr(e){return{...lr,...e||{}}}function Yr(e,{snapGrid:t=[0,0],snapToGrid:n=!1,transform:r,containerBounds:o}){let{x:i,y:a}=eo(e),l=Dr({x:i-(o?.left??0),y:a-(o?.top??0)},r),{x:s,y:u}=n?Ar(l,t):l;return{xSnapped:s,ySnapped:u,...l}}var Xr=e=>({width:e.offsetWidth,height:e.offsetHeight}),Zr=e=>e?.getRootNode?.()||window?.document,Gr=["INPUT","SELECT","TEXTAREA"];function Qr(e){let t=e.composedPath?.()?.[0]||e.target;return 1===t?.nodeType&&(Gr.includes(t.nodeName)||t.hasAttribute("contenteditable")||!!t.closest(".nokey"))}var Jr=e=>"clientX"in e,eo=(e,t)=>{let n=Jr(e),r=n?e.clientX:e.touches?.[0].clientX,o=n?e.clientY:e.touches?.[0].clientY;return{x:r-(t?.left??0),y:o-(t?.top??0)}},to=(e,t,n,r,o)=>{let i=t.querySelectorAll(`.${e}`);return i&&i.length?Array.from(i).map(t=>{let i=t.getBoundingClientRect();return{id:t.getAttribute("data-handleid"),type:e,nodeId:o,position:t.getAttribute("data-handlepos"),x:(i.left-n.left)/r,y:(i.top-n.top)/r,...Xr(t)}}):null};function no({sourceX:e,sourceY:t,targetX:n,targetY:r,sourceControlX:o,sourceControlY:i,targetControlX:a,targetControlY:l}){let s=.125*e+.375*o+.375*a+.125*n,u=.125*t+.375*i+.375*l+.125*r;return[s,u,Math.abs(s-e),Math.abs(u-t)]}function ro(e,t){return e>=0?.5*e:25*t*Math.sqrt(-e)}function oo({pos:e,x1:t,y1:n,x2:r,y2:o,c:i}){switch(e){case cr.Left:return[t-ro(t-r,i),n];case cr.Right:return[t+ro(r-t,i),n];case cr.Top:return[t,n-ro(n-o,i)];case cr.Bottom:return[t,n+ro(o-n,i)]}}function io({sourceX:e,sourceY:t,sourcePosition:n=cr.Bottom,targetX:r,targetY:o,targetPosition:i=cr.Top,curvature:a=.25}){let[l,s]=oo({pos:n,x1:e,y1:t,x2:r,y2:o,c:a}),[u,c]=oo({pos:i,x1:r,y1:o,x2:e,y2:t,c:a}),[d,f,p,h]=no({sourceX:e,sourceY:t,targetX:r,targetY:o,sourceControlX:l,sourceControlY:s,targetControlX:u,targetControlY:c});return[`M${e},${t} C${l},${s} ${u},${c} ${r},${o}`,d,f,p,h]}function ao({sourceX:e,sourceY:t,targetX:n,targetY:r}){let o=Math.abs(n-e)/2,i=n0}var uo=({source:e,sourceHandle:t,target:n,targetHandle:r})=>`xy-edge__${e}${t||""}-${n}${r||""}`;function co({sourceX:e,sourceY:t,targetX:n,targetY:r}){let[o,i,a,l]=ao({sourceX:e,sourceY:t,targetX:n,targetY:r});return[`M ${e},${t}L ${n},${r}`,o,i,a,l]}var fo={[cr.Left]:{x:-1,y:0},[cr.Right]:{x:1,y:0},[cr.Top]:{x:0,y:-1},[cr.Bottom]:{x:0,y:1}},po=(e,t)=>Math.sqrt((t.x-e.x)**2+(t.y-e.y)**2);function ho(e,t,n,r){let o=Math.min(po(e,t)/2,po(t,n)/2,r),{x:i,y:a}=t;if(e.x===i&&i===n.x||e.y===a&&a===n.y)return`L${i} ${a}`;if(e.y===a)return`L ${i+o*(e.xt===cr.Left||t===cr.Right?e.xe[h]?-1:1)*r:v[h]=(f[h]>n[h]?-1:1)*r}}if(t!==r){let e="x"===h?"y":"x",t=u[h]===c[e],n=d[e]>f[e],r=d[e]=Math.max(Math.abs(p.y-g[0].y),Math.abs(b.y-g[0].y))?(l=(p.x+b.x)/2,s=g[0].y):(l=g[0].x,s=(p.y+b.y)/2)}let x={x:d.x+y.x,y:d.y+y.y},k={x:f.x+v.x,y:f.y+v.y};return[[e,...x.x!==g[0].x||x.y!==g[0].y?[x]:[],...g,...k.x!==g[g.length-1].x||k.y!==g[g.length-1].y?[k]:[],n],l,s,b,w]}({source:{x:e,y:t},sourcePosition:n,target:{x:r,y:o},targetPosition:i,center:{x:l,y:s},offset:u,stepPosition:c}),g=`M${d[0].x} ${d[0].y}`;for(let e=1;ee.id===t):e[0])||null}function wo(e,t){return e?"string"==typeof e?e:`${t?`${t}__`:""}${Object.keys(e).sort().map(t=>`${t}=${e[t]}`).join("&")}`:""}var xo={nodeOrigin:[0,0],nodeExtent:ir,elevateNodesOnSelect:!0,zIndexMode:"basic",defaults:{}},ko={...xo,checkEquality:!0};function So(e,t){let n={...e};for(let e in t)void 0!==t[e]&&(n[e]=t[e]);return n}function Eo(e,t){if(!e.handles)return e.measured?t?.internals.handleBounds:void 0;let n=[],r=[];for(let t of e.handles){let o={id:t.id,width:t.width??1,height:t.height??1,nodeId:e.id,x:t.x,y:t.y,position:t.position,type:t.type};"source"===t.type?n.push(o):"target"===t.type&&r.push(o)}return{source:n,target:r}}function Co(e){return"manual"===e}function Mo(e,t,n,r={}){let o=So(ko,r),i={i:0},a=new Map(t),l=o?.elevateNodesOnSelect&&!Co(o.zIndexMode)?1e3:0,s=e.length>0,u=!1;t.clear(),n.clear();for(let c of e){let e=a.get(c.id);if(o.checkEquality&&c===e?.internals.userNode)t.set(c.id,e);else{let n=Sr(vr(c,o.nodeOrigin),$r(c.extent)?c.extent:o.nodeExtent,Br(c));e={...o.defaults,...c,measured:{width:c.measured?.width,height:c.measured?.height},internals:{positionAbsolute:n,handleBounds:Eo(c,e),z:Oo(c,l,o.zIndexMode),userNode:c}},t.set(c.id,e)}(void 0===e.measured||void 0===e.measured.width||void 0===e.measured.height)&&!e.hidden&&(s=!1),c.parentId&&Po(e,t,n,r,i),u||=c.selected??!1}return{nodesInitialized:s,hasSelectedNodes:u}}function Po(e,t,n,r,o){let{elevateNodesOnSelect:i,nodeOrigin:a,nodeExtent:l,zIndexMode:s}=So(xo,r),u=e.parentId,c=t.get(u);if(!c)return;(function(e,t){if(!e.parentId)return;let n=t.get(e.parentId);n?n.set(e.id,e):t.set(e.parentId,new Map([[e.id,e]]))})(e,n),o&&!c.parentId&&void 0===c.internals.rootParentIndex&&"auto"===s&&(c.internals.rootParentIndex=++o.i,c.internals.z=c.internals.z+10*o.i),o&&void 0!==c.internals.rootParentIndex&&(o.i=c.internals.rootParentIndex);let{x:d,y:f,z:p}=function(e,t,n,r,o,i){let{x:a,y:l}=t.internals.positionAbsolute,s=Br(e),u=vr(e,n),c=$r(e.extent)?Sr(u,e.extent,s):u,d=Sr({x:a+c.x,y:l+c.y},r,s);"parent"===e.extent&&(d=Er(d,s,t));let f=Oo(e,o,i),p=t.internals.z??0;return{x:d.x,y:d.y,z:p>=f?p+1:f}}(e,c,a,l,i&&!Co(s)?1e3:0,s),{positionAbsolute:h}=e.internals,m=d!==h.x||f!==h.y;(m||p!==e.internals.z)&&t.set(e.id,{...e,internals:{...e.internals,positionAbsolute:m?{x:d,y:f}:h,z:p}})}function Oo(e,t,n){let r=Lr(e.zIndex)?e.zIndex:0;return Co(n)?r:r+(e.selected?t:0)}function To(e,t,n,r=[0,0]){let o=[],i=new Map;for(let n of e){let e=t.get(n.parentId);if(!e)continue;let r=Ir(i.get(n.parentId)?.expandedRect??_r(e),n.rect);i.set(n.parentId,{expandedRect:r,parent:e})}return i.size>0&&i.forEach(({expandedRect:t,parent:i},a)=>{let l=i.internals.positionAbsolute,s=Br(i),u=i.origin??r,c=t.x0||d>0||h||m)&&(o.push({id:a,type:"position",position:{x:i.position.x-c+h,y:i.position.y-d+m}}),n.get(a)?.forEach(t=>{e.some(e=>e.id===t.id)||o.push({id:t.id,type:"position",position:{x:t.position.x+c,y:t.position.y+d}})})),(s.widthe.id===n):l?.[0])??null;return s&&i?{...s,...vo(a,s,s.position,!0)}:s}function Ao(e,t){return e||(t?.classList.contains("target")?"target":t?.classList.contains("source")?"source":null)}var Do=()=>!0;function Fo(e,{handle:t,connectionMode:n,fromNodeId:r,fromHandleId:o,fromType:i,doc:a,lib:l,flowId:s,isValidConnection:u=Do,nodeLookup:c}){let d="target"===i,f=t?a.querySelector(`.${l}-flow__handle[data-id="${s}-${t?.nodeId}-${t?.id}-${t?.type}"]`):null,{x:p,y:h}=eo(e),m=a.elementFromPoint(p,h),g=m?.classList.contains(`${l}-flow__handle`)?m:f,y={handleDomNode:g,isValid:!1,connection:null,toHandle:null};if(g){let e=Ao(void 0,g),t=g.getAttribute("data-nodeid"),i=g.getAttribute("data-handleid"),a=g.classList.contains("connectable"),l=g.classList.contains("connectableend");if(!t||!e)return y;let s={source:d?t:r,sourceHandle:d?i:o,target:d?r:t,targetHandle:d?o:i};y.connection=s,y.isValid=a&&l&&(n===nr.Strict?d&&"source"===e||!d&&"target"===e:t!==r||i!==o)&&u(s),y.toHandle=Lo(t,e,i,c,n,!0)}return y}var jo,Ho={onPointerDown:function(e,{connectionMode:t,connectionRadius:n,handleId:r,nodeId:o,edgeUpdaterType:i,isTarget:a,domNode:l,nodeLookup:s,lib:u,autoPanOnConnect:c,flowId:d,panBy:f,cancelConnection:p,onConnectStart:h,onConnect:m,onConnectEnd:g,isValidConnection:y=Do,onReconnectEnd:v,updateConnection:b,getTransform:w,getFromHandle:x,autoPanSpeed:k,dragThreshold:S=1,handleDomNode:E}){let C,M=Zr(e.target),P=0,{x:O,y:T}=eo(e),_=Ao(i,E),N=l?.getBoundingClientRect(),I=!1;if(!N||!_)return;let z=Lo(o,_,r,s,t);if(!z)return;let R=eo(e,N),L=!1,A=null,D=!1,F=null;function j(){if(!c||!N)return;let[e,t]=Mr(R,N,k);f({x:e,y:t}),P=requestAnimationFrame(j)}let H={...z,nodeId:o,type:_,position:z.position},V=s.get(o),$={inProgress:!0,isValid:null,from:vo(V,H,cr.Left,!0),fromHandle:H,fromPosition:H.position,fromNode:V,to:R,toHandle:null,toPosition:fr[H.position],toNode:null,pointer:R};function B(){I=!0,b($),h?.(e,{nodeId:o,handleId:r,handleType:_})}function W(e){if(!I){let{x:t,y:n}=eo(e),r=t-O,o=n-T;if(!(r*r+o*o>S*S))return;B()}if(!x()||!H)return void q(e);let i=w();R=eo(e,N),C=function(e,t,n,r){let o=[],i=1/0,a=function(e,t,n){let r=[],o={x:e.x-n,y:e.y-n,width:2*n,height:2*n};for(let e of t.values())zr(o,_r(e))>0&&r.push(e);return r}(e,n,t+250);for(let n of a){let a=[...n.internals.handleBounds?.source??[],...n.internals.handleBounds?.target??[]];for(let l of a){if(r.nodeId===l.nodeId&&r.type===l.type&&r.id===l.id)continue;let{x:a,y:s}=vo(n,l,l.position,!0),u=Math.sqrt((a-e.x)**2+(s-e.y)**2);u>t||(u1){let e="source"===r.type?"target":"source";return o.find(t=>t.type===e)??o[0]}return o[0]}(Dr(R,i,!1,[1,1]),n,s,H),L||=(j(),!0);let l=Fo(e,{handle:C,connectionMode:t,fromNodeId:o,fromHandleId:r,fromType:a?"target":"source",isValidConnection:y,doc:M,lib:u,flowId:d,nodeLookup:s});F=l.handleDomNode,A=l.connection,D=function(e,t){let n=null;return t?n=!0:e&&!t&&(n=!1),n}(!!C,l.isValid);let c=s.get(o),f=c?vo(c,H,cr.Left,!0):$.from,p={...$,from:f,isValid:D,to:l.toHandle&&D?Fr({x:l.toHandle.x,y:l.toHandle.y},i):R,toHandle:l.toHandle,toPosition:D&&l.toHandle?l.toHandle.position:fr[H.position],toNode:l.toHandle?s.get(l.toHandle.nodeId):null,pointer:R};b(p),$=p}function q(e){if(!("touches"in e&&e.touches.length>0)){if(I){(C||F)&&A&&D&&m?.(A);let{inProgress:t,...n}=$,r={...n,toPosition:$.toHandle?$.toPosition:null};g?.(e,r),i&&v?.(e,r)}p(),cancelAnimationFrame(P),L=!1,D=!1,A=null,F=null,M.removeEventListener("mousemove",W),M.removeEventListener("mouseup",q),M.removeEventListener("touchmove",W),M.removeEventListener("touchend",q)}}0===S&&B(),M.addEventListener("mousemove",W),M.addEventListener("mouseup",q),M.addEventListener("touchmove",W),M.addEventListener("touchend",q)},isValid:Fo},Vo=e=>({x:e.x,y:e.y,zoom:e.k}),$o=({x:e,y:t,zoom:n})=>qn.translate(e,t).scale(n),Bo=(e,t)=>e.target.closest(`.${t}`),Wo=(e,t)=>2===t&&Array.isArray(e)&&e.includes(2),qo=e=>((e*=2)<=1?e*e*e:(e-=2)*e*e+2)/2,Uo=(e,t=0,n=qo,r=()=>{})=>{let o="number"==typeof t&&t>0;return o||r(),o?e.transition().duration(t).ease(n).on("end",r):e},Ko=e=>{let t=e.ctrlKey&&Vr()?10:1;return-e.deltaY*(1===e.deltaMode?.05:e.deltaMode?1:.002)*t};function Yo(e){return{isHorizontal:e.includes("right")||e.includes("left"),isVertical:e.includes("bottom")||e.includes("top"),affectsX:e.includes("left"),affectsY:e.includes("top")}}function Xo(e,t){return Math.max(0,t-e)}function Zo(e,t){return Math.max(0,e-t)}function Go(e,t,n){return Math.max(0,t-e,e-n)}function Qo(e,t){return e?!t:t}!function(e){e.Line="line",e.Handle="handle"}(jo||={});var Jo={width:0,height:0,x:0,y:0},ei={...Jo,pointerX:0,pointerY:0,aspectRatio:1};function ti(e,t,n){let r=t.position.x+e.position.x,o=t.position.y+e.position.y,i=e.measured.width??0,a=e.measured.height??0,l=n[0]*i,s=n[1]*a;return[[r-l,o-s],[r+i-l,o+a-s]]}var ni=t(e=>{var t=r(),n="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},o=t.useState,i=t.useEffect,a=t.useLayoutEffect,l=t.useDebugValue;function s(e){var t=e.getSnapshot;e=e.value;try{var r=t();return!n(e,r)}catch{return!0}}var u=typeof window>"u"||void 0===window.document||void 0===window.document.createElement?function(e,t){return t()}:function(e,t){var n=t(),r=o({inst:{value:n,getSnapshot:t}}),u=r[0].inst,c=r[1];return a(function(){u.value=n,u.getSnapshot=t,s(u)&&c({inst:u})},[e,n,t]),i(function(){return s(u)&&c({inst:u}),e(function(){s(u)&&c({inst:u})})},[e]),l(n),n};e.useSyncExternalStore=void 0===t.useSyncExternalStore?u:t.useSyncExternalStore}),ri=t((e,t)=>{t.exports=ni()}),oi=t(e=>{var t=r(),n=ri(),o="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},i=n.useSyncExternalStore,a=t.useRef,l=t.useEffect,s=t.useMemo,u=t.useDebugValue;e.useSyncExternalStoreWithSelector=function(e,t,n,r,c){var d=a(null);if(null===d.current){var f={hasValue:!1,value:null};d.current=f}else f=d.current;d=s(function(){function e(e){if(!l){if(l=!0,i=e,e=r(e),void 0!==c&&f.hasValue){var t=f.value;if(c(t,e))return a=t}return a=e}if(t=a,o(i,e))return t;var n=r(e);return void 0!==c&&c(t,n)?(i=e,t):(i=e,a=n)}var i,a,l=!1,s=void 0===n?null:n;return[function(){return e(t())},null===s?void 0:function(){return e(s())}]},[t,n,r,c]);var p=i(e,d[0],d[1]);return l(function(){f.hasValue=!0,f.value=p},[p]),u(p),p}}),ii=e(t((e,t)=>{t.exports=oi()})(),1),ai=e=>{let t,n=new Set,r=(e,r)=>{let o="function"==typeof e?e(t):e;if(!Object.is(o,t)){let e=t;t=r??("object"!=typeof o||!o)?o:Object.assign({},t,o),n.forEach(n=>n(t,e))}},o=()=>t,i={setState:r,getState:o,getInitialState:()=>a,subscribe:e=>(n.add(e),()=>n.delete(e)),destroy:()=>{n.clear()}},a=t=e(r,o,i);return i},{useDebugValue:li}=f.default,{useSyncExternalStoreWithSelector:si}=ii.default,ui=e=>e;function ci(e,t=ui,n){let r=si(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return li(r),r}var di=(e,t)=>{let n=(e=>e?ai(e):ai)(e),r=(e,r=t)=>ci(n,e,r);return Object.assign(r,n),r};function fi(e,t){if(Object.is(e,t))return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(let[n,r]of e)if(!Object.is(r,t.get(n)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}let n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(let r of n)if(!Object.prototype.hasOwnProperty.call(t,r)||!Object.is(e[r],t[r]))return!1;return!0}var pi=e(l()),hi=(0,f.createContext)(null),mi=hi.Provider,gi=((e="react")=>`Seems like you have not used zustand provider as an ancestor. Help: https://${e}flow.dev/error#001`)("react");function yi(e,t){let n=(0,f.useContext)(hi);if(null===n)throw Error(gi);return ci(n,e,t)}function vi(){let e=(0,f.useContext)(hi);if(null===e)throw Error(gi);return(0,f.useMemo)(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe}),[e])}var bi={display:"none"},wi={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},xi="react-flow__node-desc",ki="react-flow__edge-desc",Si=e=>e.ariaLiveMessage,Ei=e=>e.ariaLabelConfig;function Ci({rfId:e}){let t=yi(Si);return(0,p.jsx)("div",{id:`react-flow__aria-live-${e}`,"aria-live":"assertive","aria-atomic":"true",style:wi,children:t})}function Mi({rfId:e,disableKeyboardA11y:t}){let n=yi(Ei);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)("div",{id:`${xi}-${e}`,style:bi,children:t?n["node.a11yDescription.default"]:n["node.a11yDescription.keyboardDisabled"]}),(0,p.jsx)("div",{id:`${ki}-${e}`,style:bi,children:n["edge.a11yDescription.default"]}),!t&&(0,p.jsx)(Ci,{rfId:e})]})}var Pi=(0,f.forwardRef)(({position:e="top-left",children:t,className:n,style:r,...o},i)=>(0,p.jsx)("div",{className:h(["react-flow__panel",n,...`${e}`.split("-")]),style:r,ref:i,...o,children:t}));function Oi({proOptions:e,position:t="bottom-right"}){return e?.hideAttribution?null:(0,p.jsx)(Pi,{position:t,className:"react-flow__attribution","data-message":"Please only hide this attribution when you are subscribed to React Flow Pro: https://pro.reactflow.dev",children:(0,p.jsx)("a",{href:"https://reactflow.dev",target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution",children:"React Flow"})})}Pi.displayName="Panel";var Ti=e=>{let t=[],n=[];for(let[,n]of e.nodeLookup)n.selected&&t.push(n.internals.userNode);for(let[,t]of e.edgeLookup)t.selected&&n.push(t);return{selectedNodes:t,selectedEdges:n}},_i=e=>e.id;function Ni(e,t){return fi(e.selectedNodes.map(_i),t.selectedNodes.map(_i))&&fi(e.selectedEdges.map(_i),t.selectedEdges.map(_i))}function Ii({onSelectionChange:e}){let t=vi(),{selectedNodes:n,selectedEdges:r}=yi(Ti,Ni);return(0,f.useEffect)(()=>{let o={nodes:n,edges:r};e?.(o),t.getState().onSelectionChangeHandlers.forEach(e=>e(o))},[n,r,e]),null}var zi=e=>!!e.onSelectionChangeHandlers;function Ri({onSelectionChange:e}){let t=yi(zi);return e||t?(0,p.jsx)(Ii,{onSelectionChange:e}):null}var Li=[0,0],Ai={x:0,y:0,zoom:1},Di=[..."nodes.edges.defaultNodes.defaultEdges.onConnect.onConnectStart.onConnectEnd.onClickConnectStart.onClickConnectEnd.nodesDraggable.autoPanOnNodeFocus.nodesConnectable.nodesFocusable.edgesFocusable.edgesReconnectable.elevateNodesOnSelect.elevateEdgesOnSelect.minZoom.maxZoom.nodeExtent.onNodesChange.onEdgesChange.elementsSelectable.connectionMode.snapGrid.snapToGrid.translateExtent.connectOnClick.defaultEdgeOptions.fitView.fitViewOptions.onNodesDelete.onEdgesDelete.onDelete.onNodeDrag.onNodeDragStart.onNodeDragStop.onSelectionDrag.onSelectionDragStart.onSelectionDragStop.onMoveStart.onMove.onMoveEnd.noPanClassName.nodeOrigin.autoPanOnConnect.autoPanOnNodeDrag.onError.connectionRadius.isValidConnection.selectNodesOnDrag.nodeDragThreshold.connectionDragThreshold.onBeforeDelete.debug.autoPanSpeed.ariaLabelConfig.zIndexMode".split("."),"rfId"],Fi=e=>({setNodes:e.setNodes,setEdges:e.setEdges,setMinZoom:e.setMinZoom,setMaxZoom:e.setMaxZoom,setTranslateExtent:e.setTranslateExtent,setNodeExtent:e.setNodeExtent,reset:e.reset,setDefaultNodesAndEdges:e.setDefaultNodesAndEdges}),ji={translateExtent:ir,nodeOrigin:Li,minZoom:.5,maxZoom:2,elementsSelectable:!0,noPanClassName:"nopan",rfId:"1"};function Hi(e){let{setNodes:t,setEdges:n,setMinZoom:r,setMaxZoom:o,setTranslateExtent:i,setNodeExtent:a,reset:l,setDefaultNodesAndEdges:s}=yi(Fi,fi),u=vi();(0,f.useEffect)(()=>(s(e.defaultNodes,e.defaultEdges),()=>{c.current=ji,l()}),[]);let c=(0,f.useRef)(ji);return(0,f.useEffect)(()=>{for(let l of Di){let s=e[l];s!==c.current[l]&&void 0!==e[l]&&("nodes"===l?t(s):"edges"===l?n(s):"minZoom"===l?r(s):"maxZoom"===l?o(s):"translateExtent"===l?i(s):"nodeExtent"===l?a(s):"ariaLabelConfig"===l?u.setState({ariaLabelConfig:Kr(s)}):"fitView"===l?u.setState({fitViewQueued:s}):"fitViewOptions"===l?u.setState({fitViewOptions:s}):u.setState({[l]:s}))}c.current=e},Di.map(t=>e[t])),null}function Vi(){return typeof window>"u"||!window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)")}var $i=typeof document<"u"?document:null;function Bi(e=null,t={target:$i,actInsideInputWithModifier:!0}){let[n,r]=(0,f.useState)(!1),o=(0,f.useRef)(!1),i=(0,f.useRef)(new Set([])),[a,l]=(0,f.useMemo)(()=>{if(null!==e){let t=(Array.isArray(e)?e:[e]).filter(e=>"string"==typeof e).map(e=>e.replace("+","\n").replace("\n\n","\n+").split("\n"));return[t,t.reduce((e,t)=>e.concat(...t),[])]}return[[],[]]},[e]);return(0,f.useEffect)(()=>{let n=t?.target??$i,s=t?.actInsideInputWithModifier??!0;if(null!==e){let e=e=>{if(o.current=e.ctrlKey||e.metaKey||e.shiftKey||e.altKey,(!o.current||o.current&&!s)&&Qr(e))return!1;let n=qi(e.code,l);if(i.current.add(e[n]),Wi(a,i.current,!1)){let n=e.composedPath?.()?.[0]||e.target,i="BUTTON"===n?.nodeName||"A"===n?.nodeName;!1!==t.preventDefault&&(o.current||!i)&&e.preventDefault(),r(!0)}},u=e=>{let t=qi(e.code,l);Wi(a,i.current,!0)?(r(!1),i.current.clear()):i.current.delete(e[t]),"Meta"===e.key&&i.current.clear(),o.current=!1},c=()=>{i.current.clear(),r(!1)};return n?.addEventListener("keydown",e),n?.addEventListener("keyup",u),window.addEventListener("blur",c),window.addEventListener("contextmenu",c),()=>{n?.removeEventListener("keydown",e),n?.removeEventListener("keyup",u),window.removeEventListener("blur",c),window.removeEventListener("contextmenu",c)}}},[e,r]),n}function Wi(e,t,n){return e.filter(e=>n||e.length===t.size).some(e=>e.every(e=>t.has(e)))}function qi(e,t){return t.includes(e)?"code":"key"}function Ui(e,t){let n=[],r=new Map,o=[];for(let t of e)if("add"!==t.type)if("remove"===t.type||"replace"===t.type)r.set(t.id,[t]);else{let e=r.get(t.id);e?e.push(t):r.set(t.id,[t])}else o.push(t);for(let e of t){let t=r.get(e.id);if(!t){n.push(e);continue}if("remove"===t[0].type)continue;if("replace"===t[0].type){n.push({...t[0].item});continue}let o={...e};for(let e of t)Ki(e,o);n.push(o)}return o.length&&o.forEach(e=>{void 0===e.index?n.push({...e.item}):n.splice(e.index,0,{...e.item})}),n}function Ki(e,t){switch(e.type){case"select":t.selected=e.selected;break;case"position":void 0!==e.position&&(t.position=e.position),void 0!==e.dragging&&(t.dragging=e.dragging);break;case"dimensions":void 0!==e.dimensions&&(t.measured={...e.dimensions},e.setAttributes&&((!0===e.setAttributes||"width"===e.setAttributes)&&(t.width=e.dimensions.width),(!0===e.setAttributes||"height"===e.setAttributes)&&(t.height=e.dimensions.height))),"boolean"==typeof e.resizing&&(t.resizing=e.resizing)}}function Yi(e,t){return Ui(e,t)}function Xi(e,t){return Ui(e,t)}function Zi(e,t){return{id:e,type:"select",selected:t}}function Gi(e,t=new Set,n=!1){let r=[];for(let[o,i]of e){let e=t.has(o);(void 0!==i.selected||e)&&i.selected!==e&&(n&&(i.selected=e),r.push(Zi(i.id,e)))}return r}function Qi({items:e=[],lookup:t}){let n=[],r=new Map(e.map(e=>[e.id,e]));for(let[r,o]of e.entries()){let e=t.get(o.id),i=e?.internals?.userNode??e;void 0!==i&&i!==o&&n.push({id:o.id,item:o,type:"replace"}),void 0===i&&n.push({item:o,type:"add",index:r})}for(let[e]of t)void 0===r.get(e)&&n.push({id:e,type:"remove"});return n}function Ji(e){return{id:e.id,type:"remove"}}var ea=(e,t)=>{},ta=e=>(e=>"id"in e&&"position"in e&&!("source"in e)&&!("target"in e))(e);function na(e){return(0,f.forwardRef)(e)}var ra=typeof window<"u"?f.useLayoutEffect:f.useEffect;function oa(e){let[t,n]=(0,f.useState)(BigInt(0)),[r]=(0,f.useState)(()=>function(e){let t=[];return{get:()=>t,reset:()=>{t=[]},push:n=>{t.push(n),e()}}}(()=>n(e=>e+BigInt(1))));return ra(()=>{let t=r.get();t.length&&(e(t),r.reset())},[t]),r}var ia=(0,f.createContext)(null);function aa({children:e}){let t=vi(),n=oa((0,f.useCallback)(e=>{let{nodes:n=[],setNodes:r,hasDefaultNodes:o,onNodesChange:i,nodeLookup:a,fitViewQueued:l,onNodesChangeMiddlewareMap:s}=t.getState(),u=n;for(let t of e)u="function"==typeof t?t(u):t;let c=Qi({items:u,lookup:a});for(let e of s.values())c=e(c);o&&r(u),c.length>0?i?.(c):l&&window.requestAnimationFrame(()=>{let{fitViewQueued:e,nodes:n,setNodes:r}=t.getState();e&&r(n)})},[])),r=oa((0,f.useCallback)(e=>{let{edges:n=[],setEdges:r,hasDefaultEdges:o,onEdgesChange:i,edgeLookup:a}=t.getState(),l=n;for(let t of e)l="function"==typeof t?t(l):t;o?r(l):i&&i(Qi({items:l,lookup:a}))},[])),o=(0,f.useMemo)(()=>({nodeQueue:n,edgeQueue:r}),[]);return(0,p.jsx)(ia.Provider,{value:o,children:e})}var la=e=>!!e.panZoom;function sa(){let e=(()=>{let e=vi();return(0,f.useMemo)(()=>({zoomIn:async t=>{let{panZoom:n}=e.getState();return!!n&&n.scaleBy(1.2,t)},zoomOut:async t=>{let{panZoom:n}=e.getState();return!!n&&n.scaleBy(1/1.2,t)},zoomTo:async(t,n)=>{let{panZoom:r}=e.getState();return!!r&&r.scaleTo(t,n)},getZoom:()=>e.getState().transform[2],setViewport:async(t,n)=>{let{transform:[r,o,i],panZoom:a}=e.getState();return!!a&&(await a.setViewport({x:t.x??r,y:t.y??o,zoom:t.zoom??i},n),!0)},getViewport:()=>{let[t,n,r]=e.getState().transform;return{x:t,y:n,zoom:r}},setCenter:async(t,n,r)=>e.getState().setCenter(t,n,r),fitBounds:async(t,n)=>{let{width:r,height:o,minZoom:i,maxZoom:a,panZoom:l}=e.getState(),s=Hr(t,r,o,i,a,n?.padding??.1);return!!l&&(await l.setViewport(s,{duration:n?.duration,ease:n?.ease,interpolate:n?.interpolate}),!0)},screenToFlowPosition:(t,n={})=>{let{transform:r,snapGrid:o,snapToGrid:i,domNode:a}=e.getState();if(!a)return t;let{x:l,y:s}=a.getBoundingClientRect(),u={x:t.x-l,y:t.y-s},c=n.snapGrid??o;return Dr(u,r,n.snapToGrid??i,c)},flowToScreenPosition:t=>{let{transform:n,domNode:r}=e.getState();if(!r)return t;let{x:o,y:i}=r.getBoundingClientRect(),a=Fr(t,n);return{x:a.x+o,y:a.y+i}}}),[])})(),t=vi(),n=function(){let e=(0,f.useContext)(ia);if(!e)throw Error("useBatchContext must be used within a BatchProvider");return e}(),r=yi(la),o=(0,f.useMemo)(()=>{let e=e=>t.getState().nodeLookup.get(e),r=e=>{n.nodeQueue.push(e)},o=e=>{n.edgeQueue.push(e)},i=e=>{let{nodeLookup:n,nodeOrigin:r}=t.getState(),o=ta(e)?e:n.get(e.id),i=o.parentId?qr(o.position,o.measured,o.parentId,n,r):o.position;return _r({...o,position:i,width:o.measured?.width??o.width,height:o.measured?.height??o.height})},a=(e,t,n={replace:!1})=>{r(r=>r.map(r=>{if(r.id===e){let e="function"==typeof t?t(r):t;return n.replace&&ta(e)?e:{...r,...e}}return r}))},l=(e,t,n={replace:!1})=>{o(r=>r.map(r=>{if(r.id===e){let e="function"==typeof t?t(r):t;return n.replace&&(e=>hr(e))(e)?e:{...r,...e}}return r}))};return{getNodes:()=>t.getState().nodes.map(e=>({...e})),getNode:t=>e(t)?.internals.userNode,getInternalNode:e,getEdges:()=>{let{edges:e=[]}=t.getState();return e.map(e=>({...e}))},getEdge:e=>t.getState().edgeLookup.get(e),setNodes:r,setEdges:o,addNodes:e=>{let t=Array.isArray(e)?e:[e];n.nodeQueue.push(e=>[...e,...t])},addEdges:e=>{let t=Array.isArray(e)?e:[e];n.edgeQueue.push(e=>[...e,...t])},toObject:()=>{let{nodes:e=[],edges:n=[],transform:r}=t.getState(),[o,i,a]=r;return{nodes:e.map(e=>({...e})),edges:n.map(e=>({...e})),viewport:{x:o,y:i,zoom:a}}},deleteElements:async({nodes:e=[],edges:n=[]})=>{let{nodes:r,edges:o,onNodesDelete:i,onEdgesDelete:a,triggerNodeChanges:l,triggerEdgeChanges:s,onDelete:u,onBeforeDelete:c}=t.getState(),{nodes:d,edges:f}=await async function({nodesToRemove:e=[],edgesToRemove:t=[],nodes:n,edges:r,onBeforeDelete:o}){let i=new Set(e.map(e=>e.id)),a=[];for(let e of n){if(!1===e.deletable)continue;let t=i.has(e.id),n=!t&&e.parentId&&a.find(t=>t.id===e.parentId);(t||n)&&a.push(e)}let l=new Set(t.map(e=>e.id)),s=r.filter(e=>!1!==e.deletable),u=((e,t)=>{let n=new Set;return e.forEach(e=>{n.add(e.id)}),t.filter(e=>n.has(e.source)||n.has(e.target))})(a,s);for(let e of s)l.has(e.id)&&!u.find(t=>t.id===e.id)&&u.push(e);if(!o)return{edges:u,nodes:a};let c=await o({nodes:a,edges:u});return"boolean"==typeof c?c?{edges:u,nodes:a}:{edges:[],nodes:[]}:c}({nodesToRemove:e,edgesToRemove:n,nodes:r,edges:o,onBeforeDelete:c}),p=f.length>0,h=d.length>0;if(p){let e=f.map(Ji);a?.(f),s(e)}if(h){let e=d.map(Ji);i?.(d),l(e)}return(h||p)&&u?.({nodes:d,edges:f}),{deletedNodes:d,deletedEdges:f}},getIntersectingNodes:(e,n=!0,r)=>{let o=Rr(e),a=o?e:i(e),l=void 0!==r;return a?(r||t.getState().nodes).filter(r=>{let i=t.getState().nodeLookup.get(r.id);if(i&&!o&&(r.id===e.id||!i.internals.positionAbsolute))return!1;let s=_r(l?r:i),u=zr(s,a);return n&&u>0||u>=s.width*s.height||u>=a.width*a.height}):[]},isNodeIntersecting:(e,t,n=!0)=>{let r=Rr(e)?e:i(e);if(!r)return!1;let o=zr(r,t);return n&&o>0||o>=t.width*t.height||o>=r.width*r.height},updateNode:a,updateNodeData:(e,t,n={replace:!1})=>{a(e,e=>{let r="function"==typeof t?t(e):t;return n.replace?{...e,data:r}:{...e,data:{...e.data,...r}}},n)},updateEdge:l,updateEdgeData:(e,t,n={replace:!1})=>{l(e,e=>{let r="function"==typeof t?t(e):t;return n.replace?{...e,data:r}:{...e,data:{...e.data,...r}}},n)},getNodesBounds:e=>{let{nodeLookup:n,nodeOrigin:r}=t.getState();return((e,t={nodeOrigin:[0,0]})=>0===e.length?{x:0,y:0,width:0,height:0}:Tr(e.reduce((e,n)=>{let r="string"==typeof n,o=t.nodeLookup||r?void 0:n;return t.nodeLookup&&(o=r?t.nodeLookup.get(n):mr(n)?n:t.nodeLookup.get(n.id)),Pr(e,o?Nr(o,t.nodeOrigin):{x:0,y:0,x2:0,y2:0})},{x:1/0,y:1/0,x2:-1/0,y2:-1/0})))(e,{nodeLookup:n,nodeOrigin:r})},getHandleConnections:({type:e,id:n,nodeId:r})=>Array.from(t.getState().connectionLookup.get(`${r}-${e}${n?`-${n}`:""}`)?.values()??[]),getNodeConnections:({type:e,handleId:n,nodeId:r})=>Array.from(t.getState().connectionLookup.get(`${r}${e?n?`-${e}-${n}`:`-${e}`:""}`)?.values()??[]),fitView:async e=>{let r=t.getState().fitViewResolver??function(){let e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}}();return t.setState({fitViewQueued:!0,fitViewOptions:e,fitViewResolver:r}),n.nodeQueue.push(e=>[...e]),r.promise}}},[]);return(0,f.useMemo)(()=>({...o,...e,viewportInitialized:r}),[r])}var ua=e=>e.selected,ca=typeof window<"u"?window:void 0;function da({deleteKeyCode:e,multiSelectionKeyCode:t}){let n=vi(),{deleteElements:r}=sa(),o=Bi(e,{actInsideInputWithModifier:!1}),i=Bi(t,{target:ca});(0,f.useEffect)(()=>{if(o){let{edges:e,nodes:t}=n.getState();r({nodes:t.filter(ua),edges:e.filter(ua)}),n.setState({nodesSelectionActive:!1})}},[o]),(0,f.useEffect)(()=>{n.setState({multiSelectionActive:i})},[i])}var fa={position:"absolute",width:"100%",height:"100%",top:0,left:0},pa=e=>({userSelectionActive:e.userSelectionActive,lib:e.lib,connectionInProgress:e.connection.inProgress});function ha({onPaneContextMenu:e,zoomOnScroll:t=!0,zoomOnPinch:n=!0,panOnScroll:r=!1,panOnScrollSpeed:o=.5,panOnScrollMode:i=rr.Free,zoomOnDoubleClick:a=!0,panOnDrag:l=!0,defaultViewport:s,translateExtent:u,minZoom:c,maxZoom:d,zoomActivationKeyCode:h,preventScrolling:m=!0,children:g,noWheelClassName:y,noPanClassName:v,onViewportChange:b,isControlledViewport:w,paneClickDistance:x,selectionOnDrag:k}){let S=vi(),E=(0,f.useRef)(null),{userSelectionActive:C,lib:M,connectionInProgress:P}=yi(pa,fi),O=Bi(h),T=(0,f.useRef)();!function(e){let t=vi();(0,f.useEffect)(()=>{let n=()=>{if(!e.current||!(e.current.checkVisibility?.()??1))return!1;let n=Xr(e.current);(0===n.height||0===n.width)&&t.getState().onError?.("004","The parent container needs a width and a height to render the graph."),t.setState({width:n.width||500,height:n.height||500})};if(e.current){n(),window.addEventListener("resize",n);let t=new ResizeObserver(()=>n());return t.observe(e.current),()=>{window.removeEventListener("resize",n),t&&e.current&&t.unobserve(e.current)}}},[])}(E);let _=(0,f.useCallback)(e=>{b?.({x:e[0],y:e[1],zoom:e[2]}),w||S.setState({transform:e})},[b,w]);return(0,f.useEffect)(()=>{if(E.current){T.current=function({domNode:e,minZoom:t,maxZoom:n,translateExtent:r,viewport:o,onPanZoom:i,onPanZoomStart:a,onPanZoomEnd:l,onDraggingChange:s}){let u={isZoomingOrPanning:!1,usedRightMouseButton:!1,prevViewport:{},mouseButton:0,timerId:void 0,panScrollTimeout:void 0,isPanScrolling:!1},c=e.getBoundingClientRect(),d=tr().scaleExtent([t,n]).translateExtent(r),f=_e(e).call(d);y({x:o.x,y:o.y,zoom:kr(o.zoom,t,n)},[[0,0],[c.width,c.height]],r);let p=f.on("wheel.zoom"),h=f.on("dblclick.zoom");async function m(e,t){return!!f&&new Promise(n=>{d?.interpolate("linear"===t?.interpolate?At:Kt).transform(Uo(f,t?.duration,t?.ease,()=>n(!0)),e)})}function g(){d.on("zoom",null)}async function y(e,t,n){let r=$o(e),o=d?.constrain()(r,t,n);return o&&await m(o),o}return d.wheelDelta(Ko),{update:function({noWheelClassName:e,noPanClassName:t,onPaneContextMenu:n,userSelectionActive:r,panOnScroll:o,panOnDrag:c,panOnScrollMode:m,panOnScrollSpeed:y,preventScrolling:v,zoomOnPinch:b,zoomOnScroll:w,zoomOnDoubleClick:x,zoomActivationKeyPressed:k,lib:S,onTransformChange:E,connectionInProgress:C,paneClickDistance:M,selectionOnDrag:P}){r&&!u.isZoomingOrPanning&&g();let O=o&&!k&&!r;d.clickDistance(P?1/0:!Lr(M)||M<0?0:M);let T=O?function({zoomPanValues:e,noWheelClassName:t,d3Selection:n,d3Zoom:r,panOnScrollMode:o,panOnScrollSpeed:i,zoomOnPinch:a,onPanZoomStart:l,onPanZoom:s,onPanZoomEnd:u}){return c=>{if(Bo(c,t))return c.ctrlKey&&c.preventDefault(),!1;c.preventDefault(),c.stopImmediatePropagation();let d=n.property("__zoom").k||1;if(c.ctrlKey&&a){let e=Ne(c),t=d*2**Ko(c);return void r.scaleTo(n,t,e,c)}let f=1===c.deltaMode?20:1,p=o===rr.Vertical?0:c.deltaX*f,h=o===rr.Horizontal?0:c.deltaY*f;!Vr()&&c.shiftKey&&o!==rr.Vertical&&(p=c.deltaY*f,h=0),r.translateBy(n,-p/d*i,-h/d*i,{internal:!0});let m=Vo(n.property("__zoom"));clearTimeout(e.panScrollTimeout),e.isPanScrolling?(s?.(c,m),e.panScrollTimeout=setTimeout(()=>{u?.(c,m),e.isPanScrolling=!1},150)):(e.isPanScrolling=!0,l?.(c,m))}}({zoomPanValues:u,noWheelClassName:e,d3Selection:f,d3Zoom:d,panOnScrollMode:m,panOnScrollSpeed:y,zoomOnPinch:b,onPanZoomStart:a,onPanZoom:i,onPanZoomEnd:l}):function({noWheelClassName:e,preventScrolling:t,d3ZoomHandler:n}){return function(r,o){let i="wheel"===r.type,a=!t&&i&&!r.ctrlKey,l=Bo(r,e);if(r.ctrlKey&&i&&l&&r.preventDefault(),a||l)return null;r.preventDefault(),n.call(this,r,o)}}({noWheelClassName:e,preventScrolling:v,d3ZoomHandler:p});f.on("wheel.zoom",T,{passive:!1});let _=function({zoomPanValues:e,onDraggingChange:t,onPanZoomStart:n}){return r=>{if(r.sourceEvent?.internal)return;let o=Vo(r.transform);e.mouseButton=r.sourceEvent?.button||0,e.isZoomingOrPanning=!0,e.prevViewport=o,"mousedown"===r.sourceEvent?.type&&t(!0),n&&n?.(r.sourceEvent,o)}}({zoomPanValues:u,onDraggingChange:s,onPanZoomStart:a});d.on("start",_);let N=function({zoomPanValues:e,panOnDrag:t,onPaneContextMenu:n,onTransformChange:r,onPanZoom:o}){return i=>{e.usedRightMouseButton=!(!n||!Wo(t,e.mouseButton??0)),i.sourceEvent?.sync||r([i.transform.x,i.transform.y,i.transform.k]),o&&!i.sourceEvent?.internal&&o?.(i.sourceEvent,Vo(i.transform))}}({zoomPanValues:u,panOnDrag:c,onPaneContextMenu:!!n,onPanZoom:i,onTransformChange:E});d.on("zoom",N);let I=function({zoomPanValues:e,panOnDrag:t,panOnScroll:n,onDraggingChange:r,onPanZoomEnd:o,onPaneContextMenu:i}){return a=>{if(!a.sourceEvent?.internal&&(e.isZoomingOrPanning=!1,i&&Wo(t,e.mouseButton??0)&&!e.usedRightMouseButton&&a.sourceEvent&&i(a.sourceEvent),e.usedRightMouseButton=!1,r(!1),o)){let t=Vo(a.transform);e.prevViewport=t,clearTimeout(e.timerId),e.timerId=setTimeout(()=>{o?.(a.sourceEvent,t)},n?150:0)}}}({zoomPanValues:u,panOnDrag:c,panOnScroll:o,onPaneContextMenu:n,onPanZoomEnd:l,onDraggingChange:s});d.on("end",I);let z=function({zoomActivationKeyPressed:e,zoomOnScroll:t,zoomOnPinch:n,panOnDrag:r,panOnScroll:o,zoomOnDoubleClick:i,userSelectionActive:a,noWheelClassName:l,noPanClassName:s,lib:u,connectionInProgress:c}){return d=>{let f=e||t,p=n&&d.ctrlKey,h="wheel"===d.type;if(1===d.button&&"mousedown"===d.type&&(Bo(d,`${u}-flow__node`)||Bo(d,`${u}-flow__edge`)))return!0;if(!r&&!f&&!o&&!i&&!n||a||c&&!h||Bo(d,l)&&h||Bo(d,s)&&(!h||o&&h&&!e)||!n&&d.ctrlKey&&h)return!1;if(!n&&"touchstart"===d.type&&d.touches?.length>1)return d.preventDefault(),!1;if(!f&&!o&&!p&&h||!r&&("mousedown"===d.type||"touchstart"===d.type)||Array.isArray(r)&&!r.includes(d.button)&&"mousedown"===d.type)return!1;let m=Array.isArray(r)&&r.includes(d.button)||!d.button||d.button<=1;return(!d.ctrlKey||h)&&m}}({zoomActivationKeyPressed:k,panOnDrag:c,zoomOnScroll:w,panOnScroll:o,zoomOnDoubleClick:x,zoomOnPinch:b,userSelectionActive:r,noPanClassName:t,noWheelClassName:e,lib:S,connectionInProgress:C});d.filter(z),x?f.on("dblclick.zoom",h):f.on("dblclick.zoom",null)},destroy:g,setViewport:async function(e,t){let n=$o(e);return await m(n,t),n},setViewportConstrained:y,getViewport:function(){let e=f?Un(f.node()):{x:0,y:0,k:1};return{x:e.x,y:e.y,zoom:e.k}},scaleTo:async function(e,t){return!!f&&new Promise(n=>{d?.interpolate("linear"===t?.interpolate?At:Kt).scaleTo(Uo(f,t?.duration,t?.ease,()=>n(!0)),e)})},scaleBy:async function(e,t){return!!f&&new Promise(n=>{d?.interpolate("linear"===t?.interpolate?At:Kt).scaleBy(Uo(f,t?.duration,t?.ease,()=>n(!0)),e)})},setScaleExtent:function(e){d?.scaleExtent(e)},setTranslateExtent:function(e){d?.translateExtent(e)},syncViewport:function(e){if(f){let t=$o(e),n=f.property("__zoom");(n.k!==e.zoom||n.x!==e.x||n.y!==e.y)&&d?.transform(f,t,null,{sync:!0})}},setClickDistance:function(e){let t=!Lr(e)||e<0?0:e;d?.clickDistance(t)}}}({domNode:E.current,minZoom:c,maxZoom:d,translateExtent:u,viewport:s,onDraggingChange:e=>S.setState(t=>t.paneDragging===e?t:{paneDragging:e}),onPanZoomStart:(e,t)=>{let{onViewportChangeStart:n,onMoveStart:r}=S.getState();r?.(e,t),n?.(t)},onPanZoom:(e,t)=>{let{onViewportChange:n,onMove:r}=S.getState();r?.(e,t),n?.(t)},onPanZoomEnd:(e,t)=>{let{onViewportChangeEnd:n,onMoveEnd:r}=S.getState();r?.(e,t),n?.(t)}});let{x:e,y:t,zoom:n}=T.current.getViewport();return S.setState({panZoom:T.current,transform:[e,t,n],domNode:E.current.closest(".react-flow")}),()=>{T.current?.destroy()}}},[]),(0,f.useEffect)(()=>{T.current?.update({onPaneContextMenu:e,zoomOnScroll:t,zoomOnPinch:n,panOnScroll:r,panOnScrollSpeed:o,panOnScrollMode:i,zoomOnDoubleClick:a,panOnDrag:l,zoomActivationKeyPressed:O,preventScrolling:m,noPanClassName:v,userSelectionActive:C,noWheelClassName:y,lib:M,onTransformChange:_,connectionInProgress:P,selectionOnDrag:k,paneClickDistance:x})},[e,t,n,r,o,i,a,l,O,m,v,C,y,M,_,P,k,x]),(0,p.jsx)("div",{className:"react-flow__renderer",ref:E,style:fa,children:g})}var ma=e=>({userSelectionActive:e.userSelectionActive,userSelectionRect:e.userSelectionRect});function ga(){let{userSelectionActive:e,userSelectionRect:t}=yi(ma,fi);return e&&t?(0,p.jsx)("div",{className:"react-flow__selection react-flow__container",style:{width:t.width,height:t.height,transform:`translate(${t.x}px, ${t.y}px)`}}):null}var ya=(e,t)=>n=>{n.target===t.current&&e?.(n)},va=e=>({userSelectionActive:e.userSelectionActive,elementsSelectable:e.elementsSelectable,connectionInProgress:e.connection.inProgress,dragging:e.paneDragging,panBy:e.panBy,autoPanSpeed:e.autoPanSpeed});function ba({isSelecting:e,selectionKeyPressed:t,selectionMode:n=or.Full,panOnDrag:r,autoPanOnSelection:o,paneClickDistance:i,selectionOnDrag:a,onSelectionStart:l,onSelectionEnd:s,onPaneClick:u,onPaneContextMenu:c,onPaneScroll:d,onPaneMouseEnter:m,onPaneMouseMove:g,onPaneMouseLeave:y,children:v}){let b=(0,f.useRef)(0),w=vi(),{userSelectionActive:x,elementsSelectable:k,dragging:S,connectionInProgress:E,panBy:C,autoPanSpeed:M}=yi(va,fi),P=k&&(e||x),O=(0,f.useRef)(null),T=(0,f.useRef)(),_=(0,f.useRef)(new Set),N=(0,f.useRef)(new Set),I=(0,f.useRef)(!1),z=(0,f.useRef)({x:0,y:0}),R=(0,f.useRef)(!1),L=e=>{I.current||E?I.current=!1:(u?.(e),w.getState().resetSelectedElements(),w.setState({nodesSelectionActive:!1}))},A=d?e=>d(e):void 0;function D(e,t){let{userSelectionRect:r}=w.getState();if(!r)return;let{transform:o,nodeLookup:i,edgeLookup:a,connectionLookup:l,triggerNodeChanges:s,triggerEdgeChanges:u,defaultEdgeOptions:c}=w.getState(),d={x:r.startX,y:r.startY},{x:f,y:p}=Fr(d,o),h={startX:d.x,startY:d.y,x:ee.id)),N.current=new Set;let y=c?.selectable??!0;for(let e of _.current){let t=l.get(e);if(t)for(let{edgeId:e}of t.values()){let t=a.get(e);t&&(t.selectable??y)&&N.current.add(e)}}Ur(m,_.current)||s(Gi(i,_.current,!0)),Ur(g,N.current)||u(Gi(a,N.current)),w.setState({userSelectionRect:h,userSelectionActive:!0,nodesSelectionActive:!1})}function F(){if(!o||!T.current)return;let[e,t]=Mr(z.current,T.current,M);C({x:e,y:t}).then(e=>{if(!I.current||!e)return void(b.current=requestAnimationFrame(F));let{x:t,y:n}=z.current;D(t,n),b.current=requestAnimationFrame(F)})}let j=()=>{cancelAnimationFrame(b.current),b.current=0,R.current=!1};return(0,f.useEffect)(()=>()=>j(),[]),(0,p.jsxs)("div",{className:h(["react-flow__pane",{draggable:!0===r||Array.isArray(r)&&r.includes(0),dragging:S,selection:e}]),onClick:P?void 0:ya(L,O),onContextMenu:ya(e=>{Array.isArray(r)&&r?.includes(2)?e.preventDefault():c?.(e)},O),onWheel:ya(A,O),onPointerEnter:P?void 0:m,onPointerMove:P?e=>{let{userSelectionRect:n,transform:r,resetSelectedElements:o}=w.getState();if(!T.current||!n)return;let{x:a,y:s}=eo(e.nativeEvent,T.current);z.current={x:a,y:s};let u=Fr({x:n.startX,y:n.startY},r);if(!I.current){let n=t?0:i;if(Math.hypot(a-u.x,s-u.y)<=n)return;o(),l?.(e)}I.current=!0,R.current||=(F(),!0),D(a,s)}:g,onPointerUp:P?e=>{0===e.button&&(e.target?.releasePointerCapture?.(e.pointerId),!x&&e.target===O.current&&w.getState().userSelectionRect&&L?.(e),w.setState({userSelectionActive:!1,userSelectionRect:null}),I.current&&(s?.(e),w.setState({nodesSelectionActive:_.current.size>0})),j())}:void 0,onPointerCancel:P?e=>{e.target?.releasePointerCapture?.(e.pointerId),j()}:void 0,onPointerDownCapture:P?n=>{let{domNode:r,transform:o}=w.getState();if(T.current=r?.getBoundingClientRect(),!T.current)return;let i=n.target===O.current;if(!i&&n.target.closest(".nokey")||!e||!(a&&i||t)||0!==n.button||!n.isPrimary)return;n.target?.setPointerCapture?.(n.pointerId),I.current=!1;let{x:l,y:s}=eo(n.nativeEvent,T.current),u=Dr({x:l,y:s},o);w.setState({userSelectionRect:{width:0,height:0,startX:u.x,startY:u.y,x:l,y:s}}),i||(n.stopPropagation(),n.preventDefault())}:void 0,onClickCapture:P?e=>{I.current&&=(e.stopPropagation(),!1)}:void 0,onPointerLeave:y,ref:O,style:fa,children:[v,(0,p.jsx)(ga,{})]})}function wa({id:e,store:t,unselect:n=!1,nodeRef:r}){let{addSelectedNodes:o,unselectNodesAndEdges:i,multiSelectionActive:a,nodeLookup:l,onError:s}=t.getState(),u=l.get(e);u?(t.setState({nodesSelectionActive:!1}),u.selected?(n||u.selected&&a)&&(i({nodes:[u],edges:[]}),requestAnimationFrame(()=>r?.current?.blur())):o([e])):s?.("012",(e=>`Node with id "${e}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`)(e))}function xa({nodeRef:e,disabled:t=!1,noDragClassName:n,handleSelector:r,nodeId:o,isSelectable:i,nodeClickDistance:a}){let l=vi(),[s,u]=(0,f.useState)(!1),c=(0,f.useRef)();return(0,f.useEffect)(()=>{c.current=function({onNodeMouseDown:e,getStoreItems:t,onDragStart:n,onDrag:r,onDragStop:o}){let i={x:null,y:null},a=0,l=new Map,s=!1,u={x:0,y:0},c=null,d=!1,f=null,p=!1,h=!1,m=null;return{update:function({noDragClassName:g,handleSelector:y,domNode:v,isSelectable:b,nodeId:w,nodeClickDistance:x=0}){function k({x:e,y:n}){let{nodeLookup:o,nodeExtent:a,snapGrid:s,snapToGrid:u,nodeOrigin:c,onNodeDrag:d,onSelectionDrag:f,onError:p,updateNodePositions:g}=t();i={x:e,y:n};let y=!1,v=l.size>1,b=v&&a?Or(br(l)):null,x=v&&u?function({dragItems:e,snapGrid:t,x:n,y:r}){let o=e.values().next().value;if(!o)return null;let i={x:n-o.distance.x,y:r-o.distance.y},a=Ar(i,t);return{x:a.x-i.x,y:a.y-i.y}}({dragItems:l,snapGrid:s,x:e,y:n}):null;for(let[t,r]of l){if(!o.has(t))continue;let i={x:e-r.distance.x,y:n-r.distance.y};u&&(i=x?{x:Math.round(i.x+x.x),y:Math.round(i.y+x.y)}:Ar(i,s));let l=null;if(v&&a&&!r.extent&&b){let{positionAbsolute:e}=r.internals,t=e.x-b.x+a[0][0],n=e.x+r.measured.width-b.x2+a[1][0];l=[[t,e.y-b.y+a[0][1]],[n,e.y+r.measured.height-b.y2+a[1][1]]]}let{position:d,positionAbsolute:f}=xr({nodeId:t,nextPosition:i,nodeLookup:o,nodeExtent:l||a,nodeOrigin:c,onError:p});y=y||r.position.x!==d.x||r.position.y!==d.y,r.position=d,r.internals.positionAbsolute=f}if(h||=y,y&&(g(l,!0),m&&(r||d||!w&&f))){let[e,t]=Ro({nodeId:w,dragItems:l,nodeLookup:o});r?.(m,l,e,t),d?.(m,e,t),w||f?.(m,t)}}async function S(){if(!c)return;let{transform:e,panBy:n,autoPanSpeed:r,autoPanOnNodeDrag:o}=t();if(!o)return s=!1,void cancelAnimationFrame(a);let[l,d]=Mr(u,c,r);(0!==l||0!==d)&&(i.x=(i.x??0)-l/e[2],i.y=(i.y??0)-d/e[2],await n({x:l,y:d})&&k(i)),a=requestAnimationFrame(S)}function E(r){let{nodeLookup:o,multiSelectionActive:a,nodesDraggable:s,transform:u,snapGrid:f,snapToGrid:p,selectNodesOnDrag:h,onNodeDragStart:m,onSelectionDragStart:g,unselectNodesAndEdges:y}=t();d=!0,(!h||!b)&&!a&&w&&(o.get(w)?.selected||y()),b&&h&&w&&e?.(w);let v=Yr(r.sourceEvent,{transform:u,snapGrid:f,snapToGrid:p,containerBounds:c});if(i=v,l=function(e,t,n,r){let o=new Map;for(let[i,a]of e)if((a.selected||a.id===r)&&(!a.parentId||!Io(a,e))&&(a.draggable||t&&void 0===a.draggable)){let t=e.get(i);t&&o.set(i,{id:i,position:t.position||{x:0,y:0},distance:{x:n.x-t.internals.positionAbsolute.x,y:n.y-t.internals.positionAbsolute.y},extent:t.extent,parentId:t.parentId,origin:t.origin,expandParent:t.expandParent,internals:{positionAbsolute:t.internals.positionAbsolute||{x:0,y:0}},measured:{width:t.measured.width??0,height:t.measured.height??0}})}return o}(o,s,v,w),l.size>0&&(n||m||!w&&g)){let[e,t]=Ro({nodeId:w,dragItems:l,nodeLookup:o});n?.(r.sourceEvent,l,e,t),m?.(r.sourceEvent,e,t),w||g?.(r.sourceEvent,t)}}f=_e(v);let C=We().clickDistance(x).on("start",e=>{let{domNode:n,nodeDragThreshold:r,transform:o,snapGrid:a,snapToGrid:l}=t();c=n?.getBoundingClientRect()||null,p=!1,h=!1,m=e.sourceEvent,0===r&&E(e),i=Yr(e.sourceEvent,{transform:o,snapGrid:a,snapToGrid:l,containerBounds:c}),u=eo(e.sourceEvent,c)}).on("drag",e=>{let{autoPanOnNodeDrag:n,transform:r,snapGrid:o,snapToGrid:a,nodeDragThreshold:f,nodeLookup:h}=t(),g=Yr(e.sourceEvent,{transform:r,snapGrid:o,snapToGrid:a,containerBounds:c});if(m=e.sourceEvent,("touchmove"===e.sourceEvent.type&&e.sourceEvent.touches.length>1||w&&!h.has(w))&&(p=!0),!p){if(!s&&n&&d&&(s=!0,S()),!d){let t=eo(e.sourceEvent,c),n=t.x-u.x,r=t.y-u.y;Math.sqrt(n*n+r*r)>f&&E(e)}(i.x!==g.xSnapped||i.y!==g.ySnapped)&&l&&d&&(u=eo(e.sourceEvent,c),k(g))}}).on("end",e=>{if(d&&!p){if(s=!1,d=!1,cancelAnimationFrame(a),l.size>0){let{nodeLookup:n,updateNodePositions:r,onNodeDragStop:i,onSelectionDragStop:a}=t();if(h&&=(r(l,!1),!1),o||i||!w&&a){let[t,r]=Ro({nodeId:w,dragItems:l,nodeLookup:n,dragging:!1});o?.(e.sourceEvent,l,t,r),i?.(e.sourceEvent,t,r),w||a?.(e.sourceEvent,r)}}}else p&&l.size>0&&t().updateNodePositions(l,!1)}).filter(e=>{let t=e.target;return!e.button&&(!g||!zo(t,`.${g}`,v))&&(!y||zo(t,y,v))});f.call(C)},destroy:function(){f?.on(".drag",null)}}}({getStoreItems:()=>l.getState(),onNodeMouseDown:t=>{wa({id:t,store:l,nodeRef:e})},onDragStart:()=>{u(!0)},onDragStop:()=>{u(!1)}})},[]),(0,f.useEffect)(()=>{if(!t&&e.current&&c.current)return c.current.update({noDragClassName:n,handleSelector:r,domNode:e.current,isSelectable:i,nodeId:o,nodeClickDistance:a}),()=>{c.current?.destroy()}},[n,r,t,i,e,o,a]),s}function ka(){let e=vi();return(0,f.useCallback)(t=>{let{nodeExtent:n,snapToGrid:r,snapGrid:o,nodesDraggable:i,onError:a,updateNodePositions:l,nodeLookup:s,nodeOrigin:u}=e.getState(),c=new Map,d=(e=>t=>t.selected&&(t.draggable||e&&void 0===t.draggable))(i),f=r?o[0]:5,p=r?o[1]:5,h=t.direction.x*f*t.factor,m=t.direction.y*p*t.factor;for(let[,e]of s){if(!d(e))continue;let t={x:e.internals.positionAbsolute.x+h,y:e.internals.positionAbsolute.y+m};r&&(t=Ar(t,o));let{position:i,positionAbsolute:l}=xr({nodeId:e.id,nextPosition:t,nodeLookup:s,nodeExtent:n,nodeOrigin:u,onError:a});e.position=i,e.internals.positionAbsolute=l,c.set(e.id,e)}l(c)},[])}var Sa=(0,f.createContext)(null),Ea=Sa.Provider;Sa.Consumer;var Ca=()=>(0,f.useContext)(Sa),Ma=e=>({connectOnClick:e.connectOnClick,noPanClassName:e.noPanClassName,rfId:e.rfId}),Pa=(0,f.memo)(na(function({type:e="source",position:t=cr.Top,isValidConnection:n,isConnectable:r=!0,isConnectableStart:o=!0,isConnectableEnd:i=!0,id:a,onConnect:l,children:s,className:u,onMouseDown:c,onTouchStart:d,...f},m){let g=a||null,y="target"===e,v=vi(),b=Ca(),{connectOnClick:w,noPanClassName:x,rfId:k}=yi(Ma,fi),{connectingFrom:S,connectingTo:E,clickConnecting:C,isPossibleEndHandle:M,connectionInProcess:P,clickConnectionInProcess:O,valid:T}=yi(((e,t,n)=>r=>{let{connectionClickStartHandle:o,connectionMode:i,connection:a}=r,{fromHandle:l,toHandle:s,isValid:u}=a,c=s?.nodeId===e&&s?.id===t&&s?.type===n;return{connectingFrom:l?.nodeId===e&&l?.id===t&&l?.type===n,connectingTo:c,clickConnecting:o?.nodeId===e&&o?.id===t&&o?.type===n,isPossibleEndHandle:i===nr.Strict?l?.type!==n:e!==l?.nodeId||t!==l?.id,connectionInProcess:!!l,clickConnectionInProcess:!!o,valid:c&&u}})(b,g,e),fi);b||v.getState().onError?.("010","Handle: No node id found. Make sure to only use a Handle inside a custom Node.");let _=e=>{let{defaultEdgeOptions:t,onConnect:n,hasDefaultEdges:r}=v.getState(),o={...t,...e};if(r){let{edges:e,setEdges:t,onError:n}=v.getState();t(function(e,t,n={}){return((e,t,n={})=>{if(!e.source||!e.target)return n.onError?.("006","Can't create edge. An edge needs a source and a target."),t;let r,o=n.getEdgeId||uo;return r=hr(e)?{...e}:{...e,id:o(e)},((e,t)=>t.some(t=>!(t.source!==e.source||t.target!==e.target||t.sourceHandle!==e.sourceHandle&&(t.sourceHandle||e.sourceHandle)||t.targetHandle!==e.targetHandle&&(t.targetHandle||e.targetHandle))))(r,t)?t:(null===r.sourceHandle&&delete r.sourceHandle,null===r.targetHandle&&delete r.targetHandle,t.concat(r))})(e,t,{...n,onError:n.onError??ea})}(o,e,{onError:n}))}n?.(o),l?.(o)},N=e=>{if(!b)return;let t=Jr(e.nativeEvent);if(o&&(t&&0===e.button||!t)){let t=v.getState();Ho.onPointerDown(e.nativeEvent,{handleDomNode:e.currentTarget,autoPanOnConnect:t.autoPanOnConnect,connectionMode:t.connectionMode,connectionRadius:t.connectionRadius,domNode:t.domNode,nodeLookup:t.nodeLookup,lib:t.lib,isTarget:y,handleId:g,nodeId:b,flowId:t.rfId,panBy:t.panBy,cancelConnection:t.cancelConnection,onConnectStart:t.onConnectStart,onConnectEnd:(...e)=>v.getState().onConnectEnd?.(...e),updateConnection:t.updateConnection,onConnect:_,isValidConnection:n||((...e)=>v.getState().isValidConnection?.(...e)??!0),getTransform:()=>v.getState().transform,getFromHandle:()=>v.getState().connection.fromHandle,autoPanSpeed:t.autoPanSpeed,dragThreshold:t.connectionDragThreshold})}t?c?.(e):d?.(e)};return(0,p.jsx)("div",{"data-handleid":g,"data-nodeid":b,"data-handlepos":t,"data-id":`${k}-${b}-${g}-${e}`,className:h(["react-flow__handle",`react-flow__handle-${t}`,"nodrag",x,u,{source:!y,target:y,connectable:r,connectablestart:o,connectableend:i,clickconnecting:C,connectingfrom:S,connectingto:E,valid:T,connectionindicator:r&&(!P||M)&&(P||O?i:o)}]),onMouseDown:N,onTouchStart:N,onClick:w?t=>{let{onClickConnectStart:r,onClickConnectEnd:i,connectionClickStartHandle:a,connectionMode:l,isValidConnection:s,lib:u,rfId:c,nodeLookup:d,connection:f}=v.getState();if(!b||!a&&!o)return;if(!a)return r?.(t.nativeEvent,{nodeId:b,handleId:g,handleType:e}),void v.setState({connectionClickStartHandle:{nodeId:b,type:e,id:g}});let p=Zr(t.target),h=n||s,{connection:m,isValid:y}=Ho.isValid(t.nativeEvent,{handle:{nodeId:b,id:g,type:e},connectionMode:l,fromNodeId:a.nodeId,fromHandleId:a.id||null,fromType:a.type,isValidConnection:h,flowId:c,doc:p,lib:u,nodeLookup:d});y&&m&&_(m);let w=structuredClone(f);delete w.inProgress,w.toPosition=w.toHandle?w.toHandle.position:null,i?.(t,w),v.setState({connectionClickStartHandle:null})}:void 0,ref:m,...f,children:s})})),Oa={ArrowUp:{x:0,y:-1},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0},ArrowRight:{x:1,y:0}},Ta={input:function({data:e,isConnectable:t,sourcePosition:n=cr.Bottom}){return(0,p.jsxs)(p.Fragment,{children:[e?.label,(0,p.jsx)(Pa,{type:"source",position:n,isConnectable:t})]})},default:function({data:e,isConnectable:t,targetPosition:n=cr.Top,sourcePosition:r=cr.Bottom}){return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(Pa,{type:"target",position:n,isConnectable:t}),e?.label,(0,p.jsx)(Pa,{type:"source",position:r,isConnectable:t})]})},output:function({data:e,isConnectable:t,targetPosition:n=cr.Top}){return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(Pa,{type:"target",position:n,isConnectable:t}),e?.label]})},group:function(){return null}},_a=e=>{let{width:t,height:n,x:r,y:o}=br(e.nodeLookup,{filter:e=>!!e.selected});return{width:Lr(t)?t:null,height:Lr(n)?n:null,userSelectionActive:e.userSelectionActive,transformString:`translate(${e.transform[0]}px,${e.transform[1]}px) scale(${e.transform[2]}) translate(${r}px,${o}px)`}};function Na({onSelectionContextMenu:e,noPanClassName:t,disableKeyboardA11y:n}){let r=vi(),{width:o,height:i,transformString:a,userSelectionActive:l}=yi(_a,fi),s=ka(),u=(0,f.useRef)(null);(0,f.useEffect)(()=>{n||u.current?.focus({preventScroll:!0})},[n]);let c=!l&&null!==o&&null!==i;if(xa({nodeRef:u,disabled:!c}),!c)return null;let d=e?t=>{e(t,r.getState().nodes.filter(e=>e.selected))}:void 0;return(0,p.jsx)("div",{className:h(["react-flow__nodesselection","react-flow__container",t]),style:{transform:a},children:(0,p.jsx)("div",{ref:u,className:"react-flow__nodesselection-rect",onContextMenu:d,tabIndex:n?void 0:-1,onKeyDown:n?void 0:e=>{Object.prototype.hasOwnProperty.call(Oa,e.key)&&(e.preventDefault(),s({direction:Oa[e.key],factor:e.shiftKey?4:1}))},style:{width:o,height:i}})})}var Ia=typeof window<"u"?window:void 0,za=e=>({nodesSelectionActive:e.nodesSelectionActive,userSelectionActive:e.userSelectionActive});function Ra({children:e,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:o,onPaneContextMenu:i,onPaneScroll:a,paneClickDistance:l,deleteKeyCode:s,selectionKeyCode:u,selectionOnDrag:c,selectionMode:d,onSelectionStart:f,onSelectionEnd:h,multiSelectionKeyCode:m,panActivationKeyCode:g,zoomActivationKeyCode:y,elementsSelectable:v,zoomOnScroll:b,zoomOnPinch:w,panOnScroll:x,panOnScrollSpeed:k,panOnScrollMode:S,zoomOnDoubleClick:E,panOnDrag:C,autoPanOnSelection:M,defaultViewport:P,translateExtent:O,minZoom:T,maxZoom:_,preventScrolling:N,onSelectionContextMenu:I,noWheelClassName:z,noPanClassName:R,disableKeyboardA11y:L,onViewportChange:A,isControlledViewport:D}){let{nodesSelectionActive:F,userSelectionActive:j}=yi(za,fi),H=Bi(u,{target:Ia}),V=Bi(g,{target:Ia}),$=V||C,B=V||x,W=c&&!0!==$,q=H||j||W;return da({deleteKeyCode:s,multiSelectionKeyCode:m}),(0,p.jsx)(ha,{onPaneContextMenu:i,elementsSelectable:v,zoomOnScroll:b,zoomOnPinch:w,panOnScroll:B,panOnScrollSpeed:k,panOnScrollMode:S,zoomOnDoubleClick:E,panOnDrag:!H&&$,defaultViewport:P,translateExtent:O,minZoom:T,maxZoom:_,zoomActivationKeyCode:y,preventScrolling:N,noWheelClassName:z,noPanClassName:R,onViewportChange:A,isControlledViewport:D,paneClickDistance:l,selectionOnDrag:W,children:(0,p.jsxs)(ba,{onSelectionStart:f,onSelectionEnd:h,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:o,onPaneContextMenu:i,onPaneScroll:a,panOnDrag:$,autoPanOnSelection:M,isSelecting:!!q,selectionMode:d,selectionKeyPressed:H,paneClickDistance:l,selectionOnDrag:W,children:[e,F&&(0,p.jsx)(Na,{onSelectionContextMenu:I,noPanClassName:R,disableKeyboardA11y:L})]})})}Ra.displayName="FlowRenderer";var La=(0,f.memo)(Ra),Aa=e=>e.updateNodeInternals;function Da({node:e,nodeType:t,hasDimensions:n,resizeObserver:r}){let o=vi(),i=(0,f.useRef)(null),a=(0,f.useRef)(null),l=(0,f.useRef)(e.sourcePosition),s=(0,f.useRef)(e.targetPosition),u=(0,f.useRef)(t),c=n&&!!e.internals.handleBounds;return(0,f.useEffect)(()=>{i.current&&!e.hidden&&(!c||a.current!==i.current)&&(a.current&&r?.unobserve(a.current),r?.observe(i.current),a.current=i.current)},[c,e.hidden]),(0,f.useEffect)(()=>()=>{a.current&&=(r?.unobserve(a.current),null)},[]),(0,f.useEffect)(()=>{if(i.current){let n=u.current!==t,r=l.current!==e.sourcePosition,a=s.current!==e.targetPosition;(n||r||a)&&(u.current=t,l.current=e.sourcePosition,s.current=e.targetPosition,o.getState().updateNodeInternals(new Map([[e.id,{id:e.id,nodeElement:i.current,force:!0}]])))}},[e.id,t,e.sourcePosition,e.targetPosition]),i}var Fa=(0,f.memo)(function({id:e,onClick:t,onMouseEnter:n,onMouseMove:r,onMouseLeave:o,onContextMenu:i,onDoubleClick:a,nodesDraggable:l,elementsSelectable:s,nodesConnectable:u,nodesFocusable:c,resizeObserver:d,noDragClassName:f,noPanClassName:m,disableKeyboardA11y:g,rfId:y,nodeTypes:v,nodeClickDistance:b,onError:w}){let{node:x,internals:k,isParent:S}=yi(t=>{let n=t.nodeLookup.get(e),r=t.parentLookup.has(e);return{node:n,internals:n.internals,isParent:r}},fi),E=x.type||"default",C=v?.[E]||Ta[E];void 0===C&&(w?.("003",(e=>`Node type "${e}" not found. Using fallback type "default".`)(E)),E="default",C=v?.default||Ta.default);let M=!!(x.draggable||l&&void 0===x.draggable),P=!!(x.selectable||s&&void 0===x.selectable),O=!!(x.connectable||u&&void 0===x.connectable),T=!!(x.focusable||c&&void 0===x.focusable),_=vi(),N=Wr(x),I=Da({node:x,nodeType:E,hasDimensions:N,resizeObserver:d}),z=xa({nodeRef:I,disabled:x.hidden||!M,noDragClassName:f,handleSelector:x.dragHandle,nodeId:e,isSelectable:P,nodeClickDistance:b}),R=ka();if(x.hidden)return null;let L=Br(x),A=function(e){return void 0===e.internals.handleBounds?{width:e.width??e.initialWidth??e.style?.width,height:e.height??e.initialHeight??e.style?.height}:{width:e.width??e.style?.width,height:e.height??e.style?.height}}(x),D=P||M||t||n||r||o,F=n?e=>n(e,{...k.userNode}):void 0,j=r?e=>r(e,{...k.userNode}):void 0,H=o?e=>o(e,{...k.userNode}):void 0,V=i?e=>i(e,{...k.userNode}):void 0,$=a?e=>a(e,{...k.userNode}):void 0;return(0,p.jsx)("div",{className:h(["react-flow__node",`react-flow__node-${E}`,{[m]:M},x.className,{selected:x.selected,selectable:P,parent:S,draggable:M,dragging:z}]),ref:I,style:{zIndex:k.z,transform:`translate(${k.positionAbsolute.x}px,${k.positionAbsolute.y}px)`,pointerEvents:D?"all":"none",visibility:N?"visible":"hidden",...x.style,...A},"data-id":e,"data-testid":`rf__node-${e}`,onMouseEnter:F,onMouseMove:j,onMouseLeave:H,onContextMenu:V,onClick:n=>{let{selectNodesOnDrag:r,nodeDragThreshold:o}=_.getState();P&&(!r||!M||o>0)&&wa({id:e,store:_,nodeRef:I}),t&&t(n,{...k.userNode})},onDoubleClick:$,onKeyDown:T?t=>{if(!Qr(t.nativeEvent)&&!g)if(ar.includes(t.key)&&P){let n="Escape"===t.key;wa({id:e,store:_,unselect:n,nodeRef:I})}else if(M&&x.selected&&Object.prototype.hasOwnProperty.call(Oa,t.key)){t.preventDefault();let{ariaLabelConfig:e}=_.getState();_.setState({ariaLiveMessage:e["node.a11yDescription.ariaLiveMessage"]({direction:t.key.replace("Arrow","").toLowerCase(),x:~~k.positionAbsolute.x,y:~~k.positionAbsolute.y})}),R({direction:Oa[t.key],factor:t.shiftKey?4:1})}}:void 0,tabIndex:T?0:void 0,onFocus:T?()=>{if(g||!I.current?.matches(":focus-visible"))return;let{transform:t,width:n,height:r,autoPanOnNodeFocus:o,setCenter:i}=_.getState();o&&(wr(new Map([[e,x]]),{x:0,y:0,width:n,height:r},t,!0).length>0||i(x.position.x+L.width/2,x.position.y+L.height/2,{zoom:t[2]}))}:void 0,role:x.ariaRole??(T?"group":void 0),"aria-roledescription":"node","aria-describedby":g?void 0:`${xi}-${y}`,"aria-label":x.ariaLabel,...x.domAttributes,children:(0,p.jsx)(Ea,{value:e,children:(0,p.jsx)(C,{id:e,data:x.data,type:E,positionAbsoluteX:k.positionAbsolute.x,positionAbsoluteY:k.positionAbsolute.y,selected:x.selected??!1,selectable:P,draggable:M,deletable:x.deletable??!0,isConnectable:O,sourcePosition:x.sourcePosition,targetPosition:x.targetPosition,dragging:z,dragHandle:x.dragHandle,zIndex:k.z,parentId:x.parentId,...L})})})}),ja=e=>({nodesDraggable:e.nodesDraggable,nodesConnectable:e.nodesConnectable,nodesFocusable:e.nodesFocusable,elementsSelectable:e.elementsSelectable,onError:e.onError});function Ha(e){let{nodesDraggable:t,nodesConnectable:n,nodesFocusable:r,elementsSelectable:o,onError:i}=yi(ja,fi),a=function(e){return yi((0,f.useCallback)((e=>t=>e?wr(t.nodeLookup,{x:0,y:0,width:t.width,height:t.height},t.transform,!0).map(e=>e.id):Array.from(t.nodeLookup.keys()))(e),[e]),fi)}(e.onlyRenderVisibleElements),l=function(){let e=yi(Aa),[t]=(0,f.useState)(()=>typeof ResizeObserver>"u"?null:new ResizeObserver(t=>{let n=new Map;t.forEach(e=>{let t=e.target.getAttribute("data-id");n.set(t,{id:t,nodeElement:e.target,force:!0})}),e(n)}));return(0,f.useEffect)(()=>()=>{t?.disconnect()},[t]),t}();return(0,p.jsx)("div",{className:"react-flow__nodes",style:fa,children:a.map(a=>(0,p.jsx)(Fa,{id:a,nodeTypes:e.nodeTypes,nodeExtent:e.nodeExtent,onClick:e.onNodeClick,onMouseEnter:e.onNodeMouseEnter,onMouseMove:e.onNodeMouseMove,onMouseLeave:e.onNodeMouseLeave,onContextMenu:e.onNodeContextMenu,onDoubleClick:e.onNodeDoubleClick,noDragClassName:e.noDragClassName,noPanClassName:e.noPanClassName,rfId:e.rfId,disableKeyboardA11y:e.disableKeyboardA11y,resizeObserver:l,nodesDraggable:t,nodesConnectable:n,nodesFocusable:r,elementsSelectable:o,nodeClickDistance:e.nodeClickDistance,onError:i},a))})}Ha.displayName="NodeRenderer";var Va=(0,f.memo)(Ha);function $a(e){return yi((0,f.useCallback)(t=>{if(!e)return t.edges.map(e=>e.id);let n=[];if(t.width&&t.height)for(let e of t.edges){let r=t.nodeLookup.get(e.source),o=t.nodeLookup.get(e.target);r&&o&&so({sourceNode:r,targetNode:o,width:t.width,height:t.height,transform:t.transform})&&n.push(e.id)}return n},[e]),fi)}var Ba={[ur.Arrow]:({color:e="none",strokeWidth:t=1})=>(0,p.jsx)("polyline",{className:"arrow",style:{strokeWidth:t,...e&&{stroke:e}},strokeLinecap:"round",fill:"none",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4"}),[ur.ArrowClosed]:({color:e="none",strokeWidth:t=1})=>(0,p.jsx)("polyline",{className:"arrowclosed",style:{strokeWidth:t,...e&&{stroke:e,fill:e}},strokeLinecap:"round",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4 -5,-4"})},Wa=({id:e,type:t,color:n,width:r=12.5,height:o=12.5,markerUnits:i="strokeWidth",strokeWidth:a,orient:l="auto-start-reverse"})=>{let s=function(e){let t=vi();return(0,f.useMemo)(()=>Object.prototype.hasOwnProperty.call(Ba,e)?Ba[e]:(t.getState().onError?.("009",(e=>`Marker type "${e}" doesn't exist.`)(e)),null),[e])}(t);return s?(0,p.jsx)("marker",{className:"react-flow__arrowhead",id:e,markerWidth:`${r}`,markerHeight:`${o}`,viewBox:"-10 -10 20 20",markerUnits:i,orient:l,refX:"0",refY:"0",children:(0,p.jsx)(s,{color:n,strokeWidth:a})}):null},qa=({defaultColor:e,rfId:t})=>{let n=yi(e=>e.edges),r=yi(e=>e.defaultEdgeOptions),o=(0,f.useMemo)(()=>function(e,{id:t,defaultColor:n,defaultMarkerStart:r,defaultMarkerEnd:o}){let i=new Set;return e.reduce((e,a)=>([a.markerStart||r,a.markerEnd||o].forEach(r=>{if(r&&"object"==typeof r){let o=wo(r,t);i.has(o)||(e.push({id:o,color:r.color||n,...r}),i.add(o))}}),e),[]).sort((e,t)=>e.id.localeCompare(t.id))}(n,{id:t,defaultColor:e,defaultMarkerStart:r?.markerStart,defaultMarkerEnd:r?.markerEnd}),[n,r,t,e]);return o.length?(0,p.jsx)("svg",{className:"react-flow__marker","aria-hidden":"true",children:(0,p.jsx)("defs",{children:o.map(e=>(0,p.jsx)(Wa,{id:e.id,type:e.type,color:e.color,width:e.width,height:e.height,markerUnits:e.markerUnits,strokeWidth:e.strokeWidth,orient:e.orient},e.id))})}):null};qa.displayName="MarkerDefinitions";var Ua=(0,f.memo)(qa);function Ka({x:e,y:t,label:n,labelStyle:r,labelShowBg:o=!0,labelBgStyle:i,labelBgPadding:a=[2,4],labelBgBorderRadius:l=2,children:s,className:u,...c}){let[d,m]=(0,f.useState)({x:1,y:0,width:0,height:0}),g=h(["react-flow__edge-textwrapper",u]),y=(0,f.useRef)(null);return(0,f.useEffect)(()=>{if(y.current){let e=y.current.getBBox();m({x:e.x,y:e.y,width:e.width,height:e.height})}},[n]),n?(0,p.jsxs)("g",{transform:`translate(${e-d.width/2} ${t-d.height/2})`,className:g,visibility:d.width?"visible":"hidden",...c,children:[o&&(0,p.jsx)("rect",{width:d.width+2*a[0],x:-a[0],y:-a[1],height:d.height+2*a[1],className:"react-flow__edge-textbg",style:i,rx:l,ry:l}),(0,p.jsx)("text",{className:"react-flow__edge-text",y:d.height/2,dy:"0.3em",ref:y,style:r,children:n}),s]}):null}Ka.displayName="EdgeText";var Ya=(0,f.memo)(Ka);function Xa({path:e,labelX:t,labelY:n,label:r,labelStyle:o,labelShowBg:i,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:s,interactionWidth:u=20,...c}){return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)("path",{...c,d:e,fill:"none",className:h(["react-flow__edge-path",c.className])}),u?(0,p.jsx)("path",{d:e,fill:"none",strokeOpacity:0,strokeWidth:u,className:"react-flow__edge-interaction"}):null,r&&Lr(t)&&Lr(n)?(0,p.jsx)(Ya,{x:t,y:n,label:r,labelStyle:o,labelShowBg:i,labelBgStyle:a,labelBgPadding:l,labelBgBorderRadius:s}):null]})}function Za({pos:e,x1:t,y1:n,x2:r,y2:o}){return e===cr.Left||e===cr.Right?[.5*(t+r),n]:[t,.5*(n+o)]}function Ga({sourceX:e,sourceY:t,sourcePosition:n=cr.Bottom,targetX:r,targetY:o,targetPosition:i=cr.Top}){let[a,l]=Za({pos:n,x1:e,y1:t,x2:r,y2:o}),[s,u]=Za({pos:i,x1:r,y1:o,x2:e,y2:t}),[c,d,f,p]=no({sourceX:e,sourceY:t,targetX:r,targetY:o,sourceControlX:a,sourceControlY:l,targetControlX:s,targetControlY:u});return[`M${e},${t} C${a},${l} ${s},${u} ${r},${o}`,c,d,f,p]}function Qa(e){return(0,f.memo)(({id:t,sourceX:n,sourceY:r,targetX:o,targetY:i,sourcePosition:a,targetPosition:l,label:s,labelStyle:u,labelShowBg:c,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:h,style:m,markerEnd:g,markerStart:y,interactionWidth:v})=>{let[b,w,x]=Ga({sourceX:n,sourceY:r,sourcePosition:a,targetX:o,targetY:i,targetPosition:l});return(0,p.jsx)(Xa,{id:e.isInternal?void 0:t,path:b,labelX:w,labelY:x,label:s,labelStyle:u,labelShowBg:c,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:h,style:m,markerEnd:g,markerStart:y,interactionWidth:v})})}var Ja=Qa({isInternal:!1}),el=Qa({isInternal:!0});function tl(e){return(0,f.memo)(({id:t,sourceX:n,sourceY:r,targetX:o,targetY:i,label:a,labelStyle:l,labelShowBg:s,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:d,style:f,sourcePosition:h=cr.Bottom,targetPosition:m=cr.Top,markerEnd:g,markerStart:y,pathOptions:v,interactionWidth:b})=>{let[w,x,k]=mo({sourceX:n,sourceY:r,sourcePosition:h,targetX:o,targetY:i,targetPosition:m,borderRadius:v?.borderRadius,offset:v?.offset,stepPosition:v?.stepPosition});return(0,p.jsx)(Xa,{id:e.isInternal?void 0:t,path:w,labelX:x,labelY:k,label:a,labelStyle:l,labelShowBg:s,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:d,style:f,markerEnd:g,markerStart:y,interactionWidth:b})})}Ja.displayName="SimpleBezierEdge",el.displayName="SimpleBezierEdgeInternal";var nl=tl({isInternal:!1}),rl=tl({isInternal:!0});function ol(e){return(0,f.memo)(({id:t,...n})=>{let r=e.isInternal?void 0:t;return(0,p.jsx)(nl,{...n,id:r,pathOptions:(0,f.useMemo)(()=>({borderRadius:0,offset:n.pathOptions?.offset}),[n.pathOptions?.offset])})})}nl.displayName="SmoothStepEdge",rl.displayName="SmoothStepEdgeInternal";var il=ol({isInternal:!1}),al=ol({isInternal:!0});function ll(e){return(0,f.memo)(({id:t,sourceX:n,sourceY:r,targetX:o,targetY:i,label:a,labelStyle:l,labelShowBg:s,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:d,style:f,markerEnd:h,markerStart:m,interactionWidth:g})=>{let[y,v,b]=co({sourceX:n,sourceY:r,targetX:o,targetY:i});return(0,p.jsx)(Xa,{id:e.isInternal?void 0:t,path:y,labelX:v,labelY:b,label:a,labelStyle:l,labelShowBg:s,labelBgStyle:u,labelBgPadding:c,labelBgBorderRadius:d,style:f,markerEnd:h,markerStart:m,interactionWidth:g})})}il.displayName="StepEdge",al.displayName="StepEdgeInternal";var sl=ll({isInternal:!1}),ul=ll({isInternal:!0});function cl(e){return(0,f.memo)(({id:t,sourceX:n,sourceY:r,targetX:o,targetY:i,sourcePosition:a=cr.Bottom,targetPosition:l=cr.Top,label:s,labelStyle:u,labelShowBg:c,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:h,style:m,markerEnd:g,markerStart:y,pathOptions:v,interactionWidth:b})=>{let[w,x,k]=io({sourceX:n,sourceY:r,sourcePosition:a,targetX:o,targetY:i,targetPosition:l,curvature:v?.curvature});return(0,p.jsx)(Xa,{id:e.isInternal?void 0:t,path:w,labelX:x,labelY:k,label:s,labelStyle:u,labelShowBg:c,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:h,style:m,markerEnd:g,markerStart:y,interactionWidth:b})})}sl.displayName="StraightEdge",ul.displayName="StraightEdgeInternal";var dl=cl({isInternal:!1}),fl=cl({isInternal:!0});dl.displayName="BezierEdge",fl.displayName="BezierEdgeInternal";var pl={default:fl,straight:ul,step:al,smoothstep:rl,simplebezier:el},hl={sourceX:null,sourceY:null,targetX:null,targetY:null,sourcePosition:null,targetPosition:null},ml=(e,t,n)=>n===cr.Left?e-t:n===cr.Right?e+t:e,gl=(e,t,n)=>n===cr.Top?e-t:n===cr.Bottom?e+t:e,yl="react-flow__edgeupdater";function vl({position:e,centerX:t,centerY:n,radius:r=10,onMouseDown:o,onMouseEnter:i,onMouseOut:a,type:l}){return(0,p.jsx)("circle",{onMouseDown:o,onMouseEnter:i,onMouseOut:a,className:h([yl,`${yl}-${l}`]),cx:ml(t,r,e),cy:gl(n,r,e),r,stroke:"transparent",fill:"transparent"})}function bl({isReconnectable:e,reconnectRadius:t,edge:n,sourceX:r,sourceY:o,targetX:i,targetY:a,sourcePosition:l,targetPosition:s,onReconnect:u,onReconnectStart:c,onReconnectEnd:d,setReconnecting:f,setUpdateHover:h}){let m=vi(),g=(e,t)=>{if(0!==e.button)return;let{autoPanOnConnect:r,domNode:o,connectionMode:i,connectionRadius:a,lib:l,onConnectStart:s,cancelConnection:p,nodeLookup:h,rfId:g,panBy:y,updateConnection:v}=m.getState(),b="target"===t.type;Ho.onPointerDown(e.nativeEvent,{autoPanOnConnect:r,connectionMode:i,connectionRadius:a,domNode:o,handleId:t.id,nodeId:t.nodeId,nodeLookup:h,isTarget:b,edgeUpdaterType:t.type,lib:l,flowId:g,cancelConnection:p,panBy:y,isValidConnection:(...e)=>m.getState().isValidConnection?.(...e)??!0,onConnect:e=>u?.(n,e),onConnectStart:(r,o)=>{f(!0),c?.(e,n,t.type),s?.(r,o)},onConnectEnd:(...e)=>m.getState().onConnectEnd?.(...e),onReconnectEnd:(e,r)=>{f(!1),d?.(e,n,t.type,r)},updateConnection:v,getTransform:()=>m.getState().transform,getFromHandle:()=>m.getState().connection.fromHandle,dragThreshold:m.getState().connectionDragThreshold,handleDomNode:e.currentTarget})},y=()=>h(!0),v=()=>h(!1);return(0,p.jsxs)(p.Fragment,{children:[(!0===e||"source"===e)&&(0,p.jsx)(vl,{position:l,centerX:r,centerY:o,radius:t,onMouseDown:e=>g(e,{nodeId:n.target,id:n.targetHandle??null,type:"target"}),onMouseEnter:y,onMouseOut:v,type:"source"}),(!0===e||"target"===e)&&(0,p.jsx)(vl,{position:s,centerX:i,centerY:a,radius:t,onMouseDown:e=>g(e,{nodeId:n.source,id:n.sourceHandle??null,type:"source"}),onMouseEnter:y,onMouseOut:v,type:"target"})]})}var wl=(0,f.memo)(function({id:e,edgesFocusable:t,edgesReconnectable:n,elementsSelectable:r,onClick:o,onDoubleClick:i,onContextMenu:a,onMouseEnter:l,onMouseMove:s,onMouseLeave:u,reconnectRadius:c,onReconnect:d,onReconnectStart:m,onReconnectEnd:g,rfId:y,edgeTypes:v,noPanClassName:b,onError:w,disableKeyboardA11y:x}){let k=yi(t=>t.edgeLookup.get(e)),S=yi(e=>e.defaultEdgeOptions);k=S?{...S,...k}:k;let E=k.type||"default",C=v?.[E]||pl[E];void 0===C&&(w?.("011",(e=>`Edge type "${e}" not found. Using fallback type "default".`)(E)),E="default",C=v?.default||pl.default);let M=!!(k.focusable||t&&void 0===k.focusable),P=void 0!==d&&(k.reconnectable||n&&void 0===k.reconnectable),O=!!(k.selectable||r&&void 0===k.selectable),T=(0,f.useRef)(null),[_,N]=(0,f.useState)(!1),[I,z]=(0,f.useState)(!1),R=vi(),{zIndex:L,sourceX:A,sourceY:D,targetX:F,targetY:j,sourcePosition:H,targetPosition:V}=yi((0,f.useCallback)(t=>{let n=t.nodeLookup.get(k.source),r=t.nodeLookup.get(k.target);if(!n||!r)return{zIndex:k.zIndex,...hl};let o=function(e){let{sourceNode:t,targetNode:n}=e;if(!go(t)||!go(n))return null;let r=t.internals.handleBounds||yo(t.handles),o=n.internals.handleBounds||yo(n.handles),i=bo(r?.source??[],e.sourceHandle),a=bo(e.connectionMode===nr.Strict?o?.target??[]:(o?.target??[]).concat(o?.source??[]),e.targetHandle);if(!i||!a)return e.onError?.("008",((e,{id:t,sourceHandle:n,targetHandle:r})=>`Couldn't create edge for ${e} handle id: "${"source"===e?n:r}", edge id: ${t}.`)(i?"target":"source",{id:e.id,sourceHandle:e.sourceHandle,targetHandle:e.targetHandle})),null;let l=i?.position||cr.Bottom,s=a?.position||cr.Top,u=vo(t,i,l),c=vo(n,a,s);return{sourceX:u.x,sourceY:u.y,targetX:c.x,targetY:c.y,sourcePosition:l,targetPosition:s}}({id:e,sourceNode:n,targetNode:r,sourceHandle:k.sourceHandle||null,targetHandle:k.targetHandle||null,connectionMode:t.connectionMode,onError:w});return{zIndex:lo({selected:k.selected,zIndex:k.zIndex,sourceNode:n,targetNode:r,elevateOnSelect:t.elevateEdgesOnSelect,zIndexMode:t.zIndexMode}),...o||hl}},[k.source,k.target,k.sourceHandle,k.targetHandle,k.selected,k.zIndex]),fi),$=(0,f.useMemo)(()=>k.markerStart?`url('#${wo(k.markerStart,y)}')`:void 0,[k.markerStart,y]),B=(0,f.useMemo)(()=>k.markerEnd?`url('#${wo(k.markerEnd,y)}')`:void 0,[k.markerEnd,y]);if(k.hidden||null===A||null===D||null===F||null===j)return null;let W=i?e=>{i(e,{...k})}:void 0,q=a?e=>{a(e,{...k})}:void 0,U=l?e=>{l(e,{...k})}:void 0,K=s?e=>{s(e,{...k})}:void 0,Y=u?e=>{u(e,{...k})}:void 0;return(0,p.jsx)("svg",{style:{zIndex:L},children:(0,p.jsxs)("g",{className:h(["react-flow__edge",`react-flow__edge-${E}`,k.className,b,{selected:k.selected,animated:k.animated,inactive:!O&&!o,updating:_,selectable:O}]),onClick:t=>{let{addSelectedEdges:n,unselectNodesAndEdges:r,multiSelectionActive:i}=R.getState();O&&(R.setState({nodesSelectionActive:!1}),k.selected&&i?(r({nodes:[],edges:[k]}),T.current?.blur()):n([e])),o&&o(t,k)},onDoubleClick:W,onContextMenu:q,onMouseEnter:U,onMouseMove:K,onMouseLeave:Y,onKeyDown:M?t=>{if(!x&&ar.includes(t.key)&&O){let{unselectNodesAndEdges:n,addSelectedEdges:r}=R.getState();"Escape"===t.key?(T.current?.blur(),n({edges:[k]})):r([e])}}:void 0,tabIndex:M?0:void 0,role:k.ariaRole??(M?"group":"img"),"aria-roledescription":"edge","data-id":e,"data-testid":`rf__edge-${e}`,"aria-label":null===k.ariaLabel?void 0:k.ariaLabel||`Edge from ${k.source} to ${k.target}`,"aria-describedby":M?`${ki}-${y}`:void 0,ref:T,...k.domAttributes,children:[!I&&(0,p.jsx)(C,{id:e,source:k.source,target:k.target,type:k.type,selected:k.selected,animated:k.animated,selectable:O,deletable:k.deletable??!0,label:k.label,labelStyle:k.labelStyle,labelShowBg:k.labelShowBg,labelBgStyle:k.labelBgStyle,labelBgPadding:k.labelBgPadding,labelBgBorderRadius:k.labelBgBorderRadius,sourceX:A,sourceY:D,targetX:F,targetY:j,sourcePosition:H,targetPosition:V,data:k.data,style:k.style,sourceHandleId:k.sourceHandle,targetHandleId:k.targetHandle,markerStart:$,markerEnd:B,pathOptions:"pathOptions"in k?k.pathOptions:void 0,interactionWidth:k.interactionWidth}),P&&(0,p.jsx)(bl,{edge:k,isReconnectable:P,reconnectRadius:c,onReconnect:d,onReconnectStart:m,onReconnectEnd:g,sourceX:A,sourceY:D,targetX:F,targetY:j,sourcePosition:H,targetPosition:V,setUpdateHover:N,setReconnecting:z})]})})}),xl=e=>({edgesFocusable:e.edgesFocusable,edgesReconnectable:e.edgesReconnectable,elementsSelectable:e.elementsSelectable,connectionMode:e.connectionMode,onError:e.onError});function kl({defaultMarkerColor:e,onlyRenderVisibleElements:t,rfId:n,edgeTypes:r,noPanClassName:o,onReconnect:i,onEdgeContextMenu:a,onEdgeMouseEnter:l,onEdgeMouseMove:s,onEdgeMouseLeave:u,onEdgeClick:c,reconnectRadius:d,onEdgeDoubleClick:f,onReconnectStart:h,onReconnectEnd:m,disableKeyboardA11y:g}){let{edgesFocusable:y,edgesReconnectable:v,elementsSelectable:b,onError:w}=yi(xl,fi),x=$a(t);return(0,p.jsxs)("div",{className:"react-flow__edges",children:[(0,p.jsx)(Ua,{defaultColor:e,rfId:n}),x.map(e=>(0,p.jsx)(wl,{id:e,edgesFocusable:y,edgesReconnectable:v,elementsSelectable:b,noPanClassName:o,onReconnect:i,onContextMenu:a,onMouseEnter:l,onMouseMove:s,onMouseLeave:u,onClick:c,reconnectRadius:d,onDoubleClick:f,onReconnectStart:h,onReconnectEnd:m,rfId:n,onError:w,edgeTypes:r,disableKeyboardA11y:g},e))]})}kl.displayName="EdgeRenderer";var Sl=(0,f.memo)(kl),El=e=>`translate(${e.transform[0]}px,${e.transform[1]}px) scale(${e.transform[2]})`;function Cl({children:e}){return(0,p.jsx)("div",{className:"react-flow__viewport xyflow__viewport react-flow__container",style:{transform:yi(El)},children:e})}function Ml(e){let t=sa(),n=(0,f.useRef)(!1);(0,f.useEffect)(()=>{!n.current&&t.viewportInitialized&&e&&(setTimeout(()=>e(t),1),n.current=!0)},[e,t.viewportInitialized])}var Pl=e=>e.panZoom?.syncViewport;function Ol(e){let t=yi(Pl),n=vi();return(0,f.useEffect)(()=>{e&&(t?.(e),n.setState({transform:[e.x,e.y,e.zoom]}))},[e,t]),null}function Tl(e){return e.connection.inProgress?{...e.connection,to:Dr(e.connection.to,e.transform)}:{...e.connection}}var _l=e=>({nodesConnectable:e.nodesConnectable,isValid:e.connection.isValid,inProgress:e.connection.inProgress,width:e.width,height:e.height});function Nl({containerStyle:e,style:t,type:n,component:r}){let{nodesConnectable:o,width:i,height:a,isValid:l,inProgress:s}=yi(_l,fi);return i&&o&&s?(0,p.jsx)("svg",{style:e,width:i,height:a,className:"react-flow__connectionline react-flow__container",children:(0,p.jsx)("g",{className:h(["react-flow__connection",pr(l)]),children:(0,p.jsx)(Il,{style:t,type:n,CustomComponent:r,isValid:l})})}):null}var Il=({style:e,type:t=sr.Bezier,CustomComponent:n,isValid:r})=>{let{inProgress:o,from:i,fromNode:a,fromHandle:l,fromPosition:s,to:u,toNode:c,toHandle:d,toPosition:f,pointer:h}=yi(Tl,fi);if(!o)return;if(n)return(0,p.jsx)(n,{connectionLineType:t,connectionLineStyle:e,fromNode:a,fromHandle:l,fromX:i.x,fromY:i.y,toX:u.x,toY:u.y,fromPosition:s,toPosition:f,connectionStatus:pr(r),toNode:c,toHandle:d,pointer:h});let m="",g={sourceX:i.x,sourceY:i.y,sourcePosition:s,targetX:u.x,targetY:u.y,targetPosition:f};switch(t){case sr.Bezier:[m]=io(g);break;case sr.SimpleBezier:[m]=Ga(g);break;case sr.Step:[m]=mo({...g,borderRadius:0});break;case sr.SmoothStep:[m]=mo(g);break;default:[m]=co(g)}return(0,p.jsx)("path",{d:m,fill:"none",className:"react-flow__connection-path",style:e})};Il.displayName="ConnectionLine";var zl={};function Rl(e=zl){(0,f.useRef)(e),vi(),(0,f.useEffect)(()=>{},[e])}function Ll(){vi(),(0,f.useRef)(!1),(0,f.useEffect)(()=>{},[])}function Al({nodeTypes:e,edgeTypes:t,onInit:n,onNodeClick:r,onEdgeClick:o,onNodeDoubleClick:i,onEdgeDoubleClick:a,onNodeMouseEnter:l,onNodeMouseMove:s,onNodeMouseLeave:u,onNodeContextMenu:c,onSelectionContextMenu:d,onSelectionStart:f,onSelectionEnd:h,connectionLineType:m,connectionLineStyle:g,connectionLineComponent:y,connectionLineContainerStyle:v,selectionKeyCode:b,selectionOnDrag:w,selectionMode:x,multiSelectionKeyCode:k,panActivationKeyCode:S,zoomActivationKeyCode:E,deleteKeyCode:C,onlyRenderVisibleElements:M,elementsSelectable:P,defaultViewport:O,translateExtent:T,minZoom:_,maxZoom:N,preventScrolling:I,defaultMarkerColor:z,zoomOnScroll:R,zoomOnPinch:L,panOnScroll:A,panOnScrollSpeed:D,panOnScrollMode:F,zoomOnDoubleClick:j,panOnDrag:H,autoPanOnSelection:V,onPaneClick:$,onPaneMouseEnter:B,onPaneMouseMove:W,onPaneMouseLeave:q,onPaneScroll:U,onPaneContextMenu:K,paneClickDistance:Y,nodeClickDistance:X,onEdgeContextMenu:Z,onEdgeMouseEnter:G,onEdgeMouseMove:Q,onEdgeMouseLeave:J,reconnectRadius:ee,onReconnect:te,onReconnectStart:ne,onReconnectEnd:re,noDragClassName:oe,noWheelClassName:ie,noPanClassName:ae,disableKeyboardA11y:le,nodeExtent:se,rfId:ue,viewport:ce,onViewportChange:de}){return Rl(e),Rl(t),Ll(),Ml(n),Ol(ce),(0,p.jsx)(La,{onPaneClick:$,onPaneMouseEnter:B,onPaneMouseMove:W,onPaneMouseLeave:q,onPaneContextMenu:K,onPaneScroll:U,paneClickDistance:Y,deleteKeyCode:C,selectionKeyCode:b,selectionOnDrag:w,selectionMode:x,onSelectionStart:f,onSelectionEnd:h,multiSelectionKeyCode:k,panActivationKeyCode:S,zoomActivationKeyCode:E,elementsSelectable:P,zoomOnScroll:R,zoomOnPinch:L,zoomOnDoubleClick:j,panOnScroll:A,panOnScrollSpeed:D,panOnScrollMode:F,panOnDrag:H,autoPanOnSelection:V,defaultViewport:O,translateExtent:T,minZoom:_,maxZoom:N,onSelectionContextMenu:d,preventScrolling:I,noDragClassName:oe,noWheelClassName:ie,noPanClassName:ae,disableKeyboardA11y:le,onViewportChange:de,isControlledViewport:!!ce,children:(0,p.jsxs)(Cl,{children:[(0,p.jsx)(Sl,{edgeTypes:t,onEdgeClick:o,onEdgeDoubleClick:a,onReconnect:te,onReconnectStart:ne,onReconnectEnd:re,onlyRenderVisibleElements:M,onEdgeContextMenu:Z,onEdgeMouseEnter:G,onEdgeMouseMove:Q,onEdgeMouseLeave:J,reconnectRadius:ee,defaultMarkerColor:z,noPanClassName:ae,disableKeyboardA11y:le,rfId:ue}),(0,p.jsx)(Nl,{style:g,type:m,component:y,containerStyle:v}),(0,p.jsx)("div",{className:"react-flow__edgelabel-renderer"}),(0,p.jsx)(Va,{nodeTypes:e,onNodeClick:r,onNodeDoubleClick:i,onNodeMouseEnter:l,onNodeMouseMove:s,onNodeMouseLeave:u,onNodeContextMenu:c,nodeClickDistance:X,onlyRenderVisibleElements:M,noPanClassName:ae,noDragClassName:oe,disableKeyboardA11y:le,nodeExtent:se,rfId:ue}),(0,p.jsx)("div",{className:"react-flow__viewport-portal"})]})})}Al.displayName="GraphView";var Dl=(0,f.memo)(Al),Fl=(e,t)=>{},jl=({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:o,height:i,fitView:a,fitViewOptions:l,minZoom:s=.5,maxZoom:u=2,nodeOrigin:c,nodeExtent:d,zIndexMode:f="basic"}={})=>{let p=new Map,h=new Map,m=new Map,g=new Map,y=r??t??[],v=n??e??[],b=c??[0,0],w=d??ir;No(m,g,y);let{nodesInitialized:x}=Mo(v,p,h,{nodeOrigin:b,nodeExtent:w,zIndexMode:f}),k=[0,0,1];if(a&&o&&i){let{x:e,y:t,zoom:n}=Hr(br(p,{filter:e=>!(!e.width&&!e.initialWidth||!e.height&&!e.initialHeight)}),o,i,s,u,l?.padding??.1);k=[e,t,n]}return{rfId:"1",width:o??0,height:i??0,transform:k,nodes:v,nodesInitialized:x,nodeLookup:p,parentLookup:h,edges:y,edgeLookup:g,connectionLookup:m,onNodesChange:null,onEdgesChange:null,hasDefaultNodes:void 0!==n,hasDefaultEdges:void 0!==r,panZoom:null,minZoom:s,maxZoom:u,translateExtent:ir,nodeExtent:w,nodesSelectionActive:!1,userSelectionActive:!1,userSelectionRect:null,connectionMode:nr.Strict,domNode:null,paneDragging:!1,noPanClassName:"nopan",nodeOrigin:b,nodeDragThreshold:1,connectionDragThreshold:1,snapGrid:[15,15],snapToGrid:!1,nodesDraggable:!0,nodesConnectable:!0,nodesFocusable:!0,edgesFocusable:!0,edgesReconnectable:!0,elementsSelectable:!0,elevateNodesOnSelect:!0,elevateEdgesOnSelect:!0,selectNodesOnDrag:!0,multiSelectionActive:!1,fitViewQueued:a??!1,fitViewOptions:l,fitViewResolver:null,connection:{...dr},connectionClickStartHandle:null,connectOnClick:!0,ariaLiveMessage:"",autoPanOnConnect:!0,autoPanOnNodeDrag:!0,autoPanOnNodeFocus:!0,autoPanSpeed:15,connectionRadius:20,onError:Fl,isValidConnection:void 0,onSelectionChangeHandlers:[],lib:"react",debug:!1,ariaLabelConfig:lr,zIndexMode:f,onNodesChangeMiddlewareMap:new Map,onEdgesChangeMiddlewareMap:new Map}};function Hl({initialNodes:e,initialEdges:t,defaultNodes:n,defaultEdges:r,initialWidth:o,initialHeight:i,initialMinZoom:a,initialMaxZoom:l,initialFitViewOptions:s,fitView:u,nodeOrigin:c,nodeExtent:d,zIndexMode:h,children:m}){let[g]=(0,f.useState)(()=>(({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:o,height:i,fitView:a,fitViewOptions:l,minZoom:s,maxZoom:u,nodeOrigin:c,nodeExtent:d,zIndexMode:f})=>((e,t)=>e?di(e,t):di)((p,h)=>{async function m(){let{nodeLookup:e,panZoom:t,fitViewOptions:n,fitViewResolver:r,width:o,height:i,minZoom:a,maxZoom:l}=h();t&&(await async function({nodes:e,width:t,height:n,panZoom:r,minZoom:o,maxZoom:i},a){if(0===e.size)return!0;let l=Hr(br(function(e,t){let n=new Map,r=t?.nodes?new Set(t.nodes.map(e=>e.id)):null;return e.forEach(e=>{e.measured.width&&e.measured.height&&(t?.includeHiddenNodes||!e.hidden)&&(!r||r.has(e.id))&&n.set(e.id,e)}),n}(e,a)),t,n,a?.minZoom??o,a?.maxZoom??i,a?.padding??.1);return await r.setViewport(l,{duration:a?.duration,ease:a?.ease,interpolate:a?.interpolate}),!0}({nodes:e,width:o,height:i,panZoom:t,minZoom:a,maxZoom:l},n),r?.resolve(!0),p({fitViewResolver:null}))}return{...jl({nodes:e,edges:t,width:o,height:i,fitView:a,fitViewOptions:l,minZoom:s,maxZoom:u,nodeOrigin:c,nodeExtent:d,defaultNodes:n,defaultEdges:r,zIndexMode:f}),setNodes:e=>{let{nodeLookup:t,parentLookup:n,nodeOrigin:r,elevateNodesOnSelect:o,fitViewQueued:i,zIndexMode:a,nodesSelectionActive:l}=h(),{nodesInitialized:s,hasSelectedNodes:u}=Mo(e,t,n,{nodeOrigin:r,nodeExtent:d,elevateNodesOnSelect:o,checkEquality:!0,zIndexMode:a}),c=l&&u;i&&s?(m(),p({nodes:e,nodesInitialized:s,fitViewQueued:!1,fitViewOptions:void 0,nodesSelectionActive:c})):p({nodes:e,nodesInitialized:s,nodesSelectionActive:c})},setEdges:e=>{let{connectionLookup:t,edgeLookup:n}=h();No(t,n,e),p({edges:e})},setDefaultNodesAndEdges:(e,t)=>{if(e){let{setNodes:t}=h();t(e),p({hasDefaultNodes:!0})}if(t){let{setEdges:e}=h();e(t),p({hasDefaultEdges:!0})}},updateNodeInternals:e=>{let{triggerNodeChanges:t,nodeLookup:n,parentLookup:r,domNode:o,nodeOrigin:i,nodeExtent:a,debug:l,fitViewQueued:s,zIndexMode:u}=h(),{changes:c,updatedInternals:d}=function(e,t,n,r,o,i,a){let l=r?.querySelector(".xyflow__viewport"),s=!1;if(!l)return{changes:[],updatedInternals:s};let u=[],c=window.getComputedStyle(l),{m22:d}=new window.DOMMatrixReadOnly(c.transform),f=[];for(let r of e.values()){let e=t.get(r.id);if(!e)continue;if(e.hidden){t.set(e.id,{...e,internals:{...e.internals,handleBounds:void 0}}),s=!0;continue}let l=Xr(r.nodeElement),c=e.measured.width!==l.width||e.measured.height!==l.height;if(l.width&&l.height&&(c||!e.internals.handleBounds||r.force)){let p=r.nodeElement.getBoundingClientRect(),h=$r(e.extent)?e.extent:i,{positionAbsolute:m}=e.internals;e.parentId&&"parent"===e.extent?m=Er(m,l,t.get(e.parentId)):h&&(m=Sr(m,h,l));let g={...e,measured:l,internals:{...e.internals,positionAbsolute:m,handleBounds:{source:to("source",r.nodeElement,p,d,e.id),target:to("target",r.nodeElement,p,d,e.id)}}};t.set(e.id,g),e.parentId&&Po(g,t,n,{nodeOrigin:o,zIndexMode:a}),s=!0,c&&(u.push({id:e.id,type:"dimensions",dimensions:l}),e.expandParent&&e.parentId&&f.push({id:e.id,parentId:e.parentId,rect:_r(g,o)}))}}if(f.length>0){let e=To(f,t,n,o);u.push(...e)}return{changes:u,updatedInternals:s}}(e,n,r,o,i,a,u);d&&(function(e,t,n){let r=So(xo,n);for(let n of e.values())if(n.parentId)Po(n,e,t,r);else{let e=Sr(vr(n,r.nodeOrigin),$r(n.extent)?n.extent:r.nodeExtent,Br(n));n.internals.positionAbsolute=e}}(n,r,{nodeOrigin:i,nodeExtent:a,zIndexMode:u}),s?(m(),p({fitViewQueued:!1,fitViewOptions:void 0})):p({}),c?.length>0&&(l&&console.log("React Flow: trigger node changes",c),t?.(c)))},updateNodePositions:(e,t=!1)=>{let n=[],r=[],{nodeLookup:o,triggerNodeChanges:i,connection:a,updateConnection:l,onNodesChangeMiddlewareMap:s}=h();for(let[i,s]of e){let e=o.get(i),u=!!(e?.expandParent&&e?.parentId&&s?.position),c={id:i,type:"position",position:u?{x:Math.max(0,s.position.x),y:Math.max(0,s.position.y)}:s.position,dragging:t};if(e&&a.inProgress&&a.fromNode.id===e.id){let t=vo(e,a.fromHandle,cr.Left,!0);l({...a,from:t})}u&&e.parentId&&n.push({id:i,parentId:e.parentId,rect:{...s.internals.positionAbsolute,width:s.measured.width??0,height:s.measured.height??0}}),r.push(c)}if(n.length>0){let{parentLookup:e,nodeOrigin:t}=h(),i=To(n,o,e,t);r.push(...i)}for(let e of s.values())r=e(r);i(r)},triggerNodeChanges:e=>{let{onNodesChange:t,setNodes:n,nodes:r,hasDefaultNodes:o,debug:i}=h();e?.length&&(o&&n(Yi(e,r)),i&&console.log("React Flow: trigger node changes",e),t?.(e))},triggerEdgeChanges:e=>{let{onEdgesChange:t,setEdges:n,edges:r,hasDefaultEdges:o,debug:i}=h();e?.length&&(o&&n(Xi(e,r)),i&&console.log("React Flow: trigger edge changes",e),t?.(e))},addSelectedNodes:e=>{let{multiSelectionActive:t,edgeLookup:n,nodeLookup:r,triggerNodeChanges:o,triggerEdgeChanges:i}=h();t?o(e.map(e=>Zi(e,!0))):(o(Gi(r,new Set([...e]),!0)),i(Gi(n)))},addSelectedEdges:e=>{let{multiSelectionActive:t,edgeLookup:n,nodeLookup:r,triggerNodeChanges:o,triggerEdgeChanges:i}=h();t?i(e.map(e=>Zi(e,!0))):(i(Gi(n,new Set([...e]))),o(Gi(r,new Set,!0)))},unselectNodesAndEdges:({nodes:e,edges:t}={})=>{let{edges:n,nodes:r,nodeLookup:o,triggerNodeChanges:i,triggerEdgeChanges:a}=h(),l=e||r,s=t||n,u=[];for(let e of l){if(!e.selected)continue;let t=o.get(e.id);t&&(t.selected=!1),u.push(Zi(e.id,!1))}let c=[];for(let e of s)e.selected&&c.push(Zi(e.id,!1));i(u),a(c)},setMinZoom:e=>{let{panZoom:t,maxZoom:n}=h();t?.setScaleExtent([e,n]),p({minZoom:e})},setMaxZoom:e=>{let{panZoom:t,minZoom:n}=h();t?.setScaleExtent([n,e]),p({maxZoom:e})},setTranslateExtent:e=>{h().panZoom?.setTranslateExtent(e),p({translateExtent:e})},resetSelectedElements:()=>{let{edges:e,nodes:t,triggerNodeChanges:n,triggerEdgeChanges:r,elementsSelectable:o}=h();if(!o)return;let i=t.reduce((e,t)=>t.selected?[...e,Zi(t.id,!1)]:e,[]),a=e.reduce((e,t)=>t.selected?[...e,Zi(t.id,!1)]:e,[]);n(i),r(a)},setNodeExtent:e=>{let{nodes:t,nodeLookup:n,parentLookup:r,nodeOrigin:o,elevateNodesOnSelect:i,nodeExtent:a,zIndexMode:l}=h();e[0][0]===a[0][0]&&e[0][1]===a[0][1]&&e[1][0]===a[1][0]&&e[1][1]===a[1][1]||(Mo(t,n,r,{nodeOrigin:o,nodeExtent:e,elevateNodesOnSelect:i,checkEquality:!1,zIndexMode:l}),p({nodeExtent:e}))},panBy:e=>{let{transform:t,width:n,height:r,panZoom:o,translateExtent:i}=h();return async function({delta:e,panZoom:t,transform:n,translateExtent:r,width:o,height:i}){if(!t||!e.x&&!e.y)return!1;let a=await t.setViewportConstrained({x:n[0]+e.x,y:n[1]+e.y,zoom:n[2]},[[0,0],[o,i]],r);return!!a&&(a.x!==n[0]||a.y!==n[1]||a.k!==n[2])}({delta:e,panZoom:o,transform:t,translateExtent:i,width:n,height:r})},setCenter:async(e,t,n)=>{let{width:r,height:o,maxZoom:i,panZoom:a}=h();if(!a)return!1;let l=void 0===n?.zoom?i:n.zoom;return await a.setViewport({x:r/2-e*l,y:o/2-t*l,zoom:l},{duration:n?.duration,ease:n?.ease,interpolate:n?.interpolate}),!0},cancelConnection:()=>{p({connection:{...dr}})},updateConnection:e=>{p({connection:e})},reset:()=>p({...jl()})}},Object.is))({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:o,height:i,fitView:u,minZoom:a,maxZoom:l,fitViewOptions:s,nodeOrigin:c,nodeExtent:d,zIndexMode:h}));return(0,p.jsx)(mi,{value:g,children:(0,p.jsx)(aa,{children:m})})}function Vl({children:e,nodes:t,edges:n,defaultNodes:r,defaultEdges:o,width:i,height:a,fitView:l,fitViewOptions:s,minZoom:u,maxZoom:c,nodeOrigin:d,nodeExtent:h,zIndexMode:m}){return(0,f.useContext)(hi)?(0,p.jsx)(p.Fragment,{children:e}):(0,p.jsx)(Hl,{initialNodes:t,initialEdges:n,defaultNodes:r,defaultEdges:o,initialWidth:i,initialHeight:a,fitView:l,initialFitViewOptions:s,initialMinZoom:u,initialMaxZoom:c,nodeOrigin:d,nodeExtent:h,zIndexMode:m,children:e})}var $l={width:"100%",height:"100%",overflow:"hidden",position:"relative",zIndex:0},Bl=na(function({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,className:o,nodeTypes:i,edgeTypes:a,onNodeClick:l,onEdgeClick:s,onInit:u,onMove:c,onMoveStart:d,onMoveEnd:m,onConnect:g,onConnectStart:y,onConnectEnd:v,onClickConnectStart:b,onClickConnectEnd:w,onNodeMouseEnter:x,onNodeMouseMove:k,onNodeMouseLeave:S,onNodeContextMenu:E,onNodeDoubleClick:C,onNodeDragStart:M,onNodeDrag:P,onNodeDragStop:O,onNodesDelete:T,onEdgesDelete:_,onDelete:N,onSelectionChange:I,onSelectionDragStart:z,onSelectionDrag:R,onSelectionDragStop:L,onSelectionContextMenu:A,onSelectionStart:D,onSelectionEnd:F,onBeforeDelete:j,connectionMode:H,connectionLineType:V=sr.Bezier,connectionLineStyle:$,connectionLineComponent:B,connectionLineContainerStyle:W,deleteKeyCode:q="Backspace",selectionKeyCode:U="Shift",selectionOnDrag:K=!1,selectionMode:Y=or.Full,panActivationKeyCode:X="Space",multiSelectionKeyCode:Z=(Vr()?"Meta":"Control"),zoomActivationKeyCode:G=(Vr()?"Meta":"Control"),snapToGrid:Q,snapGrid:J,onlyRenderVisibleElements:ee=!1,selectNodesOnDrag:te,nodesDraggable:ne,autoPanOnNodeFocus:re,nodesConnectable:oe,nodesFocusable:ie,nodeOrigin:ae=Li,edgesFocusable:le,edgesReconnectable:se,elementsSelectable:ue=!0,defaultViewport:ce=Ai,minZoom:de=.5,maxZoom:fe=2,translateExtent:pe=ir,preventScrolling:he=!0,nodeExtent:me,defaultMarkerColor:ge="#b1b1b7",zoomOnScroll:ye=!0,zoomOnPinch:ve=!0,panOnScroll:be=!1,panOnScrollSpeed:we=.5,panOnScrollMode:xe=rr.Free,zoomOnDoubleClick:ke=!0,panOnDrag:Se=!0,onPaneClick:Ee,onPaneMouseEnter:Ce,onPaneMouseMove:Me,onPaneMouseLeave:Pe,onPaneScroll:Oe,onPaneContextMenu:Te,paneClickDistance:_e=1,nodeClickDistance:Ne=0,children:Ie,onReconnect:ze,onReconnectStart:Re,onReconnectEnd:Le,onEdgeContextMenu:Ae,onEdgeDoubleClick:De,onEdgeMouseEnter:Fe,onEdgeMouseMove:je,onEdgeMouseLeave:He,reconnectRadius:Ve=10,onNodesChange:$e,onEdgesChange:Be,noDragClassName:We="nodrag",noWheelClassName:qe="nowheel",noPanClassName:Ue="nopan",fitView:Ke,fitViewOptions:Ye,connectOnClick:Xe,attributionPosition:Ze,proOptions:Ge,defaultEdgeOptions:Qe,elevateNodesOnSelect:Je=!0,elevateEdgesOnSelect:et=!1,disableKeyboardA11y:tt=!1,autoPanOnConnect:nt,autoPanOnNodeDrag:rt,autoPanOnSelection:ot=!0,autoPanSpeed:it,connectionRadius:at,isValidConnection:lt,onError:st,style:ut,id:ct,nodeDragThreshold:dt,connectionDragThreshold:ft,viewport:pt,onViewportChange:ht,width:mt,height:gt,colorMode:yt="light",debug:vt,onScroll:bt,ariaLabelConfig:wt,zIndexMode:xt="basic",...kt},St){let Et=ct||"1",Ct=function(e){let[t,n]=(0,f.useState)("system"===e?null:e);return(0,f.useEffect)(()=>{if("system"!==e)return void n(e);let t=Vi(),r=()=>n(t?.matches?"dark":"light");return r(),t?.addEventListener("change",r),()=>{t?.removeEventListener("change",r)}},[e]),null===t?Vi()?.matches?"dark":"light":t}(yt),Mt=(0,f.useCallback)(e=>{e.currentTarget.scrollTo({top:0,left:0,behavior:"instant"}),bt?.(e)},[bt]);return(0,p.jsx)("div",{"data-testid":"rf__wrapper",...kt,onScroll:Mt,style:{...ut,...$l},ref:St,className:h(["react-flow",o,Ct]),id:ct,role:"application",children:(0,p.jsxs)(Vl,{nodes:e,edges:t,width:mt,height:gt,fitView:Ke,fitViewOptions:Ye,minZoom:de,maxZoom:fe,nodeOrigin:ae,nodeExtent:me,zIndexMode:xt,children:[(0,p.jsx)(Hi,{nodes:e,edges:t,defaultNodes:n,defaultEdges:r,onConnect:g,onConnectStart:y,onConnectEnd:v,onClickConnectStart:b,onClickConnectEnd:w,nodesDraggable:ne,autoPanOnNodeFocus:re,nodesConnectable:oe,nodesFocusable:ie,edgesFocusable:le,edgesReconnectable:se,elementsSelectable:ue,elevateNodesOnSelect:Je,elevateEdgesOnSelect:et,minZoom:de,maxZoom:fe,nodeExtent:me,onNodesChange:$e,onEdgesChange:Be,snapToGrid:Q,snapGrid:J,connectionMode:H,translateExtent:pe,connectOnClick:Xe,defaultEdgeOptions:Qe,fitView:Ke,fitViewOptions:Ye,onNodesDelete:T,onEdgesDelete:_,onDelete:N,onNodeDragStart:M,onNodeDrag:P,onNodeDragStop:O,onSelectionDrag:R,onSelectionDragStart:z,onSelectionDragStop:L,onMove:c,onMoveStart:d,onMoveEnd:m,noPanClassName:Ue,nodeOrigin:ae,rfId:Et,autoPanOnConnect:nt,autoPanOnNodeDrag:rt,autoPanSpeed:it,onError:st,connectionRadius:at,isValidConnection:lt,selectNodesOnDrag:te,nodeDragThreshold:dt,connectionDragThreshold:ft,onBeforeDelete:j,debug:vt,ariaLabelConfig:wt,zIndexMode:xt}),(0,p.jsx)(Dl,{onInit:u,onNodeClick:l,onEdgeClick:s,onNodeMouseEnter:x,onNodeMouseMove:k,onNodeMouseLeave:S,onNodeContextMenu:E,onNodeDoubleClick:C,nodeTypes:i,edgeTypes:a,connectionLineType:V,connectionLineStyle:$,connectionLineComponent:B,connectionLineContainerStyle:W,selectionKeyCode:U,selectionOnDrag:K,selectionMode:Y,deleteKeyCode:q,multiSelectionKeyCode:Z,panActivationKeyCode:X,zoomActivationKeyCode:G,onlyRenderVisibleElements:ee,defaultViewport:ce,translateExtent:pe,minZoom:de,maxZoom:fe,preventScrolling:he,zoomOnScroll:ye,zoomOnPinch:ve,zoomOnDoubleClick:ke,panOnScroll:be,panOnScrollSpeed:we,panOnScrollMode:xe,panOnDrag:Se,autoPanOnSelection:ot,onPaneClick:Ee,onPaneMouseEnter:Ce,onPaneMouseMove:Me,onPaneMouseLeave:Pe,onPaneScroll:Oe,onPaneContextMenu:Te,paneClickDistance:_e,nodeClickDistance:Ne,onSelectionContextMenu:A,onSelectionStart:D,onSelectionEnd:F,onReconnect:ze,onReconnectStart:Re,onReconnectEnd:Le,onEdgeContextMenu:Ae,onEdgeDoubleClick:De,onEdgeMouseEnter:Fe,onEdgeMouseMove:je,onEdgeMouseLeave:He,reconnectRadius:Ve,defaultMarkerColor:ge,noDragClassName:We,noWheelClassName:qe,noPanClassName:Ue,rfId:Et,disableKeyboardA11y:tt,nodeExtent:me,viewport:pt,onViewportChange:ht}),(0,p.jsx)(Ri,{onSelectionChange:I}),Ie,(0,p.jsx)(Oi,{proOptions:Ge,position:Ze}),(0,p.jsx)(Mi,{rfId:Et,disableKeyboardA11y:tt})]})})});function Wl(){let e=vi();return(0,f.useCallback)(t=>{let{domNode:n,updateNodeInternals:r}=e.getState(),o=Array.isArray(t)?t:[t],i=new Map;o.forEach(e=>{let t=n?.querySelector(`.react-flow__node[data-id="${e}"]`);t&&i.set(e,{id:e,nodeElement:t,force:!0})}),requestAnimationFrame(()=>r(i,{triggerFitView:!1}))},[])}var ql,Ul=e=>({x:e.transform[0],y:e.transform[1],zoom:e.transform[2]});function Kl(){return yi(Ul,fi)}function Yl({dimensions:e,lineWidth:t,variant:n,className:r}){return(0,p.jsx)("path",{strokeWidth:t,d:`M${e[0]/2} 0 V${e[1]} M0 ${e[1]/2} H${e[0]}`,className:h(["react-flow__background-pattern",n,r])})}function Xl({radius:e,className:t}){return(0,p.jsx)("circle",{cx:e,cy:e,r:e,className:h(["react-flow__background-pattern","dots",t])})}!function(e){e.Lines="lines",e.Dots="dots",e.Cross="cross"}(ql||={});var Zl={[ql.Dots]:1,[ql.Lines]:1,[ql.Cross]:6},Gl=e=>({transform:e.transform,patternId:`pattern-${e.rfId}`});function Ql({id:e,variant:t=ql.Dots,gap:n=20,size:r,lineWidth:o=1,offset:i=0,color:a,bgColor:l,style:s,className:u,patternClassName:c}){let d=(0,f.useRef)(null),{transform:m,patternId:g}=yi(Gl,fi),y=r||Zl[t],v=t===ql.Dots,b=t===ql.Cross,w=Array.isArray(n)?n:[n,n],x=[w[0]*m[2]||1,w[1]*m[2]||1],k=y*m[2],S=Array.isArray(i)?i:[i,i],E=b?[k,k]:x,C=[S[0]*m[2]||1+E[0]/2,S[1]*m[2]||1+E[1]/2],M=`${g}${e||""}`;return(0,p.jsxs)("svg",{className:h(["react-flow__background",u]),style:{...s,...fa,"--xy-background-color-props":l,"--xy-background-pattern-color-props":a},ref:d,"data-testid":"rf__background",children:[(0,p.jsx)("pattern",{id:M,x:m[0]%x[0],y:m[1]%x[1],width:x[0],height:x[1],patternUnits:"userSpaceOnUse",patternTransform:`translate(-${C[0]},-${C[1]})`,children:v?(0,p.jsx)(Xl,{radius:k/2,className:c}):(0,p.jsx)(Yl,{dimensions:E,lineWidth:o,variant:t,className:c})}),(0,p.jsx)("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:`url(#${M})`})]})}Ql.displayName="Background";var Jl=(0,f.memo)(Ql);function es(){return(0,p.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32",children:(0,p.jsx)("path",{d:"M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"})})}function ts(){return(0,p.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 5",children:(0,p.jsx)("path",{d:"M0 0h32v4.2H0z"})})}function ns(){return(0,p.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 30",children:(0,p.jsx)("path",{d:"M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z"})})}function rs(){return(0,p.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32",children:(0,p.jsx)("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z"})})}function os(){return(0,p.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32",children:(0,p.jsx)("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z"})})}function is({children:e,className:t,...n}){return(0,p.jsx)("button",{type:"button",className:h(["react-flow__controls-button",t]),...n,children:e})}var as=e=>({isInteractive:e.nodesDraggable||e.nodesConnectable||e.elementsSelectable,minZoomReached:e.transform[2]<=e.minZoom,maxZoomReached:e.transform[2]>=e.maxZoom,ariaLabelConfig:e.ariaLabelConfig});function ls({style:e,showZoom:t=!0,showFitView:n=!0,showInteractive:r=!0,fitViewOptions:o,onZoomIn:i,onZoomOut:a,onFitView:l,onInteractiveChange:s,className:u,children:c,position:d="bottom-left",orientation:f="vertical","aria-label":m}){let g=vi(),{isInteractive:y,minZoomReached:v,maxZoomReached:b,ariaLabelConfig:w}=yi(as,fi),{zoomIn:x,zoomOut:k,fitView:S}=sa();return(0,p.jsxs)(Pi,{className:h(["react-flow__controls","horizontal"===f?"horizontal":"vertical",u]),position:d,style:e,"data-testid":"rf__controls","aria-label":m??w["controls.ariaLabel"],children:[t&&(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(is,{onClick:()=>{x(),i?.()},className:"react-flow__controls-zoomin",title:w["controls.zoomIn.ariaLabel"],"aria-label":w["controls.zoomIn.ariaLabel"],disabled:b,children:(0,p.jsx)(es,{})}),(0,p.jsx)(is,{onClick:()=>{k(),a?.()},className:"react-flow__controls-zoomout",title:w["controls.zoomOut.ariaLabel"],"aria-label":w["controls.zoomOut.ariaLabel"],disabled:v,children:(0,p.jsx)(ts,{})})]}),n&&(0,p.jsx)(is,{className:"react-flow__controls-fitview",onClick:()=>{S(o),l?.()},title:w["controls.fitView.ariaLabel"],"aria-label":w["controls.fitView.ariaLabel"],children:(0,p.jsx)(ns,{})}),r&&(0,p.jsx)(is,{className:"react-flow__controls-interactive",onClick:()=>{g.setState({nodesDraggable:!y,nodesConnectable:!y,elementsSelectable:!y}),s?.(!y)},title:w["controls.interactive.ariaLabel"],"aria-label":w["controls.interactive.ariaLabel"],children:y?(0,p.jsx)(os,{}):(0,p.jsx)(rs,{})}),c]})}ls.displayName="Controls",(0,f.memo)(ls);var ss=(0,f.memo)(function({id:e,x:t,y:n,width:r,height:o,style:i,color:a,strokeColor:l,strokeWidth:s,className:u,borderRadius:c,shapeRendering:d,selected:f,onClick:m}){let{background:g,backgroundColor:y}=i||{},v=a||g||y;return(0,p.jsx)("rect",{className:h(["react-flow__minimap-node",{selected:f},u]),x:t,y:n,rx:c,ry:c,width:r,height:o,style:{fill:v,stroke:l,strokeWidth:s},shapeRendering:d,onClick:m?t=>m(t,e):void 0})}),us=e=>e.nodes.map(e=>e.id),cs=e=>e instanceof Function?e:()=>e,ds=(0,f.memo)(function({id:e,nodeColorFunc:t,nodeStrokeColorFunc:n,nodeClassNameFunc:r,nodeBorderRadius:o,nodeStrokeWidth:i,shapeRendering:a,NodeComponent:l,onClick:s}){let{node:u,x:c,y:d,width:f,height:h}=yi(t=>{let n=t.nodeLookup.get(e);if(!n)return{node:void 0,x:0,y:0,width:0,height:0};let r=n.internals.userNode,{x:o,y:i}=n.internals.positionAbsolute,{width:a,height:l}=Br(r);return{node:r,x:o,y:i,width:a,height:l}},fi);return u&&!u.hidden&&Wr(u)?(0,p.jsx)(l,{x:c,y:d,width:f,height:h,style:u.style,selected:!!u.selected,className:r(u),color:t(u),borderRadius:o,strokeColor:n(u),strokeWidth:i,shapeRendering:a,onClick:s,id:u.id}):null}),fs=(0,f.memo)(function({nodeStrokeColor:e,nodeColor:t,nodeClassName:n="",nodeBorderRadius:r=5,nodeStrokeWidth:o,nodeComponent:i=ss,onClick:a}){let l=yi(us,fi),s=cs(t),u=cs(e),c=cs(n),d=typeof window>"u"||window.chrome?"crispEdges":"geometricPrecision";return(0,p.jsx)(p.Fragment,{children:l.map(e=>(0,p.jsx)(ds,{id:e,nodeColorFunc:s,nodeStrokeColorFunc:u,nodeClassNameFunc:c,nodeBorderRadius:r,nodeStrokeWidth:o,NodeComponent:i,onClick:a,shapeRendering:d},e))})}),ps=e=>!e.hidden,hs=e=>{let t={x:-e.transform[0]/e.transform[2],y:-e.transform[1]/e.transform[2],width:e.width/e.transform[2],height:e.height/e.transform[2]};return{viewBB:t,boundingRect:e.nodeLookup.size>0?Ir(br(e.nodeLookup,{filter:ps}),t):t,rfId:e.rfId,panZoom:e.panZoom,translateExtent:e.translateExtent,flowWidth:e.width,flowHeight:e.height,ariaLabelConfig:e.ariaLabelConfig}};function ms({style:e,className:t,nodeStrokeColor:n,nodeColor:r,nodeClassName:o="",nodeBorderRadius:i=5,nodeStrokeWidth:a,nodeComponent:l,bgColor:s,maskColor:u,maskStrokeColor:c,maskStrokeWidth:d,position:m="bottom-right",onClick:g,onNodeClick:y,pannable:v=!1,zoomable:b=!1,ariaLabel:w,inversePan:x,zoomStep:k=1,offsetScale:S=5}){let E=vi(),C=(0,f.useRef)(null),{boundingRect:M,viewBB:P,rfId:O,panZoom:T,translateExtent:_,flowWidth:N,flowHeight:I,ariaLabelConfig:z}=yi(hs,fi),R=e?.width??200,L=e?.height??150,A=M.width/R,D=M.height/L,F=Math.max(A,D),j=F*R,H=F*L,V=S*F,$=M.x-(j-M.width)/2-V,B=M.y-(H-M.height)/2-V,W=j+2*V,q=H+2*V,U=`react-flow__minimap-desc-${O}`,K=(0,f.useRef)(0),Y=(0,f.useRef)();K.current=F,(0,f.useEffect)(()=>{if(C.current&&T)return Y.current=function({domNode:e,panZoom:t,getTransform:n,getViewScale:r}){let o=_e(e);return{update:function({translateExtent:e,width:i,height:a,zoomStep:l=1,pannable:s=!0,zoomable:u=!0,inversePan:c=!1}){let d=[0,0],f=tr().on("start",e=>{("mousedown"===e.sourceEvent.type||"touchstart"===e.sourceEvent.type)&&(d=[e.sourceEvent.clientX??e.sourceEvent.touches[0].clientX,e.sourceEvent.clientY??e.sourceEvent.touches[0].clientY])}).on("zoom",s?o=>{let l=n();if("mousemove"!==o.sourceEvent.type&&"touchmove"!==o.sourceEvent.type||!t)return;let s=[o.sourceEvent.clientX??o.sourceEvent.touches[0].clientX,o.sourceEvent.clientY??o.sourceEvent.touches[0].clientY],u=[s[0]-d[0],s[1]-d[1]];d=s;let f=r()*Math.max(l[2],Math.log(l[2]))*(c?-1:1),p={x:l[0]-u[0]*f,y:l[1]-u[1]*f},h=[[0,0],[i,a]];t.setViewportConstrained({x:p.x,y:p.y,zoom:l[2]},h,e)}:null).on("zoom.wheel",u?e=>{if("wheel"!==e.sourceEvent.type||!t)return;let r=n(),o=e.sourceEvent.ctrlKey&&Vr()?10:1,i=-e.sourceEvent.deltaY*(1===e.sourceEvent.deltaMode?.05:e.sourceEvent.deltaMode?1:.002)*l,a=r[2]*2**(i*o);t.scaleTo(a)}:null);o.call(f,{})},destroy:function(){o.on("zoom",null)},pointer:Ne}}({domNode:C.current,panZoom:T,getTransform:()=>E.getState().transform,getViewScale:()=>K.current}),()=>{Y.current?.destroy()}},[T]),(0,f.useEffect)(()=>{Y.current?.update({translateExtent:_,width:N,height:I,inversePan:x,pannable:v,zoomStep:k,zoomable:b})},[v,b,x,k,_,N,I]);let X=g?e=>{let[t,n]=Y.current?.pointer(e)||[0,0];g(e,{x:t,y:n})}:void 0,Z=y?(0,f.useCallback)((e,t)=>{let n=E.getState().nodeLookup.get(t).internals.userNode;y(e,n)},[]):void 0,G=w??z["minimap.ariaLabel"];return(0,p.jsx)(Pi,{position:m,style:{...e,"--xy-minimap-background-color-props":"string"==typeof s?s:void 0,"--xy-minimap-mask-background-color-props":"string"==typeof u?u:void 0,"--xy-minimap-mask-stroke-color-props":"string"==typeof c?c:void 0,"--xy-minimap-mask-stroke-width-props":"number"==typeof d?d*F:void 0,"--xy-minimap-node-background-color-props":"string"==typeof r?r:void 0,"--xy-minimap-node-stroke-color-props":"string"==typeof n?n:void 0,"--xy-minimap-node-stroke-width-props":"number"==typeof a?a:void 0},className:h(["react-flow__minimap",t]),"data-testid":"rf__minimap",children:(0,p.jsxs)("svg",{width:R,height:L,viewBox:`${$} ${B} ${W} ${q}`,className:"react-flow__minimap-svg",role:"img","aria-labelledby":U,ref:C,onClick:X,children:[G&&(0,p.jsx)("title",{id:U,children:G}),(0,p.jsx)(fs,{onClick:Z,nodeColor:r,nodeStrokeColor:n,nodeBorderRadius:i,nodeClassName:o,nodeStrokeWidth:a,nodeComponent:l}),(0,p.jsx)("path",{className:"react-flow__minimap-mask",d:`M${$-V},${B-V}h${W+2*V}v${q+2*V}h${-W-2*V}z\n M${P.x},${P.y}h${P.width}v${P.height}h${-P.width}z`,fillRule:"evenodd",pointerEvents:"none"})]})})}ms.displayName="MiniMap",(0,f.memo)(ms);var gs={[jo.Line]:"right",[jo.Handle]:"bottom-right"},ys=(0,f.memo)(function({nodeId:e,position:t,variant:n=jo.Handle,className:r,style:o,children:i,color:a,minWidth:l=10,minHeight:s=10,maxWidth:u=Number.MAX_VALUE,maxHeight:c=Number.MAX_VALUE,keepAspectRatio:d=!1,resizeDirection:m,autoScale:g=!0,shouldResize:y,onResizeStart:v,onResize:b,onResizeEnd:w}){let x=Ca(),k="string"==typeof e?e:x,S=vi(),E=(0,f.useRef)(null),C=n===jo.Handle,M=yi((0,f.useCallback)((e=>t=>e?`${Math.max(1/t.transform[2],1)}`:void 0)(C&&g),[C,g]),fi),P=(0,f.useRef)(null),O=t??gs[n];return(0,f.useEffect)(()=>{if(E.current&&k)return P.current||=function({domNode:e,nodeId:t,getStoreItems:n,onChange:r,onEnd:o}){let i=_e(e),a={controlDirection:Yo("bottom-right"),boundaries:{minWidth:0,minHeight:0,maxWidth:Number.MAX_VALUE,maxHeight:Number.MAX_VALUE},resizeDirection:void 0,keepAspectRatio:!1};return{update:function({controlPosition:e,boundaries:l,keepAspectRatio:s,resizeDirection:u,onResizeStart:c,onResize:d,onResizeEnd:f,shouldResize:p}){let h={...Jo},m={...ei};a={boundaries:l,resizeDirection:u,keepAspectRatio:s,controlDirection:Yo(e)};let g,y,v,b,w=null,x=[],k=!1,S=We().on("start",e=>{let{nodeLookup:r,transform:o,snapGrid:i,snapToGrid:a,nodeOrigin:l,paneDomNode:s}=n();if(g=r.get(t),!g)return;w=s?.getBoundingClientRect()??null;let{xSnapped:u,ySnapped:d}=Yr(e.sourceEvent,{transform:o,snapGrid:i,snapToGrid:a,containerBounds:w});h={width:g.measured.width??0,height:g.measured.height??0,x:g.position.x??0,y:g.position.y??0},m={...h,pointerX:u,pointerY:d,aspectRatio:h.width/h.height},y=void 0,v=$r(g.extent)?g.extent:void 0,g.parentId&&("parent"===g.extent||g.expandParent)&&(y=r.get(g.parentId)),y&&"parent"===g.extent&&(v=[[0,0],[y.measured.width,y.measured.height]]),x=[],b=void 0;for(let[e,n]of r)if(n.parentId===t&&(x.push({id:e,position:{...n.position},extent:n.extent}),"parent"===n.extent||n.expandParent)){let e=ti(n,g,n.origin??l);b=b?[[Math.min(e[0][0],b[0][0]),Math.min(e[0][1],b[0][1])],[Math.max(e[1][0],b[1][0]),Math.max(e[1][1],b[1][1])]]:e}c?.(e,{...h})}).on("drag",e=>{let{transform:t,snapGrid:o,snapToGrid:i,nodeOrigin:l}=n(),s=Yr(e.sourceEvent,{transform:t,snapGrid:o,snapToGrid:i,containerBounds:w}),u=[];if(!g)return;let{x:c,y:f,width:S,height:E}=h,C={},M=g.origin??l,{width:P,height:O,x:T,y:_}=function(e,t,n,r,o,i,a,l){let{affectsX:s,affectsY:u}=t,{isHorizontal:c,isVertical:d}=t,f=c&&d,{xSnapped:p,ySnapped:h}=n,{minWidth:m,maxWidth:g,minHeight:y,maxHeight:v}=r,{x:b,y:w,width:x,height:k,aspectRatio:S}=e,E=Math.floor(c?p-e.pointerX:0),C=Math.floor(d?h-e.pointerY:0),M=x+(s?-E:E),P=k+(u?-C:C),O=-i[0]*x,T=-i[1]*k,_=Go(M,m,g),N=Go(P,y,v);if(a){let e=0,t=0;s&&E<0?e=Xo(b+E+O,a[0][0]):!s&&E>0&&(e=Zo(b+M+O,a[1][0])),u&&C<0?t=Xo(w+C+T,a[0][1]):!u&&C>0&&(t=Zo(w+P+T,a[1][1])),_=Math.max(_,e),N=Math.max(N,t)}if(l){let e=0,t=0;s&&E>0?e=Zo(b+E,l[0][0]):!s&&E<0&&(e=Xo(b+M,l[1][0])),u&&C>0?t=Zo(w+C,l[0][1]):!u&&C<0&&(t=Xo(w+P,l[1][1])),_=Math.max(_,e),N=Math.max(N,t)}if(o){if(c){let e=Go(M/S,y,v)*S;if(_=Math.max(_,e),a){let e=0;e=!s&&!u||s&&!u&&f?Zo(w+T+M/S,a[1][1])*S:Xo(w+T+(s?E:-E)/S,a[0][1])*S,_=Math.max(_,e)}if(l){let e=0;e=!s&&!u||s&&!u&&f?Xo(w+M/S,l[1][1])*S:Zo(w+(s?E:-E)/S,l[0][1])*S,_=Math.max(_,e)}}if(d){let e=Go(P*S,m,g)/S;if(N=Math.max(N,e),a){let e=0;e=!s&&!u||u&&!s&&f?Zo(b+P*S+O,a[1][0])/S:Xo(b+(u?C:-C)*S+O,a[0][0])/S,N=Math.max(N,e)}if(l){let e=0;e=!s&&!u||u&&!s&&f?Xo(b+P*S,l[1][0])/S:Zo(b+(u?C:-C)*S,l[0][0])/S,N=Math.max(N,e)}}}C+=C<0?N:-N,E+=E<0?_:-_,o&&(f?M>P*S?C=(Qo(s,u)?-E:E)/S:E=(Qo(s,u)?-C:C)*S:c?(C=E/S,u=s):(E=C*S,s=u));let I=s?b+E:b,z=u?w+C:w;return{width:x+(s?-E:E),height:k+(u?-C:C),x:i[0]*E*(s?-1:1)+I,y:i[1]*C*(u?-1:1)+z}}(m,a.controlDirection,s,a.boundaries,a.keepAspectRatio,M,v,b),N=P!==S,I=O!==E,z=T!==c&&N,R=_!==f&&I;if(!(z||R||N||I))return;if((z||R||1===M[0]||1===M[1])&&(C.x=z?T:h.x,C.y=R?_:h.y,h.x=C.x,h.y=C.y,x.length>0)){let e=T-c,t=_-f;for(let n of x)n.position={x:n.position.x-e+M[0]*(P-S),y:n.position.y-t+M[1]*(O-E)},u.push(n)}if((N||I)&&(C.width=!N||a.resizeDirection&&"horizontal"!==a.resizeDirection?h.width:P,C.height=!I||a.resizeDirection&&"vertical"!==a.resizeDirection?h.height:O,h.width=C.width,h.height=C.height),y&&g.expandParent){let e=M[0]*(C.width??0);C.x&&C.x0?1:a<0?-1:0,l>0?1:l<0?-1:0];return a&&o&&(s[0]*=-1),l&&i&&(s[1]*=-1),s}({width:h.width,prevWidth:S,height:h.height,prevHeight:E,affectsX:a.controlDirection.affectsX,affectsY:a.controlDirection.affectsY}),A={...h,direction:L};!1!==p?.(e,A)&&(k=!0,d?.(e,A),r(C,u))}).on("end",e=>{k&&=(f?.(e,{...h}),o?.({...h}),!1)});i.call(S)},destroy:function(){i.on(".drag",null)}}}({domNode:E.current,nodeId:k,getStoreItems:()=>{let{nodeLookup:e,transform:t,snapGrid:n,snapToGrid:r,nodeOrigin:o,domNode:i}=S.getState();return{nodeLookup:e,transform:t,snapGrid:n,snapToGrid:r,nodeOrigin:o,paneDomNode:i}},onChange:(e,t)=>{let{triggerNodeChanges:n,nodeLookup:r,parentLookup:o,nodeOrigin:i}=S.getState(),a=[],l={x:e.x,y:e.y},s=r.get(k);if(s&&s.expandParent&&s.parentId){let t=s.origin??i,n=e.width??s.measured.width??0,u=e.height??s.measured.height??0,c=To([{id:s.id,parentId:s.parentId,rect:{width:n,height:u,...qr({x:e.x??s.position.x,y:e.y??s.position.y},{width:n,height:u},s.parentId,r,t)}}],r,o,i);a.push(...c),l.x=e.x?Math.max(t[0]*n,e.x):void 0,l.y=e.y?Math.max(t[1]*u,e.y):void 0}if(void 0!==l.x&&void 0!==l.y){let e={id:k,type:"position",position:{...l}};a.push(e)}if(void 0!==e.width&&void 0!==e.height){let t={id:k,type:"dimensions",resizing:!0,setAttributes:!m||("horizontal"===m?"width":"height"),dimensions:{width:e.width,height:e.height}};a.push(t)}for(let e of t){let t={...e,type:"position"};a.push(t)}n(a)},onEnd:({width:e,height:t})=>{let n={id:k,type:"dimensions",resizing:!1,dimensions:{width:e,height:t}};S.getState().triggerNodeChanges([n])}}),P.current.update({controlPosition:O,boundaries:{minWidth:l,minHeight:s,maxWidth:u,maxHeight:c},keepAspectRatio:d,resizeDirection:m,onResizeStart:v,onResize:b,onResizeEnd:w,shouldResize:y}),()=>{P.current?.destroy()}},[O,l,s,u,c,d,v,b,w,y]),(0,p.jsx)("div",{className:h(["react-flow__resize-control","nodrag",...O.split("-"),n,r]),ref:E,style:{...o,scale:M,...a&&{[C?"backgroundColor":"borderColor"]:a}},children:i})}),vs=(...e)=>e.filter((e,t,n)=>!!e&&""!==e.trim()&&n.indexOf(e)===t).join(" ").trim(),bs=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),ws=e=>{let t=(e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,n)=>n?n.toUpperCase():t.toLowerCase()))(e);return t.charAt(0).toUpperCase()+t.slice(1)},xs={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"},ks=e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0;return!1},Ss=(0,f.createContext)({}),Es=(0,f.forwardRef)(({color:e,size:t,strokeWidth:n,absoluteStrokeWidth:r,className:o="",children:i,iconNode:a,...l},s)=>{let{size:u=24,strokeWidth:c=2,absoluteStrokeWidth:d=!1,color:p="currentColor",className:h=""}=(0,f.useContext)(Ss)??{},m=r??d?24*Number(n??c)/Number(t??u):n??c;return(0,f.createElement)("svg",{ref:s,...xs,width:t??u??xs.width,height:t??u??xs.height,stroke:e??p,strokeWidth:m,className:vs("lucide",h,o),...!i&&!ks(l)&&{"aria-hidden":"true"},...l},[...a.map(([e,t])=>(0,f.createElement)(e,t)),...Array.isArray(i)?i:[i]])}),Cs=(e,t)=>{let n=(0,f.forwardRef)(({className:n,...r},o)=>(0,f.createElement)(Es,{ref:o,iconNode:t,className:vs(`lucide-${bs(ws(e))}`,`lucide-${e}`,n),...r}));return n.displayName=ws(e),n},Ms=Cs("activity",[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]]),Ps=Cs("app-window",[["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2",key:"izxlao"}],["path",{d:"M10 4v4",key:"pp8u80"}],["path",{d:"M2 8h20",key:"d11cs7"}],["path",{d:"M6 4v4",key:"1svtjw"}]]),Os=Cs("arrow-down",[["path",{d:"M12 5v14",key:"s699le"}],["path",{d:"m19 12-7 7-7-7",key:"1idqje"}]]),Ts=Cs("arrow-left-right",[["path",{d:"M8 3 4 7l4 4",key:"9rb6wj"}],["path",{d:"M4 7h16",key:"6tx8e3"}],["path",{d:"m16 21 4-4-4-4",key:"siv7j2"}],["path",{d:"M20 17H4",key:"h6l3hr"}]]),_s=Cs("arrow-left",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]),Ns=Cs("arrow-up-right",[["path",{d:"M7 7h10v10",key:"1tivn9"}],["path",{d:"M7 17 17 7",key:"1vkiza"}]]),Is=Cs("arrow-up",[["path",{d:"m5 12 7-7 7 7",key:"hav0vg"}],["path",{d:"M12 19V5",key:"x0mq9r"}]]),zs=Cs("blocks",[["path",{d:"M10 22V7a1 1 0 0 0-1-1H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5a1 1 0 0 0-1-1H2",key:"1ah6g2"}],["rect",{x:"14",y:"2",width:"8",height:"8",rx:"1",key:"88lufb"}]]),Rs=Cs("box",[["path",{d:"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z",key:"hh9hay"}],["path",{d:"m3.3 7 8.7 5 8.7-5",key:"g66t2b"}],["path",{d:"M12 22V12",key:"d0xqtd"}]]),Ls=Cs("boxes",[["path",{d:"M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z",key:"lc1i9w"}],["path",{d:"m7 16.5-4.74-2.85",key:"1o9zyk"}],["path",{d:"m7 16.5 5-3",key:"va8pkn"}],["path",{d:"M7 16.5v5.17",key:"jnp8gn"}],["path",{d:"M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z",key:"8zsnat"}],["path",{d:"m17 16.5-5-3",key:"8arw3v"}],["path",{d:"m17 16.5 4.74-2.85",key:"8rfmw"}],["path",{d:"M17 16.5v5.17",key:"k6z78m"}],["path",{d:"M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z",key:"1xygjf"}],["path",{d:"M12 8 7.26 5.15",key:"1vbdud"}],["path",{d:"m12 8 4.74-2.85",key:"3rx089"}],["path",{d:"M12 13.5V8",key:"1io7kd"}]]),As=Cs("brush",[["path",{d:"m11 10 3 3",key:"fzmg1i"}],["path",{d:"M6.5 21A3.5 3.5 0 1 0 3 17.5a2.62 2.62 0 0 1-.708 1.792A1 1 0 0 0 3 21z",key:"p4q2r7"}],["path",{d:"M9.969 17.031 21.378 5.624a1 1 0 0 0-3.002-3.002L6.967 14.031",key:"wy6l02"}]]),Ds=Cs("check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]),Fs=Cs("chevron-down",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]]),js=Cs("chevron-left",[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]]),Hs=Cs("chevron-right",[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]]),Vs=Cs("chevron-up",[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]]),$s=Cs("circle-alert",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]]),Bs=Cs("circle-check",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]),Ws=Cs("circle-dot",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}]]),qs=Cs("circle-play",[["path",{d:"M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z",key:"kmsa83"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]]),Us=Cs("circle-question-mark",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]]),Ks=Cs("circle-x",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]),Ys=Cs("circle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]]),Xs=Cs("clapperboard",[["path",{d:"m12.296 3.464 3.02 3.956",key:"qash78"}],["path",{d:"M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3z",key:"1h7j8b"}],["path",{d:"M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"4lm6w1"}],["path",{d:"m6.18 5.276 3.1 3.899",key:"zjj9t3"}]]),Zs=Cs("clipboard-copy",[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1",key:"tgr4d6"}],["path",{d:"M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2",key:"4jdomd"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v4",key:"3hqy98"}],["path",{d:"M21 14H11",key:"1bme5i"}],["path",{d:"m15 10-4 4 4 4",key:"5dvupr"}]]),Gs=Cs("clock-3",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 6v6h4",key:"135r8i"}]]),Qs=Cs("clock",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 6v6l4 2",key:"mmk7yg"}]]),Js=Cs("cloud-download",[["path",{d:"M12 13v8l-4-4",key:"1f5nwf"}],["path",{d:"m12 21 4-4",key:"1lfcce"}],["path",{d:"M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284",key:"ui1hmy"}]]),eu=Cs("columns-2",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M12 3v18",key:"108xh3"}]]),tu=Cs("copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]),nu=Cs("cpu",[["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M17 20v2",key:"1rnc9c"}],["path",{d:"M17 2v2",key:"11trls"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M2 17h2",key:"7oei6x"}],["path",{d:"M2 7h2",key:"asdhe0"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"M20 17h2",key:"1fpfkl"}],["path",{d:"M20 7h2",key:"1o8tra"}],["path",{d:"M7 20v2",key:"4gnj0m"}],["path",{d:"M7 2v2",key:"1i4yhu"}],["rect",{x:"4",y:"4",width:"16",height:"16",rx:"2",key:"1vbyd7"}],["rect",{x:"8",y:"8",width:"8",height:"8",rx:"1",key:"z9xiuo"}]]),ru=Cs("database",[["ellipse",{cx:"12",cy:"5",rx:"9",ry:"3",key:"msslwz"}],["path",{d:"M3 5V19A9 3 0 0 0 21 19V5",key:"1wlel7"}],["path",{d:"M3 12A9 3 0 0 0 21 12",key:"mv7ke4"}]]),ou=Cs("download",[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]]),iu=Cs("ellipsis",[["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}],["circle",{cx:"19",cy:"12",r:"1",key:"1wjl8i"}],["circle",{cx:"5",cy:"12",r:"1",key:"1pcz8c"}]]),au=Cs("ellipsis-vertical",[["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}],["circle",{cx:"12",cy:"5",r:"1",key:"gxeob9"}],["circle",{cx:"12",cy:"19",r:"1",key:"lyex9k"}]]),lu=Cs("eraser",[["path",{d:"M21 21H8a2 2 0 0 1-1.42-.587l-3.994-3.999a2 2 0 0 1 0-2.828l10-10a2 2 0 0 1 2.829 0l5.999 6a2 2 0 0 1 0 2.828L12.834 21",key:"g5wo59"}],["path",{d:"m5.082 11.09 8.828 8.828",key:"1wx5vj"}]]),su=Cs("expand",[["path",{d:"m15 15 6 6",key:"1s409w"}],["path",{d:"m15 9 6-6",key:"ko1vev"}],["path",{d:"M21 16v5h-5",key:"1ck2sf"}],["path",{d:"M21 8V3h-5",key:"1qoq8a"}],["path",{d:"M3 16v5h5",key:"1t08am"}],["path",{d:"m3 21 6-6",key:"wwnumi"}],["path",{d:"M3 8V3h5",key:"1ln10m"}],["path",{d:"M9 9 3 3",key:"v551iv"}]]),uu=Cs("external-link",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]]),cu=Cs("file-braces-corner",[["path",{d:"M14 22h4a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v6",key:"14cnrg"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M5 14a1 1 0 0 0-1 1v2a1 1 0 0 1-1 1 1 1 0 0 1 1 1v2a1 1 0 0 0 1 1",key:"sr0ebq"}],["path",{d:"M9 22a1 1 0 0 0 1-1v-2a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-2a1 1 0 0 0-1-1",key:"w793db"}]]),du=Cs("file-clock",[["path",{d:"M16 22h2a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v2.85",key:"ryk6xj"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M8 14v2.2l1.6 1",key:"6m4bie"}],["circle",{cx:"8",cy:"16",r:"6",key:"10v15b"}]]),fu=Cs("file-down",[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M12 18v-6",key:"17g6i2"}],["path",{d:"m9 15 3 3 3-3",key:"1npd3o"}]]),pu=Cs("file-plus-corner",[["path",{d:"M11.35 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v5.35",key:"17jvcc"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M14 19h6",key:"bvotb8"}],["path",{d:"M17 16v6",key:"18yu1i"}]]),hu=Cs("file-text",[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]]),mu=Cs("files",[["path",{d:"M15 2h-4a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V8",key:"14sh0y"}],["path",{d:"M16.706 2.706A2.4 2.4 0 0 0 15 2v5a1 1 0 0 0 1 1h5a2.4 2.4 0 0 0-.706-1.706z",key:"1970lx"}],["path",{d:"M5 7a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h8a2 2 0 0 0 1.732-1",key:"l4dndm"}]]),gu=Cs("film",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M7 3v18",key:"bbkbws"}],["path",{d:"M3 7.5h4",key:"zfgn84"}],["path",{d:"M3 12h18",key:"1i2n21"}],["path",{d:"M3 16.5h4",key:"1230mu"}],["path",{d:"M17 3v18",key:"in4fa5"}],["path",{d:"M17 7.5h4",key:"myr1c1"}],["path",{d:"M17 16.5h4",key:"go4c1d"}]]),yu=Cs("flask-conical",[["path",{d:"M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2",key:"18mbvz"}],["path",{d:"M6.453 15h11.094",key:"3shlmq"}],["path",{d:"M8.5 2h7",key:"csnxdl"}]]),vu=Cs("folder-open",[["path",{d:"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",key:"usdka0"}]]),bu=Cs("folder-search",[["path",{d:"M10.7 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4.1",key:"1bw5m7"}],["path",{d:"m21 21-1.9-1.9",key:"1g2n9r"}],["circle",{cx:"17",cy:"17",r:"3",key:"18b49y"}]]),wu=Cs("folder",[["path",{d:"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",key:"1kt360"}]]),xu=Cs("funnel",[["path",{d:"M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z",key:"sc7q7i"}]]),ku=Cs("gallery-vertical-end",[["path",{d:"M7 2h10",key:"nczekb"}],["path",{d:"M5 6h14",key:"u2x4p"}],["rect",{width:"18",height:"12",x:"3",y:"10",rx:"2",key:"l0tzu3"}]]),Su=Cs("gauge",[["path",{d:"m12 14 4-4",key:"9kzdfg"}],["path",{d:"M3.34 19a10 10 0 1 1 17.32 0",key:"19p75a"}]]),Eu=Cs("git-branch",[["path",{d:"M15 6a9 9 0 0 0-9 9V3",key:"1cii5b"}],["circle",{cx:"18",cy:"6",r:"3",key:"1h7g24"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}]]),Cu=Cs("git-compare-arrows",[["circle",{cx:"5",cy:"6",r:"3",key:"1qnov2"}],["path",{d:"M12 6h5a2 2 0 0 1 2 2v7",key:"1yj91y"}],["path",{d:"m15 9-3-3 3-3",key:"1lwv8l"}],["circle",{cx:"19",cy:"18",r:"3",key:"1qljk2"}],["path",{d:"M12 18H7a2 2 0 0 1-2-2V9",key:"16sdep"}],["path",{d:"m9 15 3 3-3 3",key:"1m3kbl"}]]),Mu=Cs("grid-2x2",[["path",{d:"M12 3v18",key:"108xh3"}],["path",{d:"M3 12h18",key:"1i2n21"}],["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2",key:"h1oib"}]]),Pu=Cs("hard-drive",[["path",{d:"M10 16h.01",key:"1bzywj"}],["path",{d:"M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z",key:"18tbho"}],["path",{d:"M21.946 12.013H2.054",key:"zqlbp7"}],["path",{d:"M6 16h.01",key:"1pmjb7"}]]),Ou=Cs("heart",[["path",{d:"M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",key:"mvr1a0"}]]),Tu=Cs("history",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]]),_u=Cs("house",[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"r6nss1"}]]),Nu=Cs("image-plus",[["path",{d:"M16 5h6",key:"1vod17"}],["path",{d:"M19 2v6",key:"4bpg5p"}],["path",{d:"M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5",key:"1ue2ih"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}]]),Iu=Cs("image-up",[["path",{d:"M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21",key:"9csbqa"}],["path",{d:"m14 19.5 3-3 3 3",key:"9vmjn0"}],["path",{d:"M17 22v-5.5",key:"1aa6fl"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}]]),zu=Cs("image",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]]),Ru=Cs("images",[["path",{d:"m22 11-1.296-1.296a2.4 2.4 0 0 0-3.408 0L11 16",key:"9kzy35"}],["path",{d:"M4 8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2",key:"1t0f0t"}],["circle",{cx:"13",cy:"7",r:"1",fill:"currentColor",key:"1obus6"}],["rect",{x:"8",y:"2",width:"14",height:"14",rx:"2",key:"1gvhby"}]]),Lu=Cs("info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]),Au=Cs("key-round",[["path",{d:"M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z",key:"1s6t7t"}],["circle",{cx:"16.5",cy:"7.5",r:".5",fill:"currentColor",key:"w0ekpg"}]]),Du=Cs("layers",[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]]),Fu=Cs("layout-grid",[["rect",{width:"7",height:"7",x:"3",y:"3",rx:"1",key:"1g98yp"}],["rect",{width:"7",height:"7",x:"14",y:"3",rx:"1",key:"6d4xhi"}],["rect",{width:"7",height:"7",x:"14",y:"14",rx:"1",key:"nxv5o0"}],["rect",{width:"7",height:"7",x:"3",y:"14",rx:"1",key:"1bb6yr"}]]),ju=Cs("layout-template",[["rect",{width:"18",height:"7",x:"3",y:"3",rx:"1",key:"f1a2em"}],["rect",{width:"9",height:"7",x:"3",y:"14",rx:"1",key:"jqznyg"}],["rect",{width:"5",height:"7",x:"16",y:"14",rx:"1",key:"q5h2i8"}]]),Hu=Cs("library",[["path",{d:"m16 6 4 14",key:"ji33uf"}],["path",{d:"M12 6v14",key:"1n7gus"}],["path",{d:"M8 8v12",key:"1gg7y9"}],["path",{d:"M4 4v16",key:"6qkkli"}]]),Vu=Cs("list-checks",[["path",{d:"M13 5h8",key:"a7qcls"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 19h8",key:"c3s6r1"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"m3 7 2 2 4-4",key:"1obspn"}]]),$u=Cs("list-plus",[["path",{d:"M16 5H3",key:"m91uny"}],["path",{d:"M11 12H3",key:"51ecnj"}],["path",{d:"M16 19H3",key:"zzsher"}],["path",{d:"M18 9v6",key:"1twb98"}],["path",{d:"M21 12h-6",key:"bt1uis"}]]),Bu=Cs("loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]),Wu=Cs("log-out",[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]]),qu=Cs("maximize-2",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"m21 3-7 7",key:"1l2asr"}],["path",{d:"m3 21 7-7",key:"tjx5ai"}],["path",{d:"M9 21H3v-6",key:"wtvkvv"}]]),Uu=Cs("maximize",[["path",{d:"M8 3H5a2 2 0 0 0-2 2v3",key:"1dcmit"}],["path",{d:"M21 8V5a2 2 0 0 0-2-2h-3",key:"1e4gt3"}],["path",{d:"M3 16v3a2 2 0 0 0 2 2h3",key:"wsl5sc"}],["path",{d:"M16 21h3a2 2 0 0 0 2-2v-3",key:"18trek"}]]),Ku=Cs("memory-stick",[["path",{d:"M12 12v-2",key:"fwoke6"}],["path",{d:"M12 18v-2",key:"qj6yno"}],["path",{d:"M16 12v-2",key:"heuere"}],["path",{d:"M16 18v-2",key:"s1ct0w"}],["path",{d:"M2 11h1.5",key:"15p63e"}],["path",{d:"M20 18v-2",key:"12ehxp"}],["path",{d:"M20.5 11H22",key:"khsy7a"}],["path",{d:"M4 18v-2",key:"1c3oqr"}],["path",{d:"M8 12v-2",key:"1mwtfd"}],["path",{d:"M8 18v-2",key:"qcmpov"}],["rect",{x:"2",y:"6",width:"20",height:"10",rx:"2",key:"1qcswk"}]]),Yu=Cs("minimize-2",[["path",{d:"m14 10 7-7",key:"oa77jy"}],["path",{d:"M20 10h-6V4",key:"mjg0md"}],["path",{d:"m3 21 7-7",key:"tjx5ai"}],["path",{d:"M4 14h6v6",key:"rmj7iw"}]]),Xu=Cs("minus",[["path",{d:"M5 12h14",key:"1ays0h"}]]),Zu=Cs("music-2",[["circle",{cx:"8",cy:"18",r:"4",key:"1fc0mg"}],["path",{d:"M12 18V2l7 4",key:"g04rme"}]]),Gu=Cs("music",[["path",{d:"M9 18V5l12-2v13",key:"1jmyc2"}],["circle",{cx:"6",cy:"18",r:"3",key:"fqmcym"}],["circle",{cx:"18",cy:"16",r:"3",key:"1hluhg"}]]),Qu=Cs("network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]),Ju=Cs("octagon",[["path",{d:"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z",key:"2d38gg"}]]),ec=Cs("package-open",[["path",{d:"M12 22v-9",key:"x3hkom"}],["path",{d:"M15.17 2.21a1.67 1.67 0 0 1 1.63 0L21 4.57a1.93 1.93 0 0 1 0 3.36L8.82 14.79a1.655 1.655 0 0 1-1.64 0L3 12.43a1.93 1.93 0 0 1 0-3.36z",key:"2ntwy6"}],["path",{d:"M20 13v3.87a2.06 2.06 0 0 1-1.11 1.83l-6 3.08a1.93 1.93 0 0 1-1.78 0l-6-3.08A2.06 2.06 0 0 1 4 16.87V13",key:"1pmm1c"}],["path",{d:"M21 12.43a1.93 1.93 0 0 0 0-3.36L8.83 2.2a1.64 1.64 0 0 0-1.63 0L3 4.57a1.93 1.93 0 0 0 0 3.36l12.18 6.86a1.636 1.636 0 0 0 1.63 0z",key:"12ttoo"}]]),tc=Cs("package",[["path",{d:"M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z",key:"1a0edw"}],["path",{d:"M12 22V12",key:"d0xqtd"}],["polyline",{points:"3.29 7 12 12 20.71 7",key:"ousv84"}],["path",{d:"m7.5 4.27 9 5.15",key:"1c824w"}]]),nc=Cs("paintbrush",[["path",{d:"m14.622 17.897-10.68-2.913",key:"vj2p1u"}],["path",{d:"M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z",key:"18tc5c"}],["path",{d:"M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15",key:"ytzfxy"}]]),rc=Cs("panel-right-close",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M15 3v18",key:"14nvp0"}],["path",{d:"m8 9 3 3-3 3",key:"12hl5m"}]]),oc=Cs("panel-right-open",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M15 3v18",key:"14nvp0"}],["path",{d:"m10 15-3-3 3-3",key:"1pgupc"}]]),ic=Cs("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]),ac=Cs("pin-off",[["path",{d:"M12 17v5",key:"bb1du9"}],["path",{d:"M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89",key:"znwnzq"}],["path",{d:"m2 2 20 20",key:"1ooewy"}],["path",{d:"M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11",key:"c9qhm2"}]]),lc=Cs("pin",[["path",{d:"M12 17v5",key:"bb1du9"}],["path",{d:"M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z",key:"1nkz8b"}]]),sc=Cs("play",[["path",{d:"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z",key:"10ikf1"}]]),uc=Cs("plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]),cc=Cs("power",[["path",{d:"M12 2v10",key:"mnfbl"}],["path",{d:"M18.4 6.6a9 9 0 1 1-12.77.04",key:"obofu9"}]]),dc=Cs("refresh-ccw",[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"14sxne"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16",key:"1hlbsb"}],["path",{d:"M16 16h5v5",key:"ccwih5"}]]),fc=Cs("refresh-cw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]),pc=Cs("repeat-2",[["path",{d:"m2 9 3-3 3 3",key:"1ltn5i"}],["path",{d:"M13 18H7a2 2 0 0 1-2-2V6",key:"1r6tfw"}],["path",{d:"m22 15-3 3-3-3",key:"4rnwn2"}],["path",{d:"M11 6h6a2 2 0 0 1 2 2v10",key:"2f72bc"}]]),hc=Cs("repeat",[["path",{d:"m17 2 4 4-4 4",key:"nntrym"}],["path",{d:"M3 11v-1a4 4 0 0 1 4-4h14",key:"84bu3i"}],["path",{d:"m7 22-4-4 4-4",key:"1wqhfi"}],["path",{d:"M21 13v1a4 4 0 0 1-4 4H3",key:"1rx37r"}]]),mc=Cs("rocket",[["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}],["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09",key:"u4xsad"}],["path",{d:"M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z",key:"676m9"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05",key:"92ym6u"}]]),gc=Cs("rotate-ccw",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]]),yc=Cs("rotate-cw",[["path",{d:"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8",key:"1p45f6"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}]]),vc=Cs("save-all",[["path",{d:"M10 2v3a1 1 0 0 0 1 1h5",key:"1xspal"}],["path",{d:"M18 18v-6a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6",key:"1ra60u"}],["path",{d:"M18 22H4a2 2 0 0 1-2-2V6",key:"pblm9e"}],["path",{d:"M8 18a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9.172a2 2 0 0 1 1.414.586l2.828 2.828A2 2 0 0 1 22 6.828V16a2 2 0 0 1-2.01 2z",key:"1yve0x"}]]),bc=Cs("save",[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",key:"1c8476"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7",key:"1ydtos"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7",key:"t51u73"}]]),wc=Cs("scan-line",[["path",{d:"M3 7V5a2 2 0 0 1 2-2h2",key:"aa7l1z"}],["path",{d:"M17 3h2a2 2 0 0 1 2 2v2",key:"4qcy5o"}],["path",{d:"M21 17v2a2 2 0 0 1-2 2h-2",key:"6vwrx8"}],["path",{d:"M7 21H5a2 2 0 0 1-2-2v-2",key:"ioqczr"}],["path",{d:"M7 12h10",key:"b7w52i"}]]),xc=Cs("search",[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]]),kc=Cs("server",[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]]),Sc=Cs("settings-2",[["path",{d:"M14 17H5",key:"gfn3mx"}],["path",{d:"M19 7h-9",key:"6i9tg"}],["circle",{cx:"17",cy:"17",r:"3",key:"18b49y"}],["circle",{cx:"7",cy:"7",r:"3",key:"dfmy0x"}]]),Ec=Cs("settings",[["path",{d:"M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915",key:"1i5ecw"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]),Cc=Cs("shield-check",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]),Mc=Cs("shuffle",[["path",{d:"m18 14 4 4-4 4",key:"10pe0f"}],["path",{d:"m18 2 4 4-4 4",key:"pucp1d"}],["path",{d:"M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22",key:"1ailkh"}],["path",{d:"M2 6h1.972a4 4 0 0 1 3.6 2.2",key:"km57vx"}],["path",{d:"M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45",key:"os18l9"}]]),Pc=Cs("sliders-horizontal",[["path",{d:"M10 5H3",key:"1qgfaw"}],["path",{d:"M12 19H3",key:"yhmn1j"}],["path",{d:"M14 3v4",key:"1sua03"}],["path",{d:"M16 17v4",key:"1q0r14"}],["path",{d:"M21 12h-9",key:"1o4lsq"}],["path",{d:"M21 19h-5",key:"1rlt1p"}],["path",{d:"M21 5h-7",key:"1oszz2"}],["path",{d:"M8 10v4",key:"tgpxqk"}],["path",{d:"M8 12H3",key:"a7s4jb"}]]),Oc=Cs("sparkles",[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]]),Tc=Cs("square-play",[["rect",{x:"3",y:"3",width:"18",height:"18",rx:"2",key:"h1oib"}],["path",{d:"M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z",key:"kmsa83"}]]),_c=Cs("square",[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}]]),Nc=Cs("trash-2",[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]]),Ic=Cs("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]),zc=Cs("truck",[["path",{d:"M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2",key:"wrbu53"}],["path",{d:"M15 18H9",key:"1lyqi6"}],["path",{d:"M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14",key:"lysw3i"}],["circle",{cx:"17",cy:"18",r:"2",key:"332jqn"}],["circle",{cx:"7",cy:"18",r:"2",key:"19iecd"}]]),Rc=Cs("type",[["path",{d:"M12 4v16",key:"1654pz"}],["path",{d:"M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2",key:"e0r10z"}],["path",{d:"M9 20h6",key:"s66wpe"}]]),Lc=Cs("ungroup",[["rect",{width:"8",height:"6",x:"5",y:"4",rx:"1",key:"nzclkv"}],["rect",{width:"8",height:"6",x:"11",y:"14",rx:"1",key:"4tytwb"}]]),Ac=Cs("upload",[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]]),Dc=Cs("user",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]),Fc=Cs("video",[["path",{d:"m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5",key:"ftymec"}],["rect",{x:"2",y:"6",width:"14",height:"12",rx:"2",key:"158x01"}]]),jc=Cs("wand-sparkles",[["path",{d:"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72",key:"ul74o6"}],["path",{d:"m14 7 3 3",key:"1r5n42"}],["path",{d:"M5 6v4",key:"ilb8ba"}],["path",{d:"M19 14v4",key:"blhpug"}],["path",{d:"M10 2v2",key:"7u0qdc"}],["path",{d:"M7 8H3",key:"zfb6yr"}],["path",{d:"M21 16h-4",key:"1cnmox"}],["path",{d:"M11 3H9",key:"1obp7u"}]]),Hc=Cs("webhook",[["path",{d:"M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2",key:"q3hayz"}],["path",{d:"m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06",key:"1go1hn"}],["path",{d:"m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8",key:"qlwsc0"}]]),Vc=Cs("wifi-off",[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}],["path",{d:"M5 12.859a10 10 0 0 1 5.17-2.69",key:"1dl1wf"}],["path",{d:"M19 12.859a10 10 0 0 0-2.007-1.523",key:"4k23kn"}],["path",{d:"M2 8.82a15 15 0 0 1 4.177-2.643",key:"1grhjp"}],["path",{d:"M22 8.82a15 15 0 0 0-11.288-3.764",key:"z3jwby"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]]),$c=Cs("wifi",[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]]),Bc=Cs("workflow",[["rect",{width:"8",height:"8",x:"3",y:"3",rx:"2",key:"by2w9f"}],["path",{d:"M7 11v4a2 2 0 0 0 2 2h4",key:"xkn7yn"}],["rect",{width:"8",height:"8",x:"13",y:"13",rx:"2",key:"1cgmvn"}]]),Wc=Cs("wrench",[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",key:"1ngwbx"}]]),qc=Cs("x",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]),Uc=e=>e?.ownerDocument??document,Kc=e=>e&&"window"in e&&e.window===e?e:Uc(e).defaultView||window;function Yc(e,t){return!(!t||!e)&&e.contains(t)}var Xc=(e=document)=>e.activeElement;function Zc(e){return e.target}var Gc=null,Qc=typeof document<"u"?f.useLayoutEffect:()=>{};function Jc(e){let t=e;return t.nativeEvent=e,t.isDefaultPrevented=()=>t.defaultPrevented,t.isPropagationStopped=()=>t.cancelBubble,t.persist=()=>{},t}function ed(e){let t=(0,f.useRef)({isFocused:!1,observer:null});return Qc(()=>{let e=t.current;return()=>{e.observer&&=(e.observer.disconnect(),null)}},[]),(0,f.useCallback)(n=>{let r=Zc(n);if(r instanceof HTMLButtonElement||r instanceof HTMLInputElement||r instanceof HTMLTextAreaElement||r instanceof HTMLSelectElement){t.current.isFocused=!0;let n=r;n.addEventListener("focusout",r=>{if(t.current.isFocused=!1,n.disabled){let t=Jc(r);e?.(t)}t.current.observer&&(t.current.observer.disconnect(),t.current.observer=null)},{once:!0}),t.current.observer=new MutationObserver(()=>{if(t.current.isFocused&&n.disabled){t.current.observer?.disconnect();let e=n===Xc()?null:Xc();n.dispatchEvent(new FocusEvent("blur",{relatedTarget:e})),n.dispatchEvent(new FocusEvent("focusout",{bubbles:!0,relatedTarget:e}))}}),t.current.observer.observe(n,{attributes:!0,attributeFilter:["disabled"]})}},[e])}function td(e){if(typeof window>"u"||null==window.navigator)return!1;let t=window.navigator.userAgentData?.brands;return Array.isArray(t)&&t.some(t=>e.test(t.brand))||e.test(window.navigator.userAgent)}function nd(e){return typeof window<"u"&&null!=window.navigator&&e.test(window.navigator.userAgentData?.platform||window.navigator.platform)}function rd(e){let t=null;return()=>(t??=e(),t)}var od=rd(function(){return nd(/^Mac/i)}),id=rd(function(){return nd(/^iPad/i)||od()&&navigator.maxTouchPoints>1}),ad=rd(function(){return td(/AppleWebKit/i)&&!ld()}),ld=rd(function(){return td(/Chrome/i)}),sd=rd(function(){return td(/Android/i)}),ud=rd(function(){return td(/Firefox/i)});function cd(e,t,n=!0){let{metaKey:r,ctrlKey:o,altKey:i,shiftKey:a}=t;ud()&&window.event?.type?.startsWith("key")&&"_blank"===e.target&&(od()?r=!0:o=!0);let l=ad()&&od()&&!id()?new KeyboardEvent("keydown",{keyIdentifier:"Enter",metaKey:r,ctrlKey:o,altKey:i,shiftKey:a}):new MouseEvent("click",{metaKey:r,ctrlKey:o,altKey:i,shiftKey:a,detail:1,bubbles:!0,cancelable:!0});cd.isOpening=n,function(e){if(function(){if(null==Gc){Gc=!1;try{document.createElement("div").focus({get preventScroll(){return Gc=!0,!0}})}catch{}}return Gc}())e.focus({preventScroll:!0});else{let t=function(e){let t=e.parentNode,n=[],r=document.scrollingElement||document.documentElement;for(;t instanceof HTMLElement&&t!==r;)(t.offsetHeight"u"||typeof document>"u")return;let t=Kc(e),n=Uc(e);if(pd.get(t))return;let r=t.HTMLElement.prototype.focus;t.HTMLElement.prototype.focus=function(){hd=!0,r.apply(this,arguments)},n.addEventListener("keydown",vd,!0),n.addEventListener("keyup",vd,!0),n.addEventListener("click",wd,!0),t.addEventListener("focus",xd,!0),t.addEventListener("blur",kd,!1),typeof PointerEvent<"u"&&(n.addEventListener("pointerdown",bd,!0),n.addEventListener("pointermove",bd,!0),n.addEventListener("pointerup",bd,!0)),t.addEventListener("beforeunload",()=>{Ed(e)},{once:!0}),pd.set(t,{focus:r})}var Ed=(e,t)=>{let n=Kc(e),r=Uc(e);t&&r.removeEventListener("DOMContentLoaded",t),pd.has(n)&&(n.HTMLElement.prototype.focus=pd.get(n).focus,r.removeEventListener("keydown",vd,!0),r.removeEventListener("keyup",vd,!0),r.removeEventListener("click",wd,!0),n.removeEventListener("focus",xd,!0),n.removeEventListener("blur",kd,!1),typeof PointerEvent<"u"&&(r.removeEventListener("pointerdown",bd,!0),r.removeEventListener("pointermove",bd,!0),r.removeEventListener("pointerup",bd,!0)),pd.delete(n))};function Cd(){return"pointer"!==dd}typeof document<"u"&&function(e){let t,n=Uc(e);"loading"===n.readyState?(t=()=>{Sd(e)},n.addEventListener("DOMContentLoaded",t)):Sd(e)}();var Md=new Set(["checkbox","radio","range","color","file","image","button","submit","reset"]);function Pd(){let e=(0,f.useRef)(new Map),t=(0,f.useCallback)((t,n,r,o)=>{let i=o?.once?(...t)=>{e.current.delete(r),r(...t)}:r;e.current.set(r,{type:n,eventTarget:t,fn:i,options:o}),t.addEventListener(n,i,o)},[]),n=(0,f.useCallback)((t,n,r,o)=>{let i=e.current.get(r)?.fn||r;t.removeEventListener(n,i,o),e.current.delete(r)},[]),r=(0,f.useCallback)(()=>{e.current.forEach((e,t)=>{n(e.eventTarget,e.type,t,e.options)})},[n]);return(0,f.useEffect)(()=>r,[r]),{addGlobalListener:t,removeGlobalListener:n,removeAllGlobalListeners:r}}function Od(e={}){let{autoFocus:t=!1,isTextInput:n,within:r}=e,o=(0,f.useRef)({isFocused:!1,isFocusVisible:t||Cd()}),[i,a]=(0,f.useState)(!1),[l,s]=(0,f.useState)(()=>o.current.isFocused&&o.current.isFocusVisible),u=(0,f.useCallback)(()=>s(o.current.isFocused&&o.current.isFocusVisible),[]),c=(0,f.useCallback)(e=>{o.current.isFocused=e,o.current.isFocusVisible=Cd(),a(e),u()},[u]);!function(e,t,n){Sd(),(0,f.useEffect)(()=>{if(!1===n?.enabled)return;let e=(e,t)=>{(function(e,t,n){let r=n?Zc(n):void 0,o=Uc(r),i=Kc(r),a=void 0===i?HTMLInputElement:i.HTMLInputElement,l=void 0===i?HTMLTextAreaElement:i.HTMLTextAreaElement,s=void 0===i?HTMLElement:i.HTMLElement,u=void 0===i?KeyboardEvent:i.KeyboardEvent,c=Xc(o);return!((e=e||c instanceof a&&!Md.has(c.type)||c instanceof l||c instanceof s&&c.isContentEditable)&&"keyboard"===t&&n instanceof u&&!gd[n.key])})(!!n?.isTextInput,e,t)&&(e=>{o.current.isFocusVisible=e,u()})(Cd())};return fd.add(e),()=>{fd.delete(e)}},t)}(0,[n,i],{enabled:i,isTextInput:n});let{focusProps:d}=function(e){let{isDisabled:t,onFocus:n,onBlur:r,onFocusChange:o}=e,i=(0,f.useCallback)(e=>{if(Zc(e)===e.currentTarget)return r&&r(e),o&&o(!1),!0},[r,o]),a=ed(i),l=(0,f.useCallback)(e=>{let t=Zc(e),r=Uc(t),i=r?Xc(r):Xc();t===e.currentTarget&&t===i&&(n&&n(e),o&&o(!0),a(e))},[o,n,a]);return{focusProps:{onFocus:!t&&(n||o||r)?l:void 0,onBlur:t||!r&&!o?void 0:i}}}({isDisabled:r,onFocusChange:c}),{focusWithinProps:p}=function(e){let{isDisabled:t,onBlurWithin:n,onFocusWithin:r,onFocusWithinChange:o}=e,i=(0,f.useRef)({isFocusWithin:!1}),{addGlobalListener:a,removeAllGlobalListeners:l}=Pd(),s=(0,f.useCallback)(e=>{Yc(e.currentTarget,Zc(e))&&i.current.isFocusWithin&&!Yc(e.currentTarget,e.relatedTarget)&&(i.current.isFocusWithin=!1,l(),n&&n(e),o&&o(!1))},[n,o,i,l]),u=ed(s),c=(0,f.useCallback)(e=>{if(!Yc(e.currentTarget,Zc(e)))return;let t=Zc(e),n=Uc(t),l=Xc(n);if(!i.current.isFocusWithin&&l===t){r&&r(e),o&&o(!0),i.current.isFocusWithin=!0,u(e);let t=e.currentTarget;a(n,"focus",e=>{let r=Zc(e);if(i.current.isFocusWithin&&!Yc(t,r)){let e=new n.defaultView.FocusEvent("blur",{relatedTarget:r});!function(e,t){Object.defineProperty(e,"target",{value:t}),Object.defineProperty(e,"currentTarget",{value:t})}(e,t);let o=Jc(e);s(o)}},{capture:!0})}},[r,o,u,a,s]);return t?{focusWithinProps:{onFocus:void 0,onBlur:void 0}}:{focusWithinProps:{onFocus:c,onBlur:s}}}({isDisabled:!r,onFocusWithinChange:c});return{isFocused:i,isFocusVisible:l,focusProps:r?p:d}}var Td=!1,_d=0;function Nd(e){"touch"===e.pointerType&&(Td=!0,setTimeout(()=>{Td=!1},500))}function Id(){let e=Uc(null);if(void 0!==e)return 0===_d&&typeof PointerEvent<"u"&&e.addEventListener("pointerup",Nd),_d++,()=>{!(--_d>0)&&typeof PointerEvent<"u"&&e.removeEventListener("pointerup",Nd)}}function zd(e){let{onHoverStart:t,onHoverChange:n,onHoverEnd:r,isDisabled:o}=e,[i,a]=(0,f.useState)(!1),l=(0,f.useRef)({isHovered:!1,ignoreEmulatedMouseEvents:!1,pointerType:"",target:null}).current;(0,f.useEffect)(Id,[]);let{addGlobalListener:s,removeAllGlobalListeners:u}=Pd(),{hoverProps:c,triggerHoverEnd:d}=(0,f.useMemo)(()=>{let e=(e,r)=>{if(l.pointerType=r,o||"touch"===r||l.isHovered||!Yc(e.currentTarget,Zc(e)))return;l.isHovered=!0;let u=e.currentTarget;l.target=u,s(Uc(Zc(e)),"pointerover",e=>{l.isHovered&&l.target&&!Yc(l.target,Zc(e))&&i(e,e.pointerType)},{capture:!0}),t&&t({type:"hoverstart",target:u,pointerType:r}),n&&n(!0),a(!0)},i=(e,t)=>{let o=l.target;l.pointerType="",l.target=null,"touch"!==t&&l.isHovered&&o&&(l.isHovered=!1,u(),r&&r({type:"hoverend",target:o,pointerType:t}),n&&n(!1),a(!1))},c={};return typeof PointerEvent<"u"&&(c.onPointerEnter=t=>{Td&&"mouse"===t.pointerType||e(t,t.pointerType)},c.onPointerLeave=e=>{!o&&Yc(e.currentTarget,Zc(e))&&i(e,e.pointerType)}),{hoverProps:c,triggerHoverEnd:i}},[t,n,r,o,l,s,u]);return(0,f.useEffect)(()=>{o&&d({currentTarget:l.target},l.pointerType)},[o]),{hoverProps:c,isHovered:i}}var Rd=Object.defineProperty,Ld=(e,t,n)=>(((e,t,n)=>{t in e?Rd(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n})(e,"symbol"==typeof t?t:t+"",n),n),Ad=new class{constructor(){Ld(this,"current",this.detect()),Ld(this,"handoffState","pending"),Ld(this,"currentId",0)}set(e){this.current!==e&&(this.handoffState="pending",this.currentId=0,this.current=e)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return"server"===this.current}get isClient(){return"client"===this.current}detect(){return typeof window>"u"||typeof document>"u"?"server":"client"}handoff(){"pending"===this.handoffState&&(this.handoffState="complete")}get isHandoffComplete(){return"complete"===this.handoffState}};function Dd(e){return Ad.isServer?null:null==e?document:e?.ownerDocument??document}function Fd(e){return Ad.isServer?null:null==e?document:(e?.getRootNode)?.call(e)??document}function jd(e){return Fd(e)?.activeElement??null}function Hd(e){return jd(e)===e}function Vd(e){"function"==typeof queueMicrotask?queueMicrotask(e):Promise.resolve().then(e).catch(e=>setTimeout(()=>{throw e}))}function $d(){let e=[],t={addEventListener:(e,n,r,o)=>(e.addEventListener(n,r,o),t.add(()=>e.removeEventListener(n,r,o))),requestAnimationFrame(...e){let n=requestAnimationFrame(...e);return t.add(()=>cancelAnimationFrame(n))},nextFrame:(...e)=>t.requestAnimationFrame(()=>t.requestAnimationFrame(...e)),setTimeout(...e){let n=setTimeout(...e);return t.add(()=>clearTimeout(n))},microTask(...e){let n={current:!0};return Vd(()=>{n.current&&e[0]()}),t.add(()=>{n.current=!1})},style(e,t,n){let r=e.style.getPropertyValue(t);return Object.assign(e.style,{[t]:n}),this.add(()=>{Object.assign(e.style,{[t]:r})})},group(e){let t=$d();return e(t),this.add(()=>t.dispose())},add:t=>(e.includes(t)||e.push(t),()=>{let n=e.indexOf(t);if(n>=0)for(let t of e.splice(n,1))t()}),dispose(){for(let t of e.splice(0))t()}};return t}function Bd(){let[e]=(0,f.useState)($d);return(0,f.useEffect)(()=>()=>e.dispose(),[e]),e}var Wd=(e,t)=>{Ad.isServer?(0,f.useEffect)(e,t):(0,f.useLayoutEffect)(e,t)};function qd(e){let t=(0,f.useRef)(e);return Wd(()=>{t.current=e},[e]),t}var Ud=function(e){let t=qd(e);return f.useCallback((...e)=>t.current(...e),[t])};function Kd({disabled:e=!1}={}){let t=(0,f.useRef)(null),[n,r]=(0,f.useState)(!1),o=Bd(),i=Ud(()=>{t.current=null,r(!1),o.dispose()}),a=Ud(e=>{if(o.dispose(),null===t.current){t.current=e.currentTarget,r(!0);{let n=Dd(e.currentTarget);o.addEventListener(n,"pointerup",i,!1),o.addEventListener(n,"pointermove",e=>{if(t.current){let n=function(e){let t=e.width/2,n=e.height/2;return{top:e.clientY-n,right:e.clientX+t,bottom:e.clientY+n,left:e.clientX-t}}(e);r(function(e,t){return!(!e||!t||e.rightt.right||e.bottomt.bottom)}(n,t.current.getBoundingClientRect()))}},!1),o.addEventListener(n,"pointercancel",i,!1)}}});return{pressed:n,pressProps:e?{}:{onPointerDown:a,onPointerUp:i,onClick:i}}}function Yd(e){return(0,f.useMemo)(()=>e,Object.values(e))}var Xd=(0,f.createContext)(void 0);function Zd(){return(0,f.useContext)(Xd)}function Gd(...e){return Array.from(new Set(e.flatMap(e=>"string"==typeof e?e.split(" "):[]))).filter(Boolean).join(" ")}function Qd(e,t,...n){if(e in t){let r=t[e];return"function"==typeof r?r(...n):r}let r=Error(`Tried to handle "${e}" but there is no handler defined. Only defined handlers are: ${Object.keys(t).map(e=>`"${e}"`).join(", ")}.`);throw Error.captureStackTrace&&Error.captureStackTrace(r,Qd),r}var Jd=(e=>(e[e.None=0]="None",e[e.RenderStrategy=1]="RenderStrategy",e[e.Static=2]="Static",e))(Jd||{}),ef=(e=>(e[e.Unmount=0]="Unmount",e[e.Hidden=1]="Hidden",e))(ef||{});function tf(){let e=function(){let e=(0,f.useRef)([]),t=(0,f.useCallback)(t=>{for(let n of e.current)null!=n&&("function"==typeof n?n(t):n.current=t)},[]);return(...n)=>{if(!n.every(e=>null==e))return e.current=n,t}}();return(0,f.useCallback)(t=>function({ourProps:e,theirProps:t,slot:n,defaultTag:r,features:o,visible:i=!0,name:a,mergeRefs:l}){l??=rf;let s=of(t,e);if(i)return nf(s,n,r,a,l);let u=o??0;if(2&u){let{static:e=!1,...t}=s;if(e)return nf(t,n,r,a,l)}if(1&u){let{unmount:e=!0,...t}=s;return Qd(+!e,{0:()=>null,1:()=>nf({...t,hidden:!0,style:{display:"none"}},n,r,a,l)})}return nf(s,n,r,a,l)}({mergeRefs:e,...t}),[e])}function nf(e,t={},n,r,o){let{as:i=n,children:a,refName:l="ref",...s}=uf(e,["unmount","static"]),u=void 0===e.ref?{}:{[l]:e.ref},c="function"==typeof a?a(t):a;c=df(c),"className"in s&&s.className&&"function"==typeof s.className&&(s.className=s.className(t)),s["aria-labelledby"]&&s["aria-labelledby"]===s.id&&(s["aria-labelledby"]=void 0);let d={};if(t){let e=!1,n=[];for(let[r,o]of Object.entries(t))"boolean"==typeof o&&(e=!0),!0===o&&n.push(r.replace(/([A-Z])/g,e=>`-${e.toLowerCase()}`));if(e){d["data-headlessui-state"]=n.join(" ");for(let e of n)d[`data-${e}`]=""}}if(ff(i)&&(Object.keys(sf(s)).length>0||Object.keys(sf(d)).length>0)){if(!(!(0,f.isValidElement)(c)||Array.isArray(c)&&c.length>1||function(e){return ff(e.type)}(c))){let e=c.props?.className,t="function"==typeof e?(...t)=>Gd(e(...t),s.className):Gd(e,s.className),n=t?{className:t}:{},r=of(c.props,sf(uf(s,["ref"])));for(let e in d)e in r&&delete d[e];return(0,f.cloneElement)(c,Object.assign({},r,d,u,{ref:o(cf(c),u.ref)},n))}if(Object.keys(sf(s)).length>0)throw Error(['Passing props on "Fragment"!',"",`The current component <${r} /> is rendering a "Fragment".`,"However we need to passthrough the following props:",Object.keys(sf(s)).concat(Object.keys(sf(d))).map(e=>` - ${e}`).join("\n"),"","You can apply a few solutions:",['Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',"Render a single element as the child so that we can forward the props onto that element."].map(e=>` - ${e}`).join("\n")].join("\n"))}return(0,f.createElement)(i,Object.assign({},uf(s,["ref"]),!ff(i)&&u,!ff(i)&&d),c)}function rf(...e){return e.every(e=>null==e)?void 0:t=>{for(let n of e)null!=n&&("function"==typeof n?n(t):n.current=t)}}function of(...e){if(0===e.length)return{};if(1===e.length)return e[0];let t={},n={};for(let r of e)for(let e in r)e.startsWith("on")&&"function"==typeof r[e]?(n[e]??(n[e]=[]),n[e].push(r[e])):t[e]=r[e];if(t.disabled||t["aria-disabled"])for(let e in n)/^(on(?:Click|Pointer|Mouse|Key)(?:Down|Up|Press)?)$/.test(e)&&(n[e]=[e=>(e?.preventDefault)?.call(e)]);for(let e in n)Object.assign(t,{[e](t,...r){let o=n[e];for(let e of o){if((t instanceof Event||t?.nativeEvent instanceof Event)&&t.defaultPrevented)return;e(t,...r)}}});return t}function af(...e){if(0===e.length)return{};if(1===e.length)return e[0];let t={},n={};for(let r of e)for(let e in r)e.startsWith("on")&&"function"==typeof r[e]?(n[e]??(n[e]=[]),n[e].push(r[e])):t[e]=r[e];for(let e in n)Object.assign(t,{[e](...t){let r=n[e];for(let e of r)e?.(...t)}});return t}function lf(e){return Object.assign((0,f.forwardRef)(e),{displayName:e.displayName??e.name})}function sf(e){let t=Object.assign({},e);for(let e in t)void 0===t[e]&&delete t[e];return t}function uf(e,t=[]){let n=Object.assign({},e);for(let e of t)e in n&&delete n[e];return n}function cf(e){return"19.2.7".split(".")[0]>="19"?e.props.ref:e.ref}function df(e){if(null!=e&&e.$$typeof===Symbol.for("react.lazy")){let t=e._payload;if(null!=t&&"fulfilled"===t.status)return df(t.value)}return e}function ff(e){return e===f.Fragment||e===Symbol.for("react.fragment")}function pf(e,t,n){let[r,o]=(0,f.useState)(n),i=void 0!==e,a=(0,f.useRef)(i),l=(0,f.useRef)(!1),s=(0,f.useRef)(!1);return!i||a.current||l.current?!i&&a.current&&!s.current&&(s.current=!0,a.current=i,console.error("A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.")):(l.current=!0,a.current=i,console.error("A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.")),[i?e:r,Ud(e=>(i||(0,pi.flushSync)(()=>o(e)),t?.(e)))]}function hf(e){let[t]=(0,f.useState)(e);return t}function mf(e={},t=null,n=[]){for(let[r,o]of Object.entries(e))yf(n,gf(t,r),o);return n}function gf(e,t){return e?e+"["+t+"]":t}function yf(e,t,n){if(Array.isArray(n))for(let[r,o]of n.entries())yf(e,gf(t,r.toString()),o);else n instanceof Date?e.push([t,n.toISOString()]):"boolean"==typeof n?e.push([t,n?"1":"0"]):"string"==typeof n?e.push([t,n]):"number"==typeof n?e.push([t,`${n}`]):null==n?e.push([t,""]):function(e){if("[object Object]"!==Object.prototype.toString.call(e))return!1;let t=Object.getPrototypeOf(e);return null===t||null===Object.getPrototypeOf(t)}(n)&&!(0,f.isValidElement)(n)&&mf(n,t,e)}function vf(e){var t;let n=e?.form??e.closest("form");if(n){for(let t of n.elements)if(t!==e&&("INPUT"===t.tagName&&"submit"===t.type||"BUTTON"===t.tagName&&"submit"===t.type||"INPUT"===t.nodeName&&"image"===t.type))return void t.click();null==(t=n.requestSubmit)||t.call(n)}}var bf=(e=>(e[e.None=1]="None",e[e.Focusable=2]="Focusable",e[e.Hidden=4]="Hidden",e))(bf||{}),wf=lf(function(e,t){let{features:n=1,...r}=e,o={ref:t,"aria-hidden":!(2&~n)||(r["aria-hidden"]??void 0),hidden:!(4&~n)||void 0,style:{position:"fixed",top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0",...!(4&~n)&&!!(2&~n)&&{display:"none"}}};return tf()({ourProps:o,theirProps:r,slot:{},defaultTag:"span",name:"Hidden"})}),xf=(0,f.createContext)(null);function kf({children:e}){let t=(0,f.useContext)(xf);if(!t)return f.createElement(f.Fragment,null,e);let{target:n}=t;return n?(0,pi.createPortal)(f.createElement(f.Fragment,null,e),n):null}function Sf({data:e,form:t,disabled:n,onReset:r,overrides:o}){let[i,a]=(0,f.useState)(null),l=Bd();return(0,f.useEffect)(()=>{if(r&&i)return l.addEventListener(i,"reset",r)},[i,t,r]),f.createElement(kf,null,f.createElement(Ef,{setForm:a,formId:t}),mf(e).map(([e,r])=>f.createElement(wf,{features:bf.Hidden,...sf({key:e,as:"input",type:"hidden",hidden:!0,readOnly:!0,form:t,disabled:n,name:e,value:r,...o})})))}function Ef({setForm:e,formId:t}){return(0,f.useEffect)(()=>{if(t){let n=document.getElementById(t);n&&e(n)}},[e,t]),t?null:f.createElement(wf,{features:bf.Hidden,as:"input",type:"hidden",hidden:!0,readOnly:!0,ref:t=>{if(!t)return;let n=t.closest("form");n&&e(n)}})}var Cf=(0,f.createContext)(void 0);function Mf(){return(0,f.useContext)(Cf)}function Pf(e){return!("object"!=typeof e||!e)&&"nodeType"in e}function Of(e){return Pf(e)&&"tagName"in e}function Tf(e){return Of(e)&&"accessKey"in e}function _f(e){return Of(e)&&"tabIndex"in e}function Nf(e){return Tf(e)&&"INPUT"===e.nodeName}function If(e){return Tf(e)&&"LABEL"===e.nodeName}function zf(e){return Tf(e)&&"FIELDSET"===e.nodeName}function Rf(e){return Tf(e)&&"LEGEND"===e.nodeName}function Lf(e){let t=e.parentElement,n=null;for(;t&&!zf(t);)Rf(t)&&(n=t),t=t.parentElement;let r=""===t?.getAttribute("disabled");return(!r||!function(e){if(!e)return!1;let t=e.previousElementSibling;for(;null!==t;){if(Rf(t))return!1;t=t.previousElementSibling}return!0}(n))&&r}var Af=Symbol();function Df(e,t=!0){return Object.assign(e,{[Af]:t})}function Ff(...e){let t=(0,f.useRef)(e);(0,f.useEffect)(()=>{t.current=e},[e]);let n=Ud(e=>{for(let n of t.current)null!=n&&("function"==typeof n?n(e):n.current=e)});return e.every(e=>null==e||e?.[Af])?void 0:n}var jf=(0,f.createContext)(null);function Hf(){let e=(0,f.useContext)(jf);if(null===e){let e=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(e,Hf),e}return e}function Vf(){return(0,f.useContext)(jf)?.value??void 0}function $f(){let[e,t]=(0,f.useState)([]);return[e.length>0?e.join(" "):void 0,(0,f.useMemo)(()=>function(e){let n=Ud(e=>(t(t=>[...t,e]),()=>t(t=>{let n=t.slice(),r=n.indexOf(e);return-1!==r&&n.splice(r,1),n}))),r=(0,f.useMemo)(()=>({register:n,slot:e.slot,name:e.name,props:e.props,value:e.value}),[n,e.slot,e.name,e.props,e.value]);return f.createElement(jf.Provider,{value:r},e.children)},[t])]}jf.displayName="DescriptionContext";var Bf=lf(function(e,t){let n=(0,f.useId)(),r=Zd(),{id:o=`headlessui-description-${n}`,...i}=e,a=Hf(),l=Ff(t);Wd(()=>a.register(o),[o,a.register]);let s=Yd({...a.slot,disabled:r||!1}),u={ref:l,...a.props,id:o};return tf()({ourProps:u,theirProps:i,slot:s,defaultTag:"p",name:a.name||"Description"})}),Wf=Object.assign(Bf,{}),qf=(e=>(e.Space=" ",e.Enter="Enter",e.Escape="Escape",e.Backspace="Backspace",e.Delete="Delete",e.ArrowLeft="ArrowLeft",e.ArrowUp="ArrowUp",e.ArrowRight="ArrowRight",e.ArrowDown="ArrowDown",e.Home="Home",e.End="End",e.PageUp="PageUp",e.PageDown="PageDown",e.Tab="Tab",e))(qf||{}),Uf=(0,f.createContext)(null);function Kf(){let e=(0,f.useContext)(Uf);if(null===e){let e=Error("You used a