fix: hints and labels now say what the control does (menus, inspector, timeline, takes, ideas, enhancer, context windows, montage, modes); H3 project negative prompt reaches the negative slot

This commit is contained in:
digital-garbage
2026-10-15 12:55:40 +03:00
parent 06d86977cd
commit 23b46559cc
31 changed files with 64 additions and 55 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ export default {
let volume = t.volume != null ? t.volume : 1, start = t.start_sec || 0;
const body = c.region.stack({ gap: "sm", children: [
c.hint.default({ text: separated ? "This clip's own sound. It follows its clip; slide or trim it on the timeline." : "A sound over the cut. Slide and trim it on the timeline." }),
c.field.default({ label: "Volume", control: c.slider.readout({ label: "Volume", value: volume, min: 0, max: 2, step: 0.05, precision: 2, onCommit: (v) => { volume = v; } }) }),
c.field.default({ label: "Volume", hint: "1 = as recorded. Above 1 makes the rendered sound louder; the monitor plays at most 1.", control: c.slider.readout({ label: "Volume", value: volume, min: 0, max: 2, step: 0.05, precision: 2, onCommit: (v) => { volume = v; } }) }),
separated ? null : c.field.default({ label: "Starts at (s)", control: c.number.md({ label: "Starts at", value: start, min: 0, step: 0.1, onChange: (v) => { start = v; } }) }),
].filter(Boolean) });
const win = c.modal.generic({ title: t.label || "Audio track", size: "sm", body });
+6 -4
View File
@@ -3,7 +3,9 @@
import { composer as c } from "../../composer/composer.js";
const NODE = "FunPackEnhancePrompt";
const DIALS = [["temperature", 0.7, 0, 2, 0.05], ["top_p", 0.92, 0, 1, 0.01], ["top_k", 50, 1, 1000, 1], ["min_p", 0.05, 0, 1, 0.01], ["repetition_penalty", 1.3, 1, 2, 0.05], ["presence_penalty", 0, 0, 2, 0.05]];
const DIALS = [["temperature", 0.7, 0, 2, 0.05, "Higher gives more varied wording, lower more predictable."], ["top_p", 0.92, 0, 1, 0.01, "Picks only among the likeliest words whose chances add up to this share."],
["top_k", 50, 1, 1000, 1, "Picks only among this many likeliest words."], ["min_p", 0.05, 0, 1, 0.01, "Drops words much less likely than the likeliest one; higher drops more."],
["repetition_penalty", 1.3, 1, 2, 0.05, "Above 1 discourages repeating words."], ["presence_penalty", 0, 0, 2, 0.05, "Above 0 discourages words already used."]];
export function enhance(app, own) {
const ps = app.pipeline, page = c.region.stack({ gap: "sm" });
@@ -12,7 +14,7 @@ export function enhance(app, own) {
const slot = () => (ps.slots() || []).find((s) => s.node === NODE) || null;
const set = async (name, value) => { const sl = slot(); if (sl) { await ps.save({ inputs: { [sl.id]: { [name]: value } } }); draw(); } };
const field = (label, control, hint) => c.field.default({ label, control, hint });
const num = (label, v, lo, hi, step, name) => field(label, c.number.md({ label, value: v, min: lo, max: hi, step, onChange: (x) => set(name, x) }));
const num = (label, v, lo, hi, step, name, hint) => field(label, c.number.md({ label, value: v, min: lo, max: hi, step, onChange: (x) => set(name, x) }), hint);
function readout() {
if (!last) return [c.label.section({ text: "Last run" }), c.hint.default({ text: "Nothing yet. The rewrite of a run shows here." })];
@@ -32,10 +34,10 @@ export function enhance(app, own) {
c.toggle.default({ label: "Enhance prompt", checked: on, onChange: (x) => set("enabled", x) }),
...(!on ? readout() : [
field("Instructions", c.textarea.md({ label: "Instructions", rows: 6, value: val("instructions", ""), placeholder: defaults ? defaults.instructions : "(built-in instructions)", onCommit: (x) => set("instructions", x) }), "Empty uses the built-in instructions."),
num("Max tokens", val("max_length", 400), 32, 4096, 8, "max_length"),
num("Max tokens", val("max_length", 400), 32, 4096, 8, "max_length", "The longest the rewrite may be."),
c.toggle.default({ label: "Use the picture", hint: "Sends the scene's picture to the model along with the prompt.", checked: val("use_image", true), onChange: (x) => set("use_image", x) }),
c.toggle.default({ label: "Thinking", hint: "Let a thinking model reason first. Its reasoning eats the token budget.", checked: val("thinking", false), onChange: (x) => set("thinking", x) }),
c.toggle.default({ label: "Greedy (always the likeliest word)", checked: greedy, onChange: (x) => set("greedy", x) }),
c.toggle.default({ label: "Greedy (always the likeliest word)", hint: "The same rewrite every time; the dials below are not used.", checked: greedy, onChange: (x) => set("greedy", x) }),
...(greedy ? [] : DIALS.map(([n, d, lo, hi, st]) => num(n, val(n, d), lo, hi, st, n))),
num("Seed", val("seed", 0), 0, 2 ** 53, 1, "seed"),
c.hint.default({ text: "Seed 0 = a fresh rewrite every Generate; any other number repeats the same rewrite." }),
+3 -3
View File
@@ -150,10 +150,10 @@ export default {
if (key === drawn) return; // progress ticks must not rebuild the buttons
drawn = key;
const working = busy || held || g.state().phase === QUEUED || g.state().phase === RUNNING; // also a run this page did not start
const go = c.button.sm({ label: working ? "Generating…" : "▶ Generate", tone: "primary", disabled: working || !open,
const go = c.button.sm({ label: working ? "Generating…" : "▶ Generate", tone: "primary", disabled: working || !open, title: "Generate every scene on the timeline, except left-out scenes and video clips. An earlier render of a scene stays as a take.",
onClick: () => runUnits(unitsOf(p.scenes)) });
const one = c.button.sm({ label: picked > 1 ? `Selected (${picked})` : "Selected", disabled: working || !picked, onClick: () => runUnits(unitsOf(pickedScenes())) });
const stop = c.button.sm({ label: "■ Stop", tone: "danger", onClick: () => { stopped = true; g.cancel(); } });
const one = c.button.sm({ label: picked > 1 ? `Selected (${picked})` : "Selected", title: "Generate only the picked scenes", disabled: working || !picked, onClick: () => runUnits(unitsOf(pickedScenes())) });
const stop = c.button.sm({ label: "■ Stop", tone: "danger", title: "Stop the run that is going", onClick: () => { stopped = true; g.cancel(); } });
stop.node.hidden = !working;
put(go.node, one.node, stop.node);
}
+1 -1
View File
@@ -87,7 +87,7 @@ export default {
window.addEventListener("scroll", onMove, true); window.addEventListener("resize", onMove);
app.editorSettings.push(() => c.region.stack({ gap: "sm", children: [
c.toggle.default({ label: "Shortcut ideas", hint: "A 💡 beside prompt boxes lights up when shortcut categories in your library aren't used in the prompt. Click it for insertable ideas; it never pops up on its own.",
c.toggle.default({ label: "Shortcut ideas", hint: "Shows a 💡 beside the Story and Prompt boxes while you are in them. Click it for shortcuts to insert: pairings you use, what usually follows the previous shot, and categories this prompt does not use yet.",
checked: on(), onChange: (v) => { p.setPref("suggestions", v); if (!v) closeBulb(); } })] }));
return () => { closeBulb(); clearTimeout(hold); document.removeEventListener("focusin", onFocus); document.removeEventListener("focusout", onBlur); window.removeEventListener("scroll", onMove, true); window.removeEventListener("resize", onMove); };
},
+2 -2
View File
@@ -1,4 +1,4 @@
// "+ Import" on the timeline toolbar: a video from the media bin as a clip of its own, or a sound as an audio track at the playhead.
// "+ From bin" on the timeline toolbar: a video from the media bin as a clip of its own, or a sound as an audio track at the playhead.
import { composer as c } from "../../composer/composer.js";
import { addVideoClip, addAudioTrack } from "../../shell/imports.js";
import { learn } from "../../shell/bin.js";
@@ -42,7 +42,7 @@ export default {
if (seconds == null) c.toast.warn({ text: asset.kind === "video" ? "The file's length could not be read; the clip uses the project's length." : "The file's length could not be read: the lane cannot be trimmed, and plays to the end of the file." });
}
const menu = c.button.menu({ label: "+ Import", tone: "ghost", onClick: () => c.menu.dropdown({ anchor: menu, items: [{ id: "video", label: "Video clip…", disabled: !p.project }, { id: "audio", label: "Audio track…", disabled: !p.project }],
const menu = c.button.menu({ label: "+ From bin", tone: "ghost", onClick: () => c.menu.dropdown({ anchor: menu, items: [{ id: "video", label: "Video clip…", disabled: !p.project }, { id: "audio", label: "Audio track…", disabled: !p.project }],
onPick: (id) => choose(id) }) });
host.append(menu.node);
+4 -4
View File
@@ -70,12 +70,12 @@ export function projectRows(p, slots, api) {
c.hint.default({ text: `≈ ${per.toFixed(2)} s per shot` }),
size.length ? c.field.row({ fields: size.map((x) => x.field) }) : null,
...custom(p, open),
c.field.default({ label: "Final render size", control: c.select.md({ label: "Final render size", options: sizes(open), value: open.export_size_from || "",
c.field.default({ label: "Final render size", hint: "The size of the exported video: the first clip's, the project's, or any rendered clip's own.", control: c.select.md({ label: "Final render size", options: sizes(open), value: open.export_size_from || "",
onChange: (v) => p.setField("export_size_from", v) }) }),
c.field.default({ label: "Start shots", control: c.select.md({ label: "Start shots", options: STARTS, value: open.generation_mode === "t2v" ? "t2v" : "i2v", onChange: (v) => p.setField("generation_mode", v) }) })),
c.field.default({ label: "Start shots", hint: "From an image: a scene starts from its picture, or the last frame of the clip before it. From a prompt: every scene starts without a picture.", control: c.select.md({ label: "Start shots", options: STARTS, value: open.generation_mode === "t2v" ? "t2v" : "i2v", onChange: (v) => p.setField("generation_mode", v) }) })),
c.label.section({ text: "Prompt" }),
card(c.field.default({ label: "Anchor", control: c.textarea.md({ label: "Anchor", value: p.anchor, rows: 2, onInput: (v) => p.setAnchor(v) }) }),
c.field.default({ label: "Negative prompt", control: c.textarea.md({ label: "Negative prompt", value: p.negative, rows: 2, onInput: (v) => p.setNegative(v) }) }),
card(c.field.default({ label: "Anchor", hint: "Put in front of every scene's prompt.", control: c.textarea.md({ label: "Anchor", value: p.anchor, rows: 2, onInput: (v) => p.setAnchor(v) }) }),
c.field.default({ label: "Negative prompt", hint: "What to steer away from. A model that ignores negative prompts (MiniMax H3) uses it only with Shadow negative or Negative erase on.", control: c.textarea.md({ label: "Negative prompt", value: p.negative, rows: 2, onInput: (v) => p.setNegative(v) }) }),
c.toggle.default({ label: "Postfix", hint: "Appended to every scene (a style or quality tag, say).", checked: open.postfix_enabled !== false, onChange: (v) => p.setField("postfix_enabled", v) }),
c.textarea.md({ label: "Postfix", value: p.postfix, rows: 2, disabled: open.postfix_enabled === false, onInput: (v) => p.setPostfix(v) })),
c.collapsible.default({ label: "ADVANCED PROJECT SETTINGS", body: c.region.stack({ gap: "sm", children: [
+3 -3
View File
@@ -52,15 +52,15 @@ export function sceneRows(p, app) {
if (!sc) return [c.emptyState.default({ icon: "▭", title: "No scene", hint: "Add one on the timeline." })];
const cuts = p.scenes.filter((s) => genUnitId(s) === genUnitId(sc)).length > 1;
return [
c.banner.info({ text: "Generated by this project's pipeline", action: { label: "Engine settings →", onClick: () => app.openSettings && app.openSettings("engine") } }),
c.banner.info({ text: "Made with the project's pipeline and Engine settings", action: { label: "Engine settings →", onClick: () => app.openSettings && app.openSettings("engine") } }),
...(sc.removed_from_plan ? [c.banner.info({ text: "Removed from plan — its generated clip stays on the timeline.", action: { label: "Restore to plan", onClick: () => p.edit((pr) => restoreToPlan(pr, sc.id)) } })] : []),
...(cuts ? [c.hint.default({ text: "This scene has editorial cuts — Generate regens the whole uncut scene." })] : []),
...(isSubclip(sc) ? [c.hint.default({ text: "This is a cut: its prompt and source belong to the first part." })] : [card(
c.field.default({ label: "Prompt", control: c.textarea.md({ label: "Prompt", value: sc.text || "", rows: 4, onInput: (v) => p.setText(sc.id, v) }) }),
c.field.default({ label: "Source", control: c.select.md({ label: "Source", options: SOURCES, value: (sc.source || {}).type || "carry",
onChange: (v) => p.setScene(sc.id, "source", { ...(sc.source || {}), type: v }) }) }))]),
c.label.section({ text: "Reference media (v5 pipeline)" }),
card(mediaField(app, p, sc, { label: "Aspect ratio source", hint: "Sets this scene's aspect ratio for generation — the project's own Width/Height set the actual resolution; this image's pixels are not used.",
c.label.section({ text: "Pictures" }),
card(mediaField(app, p, sc, { label: "Aspect ratio source", hint: "The scene's picture. Its shape sets this scene's aspect ratio (the project's Width/Height set the scale). A model that starts from a picture, such as LTX image-to-video, also begins the clip from it; the prompt enhancer can read it.",
key: "source_image", multiple: false, kinds: ["image"], empty: "— choose aspect ratio source —" }),
mediaField(app, p, sc, { label: "References", key: "references", multiple: true, kinds: ["image", "video", "audio"], empty: "+ Add reference" })),
c.label.section({ text: "Length" }),
+3 -3
View File
@@ -66,14 +66,14 @@ export default {
{ id: "delete", label: "Delete Current Project", danger: true, disabled: !p.project }];
},
Edit: () => [{ id: "undo", label: "Undo", hint: "⌘Z", disabled: !p.canUndo }, { id: "redo", label: "Redo", hint: "⇧⌘Z", disabled: !p.canRedo }, { separator: true },
{ id: "add", label: "Add Scene", disabled: !p.project }, { id: "del", label: "Delete Scene", disabled: !sel.ids.length }, { separator: true },
{ id: "left", label: "Move Clip Left", hint: "timeline cut", disabled: !sel.focus }, { id: "right", label: "Move Clip Right", hint: "timeline cut", disabled: !sel.focus }, { separator: true },
{ id: "add", label: "Add Scene", disabled: !p.project }, { id: "del", label: sel.ids.length > 1 ? `Delete ${sel.ids.length} Scenes` : "Delete Scene", disabled: !sel.ids.length }, { separator: true },
{ id: "left", label: "Move Clip Left", hint: "earlier", disabled: !sel.focus }, { id: "right", label: "Move Clip Right", hint: "later", disabled: !sel.focus }, { separator: true },
{ id: "exclude", label: "Toggle Exclude", disabled: !sel.focus }],
View: () => [{ id: "refresh", label: "Refresh Preview" }, { id: "reset", label: "Reset Layout" }],
Help: () => {
const running = Boolean(app.tourOn?.());
return [...(app.has.has("tour") ? [{ id: "tour", label: running ? "Restart tour" : "Start tour" }, { id: "faq", label: "Skip to FAQ" },
...(running ? [{ id: "exit", label: "Exit tour" }] : []), { separator: true }] : []), { id: "welcome", label: "Welcome tour…" }];
...(running ? [{ id: "exit", label: "Exit tour" }] : []), { separator: true }] : []), { id: "welcome", label: "Welcome window…" }];
},
};
const off = app.on((what) => { if (what === "project.load") actions.load(); });
+2 -2
View File
@@ -28,8 +28,8 @@ export default {
c.field.default({ label: "Lead clip (plays in order)", control: c.select.md({ label: "Lead clip", value: lead, options: list.map((s) => ({ value: s.id, label: label(s) })), onChange: (v) => { lead = v; pool.delete(v); draw(); } }) }),
c.field.default({ label: "Cutaway pool (random draw, in order within each clip)", control: c.checklist.default({ label: "Cutaway pool", values: [...pool],
items: list.filter((s) => s.id !== lead).map((s) => ({ value: s.id, label: label(s) })), onChange: (v) => { pool.clear(); v.forEach((x) => pool.add(x)); } }) }),
c.field.default({ label: "Segment length (frames)", control: c.number.md({ label: "Segment length", value: seg, min: 9, step: 1, precision: 0, onChange: (v) => { seg = v; } }) }),
c.field.default({ label: "Acceleration", hint: "1.0 = constant, lower = faster cuts toward the end", control: c.slider.readout({ label: "Acceleration", value: decay, min: 0.5, max: 1, step: 0.05, onCommit: (v) => { decay = v; } }) }),
c.field.default({ label: "Segment length (frames)", hint: "How long the lead clip's first piece and each cutaway piece are.", control: c.number.md({ label: "Segment length", value: seg, min: 9, step: 1, precision: 0, onChange: (v) => { seg = v; } }) }),
c.field.default({ label: "Acceleration", hint: "1.0 = equal segments; lower = the lead clip's segments get shorter toward the end", control: c.slider.readout({ label: "Acceleration", value: decay, min: 0.5, max: 1, step: 0.05, onCommit: (v) => { decay = v; } }) }),
]);
draw();
const win = c.modal.generic({ title: "Auto Montage", subtitle: "Cuts the lead clip into shrinking segments and randomly inserts cutaways between them.", size: "md", body });
+1 -1
View File
@@ -29,7 +29,7 @@ export default {
} catch (err) { tell(err.message); } finally { busy = false; draw(); }
}
function draw() {
const next = c.button.sm({ label: busy ? "Rendering…" : "⧉ Render", tone: "render", disabled: busy || !p.project, onClick: render }).node;
const next = c.button.sm({ label: busy ? "Rendering…" : "⧉ Render", tone: "render", title: "Join the generated clips into one video and save it in the media bin", disabled: busy || !p.project, onClick: render }).node;
if (node) node.replaceWith(next); else host.append(next);
node = next;
}
+1 -1
View File
@@ -40,7 +40,7 @@ export default {
};
app.pipeline.ensureLoaded().then(offerShot);
app.pipeline.subscribe(offerShot);
add("system", "Updates & ComfyUI", "Server connection, FunPack code updates, pipeline health.", "⟳", "good",
add("system", "Updates & ComfyUI", "Update FunPack, switch its branch, roll back, or restart ComfyUI.", "⟳", "good",
system(app), "System", "update git branch restart rollback");
add("customnodes", "Custom Nodes", "Install, update and remove ComfyUI node packs.", "⧉", "neutral",
packs(app), "System", "custom nodes packs install");
+1 -1
View File
@@ -38,7 +38,7 @@ export const taste = (app) => function mount() {
page.set([
error ? c.banner.warn({ text: `Could not read the keys: ${error}` }) : null,
keys && !keys.length && !error ? c.emptyState.default({ icon: "✦", title: "No keys yet", hint: "A key appears once you rate a render with learning on." }) : null,
...(keys || []).map((name) => c.settingsRow.default({ label: name, hint: "Refinement key", control: c.toolbar.default({ items: [
...(keys || []).map((name) => c.settingsRow.default({ label: name, hint: "Taste key: what your ratings taught under this name", control: c.toolbar.default({ items: [
c.button.sm({ label: "⤓ Export", tone: "ghost", onClick: () => exportKey(name) }),
c.button.sm({ label: "Delete", tone: "danger", onClick: () => remove(name) })] }) })),
c.label.section({ text: "Bring a key here" }),
+1 -1
View File
@@ -31,7 +31,7 @@ export default {
const items = list.map((t, i) => ({ id: String(i), label: `Take ${i + 1}`, thumb: still(t), icon: "▶", badge: mark(i === at ? takesOf(pr, sc).head.rating : t.rating) || undefined }));
return [c.label.section({ text: "Takes" }),
c.gallery.adaptive({ id: "takes", items, selection: [String(at)], cols: 3, empty: "", onActivate: (it) => p.edit((x) => pickTake(x, x.scenes.find((s) => s.id === sc.id), Number(it.id))) }),
c.field.default({ label: `Take ${at + 1} of ${list.length}`, hint: "Step through the renders of this scene; the one showing is the one on the clip. Rate each, the best stays.",
c.field.default({ label: `Take ${at + 1} of ${list.length}`, hint: "Step through the renders of this scene; the one showing is the one on the clip. Each take keeps its own rating.",
control: c.toolbar.default({ items: [c.button.sm({ label: "‹ Earlier", disabled: at === 0, onClick: go(-1) }), c.button.sm({ label: "Later ›", disabled: at === list.length - 1, onClick: go(1) })] }) })];
},
};
+2 -2
View File
@@ -80,7 +80,7 @@ const tools = {
const open = p.project, ids = open ? sel.ids : [];
put(c.hint.default({ text: `${clock(head.at)} / ${clock(open ? totalSeconds(open) : 0)}` }).node,
btn("+ Add", () => { const sc = p.edit((pr) => edits.addScene(pr)); if (sc) p.select(sc.id); }, !open),
btn("Split", split, !ids.length, "Split the selected clip at the playhead"),
btn("Split", split, !ids.length, "Cut the selected clip at the playhead (in the middle when the playhead is elsewhere)"),
btn("Remove", () => ids.forEach((id) => p.edit((pr) => edits.removeScene(pr, id))), !ids.length),
c.chip.neutral({ label: ids.length ? `${ids.length} clip${ids.length > 1 ? "s" : ""} selected` : "no clip selected" }).node);
};
@@ -97,7 +97,7 @@ const zoom = {
needs: ["say"],
setup({ host, app }) {
const b = (label, what, title) => c.button.sm({ label, tone: "ghost", title, onClick: () => app.say(what) }).node;
const keys = c.hint.default({ text: "J K L · S split · I O in/out · + − zoom" }).node;
const keys = c.hint.default({ text: "J back 1 s · K pause · L play · S split · I / O clip start / end · + − zoom" }).node;
keys.classList.add("fp-keyhint"); // the first thing to go when the row runs out of room
host.append(keys,
b("−", "zoom.out", "Zoom out"), b("+", "zoom.in", "Zoom in"), b("fit", "zoom.fit", "Fit the cut to the window"));
+2 -2
View File
@@ -1,5 +1,5 @@
// The guided tour: a walk along the screen, one callout per part, on whatever project is open (it changes nothing).
// Help ▸ Welcome tour / Restart tour start it, Exit tour ends it, Skip to FAQ jumps to the last callout.
// Help ▸ Start tour / Restart tour start it, Exit tour ends it, Skip to FAQ jumps to the last callout.
import { composer as c } from "../../composer/composer.js";
const zone = (n) => () => document.querySelectorAll("section.cx-zone")[n];
@@ -11,7 +11,7 @@ const STEPS = [
{ at: zone(2), side: "left", title: "Properties", text: "Project settings (size, length, postfix) and the picked scene's prompt, source, references and length." },
{ at: zone(3), side: "top", title: "Timeline", text: "One clip per scene. Drag the edges to trim, press S to split, Space to play. Lanes below carry separated sound." },
{ at: button("◆ Composer"), side: "top", title: "Composer", text: "Write the whole story in one box, cut at the cut word; keep templates, $variables and shortcuts; chat with the prompt enhancer." },
{ at: button("▶ Generate"), side: "top", title: "Generate", text: "Makes every scene that has no render yet. “Generate this scene” in Properties makes just the picked one; rate a result so FunPack learns your taste." },
{ at: button("▶ Generate"), side: "top", title: "Generate", text: "Makes every scene on the timeline, except left-out scenes and video clips; an earlier render of a scene stays as a take. “Generate this scene” in Properties makes just the picked one; rate a result so FunPack learns your taste." },
{ at: () => document.querySelector(".cx-frame-main"), side: "bottom", title: "FAQ", text: "Nothing happens? Check the ComfyUI chip in the status bar and the Log window. Wrong look? Settings ▸ Appearance. Lost a layout? View ▸ Reset Layout. Lost a project? File ▸ Open recent. That is the tour: Help ▸ Restart tour runs it again." },
];
+2 -2
View File
@@ -1,4 +1,4 @@
// The welcome screen: shown the first time this browser opens the app, and from Help ▸ Welcome tour.
// The welcome screen: shown the first time this browser opens the app, and from Help ▸ Welcome window.
import { composer as c } from "../../composer/composer.js";
import { list } from "../../shell/project.js";
@@ -38,7 +38,7 @@ export default {
const recent = app.project.project;
const done = () => { markSeen(); if (win) win.close("done"); };
const tiles = c.region.stack({ gap: "sm", children: [
card("Update", "Check this install for a newer version", () => app.maintenance.update()), card("Switch branch", "Pick another branch", () => app.maintenance.switchBranch()),
card("Update", "Pull the newest version from origin and restart ComfyUI", () => app.maintenance.update()), card("Switch branch", "Pick another branch", () => app.maintenance.switchBranch()),
card("Restart ComfyUI", "Reload the server without updating", () => app.maintenance.restart()), card("Rollback update", "Undo the last update", () => app.maintenance.rollback())] });
tiles.node.classList.add("fp-tiles");
win = c.modal.generic({
+3 -1
View File
@@ -105,7 +105,9 @@ def h3_reference_to_video():
"length": 124, "ref_image_size": "match"}},
{"id": "negative", "group": "Preparation", "node": "CLIPTextEncode",
# H3 samples at CFG 1, where a negative prompt does nothing -- wired
# only because FunPackSampler requires SOME conditioning here.
# only because FunPackSampler requires SOME conditioning here. The project's Negative
# prompt lands here so Shadow negative and Negative erase have words to work from.
"roles": [{"at": "project.negative", "input": "text", "label": "Negative prompt"}],
"inputs": {"clip": ["clip", 0], "text": ""}},
{"id": "settings", "group": "Preparation", "node": "FunPackModifierSettings", "inputs": {"settings": "{}"}},
+2 -2
View File
@@ -61,8 +61,8 @@ SETTINGS = {
},
"until_sigma": {
"type": "float", "default": 0.6, "min": 0.0, "max": 1.0, "step": 0.05,
"label": "How long for", "ui": "slider",
"hint": "The starting image comes back sharp once the picture settles.",
"label": "Sharp again at noise", "ui": "slider",
"hint": "The blurred start is used until the noise falls to this level (1 = full noise, 0 = clean). Lower keeps it blurred for longer; 1 never blurs.",
"when": {"enabled": True},
},
}
@@ -37,16 +37,21 @@ SETTINGS = {
"enabled": {"type": "bool", "default": False, "label": "Context windows (long scenes)",
"hint": "Sample a long scene in overlapping windows so it fits the model's memory."},
"length": {"type": "int", "default": 145, "min": 9, "max": 2049, "step": 8, "label": "Window length (frames)",
"hint": "Frames the model sees at once. Shorter uses less memory; longer keeps motion steadier.",
"when": {"enabled": True}},
"overlap": {"type": "int", "default": 40, "min": 0, "max": 512, "step": 8, "label": "Window overlap (frames)",
"hint": "Frames neighbouring windows share. More overlap joins smoother but runs slower; it must be shorter than the window.",
"when": {"enabled": True}},
"schedule": {"type": "enum", "default": "standard_uniform", "label": "Window schedule",
"hint": "standard_uniform: windows shift a little each step. standard_static: the same windows every step. looped_uniform: the end wraps round to the start. batched: back-to-back windows with no overlap.",
"options": [{"value": v, "label": v} for v in SCHEDULES], "when": {"enabled": True}},
"fuse": {"type": "enum", "default": "pyramid", "label": "Window blend",
"hint": "How overlapping windows are mixed: pyramid, relative, overlap-linear fade between neighbours; flat averages them equally.",
"options": [{"value": v, "label": v} for v in FUSES], "when": {"enabled": True}},
"freenoise": {"type": "bool", "default": True, "label": "FreeNoise blending", "when": {"enabled": True},
"hint": "Shuffle the starting noise so neighbouring windows share it."},
"retain_first": {"type": "bool", "default": False, "label": "Pin anchor in every window",
"hint": "Keeps the first frame in every window so the whole clip stays tied to your starting image.",
"when": {"enabled": True}},
}
+3 -3
View File
@@ -47,13 +47,13 @@ SETTINGS = {
{"value": "learned", "label": "Learn from ratings"},
{"value": "manual", "label": "My value"},
],
"label": "Value", "ui": "segmented",
"hint": "Learn: each rating nudges it toward what you liked.",
"label": "How hard", "ui": "segmented",
"hint": "Learn from ratings: each rating nudges how much it commits toward what you liked. My value: you set it below.",
"when": {"enabled": True},
},
"k": {
"type": "float", "default": 1.0, "min": 0.5, "max": 1.6, "step": 0.05,
"label": "k", "ui": "slider",
"label": "Commitment (k)", "ui": "slider",
"hint": "Above 1 commits harder, below 1 loosens. 1 is off.",
"when": {"enabled": True, "mode": "manual"},
},
@@ -31,7 +31,7 @@ SETTINGS = {
"enabled": {
"type": "bool", "default": False,
"label": "Seed search",
"hint": "After 10+ ratings, tries a few seeds for one step each and keeps the one your taste likes.",
"hint": "After 10+ rated clips (some liked, some disliked), tries a few seeds for one step each and keeps the one your taste likes.",
},
"candidates": {
"type": "int", "default": 3, "min": 2, "max": 8,
+2 -2
View File
@@ -46,8 +46,8 @@ SETTINGS = {
{"value": "learned", "label": "Learn from ratings"},
{"value": "manual", "label": "My value"},
],
"label": "Strength", "ui": "segmented",
"hint": "Learn: each rating nudges it toward what you liked.",
"label": "How strong", "ui": "segmented",
"hint": "Learn from ratings: each rating nudges how much the push is toward what you liked. My value: you set it below.",
"when": {"enabled": True},
},
"strength": {
+1 -1
View File
@@ -32,7 +32,7 @@ SETTINGS = {
"enabled": {
"type": "bool", "default": False,
"label": "Taste guidance",
"hint": "After 10+ ratings, nudges the finish of each clip toward what you rated up.",
"hint": "After 10+ rated clips (some liked, some disliked), nudges the finish of each clip toward what you rated up.",
},
"strength": {
"type": "float", "default": 0.02, "min": 0.0, "max": 0.2, "step": 0.005,
+1 -1
View File
@@ -29,7 +29,7 @@ SETTINGS = {
"enabled": {
"type": "bool", "default": False,
"label": "Taste prompt nudge",
"hint": "Shifts the prompt toward what your liked prompts share. Free: no extra passes.",
"hint": "Shifts the prompt toward what your liked prompts share. Needs 3 liked clips. Free: no extra passes.",
},
"strength": {
"type": "float", "default": 0.02, "min": 0.0, "max": 0.1, "step": 0.005,
+1 -1
View File
@@ -30,7 +30,7 @@ SETTINGS = {
"enabled": {
"type": "bool", "default": False,
"label": "Taste attention",
"hint": "Learns from your ratings and shifts what the picture pays attention to.",
"hint": "Learns from your ratings and shifts what the picture pays attention to. Does nothing until the Taste key has 2+ liked and 2+ disliked runs.",
},
"strength": {
"type": "float", "default": 2.0, "min": 0.0, "max": 10.0, "step": 0.05,
+1 -1
View File
@@ -35,7 +35,7 @@ SETTINGS = {
"enabled": {
"type": "bool", "default": False,
"label": "Taste slider",
"hint": "Leans the finish toward what your liked prompts share. Costs 2 extra passes per late step.",
"hint": "Leans the finish toward what your liked prompts share. Needs 3 liked clips. Costs 2 extra passes per late step.",
},
"strength": {
"type": "float", "default": 1.0, "min": -3.0, "max": 3.0, "step": 0.25,
+2 -2
View File
@@ -45,7 +45,7 @@ SETTINGS = {
"enabled": {
"type": "bool", "default": False,
"label": "Shot memory",
"hint": "Starts from the layout of shots you liked, with new details.",
"hint": "Some runs start from the layout of a shot you liked, with new details; ratings tune how often.",
},
"mode": {
"type": "enum", "default": "learned", "options": [
@@ -53,7 +53,7 @@ SETTINGS = {
{"value": "manual", "label": "My value"},
],
"label": "How much", "ui": "segmented",
"hint": "Learn: how strongly to reuse is tuned by your ratings.",
"hint": "Learn from ratings: each rating nudges how much is reused toward what you liked. My value: you set it below.",
"when": {"enabled": True},
},
"amount": {
+3 -3
View File
@@ -51,14 +51,14 @@ SETTINGS = {
{"value": "learned", "label": "Learn from ratings"},
{"value": "manual", "label": "My value"},
],
"label": "Strength", "ui": "segmented",
"hint": "Learn: each rating nudges it toward what you liked.",
"label": "How strong", "ui": "segmented",
"hint": "Learn from ratings: each rating nudges how much the boost is toward what you liked. My value: you set it below.",
"when": {"enabled": True},
},
"alpha": {
"type": "float", "default": 2.0, "min": 0.5, "max": 3.0, "step": 0.1,
"label": "Alpha", "ui": "slider",
"hint": "The paper used 1.2-2.5.",
"hint": "Higher boosts more. The paper used 1.2-2.5.",
"when": {"enabled": True, "mode": "manual"},
},
"block": {
+2 -2
View File
@@ -27,8 +27,8 @@ SETTINGS = {
"style": {
"type": "enum", "default": "natural", "label": "Temporal style",
"options": [{"value": v, "label": v.replace("_", " ")} for v in STYLES],
"hint": "How motion and time feel. accelerate/freeze hold the motion, decelerate/rapid make it punchier, "
"pulse repeats ease-downs, loop joins the end to the start.",
"hint": "How motion feels. natural: unchanged. accelerate, freeze: smoother, held motion (freeze most). decelerate: punchier, bigger steps. "
"pulse: repeated punch-then-ease. rapid start/end: punchy at the start and/or end. loop: the end joins the start.",
},
}
@@ -29,7 +29,7 @@ SETTINGS = {
"enabled": {
"type": "bool", "default": False,
"label": "Early taste guidance",
"hint": "After 10+ ratings, nudges motion and layout too, not just the finish.",
"hint": "After 10+ rated clips (some liked, some disliked), nudges motion and layout too, not just the finish.",
},
"strength": {
"type": "float", "default": 0.02, "min": 0.0, "max": 0.2, "step": 0.005,
+1 -1
View File
@@ -7,7 +7,7 @@ latent is turned toward the average banked direction, without adding energy (the
with a cap so it never wipes the current clip. A thin overlay of remembered motion: the best use is
crossing a remembered action onto a new prompt, and it can re-insert a cut your liked clips had.
"Like the nearest liked prompt" picks one real clip's motion instead of the average of all.
"Like the nearest liked prompt" picks one real clip's motion instead of the average of all (the average when none is close enough).
Banked directions are the size of the whole latent, so a clip is only matched with banked clips of the
same shape (length and frame size); a run of another shape says so and goes unbiased.
Picture only: sound is never touched.