feat(conditioning): support labeled prompt separators

This commit is contained in:
Artificial Sweetener
2026-08-05 22:19:50 -04:00
parent 44cc18b86c
commit e266880244
18 changed files with 273 additions and 79 deletions
+4 -4
View File
@@ -70,15 +70,15 @@ The regional node is different from the per-crop detailers. It samples one full-
### Per-Segment Prompt Batches
SimpleSyrup layers the ADetailer habit I missed from WebUI on top of the Impact-style detailer shape: writing per-segment prompt batches inline with `[SEP]`.
SimpleSyrup layers the ADetailer habit I missed from WebUI on top of the Impact-style detailer shape: writing per-segment prompt batches inline with `[SEP]`. Separators can carry an organizational name, such as `[SEP|Sky]`; the name is discarded during parsing, and prompt-to-SEG matching remains positional.
Those prompts become an ordered `CONDITIONING_BATCH`, so prompt 1 stays matched to SEG 1, prompt 2 stays matched to SEG 2, and so on. This keeps the graph readable when each detected item needs its own prompt.
- **Encode Prompt Batch** splits prompt text with `[SEP]` and encodes ordered positive and negative `CONDITIONING_BATCH` values.
- **Encode Prompt Batch** splits prompt text with `[SEP]` or `[SEP|name]` and encodes ordered positive and negative `CONDITIONING_BATCH` values.
- **Conditioning Batch Start** and **Conditioning Batch Append** build ordered conditioning batches for per-segment and regional workflows.
- **Batch Region Conditioning** combines normal `CONDITIONING` values and existing `CONDITIONING_BATCH` values into one ordered regional batch.
- **Encode Prompt Batch w/ Prompt Control** is exported when Prompt Control is installed. It keeps the `[SEP]` batching workflow while letting Prompt Control handle prompt scheduling and encoding.
- **Schedule & Encode Prompts** is also exported when Prompt Control is installed. It schedules Prompt Control LoRA tags, encodes positive and negative prompts, and returns normal conditioning or SimpleSyrup batches depending on whether `[SEP]` is used.
- **Encode Prompt Batch w/ Prompt Control** is exported when Prompt Control is installed. It keeps the `[SEP]` and `[SEP|name]` batching workflow while letting Prompt Control handle prompt scheduling and encoding.
- **Schedule & Encode Prompts** is also exported when Prompt Control is installed. It schedules Prompt Control LoRA tags, encodes positive and negative prompts, and returns normal conditioning or SimpleSyrup batches depending on whether `[SEP]` or `[SEP|name]` is used.
### Tile, Tag, and Guide
-10
View File
@@ -6,7 +6,6 @@
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Any, TypeAlias
@@ -57,15 +56,6 @@ def batch_conditioning(
return ConditioningBatch(tuple(entries))
def split_prompt_batch(text: str, separator: str = "[SEP]") -> tuple[str, ...]:
"""Split prompt text into ordered chunks using a configurable separator."""
if separator == "":
raise ValueError("separator must not be empty.")
pattern = rf"\s*{re.escape(separator)}\s*"
return tuple(re.split(pattern, text))
def select_conditioning(
conditioning: Conditioning | ConditioningBatch,
index: int,
@@ -0,0 +1,32 @@
# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
# Copyright (C) 2026 Artificial Sweetener and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Parse authored prompt text into ordered batch entries."""
from __future__ import annotations
import re
DEFAULT_PROMPT_BATCH_SEPARATOR = "[SEP]"
_NAMED_DEFAULT_SEPARATOR_PATTERN = r"\[SEP(?:\|[^\r\n\]]+)?\]"
def split_prompt_batch(
text: str,
separator: str = DEFAULT_PROMPT_BATCH_SEPARATOR,
) -> tuple[str, ...]:
"""Split prompt text while discarding default-separator labels."""
if separator == "":
raise ValueError("separator must not be empty.")
pattern = rf"\s*(?:{_separator_pattern(separator)})\s*"
return tuple(re.split(pattern, text))
def _separator_pattern(separator: str) -> str:
"""Return labeled default grammar or an escaped custom separator pattern."""
if separator == DEFAULT_PROMPT_BATCH_SEPARATOR:
return _NAMED_DEFAULT_SEPARATOR_PATTERN
return re.escape(separator)
+1 -1
View File
@@ -9,7 +9,7 @@ from __future__ import annotations
import re
from dataclasses import dataclass
from .conditioning_batch import split_prompt_batch
from .prompt_batch_parser import split_prompt_batch
PROMPT_TEXT_PATTERN = r"(?:^|>)([^<]+)(?=<|$)"
LORA_TAG_PATTERN = r"<[^>]*>"
+16 -8
View File
@@ -8,6 +8,7 @@ from __future__ import annotations
from typing import Any, ClassVar
from ..domain.prompt_batch_parser import DEFAULT_PROMPT_BATCH_SEPARATOR
from ..runtime.conditioning_encoding import ComfyConditioningEncoder
from ..services.prompt_batch_encoding_service import PromptBatchEncodingService
@@ -24,8 +25,9 @@ class EncodePromptBatch:
FUNCTION = "encode"
CATEGORY = "SimpleSyrup/Conditioning"
DESCRIPTION = (
"Encodes [SEP]-separated prompts into matched conditioning batches, "
"reusing each side's global prompt when a regional entry is missing."
"Encodes prompts separated by [SEP] or [SEP|name] into matched "
"conditioning batches, reusing each side's global prompt when a "
"regional entry is missing."
)
SEARCH_ALIASES = ["conditioning batch", "prompt batch", "segs prompts"]
@@ -55,8 +57,9 @@ class EncodePromptBatch:
"default": "",
"multiline": True,
"tooltip": (
"Ordered positive prompt entries separated by [SEP]; "
"the global entry fills missing positive regions."
"Ordered positive prompt entries separated by [SEP] "
"or [SEP|name]; the global entry fills missing "
"positive regions."
),
},
),
@@ -66,16 +69,21 @@ class EncodePromptBatch:
"default": "",
"multiline": True,
"tooltip": (
"Ordered negative prompt entries separated by [SEP]; "
"the global entry fills missing negative regions."
"Ordered negative prompt entries separated by [SEP] "
"or [SEP|name]; the global entry fills missing "
"negative regions."
),
},
),
"separator": (
"STRING",
{
"default": "[SEP]",
"tooltip": "Text marker that separates prompt entries.",
"default": DEFAULT_PROMPT_BATCH_SEPARATOR,
"tooltip": (
"Text marker that separates prompt entries. With the "
"default [SEP], use [SEP|name] to add an organizational "
"label."
),
},
),
}
@@ -30,8 +30,9 @@ class ScheduleAndEncodePromptsWithPromptControl:
FUNCTION = "execute"
CATEGORY = "SimpleSyrup/Conditioning"
DESCRIPTION = (
"Schedules Prompt-Control LoRAs and encodes prompts. [SEP] creates "
"matched conditioning batches using global text for missing regions."
"Schedules Prompt-Control LoRAs and encodes prompts. [SEP] and "
"[SEP|name] create matched conditioning batches using global text for "
"missing regions."
)
SEARCH_ALIASES = ["prompt control", "schedule prompts", "encode prompts"]
@@ -67,8 +68,9 @@ class ScheduleAndEncodePromptsWithPromptControl:
"default": "",
"multiline": False,
"tooltip": (
"Positive Prompt-Control text; [SEP] creates ordered "
"entries, and global text fills missing positive regions."
"Positive Prompt-Control text; [SEP] or [SEP|name] "
"creates ordered entries, and global text fills missing "
"positive regions."
),
},
),
@@ -78,8 +80,9 @@ class ScheduleAndEncodePromptsWithPromptControl:
"default": "",
"multiline": False,
"tooltip": (
"Negative Prompt-Control text; [SEP] creates ordered "
"entries, and global text fills missing negative regions."
"Negative Prompt-Control text; [SEP] or [SEP|name] "
"creates ordered entries, and global text fills missing "
"negative regions."
),
},
),
@@ -9,6 +9,7 @@ from __future__ import annotations
from importlib import import_module
from typing import TYPE_CHECKING, Any, ClassVar
from ..domain.prompt_batch_parser import DEFAULT_PROMPT_BATCH_SEPARATOR
from ..runtime.prompt_control_batch_graph import PromptControlBatchGraphBuilder
if TYPE_CHECKING:
@@ -47,8 +48,9 @@ class EncodePromptBatchWithPromptControl(_ComfyNodeBase):
enable_expand=True,
category="SimpleSyrup/Conditioning",
description=(
"Encodes [SEP]-separated prompts into matched Prompt Control "
"batches, reusing each side's global text for missing regions."
"Encodes prompts separated by [SEP] or [SEP|name] into matched "
"Prompt Control batches, reusing each side's global text for "
"missing regions."
),
inputs=[
_comfy_io.Clip.Input(
@@ -64,9 +66,9 @@ class EncodePromptBatchWithPromptControl(_ComfyNodeBase):
multiline=True,
default="",
tooltip=(
"Positive Prompt Control prompts in positional order; each "
"segment keeps its aligned LoRA hooks, and global text "
"fills missing positive regions."
"Positive Prompt Control prompts separated by [SEP] or "
"[SEP|name] in positional order; each segment keeps its "
"aligned LoRA hooks, and global text fills missing regions."
),
),
_comfy_io.String.Input(
@@ -74,15 +76,20 @@ class EncodePromptBatchWithPromptControl(_ComfyNodeBase):
multiline=True,
default="",
tooltip=(
"Negative Prompt Control prompts in positional order; each "
"segment shares hooks with the matching positive index, "
"and global text fills missing negative regions."
"Negative Prompt Control prompts separated by [SEP] or "
"[SEP|name] in positional order; each segment shares hooks "
"with the matching positive index, and global text fills "
"missing regions."
),
),
_comfy_io.String.Input(
"separator",
default="[SEP]",
tooltip="Text marker that splits prompts into per-SEGS entries.",
default=DEFAULT_PROMPT_BATCH_SEPARATOR,
tooltip=(
"Text marker that splits prompts into per-SEGS entries. "
"With the default [SEP], use [SEP|name] to add an "
"organizational label."
),
),
],
outputs=[
@@ -49,9 +49,9 @@ class ScheduleAndEncodePromptsWithPromptControl(_ComfyNodeBase):
enable_expand=True,
category="SimpleSyrup/Conditioning",
description=(
"Schedules Prompt-Control LoRAs and encodes prompts. With [SEP], "
"both sides are matched using global text for missing regions and "
"share each segment's LoRA hooks."
"Schedules Prompt-Control LoRAs and encodes prompts. With [SEP] "
"or [SEP|name], both sides are matched using global text for "
"missing regions and share each segment's LoRA hooks."
),
inputs=[
_comfy_io.Model.Input(
@@ -85,9 +85,9 @@ class ScheduleAndEncodePromptsWithPromptControl(_ComfyNodeBase):
multiline=False,
default="",
tooltip=(
"Positive Prompt-Control text; [SEP] creates ordered "
"conditioning entries, and global text fills missing "
"positive regions."
"Positive Prompt-Control text; [SEP] or [SEP|name] creates "
"ordered conditioning entries, and global text fills "
"missing positive regions."
),
),
_comfy_io.String.Input(
@@ -95,9 +95,9 @@ class ScheduleAndEncodePromptsWithPromptControl(_ComfyNodeBase):
multiline=False,
default="",
tooltip=(
"Negative Prompt-Control text; [SEP] creates ordered "
"conditioning entries, and global text fills missing "
"negative regions."
"Negative Prompt-Control text; [SEP] or [SEP|name] creates "
"ordered conditioning entries, and global text fills "
"missing negative regions."
),
),
],
@@ -8,6 +8,7 @@ from __future__ import annotations
from typing import Any
from ..domain.prompt_batch_parser import DEFAULT_PROMPT_BATCH_SEPARATOR
from ..domain.prompt_control_prompt import PreparedPromptSide, apply_encode_style
from ..services.prompt_control_segment_planning_service import (
PromptControlSegmentPlan,
@@ -22,7 +23,6 @@ PROMPT_CONTROL_MISSING_MESSAGE = (
"Schedule & Encode Prompts requires comfyui-prompt-control. "
"Install Prompt Control or remove this node from the workflow."
)
PROMPT_BATCH_SEPARATOR = "[SEP]"
class PromptControlScheduleEncodeGraphBuilder:
@@ -44,7 +44,7 @@ class PromptControlScheduleEncodeGraphBuilder:
plan = self.planning_service_class().prepare(
positive_prompt=positive_prompt,
negative_prompt=negative_prompt,
separator=PROMPT_BATCH_SEPARATOR,
separator=DEFAULT_PROMPT_BATCH_SEPARATOR,
)
adapter = self.graph_adapter_class.load(PROMPT_CONTROL_MISSING_MESSAGE)
expand: dict[str, dict[str, Any]] = {}
@@ -8,7 +8,8 @@ from __future__ import annotations
from typing import Any, Protocol
from ..domain.conditioning_batch import ConditioningBatch, split_prompt_batch
from ..domain.conditioning_batch import ConditioningBatch
from ..domain.prompt_batch_parser import split_prompt_batch
from ..domain.prompt_segment_alignment import build_prompt_segment_alignment
-28
View File
@@ -12,37 +12,9 @@ from simple_syrup.domain.conditioning_batch import (
ConditioningBatch,
batch_conditioning,
select_conditioning,
split_prompt_batch,
)
def test_split_prompt_batch_without_separator_returns_single_chunk() -> None:
"""Plain prompt text remains one prompt entry."""
assert split_prompt_batch("cat", "[SEP]") == ("cat",)
def test_split_prompt_batch_trims_separator_whitespace() -> None:
"""Whitespace around separators does not become prompt text."""
assert split_prompt_batch("cat [SEP] dog", "[SEP]") == ("cat", "dog")
assert split_prompt_batch("cat[SEP]dog", "[SEP]") == ("cat", "dog")
def test_split_prompt_batch_preserves_blank_prompt_and_empty_chunks() -> None:
"""Blank prompts and trailing separator chunks remain explicit entries."""
assert split_prompt_batch("", "[SEP]") == ("",)
assert split_prompt_batch("cat [SEP]", "[SEP]") == ("cat", "")
def test_split_prompt_batch_rejects_empty_separator() -> None:
"""An empty separator would split between every character."""
with pytest.raises(ValueError, match="separator must not be empty"):
split_prompt_batch("cat", "")
def test_conditioning_batch_requires_entries() -> None:
"""A batch must contain at least one selectable entry."""
+22
View File
@@ -26,6 +26,7 @@ def test_encode_prompt_batch_contract() -> None:
assert EncodePromptBatch.RETURN_NAMES == ("positive", "negative")
assert EncodePromptBatch.CATEGORY == "SimpleSyrup/Conditioning"
assert "global" in EncodePromptBatch.DESCRIPTION.lower()
assert "[sep|name]" in EncodePromptBatch.DESCRIPTION.lower()
assert list(inputs["required"]) == [
"clip",
"positive_prompt",
@@ -36,6 +37,7 @@ def test_encode_prompt_batch_contract() -> None:
assert inputs["required"]["positive_prompt"][1]["default"] == ""
assert inputs["required"]["negative_prompt"][1]["default"] == ""
assert inputs["required"]["separator"][1]["default"] == "[SEP]"
assert "[sep|name]" in inputs["required"]["separator"][1]["tooltip"].lower()
assert "global" in inputs["required"]["positive_prompt"][1]["tooltip"].lower()
assert "global" in inputs["required"]["negative_prompt"][1]["tooltip"].lower()
@@ -60,6 +62,26 @@ def test_encode_prompt_batch_aligns_missing_negative_chunks_to_global(
assert negative.entries == ("clip:blur", "clip:blur")
def test_encode_prompt_batch_accepts_named_default_separators(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Organizational separator names do not enter encoded prompt text."""
monkeypatch.setattr(EncodePromptBatch, "encoder_class", _FakeEncoder)
positive, negative = EncodePromptBatch().encode(
clip="clip",
positive_prompt="global [SEP|Sky] clouds [SEP|Ground] field",
negative_prompt="blur",
separator="[SEP]",
)
assert isinstance(positive, ConditioningBatch)
assert isinstance(negative, ConditioningBatch)
assert positive.entries == ("clip:global", "clip:clouds", "clip:field")
assert negative.entries == ("clip:blur", "clip:blur", "clip:blur")
def test_encode_prompt_batch_encodes_blank_and_empty_chunks(
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -21,6 +21,7 @@ def test_prompt_control_prompt_batch_node_schema() -> None:
assert schema.enable_expand is True
assert schema.category == "SimpleSyrup/Conditioning"
assert "global" in schema.description.lower()
assert "[sep|name]" in schema.description.lower()
assert [output.io_type for output in schema.outputs] == [
"CONDITIONING_BATCH",
"CONDITIONING_BATCH",
@@ -49,5 +50,6 @@ def test_prompt_control_prompt_batch_input_types() -> None:
]
assert inputs["required"]["clip"][0] == "CLIP"
assert inputs["required"]["separator"][1]["default"] == "[SEP]"
assert "[sep|name]" in inputs["required"]["separator"][1]["tooltip"].lower()
assert "global" in inputs["required"]["positive_prompt"][1]["tooltip"].lower()
assert "global" in inputs["required"]["negative_prompt"][1]["tooltip"].lower()
+86
View File
@@ -0,0 +1,86 @@
# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
# Copyright (C) 2026 Artificial Sweetener and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Test prompt-batch separator parsing behavior."""
from __future__ import annotations
import pytest
from simple_syrup.domain.prompt_batch_parser import split_prompt_batch
def test_split_prompt_batch_without_separator_returns_single_chunk() -> None:
"""Keep plain prompt text as one prompt entry."""
assert split_prompt_batch("cat", "[SEP]") == ("cat",)
def test_split_prompt_batch_trims_separator_whitespace() -> None:
"""Exclude whitespace surrounding separators from prompt text."""
assert split_prompt_batch("cat [SEP] dog", "[SEP]") == ("cat", "dog")
assert split_prompt_batch("cat[SEP]dog", "[SEP]") == ("cat", "dog")
def test_split_prompt_batch_preserves_blank_prompt_and_empty_chunks() -> None:
"""Keep blank prompts and trailing separator chunks as explicit entries."""
assert split_prompt_batch("", "[SEP]") == ("",)
assert split_prompt_batch("cat [SEP]", "[SEP]") == ("cat", "")
def test_split_prompt_batch_treats_custom_separator_as_literal_text() -> None:
"""Escape regex syntax in a configured custom separator."""
assert split_prompt_batch("cat .+ dog", ".+") == ("cat", "dog")
def test_split_prompt_batch_accepts_named_default_separators() -> None:
"""Discard organizational names while preserving prompt order."""
assert split_prompt_batch(
"global [SEP|Sky] clouds [SEP] field [SEP|Subject 2] person",
"[SEP]",
) == ("global", "clouds", "field", "person")
def test_split_prompt_batch_preserves_empty_chunk_after_named_separator() -> None:
"""Keep a trailing named separator as an explicit blank prompt entry."""
assert split_prompt_batch("cat [SEP|Unused region]", "[SEP]") == ("cat", "")
@pytest.mark.parametrize(
"marker",
(
"[SEP|]",
"[SEP|Sky",
"[SEP|Sky\nClouds]",
"[sep|Sky]",
),
)
def test_split_prompt_batch_keeps_malformed_or_wrong_case_markers(
marker: str,
) -> None:
"""Leave text untouched when it is not a valid default separator marker."""
prompt = f"cat {marker} dog"
assert split_prompt_batch(prompt, "[SEP]") == (prompt,)
def test_split_prompt_batch_does_not_extend_custom_separator_grammar() -> None:
"""Recognize named markers only when the configured separator is `[SEP]`."""
prompt = "cat [SEP|Sky] dog"
assert split_prompt_batch(prompt, "---") == (prompt,)
def test_split_prompt_batch_rejects_empty_separator() -> None:
"""Reject a separator that would split between every character."""
with pytest.raises(ValueError, match="separator must not be empty"):
split_prompt_batch("cat", "")
+19
View File
@@ -86,6 +86,25 @@ def test_prompt_control_batch_graph_builds_pack_chain_for_multiple_chunks(
assert output.args[1] == ["BATCH.0.5.2", 0]
def test_prompt_control_batch_graph_encodes_named_separator_chunks(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Build one Prompt Control encoding per named default-separator chunk."""
calls = _install_fake_prompt_control(monkeypatch)
graph_utils = import_module("comfy_execution.graph_utils")
graph_utils.GraphBuilder.set_default_prefix("NAMED", 0, 0)
PromptControlBatchGraphBuilder().build(
clip=[0, 0],
positive_prompt="global [SEP|Sky] clouds",
negative_prompt="blur [SEP|Sky] haze",
separator="[SEP]",
)
assert [call["text"] for call in calls] == ["global", "clouds", "blur", "haze"]
def test_prompt_control_batch_graph_attaches_segment_local_lora_hooks(
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -96,6 +96,30 @@ def test_schedule_encode_graph_packs_both_sides_to_matched_segment_counts(
]
def test_schedule_encode_graph_uses_named_default_separators(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Recognize named separators on the fixed-default scheduling path."""
calls = _install_fake_prompt_control(monkeypatch)
PromptControlScheduleEncodeGraphBuilder().build(
model=["model", 0],
clip=["clip", 0],
positive_prompt="global [SEP|Sky] clouds [SEP|Ground] field",
negative_prompt="blur",
)
assert [call["text"] for call in calls["encode"]] == [
"global",
"clouds",
"field",
"blur",
"blur",
"blur",
]
def test_schedule_encode_graph_matches_lora_region_on_both_cfg_sides(
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -38,6 +38,30 @@ def test_planner_combines_lora_tags_only_at_aligned_indexes() -> None:
]
def test_planner_aligns_lora_hooks_across_named_separators() -> None:
"""Treat separator names as comments while aligning segment-local hooks."""
plan = PromptControlSegmentPlanningService().prepare(
positive_prompt=(
"global [SEP|Sky] clouds <lora:sky:1> [SEP|Ground] field <lora:ground:0.5>"
),
negative_prompt="bad [SEP|Sky] haze",
separator="[SEP]",
)
assert [chunk.text for chunk in plan.positive.chunks] == [
"global",
"clouds ",
"field ",
]
assert [chunk.text for chunk in plan.negative.chunks] == ["bad", "haze", "bad"]
assert [hook.lora_tags for hook in plan.hooks] == [
"",
"<lora:sky:1>",
"<lora:ground:0.5>",
]
def test_planner_preserves_empties_and_fills_missing_side_from_global() -> None:
"""Authored empties remain while missing negative positions reuse global text."""
@@ -33,6 +33,7 @@ def test_legacy_schedule_and_encode_prompt_control_node_contract() -> None:
)
assert LegacyScheduleAndEncode.FUNCTION == "execute"
assert LegacyScheduleAndEncode.CATEGORY == "SimpleSyrup/Conditioning"
assert "[sep|name]" in LegacyScheduleAndEncode.DESCRIPTION.lower()
assert list(inputs["required"]) == [
"model",
"clip",
@@ -46,6 +47,7 @@ def test_legacy_schedule_and_encode_prompt_control_node_contract() -> None:
assert inputs["optional"]["encode_style"][1]["forceInput"] is True
assert inputs["required"]["positive_prompt"][1]["multiline"] is False
assert inputs["required"]["negative_prompt"][1]["multiline"] is False
assert "[sep|name]" in inputs["required"]["positive_prompt"][1]["tooltip"].lower()
def test_legacy_schedule_and_encode_prompt_control_execute_delegates(
@@ -141,6 +143,7 @@ def test_schedule_and_encode_prompt_control_node_schema() -> None:
assert schema.enable_expand is True
assert schema.category == "SimpleSyrup/Conditioning"
assert "global" in schema.description.lower()
assert "[sep|name]" in schema.description.lower()
assert [output.io_type for output in schema.outputs] == [
"MODEL",
"CONDITIONING,CONDITIONING_BATCH",
@@ -189,6 +192,7 @@ def test_schedule_and_encode_prompt_control_input_types() -> None:
assert inputs["optional"]["encode_style"][1]["forceInput"] is True
assert inputs["required"]["positive_prompt"][1]["multiline"] is False
assert inputs["required"]["negative_prompt"][1]["multiline"] is False
assert "[sep|name]" in inputs["required"]["positive_prompt"][1]["tooltip"].lower()
def test_schedule_and_encode_prompt_control_execute_delegates(