admin: copy the prompt, and list the renditions derived from the master

This commit is contained in:
gator 2026-09-10 12:22:16 +00:00
parent 30da437300
commit ddf6056d43

View file

@ -111,8 +111,13 @@ function shotHtml(s) {
+ 'after filming — it was NOT used for the video above</div>'
: '')
+ '</div>'
// Copyable on purpose: the same text pasted into the Gemini web app is how
// we tell an artifact of OUR prompt from an artifact of the API path.
+ (s.prompt_sent
? '<details><summary>Exact prompt sent</summary><pre>' + esc(s.prompt_sent) + '</pre></details>'
? '<details><summary>Exact prompt sent</summary>'
+ '<button type="button" class="btn copyp" '
+ 'style="padding:4px 10px;font-size:12px;margin:10px 0">Copy this prompt</button>'
+ '<pre>' + esc(s.prompt_sent) + '</pre></details>'
: '<div class="kv">no prompt recorded (filmed before prompts were stored)</div>')
// The prompt and the request are not the same claim. An earlier version of
// video_render validated its images and then posted only the prompt, which
@ -159,6 +164,21 @@ function projHtml(p) {
+ (p.owner_notes ? '<p class="kv" style="margin-top:10px"><b>notes</b> ' + esc(p.owner_notes) + '</p>' : '')
+ '</div></div>'
+ (p.full_text ? '<details><summary>Full piece as written</summary><pre>' + esc(p.full_text) + '</pre></details>' : '')
// The master is what Veo produced. Everything else is derived from it and
// is disposable; the master is not regenerated and not overwritten.
+ (p.renditions && p.renditions.length
? '<details open><summary>' + p.renditions.length + ' renditions</summary>'
+ p.renditions.map(function (r) {
return '<div class="kv" style="margin:10px 0;padding-left:12px;'
+ 'border-left:3px solid ' + (r.kind === "master" ? 'var(--accent2)' : 'var(--line)') + '">'
+ '<b>' + esc(r.label || r.kind || "") + '</b>'
+ (r.url ? ' &middot; <a href="' + esc(r.url) + '" target="_blank" rel="noopener">open</a>' : '')
+ (r.made_at ? ' &middot; ' + esc(when(r.made_at)) : '')
+ (r.note ? '<br><span style="opacity:.72">' + esc(r.note) + '</span>' : '')
+ '</div>';
}).join("")
+ '</details>'
: '')
+ (p.shots && p.shots.length
? '<details open><summary>' + p.shots.length + ' shots</summary>' + p.shots.map(shotHtml).join("") + '</details>'
: '')
@ -191,6 +211,27 @@ async function load() {
// Delegated, because the list is re-rendered wholesale on every load.
document.getElementById("out").onclick = async function (ev) {
var cp = ev.target.closest ? ev.target.closest(".copyp") : null;
if (cp) {
var pre = cp.parentElement.querySelector("pre");
if (pre) {
try {
await navigator.clipboard.writeText(pre.textContent);
cp.textContent = "Copied";
} catch (e) {
// Clipboard needs a secure context and permission; selecting the
// text is a working fallback rather than a dead button.
var rg = document.createRange();
rg.selectNodeContents(pre);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(rg);
cp.textContent = "Selected — press Ctrl+C";
}
setTimeout(function () { cp.textContent = "Copy this prompt"; }, 2500);
}
return;
}
var b = ev.target.closest ? ev.target.closest("[data-redub]") : null;
if (!b) return;
b.disabled = true;