fix: Reactive focus says why a view step was not offered

This commit is contained in:
digital-garbage
2026-09-30 20:50:58 +03:00
parent 55d6820296
commit c967ca81d2
2 changed files with 20 additions and 6 deletions
+18 -5
View File
@@ -160,6 +160,17 @@
});
}
// Why no shot was offered a view: the honest reason per shot.
function whyNone(scenes, saved) {
const all = scenes.flatMap((sc) => sc.views || []);
if (!all.length) return "a view only opens shots 2 and later, so the prompt needs two or more [Shot N] blocks.";
return all.map((s) => {
if (s.already) return `shot ${s.shot} already states “${s.stated}”`;
if (saved[s.key]) return `shot ${s.shot} is already decided (forget it in Settings ▸ Refinement & Taste)`;
return `no view can show shot ${s.shot}`;
}).join("; ") + ".";
}
// Called by Store before it queues a run. -> false to abort the run.
async function review(project, sceneIds) {
const { rf } = window.StudioSettings.read(project);
@@ -187,15 +198,17 @@
const todo = pending(scenes, savedViews, "views", (s) => !s.already && s.candidates.length);
if (todo.length) steps.push({ kind: "views", title: "Views", page: viewsPage(todo) });
}
// A wanted step with nothing to ask says why, or "no views" looks like "broken".
const whys = [];
if (wantViews && !steps.some((x) => x.kind === "views")) whys.push("Views: " + whyNone(scenes, savedViews));
if (!steps.length) {
// Say why nothing was asked, or "no modal" is indistinguishable from "broken".
const why = !seen.with_shot_marks
? `no [Shot N] found in the ${seen.texts == null ? "" : seen.texts + " "}scene text(s) sent to generation`
: scenes.length ? "every shot is already decided or already has a camera move/view"
: "no shot has a target or view to offer";
window.Store.get().notice = `Reactive focus: nothing to ask — ${why}.`;
? `no [Shot N] found in the ${seen.texts == null ? "" : seen.texts + " "}scene text(s) sent to generation.`
: whys.join(" ") || "every shot is already decided or already has a camera move.";
window.Store.get().notice = `Reactive focus: nothing to ask — ${why}`;
return true;
}
if (whys.length) steps[steps.length - 1].page.nodes.push(el("div", "ov-label", whys.join(" ")));
const res = await ask(steps);
if (!res) return false;
const patch = {}, learn = { decisions: [], views: [] };
+2 -1
View File
@@ -613,8 +613,9 @@ def view_options(text, stats=None):
traits = view_traits(picture)
cands = sorted(((v, _view_weight(v, traits, stats)) for v in allowed_views(traits)),
key=lambda c: -c[1])
stated = VIEW_STATED.search(CUT.sub("", picture))
out.append({"shot": int(m.group(1)), "key": shot_key(picture), "traits": traits,
"already": bool(VIEW_STATED.search(CUT.sub("", picture))),
"already": bool(stated), "stated": stated.group(0) if stated else "",
"candidates": [{"view": v, "score": round(w, 2)} for v, w in cands],
"auto": cands[0][0] if cands else None})
return out