Files
olivv-cs-ComfyUI-FunPack/app/ui/features/rating/index.js
T
digital-garbageandClaude Opus 5.5 57c74268cd fix: ratings set the odds (each like up, each dislike down, a run of dislikes much more); a redraw never commits mid-swap
A rating keeps the prompt that made the render and when; matcher replays the expander exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:13:50 +03:00

60 lines
3.5 KiB
JavaScript

// "Scene N · Rate…": say how a render turned out. FunPack's taste learns from it on the next Generate.
import { composer as c } from "../../composer/composer.js";
import { inPlace } from "../../shell/place.js";
import { genUnitId, isGenerative, unitRoot } from "../../shell/scenes.js";
import { CHOICES, FORGET, nameOf, tasteOf } from "./choices.js";
/** What to say when a rating taught nothing, or "" when it did. The run captured nothing, a learner is on and no key is
* set: both are said. Any other reason is the server's own. */
export function notTaught(r, ps) {
if (!(r && r.why)) return "";
const taste = ps.modulesById && ps.modulesById().taste;
const keyless = r.reason === "nothing" && taste && ps.useful(taste) && !String((ps.currentValues().taste || {}).key || "").trim();
// two facts, both true, not a claimed cause: the key on screen now may not be the one the run had
return keyless ? "Taste not taught: this run captured nothing, and no Taste key is set. Ratings teach only under a key: name one in Settings ▸ Engine ▸ System ▸ Taste key." : `Taste not taught: ${r.why}.`;
}
export default {
id: "rating",
mount: "timeline.actions",
needs: ["project", "api", "selection", "pipeline"],
setup({ host, app }) {
const p = app.project, sel = app.selection;
const put = inPlace(host);
const target = () => { // the picked clip's generation unit, when it has a render to talk about
const open = p.project, sc = open && open.scenes.find((s) => s.id === sel.focus);
if (!sc || !isGenerative(sc)) return null;
const root = unitRoot(open, genUnitId(sc));
const render = root && (open.scene_renders || {})[root.id];
return render && render.media ? { root, render, no: open.scenes.indexOf(sc) + 1 } : null;
};
const rate = async (value) => {
const t = target();
if (!t) return;
// What was rated and when: the prompt that made the render (the scene's text, for a render that did not keep it); when, for a run of dislikes.
p.edit((pr) => { const s = pr.scenes.find((x) => x.id === t.root.id); if (s) { s.rating = value; s.rated = nameOf(value) ? { text: t.render.text ?? s.text ?? "", at: Date.now() } : {}; } return s; });
const taste = tasteOf(value);
if (!t.render.promptId || taste === null) return; // no run to pair it with, or a word that teaches nothing: it stays a label
const body = taste === "clear" ? { rating: null, axis: null } : taste;
try {
const r = await app.api.rateTaste(t.render.promptId, body.rating, body.axis);
const text = notTaught(r, app.pipeline);
if (text) c.toast.warn({ text });
} catch { /* no taste module here: the rating stays a label */ }
};
const draw = () => {
const t = target();
if (!t) return put();
const now = nameOf(t.root.rating);
const button = c.button.sm({ label: now ? `★ ${now}` : "Rate scene…", tone: now ? "neutral" : "ghost", title: "Rate this scene's render: features that learn from ratings use it on the next generation",
onClick: async () => {
const pick = await c.modal.choice({ title: "How did it turn out?", items: [...CHOICES.map((o) => ({ id: o.value, label: o.label, hint: o.hint })), { id: FORGET, label: "Forget my rating", hint: "Clears it: nothing is learned from this clip." }] }).result;
if (pick) rate(pick);
} });
put(c.text.sm({ text: `Scene ${t.no}` }).node, button.node);
};
draw();
return app.on(draw);
},
};