Force Easy Gen into a rating-free Studio Prompt-only pipeline

Easy Gen has no rating UI and won't get one — so every Engine setting that
is a no-op without a trained refinement key/rated history is now hidden
there (gated on window.FunPackAppName, the existing Easy-Gen discriminator),
not just harder to find: refinement key, value guidance, steer mode,
reference injection, embed/score/output guidance, taste retrieval,
DynaShift, and sampler-panel velocity bias/rescue. A note points users to
the Cutting Room or the ComfyUI graph for the full learned refiner.

Also enforced at the pipeline level, not just the UI: every Easy Gen
generate call now forces the Studio node into "Prompt only" mode and the
rating-gated Chain Sampler knobs off via node_overrides, so a project
edited in the Cutting Room with these enabled still generates plainly when
run from Easy Gen. Movie Editor is untouched — FunPackAppName is unset
there, so every added gate evaluates false.
This commit is contained in:
digital-garbage
2026-07-24 10:50:36 +03:00
parent d4236be5fb
commit cfe606fb95
4 changed files with 72 additions and 16 deletions
+14 -1
View File
@@ -102,6 +102,19 @@
}
};
// Easy Gen has no rating UI, so Studio must never apply learned/rated steering here —
// forced at the graph level (not just hidden in Engine settings) so a project edited in
// the Cutting Room with these on still generates plainly when run from Easy Gen.
// "studio"/"sampler" are the built-in pipeline's fixed graph keys (see backend/builder.py).
const EASY_MODE_OVERRIDES = [
{ node: "studio", input: "mode", value: "Prompt only" },
{ node: "sampler", input: "embed_guidance", value: false },
{ node: "sampler", input: "score_slider", value: false },
{ node: "sampler", input: "output_guidance", value: false },
{ node: "sampler", input: "dynashift", value: false },
{ node: "sampler", input: "taste_nearest_prompt", value: false },
];
generateBtn.onclick = async () => {
const st = S.get();
if (!st.project) return;
@@ -114,7 +127,7 @@
try {
await S.save();
const resetSession = S.takeResetSessionFlag();
const res = await API.generate(st.project.id, null, null, resetSession, null);
const res = await API.generate(st.project.id, null, null, resetSession, EASY_MODE_OVERRIDES);
preview.watch(st.project.id, res.prompt_id, {
onDone: () => { generateBtn.disabled = false; generateBtn.textContent = "Generate"; },
});
+3 -1
View File
@@ -73,7 +73,9 @@
"Fixed seed for this project's runs."));
wrap.append(sd);
wrap.append(el("div", "sw-hint",
"Refinement key, and the full Studio/Chain Sampler/continuity controls, live under Engine."));
"Studio/Chain Sampler/continuity controls live under Engine. Studio always runs in "
+ "Prompt-only mode here — no refinement key, since Easy Gen has no rating UI to train "
+ "one. For the full learned refiner, use the Cutting Room or the ComfyUI node graph."));
}
const unsub = S.subscribe(render);
+46 -14
View File
@@ -7,6 +7,20 @@
const S = window.Store;
const API = window.MovieEditorAPI;
// Easy Gen has no rating UI at all (by design — see easy_gen/frontend/), so every
// setting that is a no-op without a trained refinement key / rated history is hidden
// there, not just made harder to find. window.FunPackAppName is the same discriminator
// settings_window.js already uses to relabel the About section for a different frontend
// sharing this file; Editor leaves it unset.
const EASY = !!window.FunPackAppName;
const RATING_GATED_KNOBS = new Set([
"embed_guidance", "embed_guidance_source", "embed_guidance_strength",
"score_slider", "score_slider_strength", "taste_nearest_prompt",
"output_guidance", "output_guidance_strength",
"dynashift", "dynashift_strength", "dynashift_threshold",
]);
const RATING_GATED_STUDIO = new Set(["reference_injection", "value_guidance", "steer_mode", "absolute_strength"]);
let _mounted = null; // { scroller (pane, set per render), content (shell root) }
let unsub = null;
let _editing = false;
@@ -75,10 +89,11 @@
const { rf } = parseStudioSettings(p);
let n = 0;
[...STUDIO_REFINER_ESSENTIALS, ...STUDIO_REFINER_ADVANCED].forEach((f) => {
if (EASY && RATING_GATED_STUDIO.has(f.name)) return;
const cur = rf[f.name] != null ? rf[f.name] : f.default;
if (cur !== f.default) n++;
});
if ((p.refinement_key || "default") !== "default") n++;
if (!EASY && (p.refinement_key || "default") !== "default") n++;
return n;
}
@@ -262,6 +277,7 @@
}
function knobVisible(k, si) {
if (EASY && RATING_GATED_KNOBS.has(k.name)) return false;
// dependsOn/dependsValue: single condition (dependsValue absent = plain truthy
// gate, as every existing boolean dependsOn already relies on).
if (k.dependsOn && !_depSatisfied(k.dependsOn, k.dependsValue, si)) return false;
@@ -342,6 +358,7 @@
const si = p.sampler_inputs || {};
let n = 0;
(CHAIN_VIEW_KNOBS[id] || []).forEach((name) => {
if (EASY && RATING_GATED_KNOBS.has(name)) return;
const k = SAMPLER_KNOB_MAP[name];
if (k && si[name] != null && si[name] !== k.default) n++;
});
@@ -431,22 +448,35 @@
const p = st.project;
const { rf } = parseStudioSettings(p);
// Refinement key — project-level (feeds Studio / Chain Sampler / SaveRefinementLatent).
// "default" uses the keyless store; a custom name trains/loads its own key. Shortcuts
// bound to a non-default key layer per-scene training on top of this.
const gKey = group(pane, "Session");
const keyCtrl = el("input"); keyCtrl.type = "text"; keyCtrl.dataset.k = "refinement_key";
keyCtrl.placeholder = "default"; keyCtrl.value = p.refinement_key || "default";
keyCtrl.onchange = () => S.patchProject({ refinement_key: (keyCtrl.value || "").trim() || "default" });
gKey.append(field("Refinement key", keyCtrl, "Named learning session — \"default\" is the keyless store."));
if (!EASY) {
// Refinement key — project-level (feeds Studio / Chain Sampler / SaveRefinementLatent).
// "default" uses the keyless store; a custom name trains/loads its own key. Shortcuts
// bound to a non-default key layer per-scene training on top of this.
const gKey = group(pane, "Session");
const keyCtrl = el("input"); keyCtrl.type = "text"; keyCtrl.dataset.k = "refinement_key";
keyCtrl.placeholder = "default"; keyCtrl.value = p.refinement_key || "default";
keyCtrl.onchange = () => S.patchProject({ refinement_key: (keyCtrl.value || "").trim() || "default" });
gKey.append(field("Refinement key", keyCtrl, "Named learning session — \"default\" is the keyless store."));
}
const gEss = group(pane, "Essentials");
STUDIO_REFINER_ESSENTIALS.forEach((f) => renderStudioRefinerBool(gEss, rf, f));
STUDIO_REFINER_ESSENTIALS.filter((f) => !EASY || !RATING_GATED_STUDIO.has(f.name))
.forEach((f) => renderStudioRefinerBool(gEss, rf, f));
const gAdv = group(pane, "Refinement");
STUDIO_REFINER_ADVANCED.forEach((f) => renderStudioRefinerField(gAdv, rf, f));
const gAdv = group(pane, EASY ? "Prompt shaping" : "Refinement");
STUDIO_REFINER_ADVANCED.filter((f) => !EASY || !RATING_GATED_STUDIO.has(f.name))
.forEach((f) => renderStudioRefinerField(gAdv, rf, f));
pane.append(hintEl("Scene text and transitions come from the timeline. Advisor, LoRA, and batch training remain in the ComfyUI Studio popup on the graph."));
if (EASY) {
pane.append(hintEl(
"Studio runs in Prompt-only mode from Easy Gen — it shapes and splits the prompt "
+ "and passes conditioning through unchanged. Rating-dependent controls (refinement "
+ "key, value guidance, steer mode, reference injection) aren't shown here since "
+ "there's no rating UI in Easy Gen to feed them. For the full learned refiner, use "
+ "the Cutting Room (Movie Editor) or the ComfyUI node graph directly."));
} else {
pane.append(hintEl("Scene text and transitions come from the timeline. Advisor, LoRA, and batch training remain in the ComfyUI Studio popup on the graph."));
}
}
function renderStudioAdjust(pane, st) {
@@ -713,7 +743,9 @@
case "studio_sampler": return renderStudioSampler(pane, st);
case "chain_continuity": return renderChainContinuity(pane, st);
case "chain_timing": return renderChainTiming(pane, st);
case "chain_guidance": return renderChainKnobsView(pane, st, "chain_guidance", "Guidance");
case "chain_guidance": return renderChainKnobsView(pane, st, "chain_guidance", "Guidance",
EASY ? "Rating-dependent guidance (embed guidance, score slider, output guidance, taste retrieval, "
+ "DynaShift) is hidden here — use the Cutting Room or ComfyUI graph for those." : null);
case "chain_decode": return renderChainKnobsView(pane, st, "chain_decode", "Decode");
case "chain_experimental": return renderChainExperimental(pane, st);
default: return renderOverview(pane, st);
+9
View File
@@ -6,6 +6,10 @@
(function () {
const { el } = window.dom;
// Easy Gen has no rating UI — velocity bias / rescue are no-ops without a rated
// velocity bank, so they're hidden there (see engine_settings.js for the same gate).
const EASY = !!window.FunPackAppName;
const SAMPLER_TYPES = ["Hybrid Euler 2S", "Distilled Flow", "KSampler"];
const VELOCITY_BIAS_MODES = ["off", "capture", "apply", "capture_and_apply"];
const MOTION_PULSE_MODES = ["off", "balanced", "aggressive", "custom"];
@@ -125,6 +129,11 @@
// ── velocity-bias / rescue block (shared by Hybrid and Distilled) ──
function renderVelocityBlock(c, dk, sub, save, saveNow) {
if (EASY) {
hint(c, "Velocity bias / rescue mode hidden — both are no-ops without a rated velocity bank, "
+ "which Easy Gen has no UI for. Use the Cutting Room or ComfyUI graph instead.");
return;
}
const vbm = selCtrl(VELOCITY_BIAS_MODES, sub.velocity_bias_mode || "off", dk + "-vbm",
(v) => { sub.velocity_bias_mode = v; saveNow(); });
row(c, "velocity bias mode", vbm);