@xpert-ai/plugin-motion 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.xpertai-plugin/plugin.json +16 -6
- package/README.md +19 -5
- package/assets/upstream/UPSTREAM-BASELINE.md +27 -0
- package/assets/upstream/app/data/reicon-LICENSE.txt +21 -0
- package/dist/assets/upstream/UPSTREAM-BASELINE.md +27 -0
- package/dist/assets/upstream/app/data/reicon-LICENSE.txt +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -9
- package/dist/index.js.map +1 -1
- package/dist/lib/constants.d.ts +7 -1
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +7 -0
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/entities/motion-export.entity.d.ts +13 -1
- package/dist/lib/entities/motion-export.entity.d.ts.map +1 -1
- package/dist/lib/entities/motion-export.entity.js +49 -1
- package/dist/lib/entities/motion-export.entity.js.map +1 -1
- package/dist/lib/entities/motion-project-version.entity.d.ts +3 -1
- package/dist/lib/entities/motion-project-version.entity.d.ts.map +1 -1
- package/dist/lib/entities/motion-project-version.entity.js +8 -0
- package/dist/lib/entities/motion-project-version.entity.js.map +1 -1
- package/dist/lib/entities/motion-project.entity.d.ts +4 -1
- package/dist/lib/entities/motion-project.entity.d.ts.map +1 -1
- package/dist/lib/entities/motion-project.entity.js +8 -0
- package/dist/lib/entities/motion-project.entity.js.map +1 -1
- package/dist/lib/hyperframes-composition.d.ts +16 -0
- package/dist/lib/hyperframes-composition.d.ts.map +1 -0
- package/dist/lib/hyperframes-composition.js +106 -0
- package/dist/lib/hyperframes-composition.js.map +1 -0
- package/dist/lib/motion-agent-response.d.ts +8 -0
- package/dist/lib/motion-agent-response.d.ts.map +1 -1
- package/dist/lib/motion-agent-response.js +13 -0
- package/dist/lib/motion-agent-response.js.map +1 -1
- package/dist/lib/motion-render.processor.d.ts +9 -0
- package/dist/lib/motion-render.processor.d.ts.map +1 -0
- package/dist/lib/motion-render.processor.js +25 -0
- package/dist/lib/motion-render.processor.js.map +1 -0
- package/dist/lib/motion-view.provider.d.ts.map +1 -1
- package/dist/lib/motion-view.provider.js +31 -2
- package/dist/lib/motion-view.provider.js.map +1 -1
- package/dist/lib/motion.middleware.d.ts.map +1 -1
- package/dist/lib/motion.middleware.js +20 -6
- package/dist/lib/motion.middleware.js.map +1 -1
- package/dist/lib/motion.plugin.d.ts.map +1 -1
- package/dist/lib/motion.plugin.js +2 -1
- package/dist/lib/motion.plugin.js.map +1 -1
- package/dist/lib/motion.service.d.ts +174 -3
- package/dist/lib/motion.service.d.ts.map +1 -1
- package/dist/lib/motion.service.js +376 -19
- package/dist/lib/motion.service.js.map +1 -1
- package/dist/lib/remote-components/motion-workbench/app.css +1 -1
- package/dist/lib/remote-components/motion-workbench/app.js +553 -38
- package/dist/lib/types.d.ts +26 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/sandbox-actions/hyperframes-render/action.json +10 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runner.mjs +188 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/darwin-arm64/README.md +3 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/darwin-arm64/bin/esbuild +0 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/darwin-arm64/package.json +20 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/linux-x64/README.md +3 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/linux-x64/bin/esbuild +0 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@esbuild/linux-x64/package.json +20 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/LICENSE +190 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/README.md +156 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/distributed.js +156122 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/hyperframe.manifest.json +22 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/hyperframe.runtime.iife.js +356 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/index.js +169375 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/public-server.js +167618 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/services/healthWorkerThread.js +53 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/dist/services/shaderTransitionWorker.js +606 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/@hyperframes/producer/package.json +109 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/LICENSE.md +21 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/README.md +3 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/bin/esbuild +0 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/install.js +289 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/lib/main.js +2532 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/esbuild/package.json +49 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/LICENSE +20 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/README.md +36 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.browser.cjs +42 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.browser.js +42 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.cjs +44 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.js +44 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/index.native.js +35 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/async/package.json +12 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/bin/nanoid.cjs +55 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.browser.cjs +44 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.browser.js +44 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.cjs +57 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.d.cts +91 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/index.js +62 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/nanoid.js +1 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/non-secure/index.cjs +24 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/non-secure/index.js +24 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/non-secure/package.json +6 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/package.json +89 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/url-alphabet/index.cjs +4 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/url-alphabet/index.js +4 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/nanoid/url-alphabet/package.json +6 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/LICENSE +15 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/README.md +21 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/package.json +25 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/picocolors.browser.js +4 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/picocolors/picocolors.js +75 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/LICENSE +20 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/README.md +25 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/at-rule.js +25 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/comment.js +13 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/container.js +447 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/css-syntax-error.js +133 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/declaration.js +24 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/document.js +33 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/fromJSON.js +68 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/input.js +276 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/lazy-result.js +563 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/list.js +58 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/map-generator.js +376 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/no-work-result.js +137 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/node.js +456 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/parse.js +42 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/parser.js +618 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/postcss.d.mts +66 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/postcss.js +101 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/postcss.mjs +30 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/previous-map.js +161 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/processor.js +67 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/result.js +42 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/root.js +61 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/rule.js +27 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/stringifier.js +374 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/stringify.js +11 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/symbols.js +5 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/terminal-highlight.js +70 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/tokenize.js +270 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/warn-once.js +13 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/lib/warning.js +37 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/postcss/package.json +88 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/LICENSE +28 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/README.md +765 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/array-set.js +121 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/base64-vlq.js +140 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/base64.js +67 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/binary-search.js +111 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/mapping-list.js +79 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/quick-sort.js +132 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/source-map-consumer.js +1188 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/source-map-generator.js +444 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/source-node.js +413 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/lib/util.js +594 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/package.json +71 -0
- package/dist/sandbox-actions/hyperframes-render/bundle/runtime-modules/source-map-js/source-map.js +8 -0
- package/dist/xpert-motion-assistant.yaml +240 -37
- package/package.json +17 -9
- package/skills/motion-agent-skill/SKILL.md +19 -7
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { __decorate, __metadata, __param } from "tslib";
|
|
2
|
-
import { BadRequestException, Inject, Injectable, NotFoundException, Optional } from '@nestjs/common';
|
|
2
|
+
import { BadRequestException, ConflictException, Inject, Injectable, NotFoundException, Optional, ServiceUnavailableException } from '@nestjs/common';
|
|
3
3
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
4
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
5
5
|
import { Repository } from 'typeorm';
|
|
6
|
-
import { XPERT_RUNTIME_CAPABILITIES_TOKEN } from '@xpert-ai/plugin-sdk';
|
|
6
|
+
import { MANAGED_QUEUE_SERVICE_TOKEN, SandboxJobsRuntimeCapability, SYSTEM_GLOBAL_SCOPE, WorkspaceFilesRuntimeCapability, XPERT_RUNTIME_CAPABILITIES_TOKEN } from '@xpert-ai/plugin-sdk';
|
|
7
|
+
import { MOTION_PLUGIN_NAME, MOTION_RENDER_JOB, MOTION_RENDER_QUEUE, MOTION_RENDER_SANDBOX_ACTION, MOTION_RENDER_SANDBOX_ACTION_VERSION } from './constants.js';
|
|
7
8
|
import { MotionActionLog, MotionExport, MotionProject, MotionProjectVersion, MotionStyle } from './entities/index.js';
|
|
8
|
-
import { MOTION_WORKSPACE_FILES_RUNTIME_CAPABILITY } from './types.js';
|
|
9
9
|
import { compactJson, normalizeJsonObject, normalizeStringArray, stableJson } from './json-utils.js';
|
|
10
10
|
import { exportTextArtifact, injectMotionRuntime, validateHtmlArtifact } from './html-motion.js';
|
|
11
|
-
import {
|
|
11
|
+
import { validateVideoComposition } from './video-composition.js';
|
|
12
|
+
import { createStarterHyperframesComposition, readHyperframesCompositionMetadata, validateHyperframesComposition } from './hyperframes-composition.js';
|
|
12
13
|
import { getMotionRecipeDetail, loadDesignSystemsCount, loadMotionRecipes, readJsonDataFile, readMotionSpec, searchMotionRecipes } from './recipe-library.js';
|
|
13
14
|
let MotionService = class MotionService {
|
|
14
|
-
constructor(projectRepository, versionRepository, styleRepository, exportRepository, logRepository, runtimeCapabilities) {
|
|
15
|
+
constructor(projectRepository, versionRepository, styleRepository, exportRepository, logRepository, runtimeCapabilities, managedQueue) {
|
|
15
16
|
this.projectRepository = projectRepository;
|
|
16
17
|
this.versionRepository = versionRepository;
|
|
17
18
|
this.styleRepository = styleRepository;
|
|
18
19
|
this.exportRepository = exportRepository;
|
|
19
20
|
this.logRepository = logRepository;
|
|
20
21
|
this.runtimeCapabilities = runtimeCapabilities;
|
|
22
|
+
this.managedQueue = managedQueue;
|
|
21
23
|
}
|
|
22
24
|
getLibraryStats() {
|
|
23
25
|
const htmlTemplates = readJsonDataFile('html-templates.json');
|
|
@@ -66,12 +68,21 @@ let MotionService = class MotionService {
|
|
|
66
68
|
async createProject(scope, input) {
|
|
67
69
|
const title = normalizeRequired(input.title, 'Motion project title is required.');
|
|
68
70
|
const surface = normalizeSurface(input.surface);
|
|
69
|
-
const html =
|
|
71
|
+
const html = surface === 'web'
|
|
72
|
+
? input.html ? injectMotionRuntime(input.html) : starterHtml(title, input.brief)
|
|
73
|
+
: null;
|
|
74
|
+
if (surface === 'video' && input.videoComposition && input.hyperframesHtml) {
|
|
75
|
+
throw new BadRequestException('Choose either a HyperFrames composition or a legacy Canvas composition.');
|
|
76
|
+
}
|
|
77
|
+
const videoEngine = surface === 'video'
|
|
78
|
+
? input.videoComposition ? 'legacy_canvas' : 'hyperframes'
|
|
79
|
+
: null;
|
|
70
80
|
const videoComposition = input.videoComposition !== undefined && input.videoComposition !== null
|
|
71
81
|
? validateVideoComposition(input.videoComposition)
|
|
72
|
-
:
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
: null;
|
|
83
|
+
const hyperframesHtml = surface === 'video' && videoEngine === 'hyperframes'
|
|
84
|
+
? validateHyperframesComposition(input.hyperframesHtml ?? createStarterHyperframesComposition(title, input.brief))
|
|
85
|
+
: null;
|
|
75
86
|
const project = await this.projectRepository.save(this.projectRepository.create({
|
|
76
87
|
...scopedCreate(scope),
|
|
77
88
|
createdById: scope.userId ?? null,
|
|
@@ -85,10 +96,12 @@ let MotionService = class MotionService {
|
|
|
85
96
|
motionProfile: normalizeOptional(input.motionProfile),
|
|
86
97
|
selectedRecipeIds: normalizeStringArray(input.selectedRecipeIds ?? []),
|
|
87
98
|
workingHtml: html,
|
|
99
|
+
videoEngine,
|
|
88
100
|
videoComposition,
|
|
101
|
+
hyperframesHtml,
|
|
89
102
|
currentVersionNumber: 0,
|
|
90
103
|
workingCopyRevision: 1,
|
|
91
|
-
artifactChecksum: checksumArtifact(surface, html, videoComposition),
|
|
104
|
+
artifactChecksum: checksumArtifact(surface, html, videoEngine, hyperframesHtml, videoComposition),
|
|
92
105
|
lastEditedById: scope.userId ?? null,
|
|
93
106
|
lastEditedAt: new Date()
|
|
94
107
|
}));
|
|
@@ -124,6 +137,8 @@ let MotionService = class MotionService {
|
|
|
124
137
|
item: compactProject(project),
|
|
125
138
|
workingCopy: {
|
|
126
139
|
html: project.workingHtml ?? null,
|
|
140
|
+
videoEngine: resolveProjectVideoEngine(project),
|
|
141
|
+
hyperframesHtml: project.hyperframesHtml ?? null,
|
|
127
142
|
videoComposition: project.videoComposition ?? null,
|
|
128
143
|
componentSelection: project.componentSelection ?? null,
|
|
129
144
|
layerSelection: project.layerSelection ?? null,
|
|
@@ -141,11 +156,13 @@ let MotionService = class MotionService {
|
|
|
141
156
|
const html = injectMotionRuntime(input.html);
|
|
142
157
|
project.surface = 'web';
|
|
143
158
|
project.workingHtml = html;
|
|
159
|
+
project.videoEngine = null;
|
|
160
|
+
project.hyperframesHtml = null;
|
|
144
161
|
project.videoComposition = null;
|
|
145
162
|
project.selectedRecipeIds = normalizeStringArray(input.selectedRecipeIds ?? project.selectedRecipeIds ?? []);
|
|
146
163
|
project.componentSelection = normalizeJsonObject(input.componentSelection);
|
|
147
164
|
project.workingCopyRevision = (project.workingCopyRevision ?? 0) + 1;
|
|
148
|
-
project.artifactChecksum = checksumArtifact('web', html, null);
|
|
165
|
+
project.artifactChecksum = checksumArtifact('web', html, null, null, null);
|
|
149
166
|
project.lastEditedById = scope.userId ?? null;
|
|
150
167
|
project.lastEditedAt = new Date();
|
|
151
168
|
const saved = await this.projectRepository.save(project);
|
|
@@ -167,11 +184,13 @@ let MotionService = class MotionService {
|
|
|
167
184
|
const composition = validateVideoComposition(input.composition);
|
|
168
185
|
project.surface = 'video';
|
|
169
186
|
project.workingHtml = null;
|
|
187
|
+
project.videoEngine = 'legacy_canvas';
|
|
188
|
+
project.hyperframesHtml = null;
|
|
170
189
|
project.videoComposition = composition;
|
|
171
190
|
project.selectedRecipeIds = normalizeStringArray(input.selectedRecipeIds ?? project.selectedRecipeIds ?? []);
|
|
172
191
|
project.layerSelection = normalizeJsonObject(input.layerSelection);
|
|
173
192
|
project.workingCopyRevision = (project.workingCopyRevision ?? 0) + 1;
|
|
174
|
-
project.artifactChecksum = checksumArtifact('video', null, composition);
|
|
193
|
+
project.artifactChecksum = checksumArtifact('video', null, 'legacy_canvas', null, composition);
|
|
175
194
|
project.lastEditedById = scope.userId ?? null;
|
|
176
195
|
project.lastEditedAt = new Date();
|
|
177
196
|
const saved = await this.projectRepository.save(project);
|
|
@@ -188,11 +207,38 @@ let MotionService = class MotionService {
|
|
|
188
207
|
project: compactProject(saved)
|
|
189
208
|
};
|
|
190
209
|
}
|
|
210
|
+
async saveHyperframesComposition(scope, input) {
|
|
211
|
+
const project = await this.requireProject(scope, input.projectId);
|
|
212
|
+
const html = validateHyperframesComposition(input.html);
|
|
213
|
+
project.surface = 'video';
|
|
214
|
+
project.workingHtml = null;
|
|
215
|
+
project.videoEngine = 'hyperframes';
|
|
216
|
+
project.hyperframesHtml = html;
|
|
217
|
+
project.videoComposition = null;
|
|
218
|
+
project.layerSelection = null;
|
|
219
|
+
project.selectedRecipeIds = normalizeStringArray(input.selectedRecipeIds ?? project.selectedRecipeIds ?? []);
|
|
220
|
+
project.workingCopyRevision = (project.workingCopyRevision ?? 0) + 1;
|
|
221
|
+
project.artifactChecksum = checksumArtifact('video', null, 'hyperframes', html, null);
|
|
222
|
+
project.lastEditedById = scope.userId ?? null;
|
|
223
|
+
project.lastEditedAt = new Date();
|
|
224
|
+
const saved = await this.projectRepository.save(project);
|
|
225
|
+
await this.writeLog(scope, {
|
|
226
|
+
motionProjectId: saved.id,
|
|
227
|
+
action: 'hyperframes_composition_saved',
|
|
228
|
+
actorType: scope.assistantId ? 'agent' : 'user',
|
|
229
|
+
message: input.changeSummary ?? 'HyperFrames composition was saved.',
|
|
230
|
+
snapshot: { selectedRecipeIds: saved.selectedRecipeIds, bytes: Buffer.byteLength(html), engine: 'hyperframes' }
|
|
231
|
+
});
|
|
232
|
+
return { success: true, message: 'HyperFrames composition was saved.', project: compactProject(saved) };
|
|
233
|
+
}
|
|
191
234
|
async finalizeVersion(scope, input) {
|
|
192
235
|
const project = await this.requireProject(scope, input.projectId);
|
|
193
236
|
if (project.surface === 'web') {
|
|
194
237
|
validateHtmlArtifact(project.workingHtml ?? '');
|
|
195
238
|
}
|
|
239
|
+
else if (resolveProjectVideoEngine(project) === 'hyperframes') {
|
|
240
|
+
validateHyperframesComposition(project.hyperframesHtml ?? '');
|
|
241
|
+
}
|
|
196
242
|
else {
|
|
197
243
|
validateVideoComposition(project.videoComposition);
|
|
198
244
|
}
|
|
@@ -205,10 +251,12 @@ let MotionService = class MotionService {
|
|
|
205
251
|
sourceType: input.sourceType ?? inferVersionSource(scope, project.surface),
|
|
206
252
|
surface: project.surface ?? 'web',
|
|
207
253
|
html: project.workingHtml ?? null,
|
|
254
|
+
videoEngine: resolveProjectVideoEngine(project),
|
|
255
|
+
hyperframesHtml: project.hyperframesHtml ?? null,
|
|
208
256
|
videoComposition: project.videoComposition ?? null,
|
|
209
257
|
selectedRecipeIds: normalizeStringArray(project.selectedRecipeIds ?? []),
|
|
210
258
|
selectionSummary: project.surface === 'web' ? project.componentSelection ?? null : project.layerSelection ?? null,
|
|
211
|
-
artifactChecksum: project.artifactChecksum ?? checksumArtifact(project.surface ?? 'web', project.workingHtml, project.videoComposition),
|
|
259
|
+
artifactChecksum: project.artifactChecksum ?? checksumArtifact(project.surface ?? 'web', project.workingHtml, resolveProjectVideoEngine(project), project.hyperframesHtml, project.videoComposition),
|
|
212
260
|
changeSummary: normalizeOptional(input.changeSummary) ?? `Motion version ${versionNumber}`,
|
|
213
261
|
workspaceCatalog: project.workspaceCatalog,
|
|
214
262
|
workspaceScopeId: project.workspaceScopeId,
|
|
@@ -246,6 +294,8 @@ let MotionService = class MotionService {
|
|
|
246
294
|
}
|
|
247
295
|
project.surface = version.surface ?? project.surface;
|
|
248
296
|
project.workingHtml = version.html ?? null;
|
|
297
|
+
project.videoEngine = resolveVersionVideoEngine(version);
|
|
298
|
+
project.hyperframesHtml = version.hyperframesHtml ?? null;
|
|
249
299
|
project.videoComposition = version.videoComposition ?? null;
|
|
250
300
|
project.selectedRecipeIds = normalizeStringArray(version.selectedRecipeIds ?? []);
|
|
251
301
|
project.currentVersionId = version.id;
|
|
@@ -270,10 +320,242 @@ let MotionService = class MotionService {
|
|
|
270
320
|
version: compactVersion(version)
|
|
271
321
|
};
|
|
272
322
|
}
|
|
323
|
+
async getProductionRenderCapability() {
|
|
324
|
+
const sandbox = this.sandboxJobs(false);
|
|
325
|
+
if (!sandbox)
|
|
326
|
+
return unavailableRenderCapability('PROVIDER_UNAVAILABLE', 'Platform Sandbox Jobs capability is unavailable.');
|
|
327
|
+
const action = await sandbox.getActionHealth({
|
|
328
|
+
pluginName: MOTION_PLUGIN_NAME,
|
|
329
|
+
action: MOTION_RENDER_SANDBOX_ACTION,
|
|
330
|
+
actionVersion: MOTION_RENDER_SANDBOX_ACTION_VERSION
|
|
331
|
+
}).catch((error) => ({
|
|
332
|
+
available: false,
|
|
333
|
+
reason: 'PROFILE_UNHEALTHY',
|
|
334
|
+
message: errorMessage(error)
|
|
335
|
+
}));
|
|
336
|
+
if (!action.available) {
|
|
337
|
+
return unavailableRenderCapability(action.reason ?? 'PROFILE_UNHEALTHY', action.message ?? 'Motion render Action is unavailable.');
|
|
338
|
+
}
|
|
339
|
+
if (!this.managedQueue)
|
|
340
|
+
return unavailableRenderCapability('WORKER_UNAVAILABLE', 'Managed Queue is unavailable.');
|
|
341
|
+
const pool = await this.managedQueue.getExecutionPoolHealth({ executionPool: 'sandbox-browser' }).catch((error) => ({
|
|
342
|
+
executionPool: 'sandbox-browser',
|
|
343
|
+
available: false,
|
|
344
|
+
workerCount: 0,
|
|
345
|
+
warning: errorMessage(error)
|
|
346
|
+
}));
|
|
347
|
+
return pool.available
|
|
348
|
+
? {
|
|
349
|
+
available: true,
|
|
350
|
+
backend: 'sandbox-job',
|
|
351
|
+
action: MOTION_RENDER_SANDBOX_ACTION,
|
|
352
|
+
actionVersion: MOTION_RENDER_SANDBOX_ACTION_VERSION,
|
|
353
|
+
runtimeProfile: action.runtimeProfile ?? null,
|
|
354
|
+
sandboxRuntimeVersion: action.sandboxRuntimeVersion ?? null,
|
|
355
|
+
workerCount: pool.workerCount
|
|
356
|
+
}
|
|
357
|
+
: unavailableRenderCapability('WORKER_UNAVAILABLE', pool.warning ?? 'No worker is consuming sandbox-browser.');
|
|
358
|
+
}
|
|
359
|
+
async requestProductionRender(scope, input) {
|
|
360
|
+
const project = await this.requireProject(scope, input.projectId);
|
|
361
|
+
if (project.surface !== 'video' || resolveProjectVideoEngine(project) !== 'hyperframes') {
|
|
362
|
+
throw new BadRequestException('Production rendering requires a native HyperFrames video project.');
|
|
363
|
+
}
|
|
364
|
+
const hyperframesHtml = validateHyperframesComposition(project.hyperframesHtml ?? '');
|
|
365
|
+
const inputChecksum = project.artifactChecksum ?? checksumArtifact('video', null, 'hyperframes', hyperframesHtml, null);
|
|
366
|
+
if (input.expectedChecksum && input.expectedChecksum !== inputChecksum) {
|
|
367
|
+
throw new ConflictException('Motion composition changed; reload before starting production render.');
|
|
368
|
+
}
|
|
369
|
+
const capability = await this.getProductionRenderCapability();
|
|
370
|
+
if ('reason' in capability)
|
|
371
|
+
throw new ServiceUnavailableException(`${capability.reason}: ${capability.message}`);
|
|
372
|
+
if (!this.managedQueue)
|
|
373
|
+
throw new ServiceUnavailableException('Managed Queue is required for production render.');
|
|
374
|
+
const kind = normalizeRenderKind(input.kind);
|
|
375
|
+
const quality = normalizeRenderQuality(input.quality);
|
|
376
|
+
const fps = normalizeRenderFps(input.fps);
|
|
377
|
+
const metadata = readHyperframesCompositionMetadata(hyperframesHtml);
|
|
378
|
+
const record = await this.exportRepository.save(this.exportRepository.create({
|
|
379
|
+
...scopedCreate(scope),
|
|
380
|
+
projectId: scope.projectId ?? project.projectId ?? null,
|
|
381
|
+
motionProjectId: requireId(project.id),
|
|
382
|
+
versionId: project.currentVersionId ?? null,
|
|
383
|
+
kind,
|
|
384
|
+
status: 'queued',
|
|
385
|
+
backend: 'hyperframes',
|
|
386
|
+
progress: 0,
|
|
387
|
+
stage: 'queued',
|
|
388
|
+
inputChecksum,
|
|
389
|
+
mimeType: mimeTypeForKind(kind),
|
|
390
|
+
changeSummary: normalizeOptional(input.changeSummary),
|
|
391
|
+
report: { quality, fps, fileName: sanitizeFileName(input.fileName ?? `${project.title}.${kind}`), ...metadata },
|
|
392
|
+
createdById: scope.userId ?? null,
|
|
393
|
+
assistantId: scope.assistantId ?? null,
|
|
394
|
+
conversationId: scope.conversationId ?? null
|
|
395
|
+
}));
|
|
396
|
+
const exportId = requireId(record.id);
|
|
397
|
+
const queued = await this.managedQueue.enqueue({
|
|
398
|
+
pluginName: MOTION_PLUGIN_NAME,
|
|
399
|
+
queueName: MOTION_RENDER_QUEUE,
|
|
400
|
+
jobName: MOTION_RENDER_JOB,
|
|
401
|
+
payload: { exportId, tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },
|
|
402
|
+
tenantId: scope.tenantId,
|
|
403
|
+
organizationId: scope.organizationId,
|
|
404
|
+
scopeKey: SYSTEM_GLOBAL_SCOPE,
|
|
405
|
+
userId: scope.userId,
|
|
406
|
+
jobId: `motion-render-${exportId}`,
|
|
407
|
+
attempts: 1,
|
|
408
|
+
executionPool: 'sandbox-browser',
|
|
409
|
+
removeOnComplete: { age: 24 * 60 * 60, count: 100 },
|
|
410
|
+
removeOnFail: { age: 7 * 24 * 60 * 60, count: 100 }
|
|
411
|
+
}).catch(async (error) => {
|
|
412
|
+
record.status = 'failed';
|
|
413
|
+
record.stage = 'queueing';
|
|
414
|
+
record.errorMessage = errorMessage(error);
|
|
415
|
+
record.completedAt = new Date();
|
|
416
|
+
await this.exportRepository.save(record);
|
|
417
|
+
throw error;
|
|
418
|
+
});
|
|
419
|
+
record.jobId = queued.jobId;
|
|
420
|
+
await this.exportRepository.save(record);
|
|
421
|
+
await this.writeLog(scope, {
|
|
422
|
+
motionProjectId: project.id,
|
|
423
|
+
versionId: project.currentVersionId,
|
|
424
|
+
action: 'production_render_queued',
|
|
425
|
+
actorType: scope.assistantId ? 'agent' : 'user',
|
|
426
|
+
message: input.changeSummary ?? `Queued ${kind.toUpperCase()} production render.`,
|
|
427
|
+
snapshot: { exportId, jobId: queued.jobId, kind, quality, fps, engine: 'hyperframes', inputChecksum }
|
|
428
|
+
});
|
|
429
|
+
return { success: true, message: 'HyperFrames production render was queued.', export: compactExport(record), capability };
|
|
430
|
+
}
|
|
431
|
+
async processProductionRender(job) {
|
|
432
|
+
const data = job.data;
|
|
433
|
+
const scope = { tenantId: data.tenantId, organizationId: data.organizationId ?? null };
|
|
434
|
+
const record = await this.exportRepository.findOne({ where: scopedWhere(scope, { id: data.exportId }) });
|
|
435
|
+
if (!record || record.status === 'cancelled' || record.status === 'succeeded')
|
|
436
|
+
return;
|
|
437
|
+
const project = await this.requireProject(scope, record.motionProjectId);
|
|
438
|
+
if (resolveProjectVideoEngine(project) !== 'hyperframes')
|
|
439
|
+
throw new BadRequestException('Motion render source is not HyperFrames.');
|
|
440
|
+
const html = validateHyperframesComposition(project.hyperframesHtml ?? '');
|
|
441
|
+
const checksum = checksumArtifact('video', null, 'hyperframes', html, null);
|
|
442
|
+
if (checksum !== record.inputChecksum)
|
|
443
|
+
throw new ConflictException('Motion render source checksum changed after queueing.');
|
|
444
|
+
const settings = renderSettings(record.report);
|
|
445
|
+
const outputPath = `motion.${record.kind}`;
|
|
446
|
+
record.status = 'running';
|
|
447
|
+
record.stage = 'sandbox-starting';
|
|
448
|
+
record.progress = 10;
|
|
449
|
+
record.sandboxJobId = requireId(record.id);
|
|
450
|
+
record.errorMessage = null;
|
|
451
|
+
await this.exportRepository.save(record);
|
|
452
|
+
try {
|
|
453
|
+
const result = await this.sandboxJobs().run({
|
|
454
|
+
jobId: requireId(record.id),
|
|
455
|
+
action: MOTION_RENDER_SANDBOX_ACTION,
|
|
456
|
+
actionVersion: MOTION_RENDER_SANDBOX_ACTION_VERSION,
|
|
457
|
+
idempotencyKey: `motion-hyperframes:${record.id}:${checksum}`,
|
|
458
|
+
scope: {
|
|
459
|
+
tenantId: scope.tenantId,
|
|
460
|
+
organizationId: scope.organizationId,
|
|
461
|
+
userId: record.createdById ?? null,
|
|
462
|
+
pluginName: MOTION_PLUGIN_NAME,
|
|
463
|
+
businessResourceType: 'motion-render',
|
|
464
|
+
businessResourceId: requireId(record.id)
|
|
465
|
+
},
|
|
466
|
+
payload: {
|
|
467
|
+
compositionHtml: html,
|
|
468
|
+
kind: record.kind,
|
|
469
|
+
quality: settings.quality,
|
|
470
|
+
fps: settings.fps
|
|
471
|
+
},
|
|
472
|
+
files: [],
|
|
473
|
+
outputs: [{
|
|
474
|
+
path: outputPath,
|
|
475
|
+
originalName: settings.fileName,
|
|
476
|
+
mimeType: mimeTypeForKind(record.kind),
|
|
477
|
+
destination: {
|
|
478
|
+
...resolveProjectWorkspaceScope({ ...scope, userId: record.createdById }, project),
|
|
479
|
+
folder: buildMotionExportFolder(requireId(project.id))
|
|
480
|
+
}
|
|
481
|
+
}, {
|
|
482
|
+
path: 'report.json',
|
|
483
|
+
originalName: `${settings.fileName}.report.json`,
|
|
484
|
+
mimeType: 'application/json',
|
|
485
|
+
destination: {
|
|
486
|
+
...resolveProjectWorkspaceScope({ ...scope, userId: record.createdById }, project),
|
|
487
|
+
folder: `${buildMotionExportFolder(requireId(project.id))}/reports`
|
|
488
|
+
}
|
|
489
|
+
}],
|
|
490
|
+
timeoutMs: 20 * 60_000
|
|
491
|
+
});
|
|
492
|
+
const output = result.outputs.find((candidate) => candidate.path === outputPath);
|
|
493
|
+
if (!output)
|
|
494
|
+
throw new Error(`HyperFrames render did not return ${outputPath}.`);
|
|
495
|
+
record.status = 'succeeded';
|
|
496
|
+
record.stage = 'complete';
|
|
497
|
+
record.progress = 100;
|
|
498
|
+
record.sandboxJobId = result.id;
|
|
499
|
+
record.filePath = output.workspacePath ?? null;
|
|
500
|
+
record.fileReference = output.reference;
|
|
501
|
+
record.fileUrl = output.fileUrl ?? null;
|
|
502
|
+
record.mimeType = output.mimeType;
|
|
503
|
+
record.size = output.size;
|
|
504
|
+
record.checksum = output.sha256;
|
|
505
|
+
record.errorMessage = null;
|
|
506
|
+
record.completedAt = new Date();
|
|
507
|
+
record.report = {
|
|
508
|
+
...settings,
|
|
509
|
+
renderer: '@hyperframes/producer',
|
|
510
|
+
action: result.action,
|
|
511
|
+
actionVersion: result.actionVersion,
|
|
512
|
+
runtimeProfile: result.runtimeProfile,
|
|
513
|
+
sandboxRuntimeVersion: result.sandboxRuntimeVersion,
|
|
514
|
+
sandboxJobId: result.id,
|
|
515
|
+
attempt: result.attempt
|
|
516
|
+
};
|
|
517
|
+
await this.exportRepository.save(record);
|
|
518
|
+
project.lastExportKind = record.kind;
|
|
519
|
+
project.lastExportPath = output.workspacePath ?? output.fileUrl ?? project.lastExportPath;
|
|
520
|
+
await this.projectRepository.save(project);
|
|
521
|
+
await this.writeLog(scope, {
|
|
522
|
+
motionProjectId: project.id,
|
|
523
|
+
versionId: record.versionId,
|
|
524
|
+
action: 'production_render_completed',
|
|
525
|
+
message: `Completed ${record.kind.toUpperCase()} production render.`,
|
|
526
|
+
snapshot: { exportId: record.id, sandboxJobId: result.id, size: output.size, checksum: output.sha256 }
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
record.status = 'failed';
|
|
531
|
+
record.stage = 'failed';
|
|
532
|
+
record.errorMessage = errorMessage(error);
|
|
533
|
+
record.completedAt = new Date();
|
|
534
|
+
await this.exportRepository.save(record);
|
|
535
|
+
await this.writeLog(scope, {
|
|
536
|
+
motionProjectId: project.id,
|
|
537
|
+
versionId: record.versionId,
|
|
538
|
+
action: 'production_render_failed',
|
|
539
|
+
message: 'HyperFrames production render failed.',
|
|
540
|
+
errorMessage: record.errorMessage,
|
|
541
|
+
snapshot: { exportId: record.id, inputChecksum: record.inputChecksum }
|
|
542
|
+
});
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
273
546
|
async exportArtifact(scope, input) {
|
|
274
547
|
const project = await this.requireProject(scope, input.projectId);
|
|
275
548
|
const kind = normalizeExportKind(input.kind);
|
|
276
549
|
if (kind === 'mp4' || kind === 'gif') {
|
|
550
|
+
if (project.surface === 'video' && resolveProjectVideoEngine(project) === 'hyperframes') {
|
|
551
|
+
return this.requestProductionRender(scope, {
|
|
552
|
+
projectId: input.projectId,
|
|
553
|
+
kind,
|
|
554
|
+
fileName: input.fileName,
|
|
555
|
+
expectedChecksum: project.artifactChecksum,
|
|
556
|
+
changeSummary: input.changeSummary
|
|
557
|
+
});
|
|
558
|
+
}
|
|
277
559
|
return {
|
|
278
560
|
success: true,
|
|
279
561
|
message: 'Use the Motion Workbench browser exporter for MP4/GIF, then save the generated file through save_export_file.',
|
|
@@ -332,7 +614,7 @@ let MotionService = class MotionService {
|
|
|
332
614
|
}
|
|
333
615
|
async saveMediaFile(scope, input) {
|
|
334
616
|
const project = await this.requireProject(scope, input.projectId);
|
|
335
|
-
const workspaceFiles = this.runtimeCapabilities?.get(
|
|
617
|
+
const workspaceFiles = this.runtimeCapabilities?.get(WorkspaceFilesRuntimeCapability);
|
|
336
618
|
if (!workspaceFiles) {
|
|
337
619
|
throw new BadRequestException('Motion media upload requires platform workspace file storage.');
|
|
338
620
|
}
|
|
@@ -509,9 +791,15 @@ let MotionService = class MotionService {
|
|
|
509
791
|
}
|
|
510
792
|
return project;
|
|
511
793
|
}
|
|
794
|
+
sandboxJobs(required = true) {
|
|
795
|
+
const jobs = this.runtimeCapabilities?.get(SandboxJobsRuntimeCapability);
|
|
796
|
+
if (!jobs && required)
|
|
797
|
+
throw new ServiceUnavailableException('Platform Sandbox Jobs capability is unavailable.');
|
|
798
|
+
return jobs;
|
|
799
|
+
}
|
|
512
800
|
async saveExportBuffer(scope, input) {
|
|
513
801
|
const checksum = sha256(input.buffer);
|
|
514
|
-
const workspaceFiles = this.runtimeCapabilities?.get(
|
|
802
|
+
const workspaceFiles = this.runtimeCapabilities?.get(WorkspaceFilesRuntimeCapability);
|
|
515
803
|
let workspaceFile = null;
|
|
516
804
|
if (workspaceFiles) {
|
|
517
805
|
workspaceFile = await workspaceFiles.uploadBuffer({
|
|
@@ -539,6 +827,10 @@ let MotionService = class MotionService {
|
|
|
539
827
|
motionProjectId: requireId(input.project.id),
|
|
540
828
|
versionId: input.versionId ?? null,
|
|
541
829
|
kind: input.kind,
|
|
830
|
+
status: 'succeeded',
|
|
831
|
+
backend: 'browser',
|
|
832
|
+
progress: 100,
|
|
833
|
+
stage: 'complete',
|
|
542
834
|
filePath: workspaceFile?.filePath ?? null,
|
|
543
835
|
fileUrl: workspaceFile?.fileUrl ?? workspaceFile?.url ?? null,
|
|
544
836
|
mimeType: input.mimeType,
|
|
@@ -549,7 +841,8 @@ let MotionService = class MotionService {
|
|
|
549
841
|
changeSummary: normalizeOptional(input.changeSummary),
|
|
550
842
|
createdById: scope.userId ?? null,
|
|
551
843
|
assistantId: scope.assistantId ?? null,
|
|
552
|
-
conversationId: scope.conversationId ?? null
|
|
844
|
+
conversationId: scope.conversationId ?? null,
|
|
845
|
+
completedAt: new Date()
|
|
553
846
|
}));
|
|
554
847
|
input.project.lastExportKind = input.kind;
|
|
555
848
|
input.project.lastExportPath = workspaceFile?.filePath ?? input.project.lastExportPath;
|
|
@@ -588,11 +881,13 @@ MotionService = __decorate([
|
|
|
588
881
|
__param(4, InjectRepository(MotionActionLog)),
|
|
589
882
|
__param(5, Optional()),
|
|
590
883
|
__param(5, Inject(XPERT_RUNTIME_CAPABILITIES_TOKEN)),
|
|
884
|
+
__param(6, Optional()),
|
|
885
|
+
__param(6, Inject(MANAGED_QUEUE_SERVICE_TOKEN)),
|
|
591
886
|
__metadata("design:paramtypes", [Repository,
|
|
592
887
|
Repository,
|
|
593
888
|
Repository,
|
|
594
889
|
Repository,
|
|
595
|
-
Repository, Object])
|
|
890
|
+
Repository, Object, Object])
|
|
596
891
|
], MotionService);
|
|
597
892
|
export { MotionService };
|
|
598
893
|
function scopedCreate(scope) {
|
|
@@ -619,6 +914,7 @@ function compactProject(project) {
|
|
|
619
914
|
title: project.title,
|
|
620
915
|
brief: project.brief ?? null,
|
|
621
916
|
surface: project.surface ?? 'web',
|
|
917
|
+
videoEngine: resolveProjectVideoEngine(project),
|
|
622
918
|
status: project.status ?? 'draft',
|
|
623
919
|
designSystemId: project.designSystemId ?? null,
|
|
624
920
|
motionProfile: project.motionProfile ?? null,
|
|
@@ -640,6 +936,7 @@ function compactVersion(version) {
|
|
|
640
936
|
versionNumber: version.versionNumber,
|
|
641
937
|
sourceType: version.sourceType ?? 'workbench',
|
|
642
938
|
surface: version.surface ?? 'web',
|
|
939
|
+
videoEngine: resolveVersionVideoEngine(version),
|
|
643
940
|
selectedRecipeIds: version.selectedRecipeIds ?? [],
|
|
644
941
|
artifactChecksum: version.artifactChecksum ?? null,
|
|
645
942
|
changeSummary: version.changeSummary ?? null,
|
|
@@ -651,11 +948,21 @@ function compactExport(record) {
|
|
|
651
948
|
id: record.id,
|
|
652
949
|
versionId: record.versionId ?? null,
|
|
653
950
|
kind: record.kind,
|
|
951
|
+
status: record.status ?? 'succeeded',
|
|
952
|
+
backend: record.backend ?? 'browser',
|
|
953
|
+
progress: record.progress ?? 100,
|
|
954
|
+
stage: record.stage ?? null,
|
|
955
|
+
jobId: record.jobId ?? null,
|
|
956
|
+
sandboxJobId: record.sandboxJobId ?? null,
|
|
957
|
+
inputChecksum: record.inputChecksum ?? null,
|
|
654
958
|
filePath: record.filePath ?? null,
|
|
959
|
+
fileReference: record.fileReference ?? null,
|
|
655
960
|
fileUrl: record.fileUrl ?? null,
|
|
656
961
|
mimeType: record.mimeType ?? null,
|
|
657
962
|
size: record.size ?? null,
|
|
658
963
|
checksum: record.checksum ?? null,
|
|
964
|
+
errorMessage: record.errorMessage ?? null,
|
|
965
|
+
report: record.report ?? null,
|
|
659
966
|
changeSummary: record.changeSummary ?? null,
|
|
660
967
|
createdAt: record.createdAt?.toISOString?.() ?? null
|
|
661
968
|
};
|
|
@@ -709,8 +1016,58 @@ function normalizeExportKind(kind) {
|
|
|
709
1016
|
}
|
|
710
1017
|
throw new BadRequestException('Unsupported Motion export kind.');
|
|
711
1018
|
}
|
|
712
|
-
function checksumArtifact(surface, html, composition) {
|
|
713
|
-
|
|
1019
|
+
function checksumArtifact(surface, html, videoEngine, hyperframesHtml, composition) {
|
|
1020
|
+
const source = surface === 'web'
|
|
1021
|
+
? `web\n${html ?? ''}`
|
|
1022
|
+
: videoEngine === 'hyperframes'
|
|
1023
|
+
? `video\nhyperframes\n${hyperframesHtml ?? ''}`
|
|
1024
|
+
: `video\nlegacy_canvas\n${stableJson(composition)}`;
|
|
1025
|
+
return sha256(Buffer.from(source, 'utf8'));
|
|
1026
|
+
}
|
|
1027
|
+
function resolveProjectVideoEngine(project) {
|
|
1028
|
+
if (project.surface !== 'video')
|
|
1029
|
+
return null;
|
|
1030
|
+
// All video rows written before the discriminator existed used Canvas/WebCodecs.
|
|
1031
|
+
return project.videoEngine === 'hyperframes' ? 'hyperframes' : 'legacy_canvas';
|
|
1032
|
+
}
|
|
1033
|
+
function resolveVersionVideoEngine(version) {
|
|
1034
|
+
if (version.surface !== 'video')
|
|
1035
|
+
return null;
|
|
1036
|
+
return version.videoEngine === 'hyperframes' ? 'hyperframes' : 'legacy_canvas';
|
|
1037
|
+
}
|
|
1038
|
+
function normalizeRenderKind(kind) {
|
|
1039
|
+
if (!kind || kind === 'mp4')
|
|
1040
|
+
return 'mp4';
|
|
1041
|
+
if (kind === 'gif')
|
|
1042
|
+
return 'gif';
|
|
1043
|
+
throw new BadRequestException('Production render kind must be MP4 or GIF.');
|
|
1044
|
+
}
|
|
1045
|
+
function normalizeRenderQuality(quality) {
|
|
1046
|
+
if (!quality || quality === 'standard')
|
|
1047
|
+
return 'standard';
|
|
1048
|
+
if (quality === 'draft' || quality === 'high')
|
|
1049
|
+
return quality;
|
|
1050
|
+
throw new BadRequestException('Unsupported HyperFrames render quality.');
|
|
1051
|
+
}
|
|
1052
|
+
function normalizeRenderFps(fps) {
|
|
1053
|
+
if (fps === undefined || fps === 30)
|
|
1054
|
+
return 30;
|
|
1055
|
+
if (fps === 24 || fps === 60)
|
|
1056
|
+
return fps;
|
|
1057
|
+
throw new BadRequestException('HyperFrames render fps must be 24, 30, or 60.');
|
|
1058
|
+
}
|
|
1059
|
+
function unavailableRenderCapability(reason, message) {
|
|
1060
|
+
return { available: false, backend: 'sandbox-job', reason, message };
|
|
1061
|
+
}
|
|
1062
|
+
function renderSettings(report) {
|
|
1063
|
+
return {
|
|
1064
|
+
quality: normalizeRenderQuality(typeof report?.quality === 'string' ? report.quality : undefined),
|
|
1065
|
+
fps: normalizeRenderFps(typeof report?.fps === 'number' ? report.fps : undefined),
|
|
1066
|
+
fileName: sanitizeFileName(typeof report?.fileName === 'string' ? report.fileName : 'motion.mp4')
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function errorMessage(error) {
|
|
1070
|
+
return error instanceof Error && error.message ? error.message : String(error);
|
|
714
1071
|
}
|
|
715
1072
|
function sha256(buffer) {
|
|
716
1073
|
return createHash('sha256').update(buffer).digest('hex');
|