vidspotai-shared 1.0.94 → 1.0.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/lib/globals/aiModels/enums.d.ts +2 -0
  2. package/lib/globals/aiModels/enums.d.ts.map +1 -1
  3. package/lib/globals/aiModels/enums.js +5 -0
  4. package/lib/globals/aiModels/index.d.ts.map +1 -1
  5. package/lib/globals/aiModels/index.js +2 -0
  6. package/lib/globals/aiModels/providers/hostedAvatars.d.ts +22 -0
  7. package/lib/globals/aiModels/providers/hostedAvatars.d.ts.map +1 -0
  8. package/lib/globals/aiModels/providers/hostedAvatars.js +129 -0
  9. package/lib/globals/types.d.ts +58 -1
  10. package/lib/globals/types.d.ts.map +1 -1
  11. package/lib/globals/types.js +61 -1
  12. package/lib/models/demoVideoProject.model.d.ts +244 -0
  13. package/lib/models/demoVideoProject.model.d.ts.map +1 -0
  14. package/lib/models/demoVideoProject.model.js +2 -0
  15. package/lib/models/githubConnection.model.d.ts +35 -0
  16. package/lib/models/githubConnection.model.d.ts.map +1 -0
  17. package/lib/models/githubConnection.model.js +2 -0
  18. package/lib/models/index.d.ts +3 -0
  19. package/lib/models/index.d.ts.map +1 -1
  20. package/lib/models/index.js +3 -0
  21. package/lib/models/outreach.model.d.ts +175 -0
  22. package/lib/models/outreach.model.d.ts.map +1 -0
  23. package/lib/models/outreach.model.js +2 -0
  24. package/lib/models/video.model.d.ts +4 -0
  25. package/lib/models/video.model.d.ts.map +1 -1
  26. package/lib/schemas/brandKit.schema.d.ts +4 -0
  27. package/lib/schemas/brandKit.schema.d.ts.map +1 -1
  28. package/lib/schemas/brandKit.schema.js +10 -0
  29. package/lib/schemas/demoQuestion.schema.d.ts +286 -0
  30. package/lib/schemas/demoQuestion.schema.d.ts.map +1 -0
  31. package/lib/schemas/demoQuestion.schema.js +210 -0
  32. package/lib/schemas/demoSpec.schema.d.ts +109 -6
  33. package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
  34. package/lib/schemas/demoSpec.schema.js +95 -7
  35. package/lib/schemas/index.d.ts +1 -0
  36. package/lib/schemas/index.d.ts.map +1 -1
  37. package/lib/schemas/index.js +1 -0
  38. package/lib/schemas/videoPlan.schema.d.ts +2 -2
  39. package/lib/schemas/videoPlan.schema.d.ts.map +1 -1
  40. package/lib/schemas/videoPlan.schema.js +11 -1
  41. package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
  42. package/lib/services/agent/providerFallback/chains.js +9 -1
  43. package/lib/services/agent/taskPoller.d.ts.map +1 -1
  44. package/lib/services/agent/taskPoller.js +17 -6
  45. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +21 -9
  46. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts.map +1 -1
  47. package/lib/services/agent/tools/generateAvatarVideo.tool.js +135 -39
  48. package/lib/services/agent/tools/planVideo.tool.d.ts +2 -2
  49. package/lib/services/aiGen/aiGenFactory.service.d.ts +1 -1
  50. package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
  51. package/lib/services/aiGen/aiGenFactory.service.js +10 -2
  52. package/lib/services/aiGen/helpers.d.ts.map +1 -1
  53. package/lib/services/aiGen/helpers.js +12 -2
  54. package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
  55. package/lib/services/aiGen/providers/kling/kling.service.js +47 -8
  56. package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts +8 -0
  57. package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
  58. package/lib/services/aiGen/providers/pixverse/pixverse.service.js +55 -2
  59. package/lib/services/avatarGen/IAvatarVideoProvider.d.ts +136 -0
  60. package/lib/services/avatarGen/IAvatarVideoProvider.d.ts.map +1 -0
  61. package/lib/services/avatarGen/IAvatarVideoProvider.js +42 -0
  62. package/lib/services/avatarGen/avatarProviderFactory.d.ts +18 -0
  63. package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -0
  64. package/lib/services/avatarGen/avatarProviderFactory.js +54 -0
  65. package/lib/services/avatarGen/index.d.ts +6 -0
  66. package/lib/services/avatarGen/index.d.ts.map +1 -0
  67. package/lib/services/avatarGen/index.js +21 -0
  68. package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts +45 -0
  69. package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts.map +1 -0
  70. package/lib/services/avatarGen/providers/heygenAvatarProvider.js +190 -0
  71. package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts +32 -0
  72. package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts.map +1 -0
  73. package/lib/services/avatarGen/providers/klingAvatarProvider.js +69 -0
  74. package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts +56 -0
  75. package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts.map +1 -0
  76. package/lib/services/avatarGen/providers/tavusAvatarProvider.js +183 -0
  77. package/lib/services/demo/demoAuthor.d.ts +45 -0
  78. package/lib/services/demo/demoAuthor.d.ts.map +1 -0
  79. package/lib/services/demo/demoAuthor.js +75 -0
  80. package/lib/services/demo/demoAuthorNormalize.d.ts +49 -0
  81. package/lib/services/demo/demoAuthorNormalize.d.ts.map +1 -0
  82. package/lib/services/demo/demoAuthorNormalize.js +327 -0
  83. package/lib/services/demo/demoAuthorPrompt.d.ts +102 -0
  84. package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -0
  85. package/lib/services/demo/demoAuthorPrompt.js +243 -0
  86. package/lib/services/demo/demoCredits.d.ts +20 -0
  87. package/lib/services/demo/demoCredits.d.ts.map +1 -0
  88. package/lib/services/demo/demoCredits.js +51 -0
  89. package/lib/services/demo/demoMediaGuard.d.ts +43 -0
  90. package/lib/services/demo/demoMediaGuard.d.ts.map +1 -0
  91. package/lib/services/demo/demoMediaGuard.js +207 -0
  92. package/lib/services/demo/demoMusic.d.ts +13 -0
  93. package/lib/services/demo/demoMusic.d.ts.map +1 -0
  94. package/lib/services/demo/demoMusic.js +93 -0
  95. package/lib/services/demo/demoPrefill.d.ts +76 -0
  96. package/lib/services/demo/demoPrefill.d.ts.map +1 -0
  97. package/lib/services/demo/demoPrefill.js +435 -0
  98. package/lib/services/demo/demoRevisePrompt.d.ts +17 -0
  99. package/lib/services/demo/demoRevisePrompt.d.ts.map +1 -0
  100. package/lib/services/demo/demoRevisePrompt.js +85 -0
  101. package/lib/services/demo/demoReviser.d.ts +63 -0
  102. package/lib/services/demo/demoReviser.d.ts.map +1 -0
  103. package/lib/services/demo/demoReviser.js +81 -0
  104. package/lib/services/demo/demoScriptPrompt.d.ts +63 -0
  105. package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -0
  106. package/lib/services/demo/demoScriptPrompt.js +234 -0
  107. package/lib/services/demo/demoScriptWriter.d.ts +65 -0
  108. package/lib/services/demo/demoScriptWriter.d.ts.map +1 -0
  109. package/lib/services/demo/demoScriptWriter.js +211 -0
  110. package/lib/services/demo/demoTriage.d.ts +65 -0
  111. package/lib/services/demo/demoTriage.d.ts.map +1 -0
  112. package/lib/services/demo/demoTriage.js +130 -0
  113. package/lib/services/demo/githubOAuth.d.ts +78 -0
  114. package/lib/services/demo/githubOAuth.d.ts.map +1 -0
  115. package/lib/services/demo/githubOAuth.js +209 -0
  116. package/lib/services/demo/githubOAuthState.d.ts +10 -0
  117. package/lib/services/demo/githubOAuthState.d.ts.map +1 -0
  118. package/lib/services/demo/githubOAuthState.js +63 -0
  119. package/lib/services/demo/index.d.ts +24 -0
  120. package/lib/services/demo/index.d.ts.map +1 -0
  121. package/lib/services/demo/index.js +39 -0
  122. package/lib/services/firestore.service.d.ts +11 -0
  123. package/lib/services/firestore.service.d.ts.map +1 -1
  124. package/lib/services/firestore.service.js +24 -0
  125. package/lib/services/index.d.ts +2 -0
  126. package/lib/services/index.d.ts.map +1 -1
  127. package/lib/services/index.js +2 -0
  128. package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
  129. package/lib/services/tts/providers/elevenlabs.service.js +16 -8
  130. package/lib/utils/index.d.ts +1 -0
  131. package/lib/utils/index.d.ts.map +1 -1
  132. package/lib/utils/index.js +1 -0
  133. package/lib/utils/outreachTemplate.d.ts +46 -0
  134. package/lib/utils/outreachTemplate.d.ts.map +1 -0
  135. package/lib/utils/outreachTemplate.js +174 -0
  136. package/package.json +1 -1
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEMO_GEN_ASSET_MAX_VIDEOS = exports.DEMO_GEN_ASSET_MAX_IMAGES = void 0;
4
+ exports.isGenAssetRef = isGenAssetRef;
5
+ exports.genAssetIdFromRef = genAssetIdFromRef;
6
+ exports.collectGenAssetRefs = collectGenAssetRefs;
7
+ exports.normalizeDemoSpec = normalizeDemoSpec;
8
+ exports.specTotalMs = specTotalMs;
9
+ const demoSpec_schema_1 = require("../../schemas/demoSpec.schema");
10
+ const demoAuthorPrompt_1 = require("./demoAuthorPrompt");
11
+ // ─── 1. wizard pins ──────────────────────────────────────────────────────────
12
+ /** Overwrite style/narrative/aspect axes the user explicitly locked. */
13
+ function applyWizardPins(spec, answers, notes) {
14
+ if (!answers)
15
+ return spec;
16
+ const style = { ...spec.style };
17
+ let narrative = spec.narrative;
18
+ let aspect = spec.aspect;
19
+ if (answers.visualStyle && style.visual !== answers.visualStyle) {
20
+ notes.push(`visual: ${style.visual} → ${answers.visualStyle} (wizard pin)`);
21
+ style.visual = answers.visualStyle;
22
+ }
23
+ if (answers.pace && style.pace !== answers.pace) {
24
+ notes.push(`pace: ${style.pace} → ${answers.pace} (wizard pin)`);
25
+ style.pace = answers.pace;
26
+ }
27
+ if (answers.colorMood && style.colorMood !== answers.colorMood) {
28
+ notes.push(`colorMood: ${style.colorMood} → ${answers.colorMood} (wizard pin)`);
29
+ style.colorMood = answers.colorMood;
30
+ }
31
+ if (answers.audioIntent && style.audioIntent !== answers.audioIntent) {
32
+ notes.push(`audioIntent: ${style.audioIntent} → ${answers.audioIntent} (wizard pin)`);
33
+ style.audioIntent = answers.audioIntent;
34
+ }
35
+ if (answers.narrative && narrative !== answers.narrative) {
36
+ notes.push(`narrative: ${narrative} → ${answers.narrative} (wizard pin)`);
37
+ narrative = answers.narrative;
38
+ }
39
+ if (answers.aspect && aspect !== answers.aspect) {
40
+ notes.push(`aspect: ${aspect} → ${answers.aspect} (wizard pin)`);
41
+ aspect = answers.aspect;
42
+ }
43
+ return { ...spec, style, narrative, aspect };
44
+ }
45
+ // ─── 1.5 generated-asset requests (caps + reference accounting) ──────────────
46
+ /** Hard caps on planner-requested AI media per spec (cost + wall-clock bound). */
47
+ exports.DEMO_GEN_ASSET_MAX_IMAGES = 4;
48
+ exports.DEMO_GEN_ASSET_MAX_VIDEOS = 1;
49
+ /** Is this URL-slot value a reference to a declared generated asset? */
50
+ function isGenAssetRef(url) {
51
+ return !!url && url.startsWith(demoSpec_schema_1.GEN_ASSET_URI_PREFIX);
52
+ }
53
+ /** `gen://hero-shot` → `hero-shot` (undefined when not a gen ref). */
54
+ function genAssetIdFromRef(url) {
55
+ return url.slice(demoSpec_schema_1.GEN_ASSET_URI_PREFIX.length);
56
+ }
57
+ /** Every `gen://` ref AND every raw media string cited anywhere in a scene list
58
+ * (typed media slots + custom-scene prop bags, walked deeply so codegen props
59
+ * count as usage). */
60
+ function collectGenAssetRefs(scenes) {
61
+ const ids = new Set();
62
+ const urls = new Set();
63
+ const visit = (value, depth) => {
64
+ if (depth > 6 || value == null)
65
+ return;
66
+ if (typeof value === "string") {
67
+ if (isGenAssetRef(value))
68
+ ids.add(genAssetIdFromRef(value));
69
+ else if (value)
70
+ urls.add(value);
71
+ return;
72
+ }
73
+ if (Array.isArray(value)) {
74
+ for (const v of value)
75
+ visit(v, depth + 1);
76
+ return;
77
+ }
78
+ if (typeof value === "object") {
79
+ for (const v of Object.values(value)) {
80
+ visit(v, depth + 1);
81
+ }
82
+ }
83
+ };
84
+ for (const scene of scenes) {
85
+ switch (scene.type) {
86
+ case "deviceMockup":
87
+ scene.screenshotUrls.forEach((u) => visit(u, 0));
88
+ visit(scene.videoUrl, 0);
89
+ break;
90
+ case "featureShowcase":
91
+ case "splitFeature":
92
+ visit(scene.screenshotUrl, 0);
93
+ visit(scene.videoUrl, 0);
94
+ break;
95
+ case "mediaShot":
96
+ visit(scene.imageUrl, 0);
97
+ visit(scene.videoUrl, 0);
98
+ break;
99
+ case "custom":
100
+ visit(scene.props, 0);
101
+ break;
102
+ default:
103
+ break;
104
+ }
105
+ }
106
+ return { ids, urls };
107
+ }
108
+ /** Dedupe ids (first declaration wins) + enforce the per-kind caps. READY
109
+ * assets (already generated + paid for on a prior pass/revise) take cap
110
+ * priority over pending requests; within each group, declaration order wins —
111
+ * the planner is told to list by importance. Original order is preserved. */
112
+ function capGeneratedAssets(assets, notes) {
113
+ const seen = new Set();
114
+ const deduped = [];
115
+ for (const asset of assets) {
116
+ if (seen.has(asset.id)) {
117
+ notes.push(`generatedAssets — dropped duplicate id "${asset.id}"`);
118
+ continue;
119
+ }
120
+ seen.add(asset.id);
121
+ deduped.push(asset);
122
+ }
123
+ const kept = new Set();
124
+ const counts = { image: 0, video: 0 };
125
+ const caps = { image: exports.DEMO_GEN_ASSET_MAX_IMAGES, video: exports.DEMO_GEN_ASSET_MAX_VIDEOS };
126
+ const isReady = (a) => a.status === "ready" && !!a.url;
127
+ for (const asset of [...deduped.filter(isReady), ...deduped.filter((a) => !isReady(a))]) {
128
+ if (counts[asset.kind] >= caps[asset.kind]) {
129
+ notes.push(`generatedAssets — dropped ${asset.kind} "${asset.id}" (cap ${caps[asset.kind]})`);
130
+ continue;
131
+ }
132
+ counts[asset.kind]++;
133
+ kept.add(asset.id);
134
+ }
135
+ return deduped.filter((a) => kept.has(a.id));
136
+ }
137
+ function sanitizeScreenshots(scenes, allowed, notes) {
138
+ // Placement policy: deviceMockup + featureShowcase present media AS the
139
+ // product's real screen, so they accept REAL media only — a generated image
140
+ // there would fabricate product UI. splitFeature side visuals and mediaShot
141
+ // full-bleed moments may cite generated assets (that's what they're for).
142
+ const images = allowed.realImages;
143
+ const videos = allowed.realVideos;
144
+ const anyImages = new Set([...allowed.realImages, ...allowed.genImages]);
145
+ const anyVideos = new Set([...allowed.realVideos, ...allowed.genVideos]);
146
+ const isImage = (u) => !!u && images.has(u);
147
+ const isVideo = (u) => !!u && videos.has(u);
148
+ const isAnyImage = (u) => !!u && anyImages.has(u);
149
+ const isAnyVideo = (u) => !!u && anyVideos.has(u);
150
+ const out = [];
151
+ for (const scene of scenes) {
152
+ switch (scene.type) {
153
+ case "deviceMockup": {
154
+ const valid = scene.screenshotUrls.filter((u) => images.has(u));
155
+ const validVideo = isVideo(scene.videoUrl) ? scene.videoUrl : undefined;
156
+ if (valid.length === 0 && !validVideo) {
157
+ notes.push(`dropped deviceMockup "${scene.id}" — no valid media URLs`);
158
+ continue; // drop the scene entirely
159
+ }
160
+ if (valid.length !== scene.screenshotUrls.length) {
161
+ notes.push(`deviceMockup "${scene.id}" — removed ${scene.screenshotUrls.length - valid.length} invalid screenshot URL(s)`);
162
+ }
163
+ if (scene.videoUrl && !validVideo) {
164
+ notes.push(`deviceMockup "${scene.id}" — stripped invented videoUrl`);
165
+ }
166
+ out.push({
167
+ ...scene,
168
+ screenshotUrls: valid,
169
+ ...(validVideo ? { videoUrl: validVideo } : { videoUrl: undefined }),
170
+ });
171
+ break;
172
+ }
173
+ case "featureShowcase":
174
+ case "splitFeature": {
175
+ // splitFeature side visuals may cite generated assets; featureShowcase
176
+ // (media inside the fake-app surface = "the product's screen") may not.
177
+ const okImage = scene.type === "splitFeature" ? isAnyImage : isImage;
178
+ const okVideo = scene.type === "splitFeature" ? isAnyVideo : isVideo;
179
+ let next = scene;
180
+ if (scene.screenshotUrl && !okImage(scene.screenshotUrl)) {
181
+ notes.push(`${scene.type} "${scene.id}" — stripped invented screenshotUrl (falls back to coded surface)`);
182
+ const { screenshotUrl: _drop, ...rest } = next;
183
+ next = rest;
184
+ }
185
+ if (scene.videoUrl && !okVideo(scene.videoUrl)) {
186
+ notes.push(`${scene.type} "${scene.id}" — stripped invented videoUrl`);
187
+ const { videoUrl: _dropV, ...rest } = next;
188
+ next = rest;
189
+ }
190
+ out.push(next);
191
+ break;
192
+ }
193
+ case "mediaShot": {
194
+ // The full-bleed media home: real uploads AND generated assets both fine.
195
+ const validImage = isAnyImage(scene.imageUrl) ? scene.imageUrl : undefined;
196
+ const validVideo = isAnyVideo(scene.videoUrl) ? scene.videoUrl : undefined;
197
+ if (!validImage && !validVideo) {
198
+ notes.push(`dropped mediaShot "${scene.id}" — no valid media URLs`);
199
+ continue;
200
+ }
201
+ if (scene.imageUrl && !validImage) {
202
+ notes.push(`mediaShot "${scene.id}" — stripped invented imageUrl`);
203
+ }
204
+ if (scene.videoUrl && !validVideo) {
205
+ notes.push(`mediaShot "${scene.id}" — stripped invented videoUrl`);
206
+ }
207
+ out.push({ ...scene, imageUrl: validImage, videoUrl: validVideo });
208
+ break;
209
+ }
210
+ default:
211
+ out.push(scene);
212
+ }
213
+ }
214
+ return out;
215
+ }
216
+ // ─── 3. brand colors ─────────────────────────────────────────────────────────
217
+ /** Force the user's brand tokens back onto the spec (the model authors copy, not palette). */
218
+ function forceBrandColors(spec, input, notes) {
219
+ const given = input.brand.colors;
220
+ if (!given)
221
+ return spec;
222
+ const colors = { ...spec.brand.colors };
223
+ let changed = false;
224
+ if (given.primary && colors.primary !== given.primary) {
225
+ colors.primary = given.primary;
226
+ changed = true;
227
+ }
228
+ if (given.accent && colors.accent !== given.accent) {
229
+ colors.accent = given.accent;
230
+ changed = true;
231
+ }
232
+ if (given.palette?.length) {
233
+ colors.palette = given.palette;
234
+ }
235
+ if (changed)
236
+ notes.push(`brand colors forced to user tokens`);
237
+ return { ...spec, brand: { ...spec.brand, colors } };
238
+ }
239
+ // ─── 4. duration budget ──────────────────────────────────────────────────────
240
+ /**
241
+ * Scale every scene's duration by a single factor so the total lands inside the
242
+ * budget window. Proportional scaling preserves the author's intended pacing
243
+ * (a 4s hero beat stays ~2× a 2s cut). Only scales when the total is outside
244
+ * [min,max]; snaps to `target` when it does. Enforces a per-scene floor.
245
+ */
246
+ const MIN_SCENE_MS = 700;
247
+ function fitDuration(scenes, budget, notes) {
248
+ const total = scenes.reduce((acc, s) => acc + s.durationMs, 0);
249
+ if (total >= budget.min && total <= budget.max)
250
+ return scenes;
251
+ const factor = budget.target / Math.max(total, 1);
252
+ notes.push(`total duration ${Math.round(total / 1000)}s outside ${Math.round(budget.min / 1000)}–${Math.round(budget.max / 1000)}s → scaled ×${factor.toFixed(2)} to ~${Math.round(budget.target / 1000)}s`);
253
+ return scenes.map((s) => ({
254
+ ...s,
255
+ durationMs: Math.max(MIN_SCENE_MS, Math.round(s.durationMs * factor)),
256
+ }));
257
+ }
258
+ // ─── orchestration ───────────────────────────────────────────────────────────
259
+ /**
260
+ * Run the full deterministic normalize pipeline on a raw authored spec and
261
+ * return a schema-valid, render-ready DemoSpec plus the list of adjustments.
262
+ * Throws only if the model produced something with zero scenes (unrecoverable).
263
+ */
264
+ function normalizeDemoSpec(raw, input) {
265
+ const notes = [];
266
+ const budget = (0, demoAuthorPrompt_1.budgetForLength)(input.answers?.targetLength);
267
+ // productName is authoritative from the caller, never the model.
268
+ let spec = { ...raw, productName: input.productName };
269
+ spec = applyWizardPins(spec, input.answers, notes);
270
+ spec = forceBrandColors(spec, input, notes);
271
+ // Planner-requested AI media: dedupe + cap FIRST so refs to dropped requests
272
+ // are treated as invented URLs by the sanitizer below.
273
+ const generatedAssets = capGeneratedAssets(spec.generatedAssets ?? [], notes);
274
+ // Allowed media sets: images = uploaded/captured screenshots + image assets;
275
+ // videos = the user's video assets. Only these URLs may survive on scene props.
276
+ // `gen://<id>` refs are allowed iff they point at a surviving declared asset of
277
+ // the matching kind — the asset-generation stage resolves them to real URLs.
278
+ // Which slots accept gen refs is decided per scene type in sanitizeScreenshots.
279
+ const imageAssetUrls = (input.mediaAssets ?? [])
280
+ .filter((a) => a.kind === "image")
281
+ .map((a) => a.url);
282
+ // Ready assets' RESOLVED urls are also allowed (in the gen sets, so placement
283
+ // policy still holds): on revise, the incoming spec's scenes already carry the
284
+ // substituted GCS URLs, and stripping them as "invented" would throw away
285
+ // media we already paid for.
286
+ const readyGenUrls = (kind) => generatedAssets
287
+ .filter((a) => a.kind === kind && a.status === "ready" && !!a.url)
288
+ .map((a) => a.url);
289
+ const genRef = (a) => `${demoSpec_schema_1.GEN_ASSET_URI_PREFIX}${a.id}`;
290
+ const allowed = {
291
+ realImages: new Set([...(input.screenshotUrls ?? []), ...imageAssetUrls]),
292
+ realVideos: new Set((input.mediaAssets ?? []).filter((a) => a.kind === "video").map((a) => a.url)),
293
+ genImages: new Set([
294
+ ...generatedAssets.filter((a) => a.kind === "image").map(genRef),
295
+ ...readyGenUrls("image"),
296
+ ]),
297
+ genVideos: new Set([
298
+ ...generatedAssets.filter((a) => a.kind === "video").map(genRef),
299
+ ...readyGenUrls("video"),
300
+ ]),
301
+ };
302
+ let scenes = sanitizeScreenshots(spec.scenes, allowed, notes);
303
+ if (scenes.length === 0) {
304
+ throw new Error("normalizeDemoSpec: authored spec has no renderable scenes after media sanitation");
305
+ }
306
+ scenes = fitDuration(scenes, budget, notes);
307
+ // Drop asset requests nothing cites anymore (refs may have been stripped or
308
+ // their scene dropped) — an unreferenced request is pure provider spend. A
309
+ // ready asset cited by its RESOLVED url (post-materialization spec) counts.
310
+ const cited = collectGenAssetRefs(scenes);
311
+ const referencedAssets = generatedAssets.filter((a) => {
312
+ if (cited.ids.has(a.id))
313
+ return true;
314
+ if (a.status === "ready" && a.url && cited.urls.has(a.url))
315
+ return true;
316
+ notes.push(`generatedAssets — dropped unreferenced "${a.id}"`);
317
+ return false;
318
+ });
319
+ spec = { ...spec, scenes, generatedAssets: referencedAssets };
320
+ // Final schema pass applies all defaults + guarantees a valid contract.
321
+ const parsed = demoSpec_schema_1.DemoSpecSchema.parse(spec);
322
+ return { spec: parsed, adjustments: notes };
323
+ }
324
+ /** Convenience — total timeline of a normalized spec (ms). */
325
+ function specTotalMs(spec) {
326
+ return (0, demoSpec_schema_1.demoSpecDurationMs)(spec);
327
+ }
@@ -0,0 +1,102 @@
1
+ import { DemoBrand } from "../../schemas/demoSpec.schema";
2
+ import { IDemoClarifyAnswerValue, IDemoMediaAsset, IDemoVideoWizardAnswers } from "../../models/demoVideoProject.model";
3
+ /**
4
+ * Prompt construction for the SaaS Demo Video author (plan Phase 3 / §4 stage 2).
5
+ *
6
+ * The author is a single Claude `.structured<DemoSpec>()` call: it turns the
7
+ * user's product facts + brand tokens + product screenshots (as vision) + the
8
+ * clarifying-wizard answers into a valid, render-ready `DemoSpec` that the
9
+ * Layer-A scene-template engine renders to MP4 with zero manual effort.
10
+ *
11
+ * Kept separate from `demoAuthor.ts` because the system prompt is the largest,
12
+ * most-iterated surface — it encodes the scene vocabulary, the §2.6 style
13
+ * taxonomy, the anti-overfit rule, and the screenshot-vs-coded-chrome guidance.
14
+ * The prompt only DESCRIBES the contract; the zod schema is what actually
15
+ * constrains the output (passed as the tool `input_schema`), and a
16
+ * deterministic normalize pass (demoAuthor.ts) enforces the invariants a prompt
17
+ * can only request (wizard pins, real screenshot URLs, duration budget).
18
+ */
19
+ export interface DurationBudget {
20
+ min: number;
21
+ target: number;
22
+ max: number;
23
+ /** Rough scene-count guidance handed to the author for pacing. */
24
+ sceneHint: string;
25
+ }
26
+ export declare const DURATION_BUDGETS: Record<NonNullable<IDemoVideoWizardAnswers["targetLength"]>, DurationBudget>;
27
+ /** Default when the user left targetLength on "decide for me". */
28
+ export declare const DEFAULT_DURATION_BUDGET: DurationBudget;
29
+ export declare function budgetForLength(targetLength: IDemoVideoWizardAnswers["targetLength"] | undefined): DurationBudget;
30
+ /**
31
+ * Everything the author needs to write a DemoSpec. Product facts + resolved
32
+ * brand + the raw inputs (screenshots/website) + wizard answers. Brand colors
33
+ * are honored verbatim (never re-invented); copy + scene structure are authored.
34
+ */
35
+ export interface DemoAuthorInput {
36
+ /** Product display name — overlay text only, never AI-rendered into pixels. */
37
+ productName: string;
38
+ /** One-line value proposition, if the user gave one. */
39
+ tagline?: string;
40
+ /** Short product description / what it does. */
41
+ description?: string;
42
+ /** Feature labels to highlight (from the wizard "which features" multi-select). */
43
+ features?: string[];
44
+ /** Target-audience hint for script tone. */
45
+ audience?: string;
46
+ /** Resolved brand tokens (colors/fonts/identity). Colors are used as-is. */
47
+ brand: DemoBrand;
48
+ /** Real product screenshot URLs (GCS). Fed as vision AND the ONLY image URLs the author may cite. */
49
+ screenshotUrls?: string[];
50
+ /**
51
+ * Media the user wants FEATURED in the video (images + video clips). Unlike
52
+ * screenshots (reference the author MAY use), these SHOULD appear on the
53
+ * timeline. Video clips are placed via *.videoUrl scene props; image assets are
54
+ * merged into the screenshot set upstream. The author is told they exist + must
55
+ * surface them. See [[IDemoMediaAsset]].
56
+ */
57
+ mediaAssets?: IDemoMediaAsset[];
58
+ /** Product website, for context. The worker's gather stage fetches it (see `pageSummary`). */
59
+ websiteUrl?: string;
60
+ /**
61
+ * A compact, LLM-free summary of the product page's copy (title/description +
62
+ * visible-text lead), extracted by the worker during gather even when the user
63
+ * never clicked "Autofill". Steering context for the script — NOT rendered
64
+ * verbatim on screen. See [[gatherPageSignals]].
65
+ */
66
+ pageSummary?: string;
67
+ /** User's free-text notes about the repo/product — steering context, not rendered verbatim. */
68
+ repoNotes?: string;
69
+ /** Clarifying-wizard answers. Omitted fields === "decide for me" (author picks). */
70
+ answers?: IDemoVideoWizardAnswers;
71
+ /**
72
+ * Answers to the agent's DYNAMIC clarifying questions (triage/mid-run loop),
73
+ * keyed by question id. Authoritative steering context — the author should honor
74
+ * them. See [[DemoQuestion]].
75
+ */
76
+ clarifyAnswers?: Record<string, IDemoClarifyAnswerValue>;
77
+ }
78
+ /**
79
+ * The system prompt. Deliberately opinionated about quality (invented realistic
80
+ * domain data, cursor-synced walkthroughs, staggered reveals, on-brand color)
81
+ * and about the two hard reliability rules a prompt CAN enforce by instruction:
82
+ * never invent screenshot URLs, never render the product name as pixels.
83
+ */
84
+ export interface DemoAuthorPromptOpts {
85
+ /**
86
+ * Allow the planner to PROPOSE `custom` scenes (with an `intent` brief the
87
+ * Layer-B codegen agent later authors). Mirrors the worker's
88
+ * `DEMO_CODEGEN_ENABLED` gate — default false, so the prompt keeps the hard
89
+ * "no custom scenes" rule and behavior is unchanged until the flag is on.
90
+ */
91
+ allowCustomScenes?: boolean;
92
+ /**
93
+ * Allow the planner to COMMISSION AI-generated media (`generatedAssets` +
94
+ * `gen://` refs). Default true — the deterministic caps bound the cost; the
95
+ * worker passes false only under the DEMO_GEN_ASSETS_DISABLED kill-switch.
96
+ */
97
+ allowGeneratedAssets?: boolean;
98
+ }
99
+ export declare function buildDemoAuthorSystemPrompt(opts?: DemoAuthorPromptOpts): string;
100
+ /** Build the user-turn text (images are attached separately as imageUrls). */
101
+ export declare function buildDemoAuthorUserText(input: DemoAuthorInput): string;
102
+ //# sourceMappingURL=demoAuthorPrompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demoAuthorPrompt.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoAuthorPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,uBAAuB,EACxB,MAAM,qCAAqC,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AAMH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CACnC,WAAW,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC,EACpD,cAAc,CAQf,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,EAAE,cAA6C,CAAC;AAEpF,wBAAgB,eAAe,CAC7B,YAAY,EAAE,uBAAuB,CAAC,cAAc,CAAC,GAAG,SAAS,GAChE,cAAc,CAEhB;AAID;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,KAAK,EAAE,SAAS,CAAC;IACjB,qGAAqG;IACrG,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;CAC1D;AAwFD;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,2BAA2B,CAAC,IAAI,GAAE,oBAAyB,GAAG,MAAM,CA0CnF;AAmED,8EAA8E;AAC9E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CA+CtE"}