Rated results: caption only from the file's own prompt, never the project's; keep every file listed and mark doubtful ratings

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpnF7GwNGMbngU11wjym7P
This commit is contained in:
Claude
2026-10-18 22:24:27 +00:00
parent 63ab3a3ae6
commit 7ef57ba162
4 changed files with 24 additions and 21 deletions
+3 -3
View File
@@ -94,12 +94,12 @@ export function dataStep({ app, session, go, ui }) {
function showRated() {
const all = shownRated();
for (const k of [...ratedPicked]) if (!all.some((r) => keyOf(r) === k)) ratedPicked.delete(k);
ratedGallery.setItems(all.map((r) => ({ id: keyOf(r), label: r.media.filename, thumb: stillOf({ src: { kind: "output", ...r.media }, media_kind: r.media.filename.match(/\.(mp4|webm|mov|mkv|avi)$/i) ? "video" : "image" }), badge: [r.label, clipWords(ui.clips[keyOf(r)])].filter(Boolean).join(" · ") })));
ratedGallery.setItems(all.map((r) => ({ id: keyOf(r), label: r.media.filename, thumb: stillOf({ src: { kind: "output", ...r.media }, media_kind: r.media.filename.match(/\.(mp4|webm|mov|mkv|avi)$/i) ? "video" : "image" }), badge: [r.label, r.doubtful ? "old rating?" : "", clipWords(ui.clips[keyOf(r)])].filter(Boolean).join(" · ") })));
ui.needClips(all.filter((r) => /\.(mp4|webm|mov|mkv|avi)$/i.test(r.media.filename)).map((r) => ({ kind: "output", ...r.media })), () => showRated());
ratedGallery.setValue([...ratedPicked]);
const n = all.length, p = ratedPicked.size;
const other = ui.rated.different || 0;
const left = other ? ` ${other} left out: the file with that name is another video than the one rated (names repeat between ComfyUI instances).` : "";
const other = (ui.rated.results || []).filter((r) => r.doubtful).length;
const left = other ? ` ${other} marked "old rating?": the rating may belong to another video that had this name on another ComfyUI instance. Its caption is still the prompt of the file you see.` : "";
ratedNote.setText((n ? `${n} rated result${n > 1 ? "s" : ""} found in your projects. ${p ? `${p} picked: only those are added.` : "None picked: all are added."}` : "None found yet. Rate results in your projects and they appear here.") + left);
addRated.node.disabled = !n || !open;
}
+3 -3
View File
@@ -491,15 +491,15 @@ test("Results: a run shows the time per step and the time spent in all, restarts
off();
});
test("Data step: results left out because the file is another video are counted, not silent", async () => {
test("Data step: results whose rating may be from another video are marked, not hidden", async () => {
const f = fake();
f.api.trainRated = async () => ({ results: [{ label: "liked", media: { type: "output", subfolder: "", filename: "x.mp4" }, caption: "a red fox" }], counts: { liked: 1 }, different: 3 });
f.api.trainRated = async () => ({ results: [{ label: "liked", media: { type: "output", subfolder: "", filename: "x.mp4" }, caption: "a red fox", doubtful: true }, { label: "liked", media: { type: "output", subfolder: "", filename: "y.mp4" }, caption: "a cat" }], counts: { liked: 2 }, doubtful: 1 });
const { button, off } = await open(f);
button.click();
await wait(60);
const rated = [...document.querySelectorAll("button")].find((b) => /rated|results/i.test(b.textContent) && /Add|Use|Open/i.test(b.textContent));
if (rated) rated.click();
await wait(60);
assert.ok(text().includes("3 left out: the file with that name is another video"), "the count is shown");
assert.ok(text().includes('1 marked "old rating?"'), "the count is shown, and nothing is hidden");
off();
});
+6 -8
View File
@@ -146,19 +146,17 @@ def routes(table, base, web):
roots = train.roots()
here = [(r, store.resolve({"kind": "output", **r["media"]}, roots, train.bin_path)) for r in found]
here = [(r, path) for r, path in here if path] # a rating is not a video: only results still on this machine
mine = [(r, path) for r, path in here if rated.is_this_video(r, path)] # ...and the same video: a project carried to another instance meets other files under the same names
different = len(here) - len(mine)
found = []
for r, path in mine:
made_from = rated.embedded_prompt(path) # the file's own prompt beats what the project kept for it
if made_from:
r = {**r, "prompt_text": made_from, "caption": rated.prompt_caption(made_from, doc_of.get(r["project"]), parts, lambda t: shortcuts.expand(t, seed=1), shortcuts.resolve_variables)}
found.append(r)
for r, path in here:
doubtful = not rated.is_this_video(r, path) # the file may be another video that took the name of one rated on another instance: shown, but marked
made_from = rated.embedded_prompt(path) # only the file's own prompt: what the project kept under this name may belong to another video
r = {**r, "prompt_text": made_from, "caption": rated.prompt_caption(made_from, doc_of.get(r["project"]), parts, lambda t: shortcuts.expand(t, seed=1), shortcuts.resolve_variables)}
found.append({**r, "doubtful": doubtful})
want = req.rel_url.query.get("label")
if want:
found = [r for r in found if r["label"] == want]
counts = {label: sum(1 for r in found if r["label"] == label) for label in rated.LABEL_TITLES}
return web.json_response({"results": found, "counts": counts, "different": different})
return web.json_response({"results": found, "counts": counts, "doubtful": sum(1 for r in found if r["doubtful"])})
@table.post(base + "/clips")
async def _clips(req):
@@ -211,7 +211,7 @@ def test_a_file_made_from_another_prompt_is_not_the_one_that_was_rated(tmp_path)
assert rated.same_video(caption, tmp_path / "gone.png"), "unreadable: not judged"
def test_the_rated_list_leaves_out_files_that_are_other_videos_and_says_how_many(tmp_path, monkeypatch):
def test_the_rated_list_keeps_every_file_but_marks_those_that_may_be_other_videos(tmp_path, monkeypatch):
from aiohttp import web
from aiohttp.test_utils import TestClient, TestServer
from core import config, projects
@@ -238,7 +238,7 @@ def test_the_rated_list_leaves_out_files_that_are_other_videos_and_says_how_many
async with TestClient(TestServer(app)) as c:
return await (await c.get("/m/rated")).json()
got = asyncio.run(go())
assert [r["media"]["filename"] for r in got["results"]] == ["same.png"] and got["different"] == 1 and got["counts"]["liked"] == 1
assert [(r["media"]["filename"], r["doubtful"]) for r in got["results"]] == [("same.png", False), ("clash.png", True)] and got["doubtful"] == 1 and got["counts"]["liked"] == 2
def test_a_file_added_without_words_gets_the_prompt_it_was_made_from(tmp_path, monkeypatch):
@@ -274,7 +274,7 @@ def test_a_file_added_without_words_gets_the_prompt_it_was_made_from(tmp_path, m
assert "typed by hand" in [i["caption"] for i in items] and "" in [i["caption"] for i in items], "no prompt in the file: left empty; a caption already there: kept"
def test_a_rated_result_made_in_this_session_is_captioned_from_its_own_file_and_an_older_rating_on_a_reused_name_is_left_out(tmp_path, monkeypatch):
def test_a_rated_result_is_captioned_from_its_own_file_and_an_older_rating_on_a_reused_name_is_marked(tmp_path, monkeypatch):
import os
import time
from aiohttp import web
@@ -286,9 +286,12 @@ def test_a_rated_result_made_in_this_session_is_captioned_from_its_own_file_and_
_png_with_prompt(tmp_path / "now.png", ["a red fox runs through the snow"], "FunPackEnhancePrompt")
_png_with_prompt(tmp_path / "reused.png", ["a city street at night"], "FunPackEnhancePrompt")
os.utime(tmp_path / "now.png", ((now - 60_000) / 1000,) * 2)
doc = {"scenes": [{"id": "a", "rating": "10", "rated": {"text": "something the project kept that is wrong", "at": now}},
_png_with_prompt(tmp_path / "bare.png", [], "FunPackEnhancePrompt")
os.utime(tmp_path / "bare.png", ((now - 60_000) / 1000,) * 2)
doc = {"scenes": [{"id": "c", "rating": "10", "rated": {"text": "the project's words", "at": now}}, {"id": "a", "rating": "10", "rated": {"text": "something the project kept that is wrong", "at": now}},
{"id": "b", "rating": "10", "rated": {"text": "a red fox runs through the snow", "at": now - 86_400_000}}],
"scene_renders": {"a": {"media": {"type": "output", "subfolder": "", "filename": "now.png"}},
"scene_renders": {"c": {"media": {"type": "output", "subfolder": "", "filename": "bare.png"}},
"a": {"media": {"type": "output", "subfolder": "", "filename": "now.png"}},
"b": {"media": {"type": "output", "subfolder": "", "filename": "reused.png"}}}}
class Project:
@@ -306,5 +309,7 @@ def test_a_rated_result_made_in_this_session_is_captioned_from_its_own_file_and_
async with TestClient(TestServer(app)) as c:
return await (await c.get("/m/rated")).json()
got = asyncio.run(go())
assert [(r["media"]["filename"], r["caption"]) for r in got["results"]] == [("now.png", "a red fox runs through the snow")]
assert got["different"] == 1
assert {r["media"]["filename"]: r["caption"] for r in got["results"]}["bare.png"] == "", "a file with no prompt of its own gets none: the project's words are never used"
got["results"] = [r for r in got["results"] if r["media"]["filename"] != "bare.png"]
assert [(r["media"]["filename"], r["caption"], r["doubtful"]) for r in got["results"]] == [("now.png", "a red fox runs through the snow", False), ("reused.png", "a city street at night", True)]
assert got["doubtful"] == 1