Merge pull request #50 from Mooshieblob1/feature/novelai-v5-and-character-prompts

Add NovelAI V5 model support and multi-character text prompting
This commit is contained in:
Bedovyy
2026-08-21 15:27:04 +09:00
committed by GitHub
2 changed files with 73 additions and 8 deletions
+20 -6
View File
@@ -86,6 +86,10 @@ Available V4+ models include:
- `nai-diffusion-4-full`
- `nai-diffusion-4-5-curated`
- `nai-diffusion-4-5-full`
- `nai-diffusion-5-curated`
- `nai-diffusion-5-full`
**Note:** NovelAI Diffusion V5 launched on 2026-08-21. The `nai-diffusion-5-*` identifiers above follow NovelAI's established naming convention but have not been independently confirmed against the live API yet — if generation fails with an unrecognized model error, please open an issue so the identifier can be corrected.
![ModelOption](https://github.com/bedovyy/ComfyUI_NAIDGenerator/assets/137917911/0b484edb-bcb5-428a-b2af-1372a9d7a34f)
@@ -130,20 +134,20 @@ You can find director tools like `LineArtNAID`, `EmotionNAID`, and `RemoveBGNAID
![augment_example](https://github.com/user-attachments/assets/5833e9fb-f92e-4d53-9069-58ca8503a3e7)
### V4 / V4.5 Support
### V4 / V4.5 / V5 Support
The nodes fully support NAI's V4 and V4.5 model architecture.
The nodes fully support NAI's V4, V4.5 and V5 model architecture.
#### V4 Prompt Handling
Two new nodes have been added in `NovelAI/v4` for V4/V4.5 prompt handling:
Two new nodes have been added in `NovelAI/v4` for V4/V4.5/V5 prompt handling:
- **`V4BasePrompt`**: Handles the positive prompt.
- **`V4NegativePrompt`**: Handles the negative prompt.
#### Example V4 / V4.5 Workflow
#### Example V4 / V4.5 / V5 Workflow
Here's a basic setup for a V4/V4.5 model:
Here's a basic setup for a V4/V4.5/V5 model:
```
V4BasePrompt -----> positive
@@ -151,4 +155,14 @@ V4BasePrompt -----> positive
V4NegativePrompt -> negative
```
**Note:** Basic `img2img`, `vibe transfer` and `inpainting` functionality works with V4/V4.5.
**Note:** Basic `img2img`, `vibe transfer` and `inpainting` functionality works with V4/V4.5/V5.
### Character Prompt (Multi-Character Text Prompting)
Connect one or more `CharacterPromptOptionNAID` nodes to the `option` input of `GenerateNAID` to give individual characters their own positive/negative captions, mirroring NAI's multi-character prompting feature (V4+). Chain multiple nodes together, one per character, the same way you would chain `VibeTransferOptionNAID` nodes.
- **positive / negative:** Per-character captions, merged into the request's `char_captions`.
- **use_position / x / y:** Enable to pin the character to a specific normalized position (`0.0`-`1.0`) instead of letting the model place it automatically. If any chained character enables this, positioning is used for the whole group.
- **use_order:** Treat the connection order of the chained character nodes as a priority hint for overlapping characters. If any chained character enables this, it applies to the whole group.
**Note:** This is independent of `CharacterReferenceOptionNAID`, which guides generation from a reference *image* rather than text.
+53 -2
View File
@@ -130,7 +130,9 @@ class ModelOption:
"nai-diffusion-4-curated-preview",
"nai-diffusion-4-full",
"nai-diffusion-4-5-curated",
"nai-diffusion-4-5-full"
"nai-diffusion-4-5-full",
"nai-diffusion-5-curated",
"nai-diffusion-5-full"
], { "default": "nai-diffusion-4-5-full" }),
},
"optional": { "option": ("NAID_OPTION",) },
@@ -254,6 +256,38 @@ class CharacterReferenceOption:
}
return (option,)
# -------------------------------------------------
# Character Prompt (Multi-Character Text Prompting)
# -------------------------------------------------
class CharacterPromptOption:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"positive": ("STRING", { "multiline": True, "dynamicPrompts": False, "tooltip": "This character's positive caption." }),
"negative": ("STRING", { "default": "", "multiline": True, "dynamicPrompts": False, "tooltip": "This character's negative caption." }),
"use_position": ("BOOLEAN", { "default": False, "tooltip": "Pin this character to x/y instead of letting the model place it automatically." }),
"x": ("FLOAT", { "default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "display": "number", "tooltip": "Horizontal position (0=left, 1=right). Only used when use_position is enabled." }),
"y": ("FLOAT", { "default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "display": "number", "tooltip": "Vertical position (0=top, 1=bottom). Only used when use_position is enabled." }),
"use_order": ("BOOLEAN", { "default": False, "tooltip": "Use the connection order of character prompt nodes as a priority when characters overlap. Applies to the whole group of chained character prompts." }),
},
"optional": { "option": ("NAID_OPTION",) },
}
RETURN_TYPES = ("NAID_OPTION",)
FUNCTION = "set_option"
CATEGORY = "NovelAI"
def set_option(self, positive, negative, use_position, x, y, use_order, option=None):
option = copy.deepcopy(option) if option else {}
if "characters" not in option:
option["characters"] = []
option["characters"].append({
"positive": positive, "negative": negative,
"use_position": use_position, "x": x, "y": y,
"use_order": use_order,
})
return (option,)
# -------------------------------------------------
# Generation Node
# -------------------------------------------------
@@ -358,7 +392,7 @@ class GenerateNAID:
action = "infill"
image, mask, add_original_image = option["infill"]
params["image"] = image_to_base64(resize_image(image, (width, height)))
params["mask"] = naimask_to_base64(resize_to_naimask(mask, (width, height), "4" in model))
params["mask"] = naimask_to_base64(resize_to_naimask(mask, (width, height), "nai-diffusion-4" in model or "nai-diffusion-5" in model))
params["add_original_image"] = add_original_image
if "vibe" in option:
@@ -371,6 +405,21 @@ class GenerateNAID:
if "model" in option: model = option["model"]
if "v4_prompt" in option: params["v4_prompt"].update(option["v4_prompt"])
if "characters" in option:
chars = option["characters"]
use_coords = any(c["use_position"] for c in chars)
use_order = any(c["use_order"] for c in chars)
params["v4_prompt"]["caption"]["char_captions"] = [
{"char_caption": c["positive"], "centers": [{"x": c["x"], "y": c["y"]}]} for c in chars
]
params["v4_prompt"]["use_coords"] = use_coords
params["v4_prompt"]["use_order"] = use_order
params["v4_negative_prompt"]["caption"]["char_captions"] = [
{"char_caption": c["negative"], "centers": [{"x": c["x"], "y": c["y"]}]} for c in chars
]
params["v4_negative_prompt"]["use_coords"] = use_coords
params["v4_negative_prompt"]["use_order"] = use_order
if "character_reference_single" in option:
ref = option["character_reference_single"]
base_caption = "character&style" if ref["style_aware"] else "character"
@@ -626,6 +675,7 @@ NODE_CLASS_MAPPINGS = {
"VibeTransferOptionNAID": VibeTransferOption,
"NetworkOptionNAID": NetworkOption,
"CharacterReferenceOptionNAID": CharacterReferenceOption,
"CharacterPromptOptionNAID": CharacterPromptOption,
"AnlasTrackerNAID": AnlasTrackerNAID, # New node
"MaskImageToNAID": ImageToNAIMask,
"PromptToNAID": PromptToNAID,
@@ -647,6 +697,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"VibeTransferOptionNAID": "VibeTransferOption ✒️🅝🅐🅘",
"NetworkOptionNAID": "NetworkOption ✒️🅝🅐🅘",
"CharacterReferenceOptionNAID": "Character Reference ✒️🅝🅐🅘",
"CharacterPromptOptionNAID": "Character Prompt ✒️🅝🅐🅘",
"AnlasTrackerNAID": "Anlas Tracker ✒️🅝🅐🅘", # New node
"MaskImageToNAID": "Convert Mask Image ✒️🅝🅐🅘",
"PromptToNAID": "Convert Prompt ✒️🅝🅐🅘",