Compare commits

..
2 Commits
Author SHA1 Message Date
chris.dumas eac4e73b47 Expose individual prompt reference descriptions 2026-09-10 07:59:11 +00:00
chris.dumas e973a0d785 Expose H3 prompt curator components 2026-09-09 15:34:06 +00:00
3 changed files with 88 additions and 10 deletions
+2 -1
View File
@@ -58,9 +58,10 @@
- `Dumas H3 Prompt Curator` - `Dumas H3 Prompt Curator`
- Inputs: `action_prompt`, `anatomy_guard`, `subject_count_guard`, optional `anchor`, optional `soundscape`, optional `bgm`, optional `ref_1` through `ref_9` - Inputs: `action_prompt`, `anatomy_guard`, `subject_count_guard`, optional `anchor`, optional `soundscape`, optional `bgm`, optional `ref_1` through `ref_9`
- Outputs: `prompt`, `ref_image_1` through `ref_image_9`, `reference_count`, `debug` - Outputs: `prompt`, `ref_image_1` through `ref_image_9`, `reference_count`, `debug`, `anchor`, `sounds`, `bgm`, `original_ref_1` through `original_ref_9`, `reference_description`, `original_ref_description_1` through `original_ref_description_9`
- Builds one standalone MiniMax H3 prompt from your final action text plus structured character/location references. - Builds one standalone MiniMax H3 prompt from your final action text plus structured character/location references.
- The action text can mention references by character/location name, alias, `<Picture N>`, or `<refN>`. Only mentioned references are emitted, and the output images are compacted/renumbered so skipped inputs do not leave gaps. - The action text can mention references by character/location name, alias, `<Picture N>`, or `<refN>`. Only mentioned references are emitted, and the output images are compacted/renumbered so skipped inputs do not leave gaps.
- Extra component outputs expose the cleaned anchor, sounds, BGM, selected structured references in compacted order, the compiled reference-description block used inside the prompt, and one individual description per selected reference.
- Adds curated reference context, anatomy guard text, optional subject-count guard text, anchor/style text, `overall_soundscape:` text, and `background_music:` text while respecting MiniMax H3's reference-generation shape: one prompt plus up to nine reference images. - Adds curated reference context, anatomy guard text, optional subject-count guard text, anchor/style text, `overall_soundscape:` text, and `background_music:` text while respecting MiniMax H3's reference-generation shape: one prompt plus up to nine reference images.
- `Dumas H3 Shot Length` - `Dumas H3 Shot Length`
+62 -8
View File
@@ -1249,12 +1249,21 @@ def curate_h3_prompt(
selected = _selected_prompt_refs(action_prompt, normalized_refs) selected = _selected_prompt_refs(action_prompt, normalized_refs)
picture_map = {slot_number: index for index, (slot_number, _ref) in enumerate(selected, 1)} picture_map = {slot_number: index for index, (slot_number, _ref) in enumerate(selected, 1)}
action = _replace_reference_tags(action_prompt, picture_map) action = _replace_reference_tags(action_prompt, picture_map)
anchor_text = _reference_text(anchor)
soundscape_text = _reference_text(soundscape)
bgm_text = _reference_text(bgm)
reference_description = ""
individual_reference_descriptions = []
if selected:
individual_reference_descriptions = [
_reference_context(ref, picture_number)
for picture_number, (_slot, ref) in enumerate(selected, 1)
]
reference_description = " ".join(individual_reference_descriptions)
prompt_parts = [] prompt_parts = []
_append_prompt_section(prompt_parts, "Scene anchor", anchor) _append_prompt_section(prompt_parts, "Scene anchor", anchor_text)
if selected: _append_prompt_section(prompt_parts, "Reference context", reference_description)
contexts = [_reference_context(ref, picture_number) for picture_number, (_slot, ref) in enumerate(selected, 1)]
_append_prompt_section(prompt_parts, "Reference context", " ".join(contexts))
_append_prompt_section(prompt_parts, "Action", action) _append_prompt_section(prompt_parts, "Action", action)
if anatomy_guard == "on" or (anatomy_guard == "auto" and any(ref.get("kind") == "character" for _slot, ref in selected)): if anatomy_guard == "on" or (anatomy_guard == "auto" and any(ref.get("kind") == "character" for _slot, ref in selected)):
_append_prompt_section(prompt_parts, "Anatomy guard", _ANATOMY_GUARD_TEXT) _append_prompt_section(prompt_parts, "Anatomy guard", _ANATOMY_GUARD_TEXT)
@@ -1262,14 +1271,19 @@ def curate_h3_prompt(
subject_count_guard == "auto" and any(ref.get("kind") == "character" for _slot, ref in selected) subject_count_guard == "auto" and any(ref.get("kind") == "character" for _slot, ref in selected)
): ):
_append_prompt_section(prompt_parts, "Subject count guard", _subject_count_guard_text(selected)) _append_prompt_section(prompt_parts, "Subject count guard", _subject_count_guard_text(selected))
_append_prompt_section(prompt_parts, "overall_soundscape", soundscape) _append_prompt_section(prompt_parts, "overall_soundscape", soundscape_text)
_append_prompt_section(prompt_parts, "background_music", bgm) _append_prompt_section(prompt_parts, "background_music", bgm_text)
prompt = "\n\n".join(prompt_parts).strip() prompt = "\n\n".join(prompt_parts).strip()
if len(prompt) > _H3_PROMPT_MAX_CHARS: if len(prompt) > _H3_PROMPT_MAX_CHARS:
prompt = prompt[: _H3_PROMPT_MAX_CHARS - 3].rstrip() + "..." prompt = prompt[: _H3_PROMPT_MAX_CHARS - 3].rstrip() + "..."
images = [_reference_image(ref) for _slot, ref in selected] images = [_reference_image(ref) for _slot, ref in selected]
images.extend([None] * (_H3_PROMPT_REF_SLOTS - len(images))) images.extend([None] * (_H3_PROMPT_REF_SLOTS - len(images)))
selected_refs = [ref for _slot, ref in selected]
selected_refs.extend([None] * (_H3_PROMPT_REF_SLOTS - len(selected_refs)))
individual_reference_descriptions.extend(
[""] * (_H3_PROMPT_REF_SLOTS - len(individual_reference_descriptions))
)
debug = ( debug = (
f"Selected {len(selected)} reference(s): " f"Selected {len(selected)} reference(s): "
+ ", ".join( + ", ".join(
@@ -1279,7 +1293,18 @@ def curate_h3_prompt(
if selected if selected
else "Selected 0 references." else "Selected 0 references."
) )
return (prompt, *images[:_H3_PROMPT_REF_SLOTS], len(selected), debug) return (
prompt,
*images[:_H3_PROMPT_REF_SLOTS],
len(selected),
debug,
anchor_text,
soundscape_text,
bgm_text,
*selected_refs[:_H3_PROMPT_REF_SLOTS],
reference_description,
*individual_reference_descriptions[:_H3_PROMPT_REF_SLOTS],
)
def _parse_positive_int(value): def _parse_positive_int(value):
@@ -2666,7 +2691,14 @@ class DumasH3PromptCuratorNode:
"compacted so only mentioned names, aliases, or explicit <Picture N>/<refN> " "compacted so only mentioned names, aliases, or explicit <Picture N>/<refN> "
"tags are sent onward." "tags are sent onward."
) )
RETURN_TYPES = ("STRING",) + ("IMAGE",) * _H3_PROMPT_REF_SLOTS + ("INT", "STRING") RETURN_TYPES = (
("STRING",)
+ ("IMAGE",) * _H3_PROMPT_REF_SLOTS
+ ("INT", "STRING", "STRING", "STRING", "STRING")
+ (_REFERENCE_TYPE,) * _H3_PROMPT_REF_SLOTS
+ ("STRING",)
+ ("STRING",) * _H3_PROMPT_REF_SLOTS
)
RETURN_NAMES = ( RETURN_NAMES = (
"prompt", "prompt",
"ref_image_1", "ref_image_1",
@@ -2680,6 +2712,28 @@ class DumasH3PromptCuratorNode:
"ref_image_9", "ref_image_9",
"reference_count", "reference_count",
"debug", "debug",
"anchor",
"sounds",
"bgm",
"original_ref_1",
"original_ref_2",
"original_ref_3",
"original_ref_4",
"original_ref_5",
"original_ref_6",
"original_ref_7",
"original_ref_8",
"original_ref_9",
"reference_description",
"original_ref_description_1",
"original_ref_description_2",
"original_ref_description_3",
"original_ref_description_4",
"original_ref_description_5",
"original_ref_description_6",
"original_ref_description_7",
"original_ref_description_8",
"original_ref_description_9",
) )
FUNCTION = "curate_prompt" FUNCTION = "curate_prompt"
CATEGORY = "Dumas/MiniMax" CATEGORY = "Dumas/MiniMax"
+24 -1
View File
@@ -499,6 +499,22 @@ class DumasImageNodeTests(unittest.TestCase):
self.assertIsNone(result[3]) self.assertIsNone(result[3])
self.assertEqual(result[10], 2) self.assertEqual(result[10], 2)
self.assertIn("input 3-><Picture 2> Coffee Shop", result[11]) self.assertIn("input 3-><Picture 2> Coffee Shop", result[11])
self.assertEqual(result[12], "grounded handheld thriller")
self.assertEqual(result[13], "steady rain")
self.assertEqual(result[14], "low suspense music")
self.assertEqual(result[15]["name"], "Dave")
self.assertIs(result[15]["image"], dave_image)
self.assertEqual(result[16]["name"], "Coffee Shop")
self.assertIs(result[16]["image"], cafe_image)
self.assertIsNone(result[17])
self.assertIn("<Picture 1> Dave", result[24])
self.assertIn("<Picture 2> Coffee Shop", result[24])
self.assertIn("Location context for <Picture 2> Coffee Shop", result[24])
self.assertIn("<Picture 1> Dave", result[25])
self.assertNotIn("<Picture 2> Coffee Shop", result[25])
self.assertIn("<Picture 2> Coffee Shop", result[26])
self.assertIn("Location context for <Picture 2> Coffee Shop", result[26])
self.assertEqual(result[27], "")
def test_h3_prompt_curator_renumbers_explicit_reference_tags(self): def test_h3_prompt_curator_renumbers_explicit_reference_tags(self):
node = self.image_nodes.DumasH3PromptCuratorNode() node = self.image_nodes.DumasH3PromptCuratorNode()
@@ -636,8 +652,15 @@ class DumasImageNodeTests(unittest.TestCase):
def test_h3_prompt_curator_uses_documented_reference_limits(self): def test_h3_prompt_curator_uses_documented_reference_limits(self):
node = self.image_nodes.DumasH3PromptCuratorNode() node = self.image_nodes.DumasH3PromptCuratorNode()
self.assertEqual(len(node.RETURN_TYPES), 12) self.assertEqual(len(node.RETURN_TYPES), 34)
self.assertEqual(node.RETURN_NAMES[1:10], tuple(f"ref_image_{i}" for i in range(1, 10))) self.assertEqual(node.RETURN_NAMES[1:10], tuple(f"ref_image_{i}" for i in range(1, 10)))
self.assertEqual(node.RETURN_NAMES[12:15], ("anchor", "sounds", "bgm"))
self.assertEqual(node.RETURN_NAMES[15:24], tuple(f"original_ref_{i}" for i in range(1, 10)))
self.assertEqual(node.RETURN_NAMES[24], "reference_description")
self.assertEqual(
node.RETURN_NAMES[25:34],
tuple(f"original_ref_description_{i}" for i in range(1, 10)),
)
def test_normalize_reference_upgrades_generic_summary_with_socket_picture_id(self): def test_normalize_reference_upgrades_generic_summary_with_socket_picture_id(self):
image = FakeTensorBatch() image = FakeTensorBatch()