ceroomedia commited on
Commit
ab7ca41
·
verified ·
1 Parent(s): cf48e9b

Release 0.1.3: isolate Prompter and migrate saved workflows

Browse files
CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
  # Changelog
2
 
 
 
 
 
 
 
 
3
  ## 0.1.2 — 2026-09-28
4
 
5
  - Fixed native socket dragging: DOM panels leave real canvas gutters around input and output hit targets. Hidden JSON widgets no longer affect socket spacing.
 
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.
6
+ - Upgrade identifiable DMAI NODES 0.1.0-0.1.2 UI workflows before loading; preserve legacy Prompters, node IDs, links and settings.
7
+ - Update starter workflows and progress tracking. Add an offline workflow migration tool and migration in the API submission helper, preserving source files.
8
+ - Isolate the new LoRA row and filename CSS classes from the older DMAI LoRA Loader styles.
9
+
10
  ## 0.1.2 — 2026-09-28
11
 
12
  - Fixed native socket dragging: DOM panels leave real canvas gutters around input and output hit targets. Hidden JSON widgets no longer affect socket spacing.
DMAI-NODES-v0.1.3.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1a68ce0492afcfcfd16894affb05b1667661fd9658b79be4d590139771ab07d2
3
+ size 149760
DMAI-NODES-v0.1.3.zip.sha256 ADDED
@@ -0,0 +1 @@
 
 
1
+ 1a68ce0492afcfcfd16894affb05b1667661fd9658b79be4d590139771ab07d2 DMAI-NODES-v0.1.3.zip
README.md CHANGED
@@ -14,7 +14,7 @@ tags:
14
 
15
  **A compact image workflow for ComfyUI.** Slate surfaces, clean Inter typography and English controls.
16
 
17
- **0.1.2 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
 
@@ -44,6 +44,8 @@ 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
  - [Windows Portable, local environments and Runpod](docs/INSTALLATION.md)
48
  - [Model files, presets and enhancement](docs/MODELS.md)
49
  - [API workflow and future website integration](docs/API.md)
 
14
 
15
  **A compact image workflow for ComfyUI.** Slate surfaces, clean Inter typography and English controls.
16
 
17
+ **0.1.3 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
 
 
44
 
45
  Restart ComfyUI and refresh the browser. Search for **DMAI** in the node library.
46
 
47
+ **Using an older DMAI package too?** Version 0.1.3 gives this package's Prompter 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.
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)
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.2"
 
1
  """DMAI NODES domain modules; importing these does not initialize a GPU."""
2
+ __version__ = "0.1.3"
dmai_nodes/nodes.py CHANGED
@@ -22,10 +22,10 @@ def json_config(value):
22
  return read_json(value)
23
 
24
 
25
- class DMAIPrompter(io.ComfyNode):
26
  @classmethod
27
  def define_schema(cls):
28
- return io.Schema(node_id="DMAIPrompter",display_name="DMAI Prompter",category="DMAI NODES",
29
  description="Prompt, dimensions and image count. Optional text input is prepended to local text.",
30
  inputs=[io.String.Input("config_json",default=json.dumps(DEFAULT_PROMPT),multiline=True,socketless=True),
31
  io.String.Input("text",optional=True,force_input=True,advanced=True,tooltip="Optional source text, followed by the local prompt.")],
@@ -139,4 +139,4 @@ class DMAIVAEDecode(io.ComfyNode):
139
  return io.NodeOutput(VAEDecode().decode(vae,latent)[0])
140
 
141
 
142
- NODE_CLASSES=[DMAIPrompter,DMAILoRAStack,DMAIGenerationEngine,DMAIGallery,DMAIKreaEnhancer,DMAIVAEEncode,DMAIVAEDecode]
 
22
  return read_json(value)
23
 
24
 
25
+ class DMAINodesPrompter(io.ComfyNode):
26
  @classmethod
27
  def define_schema(cls):
28
+ return io.Schema(node_id="DMAINodesPrompter",display_name="DMAI Prompter",category="DMAI NODES",
29
  description="Prompt, dimensions and image count. Optional text input is prepended to local text.",
30
  inputs=[io.String.Input("config_json",default=json.dumps(DEFAULT_PROMPT),multiline=True,socketless=True),
31
  io.String.Input("text",optional=True,force_input=True,advanced=True,tooltip="Optional source text, followed by the local prompt.")],
 
139
  return io.NodeOutput(VAEDecode().decode(vae,latent)[0])
140
 
141
 
142
+ NODE_CLASSES=[DMAINodesPrompter,DMAILoRAStack,DMAIGenerationEngine,DMAIGallery,DMAIKreaEnhancer,DMAIVAEEncode,DMAIVAEDecode]
docs/API.md CHANGED
@@ -24,6 +24,18 @@ 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
  ## Generation progress over WebSocket
28
 
29
  Connect to ComfyUI's `/ws?clientId=<client ID>` before submitting the workflow. Use the same ID as `client_id` in `POST /prompt`. The Engine sends `dmai_generation_progress` messages to that client through ComfyUI's existing WebSocket:
 
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
+ Version 0.1.3 uses `DMAINodesPrompter` as the Prompter's API `class_type`. 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.
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
+
33
+ ```bash
34
+ python custom_nodes/DMAI-NODES/tools/migrate_workflow.py old-workflow.api.json --output migrated-workflow.api.json
35
+ ```
36
+
37
+ The offline tool also accepts saved UI workflows. It requires a new output path and never replaces the source. Only records matching the new package's configuration and connection contracts are converted. Legacy CLIP/conditioning Prompters and ambiguous records remain unchanged; do not replace every occurrence of `DMAIPrompter` globally. UI workflows loaded in Comfy's frontend are migrated before nodes are created.
38
+
39
  ## Generation progress over WebSocket
40
 
41
  Connect to ComfyUI's `/ws?clientId=<client ID>` before submitting the workflow. Use the same ID as `client_id` in `POST /prompt`. The Engine sends `dmai_generation_progress` messages to that client through ComfyUI's existing WebSocket:
docs/INSTALLATION.md CHANGED
@@ -77,9 +77,29 @@ git -C .\ComfyUI\custom_nodes\DMAI-NODES pull --ff-only
77
 
78
  Restart ComfyUI afterward. 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.
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  ## Troubleshooting
81
 
82
  - **No DMAI nodes:** inspect the ComfyUI console for import errors; check the Python environment and one-level ZIP extraction.
 
83
  - **Missing model:** select the exact catalog entry, including subfolder. Refresh the model list after adding files.
84
  - **Enhancer unavailable:** install the pinned dependency or choose Clean Base/Manual with `none`.
85
  - **LoRA has no matching patches:** verify the LoRA family and selected base model; this is deliberately an error.
 
77
 
78
  Restart ComfyUI afterward. 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.
79
 
80
+ ### Updating from 0.1.2 or earlier
81
+
82
+ The new Prompter now 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.
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.1.3
88
+ git checkout v0.1.3
89
+ python -m pip install -r requirements.txt
90
+ git describe --tags --exact-match
91
+ ```
92
+
93
+ Use ComfyUI's Python environment for the dependency command; Windows Portable uses `..\..\..\python_embeded\python.exe` from that node directory. Restart ComfyUI, then hard-refresh the browser with Ctrl+F5.
94
+
95
+ UI workflows saved by DMAI NODES 0.1.0-0.1.2 are upgraded on load only when their Prompter data and output identify this package. Save the workflow after loading it. Legacy Suite/standalone Prompters keep their original IDs and connections. Ambiguous or damaged records are left for manual correction.
96
+
97
+ For API JSON sent directly to `/prompt`, use the new `DMAINodesPrompter` class type. The included API helper and offline conversion tool handle identifiable older DMAI NODES API workflows; see [API migration](API.md#prompter-id-migration). The server does not register the conflicting old ID as an alias.
98
+
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.1.3 or newer, restart ComfyUI and hard-refresh. Search `DMAINodesPrompter` or select **DMAI Prompter** with the **DMAI-NODES** badge. The new node has a `request` output of type `DMAI_PROMPT`.
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 choose Clean Base/Manual with `none`.
105
  - **LoRA has no matching patches:** verify the LoRA family and selected base model; this is deliberately an error.
docs/VALIDATION.md CHANGED
@@ -1,6 +1,17 @@
1
- # Validation — 0.1.2 preview
2
 
3
- Checked on 2026-09-28. This release has working ComfyUI registration, CPU contract tests and real local API checks. GPU image generation is still pending.
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  ## Reference environment
6
 
@@ -14,8 +25,8 @@ Checked on 2026-09-28. This release has working ComfyUI registration, CPU contra
14
 
15
  | Check | Evidence |
16
  | --- | --- |
17
- | Python suite | 81 tests passed with optional real-Comfy CPU tests enabled, including scoped native progress registry updates and multi-image progress. |
18
- | JavaScript suite | 53 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 and model-file selection. |
19
  | Node registration | All seven native V3 nodes appear in real `/object_info`. |
20
  | Runtime choices | Four model profiles, 45 native sampler names and 9 schedulers in the test installation. Extension installations can change these counts. |
21
  | 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.1.3 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 coexistence checks below were added for 0.1.3; earlier Gallery, model-picker and connector observations were made on 2026-09-28.
4
+
5
+ ## 0.1.3 coexistence and migration
6
+
7
+ - Real ComfyUI loader tests load the new extension alongside legacy V1 and Suite-style V3 Prompter fixtures in both orders. Each ID retains its own schema and execution contract. The new extension does not claim `DMAIPrompter`.
8
+ - A running CPU ComfyUI instance loaded the actual older **ComfyUI-DMAI-Suite** and the new package together. `/object_info` contained all seven new nodes and all four Suite nodes. `DMAINodesPrompter` returned `DMAI_PROMPT`; legacy `DMAIPrompter` retained `CONDITIONING, INT, INT, STRING`.
9
+ - In the real Canvas frontend, both Prompters appeared in their separate package categories and rendered their own controls. A saved 0.1.2 DMAI NODES workflow upgraded on load with its five links, 1152x768 dimensions, image count 2, model choices and LoRA strengths intact. A legacy Prompter was added, edited, saved and reloaded alongside it; its original ID and widget format were preserved.
10
+ - JavaScript regressions cover nested serialized subgraphs, named/positional widget formats, invalid or ambiguous signatures, unchanged settings/links and canonical progress tracking. The new frontend mounts only `DMAINodesPrompter`.
11
+ - Eleven Python migration tests cover mixed UI/API graphs, conservative signature checks, invalid links, UTF-8/configuration boundaries, source/output preservation and rejection of legacy nodes before API submission.
12
+ - The new LoRA row/name classes are isolated from the matching selectors found in older DMAI frontend styles.
13
+
14
+ These checks establish registration, UI coexistence and workflow migration. They do not add GPU inference coverage or certify unrelated third-party extension combinations.
15
 
16
  ## Reference environment
17
 
 
25
 
26
  | Check | Evidence |
27
  | --- | --- |
28
+ | Python suite | 99 tests passed with optional real-Comfy CPU tests enabled, including registration coexistence, workflow migration, scoped native progress registry updates and multi-image progress. |
29
+ | JavaScript suite | 64 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. |
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. |
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
  [project]
2
  name = "dmai-nodes"
3
- version = "0.1.2"
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.1.3"
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,11 +1,11 @@
1
  {
2
  "name": "DMAI NODES",
3
- "version": "0.1.2",
4
  "channel": "preview",
5
  "license": "MIT",
6
  "comfyui_reference": "0.37.0",
7
  "frontend_reference": "1.52.7",
8
- "nodes": ["DMAIPrompter", "DMAILoRAStack", "DMAIGenerationEngine", "DMAIGallery", "DMAIKreaEnhancer", "DMAIVAEEncode", "DMAIVAEDecode"],
9
  "model_profiles": ["krea2-turbo", "sdxl-checkpoint", "qwen-image", "flux1-dev"],
10
  "contains_model_weights": false,
11
  "gpu_inference_verified": false
 
1
  {
2
  "name": "DMAI NODES",
3
+ "version": "0.1.3",
4
  "channel": "preview",
5
  "license": "MIT",
6
  "comfyui_reference": "0.37.0",
7
  "frontend_reference": "1.52.7",
8
+ "nodes": ["DMAINodesPrompter", "DMAILoRAStack", "DMAIGenerationEngine", "DMAIGallery", "DMAIKreaEnhancer", "DMAIVAEEncode", "DMAIVAEDecode"],
9
  "model_profiles": ["krea2-turbo", "sdxl-checkpoint", "qwen-image", "flux1-dev"],
10
  "contains_model_weights": false,
11
  "gpu_inference_verified": false
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.1.2"
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"}
@@ -23,7 +23,7 @@ def included_files():
23
  if path.is_symlink():continue
24
  parts=path.relative_to(ROOT).parts
25
  if (len(parts)==1 and parts[0] in ROOT_FILES) or (parts[0] in DIRECTORIES and path.suffix in EXTENSIONS):result.append(path)
26
- elif len(parts)==2 and parts[0]=="tools" and path.name in {"run_workflow.py","build_release.py"}:result.append(path)
27
  return sorted(result,key=lambda item:item.relative_to(ROOT).as_posix())
28
 
29
 
@@ -38,7 +38,7 @@ def build():
38
  path=destination/f"DMAI-NODES-v{VERSION}.zip"
39
  files=included_files()
40
  names={item.relative_to(ROOT).as_posix() for item in files}
41
- required={"__init__.py","README.md","LICENSE","dmai_nodes/nodes.py","dmai_nodes/engine.py","dmai_nodes/lora.py","dmai_nodes/gallery.py","dmai_nodes/routes.py","web/dmai-nodes.js","requirements.txt","workflows/DMAI-NODES-Krea.json"}
42
  missing=required-names
43
  if missing:raise RuntimeError(f"Missing release files: {missing}")
44
  with zipfile.ZipFile(path,"w",zipfile.ZIP_DEFLATED,compresslevel=9) as archive:
 
7
  import zipfile
8
 
9
  ROOT=Path(__file__).resolve().parents[1]
10
+ VERSION="0.1.3"
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"}
 
23
  if path.is_symlink():continue
24
  parts=path.relative_to(ROOT).parts
25
  if (len(parts)==1 and parts[0] in ROOT_FILES) or (parts[0] in DIRECTORIES and path.suffix in EXTENSIONS):result.append(path)
26
+ elif len(parts)==2 and parts[0]=="tools" and path.name in {"run_workflow.py","migrate_workflow.py","build_release.py"}:result.append(path)
27
  return sorted(result,key=lambda item:item.relative_to(ROOT).as_posix())
28
 
29
 
 
38
  path=destination/f"DMAI-NODES-v{VERSION}.zip"
39
  files=included_files()
40
  names={item.relative_to(ROOT).as_posix() for item in files}
41
+ required={"__init__.py","README.md","LICENSE","dmai_nodes/nodes.py","dmai_nodes/engine.py","dmai_nodes/lora.py","dmai_nodes/gallery.py","dmai_nodes/routes.py","web/dmai-nodes.js","web/workflow-migration.mjs","tools/migrate_workflow.py","requirements.txt","workflows/DMAI-NODES-Krea.json"}
42
  missing=required-names
43
  if missing:raise RuntimeError(f"Missing release files: {missing}")
44
  with zipfile.ZipFile(path,"w",zipfile.ZIP_DEFLATED,compresslevel=9) as archive:
tools/migrate_workflow.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Copy an old DMAI NODES workflow to the conflict-free Prompter ID.
2
+
3
+ The old Suite's CLIP Prompter and ambiguous nodes are left untouched. This tool
4
+ uses only the Python standard library and never overwrites the input or output.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ from copy import deepcopy
10
+ import json
11
+ from pathlib import Path
12
+
13
+
14
+ OLD_PROMPTER_ID = "DMAIPrompter"
15
+ PROMPTER_ID = "DMAINodesPrompter"
16
+ PROMPT_KEYS = {"schema_version", "prompt", "negative_prompt", "width", "height", "count"}
17
+
18
+
19
+ def _unique_object(pairs):
20
+ result = {}
21
+ for key, value in pairs:
22
+ if key in result:
23
+ raise ValueError(f"Duplicate JSON key: {key}")
24
+ result[key] = value
25
+ return result
26
+
27
+
28
+ def _invalid_constant(value):
29
+ raise ValueError(f"Invalid JSON constant: {value}")
30
+
31
+
32
+ def read_json(raw):
33
+ return json.loads(raw, object_pairs_hook=_unique_object, parse_constant=_invalid_constant)
34
+
35
+
36
+ def _prompt_config(raw):
37
+ """Require the complete v1 request contract, not a coincidental JSON field."""
38
+ if not isinstance(raw, str) or len(raw.encode("utf-8")) > 128 * 1024:
39
+ return None
40
+ try:
41
+ value = read_json(raw)
42
+ except (ValueError, TypeError):
43
+ return None
44
+ if not isinstance(value, dict) or set(value) != PROMPT_KEYS:
45
+ return None
46
+ if type(value["schema_version"]) is not int or value["schema_version"] != 1:
47
+ return None
48
+ for key in ("prompt", "negative_prompt"):
49
+ if not isinstance(value[key], str) or len(value[key]) > 65536 or "\x00" in value[key]:
50
+ return None
51
+ for key in ("width", "height"):
52
+ if type(value[key]) is not int or not 64 <= value[key] <= 8192 or value[key] % 64:
53
+ return None
54
+ if value["width"] * value["height"] > 16777216:
55
+ return None
56
+ if type(value["count"]) is not int or not 1 <= value["count"] <= 8:
57
+ return None
58
+ return value
59
+
60
+
61
+ def _is_ui_prompter(node):
62
+ outputs = node.get("outputs")
63
+ if not isinstance(outputs, list) or len(outputs) != 1:
64
+ return False
65
+ output = outputs[0]
66
+ if (not isinstance(output, dict) or output.get("name") != "request"
67
+ or output.get("type") != "DMAI_PROMPT"
68
+ or ("slot_index" in output and (type(output["slot_index"]) is not int or output["slot_index"] != 0))):
69
+ return False
70
+ inputs = node.get("inputs", [])
71
+ if not isinstance(inputs, list) or any(
72
+ not isinstance(item, dict) or item.get("name") not in {"config_json", "text"}
73
+ or item.get("type") != "STRING" for item in inputs
74
+ ):
75
+ return False
76
+ if len({item["name"] for item in inputs}) != len(inputs):
77
+ return False
78
+ candidates = []
79
+ if "widgets_values" in node:
80
+ values = node["widgets_values"]
81
+ if not isinstance(values, list) or len(values) != 1:
82
+ return False
83
+ candidates.append(_prompt_config(values[0]))
84
+ if "widgets_values_named" in node:
85
+ values = node["widgets_values_named"]
86
+ if not isinstance(values, dict) or set(values) != {"config_json"}:
87
+ return False
88
+ candidates.append(_prompt_config(values["config_json"]))
89
+ return bool(candidates) and all(value is not None and value == candidates[0] for value in candidates)
90
+
91
+
92
+ def _link_from(value, node_id):
93
+ return (isinstance(value, list) and len(value) == 2
94
+ and type(value[0]) in (str, int) and str(value[0]) == str(node_id))
95
+
96
+
97
+ def _is_api_prompter(node_id, node, graph):
98
+ inputs = node.get("inputs")
99
+ if (not isinstance(inputs, dict) or set(inputs) - {"config_json", "text"}
100
+ or _prompt_config(inputs.get("config_json")) is None):
101
+ return False
102
+ # API JSON has no typed outputs. A slot-zero request link to our Engine is
103
+ # the extra evidence that distinguishes this from a legacy Suite node.
104
+ linked_engine = False
105
+ for target in graph.values():
106
+ if not isinstance(target, dict) or not isinstance(target.get("inputs"), dict):
107
+ continue
108
+ for name, value in target["inputs"].items():
109
+ if not _link_from(value, node_id):
110
+ continue
111
+ if type(value[1]) is not int or value[1] != 0:
112
+ return False
113
+ if target.get("class_type") == "DMAIGenerationEngine" and name == "request":
114
+ linked_engine = True
115
+ return linked_engine
116
+
117
+
118
+ def migrate_workflow(document):
119
+ """Return a migrated deep copy plus node paths; never change the caller's data."""
120
+ if not isinstance(document, dict):
121
+ raise ValueError("Expected a ComfyUI workflow or API prompt JSON object.")
122
+ result = deepcopy(document)
123
+ report = {"migrated": [], "preserved": []}
124
+
125
+ def visit_graph(graph, path):
126
+ if not isinstance(graph, dict):
127
+ return
128
+ nodes = graph.get("nodes")
129
+ if isinstance(nodes, list):
130
+ for index, node in enumerate(nodes):
131
+ if not isinstance(node, dict) or node.get("type") != OLD_PROMPTER_ID:
132
+ continue
133
+ location = f"{path}.nodes[{index}]"
134
+ if _is_ui_prompter(node):
135
+ node["type"] = PROMPTER_ID
136
+ properties = node.get("properties")
137
+ if isinstance(properties, dict) and properties.get("Node name for S&R") == OLD_PROMPTER_ID:
138
+ properties["Node name for S&R"] = PROMPTER_ID
139
+ report["migrated"].append(location)
140
+ else:
141
+ report["preserved"].append(location)
142
+ else:
143
+ for node_id, node in graph.items():
144
+ if not isinstance(node, dict) or node.get("class_type") != OLD_PROMPTER_ID:
145
+ continue
146
+ location = f"{path}[{node_id!r}]"
147
+ if _is_api_prompter(node_id, node, graph):
148
+ node["class_type"] = PROMPTER_ID
149
+ report["migrated"].append(location)
150
+ else:
151
+ report["preserved"].append(location)
152
+ # Serialized LiteGraph subgraphs live in definitions.subgraphs. Do not
153
+ # recursively scan arbitrary metadata, prompt text, or node properties.
154
+ definitions = graph.get("definitions")
155
+ if isinstance(definitions, dict) and isinstance(definitions.get("subgraphs"), list):
156
+ for index, subgraph in enumerate(definitions["subgraphs"]):
157
+ visit_graph(subgraph, f"{path}.definitions.subgraphs[{index}]")
158
+ for key in ("workflow", "prompt"):
159
+ if isinstance(graph.get(key), dict) and "class_type" not in graph[key]:
160
+ visit_graph(graph[key], f"{path}.{key}")
161
+ extra_data = graph.get("extra_data")
162
+ if isinstance(extra_data, dict):
163
+ extra_pnginfo = extra_data.get("extra_pnginfo")
164
+ if isinstance(extra_pnginfo, dict):
165
+ visit_graph(extra_pnginfo.get("workflow"), f"{path}.extra_data.extra_pnginfo.workflow")
166
+
167
+ visit_graph(result, "$")
168
+ return result, report
169
+
170
+
171
+ def main(argv=None):
172
+ parser = argparse.ArgumentParser(description=__doc__)
173
+ parser.add_argument("input", type=Path, help="Existing UI workflow or API prompt JSON")
174
+ parser.add_argument("--output", required=True, type=Path, help="New JSON filename; must not already exist")
175
+ args = parser.parse_args(argv)
176
+ if args.input.resolve() == args.output.resolve():
177
+ parser.error("Input and output must be different files. The original is always preserved.")
178
+ if args.output.exists():
179
+ parser.error("Output already exists. Choose a new filename; existing files are never overwritten.")
180
+ try:
181
+ document = read_json(args.input.read_text(encoding="utf-8-sig"))
182
+ result, report = migrate_workflow(document)
183
+ data = (json.dumps(result, indent=2, ensure_ascii=False, allow_nan=False) + "\n").encode("utf-8")
184
+ with args.output.open("xb") as output:
185
+ output.write(data)
186
+ except (OSError, ValueError, TypeError) as error:
187
+ parser.error(str(error))
188
+ print(f"Saved: {args.output}")
189
+ print(f"Migrated {len(report['migrated'])} DMAI NODES Prompter node(s).")
190
+ print(f"Preserved {len(report['preserved'])} legacy or ambiguous DMAIPrompter node(s).")
191
+ for path in report["preserved"]:
192
+ print(f" Unchanged: {path}")
193
+
194
+
195
+ if __name__ == "__main__":
196
+ main()
tools/run_workflow.py CHANGED
@@ -7,6 +7,11 @@ from urllib.error import HTTPError
7
  from urllib.request import Request,urlopen
8
  from uuid import uuid4
9
 
 
 
 
 
 
10
 
11
  def call(base,path,data=None):
12
  body=None if data is None else json.dumps(data).encode()
@@ -29,7 +34,11 @@ def main():
29
  parser.add_argument("--wait",action="store_true")
30
  parser.add_argument("--timeout",type=int,default=900)
31
  args=parser.parse_args()
32
- workflow=json.loads(args.workflow.read_text(encoding="utf-8"))
 
 
 
 
33
  request=json.loads(workflow["1"]["inputs"]["config_json"])
34
  request.update(prompt=args.prompt,count=args.count)
35
  workflow["1"]["inputs"]["config_json"]=json.dumps(request)
 
7
  from urllib.request import Request,urlopen
8
  from uuid import uuid4
9
 
10
+ try:
11
+ from .migrate_workflow import PROMPTER_ID, migrate_workflow, read_json
12
+ except ImportError:
13
+ from migrate_workflow import PROMPTER_ID, migrate_workflow, read_json
14
+
15
 
16
  def call(base,path,data=None):
17
  body=None if data is None else json.dumps(data).encode()
 
34
  parser.add_argument("--wait",action="store_true")
35
  parser.add_argument("--timeout",type=int,default=900)
36
  args=parser.parse_args()
37
+ workflow, migration=migrate_workflow(read_json(args.workflow.read_text(encoding="utf-8-sig")))
38
+ if migration["migrated"]:
39
+ print(f"Migrated {len(migration['migrated'])} old DMAI NODES Prompter ID(s) in memory; the file is unchanged.",flush=True)
40
+ if not isinstance(workflow.get("1"),dict) or workflow["1"].get("class_type") != PROMPTER_ID:
41
+ parser.error("This runner requires a DMAI NODES starter API workflow with DMAINodesPrompter at node 1. Legacy Suite or ambiguous Prompters are not converted. Export or use an updated starter API workflow.")
42
  request=json.loads(workflow["1"]["inputs"]["config_json"])
43
  request.update(prompt=args.prompt,count=args.count)
44
  workflow["1"]["inputs"]["config_json"]=json.dumps(request)
web/dmai-nodes.js CHANGED
@@ -5,10 +5,11 @@ 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
 
9
  const controllers = new WeakMap();
10
  const types = {
11
- DMAIPrompter: Prompter,
12
  DMAILoRAStack: LoRAStack,
13
  DMAIGenerationEngine: Engine,
14
  DMAIGallery: Gallery,
@@ -84,6 +85,7 @@ app.registerExtension({
84
  },
85
  nodeCreated: install,
86
  loadedGraphNode: install,
 
87
  afterConfigureGraph: refreshGraph,
88
  // Every visible socket is the real Comfy socket. No duplicate connection UI.
89
  });
 
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 = {
12
+ [PROMPTER_TYPE]: Prompter,
13
  DMAILoRAStack: LoRAStack,
14
  DMAIGenerationEngine: Engine,
15
  DMAIGallery: Gallery,
 
85
  },
86
  nodeCreated: install,
87
  loadedGraphNode: install,
88
+ beforeConfigureGraph: migratePreviewPrompters,
89
  afterConfigureGraph: refreshGraph,
90
  // Every visible socket is the real Comfy socket. No duplicate connection UI.
91
  });
web/inputs.mjs CHANGED
@@ -351,7 +351,7 @@ export class LoRAStack extends Controller {
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-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");
@@ -390,7 +390,7 @@ export class LoRAStack extends Controller {
390
  dragged = null;
391
  });
392
  const copy = el("div", "dmai-lora-copy"),
393
- title = el("span", "dmai-lora-name", entry.name.split(/[\\/]/).pop());
394
  title.title = entry.name;
395
  copy.append(
396
  title,
 
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");
 
390
  dragged = null;
391
  });
392
  const copy = el("div", "dmai-lora-copy"),
393
+ title = el("span", "dmai-nodes-lora-name", entry.name.split(/[\\/]/).pop());
394
  title.title = entry.name;
395
  copy.append(
396
  title,
web/nodes.css CHANGED
@@ -361,7 +361,7 @@
361
  gap: 3px;
362
  margin-bottom: 8px;
363
  }
364
- .dmai-nodes .dmai-lora-row {
365
  display: grid;
366
  grid-template-columns: 14px minmax(70px, 1fr) 46px 25px 18px 25px;
367
  align-items: center;
@@ -369,13 +369,13 @@
369
  min-height: 65px;
370
  border-bottom: 1px solid var(--dmai-line);
371
  }
372
- .dmai-nodes .dmai-lora-row:last-child {
373
  border: 0;
374
  }
375
  .dmai-nodes .dmai-lora-copy {
376
  overflow: hidden;
377
  }
378
- .dmai-nodes .dmai-lora-name {
379
  display: block;
380
  white-space: nowrap;
381
  text-overflow: ellipsis;
@@ -393,10 +393,10 @@
393
  padding: 7px 3px;
394
  text-align: center;
395
  }
396
- .dmai-nodes .dmai-lora-row > .dmai-icon {
397
  width: 25px;
398
  }
399
- .dmai-nodes .dmai-lora-row > .dmai-grip {
400
  width: 14px;
401
  color: #7d8c81;
402
  cursor: grab;
 
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;
 
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;
 
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;
web/progress.mjs CHANGED
@@ -1,3 +1,5 @@
 
 
1
  // Job identity comes from the submitted API graph, never the current selection.
2
  const IDLE = Object.freeze({ phase: "idle", percent: 0 });
3
  const ACTIVE = new Set(["queued", "loading", "sampling", "decoding", "saving"]);
@@ -35,7 +37,7 @@ function connections(entry) {
35
  const engine = prompt[engineId], link = engine.inputs?.request;
36
  if (engine.class_type !== "DMAIGenerationEngine" || !Array.isArray(link)) continue;
37
  const sourceId = String(link[0]);
38
- if (prompt[sourceId]?.class_type !== "DMAIPrompter") continue;
39
  if (!sources.has(sourceId)) sources.set(sourceId, new Set());
40
  sources.get(sourceId).add(engineId);
41
  }
 
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 });
5
  const ACTIVE = new Set(["queued", "loading", "sampling", "decoding", "saving"]);
 
37
  const engine = prompt[engineId], link = engine.inputs?.request;
38
  if (engine.class_type !== "DMAIGenerationEngine" || !Array.isArray(link)) continue;
39
  const sourceId = String(link[0]);
40
+ if (prompt[sourceId]?.class_type !== PROMPTER_TYPE) continue;
41
  if (!sources.has(sourceId)) sources.set(sourceId, new Set());
42
  sources.get(sourceId).add(engineId);
43
  }
web/workflow-migration.mjs ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { readPrompt } from "./core.mjs";
2
+
3
+ export const PROMPTER_TYPE = "DMAINodesPrompter";
4
+ const PREVIEW_PROMPTER_TYPE = "DMAIPrompter";
5
+ const CONFIG_KEYS = ["schema_version", "prompt", "negative_prompt", "width", "height", "count"];
6
+ const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7
+
8
+ function promptConfig(raw) {
9
+ if (typeof raw !== "string" || new TextEncoder().encode(raw).byteLength > 128 * 1024) return null;
10
+ try {
11
+ const value = readPrompt(raw);
12
+ if (
13
+ Object.keys(value).length !== CONFIG_KEYS.length ||
14
+ !CONFIG_KEYS.every((key) => Object.hasOwn(value, key)) ||
15
+ value.width * value.height > 16777216 ||
16
+ [value.prompt, value.negative_prompt].some((text) => [...text].length > 65536 || text.includes("\0"))
17
+ ) return null;
18
+ return value;
19
+ } catch { return null; }
20
+ }
21
+
22
+ // The old Suite also owns DMAIPrompter. Its CLIP/conditioning node must never
23
+ // be aliased or rewritten: only the complete 0.1.x request signature is ours.
24
+ export function isPreviewPrompter(node) {
25
+ if (!isObject(node) || node.type !== PREVIEW_PROMPTER_TYPE) return false;
26
+ if (!Array.isArray(node.outputs) || node.outputs.length !== 1) return false;
27
+ const output = node.outputs[0];
28
+ if (
29
+ output?.name !== "request" || output.type !== "DMAI_PROMPT" ||
30
+ (output.slot_index !== undefined && output.slot_index !== 0)
31
+ ) return false;
32
+ const inputs = node.inputs ?? [];
33
+ if (!Array.isArray(inputs) || inputs.some((input) =>
34
+ !["text", "config_json"].includes(input?.name) || input.type !== "STRING"
35
+ ) || new Set(inputs.map((input) => input.name)).size !== inputs.length) return false;
36
+
37
+ const candidates = [];
38
+ if (node.widgets_values !== undefined) {
39
+ if (!Array.isArray(node.widgets_values) || node.widgets_values.length !== 1) return false;
40
+ candidates.push(node.widgets_values[0]);
41
+ }
42
+ if (node.widgets_values_named !== undefined) {
43
+ const named = node.widgets_values_named;
44
+ if (!isObject(named) || Object.keys(named).length !== 1 || !Object.hasOwn(named, "config_json")) return false;
45
+ candidates.push(named.config_json);
46
+ }
47
+ if (!candidates.length) return false;
48
+ const configs = candidates.map(promptConfig);
49
+ if (configs.some((config) => !config)) return false;
50
+ return configs.every((config) => config && CONFIG_KEYS.every((key) => config[key] === configs[0][key]));
51
+ }
52
+
53
+ // ComfyUI's serialized subgraphs live in definitions.subgraphs, including
54
+ // nested definitions. Runtime node.subgraph and arbitrary metadata are not
55
+ // serialized node collections and must not be rewritten.
56
+ export function migratePreviewPrompters(workflow) {
57
+ const pending = [workflow], visited = new Set();
58
+ let migrated = 0;
59
+ while (pending.length) {
60
+ const graph = pending.pop();
61
+ if (!isObject(graph) || visited.has(graph)) continue;
62
+ visited.add(graph);
63
+ if (Array.isArray(graph.nodes)) {
64
+ for (const node of graph.nodes) {
65
+ if (!isPreviewPrompter(node)) continue;
66
+ node.type = PROMPTER_TYPE;
67
+ if (node.properties?.["Node name for S&R"] === PREVIEW_PROMPTER_TYPE)
68
+ node.properties["Node name for S&R"] = PROMPTER_TYPE;
69
+ migrated++;
70
+ }
71
+ }
72
+ const subgraphs = graph.definitions?.subgraphs;
73
+ if (Array.isArray(subgraphs)) pending.push(...subgraphs);
74
+ }
75
+ return migrated;
76
+ }
workflows/DMAI-NODES-FLUX.api.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "1": {
3
- "class_type": "DMAIPrompter",
4
  "inputs": {
5
  "config_json": "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
6
  }
 
1
  {
2
  "1": {
3
+ "class_type": "DMAINodesPrompter",
4
  "inputs": {
5
  "config_json": "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
6
  }
workflows/DMAI-NODES-FLUX.json CHANGED
@@ -4,7 +4,7 @@
4
  "nodes": [
5
  {
6
  "id": 1,
7
- "type": "DMAIPrompter",
8
  "pos": [
9
  40,
10
  100
@@ -34,7 +34,7 @@
34
  }
35
  ],
36
  "properties": {
37
- "Node name for S&R": "DMAIPrompter"
38
  },
39
  "widgets_values": [
40
  "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
 
4
  "nodes": [
5
  {
6
  "id": 1,
7
+ "type": "DMAINodesPrompter",
8
  "pos": [
9
  40,
10
  100
 
34
  }
35
  ],
36
  "properties": {
37
+ "Node name for S&R": "DMAINodesPrompter"
38
  },
39
  "widgets_values": [
40
  "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
workflows/DMAI-NODES-Krea.api.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "1": {
3
- "class_type": "DMAIPrompter",
4
  "inputs": {
5
  "config_json": "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
6
  }
 
1
  {
2
  "1": {
3
+ "class_type": "DMAINodesPrompter",
4
  "inputs": {
5
  "config_json": "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
6
  }
workflows/DMAI-NODES-Krea.json CHANGED
@@ -4,7 +4,7 @@
4
  "nodes": [
5
  {
6
  "id": 1,
7
- "type": "DMAIPrompter",
8
  "pos": [
9
  40,
10
  100
@@ -34,7 +34,7 @@
34
  }
35
  ],
36
  "properties": {
37
- "Node name for S&R": "DMAIPrompter"
38
  },
39
  "widgets_values": [
40
  "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
 
4
  "nodes": [
5
  {
6
  "id": 1,
7
+ "type": "DMAINodesPrompter",
8
  "pos": [
9
  40,
10
  100
 
34
  }
35
  ],
36
  "properties": {
37
+ "Node name for S&R": "DMAINodesPrompter"
38
  },
39
  "widgets_values": [
40
  "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
workflows/DMAI-NODES-Qwen.api.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "1": {
3
- "class_type": "DMAIPrompter",
4
  "inputs": {
5
  "config_json": "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
6
  }
 
1
  {
2
  "1": {
3
+ "class_type": "DMAINodesPrompter",
4
  "inputs": {
5
  "config_json": "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
6
  }
workflows/DMAI-NODES-Qwen.json CHANGED
@@ -4,7 +4,7 @@
4
  "nodes": [
5
  {
6
  "id": 1,
7
- "type": "DMAIPrompter",
8
  "pos": [
9
  40,
10
  100
@@ -34,7 +34,7 @@
34
  }
35
  ],
36
  "properties": {
37
- "Node name for S&R": "DMAIPrompter"
38
  },
39
  "widgets_values": [
40
  "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
 
4
  "nodes": [
5
  {
6
  "id": 1,
7
+ "type": "DMAINodesPrompter",
8
  "pos": [
9
  40,
10
  100
 
34
  }
35
  ],
36
  "properties": {
37
+ "Node name for S&R": "DMAINodesPrompter"
38
  },
39
  "widgets_values": [
40
  "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
workflows/DMAI-NODES-SDXL.api.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "1": {
3
- "class_type": "DMAIPrompter",
4
  "inputs": {
5
  "config_json": "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
6
  }
 
1
  {
2
  "1": {
3
+ "class_type": "DMAINodesPrompter",
4
  "inputs": {
5
  "config_json": "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
6
  }
workflows/DMAI-NODES-SDXL.json CHANGED
@@ -4,7 +4,7 @@
4
  "nodes": [
5
  {
6
  "id": 1,
7
- "type": "DMAIPrompter",
8
  "pos": [
9
  40,
10
  100
@@ -34,7 +34,7 @@
34
  }
35
  ],
36
  "properties": {
37
- "Node name for S&R": "DMAIPrompter"
38
  },
39
  "widgets_values": [
40
  "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"
 
4
  "nodes": [
5
  {
6
  "id": 1,
7
+ "type": "DMAINodesPrompter",
8
  "pos": [
9
  40,
10
  100
 
34
  }
35
  ],
36
  "properties": {
37
+ "Node name for S&R": "DMAINodesPrompter"
38
  },
39
  "widgets_values": [
40
  "{\"schema_version\": 1, \"prompt\": \"A sculptural object made of dark stone and translucent green glass, soft studio side light.\", \"negative_prompt\": \"\", \"width\": 832, \"height\": 1024, \"count\": 1}"