@mevdragon/vidfarm-devcli 0.20.6 → 0.20.9
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 +15 -1
- package/.agents/skills/vidfarm-director/SKILL.md +106 -0
- package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
- package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
- package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
- package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +245 -0
- package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
- package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
- package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
- package/.agents/skills/vidfarm-director/references/primitives.md +308 -0
- package/SKILL.director.md +517 -250
- package/dist/src/cli.js +563 -99
- package/dist/src/devcli/clips.js +29 -12
- package/dist/src/devcli/doctor.js +2 -2
- package/dist/src/devcli/local-backend.js +46 -2
- package/dist/src/services/clip-curation/cost.js +5 -1
- package/dist/src/services/clip-curation/gemini.js +14 -2
- package/dist/src/services/clip-curation/hunt.js +56 -0
- package/dist/src/services/clip-curation/index.js +1 -1
- package/dist/src/services/clip-curation/scan.js +5 -3
- package/package.json +30 -10
- package/SKILL.platform.md +0 -432
- package/demo/README.md +0 -28
- package/demo/dist/app.css +0 -1
- package/demo/dist/app.js +0 -1850
- package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
- package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
- package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
- package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
- package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
- package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
- package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
- package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +0 -7
- package/demo/dist/icons/timeline/captions.svg +0 -5
- package/demo/dist/icons/timeline/composition.svg +0 -12
- package/demo/dist/icons/timeline/image.svg +0 -18
- package/demo/dist/icons/timeline/music.svg +0 -10
- package/demo/dist/icons/timeline/text.svg +0 -3
- package/demo/dist/index.html +0 -15
- package/dist/src/account-pages-legacy.js +0 -9396
- package/dist/src/account-pages.js +0 -61
- package/dist/src/app.js +0 -21603
- package/dist/src/composition-runtime.js +0 -1053
- package/dist/src/config.js +0 -217
- package/dist/src/context.js +0 -447
- package/dist/src/dev-app-legacy.js +0 -739
- package/dist/src/dev-app.js +0 -6
- package/dist/src/devcli/migrate-local.js +0 -140
- package/dist/src/devcli/sync.js +0 -368
- package/dist/src/domain.js +0 -5
- package/dist/src/editor-chat-history.js +0 -82
- package/dist/src/editor-chat.js +0 -828
- package/dist/src/editor-dark-theme.js +0 -1128
- package/dist/src/frontend/debug.js +0 -71
- package/dist/src/frontend/discover-client.js +0 -130
- package/dist/src/frontend/discover-store.js +0 -23
- package/dist/src/frontend/file-directory.js +0 -1018
- package/dist/src/frontend/flockposter-cache-store.js +0 -124
- package/dist/src/frontend/homepage-client.js +0 -446
- package/dist/src/frontend/homepage-shared.js +0 -201
- package/dist/src/frontend/homepage-store.js +0 -66
- package/dist/src/frontend/homepage-view.js +0 -705
- package/dist/src/frontend/page-runtime-client.js +0 -132
- package/dist/src/frontend/page-runtime-store.js +0 -9
- package/dist/src/frontend/sentry.js +0 -42
- package/dist/src/frontend/template-editor-chat.js +0 -4181
- package/dist/src/help-page.js +0 -346
- package/dist/src/homepage.js +0 -1458
- package/dist/src/index.js +0 -16
- package/dist/src/instrument.js +0 -30
- package/dist/src/landing-page.js +0 -384
- package/dist/src/page-runtime.js +0 -2
- package/dist/src/page-shell.js +0 -1452
- package/dist/src/primitive-context.js +0 -416
- package/dist/src/primitive-registry.js +0 -3940
- package/dist/src/primitive-sdk.js +0 -4
- package/dist/src/primitives/hyperframes-media.js +0 -108
- package/dist/src/react-page-shell.js +0 -35
- package/dist/src/ready-post-schedule-component.js +0 -1540
- package/dist/src/registry.js +0 -296
- package/dist/src/reskin/agency-page.js +0 -299
- package/dist/src/reskin/calendar-page.js +0 -568
- package/dist/src/reskin/chat-page.js +0 -942
- package/dist/src/reskin/discover-page.js +0 -1788
- package/dist/src/reskin/document.js +0 -1587
- package/dist/src/reskin/help-page.js +0 -357
- package/dist/src/reskin/index-page.js +0 -62
- package/dist/src/reskin/inpaint-clipper-page.js +0 -890
- package/dist/src/reskin/inpaint-page.js +0 -2554
- package/dist/src/reskin/inpaint-video-page.js +0 -1339
- package/dist/src/reskin/job-runs-page.js +0 -477
- package/dist/src/reskin/library-page.js +0 -1634
- package/dist/src/reskin/login-page.js +0 -262
- package/dist/src/reskin/portfolio-page.js +0 -687
- package/dist/src/reskin/pricing-page.js +0 -390
- package/dist/src/reskin/settings-page.js +0 -732
- package/dist/src/reskin/theme.js +0 -711
- package/dist/src/runtime.js +0 -35
- package/dist/src/services/api-call-history.js +0 -249
- package/dist/src/services/auth.js +0 -152
- package/dist/src/services/billing-pricing.js +0 -39
- package/dist/src/services/billing.js +0 -241
- package/dist/src/services/cast.js +0 -127
- package/dist/src/services/chat-threads.js +0 -92
- package/dist/src/services/clip-records.js +0 -250
- package/dist/src/services/clip-search.js +0 -77
- package/dist/src/services/clip-vectors.js +0 -125
- package/dist/src/services/composition-sanitize.js +0 -124
- package/dist/src/services/composition-watch.js +0 -79
- package/dist/src/services/elevenlabs.js +0 -222
- package/dist/src/services/file-directory.js +0 -117
- package/dist/src/services/fork-access.js +0 -93
- package/dist/src/services/fork-manifest.js +0 -42
- package/dist/src/services/ghostcut.js +0 -179
- package/dist/src/services/hyperframes.js +0 -3654
- package/dist/src/services/job-capacity.js +0 -14
- package/dist/src/services/job-logs.js +0 -197
- package/dist/src/services/jobs.js +0 -136
- package/dist/src/services/local-dynamo.js +0 -0
- package/dist/src/services/media-processing.js +0 -766
- package/dist/src/services/primitive-media-lambda.js +0 -280
- package/dist/src/services/providers.js +0 -2748
- package/dist/src/services/rate-limits.js +0 -262
- package/dist/src/services/scene-annotations.js +0 -32
- package/dist/src/services/serverless-auth.js +0 -382
- package/dist/src/services/serverless-jobs.js +0 -1084
- package/dist/src/services/serverless-provider-keys.js +0 -409
- package/dist/src/services/serverless-records.js +0 -1515
- package/dist/src/services/serverless-template-configs.js +0 -75
- package/dist/src/services/storage.js +0 -461
- package/dist/src/services/swipe-customize.js +0 -437
- package/dist/src/services/template-certification.js +0 -413
- package/dist/src/services/template-loader.js +0 -99
- package/dist/src/services/template-runtime-bundles.js +0 -217
- package/dist/src/services/template-sources.js +0 -1017
- package/dist/src/services/upstream.js +0 -248
- package/dist/src/services/video-normalization.js +0 -2
- package/dist/src/services/webhooks.js +0 -62
- package/dist/src/template-editor-pages.js +0 -2576
- package/dist/src/template-editor-shell.js +0 -2893
- package/dist/src/template-sdk.js +0 -4
- package/dist/src/worker.js +0 -17
- package/public/assets/discover-client-app.js +0 -1
- package/public/assets/file-directory-app.js +0 -3
- package/public/assets/homepage-app.js +0 -54
- package/public/assets/homepage-client-app.js +0 -80
- package/public/assets/page-runtime-client-app.js +0 -94
- package/public/assets/placeholders/scene-placeholder.png +0 -0
- package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
- package/src/assets/SELLING_WITH_HOOKS.md +0 -377
- package/src/assets/SELLING_WITH_VSLS.md +0 -606
- package/src/assets/favicon.ico +0 -0
- package/src/assets/logo-vidfarm.png +0 -0
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { createStore } from "zustand/vanilla";
|
|
2
|
-
export const FLOCKPOSTER_CACHE_STORAGE_KEY = "vidfarm:flockposter-cache:v1";
|
|
3
|
-
const PUBLIC_SCOPE = "public";
|
|
4
|
-
const emptySnapshot = {
|
|
5
|
-
channels: [],
|
|
6
|
-
posts: [],
|
|
7
|
-
channelsUpdatedAt: null,
|
|
8
|
-
calendarUpdatedAt: null,
|
|
9
|
-
calendarRangeStart: null,
|
|
10
|
-
calendarRangeEnd: null
|
|
11
|
-
};
|
|
12
|
-
export function getFlockPosterCacheStorageKey(userId) {
|
|
13
|
-
return `${FLOCKPOSTER_CACHE_STORAGE_KEY}_${userId?.trim() || PUBLIC_SCOPE}`;
|
|
14
|
-
}
|
|
15
|
-
export function readFlockPosterCacheSnapshot(userId) {
|
|
16
|
-
if (typeof window === "undefined") {
|
|
17
|
-
return emptySnapshot;
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
const raw = window.localStorage.getItem(getFlockPosterCacheStorageKey(userId));
|
|
21
|
-
if (!raw) {
|
|
22
|
-
return emptySnapshot;
|
|
23
|
-
}
|
|
24
|
-
const parsed = JSON.parse(raw);
|
|
25
|
-
if (!parsed || typeof parsed !== "object") {
|
|
26
|
-
return emptySnapshot;
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
channels: Array.isArray(parsed.channels) ? parsed.channels : [],
|
|
30
|
-
posts: Array.isArray(parsed.posts) ? parsed.posts : [],
|
|
31
|
-
channelsUpdatedAt: typeof parsed.channelsUpdatedAt === "string" ? parsed.channelsUpdatedAt : null,
|
|
32
|
-
calendarUpdatedAt: typeof parsed.calendarUpdatedAt === "string" ? parsed.calendarUpdatedAt : null,
|
|
33
|
-
calendarRangeStart: typeof parsed.calendarRangeStart === "string" ? parsed.calendarRangeStart : null,
|
|
34
|
-
calendarRangeEnd: typeof parsed.calendarRangeEnd === "string" ? parsed.calendarRangeEnd : null
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return emptySnapshot;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function writeFlockPosterCacheSnapshot(snapshot, userId) {
|
|
42
|
-
if (typeof window === "undefined") {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
window.localStorage.setItem(getFlockPosterCacheStorageKey(userId), JSON.stringify(snapshot));
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
// Ignore storage failures and keep in-memory state only.
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
export function createFlockPosterCacheStore(userId) {
|
|
53
|
-
const initial = readFlockPosterCacheSnapshot(userId);
|
|
54
|
-
return createStore((set, get) => ({
|
|
55
|
-
...initial,
|
|
56
|
-
setChannelsData: (channels) => {
|
|
57
|
-
const current = get();
|
|
58
|
-
const next = {
|
|
59
|
-
...current,
|
|
60
|
-
channels: [
|
|
61
|
-
...current.channels.filter(isEmailChannel),
|
|
62
|
-
...channels.filter((channel) => !isEmailChannel(channel))
|
|
63
|
-
],
|
|
64
|
-
channelsUpdatedAt: new Date().toISOString()
|
|
65
|
-
};
|
|
66
|
-
writeFlockPosterCacheSnapshot(next, userId);
|
|
67
|
-
set(next);
|
|
68
|
-
},
|
|
69
|
-
setCalendarData: (input) => {
|
|
70
|
-
const current = get();
|
|
71
|
-
const mergedChannels = dedupeChannels([...current.channels, ...input.channels]);
|
|
72
|
-
const mergedPosts = input.replaceRange
|
|
73
|
-
? mergePostsForRange(current.posts, input.posts, input.rangeStart, input.rangeEnd)
|
|
74
|
-
: dedupePosts([...current.posts, ...input.posts]);
|
|
75
|
-
const next = {
|
|
76
|
-
...current,
|
|
77
|
-
channels: mergedChannels,
|
|
78
|
-
posts: mergedPosts,
|
|
79
|
-
channelsUpdatedAt: new Date().toISOString(),
|
|
80
|
-
calendarUpdatedAt: new Date().toISOString(),
|
|
81
|
-
calendarRangeStart: expandRangeStart(current.calendarRangeStart, input.rangeStart),
|
|
82
|
-
calendarRangeEnd: expandRangeEnd(current.calendarRangeEnd, input.rangeEnd)
|
|
83
|
-
};
|
|
84
|
-
writeFlockPosterCacheSnapshot(next, userId);
|
|
85
|
-
set(next);
|
|
86
|
-
}
|
|
87
|
-
}));
|
|
88
|
-
}
|
|
89
|
-
function dedupeChannels(channels) {
|
|
90
|
-
return [...new Map(channels.map((channel) => [channel.id, channel])).values()];
|
|
91
|
-
}
|
|
92
|
-
function isEmailChannel(channel) {
|
|
93
|
-
return channel.id.startsWith("email:") || channel.platform?.toLowerCase() === "email";
|
|
94
|
-
}
|
|
95
|
-
function dedupePosts(posts) {
|
|
96
|
-
return [...new Map(posts.map((post) => [post.id, post])).values()];
|
|
97
|
-
}
|
|
98
|
-
function mergePostsForRange(currentPosts, nextPosts, rangeStart, rangeEnd) {
|
|
99
|
-
const startTime = Date.parse(rangeStart);
|
|
100
|
-
const endTime = Date.parse(rangeEnd);
|
|
101
|
-
if (Number.isNaN(startTime) || Number.isNaN(endTime)) {
|
|
102
|
-
return dedupePosts([...currentPosts, ...nextPosts]);
|
|
103
|
-
}
|
|
104
|
-
return dedupePosts([
|
|
105
|
-
...currentPosts.filter((post) => {
|
|
106
|
-
const scheduledTime = Date.parse(post.scheduledAt);
|
|
107
|
-
return Number.isNaN(scheduledTime) || scheduledTime < startTime || scheduledTime > endTime;
|
|
108
|
-
}),
|
|
109
|
-
...nextPosts
|
|
110
|
-
]);
|
|
111
|
-
}
|
|
112
|
-
function expandRangeStart(current, next) {
|
|
113
|
-
if (!current) {
|
|
114
|
-
return next;
|
|
115
|
-
}
|
|
116
|
-
return Date.parse(next) < Date.parse(current) ? next : current;
|
|
117
|
-
}
|
|
118
|
-
function expandRangeEnd(current, next) {
|
|
119
|
-
if (!current) {
|
|
120
|
-
return next;
|
|
121
|
-
}
|
|
122
|
-
return Date.parse(next) > Date.parse(current) ? next : current;
|
|
123
|
-
}
|
|
124
|
-
//# sourceMappingURL=flockposter-cache-store.js.map
|
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import "./sentry.js";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { hydrateRoot } from "react-dom/client";
|
|
5
|
-
import * as Sentry from "@sentry/react";
|
|
6
|
-
import { useStore } from "zustand";
|
|
7
|
-
import { debugError, debugLog, installGlobalDebugHooks } from "./debug.js";
|
|
8
|
-
import { HomepageShell } from "./homepage-view.js";
|
|
9
|
-
import { createHomepageStore } from "./homepage-store.js";
|
|
10
|
-
installGlobalDebugHooks();
|
|
11
|
-
const bookmarkStorageKey = "vidfarm:discover-bookmarks:v1";
|
|
12
|
-
const publicBookmarkScope = "public";
|
|
13
|
-
function readBoot() {
|
|
14
|
-
const element = document.getElementById("homepage-boot");
|
|
15
|
-
if (!element?.textContent) {
|
|
16
|
-
return {
|
|
17
|
-
templates: [],
|
|
18
|
-
account: { isLoggedIn: false },
|
|
19
|
-
loadEndpoint: "/discover/feed",
|
|
20
|
-
skillEndpointPrefix: "/discover/skills/"
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
return JSON.parse(element.textContent);
|
|
24
|
-
}
|
|
25
|
-
function resolveBookmarkStorageKey(userId) {
|
|
26
|
-
return `${bookmarkStorageKey}_${userId?.trim() || publicBookmarkScope}`;
|
|
27
|
-
}
|
|
28
|
-
function readBookmarks(userId) {
|
|
29
|
-
try {
|
|
30
|
-
const raw = window.localStorage.getItem(resolveBookmarkStorageKey(userId));
|
|
31
|
-
if (!raw) {
|
|
32
|
-
return new Set();
|
|
33
|
-
}
|
|
34
|
-
const parsed = JSON.parse(raw);
|
|
35
|
-
if (!Array.isArray(parsed)) {
|
|
36
|
-
return new Set();
|
|
37
|
-
}
|
|
38
|
-
return new Set(parsed.filter((value) => typeof value === "string"));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return new Set();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function writeBookmarks(bookmarks, userId) {
|
|
45
|
-
try {
|
|
46
|
-
window.localStorage.setItem(resolveBookmarkStorageKey(userId), JSON.stringify(Array.from(bookmarks)));
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
// Ignore storage failures and keep in-memory state.
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function filterTemplates(input) {
|
|
53
|
-
const query = input.searchQuery.trim().toLowerCase();
|
|
54
|
-
return input.templates.filter((template) => {
|
|
55
|
-
const matchesSearch = !query || `${template.templateId} ${template.title} ${template.slugId} ${template.difficulty}`.toLowerCase().includes(query);
|
|
56
|
-
const matchesBookmarks = !input.bookmarksOnly || input.bookmarks.has(template.templateId);
|
|
57
|
-
return matchesSearch && matchesBookmarks;
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
// The swipe API serializes snake_case; the store/view use camelCase SwipeCard.
|
|
61
|
-
function mapSwipeCard(raw) {
|
|
62
|
-
return {
|
|
63
|
-
id: String(raw?.id ?? ""),
|
|
64
|
-
templateId: String(raw?.template_id ?? ""),
|
|
65
|
-
forkId: raw?.fork_id ?? null,
|
|
66
|
-
sourceTitle: raw?.source_title ?? null,
|
|
67
|
-
title: String(raw?.title ?? raw?.source_title ?? "Untitled"),
|
|
68
|
-
caption: String(raw?.caption ?? ""),
|
|
69
|
-
pinnedComment: raw?.pinned_comment ?? null,
|
|
70
|
-
previewUrl: raw?.preview_url ?? null,
|
|
71
|
-
durationSeconds: typeof raw?.duration_seconds === "number" ? raw.duration_seconds : null,
|
|
72
|
-
status: String(raw?.status ?? "ready")
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function HomepageClientApp({ boot }) {
|
|
76
|
-
const store = useMemo(() => createHomepageStore(boot), [boot]);
|
|
77
|
-
const templates = useStore(store, (state) => state.templates);
|
|
78
|
-
const searchQuery = useStore(store, (state) => state.searchQuery);
|
|
79
|
-
const bookmarksOnly = useStore(store, (state) => state.bookmarksOnly);
|
|
80
|
-
const bookmarks = useStore(store, (state) => state.bookmarks);
|
|
81
|
-
const loading = useStore(store, (state) => state.loading);
|
|
82
|
-
const error = useStore(store, (state) => state.error);
|
|
83
|
-
const view = useStore(store, (state) => state.view);
|
|
84
|
-
const popularTemplates = useStore(store, (state) => state.popularTemplates);
|
|
85
|
-
const popularLoading = useStore(store, (state) => state.popularLoading);
|
|
86
|
-
const popularError = useStore(store, (state) => state.popularError);
|
|
87
|
-
const swipeStack = useStore(store, (state) => state.swipeStack);
|
|
88
|
-
const swipeLoading = useStore(store, (state) => state.swipeLoading);
|
|
89
|
-
const swipeLoaded = useStore(store, (state) => state.swipeLoaded);
|
|
90
|
-
const swipeError = useStore(store, (state) => state.swipeError);
|
|
91
|
-
const swipeCustomizing = useStore(store, (state) => state.swipeCustomizing);
|
|
92
|
-
const swipeTargetDepth = useStore(store, (state) => state.swipeTargetDepth);
|
|
93
|
-
const swipeProductSet = useStore(store, (state) => state.swipeProductSet);
|
|
94
|
-
const swipeRefilling = useStore(store, (state) => state.swipeRefilling);
|
|
95
|
-
const swipeFillingRef = useRef(false);
|
|
96
|
-
const [bookmarksReady, setBookmarksReady] = useState(false);
|
|
97
|
-
const visibleTemplates = useMemo(() => filterTemplates({ templates, searchQuery, bookmarksOnly, bookmarks }), [templates, searchQuery, bookmarksOnly, bookmarks]);
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
store.getState().setBookmarks(readBookmarks(boot.account.userId));
|
|
100
|
-
setBookmarksReady(true);
|
|
101
|
-
}, [boot.account.userId, store]);
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (!bookmarksReady) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
writeBookmarks(bookmarks, boot.account.userId);
|
|
107
|
-
}, [boot.account.userId, bookmarks, bookmarksReady]);
|
|
108
|
-
const refreshFeed = useCallback(async (options) => {
|
|
109
|
-
if (options?.showLoading) {
|
|
110
|
-
store.getState().setLoading(true);
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
const response = await fetch(boot.loadEndpoint, {
|
|
114
|
-
credentials: "same-origin",
|
|
115
|
-
cache: "no-store",
|
|
116
|
-
headers: { Accept: "application/json" }
|
|
117
|
-
});
|
|
118
|
-
const payload = await response.json().catch(() => ({}));
|
|
119
|
-
if (!response.ok) {
|
|
120
|
-
throw new Error(typeof payload.error === "string" && payload.error ? payload.error : "Unable to load formats.");
|
|
121
|
-
}
|
|
122
|
-
store.getState().setTemplates(Array.isArray(payload.templates) ? payload.templates : []);
|
|
123
|
-
}
|
|
124
|
-
catch (fetchError) {
|
|
125
|
-
if (options?.showLoading) {
|
|
126
|
-
store.getState().setError(fetchError instanceof Error ? fetchError.message : "Unable to load formats.");
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
// Background refresh failure keeps the current list on screen.
|
|
130
|
-
debugError("homepage.feed_refresh_failed", { message: fetchError instanceof Error ? fetchError.message : String(fetchError) });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}, [boot.loadEndpoint, store]);
|
|
134
|
-
useEffect(() => {
|
|
135
|
-
if (boot.templates.length > 0) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
void refreshFeed({ showLoading: true });
|
|
139
|
-
}, [boot.templates.length, refreshFeed]);
|
|
140
|
-
// While one of the user's submissions is still downloading, poll the feed so
|
|
141
|
-
// the "Processing" card flips to an openable template without a manual reload.
|
|
142
|
-
const hasProcessingTemplates = templates.some((template) => template.status === "processing");
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
if (!hasProcessingTemplates) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const timer = window.setInterval(() => {
|
|
148
|
-
void refreshFeed();
|
|
149
|
-
}, 8000);
|
|
150
|
-
return () => window.clearInterval(timer);
|
|
151
|
-
}, [hasProcessingTemplates, refreshFeed]);
|
|
152
|
-
// The curated Popular picks come from a separate endpoint and are the same
|
|
153
|
-
// for everyone, so they're fetched lazily the first time the toggle is opened
|
|
154
|
-
// (and re-fetched, without a spinner, while any pick is still downloading).
|
|
155
|
-
const refreshPopular = useCallback(async (options) => {
|
|
156
|
-
if (options?.showLoading) {
|
|
157
|
-
store.getState().setPopularLoading(true);
|
|
158
|
-
}
|
|
159
|
-
try {
|
|
160
|
-
const response = await fetch("/discover/popular", {
|
|
161
|
-
credentials: "same-origin",
|
|
162
|
-
cache: "no-store",
|
|
163
|
-
headers: { Accept: "application/json" }
|
|
164
|
-
});
|
|
165
|
-
const payload = await response.json().catch(() => ({}));
|
|
166
|
-
if (!response.ok) {
|
|
167
|
-
throw new Error(typeof payload.error === "string" && payload.error ? payload.error : "Unable to load popular formats.");
|
|
168
|
-
}
|
|
169
|
-
store.getState().setPopularTemplates(Array.isArray(payload.templates) ? payload.templates : []);
|
|
170
|
-
}
|
|
171
|
-
catch (fetchError) {
|
|
172
|
-
if (options?.showLoading) {
|
|
173
|
-
store.getState().setPopularError(fetchError instanceof Error ? fetchError.message : "Unable to load popular formats.");
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
debugError("homepage.popular_refresh_failed", { message: fetchError instanceof Error ? fetchError.message : String(fetchError) });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}, [store]);
|
|
180
|
-
const popularLoaded = useStore(store, (state) => state.popularLoaded);
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
if (view !== "popular" || popularLoaded) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
void refreshPopular({ showLoading: true });
|
|
186
|
-
}, [view, popularLoaded, refreshPopular]);
|
|
187
|
-
const hasProcessingPopular = view === "popular" && popularTemplates.some((template) => template.status === "processing");
|
|
188
|
-
useEffect(() => {
|
|
189
|
-
if (!hasProcessingPopular) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const timer = window.setInterval(() => {
|
|
193
|
-
void refreshPopular();
|
|
194
|
-
}, 8000);
|
|
195
|
-
return () => window.clearInterval(timer);
|
|
196
|
-
}, [hasProcessingPopular, refreshPopular]);
|
|
197
|
-
// Swipe deck: the customer's own product, auto-recut from decomposed templates.
|
|
198
|
-
const refreshSwipeStack = useCallback(async (options) => {
|
|
199
|
-
if (options?.showLoading) {
|
|
200
|
-
store.getState().setSwipeLoading(true);
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
const response = await fetch("/discover/swipe/stack", {
|
|
204
|
-
credentials: "same-origin",
|
|
205
|
-
cache: "no-store",
|
|
206
|
-
headers: { Accept: "application/json" }
|
|
207
|
-
});
|
|
208
|
-
const payload = await response.json().catch(() => ({}));
|
|
209
|
-
if (!response.ok) {
|
|
210
|
-
throw new Error(typeof payload.error === "string" && payload.error ? payload.error : "Unable to load your swipe deck.");
|
|
211
|
-
}
|
|
212
|
-
const stack = Array.isArray(payload.stack) ? payload.stack.map(mapSwipeCard) : [];
|
|
213
|
-
store.getState().setSwipeStack(stack, {
|
|
214
|
-
customizing: typeof payload.customizing === "number" ? payload.customizing : 0,
|
|
215
|
-
targetDepth: typeof payload.target_depth === "number" ? payload.target_depth : undefined,
|
|
216
|
-
productSet: typeof payload.product_description_set === "boolean" ? payload.product_description_set : undefined
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
catch (fetchError) {
|
|
220
|
-
if (options?.showLoading) {
|
|
221
|
-
store.getState().setSwipeError(fetchError instanceof Error ? fetchError.message : "Unable to load your swipe deck.");
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
debugError("homepage.swipe_refresh_failed", { message: fetchError instanceof Error ? fetchError.message : String(fetchError) });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}, [store]);
|
|
228
|
-
// Progressively customize until the buffer hits target (server-capped), one
|
|
229
|
-
// per request. Each finished variant streams into the deck as it lands.
|
|
230
|
-
const fillSwipeDeck = useCallback(async () => {
|
|
231
|
-
if (swipeFillingRef.current)
|
|
232
|
-
return;
|
|
233
|
-
swipeFillingRef.current = true;
|
|
234
|
-
store.getState().setSwipeRefilling(true);
|
|
235
|
-
try {
|
|
236
|
-
for (let i = 0; i < 8; i += 1) {
|
|
237
|
-
const response = await fetch("/discover/swipe/refill", {
|
|
238
|
-
method: "POST",
|
|
239
|
-
credentials: "same-origin",
|
|
240
|
-
headers: { Accept: "application/json" }
|
|
241
|
-
});
|
|
242
|
-
const payload = await response.json().catch(() => ({}));
|
|
243
|
-
if (!response.ok) {
|
|
244
|
-
if (payload?.need_product_description) {
|
|
245
|
-
store.getState().setSwipeMeta({ productSet: false });
|
|
246
|
-
}
|
|
247
|
-
else {
|
|
248
|
-
store.getState().setSwipeError(typeof payload.error === "string" && payload.error ? payload.error : "Customization failed.");
|
|
249
|
-
}
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
if (payload.filled && payload.record) {
|
|
253
|
-
store.getState().appendSwipeCard(mapSwipeCard(payload.record));
|
|
254
|
-
}
|
|
255
|
-
if (!payload.filled) {
|
|
256
|
-
break; // at_target or no_eligible
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
catch (fillError) {
|
|
261
|
-
debugError("homepage.swipe_refill_failed", { message: fillError instanceof Error ? fillError.message : String(fillError) });
|
|
262
|
-
}
|
|
263
|
-
finally {
|
|
264
|
-
swipeFillingRef.current = false;
|
|
265
|
-
store.getState().setSwipeRefilling(false);
|
|
266
|
-
// Re-sync the customizing/buffered counts after filling settles.
|
|
267
|
-
void refreshSwipeStack();
|
|
268
|
-
}
|
|
269
|
-
}, [store, refreshSwipeStack]);
|
|
270
|
-
// First open of Swipe → load the current deck, then top it up.
|
|
271
|
-
useEffect(() => {
|
|
272
|
-
if (view !== "swipe" || swipeLoaded) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
void (async () => {
|
|
276
|
-
await refreshSwipeStack({ showLoading: true });
|
|
277
|
-
const state = store.getState();
|
|
278
|
-
if (state.swipeProductSet && state.swipeStack.length < state.swipeTargetDepth) {
|
|
279
|
-
void fillSwipeDeck();
|
|
280
|
-
}
|
|
281
|
-
})();
|
|
282
|
-
}, [view, swipeLoaded, refreshSwipeStack, fillSwipeDeck, store]);
|
|
283
|
-
const handleSwipeApprove = useCallback(async (id) => {
|
|
284
|
-
try {
|
|
285
|
-
const response = await fetch(`/discover/swipe/${encodeURIComponent(id)}/approve`, {
|
|
286
|
-
method: "POST",
|
|
287
|
-
credentials: "same-origin",
|
|
288
|
-
headers: { Accept: "application/json" }
|
|
289
|
-
});
|
|
290
|
-
const payload = await response.json().catch(() => ({}));
|
|
291
|
-
if (!response.ok) {
|
|
292
|
-
// The server retired this card (built by an older customize pipeline) —
|
|
293
|
-
// drop it from the deck and pull a fresh one instead of jamming.
|
|
294
|
-
if (payload && payload.superseded) {
|
|
295
|
-
store.getState().removeSwipeCard(id);
|
|
296
|
-
void fillSwipeDeck();
|
|
297
|
-
return { ok: true };
|
|
298
|
-
}
|
|
299
|
-
return { ok: false, error: typeof payload.error === "string" && payload.error ? payload.error : "Unable to approve." };
|
|
300
|
-
}
|
|
301
|
-
store.getState().removeSwipeCard(id);
|
|
302
|
-
void fillSwipeDeck(); // +1 fresh at the tail
|
|
303
|
-
return { ok: true };
|
|
304
|
-
}
|
|
305
|
-
catch (approveError) {
|
|
306
|
-
return { ok: false, error: approveError instanceof Error ? approveError.message : "Unable to approve." };
|
|
307
|
-
}
|
|
308
|
-
}, [store, fillSwipeDeck]);
|
|
309
|
-
const handleSwipeReject = useCallback(async (id) => {
|
|
310
|
-
try {
|
|
311
|
-
const response = await fetch(`/discover/swipe/${encodeURIComponent(id)}/reject`, {
|
|
312
|
-
method: "POST",
|
|
313
|
-
credentials: "same-origin",
|
|
314
|
-
headers: { Accept: "application/json" }
|
|
315
|
-
});
|
|
316
|
-
const payload = await response.json().catch(() => ({}));
|
|
317
|
-
if (!response.ok) {
|
|
318
|
-
if (payload && payload.superseded) {
|
|
319
|
-
store.getState().removeSwipeCard(id);
|
|
320
|
-
void fillSwipeDeck();
|
|
321
|
-
return { ok: true };
|
|
322
|
-
}
|
|
323
|
-
return { ok: false, error: typeof payload.error === "string" && payload.error ? payload.error : "Unable to reject." };
|
|
324
|
-
}
|
|
325
|
-
store.getState().removeSwipeCard(id);
|
|
326
|
-
void fillSwipeDeck(); // +1 fresh at the tail
|
|
327
|
-
return { ok: true };
|
|
328
|
-
}
|
|
329
|
-
catch (rejectError) {
|
|
330
|
-
return { ok: false, error: rejectError instanceof Error ? rejectError.message : "Unable to reject." };
|
|
331
|
-
}
|
|
332
|
-
}, [store, fillSwipeDeck]);
|
|
333
|
-
const handleDeleteTemplate = useCallback(async (templateId) => {
|
|
334
|
-
try {
|
|
335
|
-
const response = await fetch(`/discover/templates/${encodeURIComponent(templateId)}`, {
|
|
336
|
-
method: "DELETE",
|
|
337
|
-
credentials: "same-origin",
|
|
338
|
-
headers: { Accept: "application/json" }
|
|
339
|
-
});
|
|
340
|
-
const payload = await response.json().catch(() => ({}));
|
|
341
|
-
if (!response.ok) {
|
|
342
|
-
return { ok: false, error: typeof payload.error === "string" && payload.error ? payload.error : "Unable to delete this template." };
|
|
343
|
-
}
|
|
344
|
-
debugLog("homepage.template_deleted", { template_id: templateId });
|
|
345
|
-
await refreshFeed();
|
|
346
|
-
return { ok: true };
|
|
347
|
-
}
|
|
348
|
-
catch (deleteError) {
|
|
349
|
-
return { ok: false, error: deleteError instanceof Error ? deleteError.message : "Unable to delete this template." };
|
|
350
|
-
}
|
|
351
|
-
}, [refreshFeed]);
|
|
352
|
-
const handleAddTemplate = useCallback(async (submission) => {
|
|
353
|
-
try {
|
|
354
|
-
// Upload path: presign → PUT the bytes (straight to S3, or the local
|
|
355
|
-
// multipart fallback) → finalize with the storage key. The URL path
|
|
356
|
-
// posts source_url directly, exactly as before.
|
|
357
|
-
let uploadRef = null;
|
|
358
|
-
if (submission.file) {
|
|
359
|
-
const file = submission.file;
|
|
360
|
-
const presignResponse = await fetch("/discover/templates/upload/presign", {
|
|
361
|
-
method: "POST",
|
|
362
|
-
credentials: "same-origin",
|
|
363
|
-
headers: { "content-type": "application/json", Accept: "application/json" },
|
|
364
|
-
body: JSON.stringify({
|
|
365
|
-
file_name: file.name,
|
|
366
|
-
content_type: file.type || null,
|
|
367
|
-
size_bytes: file.size
|
|
368
|
-
})
|
|
369
|
-
});
|
|
370
|
-
const presign = await presignResponse.json().catch(() => ({}));
|
|
371
|
-
if (!presignResponse.ok) {
|
|
372
|
-
return { ok: false, error: presign.error || "Unable to start the video upload." };
|
|
373
|
-
}
|
|
374
|
-
if (presign.transport === "presigned" && presign.upload?.url && presign.storage_key) {
|
|
375
|
-
const put = await fetch(presign.upload.url, {
|
|
376
|
-
method: presign.upload.method || "PUT",
|
|
377
|
-
headers: presign.upload.headers || {},
|
|
378
|
-
body: file
|
|
379
|
-
});
|
|
380
|
-
if (!put.ok) {
|
|
381
|
-
return { ok: false, error: `Video upload failed (${put.status}). Try again.` };
|
|
382
|
-
}
|
|
383
|
-
uploadRef = { storage_key: presign.storage_key, file_name: presign.file_name || file.name };
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
const form = new FormData();
|
|
387
|
-
form.append("file", file, presign.file_name || file.name);
|
|
388
|
-
const direct = await fetch(presign.upload?.url || "/discover/templates/upload", {
|
|
389
|
-
method: "POST",
|
|
390
|
-
credentials: "same-origin",
|
|
391
|
-
body: form
|
|
392
|
-
});
|
|
393
|
-
const directPayload = await direct.json().catch(() => ({}));
|
|
394
|
-
if (!direct.ok || !directPayload.storage_key) {
|
|
395
|
-
return { ok: false, error: directPayload.error || "Video upload failed. Try again." };
|
|
396
|
-
}
|
|
397
|
-
uploadRef = { storage_key: directPayload.storage_key, file_name: directPayload.file_name || file.name };
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
const response = await fetch("/discover/templates", {
|
|
401
|
-
method: "POST",
|
|
402
|
-
credentials: "same-origin",
|
|
403
|
-
headers: { "content-type": "application/json", Accept: "application/json" },
|
|
404
|
-
body: JSON.stringify(uploadRef
|
|
405
|
-
? {
|
|
406
|
-
upload: uploadRef,
|
|
407
|
-
title: submission.title || null,
|
|
408
|
-
tagline: submission.tagline || null,
|
|
409
|
-
notes: submission.notes || null
|
|
410
|
-
}
|
|
411
|
-
: {
|
|
412
|
-
source_url: submission.sourceUrl,
|
|
413
|
-
tagline: submission.tagline || null,
|
|
414
|
-
notes: submission.notes || null
|
|
415
|
-
})
|
|
416
|
-
});
|
|
417
|
-
const payload = await response.json().catch(() => ({}));
|
|
418
|
-
if (!response.ok) {
|
|
419
|
-
return { ok: false, error: typeof payload.error === "string" && payload.error ? payload.error : "Unable to add that video." };
|
|
420
|
-
}
|
|
421
|
-
debugLog("homepage.template_added", uploadRef
|
|
422
|
-
? { storage_key: uploadRef.storage_key }
|
|
423
|
-
: { source_url: submission.sourceUrl });
|
|
424
|
-
await refreshFeed();
|
|
425
|
-
return { ok: true };
|
|
426
|
-
}
|
|
427
|
-
catch (submitError) {
|
|
428
|
-
return { ok: false, error: submitError instanceof Error ? submitError.message : "Unable to add that video." };
|
|
429
|
-
}
|
|
430
|
-
}, [refreshFeed]);
|
|
431
|
-
return (_jsx(HomepageShell, { account: boot.account, templates: templates, visibleTemplates: visibleTemplates, searchQuery: searchQuery, bookmarksOnly: bookmarksOnly, bookmarks: bookmarks, loading: loading, error: error, view: view, popularTemplates: popularTemplates, popularLoading: popularLoading, popularError: popularError, swipeStack: swipeStack, swipeLoading: swipeLoading, swipeLoaded: swipeLoaded, swipeError: swipeError, swipeCustomizing: swipeCustomizing, swipeTargetDepth: swipeTargetDepth, swipeProductSet: swipeProductSet, swipeRefilling: swipeRefilling, skillEndpointPrefix: boot.skillEndpointPrefix, onSearchQueryChange: (value) => store.getState().setSearchQuery(value), onBookmarksOnlyChange: (value) => store.getState().setBookmarksOnly(value), onViewChange: (value) => store.getState().setView(value), onToggleBookmark: (templateId) => store.getState().toggleBookmark(templateId), onAddTemplate: handleAddTemplate, onDeleteTemplate: handleDeleteTemplate, onSwipeApprove: handleSwipeApprove, onSwipeReject: handleSwipeReject }));
|
|
432
|
-
}
|
|
433
|
-
const boot = readBoot();
|
|
434
|
-
const root = document.getElementById("homepage-root");
|
|
435
|
-
debugLog("homepage.boot", {
|
|
436
|
-
is_logged_in: boot.account?.isLoggedIn ?? false,
|
|
437
|
-
template_count: boot.templates?.length ?? 0,
|
|
438
|
-
load_endpoint: boot.loadEndpoint
|
|
439
|
-
});
|
|
440
|
-
if (root) {
|
|
441
|
-
hydrateRoot(root, (_jsx(Sentry.ErrorBoundary, { fallback: _jsx(_Fragment, {}), onError: (error) => debugError("homepage.render_error", { message: error instanceof Error ? error.message : String(error) }), children: _jsx(HomepageClientApp, { boot: boot }) })));
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
debugError("homepage.no_root_element", {});
|
|
445
|
-
}
|
|
446
|
-
//# sourceMappingURL=homepage-client.js.map
|