From aea313aefa427e350e38c056b29128ea134b7e2c Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sun, 23 Aug 2026 08:49:09 +0300 Subject: [PATCH] fix(editor): an empty Negative prompt box means empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Studio reads studio_settings.refiner.negative_prompt whenever the wired negative_prompt is blank. The editor writes the wired one and has no field for the stored one, so clearing the visible box handed control to a negative saved in an earlier session, invisibly — a 353-character negative encoding on a run whose Negative prompt field was empty. The editor's box now writes both, empty included. The log says which source supplied the text instead of only how long it was. --- CHANGELOG.md | 7 +++++++ conditioning.py | 11 ++++++++--- movie_editor/backend/builder.py | 5 +++++ movie_editor/tests/test_builder.py | 19 +++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f40ad004..51a26251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,13 @@ Phrases are ranked by weight before the cap, and the prompt is located from `minimax_token_tags` rather than assumed to be the tail of the conditioning. +### Fixed +- **Clearing the editor's Negative prompt box now actually clears it.** Studio falls back to + a negative stored in its own settings whenever the wired one is blank, and the editor has + no field for that stored copy — so emptying the visible box left an earlier negative in + force with nothing on screen saying so. The editor's box is authoritative, empty included. + The log now names which of the two supplied the text. + ### Reverted - **GGUF loading is back to expanding the checkpoint at load.** The quantized path through ComfyUI-GGUF loaded in seconds instead of a minute, and produced a model the sampler could diff --git a/conditioning.py b/conditioning.py index 41cad4c0..e5b7b17d 100644 --- a/conditioning.py +++ b/conditioning.py @@ -14930,10 +14930,15 @@ class FunPackStudio: # not evaluated. It still has to be encoded (the output is a real port, and # negative_erase reads it), but it is a full text-encoder pass and it has to # be visible when accounting for what a run loaded and when. + # WHICH of the two supplied it. They are different boxes in different + # places, and a stored one silently outranking an empty wired one reads + # as the field on screen having no effect. + source = ("the wired negative_prompt input" + if str(negative_prompt or "").strip() + else "Studio's SAVED setting (the wired negative_prompt input " + "is empty)") print(f"[FunPackStudio] encoding the negative prompt with the wired CLIP " - f"({len(effective_negative)} chars)" - + (" — nothing else in this run supplies one" - if negative_conditioning is None else "")) + f"({len(effective_negative)} chars) from {source}") neg_encoded = clip.encode_from_tokens_scheduled(clip.tokenize(effective_negative)) if neg_encoded: out_negative = neg_encoded diff --git a/movie_editor/backend/builder.py b/movie_editor/backend/builder.py index e5317f13..83f8c764 100644 --- a/movie_editor/backend/builder.py +++ b/movie_editor/backend/builder.py @@ -505,6 +505,11 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No # (first run after the user clicks "Reset Studio session"); explicit so it's never # left on from a previous run. _rf["reset_session"] = bool(params.get("reset_session")) + # The editor's Negative prompt box is the ONLY negative in the editor, so it is + # authoritative — including when it is empty. Studio falls back to this stored + # setting whenever the wired negative is blank, which meant clearing the box left a + # negative from a previous session in force with nothing on screen saying so. + _rf["negative_prompt"] = str(params.get("negative_prompt") or "") # Project `$name` variables — Studio resolves them dead-last (after split), per scene. _vars = params.get("variables") if isinstance(_vars, (list, dict)) and _vars: diff --git a/movie_editor/tests/test_builder.py b/movie_editor/tests/test_builder.py index 5a2b4622..afac0eb1 100644 --- a/movie_editor/tests/test_builder.py +++ b/movie_editor/tests/test_builder.py @@ -1251,3 +1251,22 @@ def test_two_ACTIVE_sources_on_one_port_are_still_refused(): models = {k: v for k, v in _two_alternatives("none").items() if k != "full_control"} assert any("already wired from" in e for e in pipeline_wiring.validate_models_wiring(models)) + + +def test_an_empty_editor_negative_clears_studios_saved_one(): + """Studio falls back to studio_settings.refiner.negative_prompt whenever the wired + negative is blank. The editor has no field for that stored copy, so clearing the visible + Negative prompt box left an old negative in force with nothing on screen saying so.""" + import json + graph, _report = builder.build(OI, {"slots": []}, {**PARAMS, "negative_prompt": ""}) + settings = json.loads(graph["studio"]["inputs"]["studio_settings"]) + assert settings["refiner"]["negative_prompt"] == "" + + +def test_the_editor_negative_reaches_studios_settings_too(): + import json + graph, _report = builder.build(OI, {"slots": []}, + {**PARAMS, "negative_prompt": "no hats"}) + settings = json.loads(graph["studio"]["inputs"]["studio_settings"]) + assert settings["refiner"]["negative_prompt"] == "no hats" + assert graph["neg"]["inputs"]["value"] == "no hats"