@officexapp/vidfarm-devcli 0.21.26 → 0.21.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/vidfarm/SKILL.md +26 -6
- package/.agents/skills/vidfarm/references/assets-and-sourcing.md +26 -0
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +27 -6
- package/.agents/skills/vidfarm/references/core-workflows.md +1 -1
- package/.agents/skills/vidfarm/references/editor-workflows.md +44 -2
- package/.agents/skills/vidfarm/references/primitives.md +28 -0
- package/.agents/skills/vidfarm-media/SKILL.md +3 -1
- package/.agents/skills/vidfarm-media/references/tts.md +1 -1
- package/SKILL.director.md +152 -15
- package/SKILL.md +6 -2
- package/clipper.md +3 -2
- package/dist/src/cli.js +409 -19
- package/dist/src/devcli/cost-mode.js +46 -18
- package/dist/src/devcli/sequence.js +619 -0
- package/dist/src/services/sequence-prompts.js +463 -0
- package/package.json +3 -1
- package/public/assets/file-directory-app.js +26 -26
- package/public/assets/homepage-client-app.js +12 -12
- package/public/serve-shells/editor.html +66 -3
- package/public/serve-shells/library-files.html +60 -3
- package/public/serve-shells/library-raws.html +60 -3
- package/public/serve-shells/tools-clipper.html +60 -3
- package/public/serve-shells/tools-image.html +1069 -483
- package/public/serve-shells/tools-video.html +140 -14
|
@@ -591,6 +591,7 @@
|
|
|
591
591
|
background:#fff;border:1px solid var(--rk-border);border-radius:var(--rk-r-full);padding:4px 11px;text-decoration:none;line-height:1.4;
|
|
592
592
|
transition:background var(--rk-dur) var(--rk-ease),border-color var(--rk-dur) var(--rk-ease)}
|
|
593
593
|
.rk-aichat-preview-act:hover{background:var(--rk-gold-tint);border-color:var(--rk-gold-600)}
|
|
594
|
+
.rk-aichat-preview-act[hidden]{display:none}
|
|
594
595
|
|
|
595
596
|
/* ── markdown pipe tables (assistant messages + .md previews) ── */
|
|
596
597
|
.rk-md-tablewrap{max-width:100%;overflow-x:auto;margin:8px 0;border:1px solid var(--rk-border);border-radius:var(--rk-r-lg)}
|
|
@@ -622,6 +623,14 @@
|
|
|
622
623
|
.rk-aichat-file-thumb--markdown,.rk-aichat-file-thumb--text{background:var(--rk-n-50)}.rk-aichat-file-thumb--audio{background:var(--rk-gold-tint2)}
|
|
623
624
|
.rk-aichat-file-play{position:absolute;inset:0;display:grid;place-items:center;font-size:22px;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.55);pointer-events:none}
|
|
624
625
|
.rk-aichat-file-name{font-size:11px;font-weight:600;color:var(--rk-n-600);line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:112px}
|
|
626
|
+
/* "Edit in the tool" pencil — only on image/video cards; opens /tools/image or
|
|
627
|
+
/tools/video in a new tab with this file preloaded as the working file. */
|
|
628
|
+
.rk-aichat-file-open{position:absolute;top:5px;right:5px;width:24px;height:24px;border-radius:var(--rk-r-md);
|
|
629
|
+
display:grid;place-items:center;background:rgba(255,255,255,.94);border:1px solid var(--rk-border);color:var(--rk-ink);
|
|
630
|
+
opacity:0;transition:opacity var(--rk-dur) var(--rk-ease),background var(--rk-dur) var(--rk-ease);z-index:2}
|
|
631
|
+
.rk-aichat-file-open svg{width:13px;height:13px}
|
|
632
|
+
.rk-aichat-file:hover .rk-aichat-file-open,.rk-aichat-file-open:focus-visible{opacity:1}
|
|
633
|
+
.rk-aichat-file-open:hover{background:var(--rk-gold-tint)}
|
|
625
634
|
|
|
626
635
|
/* create_video pipeline: live status row + clickable result links */
|
|
627
636
|
.rk-aichat-flow{display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--rk-text-muted);padding:2px 2px}
|
|
@@ -1378,6 +1387,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
1378
1387
|
</div>
|
|
1379
1388
|
<div class="rk-inpaint-toolbar">
|
|
1380
1389
|
<button type="button" class="rk-btn rk-btn-ghost rk-btn-sm rk-inpaint-startnew" data-ipv-start-new><svg width="14" height="14" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 4v12"/><path d="M4 10h12"/></svg> Start New</button>
|
|
1390
|
+
<button type="button" class="rk-ip-iconbtn" data-ipv-copy-chat aria-label="Copy chat ID" title="Copy chat ID"><svg width="13" height="13" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8.4 11.6a3.2 3.2 0 0 0 4.5 0l2.2-2.2a3.2 3.2 0 0 0-4.5-4.5L9.4 6.1"/><path d="M11.6 8.4a3.2 3.2 0 0 0-4.5 0L4.9 10.6a3.2 3.2 0 0 0 4.5 4.5l1.2-1.2"/></svg></button>
|
|
1381
1391
|
<button type="button" class="rk-ip-iconbtn" data-ipv-settings-button aria-label="Settings" title="Settings"><svg width="16" height="16" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M8.8 3.4 9.4 2h1.2l.6 1.4 1.2.5 1.4-.6.9.9-.6 1.4.5 1.2 1.4.6v1.2l-1.4.6-.5 1.2.6 1.4-.9.9-1.4-.6-1.2.5-.6 1.4H9.4l-.6-1.4-1.2-.5-1.4.6-.9-.9.6-1.4-.5-1.2L4 8.6V7.4l1.4-.6.5-1.2-.6-1.4.9-.9 1.4.6 1.2-.5Z"/><circle cx="10" cy="8" r="2.4"/></svg></button>
|
|
1382
1392
|
<button type="button" class="rk-ip-iconbtn" data-ipv-history-toggle aria-label="Past sessions" title="Past sessions"><svg width="16" height="16" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3.2 10a6.8 6.8 0 1 1 2 4.8"/><path d="M3.2 14.4v-3.6h3.6"/><path d="M10 6.2V10l2.6 1.6"/></svg></button>
|
|
1383
1393
|
</div>
|
|
@@ -1797,7 +1807,14 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
1797
1807
|
sessionTracer = value; tracerAutoPending = false; if(tracerInput) tracerInput.value = value; return value;
|
|
1798
1808
|
}
|
|
1799
1809
|
function resolveTracer(){ if(sessionTracer) return sessionTracer; var v = tracerInput ? tracerInput.value.trim() : ""; return v; }
|
|
1800
|
-
function getTracerValue(){ if(sessionTracer) return sessionTracer; var v = tracerInput ? tracerInput.value.trim() : ""; if(v){ sessionTracer = v; tracerAutoPending = false; return v; } return
|
|
1810
|
+
function getTracerValue(){ if(sessionTracer) return sessionTracer; var v = tracerInput ? tracerInput.value.trim() : ""; if(v){ sessionTracer = v; tracerAutoPending = false; return v; } return commitRandomTracer(); }
|
|
1811
|
+
// A session with nothing to name it from yet (e.g. Copy chat ID before the first
|
|
1812
|
+
// prompt) gets a plain id — seeding nameTracerFromSeed("video-edit") here produced
|
|
1813
|
+
// the doubled "video-edit-video-edit-…".
|
|
1814
|
+
function commitRandomTracer(){
|
|
1815
|
+
var value = "video-edit-" + tracerSuffix();
|
|
1816
|
+
sessionTracer = value; tracerAutoPending = false; if(tracerInput) tracerInput.value = value; return value;
|
|
1817
|
+
}
|
|
1801
1818
|
|
|
1802
1819
|
// ===== provider list =====
|
|
1803
1820
|
function loadProviders(){
|
|
@@ -2186,6 +2203,20 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2186
2203
|
var path = String((entry && entry.path) || "").toLowerCase();
|
|
2187
2204
|
return tid === "primitive:video_generate" || path.indexOf("/primitives/videos/generate") >= 0 || path.indexOf("/primitives/video_generate") >= 0;
|
|
2188
2205
|
}
|
|
2206
|
+
// The user's message for a run-history entry. New turns carry chat_message on the
|
|
2207
|
+
// envelope; older ones are un-composed back out of the instruction we built.
|
|
2208
|
+
function userTextFromEntry(entry){
|
|
2209
|
+
var body = (entry && entry.request && entry.request.body) || {};
|
|
2210
|
+
if(typeof body.chat_message === "string" && body.chat_message.trim()) return body.chat_message.trim();
|
|
2211
|
+
var payload = body.payload || {};
|
|
2212
|
+
var raw = String(payload.prompt || payload.instruction || "");
|
|
2213
|
+
if(!raw) return "";
|
|
2214
|
+
var whole = raw.split("Whole clip: ")[1];
|
|
2215
|
+
if(whole) return whole.split(". Timed region edits:")[0].split(". Match the surrounding lighting")[0].trim();
|
|
2216
|
+
var timed = raw.split("Timed region edits: ")[1];
|
|
2217
|
+
if(timed) return timed.split(". Match the surrounding lighting")[0].trim();
|
|
2218
|
+
return raw.length > 240 ? raw.slice(0, 240) + "\u2026" : raw;
|
|
2219
|
+
}
|
|
2189
2220
|
function extractHistoryItems(entries){
|
|
2190
2221
|
var items = []; var seen = {};
|
|
2191
2222
|
(entries || []).forEach(function(entry){
|
|
@@ -2204,16 +2235,38 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2204
2235
|
});
|
|
2205
2236
|
return items;
|
|
2206
2237
|
}
|
|
2238
|
+
// Set while opening a past session: the next history load restores that session's
|
|
2239
|
+
// conversation state (its last prompt back in the composer, its newest video in the
|
|
2240
|
+
// Result panel) instead of just repainting the rail.
|
|
2241
|
+
var sessionRestorePending = false;
|
|
2242
|
+
function restoreSessionFromEntries(entries){
|
|
2243
|
+
var rows = (entries || []).filter(isVideoEntry)
|
|
2244
|
+
.sort(function(a, b){ return Date.parse(a.createdAt || 0) - Date.parse(b.createdAt || 0); });
|
|
2245
|
+
if(!rows.length){ setStatus("Opened " + resolveTracer() + " \u2014 no messages in it yet.", ""); return; }
|
|
2246
|
+
var last = rows[rows.length - 1];
|
|
2247
|
+
var text = userTextFromEntry(last);
|
|
2248
|
+
if(text && globalPrompt && !globalPrompt.value.trim()) globalPrompt.value = text;
|
|
2249
|
+
// Newest output of the session becomes the visible result.
|
|
2250
|
+
for(var i = rows.length - 1; i >= 0; i--){
|
|
2251
|
+
var urls = historyEntryUrls(rows[i]);
|
|
2252
|
+
if(urls[0]){
|
|
2253
|
+
try { resultVideo.src = absoluteUrl(urls[0]); outputPanel.hidden = false; } catch(_){}
|
|
2254
|
+
break;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
setStatus("Session restored \u2014 " + rows.length + " message" + (rows.length === 1 ? "" : "s") + ". Keep editing to pick up where you left off.", "success");
|
|
2258
|
+
}
|
|
2207
2259
|
function loadHistory(){
|
|
2208
2260
|
var tracer = resolveTracer();
|
|
2209
|
-
if(!tracer){ historyItems = []; reinjectSources(); renderHistory(); return; }
|
|
2261
|
+
if(!tracer){ historyItems = []; reinjectSources(); renderHistory(); sessionRestorePending = false; return; }
|
|
2210
2262
|
if(historyRefresh) historyRefresh.disabled = true;
|
|
2211
2263
|
var endpoint = new URL("/job-runs/history", window.location.origin);
|
|
2212
2264
|
endpoint.searchParams.set("limit", "120"); endpoint.searchParams.set("template_id", "primitive:video_generate"); endpoint.searchParams.set("tracer", tracer);
|
|
2213
2265
|
fetch(endpoint, { credentials: "same-origin" }).then(function(r){ return parseJsonResponse(r).then(function(p){ return { ok: r.ok, p: p }; }); }).then(function(res){
|
|
2214
2266
|
if(!res.ok || (res.p && res.p.error)) throw new Error((res.p && res.p.error) || "Unable to load history.");
|
|
2215
2267
|
historyItems = extractHistoryItems(res.p.entries); reinjectSources(); renderHistory();
|
|
2216
|
-
|
|
2268
|
+
if(sessionRestorePending){ sessionRestorePending = false; restoreSessionFromEntries(res.p.entries); }
|
|
2269
|
+
}).catch(function(err){ sessionRestorePending = false; setHistoryEmpty(err && err.message ? err.message : "Unable to load history."); }).then(function(){ if(historyRefresh) historyRefresh.disabled = false; });
|
|
2217
2270
|
}
|
|
2218
2271
|
function applyCompletedJob(payload){
|
|
2219
2272
|
var out = extractVideoUrl(payload); if(!out) return "";
|
|
@@ -2267,13 +2320,19 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2267
2320
|
setSubmitLoading(true);
|
|
2268
2321
|
var seed = (globalPrompt && globalPrompt.value.trim()) || (masks.find(function(m){ return String(m.prompt || "").trim(); }) || {}).prompt || "video-edit";
|
|
2269
2322
|
var tracer = tracerAutoPending ? nameTracerFromSeed(seed) : getTracerValue();
|
|
2323
|
+
// The message the USER actually wrote (not the compiled instruction). It rides
|
|
2324
|
+
// along on the primitive envelope as chat_message — zod strips it server-side,
|
|
2325
|
+
// but the api-call-history middleware records the raw body, so past sessions can
|
|
2326
|
+
// be titled/replayed by their conversation exactly like /tools/image.
|
|
2327
|
+
var chatMessage = (globalPrompt && globalPrompt.value.trim())
|
|
2328
|
+
|| (masks.map(function(m){ return String(m.prompt || "").trim(); }).filter(Boolean)[0] || "");
|
|
2270
2329
|
var payload = { prompt: instruction, output_format: "mp4", aspect_ratio: getAspectRatio() };
|
|
2271
2330
|
if(frameImgs.length) payload.frame_images = frameImgs;
|
|
2272
2331
|
if(providerSelect && providerSelect.value) payload.provider = providerSelect.value;
|
|
2273
2332
|
if(durationSelect && durationSelect.value) payload.duration = Number(durationSelect.value);
|
|
2274
2333
|
if(audioToggle && audioToggle.checked) payload.generate_audio = true;
|
|
2275
2334
|
setStatus("Creating video job\u2026", "");
|
|
2276
|
-
fetch("/api/v1/primitives/videos/generate", { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ tracer: tracer, payload: payload }) })
|
|
2335
|
+
fetch("/api/v1/primitives/videos/generate", { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ tracer: tracer, payload: payload, chat_message: chatMessage, chat_mode: "video", chat_masked: masks.length > 0 }) })
|
|
2277
2336
|
.then(function(r){ return parseJsonResponse(r).then(function(p){ return { ok: r.ok, p: p }; }); })
|
|
2278
2337
|
.then(function(res){ if(!res.ok || !res.p.job_id) throw new Error(res.p.error || "Unable to create video job."); setStatus("Video job " + res.p.job_id + " started \u2014 generating\u2026", "success"); upsertHistory(makePending(res.p)); return pollJob(res.p.job_id); })
|
|
2279
2338
|
.then(function(){ loadHistory(); loadSessions(); })
|
|
@@ -2306,8 +2365,16 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2306
2365
|
(res.p.entries || []).forEach(function(entry){
|
|
2307
2366
|
if(!isVideoEntry(entry)) return; var tracer = String(entry.tracer || "").trim(); if(!tracer) return;
|
|
2308
2367
|
var urls = historyEntryUrls(entry);
|
|
2309
|
-
if(!groups[tracer]){ groups[tracer] = { tracer: tracer, thumb: "", count: 0, createdAt: entry.createdAt || "" }; order.push(tracer); }
|
|
2310
|
-
var g = groups[tracer];
|
|
2368
|
+
if(!groups[tracer]){ groups[tracer] = { tracer: tracer, thumb: "", title: "", count: 0, createdAt: entry.createdAt || "", firstAt: entry.createdAt || "" }; order.push(tracer); }
|
|
2369
|
+
var g = groups[tracer];
|
|
2370
|
+
// One history entry = one message in this session's conversation.
|
|
2371
|
+
g.count += 1;
|
|
2372
|
+
if(!g.thumb && urls[0]){ try { g.thumb = absoluteUrl(urls[0]); } catch(_){} }
|
|
2373
|
+
// Oldest message names the session (same rule as /tools/image).
|
|
2374
|
+
if(!g.title || Date.parse(entry.createdAt || 0) <= Date.parse(g.firstAt || 0)){
|
|
2375
|
+
var text = userTextFromEntry(entry);
|
|
2376
|
+
if(text){ g.title = text.length > 60 ? text.slice(0, 60) + "\u2026" : text; g.firstAt = entry.createdAt || g.firstAt; }
|
|
2377
|
+
}
|
|
2311
2378
|
if(Date.parse(entry.createdAt || 0) > Date.parse(g.createdAt || 0)) g.createdAt = entry.createdAt;
|
|
2312
2379
|
});
|
|
2313
2380
|
var list = order.map(function(t){ return groups[t]; }).sort(function(a, b){ return Date.parse(b.createdAt || 0) - Date.parse(a.createdAt || 0); });
|
|
@@ -2324,16 +2391,20 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2324
2391
|
if(group.thumb){ var v = document.createElement("video"); v.className = "rk-ip-session-thumb"; v.src = group.thumb + "#t=0.1"; v.muted = true; v.preload = "metadata"; row.appendChild(v); }
|
|
2325
2392
|
else { var ph = document.createElement("span"); ph.className = "rk-ip-session-thumb is-empty"; ph.innerHTML = PLAY_SVG; row.appendChild(ph); }
|
|
2326
2393
|
var meta = document.createElement("div"); meta.className = "rk-ip-session-meta";
|
|
2327
|
-
var title = document.createElement("div"); title.className = "rk-ip-session-title"; title.textContent = group.tracer;
|
|
2394
|
+
var title = document.createElement("div"); title.className = "rk-ip-session-title"; title.textContent = group.title || group.tracer;
|
|
2395
|
+
title.title = group.title ? group.title + " (" + group.tracer + ")" : group.tracer;
|
|
2328
2396
|
var sub = document.createElement("div"); sub.className = "rk-ip-session-sub"; var when = relTimeShort(group.createdAt);
|
|
2329
|
-
sub.textContent = (group.count ? group.count + "
|
|
2397
|
+
sub.textContent = (group.count ? group.count + " message" + (group.count === 1 ? "" : "s") : "session") + (when ? " \u00b7 " + when : "");
|
|
2330
2398
|
meta.appendChild(title); meta.appendChild(sub); row.appendChild(meta); sessionsList.appendChild(row);
|
|
2331
2399
|
});
|
|
2332
2400
|
}
|
|
2333
2401
|
function switchToTracer(tracer){
|
|
2334
2402
|
var value = String(tracer || "").trim(); if(!value) return;
|
|
2335
|
-
|
|
2336
|
-
|
|
2403
|
+
clearUrlParams(); resetWorkspace();
|
|
2404
|
+
sessionTracer = value; if(tracerInput) tracerInput.value = value; tracerAutoPending = false; closeSessions();
|
|
2405
|
+
setStatus("Opening " + value + "\u2026", "");
|
|
2406
|
+
sessionRestorePending = true;
|
|
2407
|
+
loadHistory(); loadSessions();
|
|
2337
2408
|
}
|
|
2338
2409
|
|
|
2339
2410
|
// ===== reset =====
|
|
@@ -2378,6 +2449,13 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2378
2449
|
if(brushInput){ brushInput.addEventListener("input", function(){ if(brushLabel) brushLabel.textContent = brushInput.value; updateBrushCursorSize(); }); }
|
|
2379
2450
|
if(uploadBtn && fileInput){ uploadBtn.addEventListener("click", function(){ fileInput.click(); }); fileInput.addEventListener("change", function(){ if(fileInput.files && fileInput.files[0]) loadSourceFile(fileInput.files[0]); }); }
|
|
2380
2451
|
if(startNewBtn) startNewBtn.addEventListener("click", startNew);
|
|
2452
|
+
// Copy chat ID — the session tracer IS this chat's thread id (what /job-runs/history
|
|
2453
|
+
// groups on and what ?tracer= reopens). An unnamed session mints its id here.
|
|
2454
|
+
var copyChatBtn = document.querySelector("[data-ipv-copy-chat]");
|
|
2455
|
+
if(copyChatBtn) copyChatBtn.addEventListener("click", function(){
|
|
2456
|
+
var tracer = resolveTracer() || getTracerValue();
|
|
2457
|
+
copyText(tracer).then(function(ok){ setStatus(ok ? "Chat ID copied \u2014 " + tracer : "Chat ID: " + tracer, ok ? "success" : ""); });
|
|
2458
|
+
});
|
|
2381
2459
|
if(addMaskBtn) addMaskBtn.addEventListener("click", function(){ addMask(videoEl.currentTime || 0, colorPicker ? colorPicker.value : null); });
|
|
2382
2460
|
if(colorPicker) colorPicker.addEventListener("input", function(){ var m = activeMask(); if(m){ m.color = normalizeHex(colorPicker.value); renderMasks(); redraw(); } });
|
|
2383
2461
|
if(undoBtn) undoBtn.addEventListener("click", function(){ var m = activeMask(); if(m && m.strokes.length){ m.redo.push(m.strokes.pop()); redraw(); syncUndoRedo(); } });
|
|
@@ -2485,7 +2563,7 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
2485
2563
|
function onGenFileEsc(e){ if(e.key === "Escape") closeGenFilePanel(); }
|
|
2486
2564
|
function openGenFilePanel(){
|
|
2487
2565
|
var dir = window.__vidfarmDirectory; if(!dir || !dir.pick){ setGenStatus("File browser isn't available right now.", "error"); return; }
|
|
2488
|
-
dir.pick({ select: "file", roots: ["files","temp","raws"], title: "Attach reference images", hint: "Pick one or more images to guide the generation.", confirmLabel: "Attach", multiple: true,
|
|
2566
|
+
dir.pick({ select: "file", roots: ["files","temp","raws"], title: "Attach reference images", hint: "Pick one or more images to guide the generation \u2014 or drop / upload them from your computer.", confirmLabel: "Attach", multiple: true, allowUpload: true,
|
|
2489
2567
|
onPick: function(picked){ var arr = Array.isArray(picked) ? picked : [picked]; arr.forEach(function(f){ var url = f && (f.url || f.viewUrl); if(url){ try { genRefs.push(absoluteUrl(url)); } catch(_){} } }); renderGenRefs(); } });
|
|
2490
2568
|
}
|
|
2491
2569
|
// Paste or drop image files straight into the composer -> temp upload -> reference.
|
|
@@ -3116,6 +3194,34 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3116
3194
|
}
|
|
3117
3195
|
function fileIcon(kind) { return kind === 'pdf' ? '📕' : kind === 'json' ? '{ }' : kind === 'markdown' ? '📝' : kind === 'audio' ? '🎵' : kind === 'text' ? '📄' : '📄'; }
|
|
3118
3196
|
|
|
3197
|
+
// ── "edit this media" hand-off: dock chat → the matching creative tool ──
|
|
3198
|
+
// Images open /tools/image, videos /tools/video, each with the file preloaded as
|
|
3199
|
+
// that tool's working file (its chat then edits THAT media). Deliberately a fresh
|
|
3200
|
+
// tool session — no ?tracer — so the tool names its own chat from the first prompt.
|
|
3201
|
+
var PENCIL_SVG = '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.6 3.6a1.9 1.9 0 0 1 2.8 2.8L7.7 15.1l-3.6.8.8-3.6z"/></svg>';
|
|
3202
|
+
function toolEditUrl(file) {
|
|
3203
|
+
if (!file || !file.viewUrl) return '';
|
|
3204
|
+
var k = fileKind(file.name, file.contentType);
|
|
3205
|
+
var name = file.name || '';
|
|
3206
|
+
if (k === 'image') return '/tools/image?source_image_url=' + encodeURIComponent(file.viewUrl) + (name ? '&source_image_name=' + encodeURIComponent(name) : '');
|
|
3207
|
+
if (k === 'video') return '/tools/video?source_video_url=' + encodeURIComponent(file.viewUrl) + (name ? '&source_video_name=' + encodeURIComponent(name) : '');
|
|
3208
|
+
return '';
|
|
3209
|
+
}
|
|
3210
|
+
function toolEditLink(file, className) {
|
|
3211
|
+
var href = toolEditUrl(file);
|
|
3212
|
+
if (!href) return null;
|
|
3213
|
+
var a = document.createElement('a');
|
|
3214
|
+
a.className = className;
|
|
3215
|
+
a.href = href;
|
|
3216
|
+
a.target = '_blank';
|
|
3217
|
+
a.rel = 'noopener noreferrer';
|
|
3218
|
+
a.title = 'Edit in the ' + (fileKind(file.name, file.contentType) === 'video' ? 'video' : 'image') + ' tool';
|
|
3219
|
+
a.setAttribute('aria-label', a.title);
|
|
3220
|
+
// The card itself opens the lightbox; the pencil must not also trigger it.
|
|
3221
|
+
a.addEventListener('click', function (e) { e.stopPropagation(); });
|
|
3222
|
+
return a;
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3119
3225
|
// Compact markdown → HTML for .md previews (headings, lists, quotes, rules,
|
|
3120
3226
|
// fenced + inline code, bold/italic/links). Escapes first, then injects tags
|
|
3121
3227
|
// (same order as linkify). Backticks are built via char codes to avoid
|
|
@@ -3176,7 +3282,9 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3176
3282
|
var dlBtn = document.createElement('a'); dlBtn.className = 'rk-aichat-preview-act'; dlBtn.target = '_blank'; dlBtn.rel = 'noopener noreferrer'; dlBtn.textContent = 'Download';
|
|
3177
3283
|
var linkBtn = document.createElement('button'); linkBtn.type = 'button'; linkBtn.className = 'rk-aichat-preview-act'; linkBtn.textContent = 'Copy link';
|
|
3178
3284
|
var xBtn = document.createElement('button'); xBtn.type = 'button'; xBtn.className = 'rk-aichat-modal-x'; xBtn.setAttribute('aria-label', 'Close'); xBtn.innerHTML = '×';
|
|
3179
|
-
|
|
3285
|
+
// Same hand-off as the card pencil, spelled out as a labelled action here.
|
|
3286
|
+
var editBtn = document.createElement('a'); editBtn.className = 'rk-aichat-preview-act'; editBtn.target = '_blank'; editBtn.rel = 'noopener noreferrer'; editBtn.textContent = 'Edit';
|
|
3287
|
+
head.appendChild(nameEl); head.appendChild(editBtn); head.appendChild(copyBtn); head.appendChild(dlBtn); head.appendChild(linkBtn); head.appendChild(xBtn);
|
|
3180
3288
|
function flashAct(btn, label) { var o = btn.getAttribute('data-label') || btn.textContent; btn.setAttribute('data-label', o); btn.textContent = label; setTimeout(function () { btn.textContent = o; }, 1400); }
|
|
3181
3289
|
// Copy = file text (json/md/txt) or its URL for binaries. Copy link = the
|
|
3182
3290
|
// durable public S3 URL. Download opens in a new tab when the browser can't
|
|
@@ -3225,6 +3333,9 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3225
3333
|
var it = items[idx];
|
|
3226
3334
|
nameEl.textContent = it.name || 'file'; nameEl.title = it.name || '';
|
|
3227
3335
|
dlBtn.href = it.viewUrl; dlBtn.setAttribute('download', it.name || '');
|
|
3336
|
+
var editHref = toolEditUrl(it);
|
|
3337
|
+
editBtn.hidden = !editHref;
|
|
3338
|
+
if (editHref) { editBtn.href = editHref; editBtn.title = 'Edit in the ' + (fileKind(it.name, it.contentType) === 'video' ? 'video' : 'image') + ' tool'; }
|
|
3228
3339
|
linkBtn.style.display = it.publicUrl ? '' : 'none';
|
|
3229
3340
|
body.innerHTML = '';
|
|
3230
3341
|
var kind = fileKind(it.name, it.contentType);
|
|
@@ -3279,7 +3390,15 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3279
3390
|
var files = [], seen = {};
|
|
3280
3391
|
var arts = (job && job.artifacts) || [];
|
|
3281
3392
|
for (var i = 0; i < arts.length; i++) { var f = rkFileFromArtifact(arts[i]); if (f && !seen[f.viewUrl]) { seen[f.viewUrl] = true; files.push(f); } }
|
|
3282
|
-
|
|
3393
|
+
// Name the direct result URL after the FILE, not the tracer — fileKind() reads the
|
|
3394
|
+
// extension, and a tracer-named entry ("my-poster") classifies as 'other', losing
|
|
3395
|
+
// the image/video thumbnail and the edit-in-tool hand-off.
|
|
3396
|
+
var mu = rkResultMediaUrl(job);
|
|
3397
|
+
if (mu && !seen[mu]) {
|
|
3398
|
+
seen[mu] = true;
|
|
3399
|
+
var seg = String(mu).split('?')[0].split('/').pop() || '';
|
|
3400
|
+
files.push({ name: (/.[a-z0-9]{2,5}$/i.test(seg) ? seg : (job.tracer || 'output')), contentType: '', viewUrl: mu, publicUrl: mu });
|
|
3401
|
+
}
|
|
3283
3402
|
var res = job && job.result;
|
|
3284
3403
|
if (res && typeof res === 'object' && !Array.isArray(res) && Object.keys(res).length) {
|
|
3285
3404
|
try { var blob = new Blob([JSON.stringify(res, null, 2)], { type: 'application/json' }); files.push({ name: (job.tracer || 'result') + '.json', contentType: 'application/json', viewUrl: URL.createObjectURL(blob), publicUrl: null }); } catch (e) {}
|
|
@@ -3332,14 +3451,21 @@ body.vf-has-topbanner.rk-has-sidebar .rk-content{padding-top:var(--vf-topbanner-
|
|
|
3332
3451
|
var grid = document.createElement('div'); grid.className = 'rk-aichat-files-grid';
|
|
3333
3452
|
list.forEach(function (f, ix) {
|
|
3334
3453
|
var k = fileKind(f.name, f.contentType);
|
|
3335
|
-
|
|
3454
|
+
// A div, not a button: media cards carry a nested "edit in tool" link, and
|
|
3455
|
+
// an anchor inside a button is invalid (and unclickable in some browsers).
|
|
3456
|
+
var card = document.createElement('div'); card.className = 'rk-aichat-file'; card.tabIndex = 0; card.setAttribute('role', 'button');
|
|
3336
3457
|
var thumb = document.createElement('div'); thumb.className = 'rk-aichat-file-thumb rk-aichat-file-thumb--' + k;
|
|
3337
3458
|
if (k === 'image') { var im = document.createElement('img'); im.src = f.viewUrl; im.alt = ''; im.loading = 'lazy'; thumb.appendChild(im); }
|
|
3338
3459
|
else if (k === 'video') { var v = document.createElement('video'); v.src = f.viewUrl; v.muted = true; v.preload = 'metadata'; v.playsInline = true; thumb.appendChild(v); var pl = document.createElement('span'); pl.className = 'rk-aichat-file-play'; pl.textContent = '▶'; thumb.appendChild(pl); }
|
|
3339
3460
|
else { var ic = document.createElement('span'); ic.className = 'rk-aichat-file-ic'; ic.textContent = fileIcon(k); thumb.appendChild(ic); }
|
|
3461
|
+
var edit = toolEditLink(f, 'rk-aichat-file-open');
|
|
3462
|
+
if (edit) { edit.innerHTML = PENCIL_SVG; thumb.appendChild(edit); }
|
|
3340
3463
|
var nm = document.createElement('div'); nm.className = 'rk-aichat-file-name'; nm.textContent = f.name || 'file'; nm.title = f.name || '';
|
|
3341
3464
|
card.appendChild(thumb); card.appendChild(nm);
|
|
3342
3465
|
card.addEventListener('click', function () { openFilePreview(list, ix); });
|
|
3466
|
+
card.addEventListener('keydown', function (e) {
|
|
3467
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openFilePreview(list, ix); }
|
|
3468
|
+
});
|
|
3343
3469
|
grid.appendChild(card);
|
|
3344
3470
|
});
|
|
3345
3471
|
wrap.appendChild(grid); turn.appendChild(wrap); logBottom();
|