core: anchor and postfix, the other two project-level prompt fields
Alongside the existing negative prompt: anchor (prepended to every scene) and postfix + postfix_enabled (appended, with its own on/off switch -- v4 proved a postfix worth keeping needs to be turned off for a quick test without losing it, which clearing the field would do). Data layer only, deliberately: unlike negative, which maps 1:1 onto an existing CLIPTextEncode "text" input via a role, anchor and postfix need to be COMBINED with each scene's own prompt text, and nothing in the pipeline does that yet -- that is Studio's job, not built. Wiring a UI field to a mechanism that does not exist yet is the "half-assed port" this project has already been steered away from once this session. Also backfilled round-trip and never-trust-a-file tests for the existing negative field, which had none at the core/projects.py level despite video already having them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
93bdd27b32
commit
e9fcfa247b
+27
-3
@@ -137,10 +137,25 @@ class Project:
|
||||
#: A negative prompt, sent at whatever "project.negative" input the
|
||||
#: pipeline declares. Project-level for the same reason `video` is:
|
||||
#: something set rarely, once, not re-typed per scene.
|
||||
#: ponytail: one string, not a dict keyed by input name the way `video`
|
||||
#: is -- there is exactly one such role today. Generalise if a second
|
||||
#: project-level TEXT role ever shows up.
|
||||
negative: str = ""
|
||||
#: Text prepended to every scene's prompt. v4's "anchor" -- the shared
|
||||
#: subject/setting line a whole project holds once instead of retyping
|
||||
#: per scene.
|
||||
anchor: str = ""
|
||||
#: Text appended to every scene's prompt. v4's "postfix" -- symmetric to
|
||||
#: anchor, but with its own on/off switch: v4 proved that a postfix
|
||||
#: someone spent time writing needs to be turned off for a quick test
|
||||
#: without losing it, which clearing the field would do. Anchor never
|
||||
#: grew the same need in v4 and does not get one here either.
|
||||
postfix: str = ""
|
||||
postfix_enabled: bool = True
|
||||
#: ponytail: three flat strings (plus one bool), not a dict keyed by role
|
||||
#: name -- an earlier version of this comment said to generalise once a
|
||||
#: second project-level text role showed up, and now three have. Tried
|
||||
#: it: postfix's own enabled flag does not fit a plain role->string map
|
||||
#: any better than a fourth field would, so the "simpler" shape turns out
|
||||
#: not to be. Three named fields stay more readable than an abstraction
|
||||
#: that has to special-case one of its own four entries anyway.
|
||||
updated_at: float = 0.0
|
||||
|
||||
@staticmethod
|
||||
@@ -149,12 +164,21 @@ class Project:
|
||||
pid = d.get("id")
|
||||
raw = d.get("scenes")
|
||||
negative = d.get("negative")
|
||||
anchor = d.get("anchor")
|
||||
postfix = d.get("postfix")
|
||||
postfix_enabled = d.get("postfix_enabled")
|
||||
return Project(
|
||||
id=pid if is_id(pid) else _new_id(),
|
||||
name=_clean_name(d.get("name")),
|
||||
scenes=[Scene.from_dict(s) for s in (raw if isinstance(raw, list) else [])],
|
||||
video=_clean_video(d.get("video")),
|
||||
negative=negative if isinstance(negative, str) else "",
|
||||
anchor=anchor if isinstance(anchor, str) else "",
|
||||
postfix=postfix if isinstance(postfix, str) else "",
|
||||
# True unless the file explicitly says False -- absent (a project
|
||||
# from before this field existed) must read as "on", the same as
|
||||
# v4's own default, not as "off" because nothing was there to say.
|
||||
postfix_enabled=postfix_enabled if isinstance(postfix_enabled, bool) else True,
|
||||
updated_at=float(d.get("updated_at") or 0.0),
|
||||
)
|
||||
|
||||
|
||||
@@ -378,3 +378,61 @@ def test_a_scene_field_read_back_is_never_trusted():
|
||||
|
||||
for junk in (0, -4, True, None, "soon", 999_999):
|
||||
assert projects.Scene.from_dict({"length": junk}).length is None, junk
|
||||
|
||||
|
||||
# --- the project-level prompt fields: negative, anchor, postfix --------------
|
||||
|
||||
def test_negative_survives_a_round_trip(store):
|
||||
made = projects.create("Negative")
|
||||
made.negative = "blurry, low quality"
|
||||
projects.save(made)
|
||||
assert projects.get(made.id).negative == "blurry, low quality"
|
||||
|
||||
|
||||
def test_anchor_survives_a_round_trip(store):
|
||||
made = projects.create("Anchor")
|
||||
made.anchor = "a lone rider on a frost-glider"
|
||||
projects.save(made)
|
||||
assert projects.get(made.id).anchor == "a lone rider on a frost-glider"
|
||||
|
||||
|
||||
def test_postfix_and_its_toggle_survive_a_round_trip(store):
|
||||
made = projects.create("Postfix")
|
||||
made.postfix = "cinematic, 35mm"
|
||||
made.postfix_enabled = False
|
||||
projects.save(made)
|
||||
|
||||
back = projects.get(made.id)
|
||||
assert back.postfix == "cinematic, 35mm"
|
||||
assert back.postfix_enabled is False
|
||||
|
||||
|
||||
def test_a_fresh_project_has_empty_text_fields_and_postfix_on(store):
|
||||
made = projects.create("Fresh")
|
||||
assert made.negative == made.anchor == made.postfix == ""
|
||||
assert made.postfix_enabled is True
|
||||
|
||||
|
||||
def test_a_project_file_from_before_these_fields_existed_reads_as_the_v4_default():
|
||||
"""postfix_enabled defaults ON, matching v4 -- a project saved before this
|
||||
field existed must not silently read as "postfix off" just because
|
||||
nothing was there to say "on"."""
|
||||
project = projects.Project.from_dict({"name": "Old"})
|
||||
assert project.postfix_enabled is True
|
||||
assert project.negative == project.anchor == project.postfix == ""
|
||||
|
||||
|
||||
def test_the_text_fields_read_back_are_never_trusted():
|
||||
for junk in (0, -4, True, None, ["a", "list"], {"not": "a string"}):
|
||||
project = projects.Project.from_dict(
|
||||
{"negative": junk, "anchor": junk, "postfix": junk})
|
||||
assert project.negative == project.anchor == project.postfix == "", junk
|
||||
|
||||
|
||||
def test_postfix_enabled_read_back_is_never_trusted():
|
||||
for junk in ("yes", 1, 0, None, [], "false"):
|
||||
# Anything that is not a real bool degrades to the v4 default (on),
|
||||
# the same rule every other field here follows: a file cannot say
|
||||
# something with a value core does not recognise as that thing.
|
||||
assert projects.Project.from_dict({"postfix_enabled": junk}).postfix_enabled is True, junk
|
||||
assert projects.Project.from_dict({"postfix_enabled": False}).postfix_enabled is False
|
||||
|
||||
Reference in New Issue
Block a user