InvokeAI 6.9.0rc3__py3-none-any.whl → 6.10.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. invokeai/app/api/dependencies.py +2 -0
  2. invokeai/app/api/routers/model_manager.py +91 -2
  3. invokeai/app/api/routers/workflows.py +9 -0
  4. invokeai/app/invocations/fields.py +19 -0
  5. invokeai/app/invocations/image_to_latents.py +23 -5
  6. invokeai/app/invocations/latents_to_image.py +2 -25
  7. invokeai/app/invocations/metadata.py +9 -1
  8. invokeai/app/invocations/model.py +8 -0
  9. invokeai/app/invocations/primitives.py +12 -0
  10. invokeai/app/invocations/prompt_template.py +57 -0
  11. invokeai/app/invocations/z_image_control.py +112 -0
  12. invokeai/app/invocations/z_image_denoise.py +610 -0
  13. invokeai/app/invocations/z_image_image_to_latents.py +102 -0
  14. invokeai/app/invocations/z_image_latents_to_image.py +103 -0
  15. invokeai/app/invocations/z_image_lora_loader.py +153 -0
  16. invokeai/app/invocations/z_image_model_loader.py +135 -0
  17. invokeai/app/invocations/z_image_text_encoder.py +197 -0
  18. invokeai/app/services/model_install/model_install_common.py +14 -1
  19. invokeai/app/services/model_install/model_install_default.py +119 -19
  20. invokeai/app/services/model_records/model_records_base.py +12 -0
  21. invokeai/app/services/model_records/model_records_sql.py +17 -0
  22. invokeai/app/services/shared/graph.py +132 -77
  23. invokeai/app/services/workflow_records/workflow_records_base.py +8 -0
  24. invokeai/app/services/workflow_records/workflow_records_sqlite.py +42 -0
  25. invokeai/app/util/step_callback.py +3 -0
  26. invokeai/backend/model_manager/configs/controlnet.py +47 -1
  27. invokeai/backend/model_manager/configs/factory.py +26 -1
  28. invokeai/backend/model_manager/configs/lora.py +43 -1
  29. invokeai/backend/model_manager/configs/main.py +113 -0
  30. invokeai/backend/model_manager/configs/qwen3_encoder.py +156 -0
  31. invokeai/backend/model_manager/load/model_cache/torch_module_autocast/custom_modules/custom_diffusers_rms_norm.py +40 -0
  32. invokeai/backend/model_manager/load/model_cache/torch_module_autocast/custom_modules/custom_layer_norm.py +25 -0
  33. invokeai/backend/model_manager/load/model_cache/torch_module_autocast/torch_module_autocast.py +11 -2
  34. invokeai/backend/model_manager/load/model_loaders/lora.py +11 -0
  35. invokeai/backend/model_manager/load/model_loaders/z_image.py +935 -0
  36. invokeai/backend/model_manager/load/model_util.py +6 -1
  37. invokeai/backend/model_manager/metadata/metadata_base.py +12 -5
  38. invokeai/backend/model_manager/model_on_disk.py +3 -0
  39. invokeai/backend/model_manager/starter_models.py +70 -0
  40. invokeai/backend/model_manager/taxonomy.py +5 -0
  41. invokeai/backend/model_manager/util/select_hf_files.py +23 -8
  42. invokeai/backend/patches/layer_patcher.py +34 -16
  43. invokeai/backend/patches/layers/lora_layer_base.py +2 -1
  44. invokeai/backend/patches/lora_conversions/flux_aitoolkit_lora_conversion_utils.py +17 -2
  45. invokeai/backend/patches/lora_conversions/flux_xlabs_lora_conversion_utils.py +92 -0
  46. invokeai/backend/patches/lora_conversions/formats.py +5 -0
  47. invokeai/backend/patches/lora_conversions/z_image_lora_constants.py +8 -0
  48. invokeai/backend/patches/lora_conversions/z_image_lora_conversion_utils.py +155 -0
  49. invokeai/backend/quantization/gguf/ggml_tensor.py +27 -4
  50. invokeai/backend/quantization/gguf/loaders.py +47 -12
  51. invokeai/backend/stable_diffusion/diffusion/conditioning_data.py +13 -0
  52. invokeai/backend/util/devices.py +25 -0
  53. invokeai/backend/util/hotfixes.py +2 -2
  54. invokeai/backend/z_image/__init__.py +16 -0
  55. invokeai/backend/z_image/extensions/__init__.py +1 -0
  56. invokeai/backend/z_image/extensions/regional_prompting_extension.py +207 -0
  57. invokeai/backend/z_image/text_conditioning.py +74 -0
  58. invokeai/backend/z_image/z_image_control_adapter.py +238 -0
  59. invokeai/backend/z_image/z_image_control_transformer.py +643 -0
  60. invokeai/backend/z_image/z_image_controlnet_extension.py +531 -0
  61. invokeai/backend/z_image/z_image_patchify_utils.py +135 -0
  62. invokeai/backend/z_image/z_image_transformer_patch.py +234 -0
  63. invokeai/frontend/web/dist/assets/App-CYhlZO3Q.js +161 -0
  64. invokeai/frontend/web/dist/assets/{browser-ponyfill-CN1j0ARZ.js → browser-ponyfill-DHZxq1nk.js} +1 -1
  65. invokeai/frontend/web/dist/assets/index-dgSJAY--.js +530 -0
  66. invokeai/frontend/web/dist/index.html +1 -1
  67. invokeai/frontend/web/dist/locales/de.json +24 -6
  68. invokeai/frontend/web/dist/locales/en.json +70 -1
  69. invokeai/frontend/web/dist/locales/es.json +0 -5
  70. invokeai/frontend/web/dist/locales/fr.json +0 -6
  71. invokeai/frontend/web/dist/locales/it.json +17 -64
  72. invokeai/frontend/web/dist/locales/ja.json +379 -44
  73. invokeai/frontend/web/dist/locales/ru.json +0 -6
  74. invokeai/frontend/web/dist/locales/vi.json +7 -54
  75. invokeai/frontend/web/dist/locales/zh-CN.json +0 -6
  76. invokeai/version/invokeai_version.py +1 -1
  77. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0rc1.dist-info}/METADATA +3 -3
  78. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0rc1.dist-info}/RECORD +84 -60
  79. invokeai/frontend/web/dist/assets/App-Cn9UyjoV.js +0 -161
  80. invokeai/frontend/web/dist/assets/index-BDrf9CL-.js +0 -530
  81. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0rc1.dist-info}/WHEEL +0 -0
  82. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0rc1.dist-info}/entry_points.txt +0 -0
  83. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0rc1.dist-info}/licenses/LICENSE +0 -0
  84. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0rc1.dist-info}/licenses/LICENSE-SD1+SD2.txt +0 -0
  85. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0rc1.dist-info}/licenses/LICENSE-SDXL.txt +0 -0
  86. {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0rc1.dist-info}/top_level.txt +0 -0
@@ -49,6 +49,7 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
49
49
  FLUXConditioningInfo,
50
50
  SD3ConditioningInfo,
51
51
  SDXLConditioningInfo,
52
+ ZImageConditioningInfo,
52
53
  )
53
54
  from invokeai.backend.util.logging import InvokeAILogger
54
55
  from invokeai.version.invokeai_version import __version__
@@ -129,6 +130,7 @@ class ApiDependencies:
129
130
  FLUXConditioningInfo,
130
131
  SD3ConditioningInfo,
131
132
  CogView4ConditioningInfo,
133
+ ZImageConditioningInfo,
132
134
  ],
133
135
  ephemeral=True,
134
136
  ),
@@ -28,7 +28,7 @@ from invokeai.app.services.model_records import (
28
28
  UnknownModelException,
29
29
  )
30
30
  from invokeai.app.util.suppress_output import SuppressOutput
31
- from invokeai.backend.model_manager.configs.factory import AnyModelConfig
31
+ from invokeai.backend.model_manager.configs.factory import AnyModelConfig, ModelConfigFactory
32
32
  from invokeai.backend.model_manager.configs.main import (
33
33
  Main_Checkpoint_SD1_Config,
34
34
  Main_Checkpoint_SD2_Config,
@@ -38,6 +38,7 @@ from invokeai.backend.model_manager.configs.main import (
38
38
  from invokeai.backend.model_manager.load.model_cache.cache_stats import CacheStats
39
39
  from invokeai.backend.model_manager.metadata.fetch.huggingface import HuggingFaceMetadataFetch
40
40
  from invokeai.backend.model_manager.metadata.metadata_base import ModelMetadataWithFiles, UnknownMetadataException
41
+ from invokeai.backend.model_manager.model_on_disk import ModelOnDisk
41
42
  from invokeai.backend.model_manager.search import ModelSearch
42
43
  from invokeai.backend.model_manager.starter_models import (
43
44
  STARTER_BUNDLES,
@@ -191,6 +192,40 @@ async def get_model_record(
191
192
  raise HTTPException(status_code=404, detail=str(e))
192
193
 
193
194
 
195
+ @model_manager_router.post(
196
+ "/i/{key}/reidentify",
197
+ operation_id="reidentify_model",
198
+ responses={
199
+ 200: {
200
+ "description": "The model configuration was retrieved successfully",
201
+ "content": {"application/json": {"example": example_model_config}},
202
+ },
203
+ 400: {"description": "Bad request"},
204
+ 404: {"description": "The model could not be found"},
205
+ },
206
+ )
207
+ async def reidentify_model(
208
+ key: Annotated[str, Path(description="Key of the model to reidentify.")],
209
+ ) -> AnyModelConfig:
210
+ """Attempt to reidentify a model by re-probing its weights file."""
211
+ try:
212
+ config = ApiDependencies.invoker.services.model_manager.store.get_model(key)
213
+ models_path = ApiDependencies.invoker.services.configuration.models_path
214
+ if pathlib.Path(config.path).is_relative_to(models_path):
215
+ model_path = pathlib.Path(config.path)
216
+ else:
217
+ model_path = models_path / config.path
218
+ mod = ModelOnDisk(model_path)
219
+ result = ModelConfigFactory.from_model_on_disk(mod)
220
+ if result.config is None:
221
+ raise InvalidModelException("Unable to identify model format")
222
+ result.config.key = config.key # retain the same key
223
+ new_config = ApiDependencies.invoker.services.model_manager.store.replace_model(config.key, result.config)
224
+ return new_config
225
+ except UnknownModelException as e:
226
+ raise HTTPException(status_code=404, detail=str(e))
227
+
228
+
194
229
  class FoundModel(BaseModel):
195
230
  path: str = Field(description="Path to the model")
196
231
  is_installed: bool = Field(description="Whether or not the model is already installed")
@@ -238,9 +273,10 @@ async def scan_for_models(
238
273
  found_model = FoundModel(path=path, is_installed=is_installed)
239
274
  scan_results.append(found_model)
240
275
  except Exception as e:
276
+ error_type = type(e).__name__
241
277
  raise HTTPException(
242
278
  status_code=500,
243
- detail=f"An error occurred while searching the directory: {e}",
279
+ detail=f"An error occurred while searching the directory: {error_type}",
244
280
  )
245
281
  return scan_results
246
282
 
@@ -411,6 +447,59 @@ async def delete_model(
411
447
  raise HTTPException(status_code=404, detail=str(e))
412
448
 
413
449
 
450
+ class BulkDeleteModelsRequest(BaseModel):
451
+ """Request body for bulk model deletion."""
452
+
453
+ keys: List[str] = Field(description="List of model keys to delete")
454
+
455
+
456
+ class BulkDeleteModelsResponse(BaseModel):
457
+ """Response body for bulk model deletion."""
458
+
459
+ deleted: List[str] = Field(description="List of successfully deleted model keys")
460
+ failed: List[dict] = Field(description="List of failed deletions with error messages")
461
+
462
+
463
+ @model_manager_router.post(
464
+ "/i/bulk_delete",
465
+ operation_id="bulk_delete_models",
466
+ responses={
467
+ 200: {"description": "Models deleted (possibly with some failures)"},
468
+ },
469
+ status_code=200,
470
+ )
471
+ async def bulk_delete_models(
472
+ request: BulkDeleteModelsRequest = Body(description="List of model keys to delete"),
473
+ ) -> BulkDeleteModelsResponse:
474
+ """
475
+ Delete multiple model records from database.
476
+
477
+ The configuration records will be removed. The corresponding weights files will be
478
+ deleted as well if they reside within the InvokeAI "models" directory.
479
+ Returns a list of successfully deleted keys and failed deletions with error messages.
480
+ """
481
+ logger = ApiDependencies.invoker.services.logger
482
+ installer = ApiDependencies.invoker.services.model_manager.install
483
+
484
+ deleted = []
485
+ failed = []
486
+
487
+ for key in request.keys:
488
+ try:
489
+ installer.delete(key)
490
+ deleted.append(key)
491
+ logger.info(f"Deleted model: {key}")
492
+ except UnknownModelException as e:
493
+ logger.error(f"Failed to delete model {key}: {str(e)}")
494
+ failed.append({"key": key, "error": str(e)})
495
+ except Exception as e:
496
+ logger.error(f"Failed to delete model {key}: {str(e)}")
497
+ failed.append({"key": key, "error": str(e)})
498
+
499
+ logger.info(f"Bulk delete completed: {len(deleted)} deleted, {len(failed)} failed")
500
+ return BulkDeleteModelsResponse(deleted=deleted, failed=failed)
501
+
502
+
414
503
  @model_manager_router.delete(
415
504
  "/i/{key}/image",
416
505
  operation_id="delete_model_image",
@@ -223,6 +223,15 @@ async def get_workflow_thumbnail(
223
223
  raise HTTPException(status_code=404)
224
224
 
225
225
 
226
+ @workflows_router.get("/tags", operation_id="get_all_tags")
227
+ async def get_all_tags(
228
+ categories: Optional[list[WorkflowCategory]] = Query(default=None, description="The categories to include"),
229
+ ) -> list[str]:
230
+ """Gets all unique tags from workflows"""
231
+
232
+ return ApiDependencies.invoker.services.workflow_records.get_all_tags(categories=categories)
233
+
234
+
226
235
  @workflows_router.get("/counts_by_tag", operation_id="get_counts_by_tag")
227
236
  async def get_counts_by_tag(
228
237
  tags: list[str] = Query(description="The tags to get counts for"),
@@ -154,6 +154,7 @@ class FieldDescriptions:
154
154
  clip = "CLIP (tokenizer, text encoder, LoRAs) and skipped layer count"
155
155
  t5_encoder = "T5 tokenizer and text encoder"
156
156
  glm_encoder = "GLM (THUDM) tokenizer and text encoder"
157
+ qwen3_encoder = "Qwen3 tokenizer and text encoder"
157
158
  clip_embed_model = "CLIP Embed loader"
158
159
  clip_g_model = "CLIP-G Embed loader"
159
160
  unet = "UNet (scheduler, LoRAs)"
@@ -169,6 +170,7 @@ class FieldDescriptions:
169
170
  flux_model = "Flux model (Transformer) to load"
170
171
  sd3_model = "SD3 model (MMDiTX) to load"
171
172
  cogview4_model = "CogView4 model (Transformer) to load"
173
+ z_image_model = "Z-Image model (Transformer) to load"
172
174
  sdxl_main_model = "SDXL Main model (UNet, VAE, CLIP1, CLIP2) to load"
173
175
  sdxl_refiner_model = "SDXL Refiner Main Modde (UNet, VAE, CLIP2) to load"
174
176
  onnx_main_model = "ONNX Main model (UNet, VAE, CLIP) to load"
@@ -241,6 +243,12 @@ class BoardField(BaseModel):
241
243
  board_id: str = Field(description="The id of the board")
242
244
 
243
245
 
246
+ class StylePresetField(BaseModel):
247
+ """A style preset primitive field"""
248
+
249
+ style_preset_id: str = Field(description="The id of the style preset")
250
+
251
+
244
252
  class DenoiseMaskField(BaseModel):
245
253
  """An inpaint mask field"""
246
254
 
@@ -321,6 +329,17 @@ class CogView4ConditioningField(BaseModel):
321
329
  conditioning_name: str = Field(description="The name of conditioning tensor")
322
330
 
323
331
 
332
+ class ZImageConditioningField(BaseModel):
333
+ """A Z-Image conditioning tensor primitive value"""
334
+
335
+ conditioning_name: str = Field(description="The name of conditioning tensor")
336
+ mask: Optional[TensorField] = Field(
337
+ default=None,
338
+ description="The mask associated with this conditioning tensor for regional prompting. "
339
+ "Excluded regions should be set to False, included regions should be set to True.",
340
+ )
341
+
342
+
324
343
  class ConditioningField(BaseModel):
325
344
  """A conditioning tensor primitive value"""
326
345
 
@@ -1,5 +1,6 @@
1
1
  from contextlib import nullcontext
2
2
  from functools import singledispatchmethod
3
+ from typing import Literal
3
4
 
4
5
  import einops
5
6
  import torch
@@ -20,7 +21,7 @@ from invokeai.app.invocations.fields import (
20
21
  Input,
21
22
  InputField,
22
23
  )
23
- from invokeai.app.invocations.model import VAEField
24
+ from invokeai.app.invocations.model import BaseModelType, VAEField
24
25
  from invokeai.app.invocations.primitives import LatentsOutput
25
26
  from invokeai.app.services.shared.invocation_context import InvocationContext
26
27
  from invokeai.backend.model_manager.load.load_base import LoadedModel
@@ -29,13 +30,21 @@ from invokeai.backend.stable_diffusion.vae_tiling import patch_vae_tiling_params
29
30
  from invokeai.backend.util.devices import TorchDevice
30
31
  from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_sd15_sdxl
31
32
 
33
+ """
34
+ SDXL VAE color compensation values determined experimentally to reduce color drift.
35
+ If more reliable values are found in the future (e.g. individual color channels), they can be updated.
36
+ SD1.5, TAESD, TAESDXL VAEs distort in less predictable ways, so no compensation is offered at this time.
37
+ """
38
+ COMPENSATION_OPTIONS = Literal["None", "SDXL"]
39
+ COLOR_COMPENSATION_MAP = {"None": [1, 0], "SDXL": [1.015, -0.002]}
40
+
32
41
 
33
42
  @invocation(
34
43
  "i2l",
35
44
  title="Image to Latents - SD1.5, SDXL",
36
45
  tags=["latents", "image", "vae", "i2l"],
37
46
  category="latents",
38
- version="1.1.1",
47
+ version="1.2.0",
39
48
  )
40
49
  class ImageToLatentsInvocation(BaseInvocation):
41
50
  """Encodes an image into latents."""
@@ -52,6 +61,10 @@ class ImageToLatentsInvocation(BaseInvocation):
52
61
  # offer a way to directly set None values.
53
62
  tile_size: int = InputField(default=0, multiple_of=8, description=FieldDescriptions.vae_tile_size)
54
63
  fp32: bool = InputField(default=False, description=FieldDescriptions.fp32)
64
+ color_compensation: COMPENSATION_OPTIONS = InputField(
65
+ default="None",
66
+ description="Apply VAE scaling compensation when encoding images (reduces color drift).",
67
+ )
55
68
 
56
69
  @classmethod
57
70
  def vae_encode(
@@ -62,7 +75,7 @@ class ImageToLatentsInvocation(BaseInvocation):
62
75
  image_tensor: torch.Tensor,
63
76
  tile_size: int = 0,
64
77
  ) -> torch.Tensor:
65
- assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny))
78
+ assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
66
79
  estimated_working_memory = estimate_vae_working_memory_sd15_sdxl(
67
80
  operation="encode",
68
81
  image_tensor=image_tensor,
@@ -71,7 +84,7 @@ class ImageToLatentsInvocation(BaseInvocation):
71
84
  fp32=upcast,
72
85
  )
73
86
  with vae_info.model_on_device(working_mem_bytes=estimated_working_memory) as (_, vae):
74
- assert isinstance(vae, (AutoencoderKL, AutoencoderTiny))
87
+ assert isinstance(vae, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
75
88
  orig_dtype = vae.dtype
76
89
  if upcast:
77
90
  vae.to(dtype=torch.float32)
@@ -127,9 +140,14 @@ class ImageToLatentsInvocation(BaseInvocation):
127
140
  image = context.images.get_pil(self.image.image_name)
128
141
 
129
142
  vae_info = context.models.load(self.vae.vae)
130
- assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny))
143
+ assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
131
144
 
132
145
  image_tensor = image_resized_to_grid_as_tensor(image.convert("RGB"))
146
+
147
+ if self.color_compensation != "None" and vae_info.config.base == BaseModelType.StableDiffusionXL:
148
+ scale, bias = COLOR_COMPENSATION_MAP[self.color_compensation]
149
+ image_tensor = image_tensor * scale + bias
150
+
133
151
  if image_tensor.dim() == 3:
134
152
  image_tensor = einops.rearrange(image_tensor, "c h w -> 1 c h w")
135
153
 
@@ -2,12 +2,6 @@ from contextlib import nullcontext
2
2
 
3
3
  import torch
4
4
  from diffusers.image_processor import VaeImageProcessor
5
- from diffusers.models.attention_processor import (
6
- AttnProcessor2_0,
7
- LoRAAttnProcessor2_0,
8
- LoRAXFormersAttnProcessor,
9
- XFormersAttnProcessor,
10
- )
11
5
  from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL
12
6
  from diffusers.models.autoencoders.autoencoder_tiny import AutoencoderTiny
13
7
 
@@ -77,26 +71,9 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
77
71
  assert isinstance(vae, (AutoencoderKL, AutoencoderTiny))
78
72
  latents = latents.to(TorchDevice.choose_torch_device())
79
73
  if self.fp32:
74
+ # FP32 mode: convert everything to float32 for maximum precision
80
75
  vae.to(dtype=torch.float32)
81
-
82
- use_torch_2_0_or_xformers = hasattr(vae.decoder, "mid_block") and isinstance(
83
- vae.decoder.mid_block.attentions[0].processor,
84
- (
85
- AttnProcessor2_0,
86
- XFormersAttnProcessor,
87
- LoRAXFormersAttnProcessor,
88
- LoRAAttnProcessor2_0,
89
- ),
90
- )
91
- # if xformers or torch_2_0 is used attention block does not need
92
- # to be in float32 which can save lots of memory
93
- if use_torch_2_0_or_xformers:
94
- vae.post_quant_conv.to(latents.dtype)
95
- vae.decoder.conv_in.to(latents.dtype)
96
- vae.decoder.mid_block.to(latents.dtype)
97
- else:
98
- latents = latents.float()
99
-
76
+ latents = latents.float()
100
77
  else:
101
78
  vae.to(dtype=torch.float16)
102
79
  latents = latents.half()
@@ -158,6 +158,10 @@ GENERATION_MODES = Literal[
158
158
  "cogview4_img2img",
159
159
  "cogview4_inpaint",
160
160
  "cogview4_outpaint",
161
+ "z_image_txt2img",
162
+ "z_image_img2img",
163
+ "z_image_inpaint",
164
+ "z_image_outpaint",
161
165
  ]
162
166
 
163
167
 
@@ -166,7 +170,7 @@ GENERATION_MODES = Literal[
166
170
  title="Core Metadata",
167
171
  tags=["metadata"],
168
172
  category="metadata",
169
- version="2.0.0",
173
+ version="2.1.0",
170
174
  classification=Classification.Internal,
171
175
  )
172
176
  class CoreMetadataInvocation(BaseInvocation):
@@ -217,6 +221,10 @@ class CoreMetadataInvocation(BaseInvocation):
217
221
  default=None,
218
222
  description="The VAE used for decoding, if the main model's default was not used",
219
223
  )
224
+ qwen3_encoder: Optional[ModelIdentifierField] = InputField(
225
+ default=None,
226
+ description="The Qwen3 text encoder model used for Z-Image inference",
227
+ )
220
228
 
221
229
  # High resolution fix metadata.
222
230
  hrf_enabled: Optional[bool] = InputField(
@@ -72,6 +72,14 @@ class GlmEncoderField(BaseModel):
72
72
  text_encoder: ModelIdentifierField = Field(description="Info to load text_encoder submodel")
73
73
 
74
74
 
75
+ class Qwen3EncoderField(BaseModel):
76
+ """Field for Qwen3 text encoder used by Z-Image models."""
77
+
78
+ tokenizer: ModelIdentifierField = Field(description="Info to load tokenizer submodel")
79
+ text_encoder: ModelIdentifierField = Field(description="Info to load text_encoder submodel")
80
+ loras: List[LoRAField] = Field(default_factory=list, description="LoRAs to apply on model loading")
81
+
82
+
75
83
  class VAEField(BaseModel):
76
84
  vae: ModelIdentifierField = Field(description="Info to load vae submodel")
77
85
  seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
@@ -27,6 +27,7 @@ from invokeai.app.invocations.fields import (
27
27
  SD3ConditioningField,
28
28
  TensorField,
29
29
  UIComponent,
30
+ ZImageConditioningField,
30
31
  )
31
32
  from invokeai.app.services.images.images_common import ImageDTO
32
33
  from invokeai.app.services.shared.invocation_context import InvocationContext
@@ -461,6 +462,17 @@ class CogView4ConditioningOutput(BaseInvocationOutput):
461
462
  return cls(conditioning=CogView4ConditioningField(conditioning_name=conditioning_name))
462
463
 
463
464
 
465
+ @invocation_output("z_image_conditioning_output")
466
+ class ZImageConditioningOutput(BaseInvocationOutput):
467
+ """Base class for nodes that output a Z-Image text conditioning tensor."""
468
+
469
+ conditioning: ZImageConditioningField = OutputField(description=FieldDescriptions.cond)
470
+
471
+ @classmethod
472
+ def build(cls, conditioning_name: str) -> "ZImageConditioningOutput":
473
+ return cls(conditioning=ZImageConditioningField(conditioning_name=conditioning_name))
474
+
475
+
464
476
  @invocation_output("conditioning_output")
465
477
  class ConditioningOutput(BaseInvocationOutput):
466
478
  """Base class for nodes that output a single conditioning tensor"""
@@ -0,0 +1,57 @@
1
+ from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output
2
+ from invokeai.app.invocations.fields import InputField, OutputField, StylePresetField, UIComponent
3
+ from invokeai.app.services.shared.invocation_context import InvocationContext
4
+
5
+
6
+ @invocation_output("prompt_template_output")
7
+ class PromptTemplateOutput(BaseInvocationOutput):
8
+ """Output for the Prompt Template node"""
9
+
10
+ positive_prompt: str = OutputField(description="The positive prompt with the template applied")
11
+ negative_prompt: str = OutputField(description="The negative prompt with the template applied")
12
+
13
+
14
+ @invocation(
15
+ "prompt_template",
16
+ title="Prompt Template",
17
+ tags=["prompt", "template", "style", "preset"],
18
+ category="prompt",
19
+ version="1.0.0",
20
+ )
21
+ class PromptTemplateInvocation(BaseInvocation):
22
+ """Applies a Style Preset template to positive and negative prompts.
23
+
24
+ Select a Style Preset and provide positive/negative prompts. The node replaces
25
+ {prompt} placeholders in the template with your input prompts.
26
+ """
27
+
28
+ style_preset: StylePresetField = InputField(
29
+ description="The Style Preset to use as a template",
30
+ )
31
+ positive_prompt: str = InputField(
32
+ default="",
33
+ description="The positive prompt to insert into the template's {prompt} placeholder",
34
+ ui_component=UIComponent.Textarea,
35
+ )
36
+ negative_prompt: str = InputField(
37
+ default="",
38
+ description="The negative prompt to insert into the template's {prompt} placeholder",
39
+ ui_component=UIComponent.Textarea,
40
+ )
41
+
42
+ def invoke(self, context: InvocationContext) -> PromptTemplateOutput:
43
+ # Fetch the style preset from the database
44
+ style_preset = context._services.style_preset_records.get(self.style_preset.style_preset_id)
45
+
46
+ # Get the template prompts
47
+ positive_template = style_preset.preset_data.positive_prompt
48
+ negative_template = style_preset.preset_data.negative_prompt
49
+
50
+ # Replace {prompt} placeholder with the input prompts
51
+ rendered_positive = positive_template.replace("{prompt}", self.positive_prompt)
52
+ rendered_negative = negative_template.replace("{prompt}", self.negative_prompt)
53
+
54
+ return PromptTemplateOutput(
55
+ positive_prompt=rendered_positive,
56
+ negative_prompt=rendered_negative,
57
+ )
@@ -0,0 +1,112 @@
1
+ # Copyright (c) 2024, Lincoln D. Stein and the InvokeAI Development Team
2
+ """Z-Image Control invocation for spatial conditioning."""
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+ from invokeai.app.invocations.baseinvocation import (
7
+ BaseInvocation,
8
+ BaseInvocationOutput,
9
+ Classification,
10
+ invocation,
11
+ invocation_output,
12
+ )
13
+ from invokeai.app.invocations.fields import (
14
+ FieldDescriptions,
15
+ ImageField,
16
+ InputField,
17
+ OutputField,
18
+ )
19
+ from invokeai.app.invocations.model import ModelIdentifierField
20
+ from invokeai.app.services.shared.invocation_context import InvocationContext
21
+ from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType
22
+
23
+
24
+ class ZImageControlField(BaseModel):
25
+ """A Z-Image control conditioning field for spatial control (Canny, HED, Depth, Pose, MLSD)."""
26
+
27
+ image_name: str = Field(description="The name of the preprocessed control image")
28
+ control_model: ModelIdentifierField = Field(description="The Z-Image ControlNet adapter model")
29
+ control_context_scale: float = Field(
30
+ default=0.75,
31
+ ge=0.0,
32
+ le=2.0,
33
+ description="The strength of the control signal. Recommended range: 0.65-0.80.",
34
+ )
35
+ begin_step_percent: float = Field(
36
+ default=0.0,
37
+ ge=0.0,
38
+ le=1.0,
39
+ description="When the control is first applied (% of total steps)",
40
+ )
41
+ end_step_percent: float = Field(
42
+ default=1.0,
43
+ ge=0.0,
44
+ le=1.0,
45
+ description="When the control is last applied (% of total steps)",
46
+ )
47
+
48
+
49
+ @invocation_output("z_image_control_output")
50
+ class ZImageControlOutput(BaseInvocationOutput):
51
+ """Z-Image Control output containing control configuration."""
52
+
53
+ control: ZImageControlField = OutputField(description="Z-Image control conditioning")
54
+
55
+
56
+ @invocation(
57
+ "z_image_control",
58
+ title="Z-Image ControlNet",
59
+ tags=["image", "z-image", "control", "controlnet"],
60
+ category="control",
61
+ version="1.1.0",
62
+ classification=Classification.Prototype,
63
+ )
64
+ class ZImageControlInvocation(BaseInvocation):
65
+ """Configure Z-Image ControlNet for spatial conditioning.
66
+
67
+ Takes a preprocessed control image (e.g., Canny edges, depth map, pose)
68
+ and a Z-Image ControlNet adapter model to enable spatial control.
69
+
70
+ Supports 5 control modes: Canny, HED, Depth, Pose, MLSD.
71
+ Recommended control_context_scale: 0.65-0.80.
72
+ """
73
+
74
+ image: ImageField = InputField(
75
+ description="The preprocessed control image (Canny, HED, Depth, Pose, or MLSD)",
76
+ )
77
+ control_model: ModelIdentifierField = InputField(
78
+ description=FieldDescriptions.controlnet_model,
79
+ title="Control Model",
80
+ ui_model_base=BaseModelType.ZImage,
81
+ ui_model_type=ModelType.ControlNet,
82
+ )
83
+ control_context_scale: float = InputField(
84
+ default=0.75,
85
+ ge=0.0,
86
+ le=2.0,
87
+ description="Strength of the control signal. Recommended range: 0.65-0.80.",
88
+ title="Control Scale",
89
+ )
90
+ begin_step_percent: float = InputField(
91
+ default=0.0,
92
+ ge=0.0,
93
+ le=1.0,
94
+ description="When the control is first applied (% of total steps)",
95
+ )
96
+ end_step_percent: float = InputField(
97
+ default=1.0,
98
+ ge=0.0,
99
+ le=1.0,
100
+ description="When the control is last applied (% of total steps)",
101
+ )
102
+
103
+ def invoke(self, context: InvocationContext) -> ZImageControlOutput:
104
+ return ZImageControlOutput(
105
+ control=ZImageControlField(
106
+ image_name=self.image.image_name,
107
+ control_model=self.control_model,
108
+ control_context_scale=self.control_context_scale,
109
+ begin_step_percent=self.begin_step_percent,
110
+ end_step_percent=self.end_step_percent,
111
+ )
112
+ )