Keep multiline beat prompts intact

This commit is contained in:
2026-09-02 13:00:25 +00:00
parent 271e7de300
commit 08630964d5
2 changed files with 81 additions and 57 deletions
+42 -57
View File
@@ -312,44 +312,35 @@ def is_directive_line(line):
return bool(re.match(r"\s*(" + "|".join(DIRECTIVE_KEYS) + r")\s*:", line or "", re.I))
def expand_beats(paras, mode="auto"):
"""Turn the prompt's beat PARAGRAPHS into the final beat list. Returns
(beats, note).
Beats are separated by a BLANK line (or a '##' line). That is unambiguous, but
it is also the single easiest thing to get wrong in a textarea: six beats typed
on six consecutive lines are one paragraph, so they render as ONE shot with six
actions crammed into it -- which reads as characters moving at triple speed, not
as a splitting problem.
mode:
'auto' -- blank lines first; any paragraph still holding more than one
content line is then split one beat per line, and says so.
'each line' -- every content line is its own beat. Same result as 'auto'; kept
so the intent can be stated explicitly.
There is deliberately no strict blank-lines-only mode any more. It was the ONE
setting that could silently lose beats: six beats typed as two blocks of three
rendered as two shots, with no note to say why, because the split note is only
written when a paragraph is actually split. Nothing else on the node can change
the beat count, so removing that option removes the whole failure class. A
workflow that still stores 'blank line' falls through to 'auto' below.
Directive lines ('wardrobe:', 'seconds:', 'exit:' ...) are never beats of their
own: they attach to the next content line, or to the previous beat if they
trail the paragraph."""
# Any unrecognized mode means AUTO, never "do nothing". An earlier version fell
# through an if/elif with no else and silently DROPPED every multi-line paragraph
# -- six beats arrived as two shots with four beats simply gone. A stale value on
# this widget (including 'blank line' from a workflow saved before it was removed)
# is enough to trigger it, so the safe branch has to be the default.
if mode not in ("auto", "each line"):
mode = "auto"
out, split_from = [], 0
for p in paras:
lines = [ln for ln in (p or "").splitlines() if ln.strip()]
content = [ln for ln in lines if not is_directive_line(ln)]
if len(content) <= 1:
def expand_beats(paras, mode="auto"):
"""Turn the prompt's beat PARAGRAPHS into the final beat list. Returns
(beats, note).
Beats are separated by a BLANK line (or a '##' line). That is unambiguous, but
it is also easy to misread once a dedicated beat editor exists: inside the
beat editor, one textbox is one beat, so plain newlines inside that textbox
should stay inside the same beat rather than quietly creating new shots.
mode:
'auto' -- preserve each paragraph as one beat.
'each line' -- every content line is its own beat, with directive lines
attaching to the beat they configure.
Directive lines ('wardrobe:', 'seconds:', 'exit:' ...) are never beats of their
own: they attach to the next content line, or to the previous beat if they
trail the paragraph."""
# Any unrecognized mode means AUTO, never "do nothing". Older workflows may
# still store removed values such as 'blank line'; those should preserve whole
# paragraphs instead of unexpectedly splitting every newline into a beat.
if mode not in ("auto", "each line"):
mode = "auto"
if mode == "auto":
return [p.strip() for p in (paras or []) if str(p or "").strip()], ""
out, split_from = [], 0
for p in paras:
lines = [ln for ln in (p or "").splitlines() if ln.strip()]
content = [ln for ln in lines if not is_directive_line(ln)]
if len(content) <= 1:
out.append(p)
else:
split_from += 1
@@ -368,13 +359,12 @@ def expand_beats(paras, mode="auto"):
if out:
out[-1] = out[-1] + NL + NL.join(pending)
else:
out.append(NL.join(pending))
note = ""
if split_from and mode == "auto":
note = (f"{split_from} paragraph(s) held several lines and were split one beat per LINE "
f"-> {len(out)} beats. Separate beats with a BLANK line (or a '##' line) to control "
f"this yourself")
return out, note
out.append(NL.join(pending))
note = ""
if split_from:
note = (f"beat_split '{mode}' split {split_from} multi-line paragraph(s) into "
f"{len(out)} beats")
return out, note
def _garment_side(side):
@@ -6153,18 +6143,13 @@ class H3LongVideos:
"identity fidelity, but reference rows are re-attended EVERY step of "
"EVERY ref-conditioned shot, so on a long chain it is several times "
"slower. Neither ever upscales a small reference."}),
"beat_split": (["auto", "each line"], {"default": "auto",
"tooltip": "How the prompt box becomes beats. Beats are meant to be separated by a "
"BLANK line (or a '##' line) -- but six beats typed on six consecutive "
"lines are ONE paragraph, so they would render as one shot with six actions "
"crammed into it, which looks like everyone is moving at triple speed. "
"auto (default): blank lines first, then any paragraph still holding "
"several lines is split one beat per LINE, and the info output says so. "
"'each line': every line is its own beat -- same result, stated explicitly. "
"Neither can lose a beat. (The old strict 'blank line' option was REMOVED: "
"it was the only setting that could silently collapse beats, and a stored "
"value of it now reads as 'auto'.) Directive lines (wardrobe:, seconds:, exit:) "
"are never beats -- they attach to the beat that follows them."}),
"beat_split": (["auto", "each line"], {"default": "auto",
"tooltip": "How the prompt box becomes beats. Beats are separated by a BLANK line "
"(or a '##' line). auto (default): each paragraph stays one beat, so "
"plain newlines inside a beat remain part of that beat. 'each line': every "
"content line becomes its own beat, with directive lines (wardrobe:, "
"seconds:, exit:, etc.) attaching to the beat they configure. Older "
"removed values such as 'blank line' now behave like 'auto'."}),
"anchor_override": ("STRING", {"multiline": True, "forceInput": True, "default": "",
"tooltip": "Set the persistent look explicitly instead of using the first paragraph. "
"When this is filled in, EVERY paragraph of the prompt box is a beat/shot -- "