@vanillaskyai/video 0.10.2 → 0.10.4

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 (48) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/PUBLIC-API.md +43 -31
  3. package/README.md +18 -9
  4. package/dist/check-runtime.js +2 -2
  5. package/dist/{chunk-KHLO5OHT.js → chunk-G5TYLTL5.js} +1 -1
  6. package/dist/{chunk-77VY4O7A.js → chunk-O6FBIB4L.js} +58 -10
  7. package/dist/{chunk-TVIU23OM.js → chunk-OOBT4X46.js} +13 -3
  8. package/dist/{chunk-5C6HZNHY.js → chunk-QZAZT44G.js} +3 -2
  9. package/dist/{chunk-6KZGF63O.js → chunk-WZFLEPLM.js} +58 -35
  10. package/dist/{chunk-VQH3JTQC.js → chunk-Z2ZI5G7O.js} +1 -1
  11. package/dist/{cinema-media-APDJS7SC.js → cinema-media-3SIGVVCY.js} +4 -4
  12. package/dist/{comparison-ZVE2DE7U.js → comparison-VDGRWPB5.js} +4 -4
  13. package/dist/{editorial-timeline-YIXXVJUV.js → editorial-timeline-QJU4JJWB.js} +4 -4
  14. package/dist/{key-figure-I2C37FWV.js → key-figure-YUAQW6OE.js} +4 -4
  15. package/dist/{mobile-message-JSGBU6EX.js → mobile-message-ZMMTUDUQ.js} +4 -4
  16. package/dist/{quote-XH54G3FS.js → quote-BB6UJT6L.js} +4 -4
  17. package/dist/react.d.ts +3 -1
  18. package/dist/react.js +94 -84
  19. package/dist/{scene-video-backdrop-PCTARAAJ.js → scene-video-backdrop-4EZVVQY7.js} +2 -2
  20. package/dist/server.d.ts +15 -3
  21. package/dist/server.js +416 -275
  22. package/dist/{types-CdBmNTGD.d.ts → types-BqB8zC9u.d.ts} +1 -1
  23. package/docs/agent-integration.md +5 -4
  24. package/docs/concepts.md +2 -2
  25. package/docs/development.md +23 -0
  26. package/docs/getting-started.md +7 -5
  27. package/docs/media-and-audio.md +37 -18
  28. package/docs/performance.md +21 -10
  29. package/docs/production.md +11 -10
  30. package/docs/prompt-and-input.md +24 -15
  31. package/docs/provider-integration.md +11 -13
  32. package/docs/reference/protocol.md +7 -1
  33. package/docs/reference/provider-adapters.md +7 -4
  34. package/docs/testing.md +7 -2
  35. package/package.json +5 -2
  36. package/registry/items/backgrounds.json +2 -2
  37. package/registry/items/cinemaMedia.json +1 -1
  38. package/registry/items/comparison.json +1 -1
  39. package/registry/items/editorialTimeline.json +1 -1
  40. package/registry/items/keyFigure.json +1 -1
  41. package/registry/items/mobileMessage.json +1 -1
  42. package/registry/items/quote.json +1 -1
  43. package/starters/video-chat/README.md +25 -26
  44. package/starters/video-chat/package.json +1 -1
  45. package/starters/video-chat/providers/video.ts +10 -6
  46. package/starters/video-chat/server.ts +1 -1
  47. package/starters/video-chat/stock.ts +59 -31
  48. package/styles/video-chat.css +6 -61
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-KHLO5OHT.js";
4
- import "./chunk-5C6HZNHY.js";
5
- import "./chunk-77VY4O7A.js";
3
+ } from "./chunk-G5TYLTL5.js";
4
+ import "./chunk-QZAZT44G.js";
5
+ import "./chunk-O6FBIB4L.js";
6
6
  import {
7
7
  editorialLabel,
8
8
  fade
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  hasSceneMedia
12
12
  } from "./chunk-224QNWRA.js";
13
- import "./chunk-TVIU23OM.js";
13
+ import "./chunk-OOBT4X46.js";
14
14
 
15
15
  // src/visual-system/scene-templates/comparison.tsx
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-KHLO5OHT.js";
4
- import "./chunk-5C6HZNHY.js";
5
- import "./chunk-77VY4O7A.js";
3
+ } from "./chunk-G5TYLTL5.js";
4
+ import "./chunk-QZAZT44G.js";
5
+ import "./chunk-O6FBIB4L.js";
6
6
  import {
7
7
  fade
8
8
  } from "./chunk-4YM2M62S.js";
9
9
  import "./chunk-224QNWRA.js";
10
- import "./chunk-TVIU23OM.js";
10
+ import "./chunk-OOBT4X46.js";
11
11
 
12
12
  // src/visual-system/scene-templates/editorial-timeline.tsx
13
13
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-KHLO5OHT.js";
4
- import "./chunk-5C6HZNHY.js";
5
- import "./chunk-77VY4O7A.js";
3
+ } from "./chunk-G5TYLTL5.js";
4
+ import "./chunk-QZAZT44G.js";
5
+ import "./chunk-O6FBIB4L.js";
6
6
  import {
7
7
  editorialLabel,
8
8
  fade
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  hasSceneMedia
12
12
  } from "./chunk-224QNWRA.js";
13
- import "./chunk-TVIU23OM.js";
13
+ import "./chunk-OOBT4X46.js";
14
14
 
15
15
  // src/visual-system/scene-templates/key-figure.tsx
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  MediaScene
3
- } from "./chunk-VQH3JTQC.js";
4
- import "./chunk-5C6HZNHY.js";
3
+ } from "./chunk-Z2ZI5G7O.js";
4
+ import "./chunk-QZAZT44G.js";
5
5
  import {
6
6
  spring
7
- } from "./chunk-77VY4O7A.js";
7
+ } from "./chunk-O6FBIB4L.js";
8
8
  import {
9
9
  editorialFont
10
10
  } from "./chunk-4YM2M62S.js";
11
11
  import "./chunk-224QNWRA.js";
12
- import "./chunk-TVIU23OM.js";
12
+ import "./chunk-OOBT4X46.js";
13
13
 
14
14
  // src/visual-system/scene-templates/mobile-message.tsx
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  EditorialSurface
3
- } from "./chunk-KHLO5OHT.js";
4
- import "./chunk-5C6HZNHY.js";
5
- import "./chunk-77VY4O7A.js";
3
+ } from "./chunk-G5TYLTL5.js";
4
+ import "./chunk-QZAZT44G.js";
5
+ import "./chunk-O6FBIB4L.js";
6
6
  import {
7
7
  fade
8
8
  } from "./chunk-4YM2M62S.js";
9
9
  import {
10
10
  hasSceneMedia
11
11
  } from "./chunk-224QNWRA.js";
12
- import "./chunk-TVIU23OM.js";
12
+ import "./chunk-OOBT4X46.js";
13
13
 
14
14
  // src/visual-system/scene-templates/quote.tsx
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
package/dist/react.d.ts CHANGED
@@ -3,7 +3,7 @@ import { CSSProperties, ReactElement, ReactNode } from 'react';
3
3
  import { b as VideoEvent } from './events-B4YCc4vc.js';
4
4
  import { b as VideoOrientation, e as VideoScene, c as Video, i as VideoStyleOptions } from './types-DdZw4GRQ.js';
5
5
  import { T as TemplateRegistry } from './kit-DrRpdn0p.js';
6
- import { V as VideoChatMode, a as VideoChatAskOptions, b as VideoChatMedia, c as VideoChatSuggestion, d as VideoChatCapabilities, e as VideoChatWelcome } from './types-CdBmNTGD.js';
6
+ import { V as VideoChatMode, a as VideoChatAskOptions, b as VideoChatMedia, c as VideoChatSuggestion, d as VideoChatCapabilities, e as VideoChatWelcome } from './types-BqB8zC9u.js';
7
7
  import './catalog-types-WTbLP6Jh.js';
8
8
 
9
9
  type VideoPlaybackMode = "manual" | "muted-autoplay" | "autoplay-with-sound" | "autoplay-after-interaction";
@@ -185,6 +185,8 @@ interface VideoChatTurn {
185
185
  orientation: VideoOrientation;
186
186
  /** Generated footage keeps the orientation it was created in. */
187
187
  fixedOrientation: boolean;
188
+ /** Footage source selected for this answer; omitted in older saved turns. */
189
+ mode?: VideoChatMode;
188
190
  video?: Video;
189
191
  suggestions: readonly VideoChatSuggestion[];
190
192
  }
package/dist/react.js CHANGED
@@ -40,9 +40,9 @@ import {
40
40
  VideoFrame,
41
41
  getDimensions,
42
42
  sceneReadinessKey
43
- } from "./chunk-6KZGF63O.js";
43
+ } from "./chunk-WZFLEPLM.js";
44
44
  import "./chunk-224QNWRA.js";
45
- import "./chunk-TVIU23OM.js";
45
+ import "./chunk-OOBT4X46.js";
46
46
  import {
47
47
  getVideoDuration,
48
48
  resolveVideoTimeline
@@ -94,13 +94,13 @@ import { createElement } from "react";
94
94
 
95
95
  // src/visual-system/catalog/builtin-loaders.generated.ts
96
96
  var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
97
- "cinemaMedia": () => import("./cinema-media-APDJS7SC.js").then((module) => ({ default: module.MediaSceneTemplate })),
97
+ "cinemaMedia": () => import("./cinema-media-3SIGVVCY.js").then((module) => ({ default: module.MediaSceneTemplate })),
98
98
  "chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
99
- "editorialTimeline": () => import("./editorial-timeline-YIXXVJUV.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
- "mobileMessage": () => import("./mobile-message-JSGBU6EX.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
- "comparison": () => import("./comparison-ZVE2DE7U.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
- "quote": () => import("./quote-XH54G3FS.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
- "keyFigure": () => import("./key-figure-I2C37FWV.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
99
+ "editorialTimeline": () => import("./editorial-timeline-QJU4JJWB.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
+ "mobileMessage": () => import("./mobile-message-ZMMTUDUQ.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
+ "comparison": () => import("./comparison-VDGRWPB5.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
+ "quote": () => import("./quote-BB6UJT6L.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
+ "keyFigure": () => import("./key-figure-YUAQW6OE.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
104
104
  };
105
105
 
106
106
  // src/visual-system/catalog/builtin-player.generated.ts
@@ -754,6 +754,14 @@ function VideoPlayerRuntime({
754
754
  }
755
755
  }, []);
756
756
  const playbackEndedRef = useRef(false);
757
+ let mediaPlaying = isPlaying;
758
+ if (sceneIndexRef.current >= 0) {
759
+ try {
760
+ mediaPlaying = isPlaying && narrationReady?.() !== false;
761
+ } catch {
762
+ mediaPlaying = false;
763
+ }
764
+ }
757
765
  stateRef.current = state;
758
766
  timeRef.current = currentTime;
759
767
  callbacksRef.current = { onComplete, onPlaybackEnd, onError, onSceneChange, narrationReady, narrationTime, onFramePresented, onMediaFramePresented, onStallChange, onStateChange };
@@ -1131,7 +1139,8 @@ function VideoPlayerRuntime({
1131
1139
  time: showStartPoster ? posterTime : currentTime,
1132
1140
  width: dimensions.width,
1133
1141
  height: dimensions.height,
1134
- playing: isPlaying,
1142
+ playing: mediaPlaying,
1143
+ preparingNarration: isPlaying && !mediaPlaying,
1135
1144
  mediaAudioMuted: !nativeMediaAudio || isMuted,
1136
1145
  mediaAudioVolume: nativeMediaVolume,
1137
1146
  style: {
@@ -1243,7 +1252,7 @@ var VideoError = class extends Error {
1243
1252
  function recoverSceneMedia(scene) {
1244
1253
  if (scene.templateId === "cinemaMedia") {
1245
1254
  const title = scene.variables.fallbackText;
1246
- if (typeof title !== "string" || !title.trim() || [...title].length > 65) return void 0;
1255
+ if (typeof title !== "string" || !title.trim() || [...title].length > 65) return { ...scene, variables: { mediaType: "video", mediaUrl: "" } };
1247
1256
  return { ...scene, templateId: "chapterTitle", variables: { title: title.trim() } };
1248
1257
  }
1249
1258
  if (!["comparison", "editorialTimeline", "quote", "keyFigure"].includes(scene.templateId)) return void 0;
@@ -1397,7 +1406,11 @@ function useNarration(options) {
1397
1406
  if (!optionsRef.current.voice.getCurrentTime) return void 0;
1398
1407
  const time = currentRef.current ? optionsRef.current.voice.getCurrentTime() : void 0;
1399
1408
  if (currentRef.current && readyRef.current && time === void 0) return void 0;
1400
- if (time !== void 0 && Number.isFinite(time)) clockRef.current = Math.min(group?.totalSeconds ?? Infinity, Math.max(group?.offsetSeconds ?? 0, time));
1409
+ if (time !== void 0 && Number.isFinite(time)) {
1410
+ const end = group?.totalSeconds ?? scene.timing.fixedDuration ?? Infinity;
1411
+ const limit = currentRef.current ? Math.max(0, end - 0.01) : end;
1412
+ clockRef.current = Math.min(limit, Math.max(group?.offsetSeconds ?? 0, time));
1413
+ }
1401
1414
  return clockRef.current;
1402
1415
  }, []);
1403
1416
  const isReady = useCallback(() => optionsRef.current.enabled === false || readyRef.current || clockRef.current !== void 0 && clockRef.current >= (groupRef.current?.offsetSeconds ?? 0) + 0.04, []);
@@ -2082,7 +2095,7 @@ function errorFrom(cause) {
2082
2095
  return new VideoError("Video chat could not produce a playable response", { code: "video_chat_failed", recoverable: false });
2083
2096
  }
2084
2097
  async function responseError(response) {
2085
- return new VideoError(response.status === 429 ? "The conversation limit has been reached. Please try again later." : "Video chat could not produce a playable response", {
2098
+ return new VideoError(response.status === 429 ? "Too many requests right now. Please try again shortly." : "Video chat could not produce a playable response", {
2086
2099
  code: "http_error",
2087
2100
  status: response.status,
2088
2101
  recoverable: false
@@ -2275,7 +2288,7 @@ function useVideoChatSession(options = {}) {
2275
2288
  throw new VideoError("timeoutMs must be positive", { code: "invalid_option" });
2276
2289
  }
2277
2290
  const timeout = setTimeout(() => controller.abort(new DOMException("Video chat timed out", "TimeoutError")), timeoutMs);
2278
- const mode = "cinematic";
2291
+ const mode = currentOptions.mode ?? "cinematic";
2279
2292
  const orientation = currentOptions.orientation ?? "landscape";
2280
2293
  const id = (currentOptions.createTurnId ?? defaultTurnId)();
2281
2294
  const conversation = conversationFor(stateRef.current.turns);
@@ -2287,6 +2300,7 @@ function useVideoChatSession(options = {}) {
2287
2300
  completed: false,
2288
2301
  orientation,
2289
2302
  fixedOrientation: true,
2303
+ mode,
2290
2304
  suggestions: [],
2291
2305
  ...openingMedia ? { openingMedia } : {}
2292
2306
  };
@@ -2355,26 +2369,30 @@ function useVideoChatSession(options = {}) {
2355
2369
  }
2356
2370
  };
2357
2371
  flushRef.current = flush;
2358
- const resolveOpeningMedia = async (keyword, fallbackKeyword) => {
2359
- try {
2360
- const response = await request("opening-media", {
2361
- method: "POST",
2362
- body: JSON.stringify({ keyword, ...fallbackKeyword ? { fallbackKeyword } : {}, orientation })
2363
- }, openingController.signal);
2364
- if (!response.ok || !isOpeningCurrent() || timeline) return;
2365
- const payload = await response.json();
2366
- const media = sanitizeVideoChatMedia(payload.media);
2367
- if (media && isOpeningCurrent() && !timeline) dispatch({ type: "opening-media", id, media });
2368
- } catch {
2369
- }
2370
- };
2371
- const prepareSpeech = async (text, signal) => {
2372
- try {
2373
- return currentOptions.voice ? await withDeadline((child) => voiceRef.current.prepare(text, { signal: child }), 3e3, signal) : await voiceRef.current.prepare(text, { signal });
2374
- } catch (cause) {
2375
- if (currentOptions.voice && !signal.aborted && isCurrent()) unavailableVoiceLines.current.add(text);
2376
- throw cause;
2377
- }
2372
+ const speechLoads = /* @__PURE__ */ new Map();
2373
+ const speechLanes = [Promise.resolve(), Promise.resolve()];
2374
+ let nextSpeechLane = 0;
2375
+ let customVoiceFailed = false;
2376
+ const prepareSpeech = (text, signal) => {
2377
+ const cached = speechLoads.get(text);
2378
+ if (cached) return cached;
2379
+ const lane = nextSpeechLane++ % speechLanes.length;
2380
+ const prepared = speechLanes[lane].then(async () => {
2381
+ signal.throwIfAborted();
2382
+ try {
2383
+ if (customVoiceFailed) throw new Error("Voice preparation did not respond");
2384
+ return currentOptions.voice ? await withDeadline((child) => voiceRef.current.prepare(text, { signal: child }), 3e3, signal) : await voiceRef.current.prepare(text, { signal });
2385
+ } catch (cause) {
2386
+ if (currentOptions.voice && !signal.aborted && isCurrent()) {
2387
+ unavailableVoiceLines.current.add(text);
2388
+ if (cause instanceof DOMException && cause.name === "TimeoutError") customVoiceFailed = true;
2389
+ }
2390
+ throw cause;
2391
+ }
2392
+ });
2393
+ speechLoads.set(text, prepared);
2394
+ speechLanes[lane] = prepared.catch(() => void 0);
2395
+ return prepared;
2378
2396
  };
2379
2397
  const speakOpening = async (text) => {
2380
2398
  try {
@@ -2427,12 +2445,16 @@ function useVideoChatSession(options = {}) {
2427
2445
  if (event.type === "response.warning" || event.type === "response.error" && !event.data.terminal) {
2428
2446
  warn(event.type === "response.warning" && event.data.warning.message === MEDIA_RECOVERY_NOTICE ? MEDIA_RECOVERY_NOTICE : "Some parts were simplified so the response could continue.");
2429
2447
  }
2448
+ if (event.type === "data.video-chat-preparation") {
2449
+ const data = event.data;
2450
+ if (data && typeof data.sceneId === "string" && data.sceneId.length <= 200 && typeof data.narration === "string" && data.narration.trim() && data.narration.length <= 2e3 && speechLoads.size < 32) {
2451
+ void prepareSpeech(data.narration.trim(), controller.signal).catch(() => void 0);
2452
+ }
2453
+ continue;
2454
+ }
2430
2455
  if (event.type === "data.video-chat-opening") {
2431
2456
  const payload = event.data && typeof event.data === "object" && !Array.isArray(event.data) ? event.data : {};
2432
2457
  const line = typeof payload.line === "string" ? payload.line.trim().slice(0, 300) : "";
2433
- const keyword = typeof payload.keyword === "string" ? payload.keyword.trim().slice(0, 80) : "";
2434
- const fallbackKeyword = typeof payload.fallbackKeyword === "string" ? payload.fallbackKeyword.trim().slice(0, 80) : void 0;
2435
- if (!openingMedia && keyword) void resolveOpeningMedia(keyword, fallbackKeyword);
2436
2458
  if (!openingRequested && line) {
2437
2459
  spokenHook = line;
2438
2460
  lines.push(line);
@@ -3315,6 +3337,13 @@ function Logo() {
3315
3337
  return /* @__PURE__ */ jsx7("img", { className: "brand-logo", src: new URL("./assets/vanillasky-logo.svg", import.meta.url).href, alt: "VanillaSky", width: 144, height: 36 });
3316
3338
  }
3317
3339
 
3340
+ // src/video-chat/modes.ts
3341
+ var visualModes = [
3342
+ { id: "cinematic", label: "AI video", note: "Generated footage shaped by your prompt" },
3343
+ { id: "pexels", label: "Pexels", note: "Stock footage with lower generation costs" }
3344
+ ];
3345
+ var defaultMode = visualModes[0];
3346
+
3318
3347
  // src/video-chat/video-chat.tsx
3319
3348
  import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3320
3349
  var DESKTOP_WIDTH = 900;
@@ -3330,28 +3359,13 @@ function useViewportOrientation() {
3330
3359
  }, []);
3331
3360
  return portrait ? "portrait" : "landscape";
3332
3361
  }
3333
- var STEPS = [
3334
- "Rolling camera\u2026",
3335
- "Framing the shots\u2026",
3336
- "Filming your response\u2026",
3337
- "Still filming\u2026"
3338
- ];
3339
- function useFilmingStep(active, key) {
3340
- const [step, setStep] = useState7(0);
3341
- useEffect9(() => {
3342
- if (!active) return;
3343
- setStep(0);
3344
- const stepper = window.setInterval(() => setStep((current) => current + 1), 3e3);
3345
- return () => window.clearInterval(stepper);
3346
- }, [active, key]);
3347
- return STEPS[Math.min(step, STEPS.length - 1)];
3348
- }
3349
3362
  function Waveform({ active, listening }) {
3350
3363
  return /* @__PURE__ */ jsx8("span", { className: `waveform${active ? " on" : ""}${listening ? " hearing" : ""}`, "aria-hidden": "true", children: [0, 1, 2, 3, 4].map((bar) => /* @__PURE__ */ jsx8("span", { style: { animationDelay: `${bar * 140}ms`, animationDuration: `${900 + bar * 130}ms` } }, bar)) });
3351
3364
  }
3352
3365
  function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice = false }) {
3353
3366
  const [dismissedNoticeTurn, setDismissedNoticeTurn] = useState7();
3354
3367
  const [draft, setDraft] = useState7("");
3368
+ const [selectedMode, setSelectedMode] = useState7();
3355
3369
  const [savedSessions, setSavedSessions] = useState7([]);
3356
3370
  const [historyOpen, setHistoryOpen] = useState7(false);
3357
3371
  const [settingsOpen, setSettingsOpen] = useState7(false);
@@ -3368,7 +3382,7 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3368
3382
  const { chat, restoreSession } = useVideoChatSession({
3369
3383
  ...options,
3370
3384
  orientation: sessionOrientation,
3371
- style: { generatedLook: "Natural light, restrained camera movement, documentary realism, consistent natural color. No embedded text.", ...options.style }
3385
+ mode: selectedMode ?? options.mode
3372
3386
  });
3373
3387
  const instanceId = useId();
3374
3388
  const historyId = `${instanceId}-history`;
@@ -3405,7 +3419,6 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3405
3419
  resumeAfterInput.current = false;
3406
3420
  inputRef.current?.blur();
3407
3421
  void chat.ask(prompt, typeof value === "string" ? void 0 : {
3408
- openingMedia: value.media,
3409
3422
  opening: value.opening
3410
3423
  });
3411
3424
  }, [chat, listen]);
@@ -3428,16 +3441,10 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3428
3441
  resumeAfterInput.current = false;
3429
3442
  inputRef.current?.blur();
3430
3443
  }, [chat, listen]);
3431
- const current = chat.currentTurn;
3432
3444
  const shown = chat.shownTurn;
3433
3445
  const showing = chat.playerProps != null;
3434
- const [openingAsset, setOpeningAsset] = useState7();
3435
- const openingAssetKey = `${shown?.id ?? ""}:${shown?.openingMedia?.url ?? ""}`;
3436
- const openingMediaFailed = openingAsset?.key === openingAssetKey && openingAsset.state === "failed";
3437
- const openingMediaReady = openingAsset?.key === openingAssetKey && openingAsset.state === "ready";
3438
- const openingChapter = !showing && Boolean(shown?.opening) && (!shown?.openingMedia || openingMediaFailed || !openingMediaReady);
3439
- const waitingForPicture = chat.turns.length > 0 && !showing && (chat.status === "composing" || chat.status === "playing" || chat.status === "paused");
3440
- const filmingStep = useFilmingStep(waitingForPicture, current?.id);
3446
+ const openingChapter = !showing && Boolean(shown?.prompt);
3447
+ const openingTitle = shown?.opening ?? shown?.prompt ?? "";
3441
3448
  const status = chat.turns.length === 0 ? "idle" : chat.status === "composing" ? "drawing" : chat.status === "playing" ? "narrating" : chat.status === "error" || chat.status === "cancelled" ? "ended" : chat.status;
3442
3449
  const shownOrientation = shown?.orientation ?? sessionOrientation;
3443
3450
  const stageOrientation = shown?.fixedOrientation ? shownOrientation : sessionOrientation;
@@ -3497,7 +3504,10 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3497
3504
  onKeyDownCapture: controls.reveal,
3498
3505
  children: [
3499
3506
  /* @__PURE__ */ jsxs6("header", { className: "chrome", ...controlEvents, children: [
3500
- /* @__PURE__ */ jsx8("div", { className: "session-brand", children: /* @__PURE__ */ jsx8(Logo, {}) }),
3507
+ /* @__PURE__ */ jsxs6("div", { className: "session-brand", children: [
3508
+ /* @__PURE__ */ jsx8(Logo, {}),
3509
+ (shown?.mode ?? selectedMode ?? options.mode) === "pexels" && /* @__PURE__ */ jsx8("a", { className: "media-credit", href: "https://www.pexels.com", target: "_blank", rel: "noopener noreferrer", children: "Videos by Pexels" })
3510
+ ] }),
3501
3511
  /* @__PURE__ */ jsxs6("div", { className: "group", children: [
3502
3512
  /* @__PURE__ */ jsxs6(
3503
3513
  "button",
@@ -3552,27 +3562,8 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3552
3562
  /* @__PURE__ */ jsxs6("div", { className: "stage-area", children: [
3553
3563
  /* @__PURE__ */ jsxs6("div", { className: "stage", style: { background: "#000" }, children: [
3554
3564
  !showing && /* @__PURE__ */ jsxs6(Fragment4, { children: [
3555
- /* @__PURE__ */ jsx8("div", { className: "ground", "aria-hidden": "true" }),
3556
- openingChapter && /* @__PURE__ */ jsx8(OpeningChapter, { title: shown.opening }, shown.id),
3557
- shown?.openingMedia && !openingMediaFailed && /* @__PURE__ */ jsxs6(Fragment4, { children: [
3558
- /* @__PURE__ */ jsx8(
3559
- Frame,
3560
- {
3561
- media: shown.openingMedia,
3562
- poster: true,
3563
- revealWhenReady: true,
3564
- onReady: () => setOpeningAsset({ key: openingAssetKey, state: "ready" }),
3565
- onError: () => setOpeningAsset({ key: openingAssetKey, state: "failed" })
3566
- },
3567
- openingAssetKey
3568
- ),
3569
- openingMediaReady && /* @__PURE__ */ jsx8("div", { className: "opening-wash", "aria-hidden": "true" })
3570
- ] }),
3571
- chat.turns.length === 0 && /* @__PURE__ */ jsx8(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle }),
3572
- shown?.prompt && !shown.opening && /* @__PURE__ */ jsxs6("div", { className: "asked", children: [
3573
- /* @__PURE__ */ jsx8("p", { className: "asked-prompt", children: shown.prompt }),
3574
- waitingForPicture && /* @__PURE__ */ jsx8("p", { className: "asked-step", "aria-live": "polite", children: filmingStep })
3575
- ] })
3565
+ openingChapter && /* @__PURE__ */ jsx8(OpeningChapter, { title: openingTitle.length > 120 ? `${openingTitle.slice(0, 117).trimEnd()}\u2026` : openingTitle }, shown.id),
3566
+ chat.turns.length === 0 && /* @__PURE__ */ jsx8(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle })
3576
3567
  ] }),
3577
3568
  chat.playerProps && /* @__PURE__ */ jsx8("div", { className: "player-fit", style: { width: stageOrientation === "portrait" ? "min(100cqw, 56.25cqh)" : "min(100cqw, 177.7778cqh)" }, children: /* @__PURE__ */ jsx8(
3578
3569
  VideoPlayer,
@@ -3671,6 +3662,25 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3671
3662
  /* @__PURE__ */ jsx8("h2", { children: "Settings" }),
3672
3663
  /* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Close settings", onClick: closeSettings, children: /* @__PURE__ */ jsx8(Close, {}) })
3673
3664
  ] }),
3665
+ /* @__PURE__ */ jsxs6("fieldset", { className: "playback-options", children: [
3666
+ /* @__PURE__ */ jsx8("legend", { children: "Video source" }),
3667
+ visualModes.filter((mode) => chat.availableModes.includes(mode.id)).map((mode) => /* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
3668
+ /* @__PURE__ */ jsxs6("span", { children: [
3669
+ /* @__PURE__ */ jsx8("strong", { children: mode.label }),
3670
+ /* @__PURE__ */ jsx8("small", { children: mode.note })
3671
+ ] }),
3672
+ /* @__PURE__ */ jsx8(
3673
+ "input",
3674
+ {
3675
+ type: "radio",
3676
+ name: `${instanceId}-source`,
3677
+ value: mode.id,
3678
+ checked: (selectedMode ?? options.mode ?? "cinematic") === mode.id,
3679
+ onChange: () => setSelectedMode(mode.id)
3680
+ }
3681
+ )
3682
+ ] }, mode.id))
3683
+ ] }),
3674
3684
  /* @__PURE__ */ jsxs6("fieldset", { className: "playback-options", children: [
3675
3685
  /* @__PURE__ */ jsx8("legend", { children: "Watching" }),
3676
3686
  /* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SceneVideoBackdrop
3
- } from "./chunk-77VY4O7A.js";
4
- import "./chunk-TVIU23OM.js";
3
+ } from "./chunk-O6FBIB4L.js";
4
+ import "./chunk-OOBT4X46.js";
5
5
  export {
6
6
  SceneVideoBackdrop
7
7
  };
package/dist/server.d.ts CHANGED
@@ -2,8 +2,8 @@ import { k as VideoGenerationContext, l as VideoPlanner, m as VideoRequest, a as
2
2
  import { a as VideoFinishReason, V as VideoWarning, b as VideoEvent } from './events-B4YCc4vc.js';
3
3
  export { c as VideoWarningCategory } from './events-B4YCc4vc.js';
4
4
  import { S as SceneTemplateMetadata } from './catalog-types-WTbLP6Jh.js';
5
- import { f as VideoChatWelcomeOptions } from './types-CdBmNTGD.js';
6
- export { d as VideoChatCapabilities, g as VideoChatConversationTurn, V as VideoChatMode, h as VideoChatWelcomePrompt } from './types-CdBmNTGD.js';
5
+ import { V as VideoChatMode, f as VideoChatWelcomeOptions } from './types-BqB8zC9u.js';
6
+ export { d as VideoChatCapabilities, g as VideoChatConversationTurn, h as VideoChatWelcomePrompt } from './types-BqB8zC9u.js';
7
7
 
8
8
  interface VideoProviderUsage {
9
9
  inputTokens?: number;
@@ -236,6 +236,18 @@ interface VideoChatHandlerOptions extends Pick<VideoHandlerOptions, "templates"
236
236
  maxGeneratedVideos?: number;
237
237
  /** Generated media deadline in milliseconds, 1–120000. Defaults to 15000. Host providers must honor cancellation. */
238
238
  generateVideoTimeoutMs?: number;
239
+ /** Provider-supported clip duration in seconds, 2–20. Defaults to 5; does not change provider billing configuration. */
240
+ generatedClipDurationSec?: number;
241
+ /** Safe host-only phase timings; never includes prompts, narration, media URLs or provider error text. */
242
+ onDiagnostic?: (event: {
243
+ requestId: string;
244
+ mode: VideoChatMode;
245
+ phase: "request-accepted" | "opening-authored" | "shot-authored" | "media-start" | "media-end" | "media-skipped";
246
+ elapsedMs: number;
247
+ sceneId?: string;
248
+ durationMs?: number;
249
+ reason?: "ready" | "empty" | "provider-error" | "timeout" | "cancelled" | "allowance" | "deadline" | "not-configured";
250
+ }) => unknown;
239
251
  /** Trusted application guidance appended to the general-purpose response brief. */
240
252
  instructions?: string;
241
253
  /** Application-owned prompts and visual searches shown before the first turn. */
@@ -254,4 +266,4 @@ type VideoChatHandler = (request: Request) => Promise<Response>;
254
266
  */
255
267
  declare function createVideoChatHandler(options: VideoChatHandlerOptions): VideoChatHandler;
256
268
 
257
- export { SceneTemplateMetadata as ServerTemplateMetadata, type ServerTemplateRegistry, type VideoChatHandlerOptions, VideoChatWelcomeOptions, VideoFinishReason, type VideoGenerationSummary, type VideoProviderUsage, VideoWarning, createServerTemplateRegistry, createVideoChatHandler };
269
+ export { SceneTemplateMetadata as ServerTemplateMetadata, type ServerTemplateRegistry, type VideoChatHandlerOptions, VideoChatMode, VideoChatWelcomeOptions, VideoFinishReason, type VideoGenerationSummary, type VideoProviderUsage, VideoWarning, createServerTemplateRegistry, createVideoChatHandler };