Files
olivv-cs-ComfyUI-FunPack/easy_gen/frontend/easy_settings.js
T
digital-garbageandClaude Opus 5 48ff7f0f48 feat: schedules for every sampler, a usable frames field, ALG on any sampler
Three things that were each locked to one implementation detail:

- steps + schedule were a KSampler-branch feature, though every sampler here
  walks a sigma list handed in from outside. They are now pass-level, so
  Distilled Flow and Hybrid Euler 2S can run a named schedule (and the second
  pass can be computed rather than typed) in both frontends and on the node.
- the project's Frames field snapped to the model's grid on every family. On
  H3 that is required, but its arrows moved by 1 and the snap put the value
  straight back; on LTX the builder rounds up by itself, so the snap only
  fought the user. H3 now steps a whole 17 frames per arrow, LTX keeps what
  you type and walks 8k+1 with its arrows.
- ALG lived inside the Distilled Flow step loop, so it was unreachable with a
  stock KSampler. Its blurred/sharp swap is decided by the step's sigma, and
  sigma is an argument of every model call, so a denoiser proxy drives it from
  outside any sampler. New alg_anchor toggle on the Chain Sampler drives the
  in-loop version too, so there is one control wherever you are.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 03:53:11 +03:00

109 lines
5.0 KiB
JavaScript

// Easy Gen's own slimmed "Generation" Settings section — modeled on the
// field(label, control, hint) row pattern in movie_editor/frontend/engine_settings.js,
// but limited to plain Project-level fields (no scene-chain/continuity/overlay
// knobs, which only make sense with multiple scenes in the full Editor).
(function () {
const { el } = window.dom;
function field(labelText, control, hint) {
const row = el("div", "sw-row eng-field");
const main = el("div", "sw-row-main");
main.append(el("div", "sw-row-title", labelText));
if (hint) main.append(el("div", "sw-row-hint", hint));
row.append(main, control);
return row;
}
function numberInput(value, onChange, opts = {}) {
const inp = el("input", "sw-input");
inp.type = "number";
if (opts.min != null) inp.min = opts.min;
if (opts.step != null) inp.step = opts.step;
inp.value = value;
inp.style.width = "90px";
inp.onchange = () => onChange(Number(inp.value));
return inp;
}
function textInput(value, onChange, placeholder) {
const inp = el("input", "sw-input");
inp.type = "text";
inp.value = value || "";
if (placeholder) inp.placeholder = placeholder;
inp.style.width = "220px";
inp.onchange = () => onChange(inp.value);
return inp;
}
function mount(body) {
const S = window.Store;
const wrap = el("div", "sw-stack");
body.append(wrap);
function render() {
body.innerHTML = "";
body.append(wrap);
wrap.innerHTML = "";
const st = S.get();
const p = st.project;
if (!p) {
wrap.append(el("div", "sw-hint", "Open or create a project first."));
return;
}
const commit = async (mut) => { mut(p); await S.save(); };
wrap.append(el("div", "sw-rows-label", "Output"));
const out = el("div", "sw-rows");
out.append(field("Width", numberInput(p.width, (v) => commit((pr) => pr.width = v), { min: 64, step: 16 })));
out.append(field("Height", numberInput(p.height, (v) => commit((pr) => pr.height = v), { min: 64, step: 16 })));
// Frame geometry is per model family: LTX generates on an 8k+1 grid at the project's
// fps, MiniMax H3 on a 17k+5 grid at a fixed 24 fps. Off-grid lengths fail the run
// outright on H3, so there the input snaps; on LTX the builder rounds up by itself, so
// the typed number is kept and only the arrows follow the grid.
const grid = window.PipelineCaps?.frameGrid ? window.PipelineCaps.frameGrid(st)
: { step: 8, base: 1, fps: null, label: "8k+1" };
const spec = window.PipelineCaps?.frameInputSpec
? window.PipelineCaps.frameInputSpec(st) : { step: grid.step, min: grid.base, snap: false };
const snapTo = (v) => (window.PipelineCaps?.snapFramesIfRequired
? window.PipelineCaps.snapFramesIfRequired(v, st)
: Math.max(1, Math.round(Number(v) || 0)));
out.append(field("Frames",
numberInput(p.num_frames_per_scene, (v) => commit((pr) => pr.num_frames_per_scene = snapTo(v)),
{ min: spec.min, step: spec.step }),
spec.snap
? "Length of the generated clip, in frames — MiniMax H3 only generates on its "
+ grid.label + " grid, so this snaps to it and the arrows move " + spec.step + " at a time."
: "Length of the generated clip, in frames. The arrows walk this model's " + grid.label
+ " grid; an off-grid number is rounded up to it when the graph is built."));
out.append(field("Frame rate", numberInput(p.frame_rate, (v) => commit((pr) => pr.frame_rate = v), { min: 1 }),
grid.fps ? "MiniMax H3 always generates at " + grid.fps + " fps; the render is muxed at that rate." : null));
wrap.append(out);
wrap.append(el("div", "sw-rows-label", "Prompt"));
const pr = el("div", "sw-rows");
pr.append(field("Negative prompt", textInput(p.negative_prompt, (v) => commit((proj) => proj.negative_prompt = v)),
"Passed to the negative conditioning for every run."));
wrap.append(pr);
wrap.append(el("div", "sw-hint",
"Studio/Chain Sampler/continuity controls, including Seed, live under Engine ▸ Timing "
+ "& Seed (empty = a new random seed every Generate). 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);
render();
return () => unsub();
}
window.SettingsWindow.register({
id: "generation", group: "Generation", order: 1, title: "Generation",
subtitle: "Output size, length, and negative prompt for this project.",
keywords: "width height frames frame rate negative prompt generation seed engine",
iconBg: "linear-gradient(180deg,#ffb64d,#e07f1f)",
icon: '<svg viewBox="0 0 16 16" width="13" height="13"><path d="M9.2 1.3 3 9h4.1l-1 5.7L12.9 7H8.5l.7-5.7z" fill="#fff"/></svg>',
mount,
});
})();