@vanillaskyai/video 0.5.8 → 0.7.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.
Files changed (135) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/PUBLIC-API.md +129 -9
  3. package/README.md +70 -108
  4. package/dist/{bg-confetti-SHQI7ATB.js → bg-confetti-HITCGLGD.js} +1 -1
  5. package/dist/{bg-emoji-XYFRA63Q.js → bg-emoji-VU4UZYZN.js} +1 -1
  6. package/dist/{bg-media-FEBATBT2.js → bg-media-L34PDQXJ.js} +4 -4
  7. package/dist/{brand-message-P56WEJPG.js → brand-message-FUJ4STFO.js} +3 -3
  8. package/dist/{builtin-server-YHEZ2JRF.js → builtin-server-KB7FKF6A.js} +2 -2
  9. package/dist/{chart-bar-ZB45ZRKH.js → chart-bar-EGPHJATL.js} +4 -4
  10. package/dist/{chart-counter-HFYNE4HT.js → chart-counter-5FFWQNZO.js} +4 -4
  11. package/dist/{chart-progress-ring-ZKMLRGSB.js → chart-progress-ring-JMXQLMLC.js} +4 -4
  12. package/dist/check-runtime.js +2 -2
  13. package/dist/{chunk-66MNRUCR.js → chunk-2PYO6VAC.js} +51 -3
  14. package/dist/{chunk-ZQQQAAKP.js → chunk-2XT4MZ76.js} +33 -2
  15. package/dist/chunk-5CDAM24P.js +31 -0
  16. package/dist/{chunk-PNS52FL4.js → chunk-5SLENAJW.js} +19 -6
  17. package/dist/{chunk-EFL34TXF.js → chunk-AITKH6QT.js} +24 -8
  18. package/dist/{chunk-HT6BGERF.js → chunk-FRN6WKHA.js} +28 -10
  19. package/dist/{chunk-FZHMQFG3.js → chunk-GAIOHGNR.js} +5 -21
  20. package/dist/{chunk-3RV4YKB3.js → chunk-JNN3EYVP.js} +51 -26
  21. package/dist/{chunk-NGY3TNND.js → chunk-K746NUIP.js} +70 -50
  22. package/dist/{chunk-HVCPEAQF.js → chunk-LKBZX7GV.js} +9 -6
  23. package/dist/{chunk-G66Z5CWR.js → chunk-MMUXVA47.js} +27 -3
  24. package/dist/{chunk-6M6DATLW.js → chunk-MXZSDGZQ.js} +6 -5
  25. package/dist/{chunk-3OU7HIEB.js → chunk-RE4IMWJR.js} +15 -31
  26. package/dist/{chunk-FVTMYS6U.js → chunk-RGF452LL.js} +1 -1
  27. package/dist/{chunk-34O5BY6X.js → chunk-RXHW4EP4.js} +21 -4
  28. package/dist/chunk-SKRGRKHY.js +142 -0
  29. package/dist/{chunk-TCEHK2BW.js → chunk-YJJC4N4D.js} +2 -1
  30. package/dist/cli.js +438 -30
  31. package/dist/{compose-video-JWIEDTEJ.js → compose-video-PD6LKKRK.js} +4 -4
  32. package/dist/{cta-logo-S4OMTNXD.js → cta-logo-YR7LYUR5.js} +3 -3
  33. package/dist/{cta-media-EROTHDRR.js → cta-media-DQJBKUO2.js} +4 -4
  34. package/dist/{events-BBP30j3c.d.ts → events-B6qS1Lsb.d.ts} +2 -2
  35. package/dist/{incoming-call-5IWJ2TAS.js → incoming-call-54KQ7O5A.js} +3 -3
  36. package/dist/index.d.ts +89 -3
  37. package/dist/index.js +12 -1
  38. package/dist/{infographic-before-after-2I7YZVDD.js → infographic-before-after-ROS52GOW.js} +1 -1
  39. package/dist/{infographic-feature-list-7SK65U3K.js → infographic-feature-list-E7MGUDAN.js} +4 -4
  40. package/dist/{infographic-problem-solution-IW35S6BR.js → infographic-problem-solution-3S6NGO5N.js} +4 -4
  41. package/dist/{infographic-stat-row-XHUZZPDM.js → infographic-stat-row-SYB7AB7H.js} +4 -4
  42. package/dist/{infographic-steps-JDDEDI7X.js → infographic-steps-KCE6JWE2.js} +4 -4
  43. package/dist/{kit-DEG3fcaL.d.ts → kit-8g46H2RZ.d.ts} +1 -1
  44. package/dist/{prompt-input-6ZWXEL3V.js → prompt-input-MHX4O42G.js} +3 -3
  45. package/dist/react.d.ts +200 -6
  46. package/dist/react.js +1859 -43
  47. package/dist/{reaction-Z3JIM3MQ.js → reaction-SLEGR3BE.js} +4 -4
  48. package/dist/{scene-video-backdrop-IL4F2SVD.js → scene-video-backdrop-XFPX4O3I.js} +2 -1
  49. package/dist/server.d.ts +104 -4
  50. package/dist/server.js +976 -26
  51. package/dist/{showcase-code-NWQAAJIA.js → showcase-code-2HLAVKBM.js} +4 -4
  52. package/dist/{showcase-phone-5UW45ATT.js → showcase-phone-Q4HEO4LH.js} +4 -4
  53. package/dist/{showcase-terminal-UVOD25UF.js → showcase-terminal-DL45LUFG.js} +4 -4
  54. package/dist/{showcase-web-3AW6VHIG.js → showcase-web-2QZO73FZ.js} +4 -4
  55. package/dist/{social-milestone-KPKHFOB5.js → social-milestone-GEFUQSBF.js} +3 -3
  56. package/dist/{social-notification-EDOU6TZB.js → social-notification-FJ5VHVLK.js} +3 -3
  57. package/dist/{social-review-stack-EZOO33SV.js → social-review-stack-KXQCKFRP.js} +3 -3
  58. package/dist/{social-testimonial-DLOINMMY.js → social-testimonial-JZ7CHC7S.js} +3 -3
  59. package/dist/{social-tweet-6JUM323G.js → social-tweet-UCDXM25F.js} +3 -3
  60. package/dist/{system-prompt-4I6Z5HK3.js → system-prompt-RRXIWDDD.js} +5 -3
  61. package/dist/template-catalog.js +1 -1
  62. package/dist/templates.d.ts +3 -3
  63. package/dist/test.d.ts +2 -2
  64. package/dist/test.js +3 -3
  65. package/dist/{text-stream-J6EDDJP4.js → text-stream-FW4BLBAL.js} +2 -2
  66. package/dist/{types-BV9IqExh.d.ts → types-2wHBqtg8.d.ts} +29 -1
  67. package/dist/types-DrABlRa7.d.ts +46 -0
  68. package/docs/agent-integration.md +49 -25
  69. package/docs/architecture.md +37 -22
  70. package/docs/branding-and-personalization.md +1 -1
  71. package/docs/concepts.md +14 -0
  72. package/docs/custom-templates.md +9 -9
  73. package/docs/customization.md +41 -5
  74. package/docs/getting-started.md +81 -84
  75. package/docs/media-and-audio.md +113 -117
  76. package/docs/persistence.md +1 -1
  77. package/docs/production.md +81 -92
  78. package/docs/prompt-and-input.md +71 -161
  79. package/docs/provider-integration.md +91 -49
  80. package/docs/responsive-orientation.md +1 -1
  81. package/docs/security.md +6 -5
  82. package/docs/streaming-protocol.md +1 -1
  83. package/docs/testing.md +51 -43
  84. package/examples/custom-template/README.md +1 -1
  85. package/package.json +20 -21
  86. package/registry/items/backgrounds.json +2 -2
  87. package/registry/items/barChart.json +6 -4
  88. package/registry/items/beforeAfter.json +1 -1
  89. package/registry/items/bigNumber.json +4 -3
  90. package/registry/items/brandMessage.json +3 -2
  91. package/registry/items/cardList.json +6 -4
  92. package/registry/items/codeEditor.json +4 -3
  93. package/registry/items/confetti.json +1 -1
  94. package/registry/items/ctaLogo.json +3 -2
  95. package/registry/items/ctaMedia.json +4 -3
  96. package/registry/items/emojiBurst.json +1 -1
  97. package/registry/items/incomingCall.json +3 -2
  98. package/registry/items/media.json +4 -3
  99. package/registry/items/milestone.json +3 -2
  100. package/registry/items/notification.json +3 -2
  101. package/registry/items/phoneMockup.json +4 -3
  102. package/registry/items/problemSolution.json +4 -3
  103. package/registry/items/progressRing.json +4 -3
  104. package/registry/items/promptInput.json +3 -2
  105. package/registry/items/reaction.json +2 -2
  106. package/registry/items/reviewStack.json +3 -2
  107. package/registry/items/steps.json +6 -4
  108. package/registry/items/terminal.json +5 -4
  109. package/registry/items/testimonial.json +3 -2
  110. package/registry/items/tripleStats.json +4 -3
  111. package/registry/items/tweet.json +3 -2
  112. package/registry/items/webMockup.json +4 -3
  113. package/starters/video-chat/.env.example +16 -0
  114. package/starters/video-chat/README.md +63 -0
  115. package/starters/video-chat/index.html +12 -0
  116. package/starters/video-chat/package.json +28 -0
  117. package/starters/video-chat/server.ts +152 -0
  118. package/starters/video-chat/src/main.tsx +8 -0
  119. package/starters/video-chat/stock.ts +139 -0
  120. package/starters/video-chat/tsconfig.json +20 -0
  121. package/starters/video-chat/vite.config.ts +81 -0
  122. package/styles/video-chat.css +713 -0
  123. package/dist/chunk-C6WVCZRW.js +0 -19
  124. package/docs/input-and-first-scene.md +0 -64
  125. package/docs/integrate-nextjs.md +0 -86
  126. package/docs/live-channels.md +0 -149
  127. package/docs/use-cases.md +0 -59
  128. package/examples/nextjs-quickstart/.env.example +0 -2
  129. package/examples/nextjs-quickstart/README.md +0 -27
  130. package/examples/nextjs-quickstart/next-env.d.ts +0 -4
  131. package/examples/nextjs-quickstart/package.json +0 -25
  132. package/examples/nextjs-quickstart/src/app/api/video/route.ts +0 -24
  133. package/examples/nextjs-quickstart/src/app/layout.tsx +0 -5
  134. package/examples/nextjs-quickstart/src/app/page.tsx +0 -31
  135. package/examples/nextjs-quickstart/tsconfig.json +0 -26
@@ -4,17 +4,17 @@ import {
4
4
  import {
5
5
  SceneBackground,
6
6
  getMediaBackgroundProps
7
- } from "./chunk-6M6DATLW.js";
7
+ } from "./chunk-MXZSDGZQ.js";
8
8
  import {
9
9
  renderWithEmoji
10
10
  } from "./chunk-L5BTTCZV.js";
11
- import "./chunk-HT6BGERF.js";
11
+ import "./chunk-FRN6WKHA.js";
12
12
  import "./chunk-W3VDIBGR.js";
13
13
  import {
14
14
  interpolate
15
15
  } from "./chunk-XTOXFMLM.js";
16
- import "./chunk-C6WVCZRW.js";
17
16
  import "./chunk-224QNWRA.js";
17
+ import "./chunk-5CDAM24P.js";
18
18
  import "./chunk-XWWLKRNU.js";
19
19
 
20
20
  // src/visual-system/primitives/typography/BrandMessageBubble.tsx
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  BUILTIN_SERVER_TEMPLATE_KIT
3
- } from "./chunk-FVTMYS6U.js";
4
- import "./chunk-3RV4YKB3.js";
3
+ } from "./chunk-RGF452LL.js";
4
+ import "./chunk-JNN3EYVP.js";
5
5
  import "./chunk-2E6T633S.js";
6
6
  export {
7
7
  BUILTIN_SERVER_TEMPLATE_KIT
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  SceneBackground,
3
3
  getMediaBackgroundProps
4
- } from "./chunk-6M6DATLW.js";
4
+ } from "./chunk-MXZSDGZQ.js";
5
5
  import {
6
6
  TemplateText
7
- } from "./chunk-TCEHK2BW.js";
7
+ } from "./chunk-YJJC4N4D.js";
8
8
  import "./chunk-L5BTTCZV.js";
9
- import "./chunk-HT6BGERF.js";
9
+ import "./chunk-FRN6WKHA.js";
10
10
  import {
11
11
  TOP_TEXT_AREA_RATIO,
12
12
  withOpacity
@@ -16,10 +16,10 @@ import {
16
16
  interpolate,
17
17
  spring
18
18
  } from "./chunk-XTOXFMLM.js";
19
- import "./chunk-C6WVCZRW.js";
20
19
  import {
21
20
  hasSceneMedia
22
21
  } from "./chunk-224QNWRA.js";
22
+ import "./chunk-5CDAM24P.js";
23
23
  import {
24
24
  resolveTokens
25
25
  } from "./chunk-XWWLKRNU.js";
@@ -8,12 +8,12 @@ import "./chunk-QAAO7OAY.js";
8
8
  import {
9
9
  SceneBackground,
10
10
  getMediaBackgroundProps
11
- } from "./chunk-6M6DATLW.js";
11
+ } from "./chunk-MXZSDGZQ.js";
12
12
  import {
13
13
  TemplateText
14
- } from "./chunk-TCEHK2BW.js";
14
+ } from "./chunk-YJJC4N4D.js";
15
15
  import "./chunk-L5BTTCZV.js";
16
- import "./chunk-HT6BGERF.js";
16
+ import "./chunk-FRN6WKHA.js";
17
17
  import {
18
18
  TOP_TEXT_AREA_RATIO,
19
19
  withOpacity
@@ -22,10 +22,10 @@ import {
22
22
  interpolate,
23
23
  spring
24
24
  } from "./chunk-XTOXFMLM.js";
25
- import "./chunk-C6WVCZRW.js";
26
25
  import {
27
26
  hasSceneMedia
28
27
  } from "./chunk-224QNWRA.js";
28
+ import "./chunk-5CDAM24P.js";
29
29
  import {
30
30
  MEDIA_TEXT_SHADOW,
31
31
  TOKEN_DEFAULTS,
@@ -5,12 +5,12 @@ import {
5
5
  import {
6
6
  SceneBackground,
7
7
  getMediaBackgroundProps
8
- } from "./chunk-6M6DATLW.js";
8
+ } from "./chunk-MXZSDGZQ.js";
9
9
  import {
10
10
  TemplateText
11
- } from "./chunk-TCEHK2BW.js";
11
+ } from "./chunk-YJJC4N4D.js";
12
12
  import "./chunk-L5BTTCZV.js";
13
- import "./chunk-HT6BGERF.js";
13
+ import "./chunk-FRN6WKHA.js";
14
14
  import {
15
15
  TOP_TEXT_AREA_RATIO,
16
16
  withOpacity
@@ -18,10 +18,10 @@ import {
18
18
  import {
19
19
  interpolate
20
20
  } from "./chunk-XTOXFMLM.js";
21
- import "./chunk-C6WVCZRW.js";
22
21
  import {
23
22
  hasSceneMedia
24
23
  } from "./chunk-224QNWRA.js";
24
+ import "./chunk-5CDAM24P.js";
25
25
  import {
26
26
  TOKEN_DEFAULTS,
27
27
  resolveTokens
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  VideoFrame
3
- } from "./chunk-NGY3TNND.js";
4
- import "./chunk-C6WVCZRW.js";
3
+ } from "./chunk-K746NUIP.js";
5
4
  import "./chunk-224QNWRA.js";
5
+ import "./chunk-5CDAM24P.js";
6
6
  import "./chunk-XWWLKRNU.js";
7
7
  import "./chunk-SPVTJH3F.js";
8
8
  import {
@@ -1,4 +1,5 @@
1
1
  import {
2
+ VIDEO_PROTOCOL_VERSION,
2
3
  VIDEO_SCHEMA_VERSION
3
4
  } from "./chunk-AOWSXU2K.js";
4
5
  import {
@@ -273,7 +274,8 @@ function validateScene(value, path) {
273
274
  "variables",
274
275
  "textArchetype",
275
276
  "backgroundEffect",
276
- "timing"
277
+ "timing",
278
+ "narration"
277
279
  ], path);
278
280
  nonEmptyString(requiredField(result, "id", `${path}.id`), `${path}.id`, 128);
279
281
  nonEmptyString(requiredField(result, "templateId", `${path}.templateId`), `${path}.templateId`, 128);
@@ -285,6 +287,8 @@ function validateScene(value, path) {
285
287
  const field = ownField(result, key, `${path}.${key}`);
286
288
  if (field.present) nonEmptyString(field.value, `${path}.${key}`, 128);
287
289
  }
290
+ const narration = ownField(result, "narration", `${path}.narration`);
291
+ if (narration.present) nonEmptyString(narration.value, `${path}.narration`, 4e3);
288
292
  return result;
289
293
  }
290
294
  function validateStyle(value, path) {
@@ -296,7 +300,8 @@ function validateStyle(value, path) {
296
300
  "defaultTextArchetype",
297
301
  "defaultTransition",
298
302
  "density",
299
- "motion"
303
+ "motion",
304
+ "generatedLook"
300
305
  ], path);
301
306
  const brandValue = requiredField(result, "brand", `${path}.brand`);
302
307
  const brand = record(brandValue, `${path}.brand`);
@@ -305,6 +310,8 @@ function validateStyle(value, path) {
305
310
  if (field.present) nonEmptyString(field.value, `${path}.brand.${key}`);
306
311
  }
307
312
  validateVideoBrand(brandValue, `${path}.brand`);
313
+ const look = ownField(result, "generatedLook", `${path}.generatedLook`);
314
+ if (look.present) nonEmptyString(look.value, `${path}.generatedLook`, 1e3);
308
315
  for (const key of [
309
316
  "preset",
310
317
  "defaultBackgroundEffect",
@@ -494,11 +501,52 @@ function parseVideo(value) {
494
501
  }
495
502
  }
496
503
 
504
+ // src/protocol/stable-json.ts
505
+ function stableJson(value) {
506
+ if (value == null || typeof value !== "object") return JSON.stringify(value);
507
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
508
+ const entries = Object.entries(value).filter(([, child]) => child !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
509
+ return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`).join(",")}}`;
510
+ }
511
+
512
+ // src/protocol/checksum.ts
513
+ function checksumVideo(config) {
514
+ const input = stableJson(config);
515
+ let hash = 2166136261;
516
+ for (let index = 0; index < input.length; index += 1) {
517
+ hash ^= input.charCodeAt(index);
518
+ hash = Math.imul(hash, 16777619) >>> 0;
519
+ }
520
+ return `fnv1a32:${hash.toString(16).padStart(8, "0")}`;
521
+ }
522
+
523
+ // src/protocol/events.ts
524
+ function createVideoEventFactory(options) {
525
+ let sequence = options.initialSequence ?? 0;
526
+ return {
527
+ create(type, data) {
528
+ const currentSequence = sequence;
529
+ sequence += 1;
530
+ return {
531
+ protocolVersion: VIDEO_PROTOCOL_VERSION,
532
+ runId: options.runId,
533
+ sequence: currentSequence,
534
+ eventId: `${options.runId}:${currentSequence}`,
535
+ type,
536
+ data
537
+ };
538
+ }
539
+ };
540
+ }
541
+
497
542
  export {
498
543
  MAX_RETAINED_SOURCE_LENGTH,
499
544
  MAX_RETAINED_INSTRUCTIONS_LENGTH,
500
545
  MAX_RETAINED_MEDIA_URLS,
501
546
  MAX_RETAINED_MEDIA_URL_LENGTH,
502
547
  VideoValidationError,
503
- parseVideo
548
+ parseVideo,
549
+ stableJson,
550
+ checksumVideo,
551
+ createVideoEventFactory
504
552
  };
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  parseVideoEvent
3
- } from "./chunk-G66Z5CWR.js";
3
+ } from "./chunk-MMUXVA47.js";
4
+ import {
5
+ MAX_RETAINED_MEDIA_URL_LENGTH
6
+ } from "./chunk-2PYO6VAC.js";
4
7
  import {
5
8
  VIDEO_PROTOCOL_VERSION
6
9
  } from "./chunk-AOWSXU2K.js";
@@ -92,10 +95,38 @@ function videoSseHeaders(headers) {
92
95
  return result;
93
96
  }
94
97
 
98
+ // src/video-chat/media.ts
99
+ function sanitizeVideoChatMedia(value) {
100
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
101
+ const media = value;
102
+ if (media.type !== "image" && media.type !== "video") return null;
103
+ if (typeof media.url !== "string") return null;
104
+ const url = media.url.trim();
105
+ if (!safeMediaUrl(url)) return null;
106
+ if (media.posterUrl != null && typeof media.posterUrl !== "string") return null;
107
+ const posterUrl = typeof media.posterUrl === "string" ? media.posterUrl.trim() : "";
108
+ if (posterUrl && !safeMediaUrl(posterUrl)) return null;
109
+ return {
110
+ url,
111
+ type: media.type,
112
+ ...posterUrl ? { posterUrl } : {}
113
+ };
114
+ }
115
+ function safeMediaUrl(value) {
116
+ if (!value || value.length > MAX_RETAINED_MEDIA_URL_LENGTH) return false;
117
+ try {
118
+ const protocol = new URL(value).protocol;
119
+ return protocol === "https:" || protocol === "http:";
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+
95
125
  export {
96
126
  VIDEO_STREAM_HEADER,
97
127
  VIDEO_STREAM_CONTENT_TYPE,
98
128
  encodeVideoSseEvent,
99
129
  decodeVideoSse,
100
- videoSseHeaders
130
+ videoSseHeaders,
131
+ sanitizeVideoChatMedia
101
132
  };
@@ -0,0 +1,31 @@
1
+ // src/visual-system/scene-templates/external-video-backdrop.tsx
2
+ import React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var DEFAULT = { mode: false, audioMuted: true, audioVolume: 1 };
5
+ var sharedContext = globalThis;
6
+ var BackdropContext = sharedContext.__vanillaskyVideoBackdropContext ??= React.createContext(DEFAULT);
7
+ function ExternalVideoBackdropProvider({
8
+ mode,
9
+ audioMuted = true,
10
+ audioVolume = 1,
11
+ children
12
+ }) {
13
+ const value = React.useMemo(
14
+ () => ({ mode, audioMuted, audioVolume }),
15
+ [mode, audioMuted, audioVolume]
16
+ );
17
+ return /* @__PURE__ */ jsx(BackdropContext.Provider, { value, children });
18
+ }
19
+ function useExternalVideoBackdrop() {
20
+ return React.useContext(BackdropContext).mode;
21
+ }
22
+ function useMediaAudio() {
23
+ const { audioMuted, audioVolume } = React.useContext(BackdropContext);
24
+ return { muted: audioMuted, volume: audioVolume };
25
+ }
26
+
27
+ export {
28
+ ExternalVideoBackdropProvider,
29
+ useExternalVideoBackdrop,
30
+ useMediaAudio
31
+ };
@@ -1,19 +1,29 @@
1
1
  // src/server/prompts/system-prompt.ts
2
- var VIDEO_PLAN_INSTRUCTION = `Wire format: newline-delimited JSON (NDJSON), exactly one complete JSON object per line.
2
+ function videoPlanInstruction(narrate = false) {
3
+ const narration = narrate ? `,"narration":"one sentence said aloud over this scene"` : "";
4
+ return `Wire format: newline-delimited JSON (NDJSON), exactly one complete JSON object per line.
3
5
  Allowed plan parts:
4
- {"type":"scene.add","scene":{"id":"stable-id","templateId":"trusted-template-id","variables":{},"timing":{"fixedDuration":4}}}
5
- {"type":"scene.add","placement":"closer","scene":{"id":"closer","templateId":"trusted-closer-template-id","variables":{},"timing":{"fixedDuration":3}}}
6
+ {"type":"scene.add","scene":{"id":"stable-id","templateId":"trusted-template-id","variables":{},"timing":{"fixedDuration":4}${narration}}}
7
+ {"type":"scene.add","placement":"closer","scene":{"id":"closer","templateId":"trusted-closer-template-id","variables":{},"timing":{"fixedDuration":3}${narration}}}
6
8
  {"type":"plan.complete","finishReason":"stop"}
7
9
  Do not emit protocol envelopes, Markdown fences, comments, prose, partial objects, audio, generated source, or any part type not listed above.`;
10
+ }
11
+ var VIDEO_PLAN_INSTRUCTION = videoPlanInstruction();
8
12
  function knowledgeRules(mode) {
9
13
  return mode === "general" ? `- This request uses general knowledge mode.
10
14
  - Use stable general knowledge to answer or develop the supplied request. Prefer broadly established, non-current information.
11
15
  - Answer the request directly. Do not make missing source detail the subject when stable general knowledge can provide a useful answer; for broad questions, provide a practical, broadly applicable framework.
12
- - For financial, medical, or legal topics, keep guidance general and educational. Never present it as personalized professional advice.
16
+ - For financial, medical, or legal topics, keep guidance general and informational. Never present it as personalized professional advice.
13
17
  - Treat claims in the supplied input as authoritative. Never invent citations, quotations, URLs, personal details, live facts, guarantees, or precise claims that require a source.` : `- This request uses input-only knowledge mode.
14
18
  - The supplied input is the complete factual basis. Do not add outside claims.`;
15
19
  }
16
- function createVideoSystemPrompt(knowledgeMode = "input-only") {
20
+ var NARRATION_RULES = `
21
+ Narration rules:
22
+ - Every scene.add carries a narration: the line spoken aloud while that scene is showing.
23
+ - One sentence, 10-16 words, plain spoken English. It is heard, never drawn.
24
+ - Say what the scene shows and why it matters. Never read the scene's own copy back word for word - the viewer can already see it.
25
+ - The lines are one continuous explanation: each follows from the ones before it. Never mention scenes, videos, slides, or yourself.`;
26
+ function createVideoSystemPrompt(knowledgeMode = "input-only", narrate = false) {
17
27
  return `You are a video director.
18
28
 
19
29
  Turn the supplied input into a concise, coherent sequence using trusted scene templates. Never return prose as the deliverable and never generate HTML, React, JavaScript, CSS, or animation source.
@@ -43,11 +53,14 @@ Streaming rules:
43
53
  - End explicitly with plan.complete. A truncated stream is never treated as complete.
44
54
  - Return only plan parts accepted by the provided schema.
45
55
 
46
- ${VIDEO_PLAN_INSTRUCTION}`;
56
+ ${narrate ? NARRATION_RULES : ""}
57
+
58
+ ${videoPlanInstruction(narrate)}`;
47
59
  }
48
60
  var DEFAULT_VIDEO_SYSTEM_PROMPT = createVideoSystemPrompt();
49
61
 
50
62
  export {
63
+ videoPlanInstruction,
51
64
  VIDEO_PLAN_INSTRUCTION,
52
65
  createVideoSystemPrompt,
53
66
  DEFAULT_VIDEO_SYSTEM_PROMPT
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createVideoSystemPrompt
3
- } from "./chunk-PNS52FL4.js";
3
+ } from "./chunk-5SLENAJW.js";
4
4
  import {
5
5
  PACING_PLANNER_RULES
6
6
  } from "./chunk-3O7OMMMF.js";
@@ -64,12 +64,19 @@ function needsPlannerSchema(schema) {
64
64
  const visit = (property) => property.minimum != null || property.maximum != null || property.type === "object" || property.items != null && visit(property.items);
65
65
  return Object.values(schema.properties).some(visit);
66
66
  }
67
+ function isMediaProperty(property) {
68
+ return property?.format === "uri" || property?.format === "supplied-image" || property?.format === "stock-media-keyword";
69
+ }
70
+ function requiresAvailableMedia(template) {
71
+ const gates = getTemplateSchemaGates(template.schema);
72
+ return (template.schema.required ?? []).some((name) => isMediaProperty(template.schema.properties[name])) || gates.requiredAnyOf.some((group) => group.every((name) => isMediaProperty(template.schema.properties[name])));
73
+ }
67
74
  var COMMON_MEDIA_VARIABLES = {
68
75
  mediaUrl: "media",
69
76
  mediaType: "enum(auto|photo|video|gradient)",
70
77
  mediaPoster: "media",
71
78
  mediaPosition: "enum(center|top|bottom|left|right)",
72
- mediaTreatment: "enum(subtle|cinematic|text-safe)"
79
+ mediaTreatment: "enum(none|subtle|cinematic|text-safe)"
73
80
  };
74
81
  function plannerCatalog(templates, mediaResolverAvailable) {
75
82
  return templates.map((template) => {
@@ -103,12 +110,15 @@ function plannerCatalog(templates, mediaResolverAvailable) {
103
110
  });
104
111
  }
105
112
  function createTemplateSystemPrompt(options) {
106
- const templates = options.kit.listTemplateMetadata();
113
+ const templates = options.kit.listTemplateMetadata().filter((template) => !requiresAvailableMedia(template) || options.suppliedMediaAvailable === true || options.mediaResolverAvailable === true && getStandardMediaResolverContract(template.schema) != null);
107
114
  const resolverMediaAvailable = options.mediaResolverAvailable === true && templates.some(({ schema }) => getStandardMediaResolverContract(schema) != null);
108
115
  const ids = new Set(templates.map(({ id }) => id));
109
116
  const fields = new Set(templates.flatMap(({ schema }) => Object.keys(schema.properties)));
110
- const basePrompt = createVideoSystemPrompt(options.knowledgeMode).split("\n").filter(
111
- (line) => !line.includes("Never use media, ctaMedia, or reaction as the first generated body template") && !(resolverMediaAvailable && line.includes("Never expose a loading placeholder or unresolved media keyword"))
117
+ const basePrompt = createVideoSystemPrompt(options.knowledgeMode, options.narrate === true).split("\n").filter(
118
+ (line) => !line.includes("Never use media, ctaMedia, or reaction as the first generated body template") && // The rule that keeps the opening beat asset-free belongs to the same
119
+ // pair as the one below it: both exist so playback can start without
120
+ // waiting on a provider, and both go when the host owns that wait.
121
+ !(options.mediaOnFirstScene === true && line.includes("The first generated body scene must be asset-free")) && !(resolverMediaAvailable && line.includes("Never expose a loading placeholder or unresolved media keyword"))
112
122
  ).join("\n");
113
123
  const factRules = [
114
124
  ...ids.has("cardList") ? ["cardList needs two or three unused parallel facts"] : [],
@@ -139,12 +149,18 @@ ${options.basePrompt.trim()}` : void 0,
139
149
  terminalPayoffs.length > 0 ? `When the input has no clear grounded action, never invent one and do not use jobs:[ask]. End with a grounded payoff using ${terminalPayoffNames}. Its concise 6\u201312 words of declarative copy must answer the story's so-what without repeating hook or setup language. Use media for an honest visual payoff, emojiBurst for playful delight, and confetti only for a real celebration, win, or milestone.` : void 0,
140
150
  "requiresQuote is a hard gate: use those templates only for exact quoted words and attribution present in raw input. Never turn a role, relationship, or summary into speech. requiresScreenshot likewise needs an actual supplied screenshot URL.",
141
151
  templates.some(({ schema }) => (schema["x-vanillasky"]?.requiredAnyOf?.length ?? 0) > 0) ? "requiredAnyOf is a hard presence gate: satisfy every listed group with at least one non-empty value." : void 0,
142
- mediaTemplates.length > 0 ? `For streaming startup, ${mediaTemplateNames} ${mediaTemplates.length === 1 ? "is" : "are"} forbidden as the first generated body template, even when mediaType is gradient. Start with a content-fit non-media template.` : void 0,
152
+ mediaTemplates.length > 0 && options.mediaOnFirstScene !== true ? `For streaming startup, ${mediaTemplateNames} ${mediaTemplates.length === 1 ? "is" : "are"} forbidden as the first generated body template, even when mediaType is gradient. Start with a content-fit non-media template.` : void 0,
143
153
  listFields.length > 0 ? `For list variables (${listFields.join(", ")}), emit actual JSON arrays. Keep list labels to 1\u20133 words. Keep step labels to 1\u20132 words and at most 18 characters. Do not use pipes or newlines as list delimiters.` : void 0,
144
154
  fields.has("bars") ? 'For bars, emit an actual JSON array of 2\u20136 grounded objects with "label" and "value" fields. Use comparable units and avoid a largest-to-smallest positive ratio above 20. Do not encode bars as a string.' : void 0,
145
- resolverMediaAvailable ? "Prefer a relevant resolved image or video background on later media-capable scenes whenever the permitted factual basis names a concrete person, place, product context, activity, or outcome that can be depicted honestly. Emit mediaKeyword on scene.add as a specific 2\u20138 word semantic query of at most 80 characters. The host removes mediaKeyword before the complete scene reaches the browser and replaces it with an approved asset. Use mediaType=gradient for abstract, sensitive, unsafe, or visually ambiguous material. Never emit or invent mediaUrl or mediaPoster." : mediaTemplates.length > 0 ? `Use exact grounded values for required fields. mediaUrl must come verbatim from supplied input. Stock queries are not available. Do not select ${mediaTemplateNames} without a supplied mediaUrl; if another template has no mediaUrl, explicitly use mediaType=gradient. mediaType auto detects a URL, while gradient deliberately uses no external asset.` : "Use exact grounded values for required fields.",
155
+ resolverMediaAvailable ? `Prefer a relevant resolved image or video background on ${options.mediaOnFirstScene === true ? "every media-capable scene, the first included," : "later media-capable scenes"} whenever the permitted factual basis names a concrete person, place, product context, activity, or outcome that can be depicted honestly. Emit mediaKeyword on scene.add as a specific 2\u20138 word semantic query of at most 80 characters. The host removes mediaKeyword before the complete scene reaches the browser and replaces it with an approved asset. Use mediaType=gradient for abstract, sensitive, unsafe, or visually ambiguous material. Never emit or invent mediaUrl or mediaPoster.` : mediaTemplates.length > 0 ? `Use exact grounded values for required fields. mediaUrl must come verbatim from supplied input. Stock queries are not available. Do not select ${mediaTemplateNames} without a supplied mediaUrl; if another template has no mediaUrl, explicitly use mediaType=gradient. mediaType auto detects a URL, while gradient deliberately uses no external asset.` : "Use exact grounded values for required fields.",
146
156
  "Catalog guidance describes composition; it is not a factual source and must never replace the permitted factual basis.",
147
- JSON.stringify(plannerCatalog(templates, resolverMediaAvailable))
157
+ JSON.stringify(plannerCatalog(templates, resolverMediaAvailable)),
158
+ // Last, because the catalogue is thousands of tokens of per-scene field
159
+ // lists and it is what the model matches when it emits a scene. The wire
160
+ // format above says narration belongs there; said only above, a third of
161
+ // the way through, it loses to the schema the model has just finished
162
+ // reading, and every scene comes back silent.
163
+ options.narrate === true ? '\nThe catalog lists variables only. Every scene.add also carries "narration" beside "variables" and "timing", as specified in the wire format above. A scene without it is incomplete.' : void 0
148
164
  ].filter((line) => line != null).join("\n");
149
165
  }
150
166
 
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  getBackgroundTransform
3
3
  } from "./chunk-W3VDIBGR.js";
4
+ import {
5
+ useMediaAudio
6
+ } from "./chunk-5CDAM24P.js";
4
7
 
5
8
  // src/visual-system/scene-templates/scene-video-backdrop.tsx
6
9
  import { useEffect, useRef, useState } from "react";
@@ -27,6 +30,8 @@ var SceneVideoBackdrop = ({
27
30
  progress,
28
31
  beatIntensity = 0,
29
32
  isPlaying,
33
+ muted,
34
+ volume,
30
35
  playbackId = mediaUrl,
31
36
  retainPoster = false,
32
37
  persistent = false,
@@ -34,6 +39,9 @@ var SceneVideoBackdrop = ({
34
39
  onReady,
35
40
  onError
36
41
  }) => {
42
+ const inheritedAudio = useMediaAudio();
43
+ const resolvedMuted = muted ?? inheritedAudio.muted;
44
+ const resolvedVolume = volume ?? inheritedAudio.volume;
37
45
  const resolvedPosition = resolveMediaPosition(mediaPosition);
38
46
  const bgTransform = getBackgroundTransform(backgroundEffect, progress, beatIntensity);
39
47
  const [decodedVideoUrl, setDecodedVideoUrl] = useState();
@@ -41,6 +49,25 @@ var SceneVideoBackdrop = ({
41
49
  const startedVideoUrl = useRef(void 0);
42
50
  const startedPlaybackId = useRef(void 0);
43
51
  const videoPresentationKey = `${playbackId}\0${mediaUrl}`;
52
+ useEffect(() => {
53
+ const video = videoRef.current;
54
+ if (!video) return;
55
+ if (video.getAttribute("src") !== mediaUrl) {
56
+ video.setAttribute("src", mediaUrl);
57
+ video.load();
58
+ }
59
+ return () => {
60
+ video.pause();
61
+ video.removeAttribute("src");
62
+ video.load();
63
+ startedVideoUrl.current = void 0;
64
+ startedPlaybackId.current = void 0;
65
+ };
66
+ }, [mediaUrl]);
67
+ useEffect(() => {
68
+ const video = videoRef.current;
69
+ if (video) video.volume = resolvedVolume;
70
+ }, [resolvedVolume]);
44
71
  useEffect(() => {
45
72
  const video = videoRef.current;
46
73
  if (!video) return;
@@ -59,15 +86,6 @@ var SceneVideoBackdrop = ({
59
86
  startedVideoUrl.current = mediaUrl;
60
87
  startedPlaybackId.current = playbackId;
61
88
  }, [isPlaying, mediaUrl, playbackId]);
62
- useEffect(() => {
63
- const video = videoRef.current;
64
- return () => {
65
- if (!video) return;
66
- video.pause();
67
- video.removeAttribute("src");
68
- video.load();
69
- };
70
- }, []);
71
89
  const mediaStyle = {
72
90
  position: "absolute",
73
91
  inset: 0,
@@ -135,7 +153,7 @@ var SceneVideoBackdrop = ({
135
153
  ref: videoRef,
136
154
  src: mediaUrl,
137
155
  poster: retainPoster || decodedVideoUrl !== mediaUrl ? mediaPoster || void 0 : void 0,
138
- muted: true,
156
+ muted: resolvedMuted,
139
157
  loop: true,
140
158
  playsInline: true,
141
159
  preload: "auto",
@@ -1,29 +1,14 @@
1
1
  import {
2
2
  parseVideoEvent
3
- } from "./chunk-G66Z5CWR.js";
3
+ } from "./chunk-MMUXVA47.js";
4
+ import {
5
+ checksumVideo,
6
+ stableJson
7
+ } from "./chunk-2PYO6VAC.js";
4
8
  import {
5
9
  VIDEO_SCHEMA_VERSION
6
10
  } from "./chunk-AOWSXU2K.js";
7
11
 
8
- // src/protocol/stable-json.ts
9
- function stableJson(value) {
10
- if (value == null || typeof value !== "object") return JSON.stringify(value);
11
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
12
- const entries = Object.entries(value).filter(([, child]) => child !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
13
- return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`).join(",")}}`;
14
- }
15
-
16
- // src/protocol/checksum.ts
17
- function checksumVideo(config) {
18
- const input = stableJson(config);
19
- let hash = 2166136261;
20
- for (let index = 0; index < input.length; index += 1) {
21
- hash ^= input.charCodeAt(index);
22
- hash = Math.imul(hash, 16777619) >>> 0;
23
- }
24
- return `fnv1a32:${hash.toString(16).padStart(8, "0")}`;
25
- }
26
-
27
12
  // src/protocol/state.ts
28
13
  function createVideoState() {
29
14
  return {
@@ -188,7 +173,6 @@ function createReplayableStream(source, onError) {
188
173
  }
189
174
 
190
175
  export {
191
- checksumVideo,
192
176
  createVideoState,
193
177
  applyVideoEvent,
194
178
  createReplayableStream