@mevdragon/vidfarm-devcli 0.17.0 → 0.18.1

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.
Files changed (55) hide show
  1. package/.agents/skills/vidfarm-media/SKILL.md +43 -6
  2. package/SKILL.director.md +37 -23
  3. package/SKILL.platform.md +6 -6
  4. package/demo/dist/app.js +69 -69
  5. package/demo/dist/favicon.ico +0 -0
  6. package/dist/src/app.js +1832 -213
  7. package/dist/src/cli.js +238 -17
  8. package/dist/src/devcli/clip-store.js +29 -2
  9. package/dist/src/devcli/clips.js +116 -73
  10. package/dist/src/devcli/composition-edit.js +262 -0
  11. package/dist/src/devcli/timeline-edit.js +283 -0
  12. package/dist/src/editor-chat.js +29 -6
  13. package/dist/src/frontend/discover-client.js +130 -0
  14. package/dist/src/frontend/discover-store.js +23 -0
  15. package/dist/src/frontend/file-directory.js +744 -0
  16. package/dist/src/frontend/template-editor-chat.js +22 -19
  17. package/dist/src/landing-page.js +24 -7
  18. package/dist/src/page-shell.js +25 -1
  19. package/dist/src/reskin/agency-page.js +214 -170
  20. package/dist/src/reskin/calendar-page.js +503 -187
  21. package/dist/src/reskin/chat-page.js +739 -299
  22. package/dist/src/reskin/discover-page.js +1450 -279
  23. package/dist/src/reskin/document.js +1392 -16
  24. package/dist/src/reskin/help-page.js +139 -100
  25. package/dist/src/reskin/index-page.js +1 -1
  26. package/dist/src/reskin/inpaint-page.js +547 -0
  27. package/dist/src/reskin/job-runs-page.js +355 -127
  28. package/dist/src/reskin/library-page.js +1188 -317
  29. package/dist/src/reskin/login-page.js +124 -87
  30. package/dist/src/reskin/pricing-page.js +197 -166
  31. package/dist/src/reskin/settings-page.js +566 -187
  32. package/dist/src/reskin/theme.js +434 -17
  33. package/dist/src/services/clip-curation/gemini.js +5 -0
  34. package/dist/src/services/clip-curation/hunt.js +79 -1
  35. package/dist/src/services/clip-curation/index.js +2 -1
  36. package/dist/src/services/clip-curation/local-agent.js +4 -3
  37. package/dist/src/services/clip-curation/media-select.js +85 -0
  38. package/dist/src/services/clip-curation/query.js +5 -1
  39. package/dist/src/services/clip-curation/refine.js +50 -20
  40. package/dist/src/services/clip-curation/scan.js +10 -3
  41. package/dist/src/services/clip-curation/taxonomy.js +3 -1
  42. package/dist/src/services/clip-curation/taxonomy.v1.json +13 -1
  43. package/dist/src/services/clip-records.js +14 -1
  44. package/dist/src/services/clip-search.js +43 -13
  45. package/dist/src/services/file-directory.js +114 -0
  46. package/dist/src/services/storage.js +24 -1
  47. package/dist/src/services/upstream.js +5 -5
  48. package/dist/src/template-editor-shell.js +16 -2
  49. package/package.json +1 -1
  50. package/public/assets/discover-client-app.js +1 -0
  51. package/public/assets/file-directory-app.js +2 -0
  52. package/public/assets/homepage-client-app.js +12 -12
  53. package/public/assets/page-runtime-client-app.js +24 -24
  54. package/src/assets/favicon.ico +0 -0
  55. package/src/assets/logo-vidfarm.png +0 -0
@@ -0,0 +1,114 @@
1
+ // Shared canonical file-directory path model.
2
+ //
3
+ // The user's files live in three *separate* backends — My Files attachments,
4
+ // Temp files, and the Raws (clip) library — each with its own DynamoDB records
5
+ // and S3 prefix. This module is the ONE place that defines the unified virtual
6
+ // directory that presents them as three navigable roots:
7
+ //
8
+ // /files/<subfolders…>/<name> → My Files attachments (users/<id>/files/…)
9
+ // /temp/<YYYY-MM-DD>/<name> → Temp files (users/<id>/temporary/…)
10
+ // /raws/<subfolders…>/<name> → Raws / clip library (clips/<owner>/…)
11
+ //
12
+ // It's a *virtual* layer: physical S3 keys are untouched. The canonical path is
13
+ // what the UI copies, what the chat agent navigates, and what search annotates.
14
+ // A file's real handle is still its backend id (attachment/temp/clip id) — the
15
+ // path is the human-readable navigation + display layer on top.
16
+ //
17
+ // Pure logic, no runtime deps, so both `src/app.ts` (serve) and
18
+ // `infra/lambda/editor-chat.ts` (cloud) import it, and the frontend explorer
19
+ // client mirrors the same constants.
20
+ export const DIRECTORY_ROOT_KEYS = ["files", "temp", "raws"];
21
+ export const DIRECTORY_ROOTS = [
22
+ { key: "files", label: "My Files", description: "Durable personal files — brand assets, characters, saved context. Vector-searchable via notes." },
23
+ { key: "temp", label: "Temp", description: "Scratch space, auto-organized by date (YYYY-MM-DD) and auto-deleted after 30 days." },
24
+ { key: "raws", label: "Raws", description: "The reusable clip / source-footage library, organized into folders by source." }
25
+ ];
26
+ // The attachments backend historically scopes its folders under the internal
27
+ // key "my_files"; the canonical public root name is "files". These two helpers
28
+ // bridge the canonical name and that backend scope so callers never hardcode it.
29
+ export const ATTACHMENTS_FOLDER_SCOPE = "my_files";
30
+ export function isDirectoryRoot(value) {
31
+ return value === "files" || value === "temp" || value === "raws";
32
+ }
33
+ export function directoryRootLabel(root) {
34
+ const info = DIRECTORY_ROOTS.find((r) => r.key === root);
35
+ return info ? info.label : root;
36
+ }
37
+ /**
38
+ * Normalize a relative folder path: trim each segment, drop empties and any
39
+ * leading/trailing slashes, collapse repeats. `"/a//b/ "` → `"a/b"`.
40
+ */
41
+ export function normalizeFolderPath(value) {
42
+ return String(value ?? "")
43
+ .split("/")
44
+ .map((s) => s.trim())
45
+ .filter(Boolean)
46
+ .join("/");
47
+ }
48
+ /**
49
+ * Parse a canonical directory path into its root + relative folder path.
50
+ *
51
+ * A path is always interpreted as a FOLDER location (what to list / scope to):
52
+ * every segment after the root is treated as folder path. Files are addressed
53
+ * by backend id, not resolved from the trailing segment, so there's no
54
+ * file-vs-folder ambiguity to guess at here.
55
+ *
56
+ * "/raws/product-demos" → { root:"raws", folderPath:"product-demos" }
57
+ * "/" or "" → { root:null, folderPath:"" } (virtual root)
58
+ */
59
+ export function parseDirectoryPath(input) {
60
+ const segments = String(input ?? "")
61
+ .split("/")
62
+ .map((s) => s.trim())
63
+ .filter(Boolean);
64
+ if (segments.length === 0)
65
+ return { root: null, folderPath: "", segments: [] };
66
+ const [head, ...rest] = segments;
67
+ if (!isDirectoryRoot(head))
68
+ return { root: null, folderPath: "", segments };
69
+ return { root: head, folderPath: rest.join("/"), segments: rest };
70
+ }
71
+ /**
72
+ * Build a canonical directory path from parts. `name` omitted → a folder path.
73
+ *
74
+ * buildDirectoryPath("files", "logos", "logo.png") → "/files/logos/logo.png"
75
+ * buildDirectoryPath("raws", "demos") → "/raws/demos"
76
+ * buildDirectoryPath("temp") → "/temp"
77
+ */
78
+ export function buildDirectoryPath(root, folderPath, name) {
79
+ const parts = [root, normalizeFolderPath(folderPath)];
80
+ if (name)
81
+ parts.push(String(name).trim());
82
+ return "/" + parts.filter(Boolean).join("/");
83
+ }
84
+ /** Build a folder DirectoryItem for a child folder name under a root/parent. */
85
+ export function folderItem(root, parentFolder, childName) {
86
+ const folderPath = normalizeFolderPath(parentFolder ? `${parentFolder}/${childName}` : childName);
87
+ return {
88
+ path: buildDirectoryPath(root, folderPath),
89
+ root,
90
+ folderPath,
91
+ name: childName,
92
+ kind: "folder"
93
+ };
94
+ }
95
+ /**
96
+ * Given a flat list of items' folderPaths (+ any explicit empty-folder records)
97
+ * and the current folder, return the immediate CHILD folder names. Mirrors the
98
+ * client explorer's `immediateFolders`, kept here so server and client agree.
99
+ */
100
+ export function immediateChildFolders(folderPaths, currentFolder) {
101
+ const cur = normalizeFolderPath(currentFolder);
102
+ const prefix = cur ? cur + "/" : "";
103
+ const names = new Set();
104
+ for (const raw of folderPaths) {
105
+ const n = normalizeFolderPath(raw);
106
+ if (!n || (cur && n === cur) || n.indexOf(prefix) !== 0)
107
+ continue;
108
+ const child = n.slice(prefix.length).split("/")[0];
109
+ if (child)
110
+ names.add(child);
111
+ }
112
+ return Array.from(names).sort();
113
+ }
114
+ //# sourceMappingURL=file-directory.js.map
@@ -1,4 +1,4 @@
1
- import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, createReadStream, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, PutObjectTaggingCommand, S3Client } from "@aws-sdk/client-s3";
4
4
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
@@ -113,6 +113,29 @@ export class StorageService {
113
113
  * no client header contract), which the bucket's tag-scoped lifecycle rule
114
114
  * expires after 30 days. No-op on the local filesystem driver.
115
115
  */
116
+ /**
117
+ * Store a file that already lives on disk WITHOUT reading it fully into memory.
118
+ * Streams the bytes to S3 (ContentLength from the file's size, so no multipart
119
+ * dependency) or copies on the local driver. Use for large media (whole-video
120
+ * raw imports, render outputs) where `putBuffer(readFileSync(...))` would pin
121
+ * the entire file in a Buffer and risk OOM on memory-bounded Lambdas.
122
+ */
123
+ async putFile(key, localPath, contentType = "application/octet-stream") {
124
+ if (this.s3 && config.AWS_S3_BUCKET) {
125
+ await this.s3.send(new PutObjectCommand({
126
+ Bucket: config.AWS_S3_BUCKET,
127
+ Key: key,
128
+ Body: createReadStream(localPath),
129
+ ContentLength: statSync(localPath).size,
130
+ ContentType: contentType
131
+ }));
132
+ return { key, url: this.getPublicUrl(key) };
133
+ }
134
+ const filePath = this.resolveLocalPath(key);
135
+ mkdirSync(path.dirname(filePath), { recursive: true });
136
+ copyFileSync(localPath, filePath);
137
+ return { key, url: this.getPublicUrl(key) };
138
+ }
116
139
  async putObjectTags(key, tags) {
117
140
  if (!this.s3 || !config.AWS_S3_BUCKET)
118
141
  return;
@@ -117,9 +117,9 @@ function seedAuthHeaders(shareToken) {
117
117
  headers["vidfarm-share-token"] = shareToken;
118
118
  return headers;
119
119
  }
120
- // /editor/<template_id> issues a 302 → /editor/<template_id>?fork=<id>
120
+ // /editor/<template_id> issues a 302 → /editor/<template_id>/fork/<id>
121
121
  // whenever the caller (or the template's default fork) has one. Follow the
122
- // redirect once by hand so we can read the fork id off the query string.
122
+ // redirect once by hand so we can read the fork id off the path.
123
123
  async function resolveUpstreamForkId(input) {
124
124
  try {
125
125
  const res = await fetch(`${input.host}/editor/${encodeURIComponent(input.templateId)}`, {
@@ -132,9 +132,9 @@ async function resolveUpstreamForkId(input) {
132
132
  const location = res.headers.get("location");
133
133
  if (location) {
134
134
  const target = new URL(location, input.host);
135
- const fork = target.searchParams.get("fork");
136
- if (fork)
137
- return fork;
135
+ const match = target.pathname.match(/^\/editor\/[^/]+\/fork\/([^/]+)\/?$/);
136
+ if (match)
137
+ return decodeURIComponent(match[1]);
138
138
  }
139
139
  }
140
140
  }
@@ -2400,8 +2400,22 @@ export const TEMPLATE_EDITOR_SHELL_STYLES = `
2400
2400
  .vf-editor-chat-composer-row {
2401
2401
  display: grid;
2402
2402
  grid-template-columns: auto minmax(0, 1fr) auto;
2403
- align-items: flex-start;
2404
- gap: 6px;
2403
+ grid-template-areas: "field field field" "attach . send";
2404
+ align-items: center;
2405
+ gap: 6px 8px;
2406
+ }
2407
+
2408
+ .vf-editor-chat-composer-row .vf-editor-chat-input {
2409
+ grid-area: field;
2410
+ }
2411
+
2412
+ .vf-editor-chat-composer-row .vf-editor-chat-attach-button {
2413
+ grid-area: attach;
2414
+ }
2415
+
2416
+ .vf-editor-chat-composer-row .vf-editor-chat-send-button {
2417
+ grid-area: send;
2418
+ justify-self: end;
2405
2419
  }
2406
2420
 
2407
2421
  .vf-editor-chat-attach-button,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mevdragon/vidfarm-devcli",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "Local bridge for the Vidfarm Trackpad Editor. `vidfarm serve <template_id>` boots the FULL editor on localhost (disk-backed records/storage, free in-process render); edit composition.html on disk (Claude Code, Codex, etc.) and the browser live-morphs it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1 @@
1
+ var C=n=>{let e,o=new Set,r=(u,d)=>{let f=typeof u=="function"?u(e):u;if(!Object.is(f,e)){let E=e;e=d??(typeof f!="object"||f===null)?f:Object.assign({},e,f),o.forEach(S=>S(e,E))}},i=()=>e,c={setState:r,getState:i,getInitialState:()=>m,subscribe:u=>(o.add(u),()=>o.delete(u))},m=e=n(r,i,c);return c},R=(n=>n?C(n):C);function F(n,e){let o;try{o=n()}catch{return}return{getItem:i=>{var t;let l=m=>m===null?null:JSON.parse(m,e?.reviver),c=(t=o.getItem(i))!=null?t:null;return c instanceof Promise?c.then(l):l(c)},setItem:(i,t)=>o.setItem(i,JSON.stringify(t,e?.replacer)),removeItem:i=>o.removeItem(i)}}var D=n=>e=>{try{let o=n(e);return o instanceof Promise?o:{then(r){return D(r)(o)},catch(r){return this}}}catch(o){return{then(r){return this},catch(r){return D(r)(o)}}}},H=(n,e)=>(o,r,i)=>{let t={storage:F(()=>window.localStorage),partialize:s=>s,version:0,merge:(s,g)=>({...g,...s}),...e},l=!1,c=0,m=new Set,u=new Set,d=t.storage;if(!d)return n((...s)=>{console.warn(`[zustand persist middleware] Unable to update item '${t.name}', the given storage is currently unavailable.`),o(...s)},r,i);let f=()=>{let s=t.partialize({...r()});return d.setItem(t.name,{state:s,version:t.version})},E=i.setState;i.setState=(s,g)=>(E(s,g),f());let S=n((...s)=>(o(...s),f()),r,i);i.getInitialState=()=>S;let y,O=()=>{var s,g;if(!d)return;let _=++c;l=!1,m.forEach(a=>{var v;return a((v=r())!=null?v:S)});let b=((g=t.onRehydrateStorage)==null?void 0:g.call(t,(s=r())!=null?s:S))||void 0;return D(d.getItem.bind(d))(t.name).then(a=>{if(a)if(typeof a.version=="number"&&a.version!==t.version){if(t.migrate){let v=t.migrate(a.state,a.version);return v instanceof Promise?v.then(I=>[!0,I]):[!0,v]}console.error("State loaded from storage couldn't be migrated since no migrate function was provided")}else return[!1,a.state];return[!1,void 0]}).then(a=>{var v;if(_!==c)return;let[I,L]=a;if(y=t.merge(L,(v=r())!=null?v:S),o(y,!0),I)return f()}).then(()=>{_===c&&(b?.(r(),void 0),y=r(),l=!0,u.forEach(a=>a(y)))}).catch(a=>{_===c&&b?.(void 0,a)})};return i.persist={setOptions:s=>{t={...t,...s},s.storage&&(d=s.storage)},clearStorage:()=>{d?.removeItem(t.name)},getOptions:()=>t,rehydrate:()=>O(),hasHydrated:()=>l,onHydrate:s=>(m.add(s),()=>{m.delete(s)}),onFinishHydration:s=>(u.add(s),()=>{u.delete(s)})},t.skipHydration||O(),y||S},x=H;function N(n){return R()(x(e=>({defaultFeed:null,setDefaultFeed:(o,r)=>e({defaultFeed:{items:o,fetchedAt:r}}),clear:()=>e({defaultFeed:null})}),{name:n,version:1,storage:F(()=>sessionStorage)}))}var A=2e4;function k(){let n=document.querySelector("[data-rk-discover-boot]");if(!n?.textContent)return{};try{return JSON.parse(n.textContent)||{}}catch{return{}}}var h=k(),T=h.accountId||"",j=h.feedEndpoint||"/discover/feed",p=N(`vidfarm.discover.feed.v1.${T||"anon"}`);if(h.feedInitial&&Array.isArray(h.feedInitial)){let n=h.feedFetchedAt?Date.parse(h.feedFetchedAt):Number.NaN;p.getState().setDefaultFeed(h.feedInitial,Number.isFinite(n)?n:Date.now())}function J(n){try{let e=new URL(j,window.location.href);return n&&e.searchParams.set("q",n),T&&e.searchParams.set("account",T),e.toString()}catch{return j}}async function w(n){let e=await fetch(J(n),{credentials:"same-origin",headers:{accept:"application/json"}});if(!e.ok)throw new Error(e.status===401?"Sign in to browse the template feed.":`Couldn't load templates (${e.status}).`);let o=await e.json();return Array.isArray(o?.templates)?o.templates:[]}function P(n,e){let o=(n||"").trim();if(o){e.onLoading?.(),w(o).then(t=>e.onResult(t,{fromCache:!1,stale:!1})).catch(t=>e.onError(t instanceof Error?t:new Error(String(t))));return}let r=p.getState().defaultFeed,i=Date.now();if(r&&r.items.length){let t=i-r.fetchedAt>A;e.onResult(r.items,{fromCache:!0,stale:t}),t&&w("").then(l=>{p.getState().setDefaultFeed(l,Date.now()),e.onResult(l,{fromCache:!1,stale:!1})}).catch(()=>{});return}e.onLoading?.(),w("").then(t=>{p.getState().setDefaultFeed(t,Date.now()),e.onResult(t,{fromCache:!1,stale:!1})}).catch(t=>e.onError(t instanceof Error?t:new Error(String(t))))}window.addEventListener("pageshow",n=>{let e=p.getState().defaultFeed;!e||!n.persisted&&Date.now()-e.fetchedAt<=A||w("").then(r=>{p.getState().setDefaultFeed(r,Date.now()),window.dispatchEvent(new CustomEvent("vidfarm:discover-feed-updated",{detail:{items:r}}))}).catch(()=>{})});window.__vidfarmDiscover={store:p,loadFeed:P,invalidate:()=>p.getState().clear()};window.dispatchEvent(new CustomEvent("vidfarm:discover-store-ready"));
@@ -0,0 +1,2 @@
1
+ var re=[{key:"files",label:"My Files",description:"Durable personal files \u2014 brand assets, characters, saved context. Vector-searchable via notes."},{key:"temp",label:"Temp",description:"Scratch space, auto-organized by date (YYYY-MM-DD) and auto-deleted after 30 days."},{key:"raws",label:"Raws",description:"The reusable clip / source-footage library, organized into folders by source."}];function Ie(o){return o==="files"||o==="temp"||o==="raws"}function oe(o){return String(o??"").split("/").map(n=>n.trim()).filter(Boolean).join("/")}function $(o){let n=String(o??"").split("/").map(v=>v.trim()).filter(Boolean);if(n.length===0)return{root:null,folderPath:"",segments:[]};let[l,...i]=n;return Ie(l)?{root:l,folderPath:i.join("/"),segments:i}:{root:null,folderPath:"",segments:n}}function ee(o,n,l){let i=[o,oe(n)];return l&&i.push(String(l).trim()),"/"+i.filter(Boolean).join("/")}var He="/api/v1/user/me";function s(o,n,l){let i=document.createElement(o);return n&&(i.className=n),l!=null&&(i.textContent=l),i}function Ae(o){if(!o||o<=0)return"";let n=["B","KB","MB","GB"],l=o,i=0;for(;l>=1024&&i<n.length-1;)l/=1024,i++;return(l>=10||i===0?Math.round(l):l.toFixed(1))+" "+n[i]}function Oe(o){if(typeof o!="number"||!(o>0))return"";o=Math.round(o);let n=Math.floor(o/60),l=o%60;return n+":"+(l<10?"0"+l:l)}function ye(o){let n=s("span","rk-aichat-fic "+(o.kind==="folder"?"rk-aichat-fic-folder":"rk-aichat-fic-file"));if(o.kind==="folder")return n.textContent="/",n;if(o.thumbUrl){let i=s("img");return i.src=o.thumbUrl,i.alt="",i.loading="lazy",n.appendChild(i),n}let l=o.contentType||"";if(o.viewUrl&&l.indexOf("image/")===0){let i=s("img");return i.src=o.viewUrl,i.alt="",i.loading="lazy",n.appendChild(i),n}if(o.viewUrl&&l.indexOf("video/")===0){let i=s("video");return i.src=o.viewUrl,i.muted=!0,i.playsInline=!0,n.appendChild(i),n}return n.textContent=(o.name.split(".").pop()||o.name.slice(0,1)||"?").slice(0,3).toUpperCase(),n}var ie;function be(o){let n=document.getElementById("rk-dir-toast");n||(n=s("div"),n.id="rk-dir-toast",n.className="rk-dir-toast",document.body.appendChild(n)),n.textContent=o,n.classList.add("is-on"),ie&&window.clearTimeout(ie),ie=window.setTimeout(()=>n&&n.classList.remove("is-on"),1800)}function Ue(o){return navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(o):new Promise((n,l)=>{try{let i=s("textarea");i.value=o,i.style.position="fixed",i.style.opacity="0",document.body.appendChild(i),i.select(),document.execCommand("copy"),document.body.removeChild(i),n()}catch(i){l(i)}})}var F=null;function we(o,n){F&&F();let l=s("div","rk-dir-menu");n.forEach(h=>{let H=s("button","rk-dir-menuitem"+(h.danger?" is-danger":""),h.label);H.type="button",H.addEventListener("click",D=>{D.stopPropagation(),y(),h.run()}),l.appendChild(H)}),document.body.appendChild(l);let i=o instanceof HTMLElement?o.getBoundingClientRect():o,v=l.offsetWidth,R=l.offsetHeight,j=i.bottom+4,E=i.right-v;E<8&&(E=i.left),j+R>window.innerHeight-8&&(j=Math.max(8,i.top-R-4)),l.style.top=j+"px",l.style.left=Math.max(8,E)+"px";function y(){l.remove(),document.removeEventListener("click",I,!0),document.removeEventListener("keydown",N,!0),window.removeEventListener("scroll",y,!0),F===y&&(F=null)}function I(h){l.contains(h.target)||y()}function N(h){h.key==="Escape"&&y()}setTimeout(()=>{document.addEventListener("click",I,!0),document.addEventListener("keydown",N,!0),window.addEventListener("scroll",y,!0)},0),F=y}function ve(o,n={}){let l=n.apiBase||He,i=n.roots&&n.roots.length?n.roots:["files","temp","raws"],v=i.length===1?i[0]:null,R=n.mode||"attach",j=n.showSearch!==!1,E=n.showMultiSelect!==!1,y=n.showUpload!=null?n.showUpload:R==="attach",I=n.primaryClick||"copyPath",N=window.__rkDirectoryHost||{},h=e=>(n.onAttach||N.onAttach||(()=>be("Attached "+e.length+" item(s)")))(e),H=(e,t)=>{let r=n.onPreview||N.onPreview;r?r(e,t):e[t]&&e[t].viewUrl&&window.open(e[t].viewUrl,"_blank","noopener")},D=e=>(n.onToast||N.onToast||be)(e),A=e=>Ue(e).then(()=>D("Copied "+e)).catch(()=>D("Couldn't copy path")),m=Ce(n.initialPath,v),b="idle",K=[],w=[],q=null,z=!1,g=new Map,J=!1,O=[],V="idle",L=!1;function Ce(e,t){let r=$(e||"");return r.root?ee(r.root,r.folderPath):t?"/"+t:"/"}o.classList.add("rk-dir"),o.innerHTML="";let ae=s("div","rk-dir-search"),Y=s("input","rk-dir-search-input");Y.type="search",Y.placeholder="Search files \u2014 meaning, name, or path\u2026",ae.appendChild(Y);let x=s("div","rk-aichat-crumbs rk-dir-crumbs"),T=s("div","rk-dir-selbar");T.hidden=!0;let p=s("div","rk-aichat-files-body rk-dir-body"),u=s("label","rk-aichat-drop rk-dir-drop");u.hidden=!0;let X=s("span","rk-aichat-drop-text","Drop files here"),M=s("input");M.type="file",M.multiple=!0,M.hidden=!0,u.appendChild(X),u.appendChild(M),j&&o.appendChild(ae),o.appendChild(x),E&&o.appendChild(T),o.appendChild(p),y&&o.appendChild(u);function le(e,t){let r=new URL(l+"/directory",window.location.href);return r.searchParams.set("path",e),t&&r.searchParams.set("offset",String(t)),r.toString()}function W(e){if(!L){if(!e&&b==="ready"){f();return}b="loading",f(),fetch(le(m),{credentials:"same-origin",headers:{accept:"application/json"}}).then(t=>{if(t.status===401||t.status===403)return b="auth",null;if(!t.ok)throw new Error("http "+t.status);return t.json()}).then(t=>{if(!L){if(!t){f();return}K=Ee(t.folders||[]),w=t.files||[],q=typeof t.next_offset=="number"?t.next_offset:null,b="ready",f()}}).catch(()=>{L||(b="error",f())})}}function Ee(e){return m!=="/"?e:e.filter(t=>i.indexOf(t.root)!==-1)}function De(){q==null||z||(z=!0,f(),fetch(le(m,q),{credentials:"same-origin",headers:{accept:"application/json"}}).then(e=>e.ok?e.json():null).then(e=>{if(z=!1,L||!e){f();return}w=w.concat(e.files||[]),q=typeof e.next_offset=="number"?e.next_offset:null,f()}).catch(()=>{z=!1,f()}))}let te;function Le(){let e=Y.value.trim();if(te&&window.clearTimeout(te),!e){J=!1,O=[],f();return}te=window.setTimeout(()=>xe(e),260)}function xe(e){J=!0,V="loading",f();let t=v?"/"+v:m!=="/"?m:"";fetch(l+"/directory/search",{method:"POST",credentials:"same-origin",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({query:e,path:t,mode:"auto",limit:40})}).then(r=>r.ok?r.json():null).then(r=>{L||(O=r&&r.results||[],V="ready",f())}).catch(()=>{L||(V="error",f())})}function S(e){let t=$(e);m=t.root?ee(t.root,t.folderPath):"/",b="idle",J=!1,Y.value="",W(!0)}function U(){return $(m).root}function G(){return $(m).folderPath}function Q(e){let t=U();if(!t){D("Open a root (/files, /temp, /raws) first");return}let r=(window.prompt("New folder name")||"").trim();if(!r)return;let d=oe(e??G()),c=ee(t,d?d+"/"+r:r);fetch(l+"/directory/folders",{method:"POST",credentials:"same-origin",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify({path:c})}).then(a=>a.ok?a.json():a.json().then(k=>Promise.reject(new Error(k.error||"http "+a.status)))).then(a=>{S(a.path||(t===U()?m:"/"+t)),D("Created "+(a.path||r))}).catch(a=>D(a.message||"Couldn't create folder"))}function Te(e,t){t?g.set(e.path,e):g.delete(e.path),(n.onSelectionChange||(()=>{}))(Array.from(g.values())),ce();let r=p.querySelector('[data-path="'+_e(e.path)+'"]');r&&r.classList.toggle("is-selected",t)}function se(){g.clear(),(n.onSelectionChange||(()=>{}))([]),f()}function ne(e,t,r){let d=s("button","rk-aichat-crumb"+(t?" is-current":""),e);return d.type="button",d.addEventListener("click",r),d}function Me(){x.innerHTML="";let e=U(),t=G();if(v||x.appendChild(ne("Files",m==="/",()=>S("/"))),!e)return;let r=(re.find(a=>a.key===e)||{label:e}).label;v||x.appendChild(de()),x.appendChild(ne(r,!t,()=>S("/"+e)));let d="";t.split("/").filter(Boolean).forEach((a,k,C)=>{x.appendChild(de()),d=d?d+"/"+a:a;let B="/"+e+"/"+d;x.appendChild(ne(a,k===C.length-1,()=>S(B)))});let c=s("button","rk-dir-newfolder","+ Folder");c.type="button",c.title="New folder here (or right-click)",c.addEventListener("click",()=>Q()),x.appendChild(c)}function de(){return s("span","rk-aichat-crumb-sep","/")}function ce(){if(!E)return;let e=g.size;if(T.hidden=e===0,e===0)return;if(T.innerHTML="",T.appendChild(s("span","rk-dir-selcount",e+" selected")),R==="attach"){let d=s("button","rk-dir-selbtn","Attach");d.type="button",d.addEventListener("click",()=>{h(Array.from(g.values())),se()}),T.appendChild(d)}let t=s("button","rk-dir-selbtn","Copy paths");t.type="button",t.addEventListener("click",()=>A(Array.from(g.values()).map(d=>d.path).join(`
2
+ `)));let r=s("button","rk-dir-selbtn is-ghost","Clear");r.type="button",r.addEventListener("click",se),T.appendChild(t),T.appendChild(r)}function pe(e){let t=s("input","rk-dir-check");return t.type="checkbox",t.checked=g.has(e.path),t.setAttribute("aria-label","Select "+e.name),t.addEventListener("click",r=>r.stopPropagation()),t.addEventListener("change",()=>Te(e,t.checked)),t}function ue(e){let t=s("div","rk-aichat-frow rk-dir-row"+(g.has(e.path)?" is-selected":""));t.setAttribute("data-path",e.path),E&&t.appendChild(pe(e));let r=s("button","rk-aichat-fmainbtn");r.type="button",r.title="Copy path "+e.path,r.appendChild(ye(e));let d=s("span","rk-aichat-fmain");d.appendChild(s("span","rk-aichat-fname",e.name));let c=e.meta&&typeof e.meta.description=="string"?e.meta.description:"";d.appendChild(s("span","rk-aichat-fmeta",c||"folder")),r.appendChild(d),r.addEventListener("click",()=>A(e.path)),t.appendChild(r);let a=s("button","rk-aichat-fadd rk-dir-open","Open \u203A");return a.type="button",a.title="Open folder",a.addEventListener("click",k=>{k.stopPropagation(),S(e.path)}),t.appendChild(a),t.appendChild(he(e,[{label:"Open folder",run:()=>S(e.path)},{label:"New subfolder",run:()=>Q(e.folderPath)},{label:"Copy path",run:()=>A(e.path)}])),t}function fe(e,t,r,d){let c=s("div","rk-aichat-frow rk-dir-row"+(g.has(e.path)?" is-selected":""));c.setAttribute("data-path",e.path),E&&c.appendChild(pe(e));let a=s("button","rk-aichat-fmainbtn");a.type="button";let k=I==="copyPath"?"Copy path ":I==="attach"?"Attach ":"Preview ";a.title=k+e.name,a.appendChild(ye(e));let C=s("span","rk-aichat-fmain");C.appendChild(s("span","rk-aichat-fname",e.name));let B=d?e.path:Oe(e.durationSec)||Ae(e.sizeBytes)||e.contentType||"file";C.appendChild(s("span","rk-aichat-fmeta",B)),a.appendChild(C),a.addEventListener("click",()=>{I==="attach"?h([e]):I==="preview"?H(t,r):A(e.path)}),c.appendChild(a);let P=[];return R==="attach"&&P.push({label:"Attach",run:()=>h([e])}),e.viewUrl&&P.push({label:"View",run:()=>window.open(e.viewUrl,"_blank","noopener")}),e.viewUrl&&P.push({label:"Preview",run:()=>H(t,r)}),P.push({label:"Copy path",run:()=>A(e.path)}),c.appendChild(he(e,P)),c}function he(e,t){let r=s("button","rk-dir-kebab","\u22EF");return r.type="button",r.setAttribute("aria-label","More actions"),r.addEventListener("click",d=>{d.stopPropagation(),we(r,t)}),r}function _(e,t,r){let d=s("div","rk-aichat-fstate");if(d.appendChild(s("div",void 0,e)),t){let c=s("a");c.href=t.href,c.textContent=t.label,d.appendChild(c)}if(r){let c=s("button",void 0,"Try again");c.type="button",c.addEventListener("click",()=>W(!0)),d.appendChild(c)}return d}function f(){if(!L){if(Me(),ce(),Pe(),p.innerHTML="",J){Se();return}if(b==="loading"&&!w.length&&!K.length){p.appendChild(_("Loading your files\u2026"));return}if(b==="auth"){p.appendChild(_("Sign in to browse your files.",{href:"/login",label:"Sign in"}));return}if(b==="error"){p.appendChild(_("Couldn't reach your files.",void 0,!0));return}if(!K.length&&!w.length){p.appendChild(_(m==="/"?"No files yet.":"This folder is empty."));return}if(K.forEach(e=>p.appendChild(ue(e))),w.forEach((e,t)=>p.appendChild(fe(e,w,t))),q!=null){let e=s("button","rk-aichat-loadmore",z?"Loading\u2026":"Load more");e.type="button",e.disabled=z,e.addEventListener("click",De),p.appendChild(e)}}}function Se(){if(V==="loading"&&!O.length){p.appendChild(_("Searching\u2026"));return}if(V==="error"){p.appendChild(_("Search failed."));return}if(!O.length){p.appendChild(_("No matches."));return}let e=O.filter(t=>t.kind!=="folder");O.forEach((t,r)=>{t.kind==="folder"?p.appendChild(ue(t)):p.appendChild(fe(t,e,e.indexOf(t),!0))})}let Z=!1;function me(){let e=U();return e==="files"?l+"/attachments/upload":e==="temp"?l+"/temporary-files/upload":null}function Pe(){if(!y)return;let e=me();if(u.hidden=!e||b==="auth",!u.hidden&&!Z){let t=G(),r=U()?(re.find(d=>d.key===U())||{label:""}).label:"";X.textContent="Drop files into "+r+(t?" / "+t:"")}}function ge(e){let t=me();if(!t||Z||!e||!e.length)return;Z=!0;let r=G(),d=Array.from(e),c=0,a=0;X.textContent="Uploading "+d.length+" file(s)\u2026";let k=C=>{if(C>=d.length){Z=!1,a&&D(a+" of "+d.length+" failed"),W(!0);return}let B=new FormData;B.append("file",d[C]),B.append("folder_path",r),fetch(t,{method:"POST",credentials:"same-origin",body:B}).then(P=>{if(!P.ok)throw new Error("http "+P.status);c++}).catch(()=>{a++}).then(()=>k(C+1))};k(0)}function Re(e){return K.concat(w,O).find(t=>t.path===e)}return p.addEventListener("contextmenu",e=>{let t=!!U(),r=e.target.closest(".rk-dir-row"),d={top:e.clientY,bottom:e.clientY,left:e.clientX,right:e.clientX},c=[],a=r?Re(r.getAttribute("data-path")||""):void 0;a&&a.kind==="folder"?(c.push({label:"Open folder",run:()=>S(a.path)}),t&&c.push({label:"New subfolder",run:()=>Q(a.folderPath)}),c.push({label:"Copy path",run:()=>A(a.path)})):a&&(R==="attach"&&c.push({label:"Attach",run:()=>h([a])}),a.viewUrl&&c.push({label:"View",run:()=>window.open(a.viewUrl,"_blank","noopener")}),a.viewUrl&&c.push({label:"Preview",run:()=>H(w,w.indexOf(a))}),c.push({label:"Copy path",run:()=>A(a.path)})),t&&c.push({label:"New folder here",run:()=>Q()}),c.length&&(e.preventDefault(),we(d,c))}),Y.addEventListener("input",Le),u.addEventListener("click",e=>{(e.target===u||e.target===X)&&M.click()}),M.addEventListener("change",()=>{ge(M.files),M.value=""}),u.addEventListener("dragover",e=>{e.preventDefault(),u.classList.add("is-drag")}),u.addEventListener("dragleave",e=>{e.target===u&&u.classList.remove("is-drag")}),u.addEventListener("drop",e=>{e.preventDefault(),u.classList.remove("is-drag"),e.dataTransfer&&e.dataTransfer.files&&ge(e.dataTransfer.files)}),W(!0),{refresh:()=>W(!0),navigate:S,getSelection:()=>Array.from(g.values()),destroy:()=>{L=!0,F&&F(),o.innerHTML=""}}}function _e(o){return window.CSS&&window.CSS.escape?window.CSS.escape(o):o.replace(/["\\]/g,"\\$&")}function Be(o){let n=o.dataset,l=(n.roots||"").split(",").map(i=>i.trim()).filter(Boolean);return{roots:l.length?l:void 0,initialPath:n.initialPath||void 0,mode:n.mode==="standalone"?"standalone":"attach",showSearch:n.showSearch!=="false",showMultiSelect:n.showMultiselect!=="false",showUpload:n.showUpload!=null?n.showUpload==="true":void 0,primaryClick:n.primaryClick||"copyPath"}}function ke(){Array.prototype.slice.call(document.querySelectorAll("[data-rk-directory]")).forEach(n=>{n.getAttribute("data-rk-dir-mounted")!=="1"&&(n.setAttribute("data-rk-dir-mounted","1"),window.__rkDirectoryInstance=ve(n,Be(n)))})}window.__vidfarmDirectory={mount:ve};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",ke):ke();window.dispatchEvent(new CustomEvent("vidfarm:directory-ready"));export{ve as createDirectoryExplorer};