@mevdragon/vidfarm-devcli 0.2.13 → 0.2.14
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/dist/src/account-pages-legacy.js +7 -5
- package/dist/src/account-pages-legacy.js.map +1 -1
- package/dist/src/account-pages.js +1 -10
- package/dist/src/account-pages.js.map +1 -1
- package/dist/src/app.js +106 -418
- package/dist/src/app.js.map +1 -1
- package/dist/src/cli.js +5 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/dev-app-legacy.js +1 -1
- package/dist/src/dev-serve.js +169 -0
- package/dist/src/dev-serve.js.map +1 -0
- package/dist/src/editor-chat.js +10 -9
- package/dist/src/editor-chat.js.map +1 -1
- package/dist/src/editor-dark-theme.js +1128 -0
- package/dist/src/editor-dark-theme.js.map +1 -0
- package/dist/src/frontend/homepage-view.js +4 -4
- package/dist/src/frontend/homepage-view.js.map +1 -1
- package/dist/src/frontend/page-runtime-client.js +23 -2
- package/dist/src/frontend/page-runtime-client.js.map +1 -1
- package/dist/src/frontend/template-editor-chat.js +28 -35
- package/dist/src/frontend/template-editor-chat.js.map +1 -1
- package/dist/src/homepage.js +1 -1
- package/dist/src/homepage.js.map +1 -1
- package/dist/src/page-shell.js +27 -1105
- package/dist/src/page-shell.js.map +1 -1
- package/dist/src/primitive-registry.js +3 -3
- package/dist/src/primitive-registry.js.map +1 -1
- package/dist/src/services/fork-access.js +6 -7
- package/dist/src/services/fork-access.js.map +1 -1
- package/dist/src/services/fork-manifest.js +43 -0
- package/dist/src/services/fork-manifest.js.map +1 -0
- package/dist/src/services/serverless-records.js +2 -0
- package/dist/src/services/serverless-records.js.map +1 -1
- package/dist/src/services/storage.js +3 -2
- package/dist/src/services/storage.js.map +1 -1
- package/dist/src/template-editor-pages.js +3 -4136
- package/dist/src/template-editor-pages.js.map +1 -1
- package/dist/src/template-editor-shell.js +42 -53
- package/dist/src/template-editor-shell.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/homepage-client-app.js +4 -4
- package/public/assets/page-runtime-client-app.js +29 -29
package/dist/src/app.js
CHANGED
|
@@ -8,13 +8,14 @@ import { stepCountIs, streamText, tool } from "ai";
|
|
|
8
8
|
import { Hono } from "hono";
|
|
9
9
|
import * as Sentry from "@sentry/hono/node";
|
|
10
10
|
import { z } from "zod";
|
|
11
|
-
import { renderAgencyPage, renderCalendarPage,
|
|
11
|
+
import { renderAgencyPage, renderCalendarPage, renderJobRunsPage, renderLibraryPage, renderLoginPage, renderPricingPage, renderSettingsPage } from "./account-pages.js";
|
|
12
12
|
import { config } from "./config.js";
|
|
13
13
|
import { serverlessRecords } from "./services/serverless-records.js";
|
|
14
14
|
import { renderDevApp } from "./dev-app.js";
|
|
15
15
|
import { CUSTOMER_PLAN_TIERS } from "./domain.js";
|
|
16
16
|
import { sanitizeChatPayload, sanitizeHttpExchange } from "./editor-chat-history.js";
|
|
17
17
|
import { assertForkAccess, resolveForkAccess, ForkAccessDeniedError, ForkNotFoundError } from "./services/fork-access.js";
|
|
18
|
+
import { writeForkManifest } from "./services/fork-manifest.js";
|
|
18
19
|
import { renderHelpPage } from "./help-page.js";
|
|
19
20
|
import { renderHomepage } from "./homepage.js";
|
|
20
21
|
import { COMPOSITION_RUNTIME_SCRIPT_BODY } from "./composition-runtime.js";
|
|
@@ -41,7 +42,7 @@ import { probeMediaAsset } from "./services/media-processing.js";
|
|
|
41
42
|
import { estimateGhostcutCostUsd, isGhostcutConfigured, mirrorGhostcutVideoToStorage, pollStatus as pollGhostcutStatus, submitSubtitleRemoval } from "./services/ghostcut.js";
|
|
42
43
|
import { TemplateSourceService } from "./services/template-sources.js";
|
|
43
44
|
import { TemplateSourceAlreadyRegisteredError } from "./services/template-sources.js";
|
|
44
|
-
import { renderCustomInpaintPage
|
|
45
|
+
import { renderCustomInpaintPage } from "./template-editor-pages.js";
|
|
45
46
|
import { escapeAttribute, escapeHtml, escapeJsonForHtml, renderBrandLockup, renderPageShell, renderPrimaryNav } from "./page-shell.js";
|
|
46
47
|
import { renderReadyPostScheduleData, renderReadyPostScheduleModal, renderReadyPostScheduleScript, renderReadyPostScheduleStyles } from "./ready-post-schedule-component.js";
|
|
47
48
|
const auth = new AuthService();
|
|
@@ -560,7 +561,7 @@ function shouldInitializeTemplateRegistry(pathname) {
|
|
|
560
561
|
|| pathname === "/pricing"
|
|
561
562
|
|| pathname === "/discover/feed"
|
|
562
563
|
|| pathname.startsWith("/discover/skills/")
|
|
563
|
-
|| pathname.startsWith("/
|
|
564
|
+
|| pathname.startsWith("/editor/")
|
|
564
565
|
|| pathname.startsWith("/drafts/")
|
|
565
566
|
|| pathname === "/job-runs"
|
|
566
567
|
|| pathname === "/job-runs/history"
|
|
@@ -3155,7 +3156,7 @@ function buildHyperframesDiscoverTemplate(draftPath) {
|
|
|
3155
3156
|
templateId: `hyperframes:${slugId}`,
|
|
3156
3157
|
slugId,
|
|
3157
3158
|
difficulty: estimateDraftDifficulty(editorLayers.length, sourceType),
|
|
3158
|
-
viralDna: buildDraftViralSummary(compositionData) || "Draft composition with editable
|
|
3159
|
+
viralDna: buildDraftViralSummary(compositionData) || "Draft composition with editable timeline layers.",
|
|
3159
3160
|
previewUrl,
|
|
3160
3161
|
approvedAt: readNonEmptyString(compositionData.created_at) ?? "2026-06-30T00:00:00.000Z",
|
|
3161
3162
|
lambdaMemoryMb: estimateDraftLambdaMemoryMb(sourceType),
|
|
@@ -3327,7 +3328,7 @@ function buildHyperframesDraftEditorTemplate(composition) {
|
|
|
3327
3328
|
sound_url: composition.previewUrl ?? ""
|
|
3328
3329
|
},
|
|
3329
3330
|
proposalGenerator: [
|
|
3330
|
-
"Use this draft as a
|
|
3331
|
+
"Use this draft as a layer timeline. Edit via the Trackpad Editor composition, not imperative scene commands.",
|
|
3331
3332
|
"",
|
|
3332
3333
|
"Source assets:",
|
|
3333
3334
|
sourceAssetLines || "- No source assets declared.",
|
|
@@ -3335,7 +3336,7 @@ function buildHyperframesDraftEditorTemplate(composition) {
|
|
|
3335
3336
|
"Editable layer inventory:",
|
|
3336
3337
|
layerLines || "- No layers declared.",
|
|
3337
3338
|
"",
|
|
3338
|
-
"When rendering, prefer POST /api/v1/primitives/
|
|
3339
|
+
"When rendering, prefer POST /api/v1/primitives/timeline/render with composition_html from the draft or a composition.layers payload derived from the layer inventory."
|
|
3339
3340
|
].join("\n"),
|
|
3340
3341
|
skeletonPrompt: formatHyperframesDraftJson({
|
|
3341
3342
|
composition_id: composition.slugId,
|
|
@@ -3351,16 +3352,16 @@ function buildHyperframesDraftEditorTemplate(composition) {
|
|
|
3351
3352
|
}),
|
|
3352
3353
|
samplePrompts: [
|
|
3353
3354
|
`Open ${composition.draftPath}/composition.html and explain the frame/layer timeline.`,
|
|
3354
|
-
`Render ${composition.slugId} through /api/v1/primitives/
|
|
3355
|
+
`Render ${composition.slugId} through /api/v1/primitives/timeline/render using the current draft composition HTML.`,
|
|
3355
3356
|
`Suggest where to move or trim the overlay layers without changing source media timing.`
|
|
3356
3357
|
],
|
|
3357
3358
|
examples: previewExamples.length ? [previewExamples] : [],
|
|
3358
3359
|
previewUrl: composition.previewUrl,
|
|
3359
|
-
skillUrl: `/
|
|
3360
|
+
skillUrl: `/editor/${encodeURIComponent(composition.slugId)}/skill`,
|
|
3360
3361
|
operations: [
|
|
3361
3362
|
{
|
|
3362
3363
|
name: "render",
|
|
3363
|
-
description: "Render this draft composition through the
|
|
3364
|
+
description: "Render this draft composition through the timeline render primitive."
|
|
3364
3365
|
},
|
|
3365
3366
|
{
|
|
3366
3367
|
name: "inspect_layers",
|
|
@@ -3391,15 +3392,15 @@ function buildHyperframesDraftSkill(composition) {
|
|
|
3391
3392
|
editorLayers.map((layer) => `- ${layer.id}: ${layer.label}, ${layer.type}, track ${layer.track}, ${layer.start}s for ${layer.duration}s`).join("\n") || "- None declared.",
|
|
3392
3393
|
"",
|
|
3393
3394
|
"## Render",
|
|
3394
|
-
"Use `POST /api/v1/primitives/
|
|
3395
|
+
"Use `POST /api/v1/primitives/timeline/render` with either `payload.composition_html` from the composition route or a `payload.composition.layers` object derived from composition.json."
|
|
3395
3396
|
].join("\n");
|
|
3396
3397
|
}
|
|
3397
3398
|
function buildHyperframesDraftEditorDocsRoutes(composition) {
|
|
3398
3399
|
return [
|
|
3399
|
-
{ method: "GET", path: `/
|
|
3400
|
-
{ method: "GET", path: `/
|
|
3401
|
-
{ method: "GET", path: `/
|
|
3402
|
-
{ method: "GET", path: `/
|
|
3400
|
+
{ method: "GET", path: `/editor/${composition.slugId}`, summary: "Open this draft in the shared timeline editor." },
|
|
3401
|
+
{ method: "GET", path: `/editor/${composition.slugId}/composition`, summary: "Read the draft composition.html with draft-relative assets resolved." },
|
|
3402
|
+
{ method: "GET", path: `/editor/${composition.slugId}/composition.json`, summary: "Read the draft composition.json that drives layer/timeline metadata." },
|
|
3403
|
+
{ method: "GET", path: `/editor/${composition.slugId}/skill`, summary: "Read the generated composition skill context for the Copilot." },
|
|
3403
3404
|
...buildPrimitiveEditorDocsRoutes()
|
|
3404
3405
|
];
|
|
3405
3406
|
}
|
|
@@ -3438,32 +3439,6 @@ async function buildHyperframesDraftEditorChatBoot(input) {
|
|
|
3438
3439
|
async function getApprovedHomepageTemplates(_c, input = {}) {
|
|
3439
3440
|
return paginateHyperframesDiscoverTemplates(input);
|
|
3440
3441
|
}
|
|
3441
|
-
function buildTemplateEditorDocsRoutes(template) {
|
|
3442
|
-
const templatePath = `${TEMPLATES_PREFIX}/${template.id}`;
|
|
3443
|
-
return [
|
|
3444
|
-
{ method: "GET", path: `${templatePath}`, summary: "Fetch template metadata, operations, and base identifiers." },
|
|
3445
|
-
{ method: "GET", path: `${templatePath}/skill`, summary: "Fetch the template SKILL.md payload through the API wrapper." },
|
|
3446
|
-
{ method: "POST", path: `${templatePath}/config`, summary: "Persist template-specific customer config for later runs." },
|
|
3447
|
-
...Object.entries(template.operations).map(([name, operation]) => ({
|
|
3448
|
-
method: "POST",
|
|
3449
|
-
path: `${templatePath}/operations/${name}`,
|
|
3450
|
-
summary: operation.description
|
|
3451
|
-
})),
|
|
3452
|
-
{ method: "GET", path: `${templatePath}/jobs?tracer=:tracer_id`, summary: "List render jobs for this composition, optionally scoped to a tracer." },
|
|
3453
|
-
{ method: "GET", path: `${templatePath}/jobs/:job_id`, summary: "Read one render job record for this composition." },
|
|
3454
|
-
{ method: "GET", path: `${templatePath}/jobs/:job_id/artifacts`, summary: "Read artifacts for one composition render job." },
|
|
3455
|
-
{ method: "GET", path: `${templatePath}/jobs/:job_id/logs`, summary: "Read timestamped job logs for one run." },
|
|
3456
|
-
{ method: "POST", path: `${templatePath}/jobs/:job_id/cancel`, summary: "Cancel a queued or running job." },
|
|
3457
|
-
{ method: "GET", path: `${USER_PREFIX}/me/jobs/:job_id`, summary: "Read one render job record by id across all compositions for the current customer." },
|
|
3458
|
-
{ method: "GET", path: `${USER_PREFIX}/me/jobs/:job_id/logs`, summary: "Read timestamped job logs by job id without knowing the composition." },
|
|
3459
|
-
{ method: "GET", path: `${USER_PREFIX}/me/jobs/:job_id/artifacts`, summary: "Read artifacts by job id without knowing the composition." },
|
|
3460
|
-
{ method: "GET", path: `${USER_PREFIX}/me/jobs/:job_id/debug`, summary: "Read a consolidated job debug bundle with status, error, artifacts, structured logs, and API history." },
|
|
3461
|
-
{ method: "GET", path: `${DIRECTOR_PREFIX}/jobs/:job_id/debug`, summary: "Director-only consolidated job debug bundle using x-superagency-key." },
|
|
3462
|
-
{ method: "GET", path: `/templates/${template.id}/editor/docs`, summary: "Open the HyperFrames composition workspace." },
|
|
3463
|
-
{ method: "GET", path: `/templates/${template.id}/editor/tracer/:tracer_id`, summary: "Open the tracer-grouped render view for this composition." },
|
|
3464
|
-
{ method: "GET", path: `/templates/${template.id}/editor/tracer/:tracer_id/history`, summary: "Load tracer-scoped API history for the editor tracer page." }
|
|
3465
|
-
];
|
|
3466
|
-
}
|
|
3467
3442
|
function buildPrimitiveEditorDocsRoutes() {
|
|
3468
3443
|
const operationRoutes = primitiveRegistry.list().flatMap((primitive) => Object.entries(primitive.operations).map(([name, operation]) => ({
|
|
3469
3444
|
method: "POST",
|
|
@@ -3481,7 +3456,7 @@ function buildPrimitiveEditorDocsRoutes() {
|
|
|
3481
3456
|
{ method: "POST", path: `${PRIMITIVES_PREFIX}/videos/generate`, summary: "Primitive AI video generation job using saved OpenAI, Gemini, or OpenRouter provider keys. Body must be { tracer, payload: { prompt, provider?, model?, input_references?, frame_images?, aspect_ratio?, duration?, resolution?, generate_audio? }, webhook_url? }. input_references must be direct image asset URLs, not webpage or social post URLs. duration is seconds, not milliseconds; use duration: 4 for a 4-second AI video. Defaults: openai/sora-2, gemini/veo-3.0-generate-001, openrouter/bytedance/seedance-2.0. Use this for new AI-generated footage." },
|
|
3482
3457
|
{ method: "POST", path: `${PRIMITIVES_PREFIX}/videos/normalize`, summary: "Primitive video normalization job. Body must be { tracer, payload: { source_video_url, max_width?, max_height?, crf?, preset?, max_video_bitrate_kbps?, audio_bitrate_kbps? }, webhook_url? }." },
|
|
3483
3458
|
{ method: "POST", path: `${PRIMITIVES_PREFIX}/videos/render-slides`, summary: "Primitive media slideshow render job. Body must be { tracer, payload: { slides: [{ media_url, duration_ms, media_type?, fit? }], width?, height?, background_color?, default_fit? }, webhook_url? }." },
|
|
3484
|
-
{ method: "POST", path: `${PRIMITIVES_PREFIX}/
|
|
3459
|
+
{ method: "POST", path: `${PRIMITIVES_PREFIX}/timeline/render`, summary: "Primitive timeline/layer render job. Body must be { tracer, payload: { composition_html | composition: { composition_id, width?, height?, fps?, layers: [{ id, kind, start, duration, src?, text?, html?, x?, y?, width?, height?, track?, z? }] }, chunk_size?, max_parallel_chunks?, output_key? }, webhook_url? }. Use this instead of creating new per-template REST APIs." },
|
|
3485
3460
|
{ method: "POST", path: `${PRIMITIVES_PREFIX}/videos/trim`, summary: "Primitive video trim job. Body must be { tracer, payload: { source_video_url, start_ms, duration_ms|end_ms, max_width?, max_height?, crf?, preset?, audio_bitrate_kbps? }, webhook_url? }." },
|
|
3486
3461
|
{ method: "POST", path: `${PRIMITIVES_PREFIX}/videos/extract-audio`, summary: "Primitive audio extraction job from a source video. Body must be { tracer, payload: { source_video_url, output_format?, audio_bitrate_kbps? }, webhook_url? }." },
|
|
3487
3462
|
{ method: "POST", path: `${PRIMITIVES_PREFIX}/videos/probe`, summary: "Primitive video probe job. Body must be { tracer, payload: { source_video_url }, webhook_url? }. Returns durable JSON metadata." },
|
|
@@ -3509,59 +3484,6 @@ function buildPrimitiveEditorDocsRoutes() {
|
|
|
3509
3484
|
{ method: "POST", path: `${PRIMITIVES_PREFIX}/:primitive_kind/jobs/:job_id/cancel`, summary: "Cancel a queued or running primitive-kind job." }
|
|
3510
3485
|
];
|
|
3511
3486
|
}
|
|
3512
|
-
function buildTemplateEditorChatContext(input) {
|
|
3513
|
-
return {
|
|
3514
|
-
page: input.page,
|
|
3515
|
-
tracerId: input.tracerId ?? null,
|
|
3516
|
-
tracers: Array.isArray(input.tracers) ? input.tracers.filter((value) => value.trim().length > 0) : [],
|
|
3517
|
-
defaultRequestTracer: input.defaultRequestTracer ?? input.activeTracer ?? input.tracerId ?? null,
|
|
3518
|
-
activeTracer: input.defaultRequestTracer ?? input.activeTracer ?? input.tracerId ?? null,
|
|
3519
|
-
templateId: input.template.id,
|
|
3520
|
-
templateSlug: input.template.slugId,
|
|
3521
|
-
templateTitle: input.template.about.title,
|
|
3522
|
-
templateDescription: input.template.about.description,
|
|
3523
|
-
docsRoutes: input.docsRoutes
|
|
3524
|
-
};
|
|
3525
|
-
}
|
|
3526
|
-
async function buildTemplateEditorChatBoot(input) {
|
|
3527
|
-
const directorSkill = readRootSkillFile("SKILL.director.md", "SKILL.user.md") || null;
|
|
3528
|
-
const templateSkill = input.template.skillPath
|
|
3529
|
-
? (() => {
|
|
3530
|
-
try {
|
|
3531
|
-
return readFileSync(input.template.skillPath, "utf8");
|
|
3532
|
-
}
|
|
3533
|
-
catch {
|
|
3534
|
-
return null;
|
|
3535
|
-
}
|
|
3536
|
-
})()
|
|
3537
|
-
: null;
|
|
3538
|
-
const availableProviders = input.customer
|
|
3539
|
-
? (await normalizeEditorChatProviderRows(input.customer.id)).map((entry) => entry.provider)
|
|
3540
|
-
: [];
|
|
3541
|
-
const currentTemplateConfig = input.customer
|
|
3542
|
-
? await getTemplateConfigForRuntime(input.customer.id, input.template.id)
|
|
3543
|
-
: null;
|
|
3544
|
-
return {
|
|
3545
|
-
apiUrl: resolveEditorChatApiUrl(),
|
|
3546
|
-
threadsUrl: `${API_PREFIX}/editor-chat/threads`,
|
|
3547
|
-
vidfarmApiKey: input.customer ? await getVisibleApiKey(input.customer.id) : null,
|
|
3548
|
-
cachedContext: {
|
|
3549
|
-
directorSkill,
|
|
3550
|
-
templateSkill,
|
|
3551
|
-
availableProviders,
|
|
3552
|
-
currentTemplateConfig
|
|
3553
|
-
},
|
|
3554
|
-
template: buildTemplateEditorChatContext({
|
|
3555
|
-
page: input.page,
|
|
3556
|
-
tracerId: input.tracerId ?? null,
|
|
3557
|
-
tracers: input.tracers ?? [],
|
|
3558
|
-
defaultRequestTracer: input.defaultRequestTracer ?? input.activeTracer ?? input.tracerId ?? null,
|
|
3559
|
-
activeTracer: input.defaultRequestTracer ?? input.activeTracer ?? input.tracerId ?? null,
|
|
3560
|
-
template: input.template,
|
|
3561
|
-
docsRoutes: input.docsRoutes
|
|
3562
|
-
})
|
|
3563
|
-
};
|
|
3564
|
-
}
|
|
3565
3487
|
async function buildLibraryEditorChatBoot(input) {
|
|
3566
3488
|
const directorSkill = readRootSkillFile("SKILL.director.md", "SKILL.user.md") || null;
|
|
3567
3489
|
const availableProviders = (await normalizeEditorChatProviderRows(input.customer.id)).map((entry) => entry.provider);
|
|
@@ -4197,7 +4119,7 @@ function createEditorFrontendActionTool(input) {
|
|
|
4197
4119
|
}
|
|
4198
4120
|
function createEditorActionTool() {
|
|
4199
4121
|
return tool({
|
|
4200
|
-
description: "Mutate the
|
|
4122
|
+
description: "Mutate the timeline composition the user is editing. The browser editor applies the action directly to composition.html and the timeline updates immediately. Use action_type=set_layer_timing to change a layer's start/duration/track/playback_start (all in seconds, except track which is an integer index). Use set_layer_visual to change a layer's x/y/width/height as percentages (0-100) relative to the composition viewport, or font_size in pixels. Use set_layer_text to replace the inner text content of a text or caption layer. Use replace_composition_html only when a partial action cannot express the change — pass the full new HTML document. Always pass layer_key copied verbatim from the editor_context attached to the user's message; never invent layer keys. Include a one-line explanation describing the change so the user can audit it.",
|
|
4201
4123
|
inputSchema: z.object({
|
|
4202
4124
|
action_type: z.enum(["set_layer_timing", "set_layer_visual", "set_layer_text", "replace_composition_html"]),
|
|
4203
4125
|
layer_key: z.string().min(1).optional().describe("Stable layer identifier. Required for set_layer_timing, set_layer_visual, and set_layer_text. Read it from the editor_context layers list."),
|
|
@@ -4505,12 +4427,6 @@ async function streamEditorChatAgent(input, send) {
|
|
|
4505
4427
|
httpExchanges
|
|
4506
4428
|
};
|
|
4507
4429
|
}
|
|
4508
|
-
function matchesTemplateApiHistoryPath(pathname, template) {
|
|
4509
|
-
return pathname.includes(`${TEMPLATES_PREFIX}/${template.id}`) || pathname.includes(`${TEMPLATES_PREFIX}/${template.slugId}`);
|
|
4510
|
-
}
|
|
4511
|
-
function matchesEditorApiHistoryPath(pathname, template) {
|
|
4512
|
-
return matchesTemplateApiHistoryPath(pathname, template) || pathname.startsWith(PRIMITIVES_PREFIX);
|
|
4513
|
-
}
|
|
4514
4430
|
function formatTimelineSeconds(value) {
|
|
4515
4431
|
if (!Number.isFinite(value)) {
|
|
4516
4432
|
return "unknown";
|
|
@@ -4685,7 +4601,7 @@ function renderHyperframesCompositionWorkspacePage(input) {
|
|
|
4685
4601
|
const sourceAssets = getDraftSourceAssets(compositionData);
|
|
4686
4602
|
const editorLayers = normalizeHyperframesDraftLayers(input.composition.draftPath, compositionData);
|
|
4687
4603
|
const durationSeconds = Math.max(1, estimateDraftDurationSeconds(compositionData, editorLayers, sourceAssets) ?? input.composition.durationSeconds ?? 10);
|
|
4688
|
-
const compositionUrl = `/
|
|
4604
|
+
const compositionUrl = `/editor/${encodeURIComponent(input.composition.slugId)}/composition`;
|
|
4689
4605
|
const editorBoot = {
|
|
4690
4606
|
composition: {
|
|
4691
4607
|
title: input.composition.title,
|
|
@@ -4702,7 +4618,7 @@ function renderHyperframesCompositionWorkspacePage(input) {
|
|
|
4702
4618
|
}))
|
|
4703
4619
|
};
|
|
4704
4620
|
return renderPageShell({
|
|
4705
|
-
title: `${input.composition.title}
|
|
4621
|
+
title: `${input.composition.title}`,
|
|
4706
4622
|
mainClass: "is-wide",
|
|
4707
4623
|
body: `
|
|
4708
4624
|
<section class="frame hf-editor-frame">
|
|
@@ -4723,7 +4639,7 @@ function renderHyperframesCompositionWorkspacePage(input) {
|
|
|
4723
4639
|
<div class="frame-body hf-editor-body">
|
|
4724
4640
|
<section class="hf-editor-command">
|
|
4725
4641
|
<div>
|
|
4726
|
-
<div class="eyebrow">Draft
|
|
4642
|
+
<div class="eyebrow">Draft Template</div>
|
|
4727
4643
|
<h1>${escapeHtml(input.composition.title)}</h1>
|
|
4728
4644
|
<p>${escapeHtml(input.composition.viralDna)}</p>
|
|
4729
4645
|
</div>
|
|
@@ -5244,17 +5160,17 @@ async function renderHyperframesStudioEditorPage(input) {
|
|
|
5244
5160
|
<head>
|
|
5245
5161
|
<meta charset="UTF-8" />
|
|
5246
5162
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
5247
|
-
<title>${escapeHtml(input.composition.title)} |
|
|
5163
|
+
<title>${escapeHtml(input.composition.title)} | Studio</title>
|
|
5248
5164
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
5249
5165
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
5250
5166
|
<link rel="icon" href="/assets/favicon.ico" />
|
|
5251
|
-
<link rel="stylesheet" href="/assets/
|
|
5167
|
+
<link rel="stylesheet" href="/assets/editor/app.css" />
|
|
5252
5168
|
<style>html,body{margin:0;background:#050604;color:#fffbe6;}#root{min-height:100vh;}</style>
|
|
5253
5169
|
</head>
|
|
5254
5170
|
<body>
|
|
5255
5171
|
<div id="root"></div>
|
|
5256
5172
|
<script id="hf-boot" type="application/json">${escapeJsonForHtml(boot)}</script>
|
|
5257
|
-
<script type="module" src="/assets/
|
|
5173
|
+
<script type="module" src="/assets/editor/app.js"></script>
|
|
5258
5174
|
</body>
|
|
5259
5175
|
</html>`;
|
|
5260
5176
|
}
|
|
@@ -5294,7 +5210,7 @@ app.get("/", async (c) => {
|
|
|
5294
5210
|
return redirect(c, customer ? withAccountQuery("/library", customer.id) : "/discover");
|
|
5295
5211
|
});
|
|
5296
5212
|
app.get("/discover", async (c) => renderApprovedHomepage(c));
|
|
5297
|
-
app.get("/
|
|
5213
|
+
app.get("/editor/:compositionId", async (c) => {
|
|
5298
5214
|
const compositionId = c.req.param("compositionId");
|
|
5299
5215
|
const customer = await getOptionalPreferredBrowserCustomer(c);
|
|
5300
5216
|
const tiktokMatch = /^tiktok-([A-Za-z0-9]+)$/.exec(compositionId);
|
|
@@ -5305,7 +5221,7 @@ app.get("/hyperframes/:compositionId/editor", async (c) => {
|
|
|
5305
5221
|
if (video) {
|
|
5306
5222
|
const forkParam = c.req.query("fork");
|
|
5307
5223
|
if (video.defaultForkId && !forkParam) {
|
|
5308
|
-
const path = `/
|
|
5224
|
+
const path = `/editor/${encodeURIComponent(compositionId)}?fork=${encodeURIComponent(video.defaultForkId)}`;
|
|
5309
5225
|
return redirect(c, path);
|
|
5310
5226
|
}
|
|
5311
5227
|
if (!customer) {
|
|
@@ -5345,7 +5261,7 @@ app.get("/hyperframes/:compositionId/editor", async (c) => {
|
|
|
5345
5261
|
}
|
|
5346
5262
|
return c.html(await renderHyperframesStudioEditorPage({ composition, customer }));
|
|
5347
5263
|
});
|
|
5348
|
-
app.post("/
|
|
5264
|
+
app.post("/editor/:compositionId/export", async (c) => {
|
|
5349
5265
|
const composition = getHyperframesDiscoverTemplate(c.req.param("compositionId"));
|
|
5350
5266
|
if (!composition) {
|
|
5351
5267
|
return c.json({ error: "Composition not found" }, 404);
|
|
@@ -5365,7 +5281,7 @@ app.post("/hyperframes/:compositionId/publish", async (c) => {
|
|
|
5365
5281
|
const primitive = primitiveRegistry.get("primitive:hyperframes_render");
|
|
5366
5282
|
const operation = primitive?.operations.run;
|
|
5367
5283
|
if (!primitive || !operation) {
|
|
5368
|
-
return c.json({ error: "
|
|
5284
|
+
return c.json({ error: "Timeline render primitive not found" }, 500);
|
|
5369
5285
|
}
|
|
5370
5286
|
const payload = operation.inputSchema.parse({
|
|
5371
5287
|
composition_html: renderHyperframesDraftCompositionHtml(composition),
|
|
@@ -5423,7 +5339,7 @@ app.post("/hyperframes/:compositionId/publish", async (c) => {
|
|
|
5423
5339
|
throw error;
|
|
5424
5340
|
}
|
|
5425
5341
|
});
|
|
5426
|
-
app.get("/
|
|
5342
|
+
app.get("/editor/:compositionId/composition", async (c) => {
|
|
5427
5343
|
const compositionId = c.req.param("compositionId");
|
|
5428
5344
|
const tiktokMatch = /^tiktok-([A-Za-z0-9]+)$/.exec(compositionId);
|
|
5429
5345
|
if (tiktokMatch) {
|
|
@@ -5444,7 +5360,7 @@ app.get("/hyperframes/:compositionId/composition", async (c) => {
|
|
|
5444
5360
|
}
|
|
5445
5361
|
return c.html(renderHyperframesDraftCompositionHtml(composition));
|
|
5446
5362
|
});
|
|
5447
|
-
app.get("/
|
|
5363
|
+
app.get("/editor/:compositionId/composition.json", async (c) => {
|
|
5448
5364
|
const composition = getHyperframesDiscoverTemplate(c.req.param("compositionId"));
|
|
5449
5365
|
if (!composition) {
|
|
5450
5366
|
return c.json({ error: "Composition not found" }, 404);
|
|
@@ -5455,7 +5371,7 @@ app.get("/hyperframes/:compositionId/composition.json", async (c) => {
|
|
|
5455
5371
|
}
|
|
5456
5372
|
return c.json(compositionData);
|
|
5457
5373
|
});
|
|
5458
|
-
app.get("/
|
|
5374
|
+
app.get("/editor/:compositionId/skill", async (c) => {
|
|
5459
5375
|
const composition = getHyperframesDiscoverTemplate(c.req.param("compositionId"));
|
|
5460
5376
|
if (!composition) {
|
|
5461
5377
|
return c.text("Composition not found", 404);
|
|
@@ -5464,7 +5380,7 @@ app.get("/hyperframes/:compositionId/skill", async (c) => {
|
|
|
5464
5380
|
"content-type": "text/markdown; charset=utf-8"
|
|
5465
5381
|
});
|
|
5466
5382
|
});
|
|
5467
|
-
app.get("/
|
|
5383
|
+
app.get("/editor/:compositionId/docs/history", async (c) => {
|
|
5468
5384
|
const customer = await requireBrowserCustomer(c);
|
|
5469
5385
|
if (!customer) {
|
|
5470
5386
|
return c.json({ error: "Unauthorized" }, 401);
|
|
@@ -5502,7 +5418,7 @@ app.get("/hyperframes/:compositionId/editor/docs/history", async (c) => {
|
|
|
5502
5418
|
const linkedTemplate = String(entry.linkedJob?.template_id ?? "").toLowerCase();
|
|
5503
5419
|
const pathname = String(entry.path || "").toLowerCase();
|
|
5504
5420
|
return linkedTemplate === "primitive:hyperframes_render"
|
|
5505
|
-
|| pathname.includes(`${PRIMITIVES_PREFIX}/
|
|
5421
|
+
|| pathname.includes(`${PRIMITIVES_PREFIX}/timeline/render`);
|
|
5506
5422
|
});
|
|
5507
5423
|
const filteredEntries = parseCreatedAtIdCursor(query.cursor)
|
|
5508
5424
|
? entries.filter((entry) => (Date.parse(entry.createdAt) < Date.parse(parseCreatedAtIdCursor(query.cursor).createdAt)
|
|
@@ -5517,7 +5433,7 @@ app.get("/hyperframes/:compositionId/editor/docs/history", async (c) => {
|
|
|
5517
5433
|
next_cursor: page.nextCursor
|
|
5518
5434
|
});
|
|
5519
5435
|
});
|
|
5520
|
-
app.patch("/
|
|
5436
|
+
app.patch("/editor/:compositionId/tracers", async (c) => {
|
|
5521
5437
|
const customer = await requireBrowserCustomer(c);
|
|
5522
5438
|
if (!customer) {
|
|
5523
5439
|
return c.json({ error: "Unauthorized" }, 401);
|
|
@@ -5551,7 +5467,6 @@ app.get(`${ACCOUNT_FRONTEND_PREFIX}`, (c) => redirect(c, withAccountQuery("/libr
|
|
|
5551
5467
|
app.get(`${ACCOUNT_FRONTEND_PREFIX}/discover`, (c) => redirectToPublicPath(c, "/discover"));
|
|
5552
5468
|
app.get(`${ACCOUNT_FRONTEND_PREFIX}/help`, (c) => redirectToPublicPath(c, "/help"));
|
|
5553
5469
|
app.get(`${ACCOUNT_FRONTEND_PREFIX}/library`, (c) => dispatchScopedGetToLegacyRoute(c, "/library"));
|
|
5554
|
-
app.get(`${ACCOUNT_FRONTEND_PREFIX}/chat`, (c) => dispatchScopedGetToLegacyRoute(c, "/chat"));
|
|
5555
5470
|
app.get(`${ACCOUNT_FRONTEND_PREFIX}/inpaint`, (c) => dispatchScopedGetToLegacyRoute(c, "/inpaint"));
|
|
5556
5471
|
app.get(`${ACCOUNT_FRONTEND_PREFIX}/calendar`, (c) => dispatchScopedGetToLegacyRoute(c, "/calendar"));
|
|
5557
5472
|
app.get(`${ACCOUNT_FRONTEND_PREFIX}/settings`, (c) => dispatchScopedGetToLegacyRoute(c, "/settings"));
|
|
@@ -5560,39 +5475,6 @@ app.get(`${ACCOUNT_FRONTEND_PREFIX}/job-runs/history`, (c) => dispatchScopedGetT
|
|
|
5560
5475
|
app.get(`${ACCOUNT_FRONTEND_PREFIX}/calendar/feed`, (c) => dispatchScopedGetToLegacyRoute(c, "/calendar/feed"));
|
|
5561
5476
|
app.get(`${ACCOUNT_FRONTEND_PREFIX}/settings/flockposter`, (c) => dispatchScopedGetToLegacyRoute(c, "/settings/flockposter"));
|
|
5562
5477
|
app.post(`${ACCOUNT_FRONTEND_PREFIX}/settings/wallet/funding-link`, (c) => dispatchScopedPostToLegacyRoute(c, "/settings/wallet/funding-link"));
|
|
5563
|
-
app.get(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/docs`, (c) => dispatchScopedGetToLegacyRoute(c, `/templates/${encodeURIComponent(c.req.param("templateId"))}/editor/docs`));
|
|
5564
|
-
app.get(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/docs/history`, (c) => dispatchScopedGetToLegacyRoute(c, `/templates/${encodeURIComponent(c.req.param("templateId"))}/editor/docs/history`));
|
|
5565
|
-
app.patch(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/tracers`, async (c) => {
|
|
5566
|
-
const customer = await requireBrowserCustomer(c);
|
|
5567
|
-
if (!customer) {
|
|
5568
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
5569
|
-
}
|
|
5570
|
-
const template = templateRegistry.get(c.req.param("templateId"));
|
|
5571
|
-
if (!template) {
|
|
5572
|
-
return c.json({ error: "Template not found" }, 404);
|
|
5573
|
-
}
|
|
5574
|
-
const body = tracerRenameSchema.parse(await c.req.json());
|
|
5575
|
-
const jobsUpdated = jobs.renameTracer({
|
|
5576
|
-
customerId: customer.id,
|
|
5577
|
-
templateId: template.id,
|
|
5578
|
-
fromTracer: body.from_tracer,
|
|
5579
|
-
toTracer: body.to_tracer
|
|
5580
|
-
});
|
|
5581
|
-
const historyResult = await apiCallHistoryStore.renameTracer({
|
|
5582
|
-
customerId: customer.id,
|
|
5583
|
-
fromTracer: body.from_tracer,
|
|
5584
|
-
toTracer: body.to_tracer
|
|
5585
|
-
});
|
|
5586
|
-
return c.json({
|
|
5587
|
-
ok: true,
|
|
5588
|
-
from_tracer: body.from_tracer,
|
|
5589
|
-
to_tracer: body.to_tracer,
|
|
5590
|
-
jobs_updated: jobsUpdated,
|
|
5591
|
-
history_entries_updated: historyResult.updated
|
|
5592
|
-
});
|
|
5593
|
-
});
|
|
5594
|
-
app.get(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/tracer/:tracerId`, (c) => dispatchScopedGetToLegacyRoute(c, `/templates/${encodeURIComponent(c.req.param("templateId"))}/editor/tracer/${encodeURIComponent(c.req.param("tracerId"))}`));
|
|
5595
|
-
app.get(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/tracer/:tracerId/history`, (c) => dispatchScopedGetToLegacyRoute(c, `/templates/${encodeURIComponent(c.req.param("templateId"))}/editor/tracer/${encodeURIComponent(c.req.param("tracerId"))}/history`));
|
|
5596
5478
|
app.get("/discover/feed", async (c) => {
|
|
5597
5479
|
const limit = clampPageLimit(Number(c.req.query("limit") || ""), 100, 200);
|
|
5598
5480
|
const page = await getApprovedHomepageTemplates(c, {
|
|
@@ -5895,9 +5777,9 @@ function serveHyperframesEditorAsset(filePath) {
|
|
|
5895
5777
|
}
|
|
5896
5778
|
});
|
|
5897
5779
|
}
|
|
5898
|
-
app.get("/assets/
|
|
5780
|
+
app.get("/assets/editor/*", (c) => {
|
|
5899
5781
|
const pathname = decodeURIComponent(new URL(c.req.url).pathname);
|
|
5900
|
-
const relative = pathname.replace(/^\/assets\/
|
|
5782
|
+
const relative = pathname.replace(/^\/assets\/editor\//, "");
|
|
5901
5783
|
const filePath = resolveHyperframesEditorAsset(relative);
|
|
5902
5784
|
if (!filePath)
|
|
5903
5785
|
return c.text("Not found", 404);
|
|
@@ -5981,7 +5863,7 @@ async function publishCompositionToRenderer(input) {
|
|
|
5981
5863
|
const primitive = primitiveRegistry.get("primitive:hyperframes_render");
|
|
5982
5864
|
const operation = primitive?.operations.run;
|
|
5983
5865
|
if (!primitive || !operation) {
|
|
5984
|
-
throw new Error("
|
|
5866
|
+
throw new Error("Timeline render primitive not found");
|
|
5985
5867
|
}
|
|
5986
5868
|
const tracerValue = tracer ?? `hf-publish-${slugId}-${Date.now()}`;
|
|
5987
5869
|
const payload = operation.inputSchema.parse({
|
|
@@ -6138,7 +6020,7 @@ async function snapshotForkVersion(input) {
|
|
|
6138
6020
|
if (workingJson) {
|
|
6139
6021
|
await storage.putBuffer(storage.compositionForkVersionKey(input.fork.id, version, "composition.json"), Buffer.from(workingJson, "utf8"), "application/json");
|
|
6140
6022
|
}
|
|
6141
|
-
await serverlessRecords.createForkVersion({
|
|
6023
|
+
const versionRecord = await serverlessRecords.createForkVersion({
|
|
6142
6024
|
forkId: input.fork.id,
|
|
6143
6025
|
ownerCustomerId: input.fork.customerId,
|
|
6144
6026
|
version,
|
|
@@ -6148,10 +6030,19 @@ async function snapshotForkVersion(input) {
|
|
|
6148
6030
|
storagePrefix: storage.compositionForkVersionPrefix(input.fork.id, version),
|
|
6149
6031
|
renderJobId: input.renderJobId ?? null
|
|
6150
6032
|
});
|
|
6151
|
-
await serverlessRecords.updateCompositionFork({
|
|
6033
|
+
const updatedFork = await serverlessRecords.updateCompositionFork({
|
|
6152
6034
|
forkId: input.fork.id,
|
|
6153
6035
|
patch: { latestVersion: version }
|
|
6154
6036
|
});
|
|
6037
|
+
const forkForManifest = updatedFork ?? { ...input.fork, latestVersion: version };
|
|
6038
|
+
// Write the frozen version manifest, then refresh the working manifest so
|
|
6039
|
+
// its latest_version pointer stays in sync.
|
|
6040
|
+
await writeForkManifest(storage, forkForManifest, {
|
|
6041
|
+
kind: "version",
|
|
6042
|
+
version,
|
|
6043
|
+
createdAt: versionRecord.createdAt
|
|
6044
|
+
});
|
|
6045
|
+
await writeForkManifest(storage, forkForManifest, { kind: "working" });
|
|
6155
6046
|
return version;
|
|
6156
6047
|
}
|
|
6157
6048
|
function serializeFork(fork, caps) {
|
|
@@ -6227,6 +6118,7 @@ app.post(COMPOSITIONS_PREFIX, async (c) => {
|
|
|
6227
6118
|
title
|
|
6228
6119
|
});
|
|
6229
6120
|
await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), rawHtml, "text/html; charset=utf-8");
|
|
6121
|
+
await writeForkManifest(storage, fork, { kind: "working" });
|
|
6230
6122
|
await serverlessRecords.updateSubmittedVideo({
|
|
6231
6123
|
videoId: video.id,
|
|
6232
6124
|
patch: { defaultForkId: fork.id }
|
|
@@ -6252,6 +6144,7 @@ app.post(COMPOSITIONS_PREFIX, async (c) => {
|
|
|
6252
6144
|
});
|
|
6253
6145
|
await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), compositionHtml, "text/html; charset=utf-8");
|
|
6254
6146
|
await storage.putBuffer(storage.compositionForkWorkingKey(fork.id, "composition.json"), Buffer.from(compositionJsonText, "utf8"), "application/json");
|
|
6147
|
+
await writeForkManifest(storage, fork, { kind: "working" });
|
|
6255
6148
|
return c.json(serializeFork(fork, {
|
|
6256
6149
|
isOwner: true, canView: true, canEdit: true, canPublish: true, canShare: true, canDelete: true, role: "owner"
|
|
6257
6150
|
}), 201);
|
|
@@ -6301,6 +6194,24 @@ app.get(`${COMPOSITIONS_PREFIX}/:forkId/composition.html`, async (c) => {
|
|
|
6301
6194
|
return forkAccessErrorResponse(c, error);
|
|
6302
6195
|
}
|
|
6303
6196
|
});
|
|
6197
|
+
app.get(`${COMPOSITIONS_PREFIX}/:forkId/manifest.json`, async (c) => {
|
|
6198
|
+
const customer = await getBrowserCustomer(c);
|
|
6199
|
+
try {
|
|
6200
|
+
const access = await assertForkAccess({ forkId: c.req.param("forkId"), customerId: customer?.id ?? null, shareToken: readShareToken(c) }, "view");
|
|
6201
|
+
const text = await storage.readText(storage.compositionForkWorkingKey(access.fork.id, "manifest.json"));
|
|
6202
|
+
if (!text)
|
|
6203
|
+
return c.json({ ok: false, error: "Manifest missing" }, 404);
|
|
6204
|
+
try {
|
|
6205
|
+
return c.json(JSON.parse(text));
|
|
6206
|
+
}
|
|
6207
|
+
catch {
|
|
6208
|
+
return c.json({ ok: false, error: "Manifest is not valid JSON" }, 500);
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
catch (error) {
|
|
6212
|
+
return forkAccessErrorResponse(c, error);
|
|
6213
|
+
}
|
|
6214
|
+
});
|
|
6304
6215
|
app.put(`${COMPOSITIONS_PREFIX}/:forkId/composition.html`, async (c) => {
|
|
6305
6216
|
const customer = await getBrowserCustomer(c);
|
|
6306
6217
|
if (!customer)
|
|
@@ -6312,7 +6223,8 @@ app.put(`${COMPOSITIONS_PREFIX}/:forkId/composition.html`, async (c) => {
|
|
|
6312
6223
|
return c.json({ ok: false, error: "Composition HTML is missing data-composition-id" }, 400);
|
|
6313
6224
|
}
|
|
6314
6225
|
await storage.putText(storage.compositionForkWorkingKey(access.fork.id, "composition.html"), html, "text/html; charset=utf-8");
|
|
6315
|
-
await serverlessRecords.updateCompositionFork({ forkId: access.fork.id, patch: {} });
|
|
6226
|
+
const updated = await serverlessRecords.updateCompositionFork({ forkId: access.fork.id, patch: {} });
|
|
6227
|
+
await writeForkManifest(storage, updated ?? access.fork, { kind: "working" });
|
|
6316
6228
|
return c.json({ ok: true, bytes: html.length });
|
|
6317
6229
|
}
|
|
6318
6230
|
catch (error) {
|
|
@@ -6350,7 +6262,8 @@ app.patch(`${COMPOSITIONS_PREFIX}/:forkId/composition.json`, async (c) => {
|
|
|
6350
6262
|
const existing = existingText ? asRecord(JSON.parse(existingText)) ?? {} : {};
|
|
6351
6263
|
const merged = { ...existing, ...patch };
|
|
6352
6264
|
await storage.putBuffer(storage.compositionForkWorkingKey(access.fork.id, "composition.json"), Buffer.from(JSON.stringify(merged, null, 2), "utf8"), "application/json");
|
|
6353
|
-
await serverlessRecords.updateCompositionFork({ forkId: access.fork.id, patch: {} });
|
|
6265
|
+
const updated = await serverlessRecords.updateCompositionFork({ forkId: access.fork.id, patch: {} });
|
|
6266
|
+
await writeForkManifest(storage, updated ?? access.fork, { kind: "working" });
|
|
6354
6267
|
return c.json(merged);
|
|
6355
6268
|
}
|
|
6356
6269
|
catch (error) {
|
|
@@ -6728,7 +6641,7 @@ app.post(`${COMPOSITIONS_PREFIX}/:forkId/ghostcut-poll`, async (c) => {
|
|
|
6728
6641
|
return c.json({ ok: false, error: error instanceof Error ? error.message : "ghostcut-poll failed" }, 500);
|
|
6729
6642
|
}
|
|
6730
6643
|
});
|
|
6731
|
-
app.post(`${COMPOSITIONS_PREFIX}/:forkId/
|
|
6644
|
+
app.post(`${COMPOSITIONS_PREFIX}/:forkId/export`, async (c) => {
|
|
6732
6645
|
const caller = await getBrowserCustomer(c);
|
|
6733
6646
|
if (!caller)
|
|
6734
6647
|
return c.json({ ok: false, error: "Unauthorized" }, 401);
|
|
@@ -6746,6 +6659,7 @@ app.post(`${COMPOSITIONS_PREFIX}/:forkId/publish`, async (c) => {
|
|
|
6746
6659
|
return c.json({ ok: false, error: "Composition HTML is missing data-composition-id" }, 400);
|
|
6747
6660
|
}
|
|
6748
6661
|
await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), submittedHtml, "text/html; charset=utf-8");
|
|
6662
|
+
await writeForkManifest(storage, fork, { kind: "working" });
|
|
6749
6663
|
}
|
|
6750
6664
|
const versionParam = readNonEmptyString(requestBody.version);
|
|
6751
6665
|
const requestedVersion = versionParam ? Number.parseInt(versionParam, 10) : null;
|
|
@@ -7042,6 +6956,9 @@ app.patch(`${COMPOSITIONS_PREFIX}/:forkId/visibility`, async (c) => {
|
|
|
7042
6956
|
forkId: access.fork.id,
|
|
7043
6957
|
patch: { visibility }
|
|
7044
6958
|
});
|
|
6959
|
+
if (updated) {
|
|
6960
|
+
await writeForkManifest(storage, updated, { kind: "working" });
|
|
6961
|
+
}
|
|
7045
6962
|
return c.json(serializeFork(updated, access.capabilities));
|
|
7046
6963
|
}
|
|
7047
6964
|
catch (error) {
|
|
@@ -7150,6 +7067,27 @@ app.get(`${COMPOSITIONS_PREFIX}/:forkId/versions/:version/composition.json`, asy
|
|
|
7150
7067
|
return forkAccessErrorResponse(c, error);
|
|
7151
7068
|
}
|
|
7152
7069
|
});
|
|
7070
|
+
app.get(`${COMPOSITIONS_PREFIX}/:forkId/versions/:version/manifest.json`, async (c) => {
|
|
7071
|
+
const customer = await getBrowserCustomer(c);
|
|
7072
|
+
try {
|
|
7073
|
+
const access = await assertForkAccess({ forkId: c.req.param("forkId"), customerId: customer?.id ?? null, shareToken: readShareToken(c) }, "view");
|
|
7074
|
+
const version = Number.parseInt(c.req.param("version"), 10);
|
|
7075
|
+
if (!Number.isFinite(version))
|
|
7076
|
+
return c.json({ ok: false, error: "Invalid version" }, 400);
|
|
7077
|
+
const text = await storage.readText(storage.compositionForkVersionKey(access.fork.id, version, "manifest.json"));
|
|
7078
|
+
if (!text)
|
|
7079
|
+
return c.json({ ok: false, error: "Version manifest not found" }, 404);
|
|
7080
|
+
try {
|
|
7081
|
+
return c.json(JSON.parse(text));
|
|
7082
|
+
}
|
|
7083
|
+
catch {
|
|
7084
|
+
return c.json({ ok: false, error: "Manifest is not valid JSON" }, 500);
|
|
7085
|
+
}
|
|
7086
|
+
}
|
|
7087
|
+
catch (error) {
|
|
7088
|
+
return forkAccessErrorResponse(c, error);
|
|
7089
|
+
}
|
|
7090
|
+
});
|
|
7153
7091
|
app.post(`${COMPOSITIONS_PREFIX}/:forkId/versions`, async (c) => {
|
|
7154
7092
|
const customer = await getBrowserCustomer(c);
|
|
7155
7093
|
if (!customer)
|
|
@@ -7398,6 +7336,7 @@ app.post(`${VIDEOS_PREFIX}/:videoId/decompose`, async (c) => {
|
|
|
7398
7336
|
});
|
|
7399
7337
|
await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), readFileSync(compositionFile, "utf8"), "text/html; charset=utf-8");
|
|
7400
7338
|
await storage.putBuffer(storage.compositionForkWorkingKey(fork.id, "composition.json"), Buffer.from(readFileSync(compositionJsonFile, "utf8"), "utf8"), "application/json");
|
|
7339
|
+
await writeForkManifest(storage, fork, { kind: "working" });
|
|
7401
7340
|
return c.json(serializeFork(fork, {
|
|
7402
7341
|
isOwner: true, canView: true, canEdit: true, canPublish: true, canShare: true, canDelete: true, role: "owner"
|
|
7403
7342
|
}), 201);
|
|
@@ -8110,16 +8049,13 @@ app.get("/library", async (c) => {
|
|
|
8110
8049
|
}))
|
|
8111
8050
|
];
|
|
8112
8051
|
const posts = await Promise.all((await serverlessRecords.listReadyPosts(customer.id))
|
|
8052
|
+
.filter((post) => (post.source ?? "legacy") === "hyperframes")
|
|
8113
8053
|
.filter((post) => post.status === "ready" || post.status === "scheduled")
|
|
8114
8054
|
.map(async (post) => {
|
|
8115
8055
|
const serialized = serializeReadyPost(c, post, { includePrivate: true });
|
|
8116
|
-
const linkedJob = post.tracer
|
|
8117
|
-
? (await jobs.listJobsAsync({ customerId: post.customerId, tracer: post.tracer, limit: 100 }))
|
|
8118
|
-
.find((job) => !job.templateId.startsWith("primitive:") && Boolean(templateRegistry.get(job.templateId)))
|
|
8119
|
-
: null;
|
|
8120
8056
|
return {
|
|
8121
8057
|
...serialized,
|
|
8122
|
-
template_id:
|
|
8058
|
+
template_id: post.compositionId ?? null
|
|
8123
8059
|
};
|
|
8124
8060
|
}));
|
|
8125
8061
|
return c.html(renderLibraryPage({
|
|
@@ -8138,21 +8074,6 @@ app.get("/library", async (c) => {
|
|
|
8138
8074
|
})
|
|
8139
8075
|
}));
|
|
8140
8076
|
});
|
|
8141
|
-
app.get("/chat", async (c) => {
|
|
8142
|
-
const customer = await getPreferredBrowserCustomer(c);
|
|
8143
|
-
if (!customer) {
|
|
8144
|
-
return redirect(c, "/login");
|
|
8145
|
-
}
|
|
8146
|
-
return c.html(renderChatPage({
|
|
8147
|
-
userId: customer.id,
|
|
8148
|
-
currentAccountId: customer.id,
|
|
8149
|
-
name: customer.name,
|
|
8150
|
-
email: customer.email,
|
|
8151
|
-
editorChat: await buildChatBrainstormEditorChatBoot({
|
|
8152
|
-
customer
|
|
8153
|
-
})
|
|
8154
|
-
}));
|
|
8155
|
-
});
|
|
8156
8077
|
app.get("/inpaint", async (c) => {
|
|
8157
8078
|
const customer = await requireBrowserCustomer(c);
|
|
8158
8079
|
if (!customer) {
|
|
@@ -8233,239 +8154,6 @@ app.get("/job-runs/history", async (c) => {
|
|
|
8233
8154
|
next_cursor: page.nextCursor
|
|
8234
8155
|
});
|
|
8235
8156
|
});
|
|
8236
|
-
app.get("/templates/:templateId/editor/docs", async (c) => {
|
|
8237
|
-
const template = templateRegistry.get(c.req.param("templateId"));
|
|
8238
|
-
if (!template) {
|
|
8239
|
-
return c.text("Template not found", 404);
|
|
8240
|
-
}
|
|
8241
|
-
const customer = await getPreferredBrowserCustomer(c);
|
|
8242
|
-
const url = new URL(c.req.url);
|
|
8243
|
-
const tracers = parseTracerList(url.searchParams);
|
|
8244
|
-
const docsRoutes = [
|
|
8245
|
-
...buildTemplateEditorDocsRoutes(template),
|
|
8246
|
-
...buildPrimitiveEditorDocsRoutes()
|
|
8247
|
-
];
|
|
8248
|
-
const directorSkill = readRootSkillFile("SKILL.director.md", "SKILL.user.md") || null;
|
|
8249
|
-
return c.html(renderTemplateEditorDocsPage({
|
|
8250
|
-
account: {
|
|
8251
|
-
userId: customer?.id ?? null,
|
|
8252
|
-
name: customer?.name ?? null,
|
|
8253
|
-
email: customer?.email ?? null,
|
|
8254
|
-
isLoggedIn: Boolean(customer)
|
|
8255
|
-
},
|
|
8256
|
-
template: {
|
|
8257
|
-
id: template.id,
|
|
8258
|
-
slugId: template.slugId,
|
|
8259
|
-
version: template.version,
|
|
8260
|
-
title: template.about.title,
|
|
8261
|
-
description: template.about.description,
|
|
8262
|
-
difficulty: template.about.difficulty,
|
|
8263
|
-
viralDna: template.about.viral_dna,
|
|
8264
|
-
visualDna: template.about.visual_dna,
|
|
8265
|
-
sound: template.about.sound,
|
|
8266
|
-
proposalGenerator: template.about.proposal_generator,
|
|
8267
|
-
skeletonPrompt: template.about.skeleton_prompt,
|
|
8268
|
-
samplePrompts: template.about.sample_prompts,
|
|
8269
|
-
examples: template.examples.map((example) => example.map((entry) => ({
|
|
8270
|
-
mediaUrl: resolveTemplateExampleMediaUrl(c, template.id, entry.media_url),
|
|
8271
|
-
note: entry.note
|
|
8272
|
-
}))),
|
|
8273
|
-
previewUrl: template.about.preview_media[0] ? resolveTemplateAboutMediaUrl(c, template.id, template.about.preview_media[0]) : null,
|
|
8274
|
-
skillUrl: buildAbsoluteUrl(c, `${TEMPLATES_PREFIX}/${template.id}/skill`),
|
|
8275
|
-
operations: Object.entries(template.operations).map(([name, operation]) => ({
|
|
8276
|
-
name,
|
|
8277
|
-
description: operation.description
|
|
8278
|
-
}))
|
|
8279
|
-
},
|
|
8280
|
-
directorSkill,
|
|
8281
|
-
docsRoutes,
|
|
8282
|
-
sampleTracerId: tracers[0] ?? `${template.slugId}-001`,
|
|
8283
|
-
historyEndpoint: withAccountQuery(`/templates/${template.id}/editor/docs/history?limit=250`, customer?.id),
|
|
8284
|
-
tracerEndpoint: withAccountQuery(`/templates/${template.id}/editor/tracers`, customer?.id),
|
|
8285
|
-
initialTracers: tracers,
|
|
8286
|
-
editorChat: await buildTemplateEditorChatBoot({
|
|
8287
|
-
customer,
|
|
8288
|
-
template,
|
|
8289
|
-
page: "docs",
|
|
8290
|
-
tracers,
|
|
8291
|
-
activeTracer: tracers[0] ?? null,
|
|
8292
|
-
docsRoutes
|
|
8293
|
-
})
|
|
8294
|
-
}));
|
|
8295
|
-
});
|
|
8296
|
-
app.get("/templates/:templateId/editor/docs/history", async (c) => {
|
|
8297
|
-
const customer = await requireBrowserCustomer(c);
|
|
8298
|
-
if (!customer) {
|
|
8299
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
8300
|
-
}
|
|
8301
|
-
const template = templateRegistry.get(c.req.param("templateId"));
|
|
8302
|
-
if (!template) {
|
|
8303
|
-
return c.json({ error: "Template not found" }, 404);
|
|
8304
|
-
}
|
|
8305
|
-
const url = new URL(c.req.url);
|
|
8306
|
-
const query = apiCallHistoryQuerySchema.parse({
|
|
8307
|
-
start_time: c.req.query("start_time"),
|
|
8308
|
-
end_time: c.req.query("end_time"),
|
|
8309
|
-
limit: c.req.query("limit"),
|
|
8310
|
-
cursor: c.req.query("cursor"),
|
|
8311
|
-
tracers: parseTracerList(url.searchParams)
|
|
8312
|
-
});
|
|
8313
|
-
const limit = clampPageLimit(query.limit, 250);
|
|
8314
|
-
const tracerSet = new Set(query.tracers ?? []);
|
|
8315
|
-
const selectedTracers = query.tracers ?? [];
|
|
8316
|
-
const entries = (await buildJobRunHistoryEntries({
|
|
8317
|
-
customerId: customer.id,
|
|
8318
|
-
startTime: query.start_time,
|
|
8319
|
-
endTime: query.end_time,
|
|
8320
|
-
templateId: template.id,
|
|
8321
|
-
tracers: selectedTracers,
|
|
8322
|
-
includePrimitiveJobs: true,
|
|
8323
|
-
entries: await apiCallHistoryStore.listEntries({
|
|
8324
|
-
customerId: customer.id,
|
|
8325
|
-
startTime: query.start_time,
|
|
8326
|
-
endTime: query.end_time
|
|
8327
|
-
})
|
|
8328
|
-
})).filter((entry) => {
|
|
8329
|
-
if (String(entry.method || "").toUpperCase() !== "POST") {
|
|
8330
|
-
return false;
|
|
8331
|
-
}
|
|
8332
|
-
if (!matchesEditorApiHistoryPath(entry.path, template)) {
|
|
8333
|
-
return false;
|
|
8334
|
-
}
|
|
8335
|
-
if (!tracerSet.size) {
|
|
8336
|
-
return true;
|
|
8337
|
-
}
|
|
8338
|
-
return doesTracerMatchSelection(entry.tracer, tracerSet);
|
|
8339
|
-
});
|
|
8340
|
-
const filteredEntries = parseCreatedAtIdCursor(query.cursor)
|
|
8341
|
-
? entries.filter((entry) => (Date.parse(entry.createdAt) < Date.parse(parseCreatedAtIdCursor(query.cursor).createdAt)
|
|
8342
|
-
|| (entry.createdAt === parseCreatedAtIdCursor(query.cursor).createdAt && entry.id < parseCreatedAtIdCursor(query.cursor).id)))
|
|
8343
|
-
: entries;
|
|
8344
|
-
const page = paginateDescendingByCreatedAt(filteredEntries, limit);
|
|
8345
|
-
return c.json({
|
|
8346
|
-
entries: page.items,
|
|
8347
|
-
storage_driver: config.STORAGE_DRIVER,
|
|
8348
|
-
template_id: template.id,
|
|
8349
|
-
tracers: selectedTracers,
|
|
8350
|
-
next_cursor: page.nextCursor
|
|
8351
|
-
});
|
|
8352
|
-
});
|
|
8353
|
-
app.patch("/templates/:templateId/editor/tracers", async (c) => {
|
|
8354
|
-
const customer = await requireBrowserCustomer(c);
|
|
8355
|
-
if (!customer) {
|
|
8356
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
8357
|
-
}
|
|
8358
|
-
const template = templateRegistry.get(c.req.param("templateId"));
|
|
8359
|
-
if (!template) {
|
|
8360
|
-
return c.json({ error: "Template not found" }, 404);
|
|
8361
|
-
}
|
|
8362
|
-
const body = tracerRenameSchema.parse(await c.req.json());
|
|
8363
|
-
const jobsUpdated = jobs.renameTracer({
|
|
8364
|
-
customerId: customer.id,
|
|
8365
|
-
templateId: template.id,
|
|
8366
|
-
fromTracer: body.from_tracer,
|
|
8367
|
-
toTracer: body.to_tracer
|
|
8368
|
-
});
|
|
8369
|
-
const historyResult = await apiCallHistoryStore.renameTracer({
|
|
8370
|
-
customerId: customer.id,
|
|
8371
|
-
fromTracer: body.from_tracer,
|
|
8372
|
-
toTracer: body.to_tracer
|
|
8373
|
-
});
|
|
8374
|
-
return c.json({
|
|
8375
|
-
ok: true,
|
|
8376
|
-
from_tracer: body.from_tracer,
|
|
8377
|
-
to_tracer: body.to_tracer,
|
|
8378
|
-
jobs_updated: jobsUpdated,
|
|
8379
|
-
history_entries_updated: historyResult.updated
|
|
8380
|
-
});
|
|
8381
|
-
});
|
|
8382
|
-
app.get("/templates/:templateId/editor/tracer/:tracerId", async (c) => {
|
|
8383
|
-
const customer = await requireBrowserCustomer(c);
|
|
8384
|
-
if (!customer) {
|
|
8385
|
-
return redirect(c, "/login");
|
|
8386
|
-
}
|
|
8387
|
-
const template = templateRegistry.get(c.req.param("templateId"));
|
|
8388
|
-
if (!template) {
|
|
8389
|
-
return c.text("Template not found", 404);
|
|
8390
|
-
}
|
|
8391
|
-
const tracerId = c.req.param("tracerId");
|
|
8392
|
-
const docsRoutes = [
|
|
8393
|
-
...buildTemplateEditorDocsRoutes(template),
|
|
8394
|
-
...buildPrimitiveEditorDocsRoutes()
|
|
8395
|
-
];
|
|
8396
|
-
return c.html(renderJobRunsPage({
|
|
8397
|
-
userId: customer.id,
|
|
8398
|
-
currentAccountId: customer.id,
|
|
8399
|
-
name: customer.name,
|
|
8400
|
-
email: customer.email,
|
|
8401
|
-
storageDriver: config.STORAGE_DRIVER,
|
|
8402
|
-
jobs: [],
|
|
8403
|
-
apiCallHistory: [],
|
|
8404
|
-
loadEndpoint: withAccountQuery(`/templates/${template.id}/editor/tracer/${encodeURIComponent(tracerId)}/history?limit=250`, customer.id),
|
|
8405
|
-
pageTitle: `${template.about.title} tracer`,
|
|
8406
|
-
headerTitle: `${template.about.title} tracer`,
|
|
8407
|
-
initialGroupBy: "route",
|
|
8408
|
-
methodScope: "all",
|
|
8409
|
-
editorChrome: {
|
|
8410
|
-
backHref: withAccountQuery(`/templates/${template.id}/editor/docs`, customer.id),
|
|
8411
|
-
templateTitle: template.about.title,
|
|
8412
|
-
templateId: template.id,
|
|
8413
|
-
templateSlug: template.slugId
|
|
8414
|
-
},
|
|
8415
|
-
editorChat: await buildTemplateEditorChatBoot({
|
|
8416
|
-
customer,
|
|
8417
|
-
template,
|
|
8418
|
-
page: "tracer",
|
|
8419
|
-
tracerId,
|
|
8420
|
-
tracers: [tracerId],
|
|
8421
|
-
activeTracer: tracerId,
|
|
8422
|
-
docsRoutes
|
|
8423
|
-
})
|
|
8424
|
-
}));
|
|
8425
|
-
});
|
|
8426
|
-
app.get("/templates/:templateId/editor/tracer/:tracerId/history", async (c) => {
|
|
8427
|
-
const customer = await requireBrowserCustomer(c);
|
|
8428
|
-
if (!customer) {
|
|
8429
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
8430
|
-
}
|
|
8431
|
-
const template = templateRegistry.get(c.req.param("templateId"));
|
|
8432
|
-
if (!template) {
|
|
8433
|
-
return c.json({ error: "Template not found" }, 404);
|
|
8434
|
-
}
|
|
8435
|
-
const tracerId = c.req.param("tracerId");
|
|
8436
|
-
const query = apiCallHistoryQuerySchema.parse({
|
|
8437
|
-
start_time: c.req.query("start_time"),
|
|
8438
|
-
end_time: c.req.query("end_time"),
|
|
8439
|
-
cursor: c.req.query("cursor"),
|
|
8440
|
-
limit: c.req.query("limit")
|
|
8441
|
-
});
|
|
8442
|
-
const limit = clampPageLimit(query.limit, 250);
|
|
8443
|
-
const entries = (await buildJobRunHistoryEntries({
|
|
8444
|
-
customerId: customer.id,
|
|
8445
|
-
startTime: query.start_time,
|
|
8446
|
-
endTime: query.end_time,
|
|
8447
|
-
templateId: template.id,
|
|
8448
|
-
tracers: [tracerId],
|
|
8449
|
-
includePrimitiveJobs: true,
|
|
8450
|
-
entries: await apiCallHistoryStore.listEntries({
|
|
8451
|
-
customerId: customer.id,
|
|
8452
|
-
startTime: query.start_time,
|
|
8453
|
-
endTime: query.end_time
|
|
8454
|
-
})
|
|
8455
|
-
})).filter((entry) => doesTracerMatchSelection(entry.tracer, [tracerId]) && matchesEditorApiHistoryPath(entry.path, template));
|
|
8456
|
-
const filteredEntries = parseCreatedAtIdCursor(query.cursor)
|
|
8457
|
-
? entries.filter((entry) => (Date.parse(entry.createdAt) < Date.parse(parseCreatedAtIdCursor(query.cursor).createdAt)
|
|
8458
|
-
|| (entry.createdAt === parseCreatedAtIdCursor(query.cursor).createdAt && entry.id < parseCreatedAtIdCursor(query.cursor).id)))
|
|
8459
|
-
: entries;
|
|
8460
|
-
const page = paginateDescendingByCreatedAt(filteredEntries, limit);
|
|
8461
|
-
return c.json({
|
|
8462
|
-
entries: page.items,
|
|
8463
|
-
storage_driver: config.STORAGE_DRIVER,
|
|
8464
|
-
template_id: template.id,
|
|
8465
|
-
tracer_id: tracerId,
|
|
8466
|
-
next_cursor: page.nextCursor
|
|
8467
|
-
});
|
|
8468
|
-
});
|
|
8469
8157
|
app.get("/calendar", async (c) => {
|
|
8470
8158
|
const customer = await requireBrowserCustomer(c);
|
|
8471
8159
|
if (!customer) {
|
|
@@ -9338,7 +9026,7 @@ app.post(`${ADMIN_TEMPLATES_PREFIX}/releases/:releaseId/activate`, async (c) =>
|
|
|
9338
9026
|
// Behavior: waits for the download to finish (up to WAIT_TIMEOUT_MS), then
|
|
9339
9027
|
// creates a Fork with a decomposed composition ready to open in the editor.
|
|
9340
9028
|
// Returns { fork_id, editor_url, video_id } in a single response.
|
|
9341
|
-
app.post(`${API_PREFIX}/admin/
|
|
9029
|
+
app.post(`${API_PREFIX}/admin/tiktok-submissions`, async (c) => {
|
|
9342
9030
|
try {
|
|
9343
9031
|
requireSuperagency(c);
|
|
9344
9032
|
}
|
|
@@ -9525,11 +9213,11 @@ async function ensureSubmittedVideoReadyThenFork(input) {
|
|
|
9525
9213
|
};
|
|
9526
9214
|
}
|
|
9527
9215
|
function buildForkEditorUrl(publicBaseUrl, fork) {
|
|
9528
|
-
const path = `/
|
|
9216
|
+
const path = `/editor/${encodeURIComponent(fork.sourceSlugId)}?fork=${encodeURIComponent(fork.id)}`;
|
|
9529
9217
|
return publicBaseUrl ? `${publicBaseUrl.replace(/\/$/, "")}${path}` : path;
|
|
9530
9218
|
}
|
|
9531
9219
|
function buildTikTokEditorUrl(publicBaseUrl, slugId) {
|
|
9532
|
-
const path = `/
|
|
9220
|
+
const path = `/editor/${encodeURIComponent(slugId)}`;
|
|
9533
9221
|
return publicBaseUrl ? `${publicBaseUrl.replace(/\/$/, "")}${path}` : path;
|
|
9534
9222
|
}
|
|
9535
9223
|
function buildRawTikTokCompositionHtml(input) {
|
|
@@ -9554,7 +9242,7 @@ function buildRawTikTokCompositionHtml(input) {
|
|
|
9554
9242
|
</body>
|
|
9555
9243
|
</html>`;
|
|
9556
9244
|
}
|
|
9557
|
-
app.get(`${API_PREFIX}/admin/
|
|
9245
|
+
app.get(`${API_PREFIX}/admin/tiktok-submissions`, async (c) => {
|
|
9558
9246
|
try {
|
|
9559
9247
|
requireSuperagency(c);
|
|
9560
9248
|
}
|
|
@@ -12306,7 +11994,7 @@ function primitiveAliasPathForKind(kind) {
|
|
|
12306
11994
|
case "video_render_slides":
|
|
12307
11995
|
return `${PRIMITIVES_PREFIX}/videos/render-slides`;
|
|
12308
11996
|
case "hyperframes_render":
|
|
12309
|
-
return `${PRIMITIVES_PREFIX}/
|
|
11997
|
+
return `${PRIMITIVES_PREFIX}/timeline/render`;
|
|
12310
11998
|
case "video_normalize":
|
|
12311
11999
|
return `${PRIMITIVES_PREFIX}/videos/normalize`;
|
|
12312
12000
|
case "video_download":
|
|
@@ -12952,7 +12640,7 @@ app.post(`${PRIMITIVES_PREFIX}/images/remove-background`, async (c) => createPri
|
|
|
12952
12640
|
app.post(`${PRIMITIVES_PREFIX}/videos/generate`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_generate", operationName: "run" }));
|
|
12953
12641
|
app.post(`${PRIMITIVES_PREFIX}/videos/download`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_download", operationName: "run" }));
|
|
12954
12642
|
app.post(`${PRIMITIVES_PREFIX}/videos/render-slides`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_render_slides", operationName: "run" }));
|
|
12955
|
-
app.post(`${PRIMITIVES_PREFIX}/
|
|
12643
|
+
app.post(`${PRIMITIVES_PREFIX}/timeline/render`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:hyperframes_render", operationName: "run" }));
|
|
12956
12644
|
app.post(`${PRIMITIVES_PREFIX}/videos/normalize`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_normalize", operationName: "run" }));
|
|
12957
12645
|
app.post(`${PRIMITIVES_PREFIX}/videos/trim`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_trim", operationName: "run" }));
|
|
12958
12646
|
app.post(`${PRIMITIVES_PREFIX}/videos/extract-audio`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_extract_audio", operationName: "run" }));
|