feat: Composer says when timed/weighted phrases are in the prompt, and that they only work on H3

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-10-03 02:42:45 +03:00
co-authored by Claude Sonnet 5.5
parent 943a8b52f5
commit 196f8cd807
+41
View File
@@ -213,7 +213,10 @@
wrap.append(ta);
wrap.append(el("div", "insp-hint",
"Edits apply to the scenes as you type, and scene edits show here. The anchor is its own field. Shortcuts expand at generation time."));
timedHintEl = el("div", "insp-hint compose-var-hint");
wrap.append(timedHintEl);
composeTextarea = ta;
updateTimedHint();
if (window.ShortcutAutocomplete) window.ShortcutAutocomplete.attach(ta);
if (st.project && window.ShortcutSuggest) titleRow.append(window.ShortcutSuggest.bulb(ta));
@@ -255,6 +258,7 @@
}
let composeTextarea = null;
let varsOpen = false; // Variables panel expanded?
let timedHintEl = null; // timed phrases `[walks left@2.0-3.5]` found in the prompt
let varHintEl = null; // undeclared / cycle hint line under the prompt
// ── templates bar (above the global prompt) ─────────────────────────────────────
@@ -433,8 +437,45 @@
return cycles;
}
// Timed phrases: `[phrase@t0-t1]` / `[phrase:w@t0-t1]`, seconds from the scene start. Mirrors
// h3_token_weights._TIMED — the phrase is only allowed to shape the video inside its window.
const TIMED_RE = /(?<!\\)\[([^\[\]]*?)(?::\s*(-?\d+(?:\.\d+)?))?\s*@\s*(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*\]/g;
function updateTimedHint() {
if (!timedHintEl) return;
const txt = composeTextarea ? composeTextarea.value : "";
const found = [];
for (const m of txt.matchAll(TIMED_RE)) {
const t0 = parseFloat(m[3]), t1 = parseFloat(m[4]);
const phrase = m[1].trim();
if (!phrase) continue;
found.push({ phrase, t0, t1, w: m[2] ? parseFloat(m[2]) : 1, bad: !(t1 > t0) });
}
// (phrase:1.5) — a weight with no window. Mirrors h3_token_weights._WEIGHTED.
const weighted = [...txt.matchAll(/(?<!\\)\(([^():]*?):\s*(-?\d+(?:\.\d+)?)\s*\)|(?<!\\)\[([^\[\]@]*?):\s*(-?\d+(?:\.\d+)?)\s*\]/g)]
.map((m) => ({ phrase: (m[1] ?? m[3]).trim(), w: parseFloat(m[2] ?? m[4]) })).filter((f) => f.phrase);
const PC = window.PipelineCaps;
const h3 = !!(PC && PC.isH3 && PC.isH3(S.get()));
let msg = "";
let warn = false;
if (found.length || weighted.length) {
const bad = found.filter((f) => f.bad);
if (!h3) { msg = "⏱ Timed and weighted phrases are MiniMax H3 only — the words stay, the markup is dropped."; warn = true; }
else if (bad.length) { msg = `⚠ "${bad[0].phrase}" ends before it starts (${bad[0].t0}-${bad[0].t1}s) — window ignored.`; warn = true; }
else {
const parts = found.map((f) => `${f.phrase} ${f.t0}-${f.t1}s${f.w !== 1 ? ` ×${f.w}` : ""}`)
.concat(weighted.map((f) => `${f.phrase} ×${f.w}`));
msg = "⏱ " + parts.join(" · ")
+ (found.length ? " — a window only shapes the picture inside it; keep the subject outside the brackets." : "");
}
}
timedHintEl.textContent = msg;
timedHintEl.classList.toggle("compose-var-warn", warn);
timedHintEl.style.display = msg ? "" : "none";
}
// Live hint under the prompt: cycle warning takes priority, else list $vars used but not declared.
function updateVarHint() {
updateTimedHint();
if (!varHintEl) return;
const declared = new Set((S.projectVariables() || [])
.map((v) => String((v && v.name) || "").replace(/^\$+/, "").trim()).filter(Boolean));