@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.
- package/.agents/skills/vidfarm-media/SKILL.md +43 -6
- package/SKILL.director.md +37 -23
- package/SKILL.platform.md +6 -6
- package/demo/dist/app.js +69 -69
- package/demo/dist/favicon.ico +0 -0
- package/dist/src/app.js +1832 -213
- package/dist/src/cli.js +238 -17
- package/dist/src/devcli/clip-store.js +29 -2
- package/dist/src/devcli/clips.js +116 -73
- package/dist/src/devcli/composition-edit.js +262 -0
- package/dist/src/devcli/timeline-edit.js +283 -0
- package/dist/src/editor-chat.js +29 -6
- package/dist/src/frontend/discover-client.js +130 -0
- package/dist/src/frontend/discover-store.js +23 -0
- package/dist/src/frontend/file-directory.js +744 -0
- package/dist/src/frontend/template-editor-chat.js +22 -19
- package/dist/src/landing-page.js +24 -7
- package/dist/src/page-shell.js +25 -1
- package/dist/src/reskin/agency-page.js +214 -170
- package/dist/src/reskin/calendar-page.js +503 -187
- package/dist/src/reskin/chat-page.js +739 -299
- package/dist/src/reskin/discover-page.js +1450 -279
- package/dist/src/reskin/document.js +1392 -16
- package/dist/src/reskin/help-page.js +139 -100
- package/dist/src/reskin/index-page.js +1 -1
- package/dist/src/reskin/inpaint-page.js +547 -0
- package/dist/src/reskin/job-runs-page.js +355 -127
- package/dist/src/reskin/library-page.js +1188 -317
- package/dist/src/reskin/login-page.js +124 -87
- package/dist/src/reskin/pricing-page.js +197 -166
- package/dist/src/reskin/settings-page.js +566 -187
- package/dist/src/reskin/theme.js +434 -17
- package/dist/src/services/clip-curation/gemini.js +5 -0
- package/dist/src/services/clip-curation/hunt.js +79 -1
- package/dist/src/services/clip-curation/index.js +2 -1
- package/dist/src/services/clip-curation/local-agent.js +4 -3
- package/dist/src/services/clip-curation/media-select.js +85 -0
- package/dist/src/services/clip-curation/query.js +5 -1
- package/dist/src/services/clip-curation/refine.js +50 -20
- package/dist/src/services/clip-curation/scan.js +10 -3
- package/dist/src/services/clip-curation/taxonomy.js +3 -1
- package/dist/src/services/clip-curation/taxonomy.v1.json +13 -1
- package/dist/src/services/clip-records.js +14 -1
- package/dist/src/services/clip-search.js +43 -13
- package/dist/src/services/file-directory.js +114 -0
- package/dist/src/services/storage.js +24 -1
- package/dist/src/services/upstream.js +5 -5
- package/dist/src/template-editor-shell.js +16 -2
- package/package.json +1 -1
- package/public/assets/discover-client-app.js +1 -0
- package/public/assets/file-directory-app.js +2 -0
- package/public/assets/homepage-client-app.js +12 -12
- package/public/assets/page-runtime-client-app.js +24 -24
- package/src/assets/favicon.ico +0 -0
- 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
|
|
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
|
|
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
|
|
136
|
-
if (
|
|
137
|
-
return
|
|
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
|
-
|
|
2404
|
-
|
|
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.
|
|
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};
|