feat: linked input "Studio · Enhanced prompt (else prompt + postfix)"
Studio gains an enhanced_prompt output; the link wires to it. Enhancer off or failed sends prompt + postfix unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b48a6a2ead
commit
85e5173c7c
+29
-3
@@ -9920,6 +9920,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
prompt_enhance_temperature=0.7, prompt_enhance_top_p=0.92,
|
||||
prompt_enhance_max_length=400, prompt_enhance_thinking=False,
|
||||
prompt_enhance_image=None, prompt_enhance_sampling=None,
|
||||
prompt_enhance_output=False,
|
||||
_seed=None, _seed_source="fresh seed", _scene_seeds=None, _velocity_keys=None,
|
||||
batch_variants=1, guess_mode=False, guess_direction="up", guess_range=1.0,
|
||||
guess_freeze_seed=True, movie_editor_scene_ratings=None, scene_segments=None,
|
||||
@@ -10591,6 +10592,26 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
"scene": None, "before": _before, "after": prompt_to_encode,
|
||||
"status": _enhance_status, "image": prompt_enhance_image is not None})
|
||||
|
||||
# Studio's `enhanced_prompt` output, for a node encoding on its own (Editor link
|
||||
# "Enhanced prompt, fallback to prompt + postfix"). The same text that link's
|
||||
# fallback sends — expanded prompt + postfix — enhanced once as a whole.
|
||||
self._v2_enhanced_output = None
|
||||
if prompt_enhance_output:
|
||||
_whole = str((_link_texts or {}).get("full_prompt") or "").strip()
|
||||
if prompt_enhance and _whole:
|
||||
_after, _st = self._v2_enhance_prompt(
|
||||
_enhance_clip, _whole, _enhance_system, cache=_enhance_cache,
|
||||
seed=_enhance_seed, temperature=prompt_enhance_temperature,
|
||||
top_p=prompt_enhance_top_p, max_length=prompt_enhance_max_length,
|
||||
thinking=prompt_enhance_thinking, image=prompt_enhance_image,
|
||||
**_enhance_sampling)
|
||||
print(f"[FunPackVideoRefinerV2] Enhanced prompt output: {_st}")
|
||||
self._v2_enhanced_prompts.append({
|
||||
"scene": None, "before": _whole, "after": _after, "status": _st,
|
||||
"image": prompt_enhance_image is not None})
|
||||
_whole = _after
|
||||
self._v2_enhanced_output = _whole
|
||||
|
||||
cond, meta, encode_status, conditioning_owner = self._v2_conditioning_source(
|
||||
clip,
|
||||
prompt_to_encode,
|
||||
@@ -13517,8 +13538,8 @@ class FunPackStudio:
|
||||
Advisor LLM, LoRA management, and Conditioning Adjust under one UI."""
|
||||
|
||||
CATEGORY = "FunPack"
|
||||
RETURN_TYPES = ("MODEL", "CONDITIONING", "CONDITIONING", "INT", "SAMPLER", "SIGMAS", "SAMPLER", "SIGMAS", "IMAGE", "STRING", "STRING", "STRING", "LATENT")
|
||||
RETURN_NAMES = ("model", "modified_positive", "negative", "seed", "high_pass_sampler", "high_pass_sigmas", "low_pass_sampler", "low_pass_sigmas", "loss_graph", "status", "training_info", "encoded_prompts", "video_latent")
|
||||
RETURN_TYPES = ("MODEL", "CONDITIONING", "CONDITIONING", "INT", "SAMPLER", "SIGMAS", "SAMPLER", "SIGMAS", "IMAGE", "STRING", "STRING", "STRING", "LATENT", "STRING")
|
||||
RETURN_NAMES = ("model", "modified_positive", "negative", "seed", "high_pass_sampler", "high_pass_sigmas", "low_pass_sampler", "low_pass_sigmas", "loss_graph", "status", "training_info", "encoded_prompts", "video_latent", "enhanced_prompt")
|
||||
FUNCTION = "run"
|
||||
DESCRIPTION = (
|
||||
"FunPack Studio - all FunPack refinement tools in one node. "
|
||||
@@ -13823,6 +13844,7 @@ class FunPackStudio:
|
||||
h3_phrase_emphasis=bool(rf.get("h3_phrase_emphasis", False)),
|
||||
h3_phrase_variability=float(rf.get("h3_phrase_variability", 0.0) or 0.0),
|
||||
prompt_enhance=bool(rf.get("prompt_enhance", False)),
|
||||
prompt_enhance_output=bool(rf.get("prompt_enhance_output", False)),
|
||||
prompt_enhance_system=str(rf.get("prompt_enhance_system", "") or ""),
|
||||
prompt_enhance_temperature=float(rf.get("prompt_enhance_temperature", 0.7)),
|
||||
prompt_enhance_top_p=float(rf.get("prompt_enhance_top_p", 0.92)),
|
||||
@@ -13928,7 +13950,11 @@ class FunPackStudio:
|
||||
|
||||
cond = _h3_reconcile_token_tags(cond, "positive")
|
||||
out_negative = _h3_reconcile_token_tags(out_negative, "negative")
|
||||
result = (out_model, cond, out_negative, seed, high_sampler, high_sigmas, low_sampler, low_sigmas, loss_graph, status, training_info, encoded_prompts, video_latent)
|
||||
# Enhanced prompt + postfix, or the plain one when nothing enhanced it.
|
||||
_whole = getattr(refiner, "_v2_enhanced_output", None)
|
||||
if _whole is None:
|
||||
_whole = str((rf.get("link_texts") or {}).get("full_prompt") or "")
|
||||
result = (out_model, cond, out_negative, seed, high_sampler, high_sigmas, low_sampler, low_sigmas, loss_graph, status, training_info, encoded_prompts, video_latent, _whole)
|
||||
enhanced = getattr(refiner, "_v2_enhanced_prompts", None)
|
||||
if enhanced:
|
||||
# History carries every node's ui output; the Editor's /status reads this key.
|
||||
|
||||
@@ -51,6 +51,9 @@ CORE: dict[str, str] = {
|
||||
|
||||
|
||||
# core internal links: core_id -> {input_name: (src_core_id, output_index)}
|
||||
# FunPackStudio's `enhanced_prompt` output (last in RETURN_TYPES).
|
||||
STUDIO_ENHANCED_PROMPT_OUT = 13
|
||||
|
||||
CORE_LINKS: dict[str, dict[str, tuple[str, int]]] = {
|
||||
"studio": {"positive_prompt": ("pos", 0), "negative_prompt": ("neg", 0),
|
||||
"refinement_key_input": ("keyloader", 0)},
|
||||
@@ -820,6 +823,27 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No
|
||||
# linked inputs — they still receive the raw text and expand it themselves.
|
||||
expanded = params.get("expanded") or {}
|
||||
val = expanded[key] if key in expanded else params.get(key)
|
||||
# Enhanced prompt: written by Studio's enhancer DURING the run, so it is a link to
|
||||
# Studio's `enhanced_prompt` output, not a value. Studio sends prompt + postfix
|
||||
# through unchanged when the enhancer is off or fails.
|
||||
if key == "enhanced_prompt":
|
||||
if "studio" in graph:
|
||||
val = ["studio", STUDIO_ENHANCED_PROMPT_OUT]
|
||||
try:
|
||||
_ss_l = json.loads(str(graph["studio"]["inputs"].get("studio_settings") or "{}"))
|
||||
except ValueError:
|
||||
_ss_l = {}
|
||||
if not isinstance(_ss_l, dict):
|
||||
_ss_l = {}
|
||||
_rf_l = _ss_l.get("refiner") if isinstance(_ss_l.get("refiner"), dict) else {}
|
||||
_rf_l["prompt_enhance_output"] = True
|
||||
_ss_l["refiner"] = _rf_l
|
||||
graph["studio"]["inputs"]["studio_settings"] = json.dumps(_ss_l)
|
||||
else:
|
||||
val = expanded.get("full_prompt")
|
||||
report["ignored"].append(
|
||||
f"Linked input '{link.get('name') or key}': the built-in pipeline is off, "
|
||||
f"so there is no Studio to enhance — sent prompt + postfix instead.")
|
||||
# A latent node driven by "Project · Frames" has to receive the SAME number the
|
||||
# sampler is given, or the two disagree about the scene length and the run dies
|
||||
# on the mismatch. Same for a fixed-rate family's fps.
|
||||
@@ -858,10 +882,13 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No
|
||||
f"Linked input '{label}': {graph[sid].get('class_type')} has no widget called "
|
||||
f"'{inp}' — nothing was set. Re-pick it in Models ▸ Linked inputs.")
|
||||
continue
|
||||
graph[sid]["inputs"][inp] = val
|
||||
graph[sid]["inputs"][inp] = list(val) if isinstance(val, list) else val
|
||||
if isinstance(val, list):
|
||||
protected_edges.add((sid, inp))
|
||||
applied.append(f"{graph[sid].get('class_type')}.{inp}")
|
||||
if applied:
|
||||
report["wired"].append(f"linked '{label}' = {val} -> " + ", ".join(applied))
|
||||
shown = "Studio · enhanced_prompt" if isinstance(val, list) else val
|
||||
report["wired"].append(f"linked '{label}' = {shown} -> " + ", ".join(applied))
|
||||
|
||||
port_to_core = _port_index(object_info, core=CORE)
|
||||
|
||||
|
||||
@@ -772,7 +772,8 @@
|
||||
const tag = el("div", "insp-tag"); tag.textContent = "Exposed controls"; wrap.append(tag);
|
||||
const SRC_LBL = { frame_rate: "Project FPS", num_frames_per_scene: "Project Frames",
|
||||
width: "Project Width", height: "Project Height", prompt: "Project Prompt",
|
||||
negative_prompt: "Project Negative prompt", seed: "Project Seed" };
|
||||
negative_prompt: "Project Negative prompt", seed: "Project Seed",
|
||||
full_prompt: "Project Prompt + postfix", enhanced_prompt: "Studio Enhanced prompt" };
|
||||
linkItems.forEach((l) => {
|
||||
if (l.source === "editor") {
|
||||
const note = el("div", "lib-sub"); note.textContent = `← ${SRC_LBL[l.editor_key] || l.editor_key}`;
|
||||
|
||||
@@ -1941,6 +1941,9 @@
|
||||
{ key: "anchor", label: "Project · Anchor text (prepended)", kinds: ["string"] },
|
||||
{ key: "postfix", label: "Project · Postfix (appended)", kinds: ["string"] },
|
||||
{ key: "full_prompt", label: "Project · Prompt + postfix (what Studio encodes)", kinds: ["string"] },
|
||||
// Written by Studio's enhancer during the run, so the builder wires it to Studio's
|
||||
// enhanced_prompt output rather than pasting a value. Enhancer off → prompt + postfix.
|
||||
{ key: "enhanced_prompt", label: "Studio · Enhanced prompt (else prompt + postfix)", kinds: ["string"] },
|
||||
{ key: "seed", label: "Project · Seed", kinds: ["int", "float"] },
|
||||
{ key: "frame_rate", label: "Project · FPS", kinds: ["int", "float"] },
|
||||
{ key: "num_frames_per_scene", label: "Project · Frames", kinds: ["int", "float"] },
|
||||
@@ -1995,7 +1998,7 @@
|
||||
|
||||
if (link.source === "editor") {
|
||||
const srcLbl = (EDITOR_SOURCES.find((s) => s.key === link.editor_key) || {}).label || link.editor_key;
|
||||
const isText = ["prompt", "negative_prompt", "anchor", "postfix", "full_prompt"].includes(link.editor_key);
|
||||
const isText = ["prompt", "negative_prompt", "anchor", "postfix", "full_prompt", "enhanced_prompt"].includes(link.editor_key);
|
||||
// "the same text Studio would" is only true of full_prompt. Studio appends the postfix
|
||||
// to every scene itself, so a node linked to the global prompt encodes strictly less --
|
||||
// silently, and the postfix is where audio and style directions usually live.
|
||||
@@ -2004,6 +2007,9 @@
|
||||
note = " Shortcuts and $variables are expanded first";
|
||||
if (link.editor_key === "full_prompt") {
|
||||
note += ", so the node encodes the same text Studio would.";
|
||||
} else if (link.editor_key === "enhanced_prompt") {
|
||||
note = " Prompt + postfix, rewritten by the enhancer (Composer ▸ Enhance). Enhancer off"
|
||||
+ " or failed: prompt + postfix unchanged.";
|
||||
} else if (link.editor_key === "prompt") {
|
||||
const pj = window.Store?.get().project || {};
|
||||
const hasPostfix = pj.postfix_enabled !== false && String(pj.postfix || "").trim();
|
||||
|
||||
@@ -1580,3 +1580,20 @@ def test_studio_receives_the_texts_linked_nodes_were_given():
|
||||
settings = json.loads(graph["studio"]["inputs"]["studio_settings"])
|
||||
assert settings["refiner"]["link_texts"]["prompt"] == "cinematic rain"
|
||||
assert settings["refiner"]["link_texts"]["full_prompt"] == "cinematic rain now"
|
||||
|
||||
|
||||
def test_enhanced_prompt_link_wires_to_studios_output():
|
||||
import json
|
||||
oi = {**OI, "R2V": {"input": {"required": {"prompt": ["STRING", {"default": ""}]}},
|
||||
"output": ["CONDITIONING"], "output_name": ["CONDITIONING"]}}
|
||||
oi["FunPackStudio"] = {**OI["FunPackStudio"],
|
||||
"output": OI["FunPackStudio"]["output"] + ["STRING"],
|
||||
"output_name": OI["FunPackStudio"]["output_name"] + ["enhanced_prompt"]}
|
||||
models = {"slots": [{"id": "r2v", "node_class": "R2V", "inputs": {}, "wires": {}}],
|
||||
"links": [{"id": "l", "name": "p", "source": "editor", "editor_key": "enhanced_prompt",
|
||||
"members": [{"slotId": "r2v", "input": "prompt"}]}]}
|
||||
graph, report = builder.build(oi, models, {**PARAMS, "expanded": {"full_prompt": "a cat, soft"}})
|
||||
assert graph["slot_r2v"]["inputs"]["prompt"] == ["studio", builder.STUDIO_ENHANCED_PROMPT_OUT]
|
||||
assert oi["FunPackStudio"]["output_name"][builder.STUDIO_ENHANCED_PROMPT_OUT] == "enhanced_prompt"
|
||||
rf = json.loads(graph["studio"]["inputs"]["studio_settings"])["refiner"]
|
||||
assert rf["prompt_enhance_output"] is True
|
||||
|
||||
@@ -395,7 +395,7 @@ def test_the_image_reaches_the_enhancer():
|
||||
|
||||
def test_both_paths_hand_over_the_enhancer_image():
|
||||
src = _refine_source()
|
||||
assert src.count("image=prompt_enhance_image") == 2
|
||||
assert src.count("image=prompt_enhance_image") == 3
|
||||
assert "thinking=prompt_enhance_thinking, image=source_image" not in src
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ def test_studio_gates_the_image_on_its_own_switch():
|
||||
|
||||
def test_every_rewrite_is_reported_for_the_composer():
|
||||
src = _refine_source()
|
||||
assert src.count("self._v2_enhanced_prompts.append(") == 2
|
||||
assert src.count("self._v2_enhanced_prompts.append(") == 3
|
||||
import inspect
|
||||
assert '{"ui": {"funpack_enhanced": enhanced}, "result": result}' in inspect.getsource(C.FunPackStudio.run)
|
||||
|
||||
@@ -437,7 +437,7 @@ def test_the_repair_advisor_keeps_its_old_sampling(studio):
|
||||
def test_a_fixed_enhancer_seed_overrides_the_run_seed():
|
||||
src = _refine_source()
|
||||
assert '_enhance_seed = int(_enhance_sampling.pop("seed", 0) or 0) or seed' in src
|
||||
assert src.count("seed=_enhance_seed") == 2
|
||||
assert src.count("seed=_enhance_seed") == 3
|
||||
|
||||
|
||||
# ── how the instructions reach the model ─────────────────────────────────────
|
||||
|
||||
@@ -2295,3 +2295,29 @@ def test_h3_scale_digits_are_valid_combo_choices():
|
||||
whatever the default happens to be."""
|
||||
for n in range(1, 11):
|
||||
assert str(n) in V2_RATING_LABELS
|
||||
|
||||
|
||||
def _enhanced_output(tmp_path, **kw):
|
||||
refiner = FunPackVideoRefinerV2()
|
||||
refiner._v2_state_path = lambda refinement_key: str(tmp_path / "state.json")
|
||||
clip = GeneratingClip("a vivid cat on a sunlit sill")
|
||||
refiner.refine_v2("a cat", clip, "Unrated", "enh-out-test",
|
||||
link_texts={"full_prompt": "a cat, soft light", "prompt": "a cat"}, **kw)
|
||||
return refiner._v2_enhanced_output, clip
|
||||
|
||||
|
||||
def test_enhanced_prompt_output_enhances_prompt_plus_postfix(tmp_path):
|
||||
out, clip = _enhanced_output(tmp_path, prompt_enhance=True, prompt_enhance_output=True)
|
||||
assert out == "a vivid cat on a sunlit sill"
|
||||
assert any(t.endswith("a cat, soft light") for t, _ in clip.tokenize_calls)
|
||||
|
||||
|
||||
def test_enhanced_prompt_output_falls_back_to_prompt_plus_postfix(tmp_path):
|
||||
out, clip = _enhanced_output(tmp_path, prompt_enhance=False, prompt_enhance_output=True)
|
||||
assert out == "a cat, soft light" and clip.generate_kwargs == {}
|
||||
|
||||
|
||||
def test_enhanced_prompt_output_costs_nothing_when_unlinked(tmp_path):
|
||||
out, clip = _enhanced_output(tmp_path, prompt_enhance=True)
|
||||
assert out is None
|
||||
assert not any(t.endswith("a cat, soft light") for t, _ in clip.tokenize_calls)
|
||||
|
||||
Reference in New Issue
Block a user