@mevdragon/vidfarm-devcli 0.18.1 → 0.19.0
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/editor-capabilities/SKILL.md +166 -0
- package/.agents/skills/editor-capabilities/references/re-theme-walkthrough.md +58 -0
- package/.agents/skills/vidfarm-media/SKILL.md +2 -0
- package/SKILL.director.md +174 -16
- package/SKILL.platform.md +7 -3
- package/demo/dist/app.css +1 -1
- package/demo/dist/app.js +77 -75
- package/dist/src/app.js +1513 -145
- package/dist/src/cli.js +1365 -105
- package/dist/src/config.js +7 -0
- package/dist/src/devcli/clips.js +3 -0
- package/dist/src/devcli/composition-edit.js +396 -8
- package/dist/src/devcli/local-backend.js +123 -0
- package/dist/src/devcli/migrate-local.js +140 -0
- package/dist/src/devcli/sync.js +311 -0
- package/dist/src/devcli/timeline-edit.js +208 -1
- package/dist/src/editor-chat.js +33 -17
- package/dist/src/frontend/file-directory.js +271 -20
- package/dist/src/frontend/homepage-view.js +3 -3
- package/dist/src/frontend/template-editor-chat.js +6 -3
- package/dist/src/page-shell.js +1 -1
- package/dist/src/primitive-registry.js +409 -4
- package/dist/src/reskin/chat-page.js +103 -15
- package/dist/src/reskin/discover-page.js +247 -10
- package/dist/src/reskin/document.js +147 -10
- package/dist/src/reskin/inpaint-clipper-page.js +649 -0
- package/dist/src/reskin/inpaint-page.js +2459 -452
- package/dist/src/reskin/inpaint-video-page.js +1339 -0
- package/dist/src/reskin/library-page.js +324 -82
- package/dist/src/reskin/theme.js +36 -11
- package/dist/src/services/billing.js +4 -0
- package/dist/src/services/clip-curation/hunt.js +2 -0
- package/dist/src/services/clip-records.js +28 -0
- package/dist/src/services/file-directory.js +6 -3
- package/dist/src/services/hyperframes.js +283 -3
- package/dist/src/services/serverless-records.js +43 -0
- package/dist/src/services/storage.js +24 -2
- package/package.json +1 -1
- package/public/assets/file-directory-app.js +2 -2
- package/public/assets/homepage-client-app.js +1 -1
- package/public/assets/page-runtime-client-app.js +2 -2
- package/public/assets/placeholders/scene-placeholder.png +0 -0
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// callbacks, or, for the auto-mounted dock instance, read from a
|
|
17
17
|
// `window.__rkDirectoryHost` hook the dock's inline script sets synchronously
|
|
18
18
|
// before this deferred module runs.
|
|
19
|
-
import { buildDirectoryPath, DIRECTORY_ROOTS, normalizeFolderPath, parseDirectoryPath } from "../services/file-directory.js";
|
|
19
|
+
import { buildDirectoryPath, DIRECTORY_ROOT_KEYS, DIRECTORY_ROOTS, normalizeFolderPath, parseDirectoryPath } from "../services/file-directory.js";
|
|
20
20
|
const DEFAULT_API_BASE = "/api/v1/user/me";
|
|
21
21
|
// ── small DOM helpers ────────────────────────────────────────────────────────
|
|
22
22
|
function el(tag, cls, text) {
|
|
@@ -159,13 +159,16 @@ function openMenuAt(anchor, actions) {
|
|
|
159
159
|
// ── the explorer ─────────────────────────────────────────────────────────────
|
|
160
160
|
export function createDirectoryExplorer(mount, opts = {}) {
|
|
161
161
|
const apiBase = opts.apiBase || DEFAULT_API_BASE;
|
|
162
|
-
const roots = (opts.roots && opts.roots.length ? opts.roots :
|
|
162
|
+
const roots = (opts.roots && opts.roots.length ? opts.roots : DIRECTORY_ROOT_KEYS.slice());
|
|
163
163
|
const singleRoot = roots.length === 1 ? roots[0] : null;
|
|
164
164
|
const mode = opts.mode || "attach";
|
|
165
165
|
const showSearch = opts.showSearch !== false;
|
|
166
166
|
const showMultiSelect = opts.showMultiSelect !== false;
|
|
167
167
|
const showUpload = opts.showUpload != null ? opts.showUpload : mode === "attach";
|
|
168
168
|
const primaryClick = opts.primaryClick || "copyPath";
|
|
169
|
+
const cloudAvailable = opts.cloudAvailable != null
|
|
170
|
+
? opts.cloudAvailable
|
|
171
|
+
: typeof window !== "undefined" && window.__vidfarmCloudSpace === true;
|
|
169
172
|
const host = window.__rkDirectoryHost || {};
|
|
170
173
|
const emitAttach = (items) => (opts.onAttach || host.onAttach || (() => showToast("Attached " + items.length + " item(s)")))(items);
|
|
171
174
|
const emitPreview = (items, i) => {
|
|
@@ -189,6 +192,11 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
189
192
|
let searchResults = [];
|
|
190
193
|
let searchStatus = "idle";
|
|
191
194
|
let destroyed = false;
|
|
195
|
+
// Which backend the explorer reads: "local" (this box's disk backend) or
|
|
196
|
+
// "cloud" (vidfarm.cc via the serve-box passthrough). Only meaningful when
|
|
197
|
+
// cloudAvailable; the /cloud fetches hit /me/cloud/directory*.
|
|
198
|
+
let space = "local";
|
|
199
|
+
function spacePrefix() { return space === "cloud" ? "/cloud" : ""; }
|
|
192
200
|
function normalizeInitial(p, single) {
|
|
193
201
|
const parsed = parseDirectoryPath(p || "");
|
|
194
202
|
if (parsed.root)
|
|
@@ -216,6 +224,33 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
216
224
|
dropInput.hidden = true;
|
|
217
225
|
dropEl.appendChild(dropText);
|
|
218
226
|
dropEl.appendChild(dropInput);
|
|
227
|
+
// Local ⇄ Cloud space switch (serve boxes with an upstream only).
|
|
228
|
+
const spaceBar = el("div", "rk-dir-space");
|
|
229
|
+
const localBtn = el("button", "rk-dir-space-btn is-on", "Local");
|
|
230
|
+
const cloudBtn = el("button", "rk-dir-space-btn", "Cloud");
|
|
231
|
+
localBtn.type = "button";
|
|
232
|
+
cloudBtn.type = "button";
|
|
233
|
+
spaceBar.appendChild(localBtn);
|
|
234
|
+
spaceBar.appendChild(cloudBtn);
|
|
235
|
+
function setSpace(next) {
|
|
236
|
+
if (space === next)
|
|
237
|
+
return;
|
|
238
|
+
space = next;
|
|
239
|
+
localBtn.classList.toggle("is-on", next === "local");
|
|
240
|
+
cloudBtn.classList.toggle("is-on", next === "cloud");
|
|
241
|
+
// Reset to the space's root view and clear any active search.
|
|
242
|
+
searchMode = false;
|
|
243
|
+
searchResults = [];
|
|
244
|
+
searchInput.value = "";
|
|
245
|
+
selection.clear();
|
|
246
|
+
currentPath = singleRoot ? "/" + singleRoot : "/";
|
|
247
|
+
status = "idle";
|
|
248
|
+
load(true);
|
|
249
|
+
}
|
|
250
|
+
localBtn.addEventListener("click", () => setSpace("local"));
|
|
251
|
+
cloudBtn.addEventListener("click", () => setSpace("cloud"));
|
|
252
|
+
if (cloudAvailable)
|
|
253
|
+
mount.appendChild(spaceBar);
|
|
219
254
|
if (showSearch)
|
|
220
255
|
mount.appendChild(searchWrap);
|
|
221
256
|
mount.appendChild(crumbsEl);
|
|
@@ -226,7 +261,7 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
226
261
|
mount.appendChild(dropEl);
|
|
227
262
|
// ── fetch ──
|
|
228
263
|
function dirUrl(path, offset) {
|
|
229
|
-
const u = new URL(apiBase + "/directory", window.location.href);
|
|
264
|
+
const u = new URL(apiBase + spacePrefix() + "/directory", window.location.href);
|
|
230
265
|
u.searchParams.set("path", path);
|
|
231
266
|
if (offset)
|
|
232
267
|
u.searchParams.set("offset", String(offset));
|
|
@@ -313,7 +348,7 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
313
348
|
searchStatus = "loading";
|
|
314
349
|
render();
|
|
315
350
|
const scope = singleRoot ? "/" + singleRoot : (currentPath !== "/" ? currentPath : "");
|
|
316
|
-
fetch(apiBase + "/directory/search", {
|
|
351
|
+
fetch(apiBase + spacePrefix() + "/directory/search", {
|
|
317
352
|
method: "POST",
|
|
318
353
|
credentials: "same-origin",
|
|
319
354
|
headers: { "content-type": "application/json", accept: "application/json" },
|
|
@@ -339,10 +374,14 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
339
374
|
status = "idle";
|
|
340
375
|
searchMode = false;
|
|
341
376
|
searchInput.value = "";
|
|
377
|
+
(opts.onNavigate || (() => { }))(currentPath);
|
|
342
378
|
load(true);
|
|
343
379
|
}
|
|
344
380
|
function currentRoot() { return parseDirectoryPath(currentPath).root; }
|
|
345
381
|
function currentFolder() { return parseDirectoryPath(currentPath).folderPath; }
|
|
382
|
+
// /projects mirrors the user's composition forks — its folders and files are
|
|
383
|
+
// backed by fork storage, not user-managed, so no create/rename/upload there.
|
|
384
|
+
function canWriteRoot(r) { return !!r && r !== "projects"; }
|
|
346
385
|
// Create an empty folder under `parentFolder` (defaults to the current folder).
|
|
347
386
|
// Only valid inside a root — at the virtual root there's nowhere to put it.
|
|
348
387
|
function createFolder(parentFolder) {
|
|
@@ -351,6 +390,10 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
351
390
|
toast("Open a root (/files, /temp, /raws) first");
|
|
352
391
|
return;
|
|
353
392
|
}
|
|
393
|
+
if (!canWriteRoot(root)) {
|
|
394
|
+
toast("/projects is read-only");
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
354
397
|
const name = (window.prompt("New folder name") || "").trim();
|
|
355
398
|
if (!name)
|
|
356
399
|
return;
|
|
@@ -366,6 +409,33 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
366
409
|
.then((res) => { navigate(res.path || (root === currentRoot() ? currentPath : "/" + root)); toast("Created " + (res.path || name)); })
|
|
367
410
|
.catch((e) => toast(e.message || "Couldn't create folder"));
|
|
368
411
|
}
|
|
412
|
+
// Rename a file or folder in place via POST /me/directory/rename. Files carry
|
|
413
|
+
// their backend id (display-name-only rename); folders rename their trailing
|
|
414
|
+
// path segment (files + nested subfolders move with them server-side).
|
|
415
|
+
function renameItem(it) {
|
|
416
|
+
const isFolder = it.kind === "folder";
|
|
417
|
+
const next = (window.prompt(isFolder ? "Rename folder" : "Rename file", it.name) || "").trim();
|
|
418
|
+
if (!next || next === it.name)
|
|
419
|
+
return;
|
|
420
|
+
fetch(apiBase + "/directory/rename", {
|
|
421
|
+
method: "POST",
|
|
422
|
+
credentials: "same-origin",
|
|
423
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
424
|
+
body: JSON.stringify({ path: it.path, new_name: next, ...(isFolder ? {} : { file_id: it.id }) })
|
|
425
|
+
})
|
|
426
|
+
.then((r) => (r.ok ? r.json() : r.json().then((j) => Promise.reject(new Error(j.error || "http " + r.status)))))
|
|
427
|
+
.then(() => {
|
|
428
|
+
// Renaming shifts the item's path, so drop any stale selection and
|
|
429
|
+
// reload the current folder (the renamed child re-renders in place).
|
|
430
|
+
if (selection.has(it.path)) {
|
|
431
|
+
selection.delete(it.path);
|
|
432
|
+
(opts.onSelectionChange || (() => { }))(Array.from(selection.values()));
|
|
433
|
+
}
|
|
434
|
+
load(true);
|
|
435
|
+
toast("Renamed to " + next);
|
|
436
|
+
})
|
|
437
|
+
.catch((e) => toast(e.message || "Couldn't rename"));
|
|
438
|
+
}
|
|
369
439
|
// ── selection ──
|
|
370
440
|
function toggleSelect(it, on) {
|
|
371
441
|
if (on)
|
|
@@ -412,11 +482,14 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
412
482
|
crumbsEl.appendChild(crumb(part, idx === arr.length - 1, () => navigate(target)));
|
|
413
483
|
});
|
|
414
484
|
// Discoverable "New folder" affordance (right-click also works anywhere).
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
485
|
+
// Read-only roots (/projects) don't get it.
|
|
486
|
+
if (canWriteRoot(root)) {
|
|
487
|
+
const nf = el("button", "rk-dir-newfolder", "+ Folder");
|
|
488
|
+
nf.type = "button";
|
|
489
|
+
nf.title = "New folder here (or right-click)";
|
|
490
|
+
nf.addEventListener("click", () => createFolder());
|
|
491
|
+
crumbsEl.appendChild(nf);
|
|
492
|
+
}
|
|
420
493
|
}
|
|
421
494
|
function sep() { return el("span", "rk-aichat-crumb-sep", "/"); }
|
|
422
495
|
function renderSelbar() {
|
|
@@ -475,12 +548,14 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
475
548
|
open.title = "Open folder";
|
|
476
549
|
open.addEventListener("click", (e) => { e.stopPropagation(); navigate(it.path); });
|
|
477
550
|
row.appendChild(open);
|
|
478
|
-
// Kebab.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
{ label: "New subfolder", run: () => createFolder(it.folderPath) }
|
|
482
|
-
{ label: "
|
|
483
|
-
|
|
551
|
+
// Kebab. Create/rename only for writable roots (not /projects forks).
|
|
552
|
+
const folderActions = [{ label: "Open folder", run: () => navigate(it.path) }];
|
|
553
|
+
if (canWriteRoot(it.root)) {
|
|
554
|
+
folderActions.push({ label: "New subfolder", run: () => createFolder(it.folderPath) });
|
|
555
|
+
folderActions.push({ label: "Rename", run: () => renameItem(it) });
|
|
556
|
+
}
|
|
557
|
+
folderActions.push({ label: "Copy path", run: () => copyPath(it.path) });
|
|
558
|
+
row.appendChild(kebab(it, folderActions));
|
|
484
559
|
return row;
|
|
485
560
|
}
|
|
486
561
|
function fileRowEl(it, siblings, index, showPath) {
|
|
@@ -507,6 +582,17 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
507
582
|
copyPath(it.path);
|
|
508
583
|
});
|
|
509
584
|
row.appendChild(b);
|
|
585
|
+
// When the row's primary click previews (chat dock), keep attaching one click
|
|
586
|
+
// away with a dedicated inline "+" button so files stay easy to add to the
|
|
587
|
+
// composer without going through the kebab.
|
|
588
|
+
if (mode === "attach" && primaryClick === "preview") {
|
|
589
|
+
const add = el("button", "rk-aichat-fadd rk-dir-attach", "+");
|
|
590
|
+
add.type = "button";
|
|
591
|
+
add.title = "Attach " + it.name;
|
|
592
|
+
add.setAttribute("aria-label", "Attach " + it.name);
|
|
593
|
+
add.addEventListener("click", (e) => { e.stopPropagation(); emitAttach([it]); });
|
|
594
|
+
row.appendChild(add);
|
|
595
|
+
}
|
|
510
596
|
// Kebab: Attach / View / Preview / Copy path.
|
|
511
597
|
const actions = [];
|
|
512
598
|
if (mode === "attach")
|
|
@@ -515,6 +601,10 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
515
601
|
actions.push({ label: "View", run: () => window.open(it.viewUrl, "_blank", "noopener") });
|
|
516
602
|
if (it.viewUrl)
|
|
517
603
|
actions.push({ label: "Preview", run: () => emitPreview(siblings, index) });
|
|
604
|
+
// Raws (source-named) and approved posts (title-named) can't be file-renamed;
|
|
605
|
+
// only /files + /temp files rename in place. Folders rename in every root.
|
|
606
|
+
if ((it.root === "files" || it.root === "temp") && it.id)
|
|
607
|
+
actions.push({ label: "Rename", run: () => renameItem(it) });
|
|
518
608
|
actions.push({ label: "Copy path", run: () => copyPath(it.path) });
|
|
519
609
|
row.appendChild(kebab(it, actions));
|
|
520
610
|
return row;
|
|
@@ -663,8 +753,10 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
663
753
|
const it = rowEl ? findItem(rowEl.getAttribute("data-path") || "") : undefined;
|
|
664
754
|
if (it && it.kind === "folder") {
|
|
665
755
|
actions.push({ label: "Open folder", run: () => navigate(it.path) });
|
|
666
|
-
if (
|
|
756
|
+
if (canWriteRoot(it.root))
|
|
667
757
|
actions.push({ label: "New subfolder", run: () => createFolder(it.folderPath) });
|
|
758
|
+
if (canWriteRoot(it.root))
|
|
759
|
+
actions.push({ label: "Rename", run: () => renameItem(it) });
|
|
668
760
|
actions.push({ label: "Copy path", run: () => copyPath(it.path) });
|
|
669
761
|
}
|
|
670
762
|
else if (it) {
|
|
@@ -674,10 +766,12 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
674
766
|
actions.push({ label: "View", run: () => window.open(it.viewUrl, "_blank", "noopener") });
|
|
675
767
|
if (it.viewUrl)
|
|
676
768
|
actions.push({ label: "Preview", run: () => emitPreview(files, files.indexOf(it)) });
|
|
769
|
+
if (it.root !== "raws" && canWriteRoot(it.root) && it.id)
|
|
770
|
+
actions.push({ label: "Rename", run: () => renameItem(it) });
|
|
677
771
|
actions.push({ label: "Copy path", run: () => copyPath(it.path) });
|
|
678
772
|
}
|
|
679
|
-
// Always offer "New folder" in the current folder when inside a root.
|
|
680
|
-
if (rootSet)
|
|
773
|
+
// Always offer "New folder" in the current folder when inside a writable root.
|
|
774
|
+
if (rootSet && canWriteRoot(currentRoot()))
|
|
681
775
|
actions.push({ label: "New folder here", run: () => createFolder() });
|
|
682
776
|
if (!actions.length)
|
|
683
777
|
return; // virtual root, empty space → let the browser menu show
|
|
@@ -698,11 +792,13 @@ export function createDirectoryExplorer(mount, opts = {}) {
|
|
|
698
792
|
if (e.dataTransfer && e.dataTransfer.files)
|
|
699
793
|
uploadFiles(e.dataTransfer.files);
|
|
700
794
|
});
|
|
795
|
+
(opts.onNavigate || (() => { }))(currentPath);
|
|
701
796
|
load(true);
|
|
702
797
|
return {
|
|
703
798
|
refresh: () => load(true),
|
|
704
799
|
navigate,
|
|
705
800
|
getSelection: () => Array.from(selection.values()),
|
|
801
|
+
getCurrentPath: () => currentPath,
|
|
706
802
|
destroy: () => { destroyed = true; if (closeOpenMenu)
|
|
707
803
|
closeOpenMenu(); mount.innerHTML = ""; }
|
|
708
804
|
};
|
|
@@ -712,6 +808,160 @@ function cssEscape(s) {
|
|
|
712
808
|
return window.CSS.escape(s);
|
|
713
809
|
return s.replace(/["\\]/g, "\\$&");
|
|
714
810
|
}
|
|
811
|
+
// ── pop-panel picker (modal) ──────────────────────────────────────────────────
|
|
812
|
+
// A self-contained modal wrapper around createDirectoryExplorer so any surface
|
|
813
|
+
// (editor "Replace media", inpaint "Save to Files", …) can pick a file or a
|
|
814
|
+
// destination folder without reimplementing the overlay + CSS each time.
|
|
815
|
+
const PICKER_STYLE_ID = "rk-dirpick-style";
|
|
816
|
+
function ensurePickerStyle() {
|
|
817
|
+
if (document.getElementById(PICKER_STYLE_ID))
|
|
818
|
+
return;
|
|
819
|
+
const s = document.createElement("style");
|
|
820
|
+
s.id = PICKER_STYLE_ID;
|
|
821
|
+
// Uses reskin --rk-* tokens where present, with plain fallbacks so the modal
|
|
822
|
+
// is legible even on a page that hasn't loaded the design-system variables.
|
|
823
|
+
s.textContent = [
|
|
824
|
+
".rk-dirpick{position:fixed;inset:0;z-index:1200;display:grid;place-items:center;padding:20px;",
|
|
825
|
+
"background:rgba(28,25,23,.44);backdrop-filter:blur(2px);animation:rk-dirpick-fade .16s ease}",
|
|
826
|
+
"@keyframes rk-dirpick-fade{from{opacity:0}to{opacity:1}}",
|
|
827
|
+
".rk-dirpick-panel{width:min(560px,100%);max-height:min(82vh,700px);display:flex;flex-direction:column;",
|
|
828
|
+
"background:var(--rk-surface,#fff);border:1px solid var(--rk-border,#e6e0d6);border-radius:var(--rk-r-3xl,20px);",
|
|
829
|
+
"box-shadow:var(--rk-shadow-lg,0 24px 60px rgba(28,25,23,.28));overflow:hidden}",
|
|
830
|
+
".rk-dirpick-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px 18px;",
|
|
831
|
+
"border-bottom:1px solid var(--rk-border,#e6e0d6)}",
|
|
832
|
+
".rk-dirpick-title{font-family:var(--rk-font-display,inherit);font-weight:800;font-size:15px;color:var(--rk-ink,#1c1917)}",
|
|
833
|
+
".rk-dirpick-close{border:0;background:transparent;font-size:22px;line-height:1;color:var(--rk-text-muted,#78716c);cursor:pointer;padding:0 4px}",
|
|
834
|
+
".rk-dirpick-close:hover{color:var(--rk-ink,#1c1917)}",
|
|
835
|
+
".rk-dirpick-hint{padding:10px 18px 0;font-size:12.5px;line-height:1.4;color:var(--rk-text-muted,#78716c)}",
|
|
836
|
+
".rk-dirpick-mount{padding:12px 14px;overflow:auto;flex:1;min-height:0}",
|
|
837
|
+
".rk-dirpick-foot{display:flex;align-items:center;gap:10px;padding:12px 18px;border-top:1px solid var(--rk-border,#e6e0d6)}",
|
|
838
|
+
".rk-dirpick-dest{flex:1;min-width:0;font-size:12.5px;color:var(--rk-text-muted,#78716c);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
839
|
+
".rk-dirpick-dest b{color:var(--rk-ink,#1c1917);font-weight:700}",
|
|
840
|
+
".rk-dirpick-btn{flex:none;min-height:36px;padding:0 16px;border-radius:999px;border:0;cursor:pointer;",
|
|
841
|
+
"font:inherit;font-size:13px;font-weight:800;background:var(--rk-gold-500,var(--rk-gold-600,#fcb900));color:var(--rk-ink,#1c1917)}",
|
|
842
|
+
".rk-dirpick-btn:hover:not(:disabled){background:var(--rk-gold-600,#e0a500)}",
|
|
843
|
+
".rk-dirpick-btn:disabled{opacity:.5;cursor:default}",
|
|
844
|
+
".rk-dirpick-btn.is-ghost{background:transparent;border:1px solid var(--rk-border,#e6e0d6);color:var(--rk-ink,#1c1917)}"
|
|
845
|
+
].join("");
|
|
846
|
+
document.head.appendChild(s);
|
|
847
|
+
}
|
|
848
|
+
export function openDirectoryPicker(opts = {}) {
|
|
849
|
+
ensurePickerStyle();
|
|
850
|
+
const select = opts.select === "folder" ? "folder" : "file";
|
|
851
|
+
let done = false;
|
|
852
|
+
let explorer = null;
|
|
853
|
+
const overlay = el("div", "rk-dirpick");
|
|
854
|
+
const panel = el("div", "rk-dirpick-panel");
|
|
855
|
+
const head = el("div", "rk-dirpick-head");
|
|
856
|
+
head.appendChild(el("span", "rk-dirpick-title", opts.title || (select === "folder" ? "Choose a folder" : "Choose a file")));
|
|
857
|
+
const closeBtn = el("button", "rk-dirpick-close");
|
|
858
|
+
closeBtn.type = "button";
|
|
859
|
+
closeBtn.setAttribute("aria-label", "Close");
|
|
860
|
+
closeBtn.innerHTML = "×";
|
|
861
|
+
head.appendChild(closeBtn);
|
|
862
|
+
panel.appendChild(head);
|
|
863
|
+
if (opts.hint)
|
|
864
|
+
panel.appendChild(el("div", "rk-dirpick-hint", opts.hint));
|
|
865
|
+
const mountEl = el("div", "rk-dirpick-mount");
|
|
866
|
+
panel.appendChild(mountEl);
|
|
867
|
+
// Folder mode gets a footer that commits the currently-open folder.
|
|
868
|
+
let destEl = null;
|
|
869
|
+
let confirmBtn = null;
|
|
870
|
+
function finish(result) {
|
|
871
|
+
if (done)
|
|
872
|
+
return;
|
|
873
|
+
done = true;
|
|
874
|
+
close();
|
|
875
|
+
if (result)
|
|
876
|
+
(opts.onPick || (() => { }))(result);
|
|
877
|
+
else
|
|
878
|
+
(opts.onCancel || (() => { }))();
|
|
879
|
+
}
|
|
880
|
+
function folderItemFor(path) {
|
|
881
|
+
const parsed = parseDirectoryPath(path);
|
|
882
|
+
if (!parsed.root)
|
|
883
|
+
return null; // the virtual root "/" isn't a real folder
|
|
884
|
+
const folder = parsed.folderPath || "";
|
|
885
|
+
const name = folder ? folder.split("/").filter(Boolean).pop() || folder : parsed.root;
|
|
886
|
+
return { path: buildDirectoryPath(parsed.root, folder), root: parsed.root, folderPath: folder, name, kind: "folder" };
|
|
887
|
+
}
|
|
888
|
+
function syncFooter(path) {
|
|
889
|
+
if (!destEl || !confirmBtn)
|
|
890
|
+
return;
|
|
891
|
+
const item = folderItemFor(path);
|
|
892
|
+
if (item) {
|
|
893
|
+
destEl.innerHTML = "";
|
|
894
|
+
destEl.appendChild(document.createTextNode("Save to "));
|
|
895
|
+
const b = el("b", undefined, item.path);
|
|
896
|
+
destEl.appendChild(b);
|
|
897
|
+
confirmBtn.disabled = false;
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
destEl.textContent = "Open a folder (Files, Temp, …) to choose a destination.";
|
|
901
|
+
confirmBtn.disabled = true;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (select === "folder") {
|
|
905
|
+
const foot = el("div", "rk-dirpick-foot");
|
|
906
|
+
destEl = el("div", "rk-dirpick-dest");
|
|
907
|
+
const cancel = el("button", "rk-dirpick-btn is-ghost", "Cancel");
|
|
908
|
+
cancel.type = "button";
|
|
909
|
+
cancel.addEventListener("click", () => finish(null));
|
|
910
|
+
confirmBtn = el("button", "rk-dirpick-btn", opts.confirmLabel || "Use this folder");
|
|
911
|
+
confirmBtn.type = "button";
|
|
912
|
+
confirmBtn.addEventListener("click", () => {
|
|
913
|
+
const item = explorer ? folderItemFor(explorer.getCurrentPath()) : null;
|
|
914
|
+
if (item)
|
|
915
|
+
finish(item);
|
|
916
|
+
});
|
|
917
|
+
foot.appendChild(destEl);
|
|
918
|
+
foot.appendChild(cancel);
|
|
919
|
+
foot.appendChild(confirmBtn);
|
|
920
|
+
panel.appendChild(foot);
|
|
921
|
+
}
|
|
922
|
+
overlay.appendChild(panel);
|
|
923
|
+
document.body.appendChild(overlay);
|
|
924
|
+
function onKey(e) { if (e.key === "Escape")
|
|
925
|
+
finish(null); }
|
|
926
|
+
function close() {
|
|
927
|
+
document.removeEventListener("keydown", onKey, true);
|
|
928
|
+
try {
|
|
929
|
+
explorer && explorer.destroy();
|
|
930
|
+
}
|
|
931
|
+
catch { /* noop */ }
|
|
932
|
+
overlay.remove();
|
|
933
|
+
}
|
|
934
|
+
closeBtn.addEventListener("click", () => finish(null));
|
|
935
|
+
overlay.addEventListener("click", (e) => { if (e.target === overlay)
|
|
936
|
+
finish(null); });
|
|
937
|
+
document.addEventListener("keydown", onKey, true);
|
|
938
|
+
explorer = createDirectoryExplorer(mountEl, {
|
|
939
|
+
roots: opts.roots,
|
|
940
|
+
initialPath: opts.initialPath,
|
|
941
|
+
apiBase: opts.apiBase,
|
|
942
|
+
// Standalone hides Attach chrome; picking is our own affordance.
|
|
943
|
+
mode: "standalone",
|
|
944
|
+
showSearch: true,
|
|
945
|
+
showMultiSelect: false,
|
|
946
|
+
showUpload: false, // the picker only selects; it doesn't upload
|
|
947
|
+
// In file mode a name-click resolves the pick; folders still navigate.
|
|
948
|
+
primaryClick: select === "file" ? "attach" : "copyPath",
|
|
949
|
+
onNavigate: (p) => syncFooter(p),
|
|
950
|
+
onAttach: (items) => {
|
|
951
|
+
if (select !== "file")
|
|
952
|
+
return;
|
|
953
|
+
const it = items && items[0];
|
|
954
|
+
if (!it)
|
|
955
|
+
return;
|
|
956
|
+
if (opts.accept && !opts.accept(it)) {
|
|
957
|
+
showToast(opts.rejectMessage || "That file can't be used here.");
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
finish(it);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
return { close: () => finish(null) };
|
|
964
|
+
}
|
|
715
965
|
// ── auto-mount + global bridge ───────────────────────────────────────────────
|
|
716
966
|
function readDataOpts(node) {
|
|
717
967
|
const d = node.dataset;
|
|
@@ -723,7 +973,8 @@ function readDataOpts(node) {
|
|
|
723
973
|
showSearch: d.showSearch !== "false",
|
|
724
974
|
showMultiSelect: d.showMultiselect !== "false",
|
|
725
975
|
showUpload: d.showUpload != null ? d.showUpload === "true" : undefined,
|
|
726
|
-
primaryClick: d.primaryClick || "copyPath"
|
|
976
|
+
primaryClick: d.primaryClick || "copyPath",
|
|
977
|
+
cloudAvailable: d.cloudAvailable != null ? d.cloudAvailable === "true" : undefined
|
|
727
978
|
};
|
|
728
979
|
}
|
|
729
980
|
function autoMount() {
|
|
@@ -735,7 +986,7 @@ function autoMount() {
|
|
|
735
986
|
window.__rkDirectoryInstance = createDirectoryExplorer(node, readDataOpts(node));
|
|
736
987
|
});
|
|
737
988
|
}
|
|
738
|
-
window.__vidfarmDirectory = { mount: createDirectoryExplorer };
|
|
989
|
+
window.__vidfarmDirectory = { mount: createDirectoryExplorer, pick: openDirectoryPicker };
|
|
739
990
|
if (document.readyState === "loading")
|
|
740
991
|
document.addEventListener("DOMContentLoaded", autoMount);
|
|
741
992
|
else
|
|
@@ -66,7 +66,7 @@ function PrimaryNav({ account }) {
|
|
|
66
66
|
? [
|
|
67
67
|
{ id: "chat", href: withAccountQuery("/chat", account.userId), label: "Chat" },
|
|
68
68
|
{ id: "job-runs", href: withAccountQuery("/job-runs", account.userId), label: "Run History" },
|
|
69
|
-
{ id: "
|
|
69
|
+
{ id: "tools", href: withAccountQuery("/tools/image", account.userId), label: "Tools" },
|
|
70
70
|
{ id: "help", href: "/help", label: "Help" },
|
|
71
71
|
{ id: "logout", action: "logout", label: "Log Out" }
|
|
72
72
|
]
|
|
@@ -286,7 +286,7 @@ function editorHref(templateId, accountUserId) {
|
|
|
286
286
|
: withAccountQuery(`/templates/${encodeURIComponent(templateId)}/editor/docs`, accountUserId);
|
|
287
287
|
}
|
|
288
288
|
function CreateButton({ templateId, accountUserId }) {
|
|
289
|
-
return _jsx("a", { className: "cta-button", href: editorHref(templateId, accountUserId), children: "Editor" });
|
|
289
|
+
return _jsx("a", { className: "cta-button", href: editorHref(templateId, accountUserId), target: "_blank", rel: "noopener", children: "Editor" });
|
|
290
290
|
}
|
|
291
291
|
function CardMenu(input) {
|
|
292
292
|
const { template } = input;
|
|
@@ -343,7 +343,7 @@ function CardMenu(input) {
|
|
|
343
343
|
window.alert(result.error || "Unable to delete this template.");
|
|
344
344
|
}
|
|
345
345
|
};
|
|
346
|
-
return (_jsxs("div", { className: "discover-card-menu", ref: menuRef, children: [_jsx("button", { type: "button", className: "discover-card-menu-trigger", "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-label": `More actions for ${template.title}`, title: "More actions", onClick: () => setIsOpen((prev) => !prev), children: _jsx(MoreIcon, {}) }), isOpen ? (_jsxs("div", { className: "discover-card-menu-panel", role: "menu", children: [_jsx("button", { type: "button", className: "discover-card-menu-item", role: "menuitem", onClick: () => void handleCopyId(), children: copied ? "Copied!" : "Copy ID" }), isPending ? (_jsx("span", { className: "discover-card-menu-item is-disabled", role: "menuitem", "aria-disabled": "true", children: "Open in Editor" })) : (_jsx("a", { className: "discover-card-menu-item", role: "menuitem", href: editorHref(template.templateId, input.accountUserId), children: "Open in Editor" })), _jsx("button", { type: "button", className: "discover-card-menu-item", role: "menuitem", onClick: () => {
|
|
346
|
+
return (_jsxs("div", { className: "discover-card-menu", ref: menuRef, children: [_jsx("button", { type: "button", className: "discover-card-menu-trigger", "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-label": `More actions for ${template.title}`, title: "More actions", onClick: () => setIsOpen((prev) => !prev), children: _jsx(MoreIcon, {}) }), isOpen ? (_jsxs("div", { className: "discover-card-menu-panel", role: "menu", children: [_jsx("button", { type: "button", className: "discover-card-menu-item", role: "menuitem", onClick: () => void handleCopyId(), children: copied ? "Copied!" : "Copy ID" }), isPending ? (_jsx("span", { className: "discover-card-menu-item is-disabled", role: "menuitem", "aria-disabled": "true", children: "Open in Editor" })) : (_jsx("a", { className: "discover-card-menu-item", role: "menuitem", href: editorHref(template.templateId, input.accountUserId), target: "_blank", rel: "noopener", children: "Open in Editor" })), _jsx("button", { type: "button", className: "discover-card-menu-item", role: "menuitem", onClick: () => {
|
|
347
347
|
input.onToggleBookmark?.(template.templateId);
|
|
348
348
|
setIsOpen(false);
|
|
349
349
|
}, children: input.isBookmarked ? "Remove Bookmark" : "Bookmark" }), canDelete ? (_jsx("button", { type: "button", className: "discover-card-menu-item is-danger", role: "menuitem", disabled: deleting, onClick: () => void handleDelete(), children: deleting ? "Deleting…" : "Delete" })) : null] })) : null] }));
|
|
@@ -390,8 +390,11 @@ function collectLabeledUrls(value, output, seen = new Set(), path = []) {
|
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
function buildJobQueuedMessage(job) {
|
|
393
|
+
// MUST stay string-identical to buildAsyncJobHandoffText in src/editor-chat.ts —
|
|
394
|
+
// the server streams that text and this client copy dedupes it by substring.
|
|
395
|
+
// Job-KIND-agnostic ("job", not "render"): fires for any queued async job.
|
|
393
396
|
return [
|
|
394
|
-
`Started
|
|
397
|
+
`Started job \`${job.job_id}\`${job.tracer ? ` with tracer \`${job.tracer}\`` : ""}.`,
|
|
395
398
|
"If it takes a while, ask for an update later and I can check it for you."
|
|
396
399
|
].join("\n\n");
|
|
397
400
|
}
|
|
@@ -1479,7 +1482,7 @@ function mediaInpaintHref(preview, ensureTracer = false) {
|
|
|
1479
1482
|
if (typeof window === "undefined" || preview.kind !== "image" || !preview.url) {
|
|
1480
1483
|
return "";
|
|
1481
1484
|
}
|
|
1482
|
-
const pageUrl = new URL("/
|
|
1485
|
+
const pageUrl = new URL("/tools/image", window.location.origin);
|
|
1483
1486
|
const bridge = window.__vidfarmEditorTracers;
|
|
1484
1487
|
const existingTracers = bridge?.get() ?? readTracersFromLocation();
|
|
1485
1488
|
const tracer = existingTracers[0] || (ensureTracer ? bridge?.add() ?? "" : "");
|
|
@@ -1500,7 +1503,7 @@ function MediaPreviewModal(props) {
|
|
|
1500
1503
|
const activePreview = gallery[activeIndex] || props.preview;
|
|
1501
1504
|
const canNavigate = gallery.length > 1;
|
|
1502
1505
|
const canInpaint = activePreview.kind === "image";
|
|
1503
|
-
const initialInpaintHref = canInpaint ? mediaInpaintHref(activePreview, false) || "/
|
|
1506
|
+
const initialInpaintHref = canInpaint ? mediaInpaintHref(activePreview, false) || "/tools/image" : "/tools/image";
|
|
1504
1507
|
useEffect(() => {
|
|
1505
1508
|
setIsMounted(true);
|
|
1506
1509
|
}, []);
|
package/dist/src/page-shell.js
CHANGED
|
@@ -82,7 +82,7 @@ export function renderPrimaryNav(input) {
|
|
|
82
82
|
? [
|
|
83
83
|
{ id: "chat", href: withAccountQuery("/chat", input.accountId), label: "Chat" },
|
|
84
84
|
{ id: "job-runs", href: withAccountQuery("/job-runs", input.accountId), label: "Run History" },
|
|
85
|
-
{ id: "
|
|
85
|
+
{ id: "tools", href: withAccountQuery("/tools/image", input.accountId), label: "Tools" },
|
|
86
86
|
{ id: "help", href: "/help", label: "Help" },
|
|
87
87
|
{ id: "logout", action: "logout", label: "Log Out" }
|
|
88
88
|
]
|