Release 0.2.0: Slate redesign and Manual sampling defaults
Browse files- CHANGELOG.md +10 -0
- DMAI-NODES-v0.2.0.zip +3 -0
- DMAI-NODES-v0.2.0.zip.sha256 +1 -0
- README.md +16 -11
- dmai_nodes/__init__.py +1 -1
- dmai_nodes/engine.py +2 -0
- dmai_nodes/nodes.py +2 -2
- dmai_nodes/presets.py +1 -1
- docs/API.md +11 -3
- docs/DESIGN.md +25 -0
- docs/INSTALLATION.md +25 -12
- docs/MODELS.md +13 -7
- docs/VALIDATION.md +18 -4
- pyproject.toml +1 -1
- release.json +1 -1
- tools/build_release.py +1 -1
- web/dmai-nodes.js +7 -7
- web/dom.mjs +93 -5
- web/engine.mjs +135 -155
- web/gallery.mjs +4 -3
- web/inputs.mjs +99 -63
- web/nodes.css +184 -672
- web/progress.mjs +1 -1
- web/workflow-migration.mjs +1 -1
- workflows/DMAI-NODES-FLUX.json +18 -18
- workflows/DMAI-NODES-Krea.api.json +1 -1
- workflows/DMAI-NODES-Krea.json +19 -19
- workflows/DMAI-NODES-Qwen.json +18 -18
- workflows/DMAI-NODES-SDXL.json +18 -18
CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
| 1 |
# Changelog
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
## 0.1.3 — 2026-09-29
|
| 4 |
|
| 5 |
- Give the new Prompter the unique internal ID `DMAINodesPrompter`, keeping its visible name **DMAI Prompter**. Older DMAI Suite and standalone Prompter installations can keep their original node and frontend hooks.
|
|
|
|
| 1 |
# Changelog
|
| 2 |
|
| 3 |
+
## 0.2.0 - 2026-09-29
|
| 4 |
+
|
| 5 |
+
- Refresh the Slate interface with larger Inter text, roomier controls, a larger prompt field and clearer section spacing.
|
| 6 |
+
- Use two-row LoRA cards with full filenames, visible order, enable/edit actions and independently editable Model and CLIP strengths. Keep drag, arrow and keyboard reordering.
|
| 7 |
+
- Add readable captions outside the native Canvas socket dots and enforce minimum node dimensions so controls and ports remain usable.
|
| 8 |
+
- Start new Engines and all starter workflows in Manual, with enhancement disabled. Keep saved workflow settings and existing model adapters.
|
| 9 |
+
- Show JSON upload only in DMAI Enhanced. New preset choices come from imported settings; historical built-in presets remain resolvable for saved workflows. Remove preset export from the interface.
|
| 10 |
+
- Preserve manual drafts when switching modes. An Enhanced Engine without a selected preset asks for a DMAI JSON upload or a switch back to Manual.
|
| 11 |
+
- Keep the conflict-free `DMAINodesPrompter` ID and legacy workflow migration introduced in 0.1.3.
|
| 12 |
+
|
| 13 |
## 0.1.3 — 2026-09-29
|
| 14 |
|
| 15 |
- Give the new Prompter the unique internal ID `DMAINodesPrompter`, keeping its visible name **DMAI Prompter**. Older DMAI Suite and standalone Prompter installations can keep their original node and frontend hooks.
|
DMAI-NODES-v0.2.0.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:81eca7e53dc58d56c6dac8575be431924fed78b90779155bfc7ca309fcf189a2
|
| 3 |
+
size 157471
|
DMAI-NODES-v0.2.0.zip.sha256
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
81eca7e53dc58d56c6dac8575be431924fed78b90779155bfc7ca309fcf189a2 DMAI-NODES-v0.2.0.zip
|
README.md
CHANGED
|
@@ -12,9 +12,9 @@ tags:
|
|
| 12 |
|
| 13 |
# DMAI NODES
|
| 14 |
|
| 15 |
-
**A
|
| 16 |
|
| 17 |
-
**0.
|
| 18 |
|
| 19 |
Prompter → Generation Engine → Gallery, with an ordered LoRA Loader feeding the engine.
|
| 20 |
|
|
@@ -23,8 +23,8 @@ Prompter → Generation Engine → Gallery, with an ordered LoRA Loader feeding
|
|
| 23 |
| Node | Purpose |
|
| 24 |
| --- | --- |
|
| 25 |
| **DMAI Prompter** | Prompt, 19 aspect ratios, 1K / 2K / Custom dimensions, image count, Generate button and generation progress outline. |
|
| 26 |
-
| **DMAI LoRA Loader** |
|
| 27 |
-
| **DMAI Generation Engine** | Installed checkpoint and diffusion model picker,
|
| 28 |
| **DMAI Gallery** | Persistent PNG originals, multiple selection, per-image download and selection ZIP. |
|
| 29 |
| **DMAI Krea Enhancer** | A standalone Krea-only model node, using the optional upstream Krea2T Enhancer. |
|
| 30 |
| **DMAI VAE Encode / Decode** | Standard Comfy VAE operations, usable separately and with the engine's advanced inputs. |
|
|
@@ -33,6 +33,8 @@ Sampler and scheduler choices come from the running ComfyUI installation. Option
|
|
| 33 |
|
| 34 |
The simple path loads models inside the engine. Advanced workflows can supply MODEL, CLIP and VAE together, and an encoded LATENT. Krea enhancement is only available for Krea profiles. A settings preset never installs a new model adapter.
|
| 35 |
|
|
|
|
|
|
|
| 36 |
## Install
|
| 37 |
|
| 38 |
From your ComfyUI directory, using **the same Python environment as ComfyUI**:
|
|
@@ -44,11 +46,12 @@ python -m pip install -r custom_nodes/DMAI-NODES/requirements.txt
|
|
| 44 |
|
| 45 |
Restart ComfyUI and refresh the browser. Search for **DMAI** in the node library.
|
| 46 |
|
| 47 |
-
**Using an older DMAI package too?**
|
| 48 |
|
| 49 |
- [Windows Portable, local environments and Runpod](docs/INSTALLATION.md)
|
| 50 |
- [Model files, presets and enhancement](docs/MODELS.md)
|
| 51 |
- [API workflow and future website integration](docs/API.md)
|
|
|
|
| 52 |
- [Validation results and limits](docs/VALIDATION.md)
|
| 53 |
|
| 54 |
ZIP installation is also supported: extract the release so `ComfyUI/custom_nodes/DMAI-NODES/__init__.py` exists. A Hugging Face mirror includes the package and checksum; this repository contains **code and documentation, no model weights**.
|
|
@@ -56,13 +59,14 @@ ZIP installation is also supported: extract the release so `ComfyUI/custom_nodes
|
|
| 56 |
## First workflow
|
| 57 |
|
| 58 |
1. Import a starter from `workflows`: Krea, SDXL, Qwen or FLUX. Use the `.json` file for the UI and `.api.json` for API calls.
|
| 59 |
-
2. Select an installed file at the top of Engine under **Checkpoints** or **Diffusion models**. Open **Model files** to set its **Architecture** and any separate text encoders and VAE. Checkpoint loading currently supports SDXL; diffusion loading supports Krea 2 Turbo, Qwen Image and FLUX.1 Dev.
|
| 60 |
-
3.
|
| 61 |
-
4.
|
|
|
|
| 62 |
|
| 63 |
-
|
| 64 |
|
| 65 |
-
To wire nodes yourself, drag the native output
|
| 66 |
|
| 67 |
## Behavior that matters
|
| 68 |
|
|
@@ -72,11 +76,12 @@ To wire nodes yourself, drag the native output port to a matching input port: Pr
|
|
| 72 |
- Gallery IDs are serialized in workflows. The new package uses separate storage under `output/DMAI-NODES`; existing DMAI Studio history is not deleted or automatically imported.
|
| 73 |
- ZIP exports are limited to **100 images / 2 GiB**. Full-history selection uses a snapshot so later images are not silently added.
|
| 74 |
- Invalid saved JSON remains visible for correction. It is not replaced by defaults.
|
|
|
|
| 75 |
- This is a new package with new node IDs. Existing Studio, Carousel and Krea Edit workflows remain in their original package.
|
| 76 |
|
| 77 |
## Compatibility
|
| 78 |
|
| 79 |
-
Built against ComfyUI **v0.37.0**, with the frontend supplied by that tag. Renderer and inference coverage are recorded in [VALIDATION.md](docs/VALIDATION.md). Native connection ports remain available; custom
|
| 80 |
|
| 81 |
No cloud service or paid generation is enabled by installation. The HTTP routes use ComfyUI's existing access boundary; put authentication and TLS in front of a remote deployment.
|
| 82 |
|
|
|
|
| 12 |
|
| 13 |
# DMAI NODES
|
| 14 |
|
| 15 |
+
**A clear image workflow for ComfyUI.** Slate surfaces, readable Inter typography and English controls.
|
| 16 |
|
| 17 |
+
**0.2.0 preview** · [GitHub releases](https://github.com/ceroomedia/DMAI-NODES/releases) · [Hugging Face download mirror](https://huggingface.co/ceroomedia/DMAI-NODES)
|
| 18 |
|
| 19 |
Prompter → Generation Engine → Gallery, with an ordered LoRA Loader feeding the engine.
|
| 20 |
|
|
|
|
| 23 |
| Node | Purpose |
|
| 24 |
| --- | --- |
|
| 25 |
| **DMAI Prompter** | Prompt, 19 aspect ratios, 1K / 2K / Custom dimensions, image count, Generate button and generation progress outline. |
|
| 26 |
+
| **DMAI LoRA Loader** | Roomy two-row cards with full filenames, enable controls, independent Model / CLIP strengths, reordering and strict file checks. |
|
| 27 |
+
| **DMAI Generation Engine** | Installed checkpoint and diffusion model picker, Manual sampling by default, uploaded DMAI Enhanced settings, seed sequence and execution report. |
|
| 28 |
| **DMAI Gallery** | Persistent PNG originals, multiple selection, per-image download and selection ZIP. |
|
| 29 |
| **DMAI Krea Enhancer** | A standalone Krea-only model node, using the optional upstream Krea2T Enhancer. |
|
| 30 |
| **DMAI VAE Encode / Decode** | Standard Comfy VAE operations, usable separately and with the engine's advanced inputs. |
|
|
|
|
| 33 |
|
| 34 |
The simple path loads models inside the engine. Advanced workflows can supply MODEL, CLIP and VAE together, and an encoded LATENT. Krea enhancement is only available for Krea profiles. A settings preset never installs a new model adapter.
|
| 35 |
|
| 36 |
+
The larger Prompter puts the prompt, image format, count and Generate control together. Engine exposes its sampling settings in Manual; switch to **DMAI Enhanced** to upload a settings JSON. Readable captions sit outside the native Canvas connection dots, and minimum node sizes keep controls and ports usable when resizing.
|
| 37 |
+
|
| 38 |
## Install
|
| 39 |
|
| 40 |
From your ComfyUI directory, using **the same Python environment as ComfyUI**:
|
|
|
|
| 46 |
|
| 47 |
Restart ComfyUI and refresh the browser. Search for **DMAI** in the node library.
|
| 48 |
|
| 49 |
+
**Using an older DMAI package too?** Since version 0.1.3, this package's Prompter has its own internal ID, `DMAINodesPrompter`. Choose **DMAI Prompter** from the **DMAI-NODES** package in category **DMAI NODES**. The older Suite and standalone Prompter keep their existing IDs. See [updating from 0.1.2](docs/INSTALLATION.md#updating-from-012-or-earlier) for saved workflows and API clients.
|
| 50 |
|
| 51 |
- [Windows Portable, local environments and Runpod](docs/INSTALLATION.md)
|
| 52 |
- [Model files, presets and enhancement](docs/MODELS.md)
|
| 53 |
- [API workflow and future website integration](docs/API.md)
|
| 54 |
+
- [Interface design](docs/DESIGN.md)
|
| 55 |
- [Validation results and limits](docs/VALIDATION.md)
|
| 56 |
|
| 57 |
ZIP installation is also supported: extract the release so `ComfyUI/custom_nodes/DMAI-NODES/__init__.py` exists. A Hugging Face mirror includes the package and checksum; this repository contains **code and documentation, no model weights**.
|
|
|
|
| 59 |
## First workflow
|
| 60 |
|
| 61 |
1. Import a starter from `workflows`: Krea, SDXL, Qwen or FLUX. Use the `.json` file for the UI and `.api.json` for API calls.
|
| 62 |
+
2. Select an installed file at the top of Engine under **Checkpoints** or **Diffusion models**. Open **Model files** to set its **Architecture** and any separate text encoders and VAE. Checkpoint loading currently supports SDXL; diffusion loading supports Krea 2 Turbo, Qwen Image and FLUX.1 Dev.
|
| 63 |
+
3. Keep **Manual** to adjust Steps, CFG, Sampler, Scheduler and Denoise. For a prepared settings file, switch to **DMAI Enhanced** and choose **Upload JSON**. Enhanced requires a matching preset before generation; returning to Manual restores your manual settings.
|
| 64 |
+
4. Enter the prompt and image count. Add LoRAs if needed, setting Model and CLIP strengths independently.
|
| 65 |
+
5. Choose **Generate** in the Prompter. Its softly glowing outline follows generation across all requested images and reaches 100% when the workflow finishes. Preview or download saved results from Gallery.
|
| 66 |
|
| 67 |
+
New nodes and starter workflows use Manual with enhancement `none`. Enable the optional Krea enhancer only after installing its dependency. Existing workflows retain their saved sampling settings and selected legacy preset. Models and LoRAs remain subject to their own licenses.
|
| 68 |
|
| 69 |
+
To wire nodes yourself, drag the native output dot to a matching input dot: Prompter **Prompt** → Engine **Prompt** (`request`), LoRA Loader **LoRAs** → Engine **LoRAs**, and Engine **Images** → Gallery **Images**. Engine **Report** → Gallery **Report** adds generation metadata. The captions identify the existing ports; dragging still uses ComfyUI's dots. Use ComfyUI's native node controls to reveal optional advanced inputs; see [advanced connections](docs/MODELS.md#advanced-connections).
|
| 70 |
|
| 71 |
## Behavior that matters
|
| 72 |
|
|
|
|
| 76 |
- Gallery IDs are serialized in workflows. The new package uses separate storage under `output/DMAI-NODES`; existing DMAI Studio history is not deleted or automatically imported.
|
| 77 |
- ZIP exports are limited to **100 images / 2 GiB**. Full-history selection uses a snapshot so later images are not silently added.
|
| 78 |
- Invalid saved JSON remains visible for correction. It is not replaced by defaults.
|
| 79 |
+
- Preset upload is available in DMAI Enhanced. The preset export button has been removed; keep the original JSON file for reuse. Historical built-in presets remain available to saved workflows, with imported settings shown for new selections.
|
| 80 |
- This is a new package with new node IDs. Existing Studio, Carousel and Krea Edit workflows remain in their original package.
|
| 81 |
|
| 82 |
## Compatibility
|
| 83 |
|
| 84 |
+
Built against ComfyUI **v0.37.0**, with the frontend supplied by that tag. Renderer and inference coverage are recorded in [VALIDATION.md](docs/VALIDATION.md). Native connection ports remain available. External socket captions target Canvas; custom positioning and captions may differ in Nodes 2.0.
|
| 85 |
|
| 86 |
No cloud service or paid generation is enabled by installation. The HTTP routes use ComfyUI's existing access boundary; put authentication and TLS in front of a remote deployment.
|
| 87 |
|
dmai_nodes/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
"""DMAI NODES domain modules; importing these does not initialize a GPU."""
|
| 2 |
-
__version__ = "0.
|
|
|
|
| 1 |
"""DMAI NODES domain modules; importing these does not initialize a GPU."""
|
| 2 |
+
__version__ = "0.2.0"
|
dmai_nodes/engine.py
CHANGED
|
@@ -94,6 +94,8 @@ def parse_engine_config(value):
|
|
| 94 |
if any(entry["id"] in builtin_ids for entry in presets):
|
| 95 |
raise ValueError("Imported presets cannot replace built-in preset IDs. Use a new ID.")
|
| 96 |
if value["mode"] == "enhanced":
|
|
|
|
|
|
|
| 97 |
preset, _ = resolve_preset(value["preset_id"], profile["id"], presets)
|
| 98 |
settings = preset["settings"]
|
| 99 |
return {"schema_version": 1, "profile_id": profile["id"], "mode": value["mode"],
|
|
|
|
| 94 |
if any(entry["id"] in builtin_ids for entry in presets):
|
| 95 |
raise ValueError("Imported presets cannot replace built-in preset IDs. Use a new ID.")
|
| 96 |
if value["mode"] == "enhanced":
|
| 97 |
+
if not value["preset_id"]:
|
| 98 |
+
raise ValueError("Upload a DMAI JSON preset in DMAI Enhanced, or switch the Engine to Manual.")
|
| 99 |
preset, _ = resolve_preset(value["preset_id"], profile["id"], presets)
|
| 100 |
settings = preset["settings"]
|
| 101 |
return {"schema_version": 1, "profile_id": profile["id"], "mode": value["mode"],
|
dmai_nodes/nodes.py
CHANGED
|
@@ -10,8 +10,8 @@ LoraPort = io.Custom("DMAI_LORA_STACK")
|
|
| 10 |
ReportPort = io.Custom("DMAI_REPORT")
|
| 11 |
DEFAULT_PROMPT = {"schema_version":1,"prompt":"","negative_prompt":"","width":832,"height":1024,"count":4}
|
| 12 |
DEFAULT_LORAS = {"schema_version":1,"entries":[]}
|
| 13 |
-
DEFAULT_ENGINE = {"schema_version":1,"profile_id":"krea2-turbo","mode":"
|
| 14 |
-
"settings":{"steps":8,"cfg":1.1,"sampler":"euler","scheduler":"beta","denoise":1,"enhancer":"
|
| 15 |
"seed":481516,"models":{"diffusion_model":"krea2_turbo_fp8.safetensors","text_encoder":"qwen3vl_4b_fp8_scaled.safetensors","vae":"wan_2.1_vae.safetensors","checkpoint":""},"presets":[]}
|
| 16 |
|
| 17 |
|
|
|
|
| 10 |
ReportPort = io.Custom("DMAI_REPORT")
|
| 11 |
DEFAULT_PROMPT = {"schema_version":1,"prompt":"","negative_prompt":"","width":832,"height":1024,"count":4}
|
| 12 |
DEFAULT_LORAS = {"schema_version":1,"entries":[]}
|
| 13 |
+
DEFAULT_ENGINE = {"schema_version":1,"profile_id":"krea2-turbo","mode":"manual","preset_id":"",
|
| 14 |
+
"settings":{"steps":8,"cfg":1.1,"sampler":"euler","scheduler":"beta","denoise":1,"enhancer":"none"},
|
| 15 |
"seed":481516,"models":{"diffusion_model":"krea2_turbo_fp8.safetensors","text_encoder":"qwen3vl_4b_fp8_scaled.safetensors","vae":"wan_2.1_vae.safetensors","checkpoint":""},"presets":[]}
|
| 16 |
|
| 17 |
|
dmai_nodes/presets.py
CHANGED
|
@@ -150,4 +150,4 @@ def resolve_preset(preset_id, profile_id, imports):
|
|
| 150 |
if entry["model"]["id"] != profile_id:
|
| 151 |
raise ValueError("Preset belongs to a different model profile.")
|
| 152 |
return deepcopy(entry), source
|
| 153 |
-
raise ValueError("Selected preset was not found.
|
|
|
|
| 150 |
if entry["model"]["id"] != profile_id:
|
| 151 |
raise ValueError("Preset belongs to a different model profile.")
|
| 152 |
return deepcopy(entry), source
|
| 153 |
+
raise ValueError("Selected preset was not found. Upload its JSON in DMAI Enhanced, or switch to Manual.")
|
docs/API.md
CHANGED
|
@@ -13,7 +13,7 @@ python custom_nodes/DMAI-NODES/tools/run_workflow.py \
|
|
| 13 |
--count 2 --seed 481516 --gallery website-demo --wait
|
| 14 |
```
|
| 15 |
|
| 16 |
-
Run the command from your ComfyUI directory using its Python environment. The default example uses Krea
|
| 17 |
|
| 18 |
1. `POST /prompt` with `{"prompt": <API workflow>, "client_id": "<unique ID>"}`.
|
| 19 |
2. Keep the returned `prompt_id`.
|
|
@@ -24,9 +24,17 @@ In a completed starter job, Gallery metadata is at `history[prompt_id]["outputs"
|
|
| 24 |
|
| 25 |
Use seed strings for the full unsigned 64-bit range; JavaScript numbers cannot represent all of it exactly. Each image's exact seed is recorded in the Engine report. The CLI timeout only stops waiting; it does not cancel the job.
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
## Prompter ID migration
|
| 28 |
|
| 29 |
-
|
| 30 |
|
| 31 |
Use the updated starter API files for new integrations. The included `run_workflow.py` converts identifiable 0.1.0-0.1.2 DMAI NODES Prompters in memory before submitting and leaves the source file intact. Direct `/prompt` callers should migrate their template first:
|
| 32 |
|
|
@@ -80,7 +88,7 @@ Routes are relative to ComfyUI's base URL. JSON responses use `{"ok": true, "dat
|
|
| 80 |
| Method | Route | Purpose |
|
| 81 |
| --- | --- | --- |
|
| 82 |
| GET | `/dmai-nodes/v1/health` | Package version and capability flags |
|
| 83 |
-
| GET | `/dmai-nodes/v1/bootstrap` | Model profiles, installed files, LoRAs,
|
| 84 |
| POST | `/dmai-nodes/v1/presets/validate` | Validate a single JSON preset or pack |
|
| 85 |
| GET | `/dmai-nodes/v1/galleries/{gallery_id}?offset=0&limit=60` | Saved images, total and snapshot watermark |
|
| 86 |
| GET | `/dmai-nodes/v1/images/{image_id}` | Original PNG |
|
|
|
|
| 13 |
--count 2 --seed 481516 --gallery website-demo --wait
|
| 14 |
```
|
| 15 |
|
| 16 |
+
Run the command from your ComfyUI directory using its Python environment. The default example uses Krea 2 Turbo in Manual with enhancement `none`; it does not require the optional Krea enhancer. Use `--workflow` for a different included API example. The helper expects starter node IDs `1` (Prompter), `3` (Engine) and `4` (Gallery); adapt these lookups before using a workflow with different IDs. For the SDXL example, `--checkpoint` can set the installed checkpoint filename. Edit model selections in other API examples to match your catalog. This command queues real generation on your own server.
|
| 17 |
|
| 18 |
1. `POST /prompt` with `{"prompt": <API workflow>, "client_id": "<unique ID>"}`.
|
| 19 |
2. Keep the returned `prompt_id`.
|
|
|
|
| 24 |
|
| 25 |
Use seed strings for the full unsigned 64-bit range; JavaScript numbers cannot represent all of it exactly. Each image's exact seed is recorded in the Engine report. The CLI timeout only stops waiting; it does not cancel the job.
|
| 26 |
|
| 27 |
+
## Engine sampling modes
|
| 28 |
+
|
| 29 |
+
Version 0.2.0 starts new Engines and all starter templates in `"mode": "manual"`, with `"preset_id": ""` and `"enhancer": "none"` inside `settings`. Manual executes the validated `settings` values. Existing saved configs retain their mode and settings.
|
| 30 |
+
|
| 31 |
+
For DMAI Enhanced, use `"mode": "enhanced"`, a selected `preset_id`, and the matching validated preset in the Engine's `presets` array. Presets are data, not uploaded executable code. The frontend exposes **Upload JSON** only in Enhanced and has no preset export button; headless clients supply the same configuration directly. See [preset format and limits](MODELS.md#manual-or-dmai-enhanced).
|
| 32 |
+
|
| 33 |
+
An Enhanced config with an empty `preset_id` fails with an actionable error: upload a DMAI JSON preset or switch to Manual. The server continues resolving historical built-in preset IDs for saved workflows and API templates. The `bootstrap` response retains that catalog for compatibility; its presence does not mean the frontend offers those presets as new choices. Current node IDs, request types, API routes and JSON schema versions are unchanged from 0.1.3.
|
| 34 |
+
|
| 35 |
## Prompter ID migration
|
| 36 |
|
| 37 |
+
Since version 0.1.3, the Prompter's API `class_type` is `DMAINodesPrompter`. The request configuration, `DMAI_PROMPT` output and Engine connections keep the same contracts. Older DMAI packages still own `DMAIPrompter`; the server intentionally has no alias that could overwrite them.
|
| 38 |
|
| 39 |
Use the updated starter API files for new integrations. The included `run_workflow.py` converts identifiable 0.1.0-0.1.2 DMAI NODES Prompters in memory before submitting and leaves the source file intact. Direct `/prompt` callers should migrate their template first:
|
| 40 |
|
|
|
|
| 88 |
| Method | Route | Purpose |
|
| 89 |
| --- | --- | --- |
|
| 90 |
| GET | `/dmai-nodes/v1/health` | Package version and capability flags |
|
| 91 |
+
| GET | `/dmai-nodes/v1/bootstrap` | Model profiles, installed files, LoRAs, historical preset catalog, runtime samplers and schedulers |
|
| 92 |
| POST | `/dmai-nodes/v1/presets/validate` | Validate a single JSON preset or pack |
|
| 93 |
| GET | `/dmai-nodes/v1/galleries/{gallery_id}?offset=0&limit=60` | Saved images, total and snapshot watermark |
|
| 94 |
| GET | `/dmai-nodes/v1/images/{image_id}` | Original PNG |
|
docs/DESIGN.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Interface design — 0.2.0
|
| 2 |
+
|
| 3 |
+
The interface uses Slate charcoal, a lime action color and locally bundled Inter. All controls are in English. It follows the hierarchy and alignment principles in Apple's [Layout](https://developer.apple.com/design/human-interface-guidelines/layout) and [Typography](https://developer.apple.com/design/human-interface-guidelines/typography) guidance, with its own colors and components.
|
| 4 |
+
|
| 5 |
+
## Working sizes
|
| 6 |
+
|
| 7 |
+
- Body: 15 px. Prompt text: 16 px. Node headings: 21 px bold. Secondary labels: 13 px.
|
| 8 |
+
- Form controls: at least 44 px high. Generate: 52 px high.
|
| 9 |
+
- Panel padding: 22 px. Related controls use consistent 8–16 px gaps.
|
| 10 |
+
- Lime identifies the primary action, selected formats and active connections. Text and shape also communicate state.
|
| 11 |
+
- Reduced-motion preferences disable decorative animation; numeric progress remains available.
|
| 12 |
+
|
| 13 |
+
ComfyUI scales every node with the canvas. Around 70–100% zoom is a useful working range; fitting a large graph can make every extension's controls smaller. Minimum dimensions prevent the saved narrow layouts from squeezing the new controls. Larger user-chosen dimensions remain available.
|
| 14 |
+
|
| 15 |
+
## Structure
|
| 16 |
+
|
| 17 |
+
**Prompter:** description first, then visual aspect ratios, resolution, image count and Generate. Negative prompt is a disclosure. Real execution progress follows the perimeter.
|
| 18 |
+
|
| 19 |
+
**LoRA Loader:** the first row identifies a file, its position and enabled state. The second row holds independent Model and CLIP strengths plus ordering arrows. Names wrap rather than collapsing into a tiny column. The list scrolls when needed.
|
| 20 |
+
|
| 21 |
+
**Generation Engine:** the installed-file selector stays first. Supporting model files are under a disclosure. Manual exposes sampling controls directly. DMAI Enhanced is the only place to upload JSON settings. The old preset catalog is retained internally for saved workflows.
|
| 22 |
+
|
| 23 |
+
**Gallery:** images take the available space. Selection and ZIP stay in a visible footer while the grid scrolls. Each image retains its preview and download buttons.
|
| 24 |
+
|
| 25 |
+
**Connections:** Canvas captions sit just above the wire next to the real native socket. There is no second connector toolbar. Leave roughly 200 px between node edges when both sides have captions. Serialized input names, types and link behavior remain unchanged. Nodes 2.0 uses its own renderer and is not the visual reference for this release.
|
docs/INSTALLATION.md
CHANGED
|
@@ -30,7 +30,7 @@ For ComfyUI Desktop, use the installation directory shown in Desktop's settings
|
|
| 30 |
## Runpod
|
| 31 |
|
| 32 |
1. Start your chosen ComfyUI Pod using your normal template and persistent volume. GPU availability, model licenses and Pod cost are separate from this package.
|
| 33 |
-
2. Open the Pod terminal and find the actual ComfyUI folder. `/workspace/ComfyUI`
|
| 34 |
3. Activate the same environment as the running ComfyUI server. Check its launcher if unsure.
|
| 35 |
4. Run the commands below, adjusting the path when needed.
|
| 36 |
|
|
@@ -48,7 +48,7 @@ The instructions do not start a Pod or buy compute. No Runpod template installat
|
|
| 48 |
|
| 49 |
## Optional Krea enhancement
|
| 50 |
|
| 51 |
-
The **DMAI Krea Enhancer** and **Krea / Original**
|
| 52 |
|
| 53 |
```bash
|
| 54 |
git clone https://github.com/capitan01R/ComfyUI-Krea2T-Enhancer.git custom_nodes/ComfyUI-Krea2T-Enhancer
|
|
@@ -61,31 +61,43 @@ RES4LYF is optional and installed separately. Follow its upstream instructions a
|
|
| 61 |
|
| 62 |
## Update and remove
|
| 63 |
|
| 64 |
-
Update only when no job is running.
|
| 65 |
|
| 66 |
```bash
|
| 67 |
-
git -C custom_nodes/DMAI-NODES
|
|
|
|
| 68 |
python -m pip install -r custom_nodes/DMAI-NODES/requirements.txt
|
|
|
|
| 69 |
```
|
| 70 |
|
| 71 |
For Windows Portable, run from the portable folder instead:
|
| 72 |
|
| 73 |
```powershell
|
| 74 |
-
git -C .\ComfyUI\custom_nodes\DMAI-NODES
|
|
|
|
| 75 |
.\python_embeded\python.exe -m pip install -r .\ComfyUI\custom_nodes\DMAI-NODES\requirements.txt
|
|
|
|
| 76 |
```
|
| 77 |
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
### Updating from 0.1.2 or earlier
|
| 81 |
|
| 82 |
-
|
| 83 |
|
| 84 |
To install exactly this release from an existing Git checkout, including a checkout pinned to an older tag, run inside `custom_nodes/DMAI-NODES`:
|
| 85 |
|
| 86 |
```bash
|
| 87 |
-
git fetch origin tag v0.
|
| 88 |
-
git checkout v0.
|
| 89 |
python -m pip install -r requirements.txt
|
| 90 |
git describe --tags --exact-match
|
| 91 |
```
|
|
@@ -99,11 +111,12 @@ For API JSON sent directly to `/prompt`, use the new `DMAINodesPrompter` class t
|
|
| 99 |
## Troubleshooting
|
| 100 |
|
| 101 |
- **No DMAI nodes:** inspect the ComfyUI console for import errors; check the Python environment and one-level ZIP extraction.
|
| 102 |
-
- **Prompter missing when older DMAI nodes are installed:** update to 0.
|
| 103 |
- **Missing model:** select the exact catalog entry, including subfolder. Refresh the model list after adding files.
|
| 104 |
-
- **Enhancer unavailable:** install the pinned dependency or
|
|
|
|
| 105 |
- **LoRA has no matching patches:** verify the LoRA family and selected base model; this is deliberately an error.
|
| 106 |
- **Gallery missing original:** restore the file from your backup. An export will not silently omit a selected missing file.
|
| 107 |
-
- **Frontend layout issue:** refresh first, then test Comfy's Canvas renderer. Keep the exact Core and Frontend versions in your issue report.
|
| 108 |
|
| 109 |
Official references: [Portable](https://docs.comfy.org/installation/comfyui_portable_windows), [manual installation](https://docs.comfy.org/installation/manual_install), [Runpod ComfyUI guide](https://docs.runpod.io/tutorials/pods/comfyui).
|
|
|
|
| 30 |
## Runpod
|
| 31 |
|
| 32 |
1. Start your chosen ComfyUI Pod using your normal template and persistent volume. GPU availability, model licenses and Pod cost are separate from this package.
|
| 33 |
+
2. Open the Pod terminal and find the actual ComfyUI folder. Common examples are `/workspace/ComfyUI` and `/workspace/runpod-slim/ComfyUI`; templates differ.
|
| 34 |
3. Activate the same environment as the running ComfyUI server. Check its launcher if unsure.
|
| 35 |
4. Run the commands below, adjusting the path when needed.
|
| 36 |
|
|
|
|
| 48 |
|
| 49 |
## Optional Krea enhancement
|
| 50 |
|
| 51 |
+
The **DMAI Krea Enhancer**, Manual sampling with Enhancer set to `krea2t`, and any preset enabling that enhancer require the upstream extension. The historical **Krea / Original** recipe also enables it. New nodes and starter workflows leave enhancement at `none`. From the ComfyUI directory:
|
| 52 |
|
| 53 |
```bash
|
| 54 |
git clone https://github.com/capitan01R/ComfyUI-Krea2T-Enhancer.git custom_nodes/ComfyUI-Krea2T-Enhancer
|
|
|
|
| 61 |
|
| 62 |
## Update and remove
|
| 63 |
|
| 64 |
+
Update only when no job is running. To install **0.2.0 preview**, including from a checkout pinned to an older release, run from the ComfyUI directory with its Python environment active:
|
| 65 |
|
| 66 |
```bash
|
| 67 |
+
git -C custom_nodes/DMAI-NODES fetch origin tag v0.2.0
|
| 68 |
+
git -C custom_nodes/DMAI-NODES checkout v0.2.0
|
| 69 |
python -m pip install -r custom_nodes/DMAI-NODES/requirements.txt
|
| 70 |
+
git -C custom_nodes/DMAI-NODES describe --tags --exact-match
|
| 71 |
```
|
| 72 |
|
| 73 |
For Windows Portable, run from the portable folder instead:
|
| 74 |
|
| 75 |
```powershell
|
| 76 |
+
git -C .\ComfyUI\custom_nodes\DMAI-NODES fetch origin tag v0.2.0
|
| 77 |
+
git -C .\ComfyUI\custom_nodes\DMAI-NODES checkout v0.2.0
|
| 78 |
.\python_embeded\python.exe -m pip install -r .\ComfyUI\custom_nodes\DMAI-NODES\requirements.txt
|
| 79 |
+
git -C .\ComfyUI\custom_nodes\DMAI-NODES describe --tags --exact-match
|
| 80 |
```
|
| 81 |
|
| 82 |
+
The final command should print `v0.2.0`. Git's detached HEAD message is expected when selecting a fixed release tag. If Git reports local changes, preserve those changes before retrying; the commands above do not discard them.
|
| 83 |
+
|
| 84 |
+
Restart ComfyUI afterward, then hard-refresh with Ctrl+F5. For ZIP installations, replace package files from the new release while preserving your workflows and output. To uninstall, remove only the custom-node folder after stopping ComfyUI. Saved originals and the gallery index are in `output/DMAI-NODES` and are not removed by package updates.
|
| 85 |
+
|
| 86 |
+
### What changes in 0.2.0
|
| 87 |
+
|
| 88 |
+
The larger interface adds readable Canvas socket captions and minimum node sizes. Saved nodes may grow to the new minimum dimensions on load; their IDs and connections remain unchanged. Manual is the default for new Engines and starter workflows. Existing workflows retain their saved sampling settings and any selected historical preset.
|
| 89 |
+
|
| 90 |
+
For new prepared settings, switch to **DMAI Enhanced** and choose **Upload JSON**. Upload is available only in that mode. The old preset export control is removed. Keep your original preset files; existing imported presets remain saved in the workflow. See [sampling modes and legacy preset compatibility](MODELS.md#manual-or-dmai-enhanced).
|
| 91 |
|
| 92 |
### Updating from 0.1.2 or earlier
|
| 93 |
|
| 94 |
+
Since 0.1.3, the new Prompter registers as `DMAINodesPrompter`. Its visible name remains **DMAI Prompter**, under **DMAI NODES**, with the **DMAI-NODES** package badge. Older DMAI Suite and standalone Prompter installations use `DMAIPrompter`; they can remain enabled alongside this package. Version 0.2.0 keeps that separation.
|
| 95 |
|
| 96 |
To install exactly this release from an existing Git checkout, including a checkout pinned to an older tag, run inside `custom_nodes/DMAI-NODES`:
|
| 97 |
|
| 98 |
```bash
|
| 99 |
+
git fetch origin tag v0.2.0
|
| 100 |
+
git checkout v0.2.0
|
| 101 |
python -m pip install -r requirements.txt
|
| 102 |
git describe --tags --exact-match
|
| 103 |
```
|
|
|
|
| 111 |
## Troubleshooting
|
| 112 |
|
| 113 |
- **No DMAI nodes:** inspect the ComfyUI console for import errors; check the Python environment and one-level ZIP extraction.
|
| 114 |
+
- **Prompter missing when older DMAI nodes are installed:** update to 0.2.0 using the commands above, restart ComfyUI and hard-refresh. The coexistence fix was introduced in 0.1.3. Search `DMAINodesPrompter` or select **DMAI Prompter** with the **DMAI-NODES** badge. The new node has a `request` output of type `DMAI_PROMPT`, captioned **Prompt** in Canvas.
|
| 115 |
- **Missing model:** select the exact catalog entry, including subfolder. Refresh the model list after adding files.
|
| 116 |
+
- **Enhancer unavailable:** install the pinned dependency or use Manual with Enhancer set to `none`.
|
| 117 |
+
- **Enhanced asks for a preset:** choose **Upload JSON** in DMAI Enhanced and upload matching settings, or switch to Manual.
|
| 118 |
- **LoRA has no matching patches:** verify the LoRA family and selected base model; this is deliberately an error.
|
| 119 |
- **Gallery missing original:** restore the file from your backup. An export will not silently omit a selected missing file.
|
| 120 |
+
- **Frontend layout issue:** hard-refresh first, then test Comfy's Canvas renderer. Socket captions are drawn outside the node; leave room between neighboring nodes. Keep the exact Core and Frontend versions in your issue report.
|
| 121 |
|
| 122 |
Official references: [Portable](https://docs.comfy.org/installation/comfyui_portable_windows), [manual installation](https://docs.comfy.org/installation/manual_install), [Runpod ComfyUI guide](https://docs.runpod.io/tutorials/pods/comfyui).
|
docs/MODELS.md
CHANGED
|
@@ -19,13 +19,17 @@ These are example filenames, not included downloads. Select the compatible files
|
|
| 19 |
|
| 20 |
## Manual or DMAI Enhanced
|
| 21 |
|
| 22 |
-
**Manual** exposes Steps, CFG, Sampler, Scheduler and Denoise. Choices come from the active ComfyUI sampler registry, including names registered by extensions. A name appearing in the registry does not prove it works with every model.
|
| 23 |
|
| 24 |
-
**DMAI Enhanced**
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
FLUX.1 Dev uses embedded guidance 3.5; CFG is a separate parameter. Advanced users can supply both conditioning inputs, using Comfy's FluxGuidance upstream to change embedded guidance. External conditioning bypasses Prompter text encoding; apply any CLIP LoRAs before that external encoding and set the Engine recipe's CLIP strengths to zero. The Engine rejects active nonzero CLIP strengths with supplied conditioning.
|
| 27 |
|
| 28 |
-
The
|
| 29 |
|
| 30 |
```json
|
| 31 |
{
|
|
@@ -37,17 +41,17 @@ The preset import button accepts a single preset or a pack of up to 20. Files ar
|
|
| 37 |
}
|
| 38 |
```
|
| 39 |
|
| 40 |
-
Preset files contain data only. They cannot download models, execute Python or register adapters.
|
| 41 |
|
| 42 |
## Krea enhancer as a separate node
|
| 43 |
|
| 44 |
-
Connect a Krea MODEL through **DMAI Krea Enhancer**, then to Engine's advanced MODEL input. Also connect matching CLIP and VAE.
|
| 45 |
|
| 46 |
The standalone node requires the separately installed, reviewed [Krea2T Enhancer](https://github.com/capitan01R/ComfyUI-Krea2T-Enhancer). It exposes Enabled, Strength and Text scale. It rejects non-Krea models when enabled. Qwen, FLUX and SDXL never receive this enhancement. See [installation](INSTALLATION.md#optional-krea-enhancement) for the pinned dependency.
|
| 47 |
|
| 48 |
## Advanced connections
|
| 49 |
|
| 50 |
-
Drag ComfyUI's native output
|
| 51 |
|
| 52 |
Reveal optional advanced inputs using ComfyUI's native node controls.
|
| 53 |
|
|
@@ -71,7 +75,9 @@ Engine already decodes its normal IMAGE output. The separate nodes expose the sa
|
|
| 71 |
|
| 72 |
## LoRA behavior
|
| 73 |
|
| 74 |
-
The LoRA Loader produces an ordered recipe. Engine applies it before encoding both prompts.
|
|
|
|
|
|
|
| 75 |
|
| 76 |
File names preserve case and subfolders. Active file content hashes participate in cache invalidation. Registered patches are compared to Comfy's standard loader in CPU tests; visual compatibility with every LoRA is not guaranteed.
|
| 77 |
|
|
|
|
| 19 |
|
| 20 |
## Manual or DMAI Enhanced
|
| 21 |
|
| 22 |
+
**Manual** is the default for new Engines and all included starter workflows. It exposes Steps, CFG, Sampler, Scheduler and Denoise, with Enhancer available for Krea. Choices come from the active ComfyUI sampler registry, including names registered by extensions. A name appearing in the registry does not prove it works with every model. Changing a model file keeps the current mode and settings; a newly selected architecture starts in Manual unless that workflow already holds a saved draft for it.
|
| 23 |
|
| 24 |
+
**DMAI Enhanced** uses a validated JSON settings file. Switch to this mode, choose **Upload JSON**, and select a file for the intended architecture. If several matching presets have been imported, choose one under **DMAI settings**. Switching back to Manual restores your manual draft. The upload control appears only in Enhanced; there is no preset export button, so keep the source JSON for reuse.
|
| 25 |
+
|
| 26 |
+
Enhanced can be opened before a file has been uploaded. Generation requires a selected preset: an empty Enhanced configuration reports **Upload a DMAI JSON preset in DMAI Enhanced, or switch the Engine to Manual.** Upload matching settings or choose Manual before trying again.
|
| 27 |
+
|
| 28 |
+
Historical built-in presets are retained for existing workflows and API templates. A previously selected preset remains visible as saved workflow settings; the interface does not offer the full built-in catalog for new selections. The historical Krea Original recipe uses 8 steps, CFG 1.1, Euler, Beta, denoise 1 and the Krea enhancer at strength 1 / text scale 1. New Krea nodes use Manual with the same sampling values and enhancement `none`. These settings are starting points, not quality guarantees.
|
| 29 |
|
| 30 |
FLUX.1 Dev uses embedded guidance 3.5; CFG is a separate parameter. Advanced users can supply both conditioning inputs, using Comfy's FluxGuidance upstream to change embedded guidance. External conditioning bypasses Prompter text encoding; apply any CLIP LoRAs before that external encoding and set the Engine recipe's CLIP strengths to zero. The Engine rejects active nonzero CLIP strengths with supplied conditioning.
|
| 31 |
|
| 32 |
+
The Enhanced upload accepts a single preset or a pack of up to 20. Files are limited to 128 KiB. Unknown fields, model descriptors, non-finite values and unsupported sampling choices are rejected. Imported presets cannot replace the reserved historical built-in IDs. Use a new ID for your variation. A pack may contain different supported architectures; the interface prefers a preset matching the current architecture, or switches to the first imported preset's architecture when none matches. Confirm the model files after an architecture change.
|
| 33 |
|
| 34 |
```json
|
| 35 |
{
|
|
|
|
| 41 |
}
|
| 42 |
```
|
| 43 |
|
| 44 |
+
Preset files contain data only. They cannot download models, execute Python or register adapters. `resources/presets/dmai-presets-v1.json` is the historical compatibility catalog, retained as a reference for saved workflows. Its IDs are reserved; copy the settings into a new preset with a unique ID for upload.
|
| 45 |
|
| 46 |
## Krea enhancer as a separate node
|
| 47 |
|
| 48 |
+
Connect a Krea MODEL through **DMAI Krea Enhancer**, then to Engine's advanced MODEL input. Also connect matching CLIP and VAE. Use Manual with enhancement `none`, or an imported preset with `"enhancer": "none"`, when the upstream node has already applied it. Double enhancement is rejected.
|
| 49 |
|
| 50 |
The standalone node requires the separately installed, reviewed [Krea2T Enhancer](https://github.com/capitan01R/ComfyUI-Krea2T-Enhancer). It exposes Enabled, Strength and Text scale. It rejects non-Krea models when enabled. Qwen, FLUX and SDXL never receive this enhancement. See [installation](INSTALLATION.md#optional-krea-enhancement) for the pinned dependency.
|
| 51 |
|
| 52 |
## Advanced connections
|
| 53 |
|
| 54 |
+
Drag ComfyUI's native output dots to matching input dots. In Canvas, readable captions sit outside the nodes: Prompter **Prompt** → Engine **Prompt**, LoRA Loader **LoRAs** → Engine **LoRAs**, Engine **Images** → Gallery **Images**, and optionally Engine **Report** → Gallery **Report**. The caption **Prompt** represents the existing `request` input/output; serialized names and types are unchanged. Captions do not add another connection bar or replace native drag targets. Minimum node dimensions keep the controls and socket spacing usable; Nodes 2.0 may present ports differently.
|
| 55 |
|
| 56 |
Reveal optional advanced inputs using ComfyUI's native node controls.
|
| 57 |
|
|
|
|
| 75 |
|
| 76 |
## LoRA behavior
|
| 77 |
|
| 78 |
+
The LoRA Loader produces an ordered recipe. Engine applies it before encoding both prompts. Each card shows its order and full filename above separate **Model** and **CLIP** strength controls. Use the arrows, drag handle, or Alt+Up / Alt+Down on the handle to change the order. Both strengths accept values from -20 to 20. Invalid drafts block reordering and queue submission until corrected.
|
| 79 |
+
|
| 80 |
+
Disabled rows and rows with both strengths at zero are skipped. The active count updates as the strengths change. Active nonzero rows fail on missing or ambiguous paths or zero effective matches. Partial matches appear in the execution report.
|
| 81 |
|
| 82 |
File names preserve case and subfolders. Active file content hashes participate in cache invalidation. Registered patches are compared to Comfy's standard loader in CPU tests; visual compatibility with every LoRA is not guaranteed.
|
| 83 |
|
docs/VALIDATION.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
| 1 |
-
# Validation — 0.
|
| 2 |
|
| 3 |
-
Updated on 2026-09-29. This release has working ComfyUI registration, CPU contract tests and real local API checks. GPU image generation is still pending. The
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
## 0.1.3 coexistence and migration
|
| 6 |
|
|
@@ -25,8 +39,8 @@ These checks establish registration, UI coexistence and workflow migration. They
|
|
| 25 |
|
| 26 |
| Check | Evidence |
|
| 27 |
| --- | --- |
|
| 28 |
-
| Python suite |
|
| 29 |
-
| JavaScript suite |
|
| 30 |
| Node registration | All seven native V3 nodes appear in real `/object_info`. |
|
| 31 |
| Runtime choices | Four model profiles, 45 native sampler names and 9 schedulers in the test installation. Extension installations can change these counts. |
|
| 32 |
| LoRA loading | Four real CPU comparisons use tiny safetensors, Comfy's file loader, ModelPatcher and LoRA weight calculation. Results match the standard loader and expected low-rank math exactly. |
|
|
|
|
| 1 |
+
# Validation — 0.2.0 preview
|
| 2 |
|
| 3 |
+
Updated on 2026-09-29. This release has working ComfyUI registration, CPU contract tests and real local API checks. GPU image generation is still pending. The 0.2.0 interface checks below were made on 2026-09-29. Earlier Gallery and sampling observations retain their original scope.
|
| 4 |
+
|
| 5 |
+
## 0.2.0 interface and Manual defaults
|
| 6 |
+
|
| 7 |
+
- New and starter Engines use Manual with enhancement `none`. CPU schema and engine regressions confirm the defaults and the actionable empty-Enhanced error. Existing selected legacy presets still resolve.
|
| 8 |
+
- In the real Canvas frontend with the older DMAI Suite also installed, Manual displayed no preset selector, upload or export. Switching to Enhanced exposed Upload JSON. A local QA JSON imported successfully through the actual file chooser and validation route, showing 28 steps. Returning to Manual restored the edited 24-step draft.
|
| 9 |
+
- DPM++ 2M and Karras selections, the imported settings and the manual draft survived workflow save/reload. A native Prompter-to-Engine drag recreated the link; the saved graph contained the new link ID with unchanged port types.
|
| 10 |
+
- Inline LoRA CLIP strength changed from 0.4 to 0.3, independently of Model strength 0.65. Moving that LoRA to position 2 retained both values. Regression tests also cover invalid CLIP drafts blocking reorder/queue serialization.
|
| 11 |
+
- Canvas captions identify native sockets at 14 px. Connector tests cover hit-target clearance, minimum dimensions after loading/resizing, caption geometry, connected metadata and collapsed nodes.
|
| 12 |
+
- The rendered interface was inspected at 70% canvas zoom. Prompter Generate and LoRA Add remain visible at the new default sizes. Gallery uses a fixed selection footer with an independently scrolling image grid; its panel has no outer overflow in the measured fixture.
|
| 13 |
+
- The compact Enhanced card measured 616 px client height and 616 px scroll height, with Upload and Seed visible. Returning to Manual grew it to 716 px without overflow, restored 24 steps and removed the upload control.
|
| 14 |
+
- Preset mode and adaptive-height tests preserve larger saved/user dimensions, including Comfy's extra native widget padding. Package-specific LoRA classes avoid the matching global selectors in the older Suite and standalone LoRA extension.
|
| 15 |
+
- Versioned frontend imports and stylesheet URLs refresh the changed assets. Existing frontend initialization/deprecation warnings from the reference host remain; this release does not claim a warning-free third-party installation.
|
| 16 |
+
|
| 17 |
+
The images in the QA Gallery are synthetic solid-color fixtures. These checks do not add GPU inference or image-quality coverage.
|
| 18 |
|
| 19 |
## 0.1.3 coexistence and migration
|
| 20 |
|
|
|
|
| 39 |
|
| 40 |
| Check | Evidence |
|
| 41 |
| --- | --- |
|
| 42 |
+
| Python suite | 102 tests passed with optional real-Comfy CPU tests enabled, including registration coexistence, workflow migration, scoped native progress registry updates and multi-image progress. |
|
| 43 |
+
| JavaScript suite | 80 tests passed: dimensions, exact seeds, ordered manifests, strength edits, mode persistence, sampler overrides, ZIP selection, keyboard containment, Comfy change events, progress presentation, job lifecycle handling, native socket geometry, model-file selection and safe workflow migration. |
|
| 44 |
| Node registration | All seven native V3 nodes appear in real `/object_info`. |
|
| 45 |
| Runtime choices | Four model profiles, 45 native sampler names and 9 schedulers in the test installation. Extension installations can change these counts. |
|
| 46 |
| LoRA loading | Four real CPU comparisons use tiny safetensors, Comfy's file loader, ModelPatcher and LoRA weight calculation. Results match the standard loader and expected low-rank math exactly. |
|
pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
[project]
|
| 2 |
name = "dmai-nodes"
|
| 3 |
-
version = "0.
|
| 4 |
description = "Compact image workflows for ComfyUI: Prompter, ordered LoRAs, Generation Engine and Gallery."
|
| 5 |
authors = [{name = "DMAI / ceroomedia"}]
|
| 6 |
readme = "README.md"
|
|
|
|
| 1 |
[project]
|
| 2 |
name = "dmai-nodes"
|
| 3 |
+
version = "0.2.0"
|
| 4 |
description = "Compact image workflows for ComfyUI: Prompter, ordered LoRAs, Generation Engine and Gallery."
|
| 5 |
authors = [{name = "DMAI / ceroomedia"}]
|
| 6 |
readme = "README.md"
|
release.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "DMAI NODES",
|
| 3 |
-
"version": "0.
|
| 4 |
"channel": "preview",
|
| 5 |
"license": "MIT",
|
| 6 |
"comfyui_reference": "0.37.0",
|
|
|
|
| 1 |
{
|
| 2 |
"name": "DMAI NODES",
|
| 3 |
+
"version": "0.2.0",
|
| 4 |
"channel": "preview",
|
| 5 |
"license": "MIT",
|
| 6 |
"comfyui_reference": "0.37.0",
|
tools/build_release.py
CHANGED
|
@@ -7,7 +7,7 @@ import shutil
|
|
| 7 |
import zipfile
|
| 8 |
|
| 9 |
ROOT=Path(__file__).resolve().parents[1]
|
| 10 |
-
VERSION="0.
|
| 11 |
ROOT_FILES={"__init__.py","README.md","LICENSE","pyproject.toml","requirements.txt","THIRD_PARTY_NOTICES.md","CHANGELOG.md","release.json"}
|
| 12 |
DIRECTORIES={"dmai_nodes","web","docs","resources","licenses","workflows"}
|
| 13 |
FORBIDDEN={".git","__pycache__",".validation","node_modules","dist"}
|
|
|
|
| 7 |
import zipfile
|
| 8 |
|
| 9 |
ROOT=Path(__file__).resolve().parents[1]
|
| 10 |
+
VERSION="0.2.0"
|
| 11 |
ROOT_FILES={"__init__.py","README.md","LICENSE","pyproject.toml","requirements.txt","THIRD_PARTY_NOTICES.md","CHANGELOG.md","release.json"}
|
| 12 |
DIRECTORIES={"dmai_nodes","web","docs","resources","licenses","workflows"}
|
| 13 |
FORBIDDEN={".git","__pycache__",".validation","node_modules","dist"}
|
web/dmai-nodes.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
import { app } from "../../scripts/app.js";
|
| 2 |
import { api } from "../../scripts/api.js";
|
| 3 |
-
import { envelope } from "./core.mjs";
|
| 4 |
-
import { Prompter, LoRAStack } from "./inputs.mjs";
|
| 5 |
-
import { Engine } from "./engine.mjs";
|
| 6 |
-
import { Gallery } from "./gallery.mjs";
|
| 7 |
-
import { GenerationProgress } from "./progress.mjs";
|
| 8 |
-
import { PROMPTER_TYPE, migratePreviewPrompters } from "./workflow-migration.mjs";
|
| 9 |
|
| 10 |
const controllers = new WeakMap();
|
| 11 |
const types = {
|
|
@@ -74,7 +74,7 @@ app.registerExtension({
|
|
| 74 |
name: "DMAI.Nodes.Slate",
|
| 75 |
setup() {
|
| 76 |
progress.start();
|
| 77 |
-
const href = new URL("./nodes.css", import.meta.url).href;
|
| 78 |
if (!document.querySelector("link[data-dmai-nodes]")) {
|
| 79 |
const link = document.createElement("link");
|
| 80 |
link.rel = "stylesheet";
|
|
|
|
| 1 |
import { app } from "../../scripts/app.js";
|
| 2 |
import { api } from "../../scripts/api.js";
|
| 3 |
+
import { envelope } from "./core.mjs?v=0.2.0";
|
| 4 |
+
import { Prompter, LoRAStack } from "./inputs.mjs?v=0.2.0";
|
| 5 |
+
import { Engine } from "./engine.mjs?v=0.2.0";
|
| 6 |
+
import { Gallery } from "./gallery.mjs?v=0.2.0";
|
| 7 |
+
import { GenerationProgress } from "./progress.mjs?v=0.2.0";
|
| 8 |
+
import { PROMPTER_TYPE, migratePreviewPrompters } from "./workflow-migration.mjs?v=0.2.0";
|
| 9 |
|
| 10 |
const controllers = new WeakMap();
|
| 11 |
const types = {
|
|
|
|
| 74 |
name: "DMAI.Nodes.Slate",
|
| 75 |
setup() {
|
| 76 |
progress.start();
|
| 77 |
+
const href = new URL("./nodes.css?v=0.2.0", import.meta.url).href;
|
| 78 |
if (!document.querySelector("link[data-dmai-nodes]")) {
|
| 79 |
const link = document.createElement("link");
|
| 80 |
link.rel = "stylesheet";
|
web/dom.mjs
CHANGED
|
@@ -1,10 +1,35 @@
|
|
| 1 |
-
import { icon } from "./icons.mjs";
|
| 2 |
|
| 3 |
// Comfy's DOM wrapper receives pointer events across its whole rectangle. A
|
| 4 |
// real widget margin leaves canvas space for native socket hit targets; CSS
|
| 5 |
// padding on the panel alone would still leave the wrapper over the sockets.
|
| 6 |
export const SOCKET_GUTTER = 22;
|
| 7 |
export const SOCKET_INSET = 12;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
export function layoutNativeSockets(node) {
|
| 10 |
const width = node.size?.[0] ?? 360;
|
|
@@ -15,7 +40,7 @@ export function layoutNativeSockets(node) {
|
|
| 15 |
]) {
|
| 16 |
// Widget-backed inputs keep Comfy's own conversion, visibility and layout.
|
| 17 |
// In particular, our hidden JSON widgets must not consume a socket row.
|
| 18 |
-
const sockets =
|
| 19 |
const top = 75;
|
| 20 |
const bottom = Math.max(top, height - 40);
|
| 21 |
sockets.forEach((slot, index) => {
|
|
@@ -29,6 +54,40 @@ export function layoutNativeSockets(node) {
|
|
| 29 |
}
|
| 30 |
}
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
export const el = (tag, className = "", text) => {
|
| 33 |
const element = document.createElement(tag);
|
| 34 |
element.className = className;
|
|
@@ -179,8 +238,22 @@ export class Controller {
|
|
| 179 |
},
|
| 180 |
);
|
| 181 |
this.widget.serialize = false;
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
this.wrap("onDrawBackground", () => this.positionSlots());
|
|
|
|
| 184 |
this.wrap("onResize", () => this.positionSlots());
|
| 185 |
this.wrap("onRemoved", () => this.dispose());
|
| 186 |
const oldRemove = this.widget.onRemove?.bind(this.widget);
|
|
@@ -207,14 +280,29 @@ export class Controller {
|
|
| 207 |
};
|
| 208 |
}
|
| 209 |
size(width, height) {
|
|
|
|
| 210 |
this.minHeight = height;
|
|
|
|
| 211 |
this.node.setSize?.([
|
| 212 |
-
Math.max(
|
| 213 |
-
Math.max(
|
| 214 |
]);
|
| 215 |
this.positionSlots();
|
| 216 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
positionSlots() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
layoutNativeSockets(this.node);
|
| 219 |
}
|
| 220 |
set(value) {
|
|
|
|
| 1 |
+
import { icon } from "./icons.mjs?v=0.2.0";
|
| 2 |
|
| 3 |
// Comfy's DOM wrapper receives pointer events across its whole rectangle. A
|
| 4 |
// real widget margin leaves canvas space for native socket hit targets; CSS
|
| 5 |
// padding on the panel alone would still leave the wrapper over the sockets.
|
| 6 |
export const SOCKET_GUTTER = 22;
|
| 7 |
export const SOCKET_INSET = 12;
|
| 8 |
+
export const SOCKET_PITCH = 36;
|
| 9 |
+
export const SOCKET_LABEL_FONT = '500 14px "DMAI Inter", "Segoe UI", sans-serif';
|
| 10 |
+
|
| 11 |
+
const SOCKET_LABELS = {
|
| 12 |
+
request: "Prompt",
|
| 13 |
+
text: "Text",
|
| 14 |
+
loras: "LoRAs",
|
| 15 |
+
model: "Model",
|
| 16 |
+
clip: "CLIP",
|
| 17 |
+
vae: "VAE",
|
| 18 |
+
sampler: "Sampler",
|
| 19 |
+
sigmas: "Sigmas",
|
| 20 |
+
latent: "Latent",
|
| 21 |
+
positive: "Positive",
|
| 22 |
+
negative: "Negative",
|
| 23 |
+
images: "Images",
|
| 24 |
+
report: "Report",
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
const nativeSockets = (slots) => (slots ?? []).filter((slot) => !slot.widget);
|
| 28 |
+
|
| 29 |
+
export function minimumSocketHeight(node) {
|
| 30 |
+
const count = Math.max(nativeSockets(node.inputs).length, nativeSockets(node.outputs).length);
|
| 31 |
+
return count ? 75 + (count - 1) * SOCKET_PITCH + 40 : 0;
|
| 32 |
+
}
|
| 33 |
|
| 34 |
export function layoutNativeSockets(node) {
|
| 35 |
const width = node.size?.[0] ?? 360;
|
|
|
|
| 40 |
]) {
|
| 41 |
// Widget-backed inputs keep Comfy's own conversion, visibility and layout.
|
| 42 |
// In particular, our hidden JSON widgets must not consume a socket row.
|
| 43 |
+
const sockets = nativeSockets(slots);
|
| 44 |
const top = 75;
|
| 45 |
const bottom = Math.max(top, height - 40);
|
| 46 |
sockets.forEach((slot, index) => {
|
|
|
|
| 54 |
}
|
| 55 |
}
|
| 56 |
|
| 57 |
+
// These captions are paint only. The dots, hit targets and links remain Comfy's
|
| 58 |
+
// native sockets. Drawing just outside the card leaves its controls full width;
|
| 59 |
+
// lifting the caption above the dot also leaves the wire and drag target clear.
|
| 60 |
+
export function drawNativeSocketLabels(node, ctx) {
|
| 61 |
+
if (node.flags?.collapsed || node.collapsed || !ctx) return;
|
| 62 |
+
const width = node.size?.[0] ?? 360;
|
| 63 |
+
ctx.save();
|
| 64 |
+
ctx.font = SOCKET_LABEL_FONT;
|
| 65 |
+
ctx.textBaseline = "middle";
|
| 66 |
+
ctx.textAlign = "left";
|
| 67 |
+
ctx.lineWidth = 1;
|
| 68 |
+
for (const [slots, output] of [[node.inputs, false], [node.outputs, true]]) {
|
| 69 |
+
for (const slot of nativeSockets(slots)) {
|
| 70 |
+
if (!slot.pos) continue;
|
| 71 |
+
const label = SOCKET_LABELS[slot.name] ?? slot.label ?? slot.name;
|
| 72 |
+
if (!label) continue;
|
| 73 |
+
const labelWidth = ctx.measureText(label).width;
|
| 74 |
+
const boxWidth = labelWidth + 16;
|
| 75 |
+
const x = output ? width + 8 : -8 - boxWidth;
|
| 76 |
+
const y = slot.pos[1] - 34;
|
| 77 |
+
const connected = output ? Boolean(slot.links?.length) : slot.link != null;
|
| 78 |
+
ctx.fillStyle = "#191d22";
|
| 79 |
+
ctx.strokeStyle = connected ? "#4e6543" : "#343b46";
|
| 80 |
+
ctx.beginPath();
|
| 81 |
+
ctx.roundRect(x, y, boxWidth, 24, 6);
|
| 82 |
+
ctx.fill();
|
| 83 |
+
ctx.stroke();
|
| 84 |
+
ctx.fillStyle = connected ? "#d9edc2" : "#d5dbe4";
|
| 85 |
+
ctx.fillText(label, x + 8, y + 12);
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
ctx.restore();
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
export const el = (tag, className = "", text) => {
|
| 92 |
const element = document.createElement(tag);
|
| 93 |
element.className = className;
|
|
|
|
| 238 |
},
|
| 239 |
);
|
| 240 |
this.widget.serialize = false;
|
| 241 |
+
const computeSize = this.node.computeSize?.bind(this.node);
|
| 242 |
+
if (computeSize) {
|
| 243 |
+
this.node.computeSize = (...args) => {
|
| 244 |
+
const size = computeSize(...args);
|
| 245 |
+
const minimum = this.minimumSize();
|
| 246 |
+
size[0] = Math.max(size[0], minimum[0]);
|
| 247 |
+
size[1] = Math.max(size[1], minimum[1]);
|
| 248 |
+
return size;
|
| 249 |
+
};
|
| 250 |
+
}
|
| 251 |
+
this.wrap("onConfigure", () => queueMicrotask(() => {
|
| 252 |
+
this.positionSlots();
|
| 253 |
+
this.hydrate();
|
| 254 |
+
}));
|
| 255 |
this.wrap("onDrawBackground", () => this.positionSlots());
|
| 256 |
+
this.wrap("onDrawForeground", (ctx) => drawNativeSocketLabels(this.node, ctx));
|
| 257 |
this.wrap("onResize", () => this.positionSlots());
|
| 258 |
this.wrap("onRemoved", () => this.dispose());
|
| 259 |
const oldRemove = this.widget.onRemove?.bind(this.widget);
|
|
|
|
| 280 |
};
|
| 281 |
}
|
| 282 |
size(width, height) {
|
| 283 |
+
this.minWidth = width;
|
| 284 |
this.minHeight = height;
|
| 285 |
+
const minimum = this.minimumSize();
|
| 286 |
this.node.setSize?.([
|
| 287 |
+
Math.max(minimum[0], this.node.size?.[0] ?? 0),
|
| 288 |
+
Math.max(minimum[1], this.node.size?.[1] ?? 0),
|
| 289 |
]);
|
| 290 |
this.positionSlots();
|
| 291 |
}
|
| 292 |
+
minimumSize() {
|
| 293 |
+
return [
|
| 294 |
+
(this.minWidth ?? 0) + SOCKET_GUTTER * 2,
|
| 295 |
+
Math.max((this.minHeight ?? 0) + SOCKET_GUTTER * 2 + 4, minimumSocketHeight(this.node)),
|
| 296 |
+
];
|
| 297 |
+
}
|
| 298 |
positionSlots() {
|
| 299 |
+
const minimum = this.minimumSize();
|
| 300 |
+
const size = this.node.size ?? minimum;
|
| 301 |
+
if (size[0] < minimum[0] || size[1] < minimum[1]) {
|
| 302 |
+
// Assign through LiteGraph's size setter so its DOM layout store updates,
|
| 303 |
+
// without recursively dispatching onResize or adding a history edit.
|
| 304 |
+
this.node.size = [Math.max(size[0], minimum[0]), Math.max(size[1], minimum[1])];
|
| 305 |
+
}
|
| 306 |
layoutNativeSockets(this.node);
|
| 307 |
}
|
| 308 |
set(value) {
|
web/engine.mjs
CHANGED
|
@@ -5,17 +5,14 @@ import {
|
|
| 5 |
input,
|
| 6 |
select,
|
| 7 |
details,
|
| 8 |
-
|
| 9 |
-
statusMessage,
|
| 10 |
-
} from "./dom.mjs";
|
| 11 |
import {
|
| 12 |
readEngine,
|
| 13 |
validateSettings,
|
| 14 |
clone,
|
| 15 |
mergePresets,
|
| 16 |
envelope,
|
| 17 |
-
|
| 18 |
-
} from "./core.mjs";
|
| 19 |
import {
|
| 20 |
modelSource,
|
| 21 |
modelGroups,
|
|
@@ -23,12 +20,13 @@ import {
|
|
| 23 |
parseModelToken,
|
| 24 |
profileForSource,
|
| 25 |
inventoryName,
|
| 26 |
-
} from "./model-picker.mjs";
|
| 27 |
|
| 28 |
export class Engine extends Controller {
|
| 29 |
constructor(node, context) {
|
| 30 |
super(node, "config_json", "Generation Engine", "bolt", context);
|
| 31 |
-
this.
|
|
|
|
| 32 |
this.wrap("onConnectionsChange", () =>
|
| 33 |
queueMicrotask(() => {
|
| 34 |
if (!this.disposed && this.value && this.commitControls()) this.build();
|
|
@@ -45,6 +43,15 @@ export class Engine extends Controller {
|
|
| 45 |
});
|
| 46 |
this.hydrate();
|
| 47 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
hydrate() {
|
| 49 |
if (this.disposed) return;
|
| 50 |
const revision = (this.revision = (this.revision ?? 0) + 1);
|
|
@@ -78,6 +85,17 @@ export class Engine extends Controller {
|
|
| 78 |
p.id === this.value.preset_id && p.model.id === this.value.profile_id,
|
| 79 |
);
|
| 80 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
effective() {
|
| 82 |
return this.value.mode === "enhanced"
|
| 83 |
? (this.chosen()?.settings ?? this.value.settings)
|
|
@@ -125,13 +143,15 @@ export class Engine extends Controller {
|
|
| 125 |
if (mode === "enhanced") {
|
| 126 |
const preset =
|
| 127 |
this.chosen() ??
|
| 128 |
-
this.presets
|
| 129 |
-
if (
|
| 130 |
-
this.
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
}
|
| 133 |
-
this.value.preset_id = preset.id;
|
| 134 |
-
this.value.settings = clone(preset.settings);
|
| 135 |
} else
|
| 136 |
this.value.settings = clone(
|
| 137 |
this.manualDrafts()[this.value.profile_id] ?? this.effective(),
|
|
@@ -150,7 +170,6 @@ export class Engine extends Controller {
|
|
| 150 |
const profile = this.data.profiles.find((p) => p.id === id);
|
| 151 |
if (!profile) return false;
|
| 152 |
const saved = this.drafts()[id],
|
| 153 |
-
preset = this.presets().find((p) => p.model.id === id),
|
| 154 |
models = {
|
| 155 |
diffusion_model: "",
|
| 156 |
text_encoder: "",
|
|
@@ -167,9 +186,9 @@ export class Engine extends Controller {
|
|
| 167 |
saved
|
| 168 |
? clone(saved)
|
| 169 |
: {
|
| 170 |
-
mode:
|
| 171 |
-
preset_id:
|
| 172 |
-
settings: clone(
|
| 173 |
models,
|
| 174 |
},
|
| 175 |
);
|
|
@@ -241,6 +260,22 @@ export class Engine extends Controller {
|
|
| 241 |
});
|
| 242 |
return picker.field;
|
| 243 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
build() {
|
| 245 |
this.modelFilesExpanded = this.modelFilesDetails?.open ?? this.modelFilesExpanded;
|
| 246 |
this.body.replaceChildren();
|
|
@@ -248,14 +283,15 @@ export class Engine extends Controller {
|
|
| 248 |
this.commitSeed = null;
|
| 249 |
const profile = this.profile(),
|
| 250 |
s = this.effective();
|
|
|
|
| 251 |
this.body.append(this.modelPicker(profile));
|
| 252 |
if (["model", "clip", "vae"].every((name) => this.linked(name)))
|
| 253 |
this.body.append(el("div", "dmai-badge", "Connected model · CLIP · VAE"));
|
| 254 |
this.modelFiles(profile);
|
| 255 |
const mode = el("div", "dmai-segmented dmai-mode");
|
| 256 |
for (const [value, label, glyph] of [
|
| 257 |
-
["enhanced", "DMAI Enhanced", "spark"],
|
| 258 |
["manual", "Manual", "sliders"],
|
|
|
|
| 259 |
]) {
|
| 260 |
const b = button(label, glyph, () => this.applyMode(value), true);
|
| 261 |
b.setAttribute("aria-pressed", String(this.value.mode === value));
|
|
@@ -263,86 +299,8 @@ export class Engine extends Controller {
|
|
| 263 |
mode.append(b);
|
| 264 |
}
|
| 265 |
this.body.append(mode);
|
| 266 |
-
const presetRow = el("div", "dmai-preset-row"),
|
| 267 |
-
presetSelect = select(
|
| 268 |
-
"Preset",
|
| 269 |
-
[
|
| 270 |
-
{
|
| 271 |
-
value: "",
|
| 272 |
-
label:
|
| 273 |
-
this.value.mode === "manual"
|
| 274 |
-
? "Custom settings"
|
| 275 |
-
: "Choose a preset",
|
| 276 |
-
},
|
| 277 |
-
...this.presets()
|
| 278 |
-
.filter((p) => p.model.id === this.value.profile_id)
|
| 279 |
-
.map((p) => ({ value: p.id, label: p.name })),
|
| 280 |
-
],
|
| 281 |
-
this.value.mode === "enhanced" ? this.value.preset_id : "",
|
| 282 |
-
);
|
| 283 |
-
presetSelect.control.disabled = !this.data;
|
| 284 |
-
presetSelect.control.addEventListener("change", () => {
|
| 285 |
-
const p = this.presets().find((p) => p.id === presetSelect.control.value);
|
| 286 |
-
if (!p) return;
|
| 287 |
-
if (!this.commitControls()) {
|
| 288 |
-
presetSelect.control.value =
|
| 289 |
-
this.value.mode === "enhanced" ? this.value.preset_id : "";
|
| 290 |
-
return;
|
| 291 |
-
}
|
| 292 |
-
if (this.value.mode === "manual")
|
| 293 |
-
this.manualDrafts()[this.value.profile_id] = clone(this.value.settings);
|
| 294 |
-
this.value.mode = "enhanced";
|
| 295 |
-
this.value.preset_id = p.id;
|
| 296 |
-
this.value.settings = clone(p.settings);
|
| 297 |
-
this.persist();
|
| 298 |
-
this.build();
|
| 299 |
-
});
|
| 300 |
-
const file = el("input");
|
| 301 |
-
file.type = "file";
|
| 302 |
-
file.accept = ".json,application/json";
|
| 303 |
-
file.hidden = true;
|
| 304 |
-
file.addEventListener("change", () =>
|
| 305 |
-
this.guard(async () => {
|
| 306 |
-
const f = file.files?.[0];
|
| 307 |
-
if (f) await this.importFile(f);
|
| 308 |
-
file.value = "";
|
| 309 |
-
}),
|
| 310 |
-
);
|
| 311 |
-
const importButton = button("Import preset JSON", "upload", () =>
|
| 312 |
-
file.click(),
|
| 313 |
-
),
|
| 314 |
-
exportButton = button("Export preset JSON", "download", () =>
|
| 315 |
-
this.exportPreset(),
|
| 316 |
-
);
|
| 317 |
-
importButton.disabled = !this.data;
|
| 318 |
-
exportButton.disabled = !profile;
|
| 319 |
-
presetRow.append(presetSelect.field, importButton, exportButton, file);
|
| 320 |
-
this.body.append(presetRow);
|
| 321 |
-
const meta = el("div", "dmai-row dmai-preset-meta"),
|
| 322 |
-
origin =
|
| 323 |
-
this.value.mode === "manual"
|
| 324 |
-
? "Manual"
|
| 325 |
-
: this.value.presets.some((p) => p.id === this.value.preset_id)
|
| 326 |
-
? "Imported"
|
| 327 |
-
: "DMAI";
|
| 328 |
-
meta.append(
|
| 329 |
-
el("span", "dmai-badge", origin),
|
| 330 |
-
el(
|
| 331 |
-
"span",
|
| 332 |
-
"dmai-muted",
|
| 333 |
-
`${this.linked("sigmas") ? "External sigmas" : `${s.steps} steps`} · CFG ${s.cfg}`,
|
| 334 |
-
),
|
| 335 |
-
);
|
| 336 |
-
this.body.append(meta);
|
| 337 |
if (this.value.mode === "manual") this.manual(s, profile);
|
| 338 |
-
else
|
| 339 |
-
this.body.append(
|
| 340 |
-
el(
|
| 341 |
-
"div",
|
| 342 |
-
"dmai-engine-summary",
|
| 343 |
-
`${this.samplingSummary(s)}${s.enhancer === "krea2t" ? " · Enhancer" : ""}`,
|
| 344 |
-
),
|
| 345 |
-
);
|
| 346 |
if (this.linked("sampler") || this.linked("sigmas"))
|
| 347 |
this.body.append(
|
| 348 |
el(
|
|
@@ -396,6 +354,72 @@ export class Engine extends Controller {
|
|
| 396 |
),
|
| 397 |
);
|
| 398 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
modelFiles(profile) {
|
| 400 |
const advanced = details("Model files");
|
| 401 |
this.modelFilesDetails = advanced.root;
|
|
@@ -477,15 +501,6 @@ export class Engine extends Controller {
|
|
| 477 |
true,
|
| 478 |
),
|
| 479 |
);
|
| 480 |
-
if (this.value.presets.length)
|
| 481 |
-
advanced.body.append(
|
| 482 |
-
button(
|
| 483 |
-
"Manage imported presets",
|
| 484 |
-
"layers",
|
| 485 |
-
() => this.managePresets(),
|
| 486 |
-
true,
|
| 487 |
-
),
|
| 488 |
-
);
|
| 489 |
if (profile?.default_guidance !== undefined && !this.linked("positive"))
|
| 490 |
advanced.body.append(
|
| 491 |
el(
|
|
@@ -498,7 +513,7 @@ export class Engine extends Controller {
|
|
| 498 |
}
|
| 499 |
manual(settings, profile) {
|
| 500 |
const grid = el("div", "dmai-settings");
|
| 501 |
-
const controls = {};
|
| 502 |
for (const [name, label, type, min, max, step] of [
|
| 503 |
["steps", "Steps", "number", 1, 150, 1],
|
| 504 |
["cfg", "CFG", "number", 0, 30, 0.1],
|
|
@@ -508,7 +523,7 @@ export class Engine extends Controller {
|
|
| 508 |
Object.assign(f.control, { min, max, step });
|
| 509 |
f.field.hidden = this.linked("sigmas") && name !== "cfg";
|
| 510 |
controls[name] = f.control;
|
| 511 |
-
|
| 512 |
}
|
| 513 |
for (const [name, label, items] of [
|
| 514 |
["sampler", "Sampler", this.data?.samplers ?? [settings.sampler]],
|
|
@@ -520,12 +535,16 @@ export class Engine extends Controller {
|
|
| 520 |
],
|
| 521 |
]) {
|
| 522 |
const f = select(label, items, settings[name]);
|
|
|
|
|
|
|
| 523 |
f.field.hidden =
|
| 524 |
(name === "sampler" && this.linked("sampler")) ||
|
| 525 |
-
(name === "scheduler" && this.linked("sigmas"))
|
|
|
|
| 526 |
controls[name] = f.control;
|
| 527 |
-
|
| 528 |
}
|
|
|
|
| 529 |
this.commitManual = () => {
|
| 530 |
try {
|
| 531 |
const next = validateSettings({
|
|
@@ -555,7 +574,10 @@ export class Engine extends Controller {
|
|
| 555 |
this.body.append(grid);
|
| 556 |
}
|
| 557 |
async importFile(file) {
|
|
|
|
|
|
|
| 558 |
if (!this.commitControls()) return;
|
|
|
|
| 559 |
if (file.size > 128 * 1024)
|
| 560 |
throw new Error("Preset files must be 128 KiB or smaller.");
|
| 561 |
let payload;
|
|
@@ -570,12 +592,14 @@ export class Engine extends Controller {
|
|
| 570 |
body: JSON.stringify(payload),
|
| 571 |
});
|
| 572 |
if (this.disposed || !this.commitControls()) return;
|
|
|
|
|
|
|
| 573 |
const presets = mergePresets(
|
| 574 |
this.value.presets,
|
| 575 |
result.presets,
|
| 576 |
this.data.presets,
|
| 577 |
);
|
| 578 |
-
const first = result.presets[0];
|
| 579 |
this.value.presets = presets;
|
| 580 |
if (this.value.profile_id !== first.model.id)
|
| 581 |
this.changeProfile(first.model.id);
|
|
@@ -626,48 +650,4 @@ export class Engine extends Controller {
|
|
| 626 |
};
|
| 627 |
draw();
|
| 628 |
}
|
| 629 |
-
exportPreset() {
|
| 630 |
-
if (!this.commitControls()) return;
|
| 631 |
-
const d = this.show("Export preset"),
|
| 632 |
-
name = input("Preset name", "text", "My settings"),
|
| 633 |
-
message = el("p", "dmai-status"),
|
| 634 |
-
save = button(
|
| 635 |
-
"Download JSON",
|
| 636 |
-
"download",
|
| 637 |
-
() =>
|
| 638 |
-
this.guard(async () => {
|
| 639 |
-
try {
|
| 640 |
-
const p = this.profile(),
|
| 641 |
-
payload = {
|
| 642 |
-
schema_version: 1,
|
| 643 |
-
id: `custom-${uid()}`,
|
| 644 |
-
name: name.control.value,
|
| 645 |
-
model: { id: p.id, label: p.label, family: p.family },
|
| 646 |
-
settings: clone(this.effective()),
|
| 647 |
-
};
|
| 648 |
-
const result = await envelope(
|
| 649 |
-
this.context.api,
|
| 650 |
-
"/presets/validate",
|
| 651 |
-
{
|
| 652 |
-
method: "POST",
|
| 653 |
-
headers: { "Content-Type": "application/json" },
|
| 654 |
-
body: JSON.stringify(payload),
|
| 655 |
-
},
|
| 656 |
-
);
|
| 657 |
-
saveBlob(
|
| 658 |
-
new Blob([JSON.stringify(result.presets[0], null, 2) + "\n"], {
|
| 659 |
-
type: "application/json",
|
| 660 |
-
}),
|
| 661 |
-
`${payload.id}.json`,
|
| 662 |
-
);
|
| 663 |
-
d.root.close();
|
| 664 |
-
} catch (error) {
|
| 665 |
-
statusMessage(message, error.message, true);
|
| 666 |
-
}
|
| 667 |
-
}),
|
| 668 |
-
true,
|
| 669 |
-
);
|
| 670 |
-
save.classList.add("dmai-primary");
|
| 671 |
-
d.body.append(name.field, message, save);
|
| 672 |
-
}
|
| 673 |
}
|
|
|
|
| 5 |
input,
|
| 6 |
select,
|
| 7 |
details,
|
| 8 |
+
} from "./dom.mjs?v=0.2.0";
|
|
|
|
|
|
|
| 9 |
import {
|
| 10 |
readEngine,
|
| 11 |
validateSettings,
|
| 12 |
clone,
|
| 13 |
mergePresets,
|
| 14 |
envelope,
|
| 15 |
+
} from "./core.mjs?v=0.2.0";
|
|
|
|
| 16 |
import {
|
| 17 |
modelSource,
|
| 18 |
modelGroups,
|
|
|
|
| 20 |
parseModelToken,
|
| 21 |
profileForSource,
|
| 22 |
inventoryName,
|
| 23 |
+
} from "./model-picker.mjs?v=0.2.0";
|
| 24 |
|
| 25 |
export class Engine extends Controller {
|
| 26 |
constructor(node, context) {
|
| 27 |
super(node, "config_json", "Generation Engine", "bolt", context);
|
| 28 |
+
this.root.classList.add("dmai-engine");
|
| 29 |
+
this.initializeSize();
|
| 30 |
this.wrap("onConnectionsChange", () =>
|
| 31 |
queueMicrotask(() => {
|
| 32 |
if (!this.disposed && this.value && this.commitControls()) this.build();
|
|
|
|
| 43 |
});
|
| 44 |
this.hydrate();
|
| 45 |
}
|
| 46 |
+
initializeSize() {
|
| 47 |
+
let height = 700;
|
| 48 |
+
try {
|
| 49 |
+
if (readEngine(this.native.value).mode === "enhanced") height = 600;
|
| 50 |
+
} catch {
|
| 51 |
+
// hydrate() reports invalid saved data without replacing its value.
|
| 52 |
+
}
|
| 53 |
+
this.size(440, height);
|
| 54 |
+
}
|
| 55 |
hydrate() {
|
| 56 |
if (this.disposed) return;
|
| 57 |
const revision = (this.revision = (this.revision ?? 0) + 1);
|
|
|
|
| 85 |
p.id === this.value.preset_id && p.model.id === this.value.profile_id,
|
| 86 |
);
|
| 87 |
}
|
| 88 |
+
availablePresets() {
|
| 89 |
+
// The built-in catalog is retained only for saved-workflow compatibility.
|
| 90 |
+
// New choices in the interface are the JSON settings the owner imported.
|
| 91 |
+
const presets = this.value.presets.filter(
|
| 92 |
+
(preset) => preset.model.id === this.value.profile_id,
|
| 93 |
+
);
|
| 94 |
+
const current = this.chosen();
|
| 95 |
+
if (current && !presets.some((preset) => preset.id === current.id))
|
| 96 |
+
presets.unshift(current);
|
| 97 |
+
return presets;
|
| 98 |
+
}
|
| 99 |
effective() {
|
| 100 |
return this.value.mode === "enhanced"
|
| 101 |
? (this.chosen()?.settings ?? this.value.settings)
|
|
|
|
| 143 |
if (mode === "enhanced") {
|
| 144 |
const preset =
|
| 145 |
this.chosen() ??
|
| 146 |
+
this.value.presets.find((p) => p.model.id === this.value.profile_id);
|
| 147 |
+
if (preset) {
|
| 148 |
+
this.value.preset_id = preset.id;
|
| 149 |
+
this.value.settings = clone(preset.settings);
|
| 150 |
+
} else {
|
| 151 |
+
// Enhanced must be reachable before a JSON file exists. Execution
|
| 152 |
+
// validates the missing preset, while Manual keeps its separate draft.
|
| 153 |
+
this.value.preset_id = "";
|
| 154 |
}
|
|
|
|
|
|
|
| 155 |
} else
|
| 156 |
this.value.settings = clone(
|
| 157 |
this.manualDrafts()[this.value.profile_id] ?? this.effective(),
|
|
|
|
| 170 |
const profile = this.data.profiles.find((p) => p.id === id);
|
| 171 |
if (!profile) return false;
|
| 172 |
const saved = this.drafts()[id],
|
|
|
|
| 173 |
models = {
|
| 174 |
diffusion_model: "",
|
| 175 |
text_encoder: "",
|
|
|
|
| 186 |
saved
|
| 187 |
? clone(saved)
|
| 188 |
: {
|
| 189 |
+
mode: "manual",
|
| 190 |
+
preset_id: "",
|
| 191 |
+
settings: clone(profile.default_settings),
|
| 192 |
models,
|
| 193 |
},
|
| 194 |
);
|
|
|
|
| 260 |
});
|
| 261 |
return picker.field;
|
| 262 |
}
|
| 263 |
+
fitHeight(profile) {
|
| 264 |
+
const current = this.node.size?.[1] ?? 0,
|
| 265 |
+
previousMinimum = Math.max(this.minimumSize()[1], this.node.computeSize?.()?.[1] ?? 0);
|
| 266 |
+
// Remember an explicit larger size across automatic growth (for example,
|
| 267 |
+
// a Krea enhancer row), so changing modes cannot erase the user's height.
|
| 268 |
+
if (this.lastEngineHeight === undefined || Math.abs(current - this.lastEngineHeight) > 2)
|
| 269 |
+
this.preferredEngineHeight = current > previousMinimum + 2 ? current : undefined;
|
| 270 |
+
this.minHeight = this.value.mode === "enhanced" ? 600 : profile?.family === "krea2" ? 770 : 700;
|
| 271 |
+
const minimum = this.minimumSize(),
|
| 272 |
+
height = Math.max(minimum[1], this.node.computeSize?.()?.[1] ?? 0, this.preferredEngineHeight ?? 0),
|
| 273 |
+
width = Math.max(minimum[0], this.node.size?.[0] ?? 0);
|
| 274 |
+
if (height !== current || width !== this.node.size?.[0])
|
| 275 |
+
this.node.setSize?.([width, height]);
|
| 276 |
+
this.positionSlots();
|
| 277 |
+
this.lastEngineHeight = this.node.size?.[1] ?? height;
|
| 278 |
+
}
|
| 279 |
build() {
|
| 280 |
this.modelFilesExpanded = this.modelFilesDetails?.open ?? this.modelFilesExpanded;
|
| 281 |
this.body.replaceChildren();
|
|
|
|
| 283 |
this.commitSeed = null;
|
| 284 |
const profile = this.profile(),
|
| 285 |
s = this.effective();
|
| 286 |
+
this.fitHeight(profile);
|
| 287 |
this.body.append(this.modelPicker(profile));
|
| 288 |
if (["model", "clip", "vae"].every((name) => this.linked(name)))
|
| 289 |
this.body.append(el("div", "dmai-badge", "Connected model · CLIP · VAE"));
|
| 290 |
this.modelFiles(profile);
|
| 291 |
const mode = el("div", "dmai-segmented dmai-mode");
|
| 292 |
for (const [value, label, glyph] of [
|
|
|
|
| 293 |
["manual", "Manual", "sliders"],
|
| 294 |
+
["enhanced", "DMAI Enhanced", "spark"],
|
| 295 |
]) {
|
| 296 |
const b = button(label, glyph, () => this.applyMode(value), true);
|
| 297 |
b.setAttribute("aria-pressed", String(this.value.mode === value));
|
|
|
|
| 299 |
mode.append(b);
|
| 300 |
}
|
| 301 |
this.body.append(mode);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
if (this.value.mode === "manual") this.manual(s, profile);
|
| 303 |
+
else this.enhanced(s);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
if (this.linked("sampler") || this.linked("sigmas"))
|
| 305 |
this.body.append(
|
| 306 |
el(
|
|
|
|
| 354 |
),
|
| 355 |
);
|
| 356 |
}
|
| 357 |
+
enhanced(settings) {
|
| 358 |
+
const panel = el("section", "dmai-enhanced"),
|
| 359 |
+
preset = this.chosen(),
|
| 360 |
+
choices = this.availablePresets();
|
| 361 |
+
panel.setAttribute("aria-label", "DMAI Enhanced settings");
|
| 362 |
+
if (choices.length > 1) {
|
| 363 |
+
const picker = select(
|
| 364 |
+
"DMAI settings",
|
| 365 |
+
choices.map((entry) => ({ value: entry.id, label: entry.name })),
|
| 366 |
+
this.value.preset_id,
|
| 367 |
+
);
|
| 368 |
+
picker.control.addEventListener("change", () => {
|
| 369 |
+
if (!this.selectPreset(picker.control.value))
|
| 370 |
+
picker.control.value = this.value.preset_id;
|
| 371 |
+
});
|
| 372 |
+
panel.append(picker.field);
|
| 373 |
+
} else {
|
| 374 |
+
const title = el("div", "dmai-enhanced-title", preset?.name ?? "Your DMAI settings");
|
| 375 |
+
if (preset)
|
| 376 |
+
title.title = this.value.presets.some((entry) => entry.id === preset.id)
|
| 377 |
+
? "Imported settings" : "Saved workflow settings";
|
| 378 |
+
panel.append(title);
|
| 379 |
+
if (!preset)
|
| 380 |
+
panel.append(el("p", "dmai-enhanced-copy", "Upload a DMAI JSON file for this model."));
|
| 381 |
+
}
|
| 382 |
+
if (preset) {
|
| 383 |
+
panel.append(
|
| 384 |
+
el("div", "dmai-enhanced-summary", `${this.linked("sigmas") ? "Connected sigmas" : `${settings.steps} steps`} · CFG ${settings.cfg}`),
|
| 385 |
+
el("div", "dmai-enhanced-sampling", this.samplingSummary(settings)),
|
| 386 |
+
);
|
| 387 |
+
if (settings.enhancer === "krea2t")
|
| 388 |
+
panel.append(el("div", "dmai-badge", "Krea enhancer enabled"));
|
| 389 |
+
}
|
| 390 |
+
const file = el("input");
|
| 391 |
+
file.type = "file";
|
| 392 |
+
file.accept = ".json,application/json";
|
| 393 |
+
file.hidden = true;
|
| 394 |
+
file.addEventListener("change", () =>
|
| 395 |
+
this.guard(async () => {
|
| 396 |
+
try {
|
| 397 |
+
const selected = file.files?.[0];
|
| 398 |
+
if (selected) await this.importFile(selected);
|
| 399 |
+
} finally {
|
| 400 |
+
file.value = "";
|
| 401 |
+
}
|
| 402 |
+
}),
|
| 403 |
+
);
|
| 404 |
+
const actions = el("div", "dmai-enhanced-actions"),
|
| 405 |
+
upload = button("Upload JSON", "upload", () => file.click(), true);
|
| 406 |
+
upload.disabled = !this.data;
|
| 407 |
+
actions.append(upload, file);
|
| 408 |
+
if (this.value.presets.length)
|
| 409 |
+
actions.append(button("Manage imported settings", "layers", () => this.managePresets()));
|
| 410 |
+
panel.append(actions);
|
| 411 |
+
this.body.append(panel);
|
| 412 |
+
}
|
| 413 |
+
selectPreset(id) {
|
| 414 |
+
const preset = this.availablePresets().find((entry) => entry.id === id);
|
| 415 |
+
if (!preset || !this.commitControls()) return false;
|
| 416 |
+
this.value.mode = "enhanced";
|
| 417 |
+
this.value.preset_id = preset.id;
|
| 418 |
+
this.value.settings = clone(preset.settings);
|
| 419 |
+
this.persist();
|
| 420 |
+
this.build();
|
| 421 |
+
return true;
|
| 422 |
+
}
|
| 423 |
modelFiles(profile) {
|
| 424 |
const advanced = details("Model files");
|
| 425 |
this.modelFilesDetails = advanced.root;
|
|
|
|
| 501 |
true,
|
| 502 |
),
|
| 503 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
if (profile?.default_guidance !== undefined && !this.linked("positive"))
|
| 505 |
advanced.body.append(
|
| 506 |
el(
|
|
|
|
| 513 |
}
|
| 514 |
manual(settings, profile) {
|
| 515 |
const grid = el("div", "dmai-settings");
|
| 516 |
+
const controls = {}, fields = {};
|
| 517 |
for (const [name, label, type, min, max, step] of [
|
| 518 |
["steps", "Steps", "number", 1, 150, 1],
|
| 519 |
["cfg", "CFG", "number", 0, 30, 0.1],
|
|
|
|
| 523 |
Object.assign(f.control, { min, max, step });
|
| 524 |
f.field.hidden = this.linked("sigmas") && name !== "cfg";
|
| 525 |
controls[name] = f.control;
|
| 526 |
+
fields[name] = f.field;
|
| 527 |
}
|
| 528 |
for (const [name, label, items] of [
|
| 529 |
["sampler", "Sampler", this.data?.samplers ?? [settings.sampler]],
|
|
|
|
| 535 |
],
|
| 536 |
]) {
|
| 537 |
const f = select(label, items, settings[name]);
|
| 538 |
+
if (name === "sampler" || name === "enhancer")
|
| 539 |
+
f.field.classList.add("dmai-field-wide");
|
| 540 |
f.field.hidden =
|
| 541 |
(name === "sampler" && this.linked("sampler")) ||
|
| 542 |
+
(name === "scheduler" && this.linked("sigmas")) ||
|
| 543 |
+
(name === "enhancer" && profile?.family !== "krea2");
|
| 544 |
controls[name] = f.control;
|
| 545 |
+
fields[name] = f.field;
|
| 546 |
}
|
| 547 |
+
grid.append(...["steps", "cfg", "sampler", "scheduler", "denoise", "enhancer"].map((name) => fields[name]));
|
| 548 |
this.commitManual = () => {
|
| 549 |
try {
|
| 550 |
const next = validateSettings({
|
|
|
|
| 574 |
this.body.append(grid);
|
| 575 |
}
|
| 576 |
async importFile(file) {
|
| 577 |
+
if (this.value.mode !== "enhanced")
|
| 578 |
+
throw new Error("Switch to DMAI Enhanced to upload JSON settings.");
|
| 579 |
if (!this.commitControls()) return;
|
| 580 |
+
const profileId = this.value.profile_id;
|
| 581 |
if (file.size > 128 * 1024)
|
| 582 |
throw new Error("Preset files must be 128 KiB or smaller.");
|
| 583 |
let payload;
|
|
|
|
| 592 |
body: JSON.stringify(payload),
|
| 593 |
});
|
| 594 |
if (this.disposed || !this.commitControls()) return;
|
| 595 |
+
if (this.value.mode !== "enhanced" || this.value.profile_id !== profileId)
|
| 596 |
+
throw new Error("Engine mode or model changed during upload. Upload the JSON again in DMAI Enhanced.");
|
| 597 |
const presets = mergePresets(
|
| 598 |
this.value.presets,
|
| 599 |
result.presets,
|
| 600 |
this.data.presets,
|
| 601 |
);
|
| 602 |
+
const first = result.presets.find((preset) => preset.model.id === profileId) ?? result.presets[0];
|
| 603 |
this.value.presets = presets;
|
| 604 |
if (this.value.profile_id !== first.model.id)
|
| 605 |
this.changeProfile(first.model.id);
|
|
|
|
| 650 |
};
|
| 651 |
draw();
|
| 652 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 653 |
}
|
web/gallery.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
-
import { Controller, el, button, saveBlob } from "./dom.mjs";
|
| 2 |
-
import { API, envelope, binary, selectionBody, uid } from "./core.mjs";
|
| 3 |
|
| 4 |
export class Gallery extends Controller {
|
| 5 |
constructor(node, context) {
|
| 6 |
super(node, "gallery_id", "Gallery", "grid", context);
|
| 7 |
-
this.
|
|
|
|
| 8 |
this.items = [];
|
| 9 |
this.total = 0;
|
| 10 |
this.watermark = 0;
|
|
|
|
| 1 |
+
import { Controller, el, button, saveBlob } from "./dom.mjs?v=0.2.0";
|
| 2 |
+
import { API, envelope, binary, selectionBody, uid } from "./core.mjs?v=0.2.0";
|
| 3 |
|
| 4 |
export class Gallery extends Controller {
|
| 5 |
constructor(node, context) {
|
| 6 |
super(node, "gallery_id", "Gallery", "grid", context);
|
| 7 |
+
this.root.classList.add("dmai-gallery");
|
| 8 |
+
this.size(760, 570);
|
| 9 |
this.items = [];
|
| 10 |
this.total = 0;
|
| 11 |
this.watermark = 0;
|
web/inputs.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
| 5 |
input,
|
| 6 |
details,
|
| 7 |
statusMessage,
|
| 8 |
-
} from "./dom.mjs";
|
| 9 |
import {
|
| 10 |
RATIOS,
|
| 11 |
dimensions,
|
|
@@ -14,8 +14,8 @@ import {
|
|
| 14 |
readManifest,
|
| 15 |
moveEntry,
|
| 16 |
uid,
|
| 17 |
-
} from "./core.mjs";
|
| 18 |
-
import { ProgressView, progressPresentation } from "./progress-view.mjs";
|
| 19 |
|
| 20 |
export class Prompter extends Controller {
|
| 21 |
constructor(node, context) {
|
|
@@ -26,7 +26,7 @@ export class Prompter extends Controller {
|
|
| 26 |
this.root.append(scroll);
|
| 27 |
this.progress = { phase: "idle", percent: 0 };
|
| 28 |
this.progressView = new ProgressView(this.root);
|
| 29 |
-
this.size(
|
| 30 |
this.hydrate();
|
| 31 |
this.stopProgress = context.watchProgress?.(node, (state) => {
|
| 32 |
if (!this.disposed) this.updateProgress(state);
|
|
@@ -55,14 +55,24 @@ export class Prompter extends Controller {
|
|
| 55 |
this.value.prompt = prompt.value;
|
| 56 |
this.persist();
|
| 57 |
});
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
this.ratioRow = el("div", "dmai-ratios");
|
|
|
|
|
|
|
| 60 |
for (const ratio of ["1:1", "4:5", "9:16", "16:9"])
|
| 61 |
this.ratioRow.append(this.ratioButton(ratio));
|
| 62 |
-
this.
|
| 63 |
-
|
| 64 |
-
);
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
| 66 |
const resolutionRow = el("div", "dmai-resolution"),
|
| 67 |
group = el("div", "dmai-segmented");
|
| 68 |
this.resolutionButtons = {};
|
|
@@ -78,9 +88,12 @@ export class Prompter extends Controller {
|
|
| 78 |
}
|
| 79 |
this.dimensions = el("output", "dmai-dimensions");
|
| 80 |
resolutionRow.append(group, this.dimensions);
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
| 82 |
const countRow = el("div", "dmai-row dmai-count");
|
| 83 |
-
countRow.append(el("span", "dmai-
|
| 84 |
this.less = button("One fewer image", "minus", () => this.count(-1));
|
| 85 |
this.more = button("One more image", "plus", () => this.count(1));
|
| 86 |
this.countOutput = el("output");
|
|
@@ -88,8 +101,9 @@ export class Prompter extends Controller {
|
|
| 88 |
const stepper = el("div", "dmai-stepper");
|
| 89 |
stepper.append(this.less, this.countOutput, this.more);
|
| 90 |
countRow.append(stepper);
|
| 91 |
-
|
| 92 |
-
|
|
|
|
| 93 |
const negative = el("textarea", "dmai-negative");
|
| 94 |
negative.value = this.value.negative_prompt;
|
| 95 |
negative.placeholder = "Negative prompt";
|
|
@@ -279,6 +293,7 @@ export class Prompter extends Controller {
|
|
| 279 |
export class LoRAStack extends Controller {
|
| 280 |
constructor(node, context) {
|
| 281 |
super(node, "manifest_json", "LoRA Loader", "layers", context);
|
|
|
|
| 282 |
const serialize = this.native.serializeValue?.bind(this.native);
|
| 283 |
this.native.serializeValue = (...args) => {
|
| 284 |
if (!this.syncStrengths())
|
|
@@ -288,7 +303,7 @@ export class LoRAStack extends Controller {
|
|
| 288 |
readManifest(this.native.value);
|
| 289 |
return serialize ? serialize(...args) : this.native.value;
|
| 290 |
};
|
| 291 |
-
this.size(
|
| 292 |
this.hydrate();
|
| 293 |
}
|
| 294 |
hydrate() {
|
|
@@ -304,26 +319,28 @@ export class LoRAStack extends Controller {
|
|
| 304 |
persist() {
|
| 305 |
this.set(this.value);
|
| 306 |
}
|
| 307 |
-
updateStrength(id, text) {
|
|
|
|
| 308 |
const value = text.trim() === "" ? NaN : Number(text);
|
| 309 |
if (!Number.isFinite(value) || value < -20 || value > 20) {
|
| 310 |
this.error(
|
| 311 |
new Error(
|
| 312 |
-
"
|
| 313 |
),
|
| 314 |
);
|
| 315 |
return false;
|
| 316 |
}
|
| 317 |
const entry = this.value.entries.find((item) => item.id === id);
|
| 318 |
if (!entry) return false;
|
| 319 |
-
entry
|
| 320 |
this.persist();
|
| 321 |
this.note();
|
|
|
|
| 322 |
return true;
|
| 323 |
}
|
| 324 |
syncStrengths() {
|
| 325 |
for (const control of this.body.querySelectorAll(".dmai-strength")) {
|
| 326 |
-
if (!this.updateStrength(control.dataset.entryId, control.value)) {
|
| 327 |
control.setAttribute("aria-invalid", "true");
|
| 328 |
control.focus();
|
| 329 |
return false;
|
|
@@ -332,6 +349,34 @@ export class LoRAStack extends Controller {
|
|
| 332 |
}
|
| 333 |
return true;
|
| 334 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
move(id, to) {
|
| 336 |
if (!this.syncStrengths()) return;
|
| 337 |
this.value.entries = moveEntry(this.value.entries, id, to);
|
|
@@ -348,11 +393,18 @@ export class LoRAStack extends Controller {
|
|
| 348 |
build() {
|
| 349 |
this.body.replaceChildren();
|
| 350 |
const list = el("div", "dmai-lora-list");
|
|
|
|
| 351 |
list.setAttribute("aria-label", "Ordered LoRA stack");
|
| 352 |
let dragged = null;
|
| 353 |
this.value.entries.forEach((entry, index) => {
|
| 354 |
const row = el("div", `dmai-nodes-lora-row${entry.enabled ? "" : " dmai-off"}`);
|
| 355 |
row.dataset.entryId = entry.id;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
const grip = button(`Reorder ${entry.name}; Alt and arrow keys`, "grip");
|
| 357 |
grip.classList.add("dmai-grip");
|
| 358 |
grip.draggable = true;
|
|
@@ -390,31 +442,9 @@ export class LoRAStack extends Controller {
|
|
| 390 |
dragged = null;
|
| 391 |
});
|
| 392 |
const copy = el("div", "dmai-lora-copy"),
|
| 393 |
-
title = el("span", "dmai-nodes-lora-name", entry.name
|
| 394 |
title.title = entry.name;
|
| 395 |
-
copy.append(
|
| 396 |
-
title,
|
| 397 |
-
el(
|
| 398 |
-
"span",
|
| 399 |
-
"dmai-muted",
|
| 400 |
-
`${String(index + 1).padStart(2, "0")} · CLIP ${entry.strength_clip}`,
|
| 401 |
-
),
|
| 402 |
-
);
|
| 403 |
-
const strength = el("input", "dmai-strength");
|
| 404 |
-
strength.type = "number";
|
| 405 |
-
strength.min = -20;
|
| 406 |
-
strength.max = 20;
|
| 407 |
-
strength.step = 0.05;
|
| 408 |
-
strength.value = entry.strength_model;
|
| 409 |
-
strength.dataset.entryId = entry.id;
|
| 410 |
-
strength.setAttribute("aria-label", `Model strength for ${entry.name}`);
|
| 411 |
-
const commitStrength = () => {
|
| 412 |
-
if (this.updateStrength(entry.id, strength.value))
|
| 413 |
-
strength.removeAttribute("aria-invalid");
|
| 414 |
-
else strength.setAttribute("aria-invalid", "true");
|
| 415 |
-
};
|
| 416 |
-
strength.addEventListener("input", commitStrength);
|
| 417 |
-
strength.addEventListener("change", commitStrength);
|
| 418 |
const toggle = button(
|
| 419 |
`${entry.enabled ? "Disable" : "Enable"} ${entry.name}`,
|
| 420 |
"power",
|
|
@@ -427,6 +457,8 @@ export class LoRAStack extends Controller {
|
|
| 427 |
);
|
| 428 |
toggle.setAttribute("aria-pressed", String(entry.enabled));
|
| 429 |
const arrows = el("div", "dmai-reorder");
|
|
|
|
|
|
|
| 430 |
const up = button(`Move ${entry.name} up`, "up", () =>
|
| 431 |
this.move(entry.id, index - 1),
|
| 432 |
),
|
|
@@ -439,45 +471,49 @@ export class LoRAStack extends Controller {
|
|
| 439 |
const edit = button(`Edit ${entry.name}`, "sliders", () =>
|
| 440 |
this.edit(entry),
|
| 441 |
);
|
| 442 |
-
|
|
|
|
|
|
|
|
|
|
| 443 |
list.append(row);
|
| 444 |
});
|
| 445 |
if (!this.value.entries.length)
|
| 446 |
list.append(el("div", "dmai-empty", "Add a LoRA to build your stack."));
|
| 447 |
-
this.
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
);
|
| 451 |
-
|
| 452 |
-
(e) => e.enabled && (e.strength_model !== 0 || e.strength_clip !== 0),
|
| 453 |
-
).length;
|
| 454 |
-
this.body.append(
|
| 455 |
-
el("div", "dmai-footnote", `${count} selected · top to bottom`),
|
| 456 |
-
);
|
| 457 |
}
|
| 458 |
edit(entry) {
|
| 459 |
if (!this.syncStrengths()) return;
|
| 460 |
const d = this.show("LoRA settings"),
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
field
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
const message = el("p", "dmai-status"),
|
| 466 |
apply = button(
|
| 467 |
"Apply",
|
| 468 |
"check",
|
| 469 |
() => {
|
| 470 |
-
const
|
| 471 |
-
field.control.value === "" ? NaN : Number(field.control.value)
|
| 472 |
-
|
|
|
|
| 473 |
statusMessage(
|
| 474 |
message,
|
| 475 |
-
"CLIP strength must be from −20 to 20.",
|
| 476 |
true,
|
| 477 |
);
|
| 478 |
return;
|
| 479 |
}
|
| 480 |
-
entry.strength_clip =
|
| 481 |
this.persist();
|
| 482 |
this.build();
|
| 483 |
d.root.close();
|
|
@@ -499,7 +535,7 @@ export class LoRAStack extends Controller {
|
|
| 499 |
);
|
| 500 |
d.body.append(
|
| 501 |
el("p", "dmai-filename", entry.name),
|
| 502 |
-
|
| 503 |
message,
|
| 504 |
apply,
|
| 505 |
remove,
|
|
|
|
| 5 |
input,
|
| 6 |
details,
|
| 7 |
statusMessage,
|
| 8 |
+
} from "./dom.mjs?v=0.2.0";
|
| 9 |
import {
|
| 10 |
RATIOS,
|
| 11 |
dimensions,
|
|
|
|
| 14 |
readManifest,
|
| 15 |
moveEntry,
|
| 16 |
uid,
|
| 17 |
+
} from "./core.mjs?v=0.2.0";
|
| 18 |
+
import { ProgressView, progressPresentation } from "./progress-view.mjs?v=0.2.0";
|
| 19 |
|
| 20 |
export class Prompter extends Controller {
|
| 21 |
constructor(node, context) {
|
|
|
|
| 26 |
this.root.append(scroll);
|
| 27 |
this.progress = { phase: "idle", percent: 0 };
|
| 28 |
this.progressView = new ProgressView(this.root);
|
| 29 |
+
this.size(460, 690);
|
| 30 |
this.hydrate();
|
| 31 |
this.stopProgress = context.watchProgress?.(node, (state) => {
|
| 32 |
if (!this.disposed) this.updateProgress(state);
|
|
|
|
| 55 |
this.value.prompt = prompt.value;
|
| 56 |
this.persist();
|
| 57 |
});
|
| 58 |
+
const promptField = el("label", "dmai-prompt-field");
|
| 59 |
+
promptField.append(el("span", "dmai-section-label", "Prompt"), prompt);
|
| 60 |
+
this.body.append(promptField);
|
| 61 |
+
const settings = el("div", "dmai-prompt-settings"),
|
| 62 |
+
format = el("div", "dmai-format-section");
|
| 63 |
+
format.append(el("span", "dmai-section-label", "Aspect ratio"));
|
| 64 |
this.ratioRow = el("div", "dmai-ratios");
|
| 65 |
+
this.ratioRow.setAttribute("role", "group");
|
| 66 |
+
this.ratioRow.setAttribute("aria-label", "Aspect ratio");
|
| 67 |
for (const ratio of ["1:1", "4:5", "9:16", "16:9"])
|
| 68 |
this.ratioRow.append(this.ratioButton(ratio));
|
| 69 |
+
const moreRatios = button("More", "more", () => this.showRatios(), true);
|
| 70 |
+
moreRatios.classList.add("dmai-ratio-more");
|
| 71 |
+
moreRatios.setAttribute("aria-label", "All 19 aspect ratios");
|
| 72 |
+
moreRatios.title = "All 19 aspect ratios";
|
| 73 |
+
this.ratioRow.append(moreRatios);
|
| 74 |
+
format.append(this.ratioRow);
|
| 75 |
+
settings.append(format);
|
| 76 |
const resolutionRow = el("div", "dmai-resolution"),
|
| 77 |
group = el("div", "dmai-segmented");
|
| 78 |
this.resolutionButtons = {};
|
|
|
|
| 88 |
}
|
| 89 |
this.dimensions = el("output", "dmai-dimensions");
|
| 90 |
resolutionRow.append(group, this.dimensions);
|
| 91 |
+
group.setAttribute("role", "group");
|
| 92 |
+
group.setAttribute("aria-label", "Resolution");
|
| 93 |
+
this.dimensions.setAttribute("aria-label", "Image dimensions");
|
| 94 |
+
settings.append(resolutionRow);
|
| 95 |
const countRow = el("div", "dmai-row dmai-count");
|
| 96 |
+
countRow.append(el("span", "dmai-section-label", "Images"));
|
| 97 |
this.less = button("One fewer image", "minus", () => this.count(-1));
|
| 98 |
this.more = button("One more image", "plus", () => this.count(1));
|
| 99 |
this.countOutput = el("output");
|
|
|
|
| 101 |
const stepper = el("div", "dmai-stepper");
|
| 102 |
stepper.append(this.less, this.countOutput, this.more);
|
| 103 |
countRow.append(stepper);
|
| 104 |
+
settings.append(countRow);
|
| 105 |
+
this.body.append(settings);
|
| 106 |
+
const advanced = details("Negative prompt");
|
| 107 |
const negative = el("textarea", "dmai-negative");
|
| 108 |
negative.value = this.value.negative_prompt;
|
| 109 |
negative.placeholder = "Negative prompt";
|
|
|
|
| 293 |
export class LoRAStack extends Controller {
|
| 294 |
constructor(node, context) {
|
| 295 |
super(node, "manifest_json", "LoRA Loader", "layers", context);
|
| 296 |
+
this.root.classList.add("dmai-lora");
|
| 297 |
const serialize = this.native.serializeValue?.bind(this.native);
|
| 298 |
this.native.serializeValue = (...args) => {
|
| 299 |
if (!this.syncStrengths())
|
|
|
|
| 303 |
readManifest(this.native.value);
|
| 304 |
return serialize ? serialize(...args) : this.native.value;
|
| 305 |
};
|
| 306 |
+
this.size(480, 520);
|
| 307 |
this.hydrate();
|
| 308 |
}
|
| 309 |
hydrate() {
|
|
|
|
| 319 |
persist() {
|
| 320 |
this.set(this.value);
|
| 321 |
}
|
| 322 |
+
updateStrength(id, text, channel = "model") {
|
| 323 |
+
if (!["model", "clip"].includes(channel)) return false;
|
| 324 |
const value = text.trim() === "" ? NaN : Number(text);
|
| 325 |
if (!Number.isFinite(value) || value < -20 || value > 20) {
|
| 326 |
this.error(
|
| 327 |
new Error(
|
| 328 |
+
`${channel === "clip" ? "CLIP" : "Model"} strength must be from −20 to 20. Correct it before queueing.`,
|
| 329 |
),
|
| 330 |
);
|
| 331 |
return false;
|
| 332 |
}
|
| 333 |
const entry = this.value.entries.find((item) => item.id === id);
|
| 334 |
if (!entry) return false;
|
| 335 |
+
entry[`strength_${channel}`] = value;
|
| 336 |
this.persist();
|
| 337 |
this.note();
|
| 338 |
+
this.updateSummary();
|
| 339 |
return true;
|
| 340 |
}
|
| 341 |
syncStrengths() {
|
| 342 |
for (const control of this.body.querySelectorAll(".dmai-strength")) {
|
| 343 |
+
if (!this.updateStrength(control.dataset.entryId, control.value, control.dataset.strength ?? "model")) {
|
| 344 |
control.setAttribute("aria-invalid", "true");
|
| 345 |
control.focus();
|
| 346 |
return false;
|
|
|
|
| 349 |
}
|
| 350 |
return true;
|
| 351 |
}
|
| 352 |
+
updateSummary() {
|
| 353 |
+
const count = this.value.entries.filter(
|
| 354 |
+
(entry) => entry.enabled && (entry.strength_model !== 0 || entry.strength_clip !== 0),
|
| 355 |
+
).length;
|
| 356 |
+
if (this.summary) this.summary.textContent = `${count} active · top to bottom`;
|
| 357 |
+
}
|
| 358 |
+
strengthField(entry, channel) {
|
| 359 |
+
const label = channel === "clip" ? "CLIP" : "Model",
|
| 360 |
+
field = el("label", "dmai-field dmai-lora-strength-field"),
|
| 361 |
+
strength = el("input", "dmai-strength");
|
| 362 |
+
strength.type = "number";
|
| 363 |
+
strength.min = -20;
|
| 364 |
+
strength.max = 20;
|
| 365 |
+
strength.step = 0.05;
|
| 366 |
+
strength.value = entry[`strength_${channel}`];
|
| 367 |
+
strength.dataset.entryId = entry.id;
|
| 368 |
+
strength.dataset.strength = channel;
|
| 369 |
+
strength.setAttribute("aria-label", `${label} strength for ${entry.name}`);
|
| 370 |
+
const commitStrength = () => {
|
| 371 |
+
if (this.updateStrength(entry.id, strength.value, channel))
|
| 372 |
+
strength.removeAttribute("aria-invalid");
|
| 373 |
+
else strength.setAttribute("aria-invalid", "true");
|
| 374 |
+
};
|
| 375 |
+
strength.addEventListener("input", commitStrength);
|
| 376 |
+
strength.addEventListener("change", commitStrength);
|
| 377 |
+
field.append(el("span", "", label), strength);
|
| 378 |
+
return field;
|
| 379 |
+
}
|
| 380 |
move(id, to) {
|
| 381 |
if (!this.syncStrengths()) return;
|
| 382 |
this.value.entries = moveEntry(this.value.entries, id, to);
|
|
|
|
| 393 |
build() {
|
| 394 |
this.body.replaceChildren();
|
| 395 |
const list = el("div", "dmai-lora-list");
|
| 396 |
+
list.setAttribute("role", "list");
|
| 397 |
list.setAttribute("aria-label", "Ordered LoRA stack");
|
| 398 |
let dragged = null;
|
| 399 |
this.value.entries.forEach((entry, index) => {
|
| 400 |
const row = el("div", `dmai-nodes-lora-row${entry.enabled ? "" : " dmai-off"}`);
|
| 401 |
row.dataset.entryId = entry.id;
|
| 402 |
+
row.setAttribute("role", "listitem");
|
| 403 |
+
const main = el("div", "dmai-lora-main"),
|
| 404 |
+
controls = el("div", "dmai-lora-controls"),
|
| 405 |
+
actions = el("div", "dmai-lora-actions"),
|
| 406 |
+
order = el("span", "dmai-nodes-lora-order", String(index + 1).padStart(2, "0"));
|
| 407 |
+
order.setAttribute("aria-label", `Position ${index + 1}`);
|
| 408 |
const grip = button(`Reorder ${entry.name}; Alt and arrow keys`, "grip");
|
| 409 |
grip.classList.add("dmai-grip");
|
| 410 |
grip.draggable = true;
|
|
|
|
| 442 |
dragged = null;
|
| 443 |
});
|
| 444 |
const copy = el("div", "dmai-lora-copy"),
|
| 445 |
+
title = el("span", "dmai-nodes-lora-name", entry.name);
|
| 446 |
title.title = entry.name;
|
| 447 |
+
copy.append(title);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
const toggle = button(
|
| 449 |
`${entry.enabled ? "Disable" : "Enable"} ${entry.name}`,
|
| 450 |
"power",
|
|
|
|
| 457 |
);
|
| 458 |
toggle.setAttribute("aria-pressed", String(entry.enabled));
|
| 459 |
const arrows = el("div", "dmai-reorder");
|
| 460 |
+
arrows.setAttribute("role", "group");
|
| 461 |
+
arrows.setAttribute("aria-label", `Order for ${entry.name}`);
|
| 462 |
const up = button(`Move ${entry.name} up`, "up", () =>
|
| 463 |
this.move(entry.id, index - 1),
|
| 464 |
),
|
|
|
|
| 471 |
const edit = button(`Edit ${entry.name}`, "sliders", () =>
|
| 472 |
this.edit(entry),
|
| 473 |
);
|
| 474 |
+
actions.append(toggle, edit);
|
| 475 |
+
main.append(grip, order, copy, actions);
|
| 476 |
+
controls.append(this.strengthField(entry, "model"), this.strengthField(entry, "clip"), arrows);
|
| 477 |
+
row.append(main, controls);
|
| 478 |
list.append(row);
|
| 479 |
});
|
| 480 |
if (!this.value.entries.length)
|
| 481 |
list.append(el("div", "dmai-empty", "Add a LoRA to build your stack."));
|
| 482 |
+
const add = button("Add LoRA", "plus", () => this.guard(() => this.library()), true);
|
| 483 |
+
add.classList.add("dmai-nodes-lora-add");
|
| 484 |
+
this.summary = el("div", "dmai-footnote");
|
| 485 |
+
this.body.append(list, add, this.summary);
|
| 486 |
+
this.updateSummary();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
}
|
| 488 |
edit(entry) {
|
| 489 |
if (!this.syncStrengths()) return;
|
| 490 |
const d = this.show("LoRA settings"),
|
| 491 |
+
strengths = el("div", "dmai-lora-dialog-strengths"),
|
| 492 |
+
model = input("Model strength", "number", entry.strength_model),
|
| 493 |
+
clip = input("CLIP strength", "number", entry.strength_clip);
|
| 494 |
+
for (const field of [model, clip]) {
|
| 495 |
+
field.control.min = -20;
|
| 496 |
+
field.control.max = 20;
|
| 497 |
+
field.control.step = 0.05;
|
| 498 |
+
}
|
| 499 |
+
strengths.append(model.field, clip.field);
|
| 500 |
const message = el("p", "dmai-status"),
|
| 501 |
apply = button(
|
| 502 |
"Apply",
|
| 503 |
"check",
|
| 504 |
() => {
|
| 505 |
+
const values = [model, clip].map((field) =>
|
| 506 |
+
field.control.value.trim() === "" ? NaN : Number(field.control.value),
|
| 507 |
+
);
|
| 508 |
+
if (values.some((value) => !Number.isFinite(value) || value < -20 || value > 20)) {
|
| 509 |
statusMessage(
|
| 510 |
message,
|
| 511 |
+
"Model and CLIP strength must each be from −20 to 20.",
|
| 512 |
true,
|
| 513 |
);
|
| 514 |
return;
|
| 515 |
}
|
| 516 |
+
[entry.strength_model, entry.strength_clip] = values;
|
| 517 |
this.persist();
|
| 518 |
this.build();
|
| 519 |
d.root.close();
|
|
|
|
| 535 |
);
|
| 536 |
d.body.append(
|
| 537 |
el("p", "dmai-filename", entry.name),
|
| 538 |
+
strengths,
|
| 539 |
message,
|
| 540 |
apply,
|
| 541 |
remove,
|
web/nodes.css
CHANGED
|
@@ -6,33 +6,23 @@
|
|
| 6 |
font-display: swap;
|
| 7 |
}
|
| 8 |
.dmai-nodes {
|
| 9 |
-
--dmai-panel: #
|
| 10 |
-
--dmai-inset: #
|
| 11 |
-
--dmai-text: #
|
| 12 |
-
--dmai-muted: #
|
| 13 |
-
--dmai-line: #
|
| 14 |
-
--dmai-accent: #
|
| 15 |
-
--dmai-hover: #
|
| 16 |
color-scheme: dark;
|
| 17 |
color: var(--dmai-text);
|
| 18 |
-
font:
|
| 19 |
-
400 12px/1.5 "DMAI Inter",
|
| 20 |
-
"Segoe UI",
|
| 21 |
-
sans-serif;
|
| 22 |
-
font-style: normal;
|
| 23 |
box-sizing: border-box;
|
| 24 |
text-align: left;
|
|
|
|
| 25 |
}
|
| 26 |
-
.dmai-nodes * {
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
.dmai-nodes [hidden] {
|
| 30 |
-
display: none !important;
|
| 31 |
-
}
|
| 32 |
-
.dmai-nodes button,
|
| 33 |
-
.dmai-nodes input,
|
| 34 |
-
.dmai-nodes textarea,
|
| 35 |
-
.dmai-nodes select {
|
| 36 |
font: inherit;
|
| 37 |
color: inherit;
|
| 38 |
}
|
|
@@ -40,676 +30,198 @@
|
|
| 40 |
cursor: pointer;
|
| 41 |
border: 0;
|
| 42 |
background: none;
|
|
|
|
| 43 |
}
|
| 44 |
-
.dmai-nodes button:disabled {
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
}
|
| 48 |
-
.dmai-nodes button:hover:not(:disabled) {
|
| 49 |
-
background: var(--dmai-hover);
|
| 50 |
-
}
|
| 51 |
-
.dmai-nodes button:focus-visible,
|
| 52 |
-
.dmai-nodes input:focus-visible,
|
| 53 |
-
.dmai-nodes select:focus-visible,
|
| 54 |
-
.dmai-nodes textarea:focus-visible,
|
| 55 |
-
.dmai-nodes summary:focus-visible {
|
| 56 |
outline: 2px solid var(--dmai-accent);
|
| 57 |
-
outline-offset:
|
| 58 |
}
|
| 59 |
-
.dmai-nodes input,
|
| 60 |
-
.dmai-nodes textarea,
|
| 61 |
-
.dmai-nodes select {
|
| 62 |
background: var(--dmai-inset);
|
| 63 |
border: 1px solid var(--dmai-line);
|
| 64 |
-
border-radius:
|
| 65 |
-
padding:
|
| 66 |
min-width: 0;
|
| 67 |
max-width: 100%;
|
|
|
|
| 68 |
}
|
| 69 |
-
.dmai-nodes input[aria-invalid="true"] {
|
| 70 |
-
|
| 71 |
-
}
|
| 72 |
-
.dmai-nodes input
|
| 73 |
-
appearance: textfield;
|
| 74 |
-
}
|
| 75 |
-
.dmai-nodes input::-webkit-inner-spin-button {
|
| 76 |
-
appearance: none;
|
| 77 |
-
}
|
| 78 |
.dmai-nodes .icon {
|
| 79 |
-
width:
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
stroke-width: 1.6;
|
| 83 |
-
fill: none;
|
| 84 |
-
stroke-linecap: round;
|
| 85 |
-
stroke-linejoin: round;
|
| 86 |
-
flex: none;
|
| 87 |
-
vertical-align: middle;
|
| 88 |
}
|
| 89 |
.dmai-panel {
|
| 90 |
background: var(--dmai-panel);
|
| 91 |
border: 1px solid var(--dmai-line);
|
| 92 |
-
border-radius:
|
| 93 |
-
width: 100%;
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
overflow: auto;
|
| 97 |
-
position: relative;
|
| 98 |
-
scrollbar-width: thin;
|
| 99 |
-
scrollbar-color: #4b584e transparent;
|
| 100 |
}
|
| 101 |
.dmai-nodes .dmai-head {
|
| 102 |
-
height:
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
}
|
| 109 |
-
.dmai-nodes .dmai-
|
| 110 |
-
font:
|
| 111 |
-
700 13px "DMAI Inter",
|
| 112 |
-
sans-serif;
|
| 113 |
-
letter-spacing: -0.1px;
|
| 114 |
-
margin: 0;
|
| 115 |
-
white-space: nowrap;
|
| 116 |
-
}
|
| 117 |
-
.dmai-nodes .dmai-glyph {
|
| 118 |
-
color: var(--dmai-muted);
|
| 119 |
-
}
|
| 120 |
-
.dmai-nodes .dmai-brand {
|
| 121 |
-
margin-left: auto;
|
| 122 |
-
font-size: 8px;
|
| 123 |
-
font-weight: 700;
|
| 124 |
-
letter-spacing: 1.5px;
|
| 125 |
-
color: var(--dmai-muted);
|
| 126 |
-
}
|
| 127 |
-
.dmai-nodes .dmai-body {
|
| 128 |
-
padding: 14px;
|
| 129 |
-
min-width: 0;
|
| 130 |
-
}
|
| 131 |
.dmai-nodes .dmai-icon {
|
| 132 |
-
display: inline-flex;
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
width: 30px;
|
| 136 |
-
height: 31px;
|
| 137 |
-
border-radius: 6px;
|
| 138 |
-
padding: 0;
|
| 139 |
-
color: var(--dmai-muted);
|
| 140 |
-
flex: none;
|
| 141 |
-
}
|
| 142 |
-
.dmai-nodes .dmai-icon:hover {
|
| 143 |
-
color: var(--dmai-accent);
|
| 144 |
}
|
|
|
|
| 145 |
.dmai-nodes .dmai-button {
|
| 146 |
-
display: inline-flex;
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
gap: 7px;
|
| 150 |
-
padding: 8px 11px;
|
| 151 |
-
border-radius: 7px;
|
| 152 |
-
color: var(--dmai-muted);
|
| 153 |
-
font-size: 11px;
|
| 154 |
}
|
| 155 |
.dmai-nodes .dmai-primary {
|
| 156 |
-
background: var(--dmai-accent);
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
}
|
| 166 |
-
.dmai-nodes .dmai-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
}
|
| 170 |
-
.dmai-nodes .dmai-
|
| 171 |
-
color: var(--dmai-muted);
|
| 172 |
-
font-size: 10px;
|
| 173 |
-
}
|
| 174 |
-
.dmai-nodes .dmai-row {
|
| 175 |
-
display: flex;
|
| 176 |
-
align-items: center;
|
| 177 |
-
justify-content: space-between;
|
| 178 |
-
gap: 8px;
|
| 179 |
-
}
|
| 180 |
-
.dmai-nodes .dmai-segmented {
|
| 181 |
-
display: flex;
|
| 182 |
-
align-items: center;
|
| 183 |
-
background: var(--dmai-inset);
|
| 184 |
-
border-radius: 7px;
|
| 185 |
-
padding: 3px;
|
| 186 |
-
gap: 3px;
|
| 187 |
-
}
|
| 188 |
-
.dmai-nodes .dmai-segmented button {
|
| 189 |
-
padding: 6px 9px;
|
| 190 |
-
border-radius: 5px;
|
| 191 |
-
font-size: 10px;
|
| 192 |
-
white-space: nowrap;
|
| 193 |
-
}
|
| 194 |
-
.dmai-nodes button[aria-pressed="true"] {
|
| 195 |
-
color: var(--dmai-accent);
|
| 196 |
-
}
|
| 197 |
-
.dmai-nodes .dmai-segmented button[aria-pressed="true"] {
|
| 198 |
-
background: var(--dmai-hover);
|
| 199 |
-
}
|
| 200 |
-
.dmai-nodes .dmai-segmented .icon {
|
| 201 |
-
width: 12px;
|
| 202 |
-
height: 12px;
|
| 203 |
-
}
|
| 204 |
.dmai-nodes .dmai-prompt {
|
| 205 |
-
display: block;
|
| 206 |
-
|
| 207 |
-
background:
|
| 208 |
-
border: 0;
|
| 209 |
-
resize: vertical;
|
| 210 |
-
min-height: 100px;
|
| 211 |
-
padding: 0;
|
| 212 |
-
font-size: 12px;
|
| 213 |
-
line-height: 1.7;
|
| 214 |
-
}
|
| 215 |
-
.dmai-nodes .dmai-ratios {
|
| 216 |
-
display: flex;
|
| 217 |
-
gap: 5px;
|
| 218 |
-
margin: 12px 0;
|
| 219 |
}
|
|
|
|
|
|
|
| 220 |
.dmai-nodes .dmai-ratios .dmai-button {
|
| 221 |
-
flex: 1;
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
}
|
| 228 |
-
.dmai-nodes .dmai-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
}
|
| 232 |
-
.dmai-nodes .dmai-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
}
|
| 239 |
-
.dmai-nodes .dmai-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
}
|
| 246 |
-
.dmai-nodes .dmai-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
}
|
| 251 |
-
.dmai-nodes .dmai-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
}
|
| 255 |
-
.dmai-nodes .dmai-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
}
|
| 260 |
-
.dmai-nodes .dmai-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
}
|
| 265 |
-
.dmai-nodes .dmai-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
}
|
| 270 |
-
.dmai-nodes .dmai-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
}
|
| 275 |
-
.dmai-nodes .dmai-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
}
|
| 280 |
-
.dmai-nodes .dmai-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
}
|
| 285 |
-
.dmai-nodes .dmai-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
}
|
| 291 |
-
.dmai-nodes .dmai-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
}
|
| 295 |
-
.dmai-nodes .dmai-
|
| 296 |
-
.dmai-nodes .dmai-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
}
|
| 300 |
-
.dmai-nodes .dmai-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
}
|
| 305 |
-
.dmai-nodes .dmai-
|
| 306 |
-
|
| 307 |
-
}
|
| 308 |
-
.dmai-nodes .dmai-
|
| 309 |
-
|
| 310 |
-
}
|
| 311 |
-
.dmai-nodes .dmai-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
}
|
| 316 |
-
.dmai-nodes .dmai-
|
| 317 |
-
|
| 318 |
-
}
|
| 319 |
-
.dmai-nodes .dmai-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
}
|
| 324 |
-
.dmai-nodes .dmai-
|
| 325 |
-
|
| 326 |
-
}
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
}
|
| 334 |
-
.dmai-nodes .dmai-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
}
|
| 339 |
-
.dmai-nodes .dmai-settings {
|
| 340 |
-
display: grid;
|
| 341 |
-
grid-template-columns: 1fr 1fr;
|
| 342 |
-
gap: 10px;
|
| 343 |
-
margin: 12px 0;
|
| 344 |
-
}
|
| 345 |
-
.dmai-nodes .dmai-seed {
|
| 346 |
-
display: flex;
|
| 347 |
-
gap: 7px;
|
| 348 |
-
align-items: flex-end;
|
| 349 |
-
border-top: 1px solid var(--dmai-line);
|
| 350 |
-
padding-top: 12px;
|
| 351 |
-
}
|
| 352 |
-
.dmai-nodes .dmai-seed .dmai-field {
|
| 353 |
-
flex: 1;
|
| 354 |
-
}
|
| 355 |
-
.dmai-nodes .dmai-generate {
|
| 356 |
-
width: 100%;
|
| 357 |
-
margin-top: 14px;
|
| 358 |
-
}
|
| 359 |
-
.dmai-nodes .dmai-lora-list {
|
| 360 |
-
display: grid;
|
| 361 |
-
gap: 3px;
|
| 362 |
-
margin-bottom: 8px;
|
| 363 |
-
}
|
| 364 |
-
.dmai-nodes .dmai-nodes-lora-row {
|
| 365 |
-
display: grid;
|
| 366 |
-
grid-template-columns: 14px minmax(70px, 1fr) 46px 25px 18px 25px;
|
| 367 |
-
align-items: center;
|
| 368 |
-
gap: 5px;
|
| 369 |
-
min-height: 65px;
|
| 370 |
-
border-bottom: 1px solid var(--dmai-line);
|
| 371 |
-
}
|
| 372 |
-
.dmai-nodes .dmai-nodes-lora-row:last-child {
|
| 373 |
-
border: 0;
|
| 374 |
-
}
|
| 375 |
-
.dmai-nodes .dmai-lora-copy {
|
| 376 |
-
overflow: hidden;
|
| 377 |
-
}
|
| 378 |
-
.dmai-nodes .dmai-nodes-lora-name {
|
| 379 |
-
display: block;
|
| 380 |
-
white-space: nowrap;
|
| 381 |
-
text-overflow: ellipsis;
|
| 382 |
-
overflow: hidden;
|
| 383 |
-
font-size: 11px;
|
| 384 |
-
}
|
| 385 |
-
.dmai-nodes .dmai-lora-copy .dmai-muted {
|
| 386 |
-
display: block;
|
| 387 |
-
font-size: 8px;
|
| 388 |
-
margin-top: 5px;
|
| 389 |
-
}
|
| 390 |
-
.dmai-nodes .dmai-strength {
|
| 391 |
-
width: 46px;
|
| 392 |
-
font-size: 10px;
|
| 393 |
-
padding: 7px 3px;
|
| 394 |
-
text-align: center;
|
| 395 |
-
}
|
| 396 |
-
.dmai-nodes .dmai-nodes-lora-row > .dmai-icon {
|
| 397 |
-
width: 25px;
|
| 398 |
-
}
|
| 399 |
-
.dmai-nodes .dmai-nodes-lora-row > .dmai-grip {
|
| 400 |
-
width: 14px;
|
| 401 |
-
color: #7d8c81;
|
| 402 |
-
cursor: grab;
|
| 403 |
-
}
|
| 404 |
-
.dmai-nodes .dmai-grip .icon {
|
| 405 |
-
width: 12px;
|
| 406 |
-
}
|
| 407 |
-
.dmai-nodes .dmai-reorder {
|
| 408 |
-
display: flex;
|
| 409 |
-
flex-direction: column;
|
| 410 |
-
}
|
| 411 |
-
.dmai-nodes .dmai-reorder .dmai-icon {
|
| 412 |
-
height: 22px;
|
| 413 |
-
width: 18px;
|
| 414 |
-
}
|
| 415 |
-
.dmai-nodes .dmai-reorder .icon {
|
| 416 |
-
width: 13px;
|
| 417 |
-
height: 13px;
|
| 418 |
-
}
|
| 419 |
-
.dmai-nodes .dmai-off {
|
| 420 |
-
opacity: 0.52;
|
| 421 |
-
}
|
| 422 |
-
.dmai-nodes .dmai-dragging {
|
| 423 |
-
opacity: 0.45;
|
| 424 |
-
}
|
| 425 |
-
.dmai-nodes .dmai-dragover {
|
| 426 |
-
box-shadow: inset 0 2px var(--dmai-accent);
|
| 427 |
-
}
|
| 428 |
-
.dmai-nodes .dmai-footnote {
|
| 429 |
-
font-size: 9px;
|
| 430 |
-
color: var(--dmai-muted);
|
| 431 |
-
border-top: 1px solid var(--dmai-line);
|
| 432 |
-
margin-top: 12px;
|
| 433 |
-
padding-top: 10px;
|
| 434 |
-
}
|
| 435 |
-
.dmai-nodes .dmai-empty {
|
| 436 |
-
padding: 22px 8px;
|
| 437 |
-
color: var(--dmai-muted);
|
| 438 |
-
font-size: 11px;
|
| 439 |
-
text-align: center;
|
| 440 |
-
}
|
| 441 |
-
.dmai-nodes .dmai-gallery-toolbar {
|
| 442 |
-
margin: -4px 0 8px;
|
| 443 |
-
}
|
| 444 |
-
.dmai-nodes .dmai-gallery-scroll {
|
| 445 |
-
max-height: 355px;
|
| 446 |
-
overflow: auto;
|
| 447 |
-
scrollbar-width: thin;
|
| 448 |
-
scrollbar-color: #4b584e transparent;
|
| 449 |
-
min-height: 150px;
|
| 450 |
-
}
|
| 451 |
-
.dmai-nodes .dmai-gallery-grid {
|
| 452 |
-
display: grid;
|
| 453 |
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 454 |
-
gap: 7px;
|
| 455 |
-
}
|
| 456 |
-
.dmai-nodes .dmai-gallery-grid.dmai-dense {
|
| 457 |
-
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 458 |
-
gap: 4px;
|
| 459 |
-
}
|
| 460 |
-
.dmai-nodes .dmai-image-card {
|
| 461 |
-
position: relative;
|
| 462 |
-
border-radius: 8px;
|
| 463 |
-
overflow: hidden;
|
| 464 |
-
isolation: isolate;
|
| 465 |
-
aspect-ratio: 4/5;
|
| 466 |
-
background: var(--dmai-inset);
|
| 467 |
-
}
|
| 468 |
-
.dmai-nodes .dmai-image-select {
|
| 469 |
-
display: block;
|
| 470 |
-
width: 100%;
|
| 471 |
-
height: 100%;
|
| 472 |
-
padding: 0;
|
| 473 |
-
position: relative;
|
| 474 |
-
border: 2px solid transparent;
|
| 475 |
-
border-radius: 8px;
|
| 476 |
-
overflow: hidden;
|
| 477 |
-
}
|
| 478 |
-
.dmai-nodes .dmai-image-select[aria-pressed="true"] {
|
| 479 |
-
border-color: var(--dmai-accent);
|
| 480 |
-
}
|
| 481 |
-
.dmai-nodes .dmai-image-select img {
|
| 482 |
-
display: block;
|
| 483 |
-
width: 100%;
|
| 484 |
-
height: 100%;
|
| 485 |
-
object-fit: cover;
|
| 486 |
-
}
|
| 487 |
-
.dmai-nodes .dmai-check {
|
| 488 |
-
position: absolute;
|
| 489 |
-
left: 6px;
|
| 490 |
-
top: 6px;
|
| 491 |
-
width: 17px;
|
| 492 |
-
height: 17px;
|
| 493 |
-
border: 1px solid #ffffff88;
|
| 494 |
-
background: #12181270;
|
| 495 |
-
color: #c8ee99;
|
| 496 |
-
border-radius: 5px;
|
| 497 |
-
font-size: 12px;
|
| 498 |
-
line-height: 15px;
|
| 499 |
-
text-align: center;
|
| 500 |
-
}
|
| 501 |
-
.dmai-nodes .dmai-image-select[aria-pressed="true"] .dmai-check {
|
| 502 |
-
background: var(--dmai-accent);
|
| 503 |
-
border-color: var(--dmai-accent);
|
| 504 |
-
color: #1d2c12;
|
| 505 |
-
}
|
| 506 |
-
.dmai-nodes .dmai-image-actions {
|
| 507 |
-
position: absolute;
|
| 508 |
-
bottom: 6px;
|
| 509 |
-
right: 6px;
|
| 510 |
-
display: flex;
|
| 511 |
-
background: #101714d9;
|
| 512 |
-
backdrop-filter: blur(8px);
|
| 513 |
-
padding: 2px;
|
| 514 |
-
border: 1px solid #ffffff24;
|
| 515 |
-
border-radius: 7px;
|
| 516 |
-
}
|
| 517 |
-
.dmai-nodes .dmai-image-actions .dmai-icon {
|
| 518 |
-
width: 24px;
|
| 519 |
-
height: 26px;
|
| 520 |
-
color: #ecf0e9;
|
| 521 |
-
}
|
| 522 |
-
.dmai-nodes .dmai-image-actions .dmai-icon:last-child {
|
| 523 |
-
color: var(--dmai-accent);
|
| 524 |
-
}
|
| 525 |
-
.dmai-nodes .dmai-image-actions .icon {
|
| 526 |
-
width: 13px;
|
| 527 |
-
height: 13px;
|
| 528 |
-
}
|
| 529 |
-
.dmai-nodes .dmai-image-actions .dmai-icon:hover {
|
| 530 |
-
background: var(--dmai-accent);
|
| 531 |
-
color: #1d2c12;
|
| 532 |
-
}
|
| 533 |
-
.dmai-nodes .dmai-selection {
|
| 534 |
-
border-top: 1px solid var(--dmai-line);
|
| 535 |
-
margin-top: 12px;
|
| 536 |
-
padding-top: 10px;
|
| 537 |
-
font-size: 10px;
|
| 538 |
-
justify-content: flex-start;
|
| 539 |
-
}
|
| 540 |
-
.dmai-nodes .dmai-selection .dmai-primary {
|
| 541 |
-
margin-left: auto;
|
| 542 |
-
width: auto;
|
| 543 |
-
font-size: 10px;
|
| 544 |
-
}
|
| 545 |
-
.dmai-nodes .dmai-status {
|
| 546 |
-
font-size: 10px;
|
| 547 |
-
padding: 10px 14px;
|
| 548 |
-
color: var(--dmai-muted);
|
| 549 |
-
overflow-wrap: anywhere;
|
| 550 |
-
border-top: 1px solid var(--dmai-line);
|
| 551 |
-
}
|
| 552 |
-
.dmai-nodes .dmai-status[data-error="true"],
|
| 553 |
-
.dmai-nodes .dmai-warning {
|
| 554 |
-
color: #edb89e;
|
| 555 |
-
font-size: 10px;
|
| 556 |
-
}
|
| 557 |
-
.dmai-nodes .dmai-raw {
|
| 558 |
-
width: 100%;
|
| 559 |
-
height: 200px;
|
| 560 |
-
resize: vertical;
|
| 561 |
-
}
|
| 562 |
-
.dmai-dialog {
|
| 563 |
-
background: var(--dmai-panel);
|
| 564 |
-
border: 1px solid var(--dmai-line);
|
| 565 |
-
border-radius: 14px;
|
| 566 |
-
padding: 0;
|
| 567 |
-
width: min(520px, calc(100vw - 30px));
|
| 568 |
-
max-height: calc(100vh - 40px);
|
| 569 |
-
box-shadow: 0 25px 70px #0008;
|
| 570 |
-
}
|
| 571 |
-
.dmai-dialog::backdrop {
|
| 572 |
-
background: #080e0bb0;
|
| 573 |
-
backdrop-filter: blur(4px);
|
| 574 |
-
}
|
| 575 |
-
.dmai-nodes .dmai-dialog-head {
|
| 576 |
-
display: flex;
|
| 577 |
-
align-items: center;
|
| 578 |
-
justify-content: space-between;
|
| 579 |
-
padding: 16px 20px;
|
| 580 |
-
border-bottom: 1px solid var(--dmai-line);
|
| 581 |
-
gap: 10px;
|
| 582 |
-
}
|
| 583 |
-
.dmai-nodes .dmai-dialog-head h2 {
|
| 584 |
-
font-size: 15px;
|
| 585 |
-
font-weight: 700;
|
| 586 |
-
margin: 0;
|
| 587 |
-
}
|
| 588 |
-
.dmai-nodes .dmai-dialog-body {
|
| 589 |
-
padding: 20px;
|
| 590 |
-
display: grid;
|
| 591 |
-
gap: 15px;
|
| 592 |
-
}
|
| 593 |
-
.dmai-nodes .dmai-format-grid {
|
| 594 |
-
display: grid;
|
| 595 |
-
grid-template-columns: repeat(4, 1fr);
|
| 596 |
-
gap: 8px;
|
| 597 |
-
}
|
| 598 |
-
.dmai-nodes .dmai-format-grid .dmai-button {
|
| 599 |
-
border: 1px solid var(--dmai-line);
|
| 600 |
-
padding: 14px 5px;
|
| 601 |
-
display: flex;
|
| 602 |
-
flex-direction: column;
|
| 603 |
-
}
|
| 604 |
-
.dmai-nodes .dmai-custom-size {
|
| 605 |
-
display: grid;
|
| 606 |
-
grid-template-columns: 1fr 30px 1fr;
|
| 607 |
-
gap: 12px;
|
| 608 |
-
align-items: end;
|
| 609 |
-
}
|
| 610 |
-
.dmai-nodes .dmai-size-output {
|
| 611 |
-
text-align: center;
|
| 612 |
-
background: var(--dmai-inset);
|
| 613 |
-
padding: 25px;
|
| 614 |
-
color: var(--dmai-accent);
|
| 615 |
-
font-size: 18px;
|
| 616 |
-
font-weight: 700;
|
| 617 |
-
border: 1px solid var(--dmai-line);
|
| 618 |
-
border-radius: 9px;
|
| 619 |
-
}
|
| 620 |
-
.dmai-nodes .dmai-library {
|
| 621 |
-
display: grid;
|
| 622 |
-
gap: 5px;
|
| 623 |
-
max-height: 350px;
|
| 624 |
-
overflow: auto;
|
| 625 |
-
}
|
| 626 |
-
.dmai-nodes .dmai-library .dmai-button {
|
| 627 |
-
justify-content: flex-start;
|
| 628 |
-
text-align: left;
|
| 629 |
-
overflow-wrap: anywhere;
|
| 630 |
-
}
|
| 631 |
-
.dmai-nodes .dmai-filename {
|
| 632 |
-
overflow-wrap: anywhere;
|
| 633 |
-
font-size: 11px;
|
| 634 |
-
}
|
| 635 |
-
.dmai-nodes .dmai-lightbox {
|
| 636 |
-
display: block;
|
| 637 |
-
width: 100%;
|
| 638 |
-
height: auto;
|
| 639 |
-
max-height: 65vh;
|
| 640 |
-
object-fit: contain;
|
| 641 |
-
border-radius: 8px;
|
| 642 |
-
}
|
| 643 |
-
@media (prefers-reduced-motion: reduce) {
|
| 644 |
-
.dmai-nodes * {
|
| 645 |
-
transition: none !important;
|
| 646 |
-
animation: none !important;
|
| 647 |
-
}
|
| 648 |
-
}
|
| 649 |
-
|
| 650 |
-
/* The outline stays inside the node so real Comfy sockets remain unobstructed. */
|
| 651 |
-
.dmai-panel.dmai-prompter {
|
| 652 |
-
overflow: hidden;
|
| 653 |
-
}
|
| 654 |
-
.dmai-nodes .dmai-prompter-scroll {
|
| 655 |
-
width: 100%;
|
| 656 |
-
height: 100%;
|
| 657 |
-
overflow: auto;
|
| 658 |
-
scrollbar-width: thin;
|
| 659 |
-
scrollbar-color: #4b584e transparent;
|
| 660 |
-
border-radius: inherit;
|
| 661 |
-
}
|
| 662 |
-
.dmai-nodes .dmai-progress-outline {
|
| 663 |
-
position: absolute;
|
| 664 |
-
inset: 0;
|
| 665 |
-
width: 100%;
|
| 666 |
-
height: 100%;
|
| 667 |
-
pointer-events: none;
|
| 668 |
-
z-index: 2;
|
| 669 |
-
overflow: visible;
|
| 670 |
-
fill: none;
|
| 671 |
-
}
|
| 672 |
-
.dmai-nodes .dmai-progress-track {
|
| 673 |
-
stroke: var(--dmai-accent);
|
| 674 |
-
stroke-width: 1;
|
| 675 |
-
opacity: 0.13;
|
| 676 |
-
}
|
| 677 |
-
.dmai-nodes .dmai-progress-stroke {
|
| 678 |
-
stroke: var(--dmai-accent);
|
| 679 |
-
stroke-width: 2;
|
| 680 |
-
stroke-linecap: round;
|
| 681 |
-
transition: stroke-dashoffset 350ms ease-out;
|
| 682 |
-
filter: drop-shadow(0 0 3px #c8ee9966);
|
| 683 |
-
}
|
| 684 |
-
.dmai-nodes[data-generating="true"] .dmai-progress-stroke {
|
| 685 |
-
animation: dmai-progress-glow 2.4s ease-in-out infinite;
|
| 686 |
-
}
|
| 687 |
-
.dmai-nodes[data-generation-phase="queued"] .dmai-progress-stroke {
|
| 688 |
-
transition: none;
|
| 689 |
-
}
|
| 690 |
-
.dmai-nodes .dmai-progress-readout {
|
| 691 |
-
display: flex;
|
| 692 |
-
justify-content: space-between;
|
| 693 |
-
align-items: center;
|
| 694 |
-
gap: 12px;
|
| 695 |
-
margin-top: 9px;
|
| 696 |
-
color: var(--dmai-muted);
|
| 697 |
-
font-size: 10px;
|
| 698 |
-
}
|
| 699 |
-
.dmai-nodes .dmai-progress-value {
|
| 700 |
-
font-variant-numeric: tabular-nums;
|
| 701 |
-
color: var(--dmai-accent);
|
| 702 |
-
}
|
| 703 |
-
.dmai-nodes[data-generation-phase="error"] .dmai-progress-stroke,
|
| 704 |
-
.dmai-nodes[data-generation-phase="interrupted"] .dmai-progress-stroke {
|
| 705 |
-
stroke: #edb89e;
|
| 706 |
-
filter: none;
|
| 707 |
-
}
|
| 708 |
-
.dmai-nodes[data-generation-phase="error"] .dmai-progress-value,
|
| 709 |
-
.dmai-nodes[data-generation-phase="interrupted"] .dmai-progress-value {
|
| 710 |
-
color: #edb89e;
|
| 711 |
-
}
|
| 712 |
-
@keyframes dmai-progress-glow {
|
| 713 |
-
0%, 100% { opacity: 0.72; }
|
| 714 |
-
50% { opacity: 1; }
|
| 715 |
-
}
|
|
|
|
| 6 |
font-display: swap;
|
| 7 |
}
|
| 8 |
.dmai-nodes {
|
| 9 |
+
--dmai-panel: #191d22;
|
| 10 |
+
--dmai-inset: #242931;
|
| 11 |
+
--dmai-text: #f5f7fa;
|
| 12 |
+
--dmai-muted: #b1b9c6;
|
| 13 |
+
--dmai-line: #363e49;
|
| 14 |
+
--dmai-accent: #c6f18d;
|
| 15 |
+
--dmai-hover: #303842;
|
| 16 |
color-scheme: dark;
|
| 17 |
color: var(--dmai-text);
|
| 18 |
+
font: 400 15px/1.5 "DMAI Inter", "Segoe UI", sans-serif;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
box-sizing: border-box;
|
| 20 |
text-align: left;
|
| 21 |
+
-webkit-font-smoothing: antialiased;
|
| 22 |
}
|
| 23 |
+
.dmai-nodes * { box-sizing: border-box; }
|
| 24 |
+
.dmai-nodes [hidden] { display: none !important; }
|
| 25 |
+
.dmai-nodes button, .dmai-nodes input, .dmai-nodes textarea, .dmai-nodes select {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
font: inherit;
|
| 27 |
color: inherit;
|
| 28 |
}
|
|
|
|
| 30 |
cursor: pointer;
|
| 31 |
border: 0;
|
| 32 |
background: none;
|
| 33 |
+
transition: background 120ms ease, color 120ms ease;
|
| 34 |
}
|
| 35 |
+
.dmai-nodes button:disabled { opacity: .4; cursor: default; }
|
| 36 |
+
.dmai-nodes button:hover:not(:disabled) { background: var(--dmai-hover); }
|
| 37 |
+
.dmai-nodes :is(button, input, select, textarea, summary):focus-visible {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
outline: 2px solid var(--dmai-accent);
|
| 39 |
+
outline-offset: 3px;
|
| 40 |
}
|
| 41 |
+
.dmai-nodes input, .dmai-nodes textarea, .dmai-nodes select {
|
|
|
|
|
|
|
| 42 |
background: var(--dmai-inset);
|
| 43 |
border: 1px solid var(--dmai-line);
|
| 44 |
+
border-radius: 10px;
|
| 45 |
+
padding: 11px 13px;
|
| 46 |
min-width: 0;
|
| 47 |
max-width: 100%;
|
| 48 |
+
min-height: 44px;
|
| 49 |
}
|
| 50 |
+
.dmai-nodes input[aria-invalid="true"] { border-color: #ffc3a6; }
|
| 51 |
+
.dmai-nodes input[type="number"] { appearance: textfield; }
|
| 52 |
+
.dmai-nodes input::-webkit-inner-spin-button { appearance: none; }
|
| 53 |
+
.dmai-nodes input::placeholder, .dmai-nodes textarea::placeholder { color: #8f99a7; opacity: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
.dmai-nodes .icon {
|
| 55 |
+
width: 20px; height: 20px; stroke: currentColor; stroke-width: 1.65;
|
| 56 |
+
fill: none; stroke-linecap: round; stroke-linejoin: round;
|
| 57 |
+
flex: none; vertical-align: middle;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
}
|
| 59 |
.dmai-panel {
|
| 60 |
background: var(--dmai-panel);
|
| 61 |
border: 1px solid var(--dmai-line);
|
| 62 |
+
border-radius: 18px;
|
| 63 |
+
width: 100%; height: 100%; min-width: 0;
|
| 64 |
+
overflow: auto; position: relative;
|
| 65 |
+
scrollbar-width: thin; scrollbar-color: #566172 transparent;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
.dmai-nodes .dmai-head {
|
| 68 |
+
min-height: 72px; display: flex; align-items: center; gap: 12px;
|
| 69 |
+
padding: 18px 22px;
|
| 70 |
+
}
|
| 71 |
+
.dmai-nodes .dmai-head h2 { font-size: 21px; font-weight: 700; letter-spacing: -.65px; margin: 0; line-height: 1.2; }
|
| 72 |
+
.dmai-nodes .dmai-glyph { color: var(--dmai-accent); display: flex; }
|
| 73 |
+
.dmai-nodes .dmai-glyph .icon { width: 22px; height: 22px; }
|
| 74 |
+
.dmai-nodes .dmai-brand { margin-left: auto; font-size: 10px; font-weight: 700; letter-spacing: 1.6px; color: var(--dmai-muted); }
|
| 75 |
+
.dmai-nodes .dmai-body { padding: 0 22px 22px; min-width: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
.dmai-nodes .dmai-icon {
|
| 77 |
+
display: inline-flex; align-items: center; justify-content: center;
|
| 78 |
+
width: 40px; height: 40px; border-radius: 10px; padding: 0;
|
| 79 |
+
color: var(--dmai-muted); flex: none;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
}
|
| 81 |
+
.dmai-nodes .dmai-icon:hover { color: var(--dmai-text); }
|
| 82 |
.dmai-nodes .dmai-button {
|
| 83 |
+
display: inline-flex; align-items: center; justify-content: center;
|
| 84 |
+
gap: 8px; padding: 11px 15px; border-radius: 10px;
|
| 85 |
+
color: var(--dmai-text); font-size: 14px; min-height: 44px; font-weight: 600;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
}
|
| 87 |
.dmai-nodes .dmai-primary {
|
| 88 |
+
background: var(--dmai-accent); color: #17220f; font-weight: 700;
|
| 89 |
+
min-height: 46px; padding: 11px 18px; border-radius: 11px;
|
| 90 |
+
display: inline-flex; align-items: center; justify-content: center; gap: 9px;
|
| 91 |
+
}
|
| 92 |
+
.dmai-nodes .dmai-primary:hover:not(:disabled) { background: #d8f8ad; color: #17220f; }
|
| 93 |
+
.dmai-nodes .dmai-muted { color: var(--dmai-muted); font-size: 13px; }
|
| 94 |
+
.dmai-nodes .dmai-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
| 95 |
+
.dmai-nodes .dmai-segmented { display: flex; align-items: center; background: #101419; border-radius: 12px; padding: 4px; gap: 4px; }
|
| 96 |
+
.dmai-nodes .dmai-segmented button { min-height: 38px; padding: 8px 13px; border-radius: 8px; font-size: 13px; white-space: nowrap; color: var(--dmai-muted); }
|
| 97 |
+
.dmai-nodes button[aria-pressed="true"] { color: var(--dmai-accent); }
|
| 98 |
+
.dmai-nodes .dmai-segmented button[aria-pressed="true"] { background: #353e46; color: var(--dmai-text); box-shadow: 0 1px 4px #0004; }
|
| 99 |
+
.dmai-nodes .dmai-segmented .icon { width: 16px; height: 16px; }
|
| 100 |
+
.dmai-nodes .dmai-section-label { display: block; font-size: 13px; font-weight: 600; color: var(--dmai-muted); margin-bottom: 10px; }
|
| 101 |
+
.dmai-nodes .dmai-prompt-field { display: flex; flex-direction: column; gap: 10px; }
|
| 102 |
+
.dmai-nodes .dmai-prompt-field > span { font-size: 13px; font-weight: 600; color: var(--dmai-muted); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
.dmai-nodes .dmai-prompt {
|
| 104 |
+
display: block; width: 100%; min-height: 160px; resize: vertical;
|
| 105 |
+
padding: 16px; font-size: 16px; line-height: 1.65;
|
| 106 |
+
background: #11161c; border-color: #3b4553; border-radius: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
}
|
| 108 |
+
.dmai-nodes .dmai-format-section { margin-top: 22px; }
|
| 109 |
+
.dmai-nodes .dmai-ratios { display: flex; align-items: stretch; gap: 8px; margin: 10px 0 16px; }
|
| 110 |
.dmai-nodes .dmai-ratios .dmai-button {
|
| 111 |
+
flex: 1; flex-direction: column; gap: 9px; min-height: 68px;
|
| 112 |
+
padding: 10px 4px; font-size: 13px; border: 1px solid var(--dmai-line);
|
| 113 |
+
background: var(--dmai-inset); font-weight: 500;
|
| 114 |
+
}
|
| 115 |
+
.dmai-nodes .dmai-ratios .dmai-button[aria-pressed="true"] { border-color: var(--dmai-accent); background: #c6f18d0d; }
|
| 116 |
+
.dmai-nodes .dmai-ratios .dmai-ratio-more { flex: .7; }
|
| 117 |
+
.dmai-nodes .dmai-ratio-shape { display: block; height: 21px; border: 1.5px solid currentColor; border-radius: 3px; max-width: 42px; }
|
| 118 |
+
.dmai-nodes .dmai-resolution { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 16px 0 20px; flex-wrap: wrap; }
|
| 119 |
+
.dmai-nodes .dmai-dimensions { font-size: 13px; color: var(--dmai-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
| 120 |
+
.dmai-nodes .dmai-count { border-top: 1px solid var(--dmai-line); padding-top: 16px; }
|
| 121 |
+
.dmai-nodes .dmai-count > span { font-size: 15px; color: var(--dmai-text); font-weight: 600; }
|
| 122 |
+
.dmai-nodes .dmai-stepper { display: flex; align-items: center; gap: 8px; background: var(--dmai-inset); border-radius: 10px; }
|
| 123 |
+
.dmai-nodes .dmai-stepper output { min-width: 30px; text-align: center; font-weight: 700; font-size: 16px; font-variant-numeric: tabular-nums; }
|
| 124 |
+
.dmai-nodes .dmai-details { border-top: 1px solid var(--dmai-line); padding-top: 16px; margin-top: 18px; }
|
| 125 |
+
.dmai-nodes .dmai-details summary { cursor: pointer; color: var(--dmai-muted); font-size: 13px; font-weight: 500; min-height: 24px; }
|
| 126 |
+
.dmai-nodes .dmai-details-body { padding-top: 14px; display: grid; gap: 14px; }
|
| 127 |
+
.dmai-nodes .dmai-negative { width: 100%; min-height: 90px; resize: vertical; }
|
| 128 |
+
.dmai-nodes .dmai-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
|
| 129 |
+
.dmai-nodes .dmai-field > span { font-size: 13px; font-weight: 500; color: var(--dmai-muted); }
|
| 130 |
+
.dmai-nodes .dmai-field select, .dmai-nodes .dmai-field input { width: 100%; font-size: 15px; }
|
| 131 |
+
.dmai-nodes .dmai-model select { font-size: 16px; font-weight: 600; min-height: 52px; }
|
| 132 |
+
.dmai-nodes .dmai-mode { margin: 22px 0; }
|
| 133 |
+
.dmai-nodes .dmai-mode button { flex: 1; }
|
| 134 |
+
.dmai-nodes.dmai-engine .dmai-details { margin-top: 14px; padding-top: 12px; border-top: 0; }
|
| 135 |
+
.dmai-nodes .dmai-enhanced { background: #c6f18d07; border: 1px solid #c6f18d35; padding: 18px; border-radius: 12px; margin: 18px 0; }
|
| 136 |
+
.dmai-nodes .dmai-enhanced-title { font-size: 16px; font-weight: 600; margin: 0 0 6px; }
|
| 137 |
+
.dmai-nodes .dmai-enhanced-copy { color: var(--dmai-muted); font-size: 13px; line-height: 1.6; margin: 0 0 14px; }
|
| 138 |
+
.dmai-nodes .dmai-enhanced-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px; }
|
| 139 |
+
.dmai-nodes .dmai-enhanced-actions .dmai-button { background: var(--dmai-inset); border: 1px solid var(--dmai-line); }
|
| 140 |
+
.dmai-nodes .dmai-badge { font-size: 12px; background: #c6f18d12; color: var(--dmai-accent); border-radius: 6px; padding: 5px 8px; }
|
| 141 |
+
.dmai-nodes .dmai-engine-summary { font-size: 14px; color: var(--dmai-muted); margin: 16px 0; }
|
| 142 |
+
.dmai-nodes .dmai-settings { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 20px 0; }
|
| 143 |
+
.dmai-nodes .dmai-field-wide { grid-column: 1 / -1; }
|
| 144 |
+
.dmai-nodes .dmai-seed { display: flex; gap: 10px; align-items: flex-end; border-top: 1px solid var(--dmai-line); padding-top: 20px; margin-top: 20px; }
|
| 145 |
+
.dmai-nodes .dmai-seed .dmai-field { flex: 1; }
|
| 146 |
+
.dmai-nodes .dmai-seed > .dmai-icon { height: 46px; width: 46px; background: var(--dmai-inset); }
|
| 147 |
+
.dmai-nodes .dmai-generate { width: 100%; margin-top: 20px; min-height: 52px; font-size: 16px; }
|
| 148 |
+
.dmai-nodes .dmai-lora-list { display: grid; gap: 12px; margin-bottom: 16px; max-height: 490px; overflow-y: auto; scrollbar-width: thin; padding: 1px; }
|
| 149 |
+
.dmai-nodes .dmai-nodes-lora-row { display: flex; flex-direction: column; gap: 16px; padding: 15px; border: 1px solid var(--dmai-line); border-radius: 13px; background: #22272e; min-width: 0; }
|
| 150 |
+
.dmai-nodes .dmai-lora-main { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
| 151 |
+
.dmai-nodes .dmai-lora-copy { min-width: 0; flex: 1; }
|
| 152 |
+
.dmai-nodes .dmai-nodes-lora-name { display: block; overflow-wrap: anywhere; font-size: 15px; font-weight: 600; line-height: 1.4; }
|
| 153 |
+
.dmai-nodes .dmai-nodes-lora-order { font-size: 12px; font-weight: 600; color: var(--dmai-muted); font-variant-numeric: tabular-nums; flex: none; }
|
| 154 |
+
.dmai-nodes .dmai-lora-main > .dmai-grip { width: 20px; height: 36px; cursor: grab; color: #8793a5; }
|
| 155 |
+
.dmai-nodes .dmai-grip .icon { width: 16px; }
|
| 156 |
+
.dmai-nodes .dmai-lora-actions { display: flex; align-items: center; flex: none; gap: 2px; }
|
| 157 |
+
.dmai-nodes .dmai-lora-actions .dmai-icon { width: 34px; height: 36px; }
|
| 158 |
+
.dmai-nodes .dmai-lora-controls { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr) auto; gap: 12px; align-items: end; }
|
| 159 |
+
.dmai-nodes .dmai-lora-strength-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
| 160 |
+
.dmai-nodes .dmai-lora-strength-field > span { font-size: 12px; color: var(--dmai-muted); }
|
| 161 |
+
.dmai-nodes .dmai-strength { width: 100%; font-size: 15px; font-weight: 600; padding: 9px 12px; font-variant-numeric: tabular-nums; background: #14191f; }
|
| 162 |
+
.dmai-nodes .dmai-reorder { display: flex; gap: 2px; }
|
| 163 |
+
.dmai-nodes .dmai-reorder .dmai-icon { width: 32px; height: 44px; }
|
| 164 |
+
.dmai-nodes .dmai-nodes-lora-add { width: 100%; border: 1px solid var(--dmai-line); background: var(--dmai-inset); }
|
| 165 |
+
.dmai-nodes .dmai-off { opacity: .55; }
|
| 166 |
+
.dmai-nodes .dmai-dragging { opacity: .45; }
|
| 167 |
+
.dmai-nodes .dmai-dragover { border-color: var(--dmai-accent); box-shadow: 0 -2px var(--dmai-accent); }
|
| 168 |
+
.dmai-nodes .dmai-footnote { font-size: 12px; color: var(--dmai-muted); margin-top: 14px; }
|
| 169 |
+
.dmai-nodes .dmai-empty { padding: 34px 12px; color: var(--dmai-muted); font-size: 14px; text-align: center; }
|
| 170 |
+
.dmai-nodes .dmai-gallery-toolbar { margin: -5px 0 16px; }
|
| 171 |
+
.dmai-nodes .dmai-gallery-toolbar > span { font-size: 14px; }
|
| 172 |
+
.dmai-nodes .dmai-gallery-scroll { max-height: 430px; overflow: auto; scrollbar-width: thin; scrollbar-color: #566172 transparent; min-height: 230px; }
|
| 173 |
+
.dmai-nodes .dmai-gallery-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 12px; }
|
| 174 |
+
.dmai-nodes .dmai-gallery-grid.dmai-dense { grid-template-columns: repeat(4,minmax(0,1fr)); gap: 8px; }
|
| 175 |
+
.dmai-nodes .dmai-image-card { position: relative; border-radius: 12px; overflow: hidden; isolation: isolate; aspect-ratio: 4/5; background: var(--dmai-inset); }
|
| 176 |
+
.dmai-nodes .dmai-image-select { display: block; width: 100%; height: 100%; padding: 0; position: relative; border: 2px solid transparent; border-radius: 12px; overflow: hidden; }
|
| 177 |
+
.dmai-nodes .dmai-image-select[aria-pressed="true"] { border-color: var(--dmai-accent); }
|
| 178 |
+
.dmai-nodes .dmai-image-select img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
| 179 |
+
.dmai-nodes .dmai-check { position: absolute; left: 10px; top: 10px; width: 25px; height: 25px; border: 1px solid #ffffffaa; background: #121820a0; color: var(--dmai-accent); border-radius: 7px; font-size: 16px; line-height: 23px; text-align: center; }
|
| 180 |
+
.dmai-nodes .dmai-image-select[aria-pressed="true"] .dmai-check { background: var(--dmai-accent); border-color: var(--dmai-accent); color: #17220f; }
|
| 181 |
+
.dmai-nodes .dmai-image-actions { position: absolute; bottom: 10px; right: 10px; display: flex; gap: 3px; background: #10151edf; backdrop-filter: blur(8px); padding: 4px; border: 1px solid #ffffff30; border-radius: 11px; }
|
| 182 |
+
.dmai-nodes .dmai-image-actions .dmai-icon { width: 36px; height: 36px; color: #f5f7fa; border-radius: 7px; }
|
| 183 |
+
.dmai-nodes .dmai-image-actions .dmai-icon:last-child { color: var(--dmai-accent); }
|
| 184 |
+
.dmai-nodes .dmai-image-actions .icon { width: 19px; height: 19px; }
|
| 185 |
+
.dmai-nodes .dmai-image-actions .dmai-icon:hover { background: var(--dmai-accent); color: #17220f; }
|
| 186 |
+
.dmai-nodes .dmai-selection { border-top: 1px solid var(--dmai-line); margin-top: 18px; padding-top: 16px; font-size: 14px; justify-content: flex-start; }
|
| 187 |
+
.dmai-nodes .dmai-selection .dmai-primary { margin-left: auto; width: auto; font-size: 14px; }
|
| 188 |
+
.dmai-nodes .dmai-status { font-size: 13px; padding: 14px 22px; color: var(--dmai-muted); overflow-wrap: anywhere; border-top: 1px solid var(--dmai-line); }
|
| 189 |
+
.dmai-nodes .dmai-status[data-error="true"], .dmai-nodes .dmai-warning { color: #ffc3a6; font-size: 13px; }
|
| 190 |
+
.dmai-nodes .dmai-raw { width: 100%; height: 240px; resize: vertical; }
|
| 191 |
+
.dmai-dialog { background: var(--dmai-panel); border: 1px solid var(--dmai-line); border-radius: 18px; padding: 0; width: min(600px,calc(100vw - 32px)); max-height: calc(100vh - 40px); box-shadow: 0 28px 80px #0009; }
|
| 192 |
+
.dmai-dialog::backdrop { background: #090d14b8; backdrop-filter: blur(4px); }
|
| 193 |
+
.dmai-nodes .dmai-dialog-head { display: flex; align-items: center; justify-content: space-between; padding: 20px 24px; border-bottom: 1px solid var(--dmai-line); gap: 12px; }
|
| 194 |
+
.dmai-nodes .dmai-dialog-head h2 { font-size: 20px; font-weight: 700; margin: 0; letter-spacing: -.3px; }
|
| 195 |
+
.dmai-nodes .dmai-dialog-body { padding: 24px; display: grid; gap: 18px; }
|
| 196 |
+
.dmai-nodes .dmai-format-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 10px; }
|
| 197 |
+
.dmai-nodes .dmai-format-grid .dmai-button { border: 1px solid var(--dmai-line); padding: 16px 5px; display: flex; flex-direction: column; gap: 10px; }
|
| 198 |
+
.dmai-nodes .dmai-custom-size { display: grid; grid-template-columns: 1fr 40px 1fr; gap: 12px; align-items: end; }
|
| 199 |
+
.dmai-nodes .dmai-size-output { text-align: center; background: var(--dmai-inset); padding: 28px; color: var(--dmai-accent); font-size: 24px; font-weight: 700; border: 1px solid var(--dmai-line); border-radius: 12px; }
|
| 200 |
+
.dmai-nodes .dmai-library { display: grid; gap: 8px; max-height: 400px; overflow: auto; }
|
| 201 |
+
.dmai-nodes .dmai-library .dmai-button { justify-content: flex-start; text-align: left; overflow-wrap: anywhere; padding: 14px; background: var(--dmai-inset); }
|
| 202 |
+
.dmai-nodes .dmai-filename { overflow-wrap: anywhere; font-size: 14px; }
|
| 203 |
+
.dmai-nodes .dmai-lightbox { display: block; width: 100%; height: auto; max-height: 65vh; object-fit: contain; border-radius: 12px; }
|
| 204 |
+
/* Keep animation within the node; native socket hit targets remain unobstructed. */
|
| 205 |
+
.dmai-panel.dmai-prompter { overflow: hidden; }
|
| 206 |
+
.dmai-nodes .dmai-prompter-scroll { width: 100%; height: 100%; overflow: auto; scrollbar-width: thin; scrollbar-color: #566172 transparent; border-radius: inherit; }
|
| 207 |
+
.dmai-nodes .dmai-progress-outline { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2; overflow: visible; fill: none; }
|
| 208 |
+
.dmai-nodes .dmai-progress-track { stroke: var(--dmai-accent); stroke-width: 1; opacity: .13; }
|
| 209 |
+
.dmai-nodes .dmai-progress-stroke { stroke: var(--dmai-accent); stroke-width: 2; stroke-linecap: round; transition: stroke-dashoffset 350ms ease-out; filter: drop-shadow(0 0 3px #c6f18d66); }
|
| 210 |
+
.dmai-nodes[data-generating="true"] .dmai-progress-stroke { animation: dmai-progress-glow 2.4s ease-in-out infinite; }
|
| 211 |
+
.dmai-nodes[data-generation-phase="queued"] .dmai-progress-stroke { transition: none; }
|
| 212 |
+
.dmai-nodes .dmai-progress-readout { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 12px; color: var(--dmai-muted); font-size: 13px; }
|
| 213 |
+
.dmai-nodes .dmai-progress-value { font-variant-numeric: tabular-nums; color: var(--dmai-accent); }
|
| 214 |
+
.dmai-nodes[data-generation-phase="error"] .dmai-progress-stroke, .dmai-nodes[data-generation-phase="interrupted"] .dmai-progress-stroke { stroke: #ffc3a6; filter: none; }
|
| 215 |
+
.dmai-nodes[data-generation-phase="error"] .dmai-progress-value, .dmai-nodes[data-generation-phase="interrupted"] .dmai-progress-value { color: #ffc3a6; }
|
| 216 |
+
@keyframes dmai-progress-glow { 0%,100% { opacity: .72; } 50% { opacity: 1; } }
|
| 217 |
+
@media (prefers-reduced-motion: reduce) { .dmai-nodes * { transition: none !important; animation: none !important; } }
|
| 218 |
+
/* The image grid takes the remaining height; downloads stay visible. */
|
| 219 |
+
.dmai-panel.dmai-gallery { display: flex; flex-direction: column; overflow: hidden; }
|
| 220 |
+
.dmai-nodes.dmai-gallery > .dmai-head { flex: none; }
|
| 221 |
+
.dmai-nodes.dmai-gallery > .dmai-body { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
| 222 |
+
.dmai-nodes.dmai-gallery .dmai-gallery-toolbar, .dmai-nodes.dmai-gallery .dmai-selection { flex: none; }
|
| 223 |
+
.dmai-nodes.dmai-gallery .dmai-gallery-scroll { flex: 1; min-height: 120px; max-height: none; }
|
| 224 |
+
.dmai-nodes.dmai-gallery > .dmai-status { flex: none; }
|
| 225 |
+
.dmai-nodes .dmai-lora-dialog-strengths { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
| 226 |
+
.dmai-nodes .dmai-enhanced-summary { margin: 10px 0 3px; font-size: 14px; font-weight: 600; }
|
| 227 |
+
.dmai-nodes .dmai-enhanced-sampling { margin: 0; font-size: 13px; color: var(--dmai-muted); overflow-wrap: anywhere; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web/progress.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { PROMPTER_TYPE } from "./workflow-migration.mjs";
|
| 2 |
|
| 3 |
// Job identity comes from the submitted API graph, never the current selection.
|
| 4 |
const IDLE = Object.freeze({ phase: "idle", percent: 0 });
|
|
|
|
| 1 |
+
import { PROMPTER_TYPE } from "./workflow-migration.mjs?v=0.2.0";
|
| 2 |
|
| 3 |
// Job identity comes from the submitted API graph, never the current selection.
|
| 4 |
const IDLE = Object.freeze({ phase: "idle", percent: 0 });
|
web/workflow-migration.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { readPrompt } from "./core.mjs";
|
| 2 |
|
| 3 |
export const PROMPTER_TYPE = "DMAINodesPrompter";
|
| 4 |
const PREVIEW_PROMPTER_TYPE = "DMAIPrompter";
|
|
|
|
| 1 |
+
import { readPrompt } from "./core.mjs?v=0.2.0";
|
| 2 |
|
| 3 |
export const PROMPTER_TYPE = "DMAINodesPrompter";
|
| 4 |
const PREVIEW_PROMPTER_TYPE = "DMAIPrompter";
|
workflows/DMAI-NODES-FLUX.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
| 6 |
"id": 1,
|
| 7 |
"type": "DMAINodesPrompter",
|
| 8 |
"pos": [
|
| 9 |
-
|
| 10 |
-
|
| 11 |
],
|
| 12 |
"size": [
|
| 13 |
-
|
| 14 |
-
|
| 15 |
],
|
| 16 |
"flags": {},
|
| 17 |
"order": 0,
|
|
@@ -44,12 +44,12 @@
|
|
| 44 |
"id": 2,
|
| 45 |
"type": "DMAILoRAStack",
|
| 46 |
"pos": [
|
| 47 |
-
|
| 48 |
-
|
| 49 |
],
|
| 50 |
"size": [
|
| 51 |
-
|
| 52 |
-
|
| 53 |
],
|
| 54 |
"flags": {},
|
| 55 |
"order": 1,
|
|
@@ -76,12 +76,12 @@
|
|
| 76 |
"id": 3,
|
| 77 |
"type": "DMAIGenerationEngine",
|
| 78 |
"pos": [
|
| 79 |
-
|
| 80 |
-
|
| 81 |
],
|
| 82 |
"size": [
|
| 83 |
-
|
| 84 |
-
|
| 85 |
],
|
| 86 |
"flags": {},
|
| 87 |
"order": 2,
|
|
@@ -167,12 +167,12 @@
|
|
| 167 |
"id": 4,
|
| 168 |
"type": "DMAIGallery",
|
| 169 |
"pos": [
|
| 170 |
-
|
| 171 |
-
|
| 172 |
],
|
| 173 |
"size": [
|
| 174 |
-
|
| 175 |
-
|
| 176 |
],
|
| 177 |
"flags": {},
|
| 178 |
"order": 3,
|
|
@@ -245,8 +245,8 @@
|
|
| 245 |
"ds": {
|
| 246 |
"scale": 0.7,
|
| 247 |
"offset": [
|
| 248 |
-
|
| 249 |
-
|
| 250 |
]
|
| 251 |
}
|
| 252 |
},
|
|
|
|
| 6 |
"id": 1,
|
| 7 |
"type": "DMAINodesPrompter",
|
| 8 |
"pos": [
|
| 9 |
+
80,
|
| 10 |
+
60
|
| 11 |
],
|
| 12 |
"size": [
|
| 13 |
+
504,
|
| 14 |
+
738
|
| 15 |
],
|
| 16 |
"flags": {},
|
| 17 |
"order": 0,
|
|
|
|
| 44 |
"id": 2,
|
| 45 |
"type": "DMAILoRAStack",
|
| 46 |
"pos": [
|
| 47 |
+
80,
|
| 48 |
+
880
|
| 49 |
],
|
| 50 |
"size": [
|
| 51 |
+
524,
|
| 52 |
+
568
|
| 53 |
],
|
| 54 |
"flags": {},
|
| 55 |
"order": 1,
|
|
|
|
| 76 |
"id": 3,
|
| 77 |
"type": "DMAIGenerationEngine",
|
| 78 |
"pos": [
|
| 79 |
+
804,
|
| 80 |
+
180
|
| 81 |
],
|
| 82 |
"size": [
|
| 83 |
+
484,
|
| 84 |
+
748
|
| 85 |
],
|
| 86 |
"flags": {},
|
| 87 |
"order": 2,
|
|
|
|
| 167 |
"id": 4,
|
| 168 |
"type": "DMAIGallery",
|
| 169 |
"pos": [
|
| 170 |
+
1488,
|
| 171 |
+
160
|
| 172 |
],
|
| 173 |
"size": [
|
| 174 |
+
804,
|
| 175 |
+
618
|
| 176 |
],
|
| 177 |
"flags": {},
|
| 178 |
"order": 3,
|
|
|
|
| 245 |
"ds": {
|
| 246 |
"scale": 0.7,
|
| 247 |
"offset": [
|
| 248 |
+
25,
|
| 249 |
+
25
|
| 250 |
]
|
| 251 |
}
|
| 252 |
},
|
workflows/DMAI-NODES-Krea.api.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
| 22 |
"2",
|
| 23 |
0
|
| 24 |
],
|
| 25 |
-
"config_json": "{\"schema_version\": 1, \"profile_id\": \"krea2-turbo\", \"mode\": \"
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"4": {
|
|
|
|
| 22 |
"2",
|
| 23 |
0
|
| 24 |
],
|
| 25 |
+
"config_json": "{\"schema_version\": 1, \"profile_id\": \"krea2-turbo\", \"mode\": \"manual\", \"preset_id\": \"\", \"settings\": {\"steps\": 8, \"cfg\": 1.1, \"sampler\": \"euler\", \"scheduler\": \"beta\", \"denoise\": 1, \"enhancer\": \"none\"}, \"seed\": 481516, \"models\": {\"diffusion_model\": \"krea2_turbo_fp8.safetensors\", \"text_encoder\": \"qwen3vl_4b_fp8_scaled.safetensors\", \"vae\": \"wan_2.1_vae.safetensors\", \"checkpoint\": \"\"}, \"presets\": []}"
|
| 26 |
}
|
| 27 |
},
|
| 28 |
"4": {
|
workflows/DMAI-NODES-Krea.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
| 6 |
"id": 1,
|
| 7 |
"type": "DMAINodesPrompter",
|
| 8 |
"pos": [
|
| 9 |
-
|
| 10 |
-
|
| 11 |
],
|
| 12 |
"size": [
|
| 13 |
-
|
| 14 |
-
|
| 15 |
],
|
| 16 |
"flags": {},
|
| 17 |
"order": 0,
|
|
@@ -44,12 +44,12 @@
|
|
| 44 |
"id": 2,
|
| 45 |
"type": "DMAILoRAStack",
|
| 46 |
"pos": [
|
| 47 |
-
|
| 48 |
-
|
| 49 |
],
|
| 50 |
"size": [
|
| 51 |
-
|
| 52 |
-
|
| 53 |
],
|
| 54 |
"flags": {},
|
| 55 |
"order": 1,
|
|
@@ -76,12 +76,12 @@
|
|
| 76 |
"id": 3,
|
| 77 |
"type": "DMAIGenerationEngine",
|
| 78 |
"pos": [
|
| 79 |
-
|
| 80 |
-
|
| 81 |
],
|
| 82 |
"size": [
|
| 83 |
-
|
| 84 |
-
|
| 85 |
],
|
| 86 |
"flags": {},
|
| 87 |
"order": 2,
|
|
@@ -160,19 +160,19 @@
|
|
| 160 |
"Node name for S&R": "DMAIGenerationEngine"
|
| 161 |
},
|
| 162 |
"widgets_values": [
|
| 163 |
-
"{\"schema_version\": 1, \"profile_id\": \"krea2-turbo\", \"mode\": \"
|
| 164 |
]
|
| 165 |
},
|
| 166 |
{
|
| 167 |
"id": 4,
|
| 168 |
"type": "DMAIGallery",
|
| 169 |
"pos": [
|
| 170 |
-
|
| 171 |
-
|
| 172 |
],
|
| 173 |
"size": [
|
| 174 |
-
|
| 175 |
-
|
| 176 |
],
|
| 177 |
"flags": {},
|
| 178 |
"order": 3,
|
|
@@ -245,8 +245,8 @@
|
|
| 245 |
"ds": {
|
| 246 |
"scale": 0.7,
|
| 247 |
"offset": [
|
| 248 |
-
|
| 249 |
-
|
| 250 |
]
|
| 251 |
}
|
| 252 |
},
|
|
|
|
| 6 |
"id": 1,
|
| 7 |
"type": "DMAINodesPrompter",
|
| 8 |
"pos": [
|
| 9 |
+
80,
|
| 10 |
+
60
|
| 11 |
],
|
| 12 |
"size": [
|
| 13 |
+
504,
|
| 14 |
+
738
|
| 15 |
],
|
| 16 |
"flags": {},
|
| 17 |
"order": 0,
|
|
|
|
| 44 |
"id": 2,
|
| 45 |
"type": "DMAILoRAStack",
|
| 46 |
"pos": [
|
| 47 |
+
80,
|
| 48 |
+
880
|
| 49 |
],
|
| 50 |
"size": [
|
| 51 |
+
524,
|
| 52 |
+
568
|
| 53 |
],
|
| 54 |
"flags": {},
|
| 55 |
"order": 1,
|
|
|
|
| 76 |
"id": 3,
|
| 77 |
"type": "DMAIGenerationEngine",
|
| 78 |
"pos": [
|
| 79 |
+
804,
|
| 80 |
+
180
|
| 81 |
],
|
| 82 |
"size": [
|
| 83 |
+
484,
|
| 84 |
+
818
|
| 85 |
],
|
| 86 |
"flags": {},
|
| 87 |
"order": 2,
|
|
|
|
| 160 |
"Node name for S&R": "DMAIGenerationEngine"
|
| 161 |
},
|
| 162 |
"widgets_values": [
|
| 163 |
+
"{\"schema_version\": 1, \"profile_id\": \"krea2-turbo\", \"mode\": \"manual\", \"preset_id\": \"\", \"settings\": {\"steps\": 8, \"cfg\": 1.1, \"sampler\": \"euler\", \"scheduler\": \"beta\", \"denoise\": 1, \"enhancer\": \"none\"}, \"seed\": 481516, \"models\": {\"diffusion_model\": \"krea2_turbo_fp8.safetensors\", \"text_encoder\": \"qwen3vl_4b_fp8_scaled.safetensors\", \"vae\": \"wan_2.1_vae.safetensors\", \"checkpoint\": \"\"}, \"presets\": []}"
|
| 164 |
]
|
| 165 |
},
|
| 166 |
{
|
| 167 |
"id": 4,
|
| 168 |
"type": "DMAIGallery",
|
| 169 |
"pos": [
|
| 170 |
+
1488,
|
| 171 |
+
160
|
| 172 |
],
|
| 173 |
"size": [
|
| 174 |
+
804,
|
| 175 |
+
618
|
| 176 |
],
|
| 177 |
"flags": {},
|
| 178 |
"order": 3,
|
|
|
|
| 245 |
"ds": {
|
| 246 |
"scale": 0.7,
|
| 247 |
"offset": [
|
| 248 |
+
25,
|
| 249 |
+
25
|
| 250 |
]
|
| 251 |
}
|
| 252 |
},
|
workflows/DMAI-NODES-Qwen.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
| 6 |
"id": 1,
|
| 7 |
"type": "DMAINodesPrompter",
|
| 8 |
"pos": [
|
| 9 |
-
|
| 10 |
-
|
| 11 |
],
|
| 12 |
"size": [
|
| 13 |
-
|
| 14 |
-
|
| 15 |
],
|
| 16 |
"flags": {},
|
| 17 |
"order": 0,
|
|
@@ -44,12 +44,12 @@
|
|
| 44 |
"id": 2,
|
| 45 |
"type": "DMAILoRAStack",
|
| 46 |
"pos": [
|
| 47 |
-
|
| 48 |
-
|
| 49 |
],
|
| 50 |
"size": [
|
| 51 |
-
|
| 52 |
-
|
| 53 |
],
|
| 54 |
"flags": {},
|
| 55 |
"order": 1,
|
|
@@ -76,12 +76,12 @@
|
|
| 76 |
"id": 3,
|
| 77 |
"type": "DMAIGenerationEngine",
|
| 78 |
"pos": [
|
| 79 |
-
|
| 80 |
-
|
| 81 |
],
|
| 82 |
"size": [
|
| 83 |
-
|
| 84 |
-
|
| 85 |
],
|
| 86 |
"flags": {},
|
| 87 |
"order": 2,
|
|
@@ -167,12 +167,12 @@
|
|
| 167 |
"id": 4,
|
| 168 |
"type": "DMAIGallery",
|
| 169 |
"pos": [
|
| 170 |
-
|
| 171 |
-
|
| 172 |
],
|
| 173 |
"size": [
|
| 174 |
-
|
| 175 |
-
|
| 176 |
],
|
| 177 |
"flags": {},
|
| 178 |
"order": 3,
|
|
@@ -245,8 +245,8 @@
|
|
| 245 |
"ds": {
|
| 246 |
"scale": 0.7,
|
| 247 |
"offset": [
|
| 248 |
-
|
| 249 |
-
|
| 250 |
]
|
| 251 |
}
|
| 252 |
},
|
|
|
|
| 6 |
"id": 1,
|
| 7 |
"type": "DMAINodesPrompter",
|
| 8 |
"pos": [
|
| 9 |
+
80,
|
| 10 |
+
60
|
| 11 |
],
|
| 12 |
"size": [
|
| 13 |
+
504,
|
| 14 |
+
738
|
| 15 |
],
|
| 16 |
"flags": {},
|
| 17 |
"order": 0,
|
|
|
|
| 44 |
"id": 2,
|
| 45 |
"type": "DMAILoRAStack",
|
| 46 |
"pos": [
|
| 47 |
+
80,
|
| 48 |
+
880
|
| 49 |
],
|
| 50 |
"size": [
|
| 51 |
+
524,
|
| 52 |
+
568
|
| 53 |
],
|
| 54 |
"flags": {},
|
| 55 |
"order": 1,
|
|
|
|
| 76 |
"id": 3,
|
| 77 |
"type": "DMAIGenerationEngine",
|
| 78 |
"pos": [
|
| 79 |
+
804,
|
| 80 |
+
180
|
| 81 |
],
|
| 82 |
"size": [
|
| 83 |
+
484,
|
| 84 |
+
748
|
| 85 |
],
|
| 86 |
"flags": {},
|
| 87 |
"order": 2,
|
|
|
|
| 167 |
"id": 4,
|
| 168 |
"type": "DMAIGallery",
|
| 169 |
"pos": [
|
| 170 |
+
1488,
|
| 171 |
+
160
|
| 172 |
],
|
| 173 |
"size": [
|
| 174 |
+
804,
|
| 175 |
+
618
|
| 176 |
],
|
| 177 |
"flags": {},
|
| 178 |
"order": 3,
|
|
|
|
| 245 |
"ds": {
|
| 246 |
"scale": 0.7,
|
| 247 |
"offset": [
|
| 248 |
+
25,
|
| 249 |
+
25
|
| 250 |
]
|
| 251 |
}
|
| 252 |
},
|
workflows/DMAI-NODES-SDXL.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
| 6 |
"id": 1,
|
| 7 |
"type": "DMAINodesPrompter",
|
| 8 |
"pos": [
|
| 9 |
-
|
| 10 |
-
|
| 11 |
],
|
| 12 |
"size": [
|
| 13 |
-
|
| 14 |
-
|
| 15 |
],
|
| 16 |
"flags": {},
|
| 17 |
"order": 0,
|
|
@@ -44,12 +44,12 @@
|
|
| 44 |
"id": 2,
|
| 45 |
"type": "DMAILoRAStack",
|
| 46 |
"pos": [
|
| 47 |
-
|
| 48 |
-
|
| 49 |
],
|
| 50 |
"size": [
|
| 51 |
-
|
| 52 |
-
|
| 53 |
],
|
| 54 |
"flags": {},
|
| 55 |
"order": 1,
|
|
@@ -76,12 +76,12 @@
|
|
| 76 |
"id": 3,
|
| 77 |
"type": "DMAIGenerationEngine",
|
| 78 |
"pos": [
|
| 79 |
-
|
| 80 |
-
|
| 81 |
],
|
| 82 |
"size": [
|
| 83 |
-
|
| 84 |
-
|
| 85 |
],
|
| 86 |
"flags": {},
|
| 87 |
"order": 2,
|
|
@@ -167,12 +167,12 @@
|
|
| 167 |
"id": 4,
|
| 168 |
"type": "DMAIGallery",
|
| 169 |
"pos": [
|
| 170 |
-
|
| 171 |
-
|
| 172 |
],
|
| 173 |
"size": [
|
| 174 |
-
|
| 175 |
-
|
| 176 |
],
|
| 177 |
"flags": {},
|
| 178 |
"order": 3,
|
|
@@ -245,8 +245,8 @@
|
|
| 245 |
"ds": {
|
| 246 |
"scale": 0.7,
|
| 247 |
"offset": [
|
| 248 |
-
|
| 249 |
-
|
| 250 |
]
|
| 251 |
}
|
| 252 |
},
|
|
|
|
| 6 |
"id": 1,
|
| 7 |
"type": "DMAINodesPrompter",
|
| 8 |
"pos": [
|
| 9 |
+
80,
|
| 10 |
+
60
|
| 11 |
],
|
| 12 |
"size": [
|
| 13 |
+
504,
|
| 14 |
+
738
|
| 15 |
],
|
| 16 |
"flags": {},
|
| 17 |
"order": 0,
|
|
|
|
| 44 |
"id": 2,
|
| 45 |
"type": "DMAILoRAStack",
|
| 46 |
"pos": [
|
| 47 |
+
80,
|
| 48 |
+
880
|
| 49 |
],
|
| 50 |
"size": [
|
| 51 |
+
524,
|
| 52 |
+
568
|
| 53 |
],
|
| 54 |
"flags": {},
|
| 55 |
"order": 1,
|
|
|
|
| 76 |
"id": 3,
|
| 77 |
"type": "DMAIGenerationEngine",
|
| 78 |
"pos": [
|
| 79 |
+
804,
|
| 80 |
+
180
|
| 81 |
],
|
| 82 |
"size": [
|
| 83 |
+
484,
|
| 84 |
+
748
|
| 85 |
],
|
| 86 |
"flags": {},
|
| 87 |
"order": 2,
|
|
|
|
| 167 |
"id": 4,
|
| 168 |
"type": "DMAIGallery",
|
| 169 |
"pos": [
|
| 170 |
+
1488,
|
| 171 |
+
160
|
| 172 |
],
|
| 173 |
"size": [
|
| 174 |
+
804,
|
| 175 |
+
618
|
| 176 |
],
|
| 177 |
"flags": {},
|
| 178 |
"order": 3,
|
|
|
|
| 245 |
"ds": {
|
| 246 |
"scale": 0.7,
|
| 247 |
"offset": [
|
| 248 |
+
25,
|
| 249 |
+
25
|
| 250 |
]
|
| 251 |
}
|
| 252 |
},
|