@vanillaskyai/video 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/PUBLIC-API.md +4 -0
- package/README.md +3 -3
- package/dist/{chunk-XYZOJ5NZ.js → chunk-LHFADNWJ.js} +9 -8
- package/dist/{compose-video-TQOHDXNW.js → compose-video-6UQ33DOO.js} +1 -1
- package/dist/{events-CTIsANzz.d.ts → events-tQ0x-VaL.d.ts} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/{kit-DA2cfJ96.d.ts → kit-DlUSg8lA.d.ts} +1 -1
- package/dist/react.d.ts +3 -3
- package/dist/react.js +150 -61
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/templates.d.ts +3 -3
- package/dist/test.d.ts +2 -2
- package/dist/test.js +1 -1
- package/dist/{types-CkO2EYr4.d.ts → types-CVMb6QEq.d.ts} +2 -2
- package/docs/concepts.md +2 -1
- package/docs/customization.md +6 -3
- package/docs/getting-started.md +2 -2
- package/docs/input-and-first-scene.md +16 -4
- package/docs/integrate-nextjs.md +2 -2
- package/docs/media-and-audio.md +5 -1
- package/docs/prompt-and-input.md +3 -2
- package/examples/nextjs-quickstart/package.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.3.1
|
|
8
|
+
|
|
9
|
+
- Redesigns player controls for each playback state with responsive circular
|
|
10
|
+
icon controls, a sound-first start action, and a dimmed replay treatment that
|
|
11
|
+
leaves the completed poster frame visible.
|
|
12
|
+
- Lets applications pass `opening: false` to omit the deterministic opening
|
|
13
|
+
scene and render their own transient loading UI while the first generated
|
|
14
|
+
scene is planned. This keeps loading state out of completed video JSON and
|
|
15
|
+
does not force `media` into the planner's template capabilities.
|
|
16
|
+
|
|
7
17
|
## 0.3.0
|
|
8
18
|
|
|
9
19
|
- Documents closer eligibility: a template may close a video only when its
|
package/PUBLIC-API.md
CHANGED
|
@@ -183,6 +183,10 @@ mounted player autoplay with sound after the first successful viewer start.
|
|
|
183
183
|
`manual`, `muted-autoplay`, and `autoplay-with-sound` cover the other browser
|
|
184
184
|
startup policies.
|
|
185
185
|
|
|
186
|
+
`VideoInput.opening` accepts custom copy, uses the deterministic fallback when
|
|
187
|
+
omitted, and accepts `false` when the application owns transient loading UI and
|
|
188
|
+
wants the completed video to begin with the first generated scene.
|
|
189
|
+
|
|
186
190
|
Saved-video playback performs no generation request. `VideoPlayerBinding` and
|
|
187
191
|
the internal reducer state are not public types.
|
|
188
192
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Give your AI a video output
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
**VanillaSky is the open-source video response layer.** Turn text, structured
|
|
6
6
|
data, and live application context into personalized video responses that start
|
|
@@ -17,7 +17,7 @@ the planning prompt, trusted templates, validation, streaming, and player.
|
|
|
17
17
|
For humans:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install @vanillaskyai/video@0.3.
|
|
20
|
+
npm install @vanillaskyai/video@0.3.1 ai @ai-sdk/openai
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
For coding agents:
|
|
@@ -106,7 +106,7 @@ shows each complete, validated scene as soon as it is ready and returns a
|
|
|
106
106
|
deterministic `Video` object when generation finishes.
|
|
107
107
|
|
|
108
108
|
A copy-and-run app is in
|
|
109
|
-
[`examples/nextjs-quickstart`](https://github.com/VanillaSkyAi/video/tree/v0.3.
|
|
109
|
+
[`examples/nextjs-quickstart`](https://github.com/VanillaSkyAi/video/tree/v0.3.1/examples/nextjs-quickstart).
|
|
110
110
|
|
|
111
111
|
## Shape the response
|
|
112
112
|
|
|
@@ -97,7 +97,7 @@ function buildVideoUserPrompt(input, openingDurationSec = 0) {
|
|
|
97
97
|
"Compose a video response from the structured customer input below.",
|
|
98
98
|
`Knowledge mode: ${input.knowledgeMode ?? "input-only"}.`,
|
|
99
99
|
`Maximum duration: ${input.maxDurationSec ?? 30} seconds, including the supplied opening.`,
|
|
100
|
-
input.opening
|
|
100
|
+
typeof input.opening === "string" && input.opening.trim() ? `The host has already added the opening scene, which consumes ${openingDurationSec} seconds. Continue after it and do not repeat or rewrite it.` : "Add the first grounded scene as soon as it is complete.",
|
|
101
101
|
"The first generated body scene must be fully playable without external media. Use a content-fit text, data, comparison, list, or device-free template with no media URL or keyword.",
|
|
102
102
|
"Never use media, ctaMedia, or reaction as the first generated body template, even with mediaType=gradient. Choose a non-media template first.",
|
|
103
103
|
"Add that scene before resolving any stock or supplied asset. Media belongs on later body scenes and must arrive without blocking scene additions.",
|
|
@@ -139,10 +139,11 @@ var DEFAULT_OPENING_TEXT = "Creating your video...";
|
|
|
139
139
|
function resolveVideoInput(input) {
|
|
140
140
|
return {
|
|
141
141
|
...input,
|
|
142
|
-
opening: input.opening?.trim() || DEFAULT_OPENING_TEXT
|
|
142
|
+
opening: input.opening === false ? false : input.opening?.trim() || DEFAULT_OPENING_TEXT
|
|
143
143
|
};
|
|
144
144
|
}
|
|
145
|
-
function resolveStreamCapabilities(capabilities) {
|
|
145
|
+
function resolveStreamCapabilities(capabilities, hasRuntimeOpening) {
|
|
146
|
+
if (!hasRuntimeOpening) return capabilities;
|
|
146
147
|
if (capabilities?.templates == null) return capabilities;
|
|
147
148
|
return {
|
|
148
149
|
...capabilities,
|
|
@@ -174,7 +175,7 @@ function safeAbortReason(value) {
|
|
|
174
175
|
return safePublicDiagnostic(value instanceof Error ? value.message : value, "Request aborted");
|
|
175
176
|
}
|
|
176
177
|
function buildInitialConfig(input, audio, snapshotRetention, closerReserveSec, getTemplatePacing) {
|
|
177
|
-
const openingText = input.opening
|
|
178
|
+
const openingText = typeof input.opening === "string" ? input.opening.trim() : void 0;
|
|
178
179
|
const rawScenes = openingText ? [{
|
|
179
180
|
id: "supplied-opening",
|
|
180
181
|
templateId: "media",
|
|
@@ -238,7 +239,7 @@ function validateInput(input) {
|
|
|
238
239
|
if (input.maxDurationSec != null && (!Number.isFinite(input.maxDurationSec) || input.maxDurationSec < 5 || input.maxDurationSec > 120)) {
|
|
239
240
|
throw new Error("Video response maximum duration must be between 5 and 120 seconds");
|
|
240
241
|
}
|
|
241
|
-
if (input.opening != null && !input.opening.trim()) {
|
|
242
|
+
if (input.opening !== false && input.opening != null && !input.opening.trim()) {
|
|
242
243
|
throw new Error("Video response opening must be a non-empty string");
|
|
243
244
|
}
|
|
244
245
|
if (input.audio && !input.audio.src.trim()) {
|
|
@@ -331,7 +332,7 @@ function createSceneQualityWarnings(scene) {
|
|
|
331
332
|
}
|
|
332
333
|
function createVideo(rawInput, options) {
|
|
333
334
|
validateInput(rawInput);
|
|
334
|
-
const
|
|
335
|
+
const requiresGeneratedScene = rawInput.opening == null || rawInput.opening === false;
|
|
335
336
|
const input = resolveVideoInput(rawInput);
|
|
336
337
|
const requestId = options.requestId ?? createId("request");
|
|
337
338
|
const runId = options.runId ?? createId("run");
|
|
@@ -446,7 +447,7 @@ function createVideo(rawInput, options) {
|
|
|
446
447
|
format: { orientation: initialConfig.orientation ?? "portrait" },
|
|
447
448
|
style: initialConfig.style,
|
|
448
449
|
meta: initialConfig.meta,
|
|
449
|
-
capabilities: resolveStreamCapabilities(options.capabilities)
|
|
450
|
+
capabilities: resolveStreamCapabilities(options.capabilities, initialConfig.scenes.length > 0)
|
|
450
451
|
}));
|
|
451
452
|
if (initialConfig.audio) yield emit(events.create("audio.set", { audio: initialConfig.audio }));
|
|
452
453
|
for (const warning of initial.warnings) {
|
|
@@ -784,7 +785,7 @@ function createVideo(rawInput, options) {
|
|
|
784
785
|
warning: createIncompletePlanWarning()
|
|
785
786
|
}));
|
|
786
787
|
}
|
|
787
|
-
if (generatedSceneCount === 0 &&
|
|
788
|
+
if (generatedSceneCount === 0 && requiresGeneratedScene) {
|
|
788
789
|
throw new Error("The planner completed without adding a scene");
|
|
789
790
|
}
|
|
790
791
|
const snapshot = parseVideo(state.config);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as VideoOrientation, h as VideoStyle, V as Video, k as VideoCapabilities, a as VideoAudio, g as VideoScene, l as VIDEO_PROTOCOL_VERSION } from './types-
|
|
1
|
+
import { f as VideoOrientation, h as VideoStyle, V as Video, k as VideoCapabilities, a as VideoAudio, g as VideoScene, l as VIDEO_PROTOCOL_VERSION } from './types-CVMb6QEq.js';
|
|
2
2
|
|
|
3
3
|
type VideoWarningCategory = "validation" | "readability" | "grounding" | "provider" | "media" | "protocol";
|
|
4
4
|
type VideoWarningCode = "scene_duration_adjusted" | "scene_omitted_unreadable" | "scene_omitted_for_closer" | "scene_patch_rejected_readability" | "chart_scale_imbalance" | "plan_incomplete" | "plan_missing_closer" | "provider_warning" | "provider_diagnostics_unavailable";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { V as Video } from './types-
|
|
2
|
-
export { a as VideoAudio, b as VideoBackground, c as VideoBrand, d as VideoInput, e as VideoKnowledgeMode, f as VideoOrientation, g as VideoScene, h as VideoStyle, i as VideoStyleOptions, j as VideoSuppliedMedia } from './types-
|
|
1
|
+
import { V as Video } from './types-CVMb6QEq.js';
|
|
2
|
+
export { a as VideoAudio, b as VideoBackground, c as VideoBrand, d as VideoInput, e as VideoKnowledgeMode, f as VideoOrientation, g as VideoScene, h as VideoStyle, i as VideoStyleOptions, j as VideoSuppliedMedia } from './types-CVMb6QEq.js';
|
|
3
3
|
export { V as VideoStatus } from './state-DZcKuS32.js';
|
|
4
4
|
|
|
5
5
|
type VideoValidationErrorCode = "invalid_video" | "unsupported_video_version";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h as VideoStyle, k as VideoCapabilities } from './types-
|
|
1
|
+
import { h as VideoStyle, k as VideoCapabilities } from './types-CVMb6QEq.js';
|
|
2
2
|
import { ComponentType } from 'react';
|
|
3
3
|
import { S as SceneTemplateMetadata, d as TemplateJsonSchema, f as TemplateJsonSchemaProperty } from './catalog-types-BIhSpOWK.js';
|
|
4
4
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CSSProperties, ReactElement } from 'react';
|
|
2
|
-
import { V as VideoEvent, a as VideoWarning } from './events-
|
|
3
|
-
import { f as VideoOrientation, V as Video, d as VideoInput } from './types-
|
|
4
|
-
import { T as TemplateRegistry } from './kit-
|
|
2
|
+
import { V as VideoEvent, a as VideoWarning } from './events-tQ0x-VaL.js';
|
|
3
|
+
import { f as VideoOrientation, V as Video, d as VideoInput } from './types-CVMb6QEq.js';
|
|
4
|
+
import { T as TemplateRegistry } from './kit-DlUSg8lA.js';
|
|
5
5
|
import { V as VideoStatus } from './state-DZcKuS32.js';
|
|
6
6
|
import './catalog-types-BIhSpOWK.js';
|
|
7
7
|
|
package/dist/react.js
CHANGED
|
@@ -152,6 +152,51 @@ function savedVideoState(video) {
|
|
|
152
152
|
config: video
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
+
function PlayerIcon({ name, size = 22 }) {
|
|
156
|
+
const shared = {
|
|
157
|
+
width: size,
|
|
158
|
+
height: size,
|
|
159
|
+
viewBox: "0 0 24 24",
|
|
160
|
+
fill: "none",
|
|
161
|
+
stroke: "currentColor",
|
|
162
|
+
strokeWidth: 2,
|
|
163
|
+
strokeLinecap: "round",
|
|
164
|
+
strokeLinejoin: "round",
|
|
165
|
+
"aria-hidden": true,
|
|
166
|
+
focusable: false
|
|
167
|
+
};
|
|
168
|
+
if (name === "play") {
|
|
169
|
+
return /* @__PURE__ */ jsx("svg", { ...shared, children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z", fill: "currentColor", stroke: "none" }) });
|
|
170
|
+
}
|
|
171
|
+
if (name === "pause") {
|
|
172
|
+
return /* @__PURE__ */ jsxs("svg", { ...shared, children: [
|
|
173
|
+
/* @__PURE__ */ jsx("rect", { x: "7", y: "5", width: "3.5", height: "14", rx: "1", fill: "currentColor", stroke: "none" }),
|
|
174
|
+
/* @__PURE__ */ jsx("rect", { x: "13.5", y: "5", width: "3.5", height: "14", rx: "1", fill: "currentColor", stroke: "none" })
|
|
175
|
+
] });
|
|
176
|
+
}
|
|
177
|
+
if (name === "replay") {
|
|
178
|
+
return /* @__PURE__ */ jsxs("svg", { ...shared, children: [
|
|
179
|
+
/* @__PURE__ */ jsx("path", { d: "M4.5 9A8 8 0 1 1 5 16" }),
|
|
180
|
+
/* @__PURE__ */ jsx("path", { d: "M4.5 4.5V9H9" })
|
|
181
|
+
] });
|
|
182
|
+
}
|
|
183
|
+
if (name === "volume-off") {
|
|
184
|
+
return /* @__PURE__ */ jsxs("svg", { ...shared, children: [
|
|
185
|
+
/* @__PURE__ */ jsx("path", { d: "M11 5 6.5 9H3v6h3.5L11 19z" }),
|
|
186
|
+
/* @__PURE__ */ jsx("path", { d: "m16 9 5 5M21 9l-5 5" })
|
|
187
|
+
] });
|
|
188
|
+
}
|
|
189
|
+
if (name === "volume") {
|
|
190
|
+
return /* @__PURE__ */ jsxs("svg", { ...shared, children: [
|
|
191
|
+
/* @__PURE__ */ jsx("path", { d: "M11 5 6.5 9H3v6h3.5L11 19z" }),
|
|
192
|
+
/* @__PURE__ */ jsx("path", { d: "M15 9.5a4 4 0 0 1 0 5M17.8 7a7.5 7.5 0 0 1 0 10" })
|
|
193
|
+
] });
|
|
194
|
+
}
|
|
195
|
+
if (name === "exit-fullscreen") {
|
|
196
|
+
return /* @__PURE__ */ jsx("svg", { ...shared, children: /* @__PURE__ */ jsx("path", { d: "M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5" }) });
|
|
197
|
+
}
|
|
198
|
+
return /* @__PURE__ */ jsx("svg", { ...shared, children: /* @__PURE__ */ jsx("path", { d: "M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5" }) });
|
|
199
|
+
}
|
|
155
200
|
function VideoPlayerRuntime({
|
|
156
201
|
kit,
|
|
157
202
|
stream,
|
|
@@ -469,33 +514,38 @@ function VideoPlayerRuntime({
|
|
|
469
514
|
if (document.fullscreenElement === container) await document.exitFullscreen?.();
|
|
470
515
|
else await container.requestFullscreen?.();
|
|
471
516
|
};
|
|
517
|
+
const controlSize = Math.max(40, Math.min(52, Math.round(displayWidth * 0.15)));
|
|
518
|
+
const controlInset = Math.max(10, Math.min(20, Math.round(displayWidth * 0.056)));
|
|
472
519
|
const controlButtonStyle = {
|
|
473
520
|
display: "inline-grid",
|
|
474
521
|
placeItems: "center",
|
|
475
522
|
flex: "0 0 auto",
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
523
|
+
width: controlSize,
|
|
524
|
+
height: controlSize,
|
|
525
|
+
minWidth: controlSize,
|
|
526
|
+
minHeight: controlSize,
|
|
527
|
+
padding: 0,
|
|
528
|
+
border: "1px solid rgba(255, 255, 255, 0.08)",
|
|
529
|
+
borderRadius: 999,
|
|
530
|
+
backgroundColor: "rgba(255, 255, 255, 0.16)",
|
|
482
531
|
color: "#ffffff",
|
|
483
|
-
|
|
532
|
+
boxShadow: "0 5px 18px rgba(0, 0, 0, 0.16)",
|
|
533
|
+
backdropFilter: "blur(12px)",
|
|
484
534
|
cursor: "pointer"
|
|
485
535
|
};
|
|
486
536
|
const startButtonStyle = {
|
|
487
537
|
display: "inline-flex",
|
|
488
538
|
alignItems: "center",
|
|
489
539
|
gap: 10,
|
|
490
|
-
minHeight:
|
|
491
|
-
padding: "
|
|
492
|
-
border: "1px solid rgba(
|
|
540
|
+
minHeight: 60,
|
|
541
|
+
padding: "14px 22px",
|
|
542
|
+
border: "1px solid rgba(9, 7, 18, 0.08)",
|
|
493
543
|
borderRadius: 999,
|
|
494
|
-
backgroundColor: "
|
|
495
|
-
color: "#
|
|
496
|
-
boxShadow: "0 8px 28px rgba(0, 0, 0, 0.
|
|
497
|
-
|
|
498
|
-
|
|
544
|
+
backgroundColor: "#ffffff",
|
|
545
|
+
color: "#090712",
|
|
546
|
+
boxShadow: "0 8px 28px rgba(0, 0, 0, 0.24)",
|
|
547
|
+
font: "700 16px/1 system-ui, sans-serif",
|
|
548
|
+
whiteSpace: "nowrap",
|
|
499
549
|
cursor: "pointer"
|
|
500
550
|
};
|
|
501
551
|
const startButtonPositionStyle = {
|
|
@@ -579,7 +629,7 @@ function VideoPlayerRuntime({
|
|
|
579
629
|
onClick: armPlayback,
|
|
580
630
|
style: { ...startButtonStyle, ...startButtonPositionStyle },
|
|
581
631
|
children: [
|
|
582
|
-
/* @__PURE__ */ jsx(
|
|
632
|
+
/* @__PURE__ */ jsx(PlayerIcon, { name: "play", size: 20 }),
|
|
583
633
|
config?.audio && !isMuted ? "Play with sound" : "Play video"
|
|
584
634
|
]
|
|
585
635
|
}
|
|
@@ -615,7 +665,7 @@ function VideoPlayerRuntime({
|
|
|
615
665
|
...startButtonPositionStyle
|
|
616
666
|
},
|
|
617
667
|
children: [
|
|
618
|
-
/* @__PURE__ */ jsx(
|
|
668
|
+
/* @__PURE__ */ jsx(PlayerIcon, { name: "play", size: 20 }),
|
|
619
669
|
config?.audio && !isMuted ? "Play with sound" : "Play video"
|
|
620
670
|
]
|
|
621
671
|
}
|
|
@@ -631,60 +681,99 @@ function VideoPlayerRuntime({
|
|
|
631
681
|
loop: state.status === "streaming" || introPlaying
|
|
632
682
|
}
|
|
633
683
|
) : null,
|
|
634
|
-
|
|
684
|
+
ended ? /* @__PURE__ */ jsx(
|
|
685
|
+
"div",
|
|
686
|
+
{
|
|
687
|
+
"data-testid": "video-ended-scrim",
|
|
688
|
+
"aria-hidden": "true",
|
|
689
|
+
style: {
|
|
690
|
+
position: "absolute",
|
|
691
|
+
inset: 0,
|
|
692
|
+
zIndex: 1,
|
|
693
|
+
pointerEvents: "none",
|
|
694
|
+
background: "rgba(4, 3, 18, 0.52)",
|
|
695
|
+
backdropFilter: "blur(4px)"
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
) : null,
|
|
699
|
+
ended ? /* @__PURE__ */ jsxs(
|
|
700
|
+
"button",
|
|
701
|
+
{
|
|
702
|
+
type: "button",
|
|
703
|
+
"data-testid": "video-replay-button",
|
|
704
|
+
"aria-label": "Replay video response",
|
|
705
|
+
onClick: togglePlayback,
|
|
706
|
+
style: {
|
|
707
|
+
...startButtonStyle,
|
|
708
|
+
...startButtonPositionStyle,
|
|
709
|
+
zIndex: 3
|
|
710
|
+
},
|
|
711
|
+
children: [
|
|
712
|
+
/* @__PURE__ */ jsx(PlayerIcon, { name: "replay", size: 21 }),
|
|
713
|
+
"Replay"
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
) : null,
|
|
717
|
+
!generationCoverVisible && !showStartPoster && config?.scenes.length ? /* @__PURE__ */ jsxs(
|
|
635
718
|
"div",
|
|
636
719
|
{
|
|
637
720
|
"data-testid": "video-controls",
|
|
721
|
+
"data-layout": "split",
|
|
638
722
|
style: {
|
|
639
723
|
position: "absolute",
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
display: "flex",
|
|
644
|
-
alignItems: "center",
|
|
645
|
-
gap: 6,
|
|
646
|
-
minHeight: 48,
|
|
647
|
-
padding: "6px 8px",
|
|
648
|
-
border: "1px solid rgba(255, 255, 255, 0.2)",
|
|
649
|
-
borderRadius: 12,
|
|
650
|
-
backgroundColor: "rgba(9, 7, 18, 0.82)",
|
|
651
|
-
backdropFilter: "blur(12px)",
|
|
652
|
-
color: "#ffffff",
|
|
653
|
-
boxShadow: "0 4px 20px rgba(0, 0, 0, 0.35)"
|
|
724
|
+
inset: 0,
|
|
725
|
+
zIndex: 4,
|
|
726
|
+
pointerEvents: "none"
|
|
654
727
|
},
|
|
655
728
|
children: [
|
|
656
|
-
|
|
657
|
-
"
|
|
658
|
-
{
|
|
659
|
-
type: "button",
|
|
660
|
-
"aria-label": ended ? "Replay video response" : isPlaying ? "Pause video response" : "Play video response",
|
|
661
|
-
onClick: togglePlayback,
|
|
662
|
-
style: controlButtonStyle,
|
|
663
|
-
children: ended ? "\u21BB Replay" : isPlaying ? "\u2161" : "\u25B6"
|
|
664
|
-
}
|
|
665
|
-
) : null,
|
|
666
|
-
config?.audio ? /* @__PURE__ */ jsx(
|
|
667
|
-
"button",
|
|
729
|
+
/* @__PURE__ */ jsx(
|
|
730
|
+
"div",
|
|
668
731
|
{
|
|
669
|
-
|
|
670
|
-
"
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
732
|
+
"data-testid": "video-primary-controls",
|
|
733
|
+
style: { position: "absolute", left: controlInset, bottom: controlInset, display: "flex", pointerEvents: "auto" },
|
|
734
|
+
children: /* @__PURE__ */ jsx(
|
|
735
|
+
"button",
|
|
736
|
+
{
|
|
737
|
+
type: "button",
|
|
738
|
+
"aria-label": ended ? "Play video response from beginning" : isPlaying ? "Pause video response" : "Play video response",
|
|
739
|
+
onClick: togglePlayback,
|
|
740
|
+
style: controlButtonStyle,
|
|
741
|
+
children: /* @__PURE__ */ jsx(PlayerIcon, { name: isPlaying ? "pause" : "play" })
|
|
742
|
+
}
|
|
743
|
+
)
|
|
678
744
|
}
|
|
679
|
-
)
|
|
680
|
-
/* @__PURE__ */
|
|
681
|
-
"
|
|
745
|
+
),
|
|
746
|
+
/* @__PURE__ */ jsxs(
|
|
747
|
+
"div",
|
|
682
748
|
{
|
|
683
|
-
|
|
684
|
-
"
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
749
|
+
"data-testid": "video-secondary-controls",
|
|
750
|
+
style: { position: "absolute", right: controlInset, bottom: controlInset, display: "flex", gap: 10, pointerEvents: "auto" },
|
|
751
|
+
children: [
|
|
752
|
+
config?.audio ? /* @__PURE__ */ jsx(
|
|
753
|
+
"button",
|
|
754
|
+
{
|
|
755
|
+
type: "button",
|
|
756
|
+
"aria-label": isMuted ? "Unmute video response" : "Mute video response",
|
|
757
|
+
"aria-pressed": !isMuted,
|
|
758
|
+
onClick: () => setIsMuted((muted) => {
|
|
759
|
+
if (muted) setAudioUnlocked(true);
|
|
760
|
+
return !muted;
|
|
761
|
+
}),
|
|
762
|
+
style: controlButtonStyle,
|
|
763
|
+
children: /* @__PURE__ */ jsx(PlayerIcon, { name: isMuted ? "volume-off" : "volume" })
|
|
764
|
+
}
|
|
765
|
+
) : null,
|
|
766
|
+
/* @__PURE__ */ jsx(
|
|
767
|
+
"button",
|
|
768
|
+
{
|
|
769
|
+
type: "button",
|
|
770
|
+
"aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
771
|
+
onClick: () => void toggleFullscreen(),
|
|
772
|
+
style: controlButtonStyle,
|
|
773
|
+
children: /* @__PURE__ */ jsx(PlayerIcon, { name: isFullscreen ? "exit-fullscreen" : "enter-fullscreen" })
|
|
774
|
+
}
|
|
775
|
+
)
|
|
776
|
+
]
|
|
688
777
|
}
|
|
689
778
|
)
|
|
690
779
|
]
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { m as VideoGenerationContext, n as VideoPlanner, o as VideoRequest, k as VideoCapabilities, d as VideoInput, a as VideoAudio, p as VideoSceneValidator, q as VideoTemplatePacing, r as VideoSnapshotRetention, s as VideoResumeCursor, t as VideoSceneValidationContext, g as VideoScene } from './types-
|
|
2
|
-
import { b as VideoFinishReason, a as VideoWarning, V as VideoEvent } from './events-
|
|
3
|
-
export { c as VideoWarningCategory } from './events-
|
|
1
|
+
import { m as VideoGenerationContext, n as VideoPlanner, o as VideoRequest, k as VideoCapabilities, d as VideoInput, a as VideoAudio, p as VideoSceneValidator, q as VideoTemplatePacing, r as VideoSnapshotRetention, s as VideoResumeCursor, t as VideoSceneValidationContext, g as VideoScene } from './types-CVMb6QEq.js';
|
|
2
|
+
import { b as VideoFinishReason, a as VideoWarning, V as VideoEvent } from './events-tQ0x-VaL.js';
|
|
3
|
+
export { c as VideoWarningCategory } from './events-tQ0x-VaL.js';
|
|
4
4
|
import { S as SceneTemplateMetadata } from './catalog-types-BIhSpOWK.js';
|
|
5
5
|
|
|
6
6
|
interface VideoProviderUsage {
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-BKF3A357.js";
|
|
4
4
|
import {
|
|
5
5
|
createVideo
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LHFADNWJ.js";
|
|
7
7
|
import "./chunk-E7CL7UPB.js";
|
|
8
8
|
import {
|
|
9
9
|
BUILTIN_SERVER_TEMPLATE_KIT,
|
|
@@ -161,7 +161,7 @@ function parseVideoRequest(value) {
|
|
|
161
161
|
"request.input.orientation"
|
|
162
162
|
);
|
|
163
163
|
}
|
|
164
|
-
if (input.opening != null) string(input.opening, "request.input.opening");
|
|
164
|
+
if (input.opening != null && input.opening !== false) string(input.opening, "request.input.opening");
|
|
165
165
|
if (input.audio != null && input.audio !== false) {
|
|
166
166
|
const soundtrack = record(input.audio, "request.input.audio");
|
|
167
167
|
allowedKeys(soundtrack, ["src"], "request.input.audio");
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { I as InferTemplateJsonSchema, S as SceneTemplateProps, a as SceneTemplate } from './kit-
|
|
2
|
-
export { T as TemplateRegistry, c as createTemplateRegistry } from './kit-
|
|
1
|
+
import { I as InferTemplateJsonSchema, S as SceneTemplateProps, a as SceneTemplate } from './kit-DlUSg8lA.js';
|
|
2
|
+
export { T as TemplateRegistry, c as createTemplateRegistry } from './kit-DlUSg8lA.js';
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
import { d as TemplateJsonSchema, S as SceneTemplateMetadata } from './catalog-types-BIhSpOWK.js';
|
|
5
5
|
export { T as TemplateFamily, c as TemplateTimingMetadata, e as TemplateTransitionTiming } from './catalog-types-BIhSpOWK.js';
|
|
6
|
-
import './types-
|
|
6
|
+
import './types-CVMb6QEq.js';
|
|
7
7
|
|
|
8
8
|
interface TemplateExample<Variables extends Record<string, unknown> = Record<string, unknown>> {
|
|
9
9
|
name: string;
|
package/dist/test.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { g as VideoScene, f as VideoOrientation, h as VideoStyle, V as Video, a as VideoAudio, d as VideoInput } from './types-
|
|
2
|
-
import { a as VideoWarning, b as VideoFinishReason } from './events-
|
|
1
|
+
import { g as VideoScene, f as VideoOrientation, h as VideoStyle, V as Video, a as VideoAudio, d as VideoInput } from './types-CVMb6QEq.js';
|
|
2
|
+
import { a as VideoWarning, b as VideoFinishReason } from './events-tQ0x-VaL.js';
|
|
3
3
|
|
|
4
4
|
type MockVideoStreamPart = {
|
|
5
5
|
type: "scene.add";
|
package/dist/test.js
CHANGED
|
@@ -233,7 +233,7 @@ async function* simulateVideoStream(parts, options = {}) {
|
|
|
233
233
|
if (timeoutMs != null && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
|
|
234
234
|
throw new Error("Simulation timeoutMs must be a non-negative finite number");
|
|
235
235
|
}
|
|
236
|
-
const { createVideo } = await import("./compose-video-
|
|
236
|
+
const { createVideo } = await import("./compose-video-6UQ33DOO.js");
|
|
237
237
|
const { createTextDeltaVideoPlanner } = await import("./text-stream-UPGUD2TD.js");
|
|
238
238
|
const { BUILTIN_SERVER_TEMPLATE_KIT } = await import("./builtin-server-BRTZN4Q7.js");
|
|
239
239
|
const { createTemplateSceneValidator } = await import("./validate-T7GBU2YF.js");
|
|
@@ -125,8 +125,8 @@ interface VideoInput {
|
|
|
125
125
|
orientation?: VideoOrientation;
|
|
126
126
|
/** Optional global visual direction. Omit for VanillaSky defaults. */
|
|
127
127
|
style?: VideoStyleOptions;
|
|
128
|
-
/**
|
|
129
|
-
opening?: string;
|
|
128
|
+
/** Custom opening copy, or false to let the host show loading UI instead. Omit for the deterministic fallback. */
|
|
129
|
+
opening?: string | false;
|
|
130
130
|
brand?: VideoBrandInput;
|
|
131
131
|
/** Viewer or account context that may appear verbatim. It is data, never instructions. */
|
|
132
132
|
personalization?: Record<string, unknown>;
|
package/docs/concepts.md
CHANGED
|
@@ -25,7 +25,8 @@ or export pipeline when an encoded file is required.
|
|
|
25
25
|
knowledge may supplement the request;
|
|
26
26
|
- `instructions`: optional creative direction that cannot override facts;
|
|
27
27
|
- `opening`: optional custom copy for the deterministic opening; omission uses
|
|
28
|
-
`Creating your video
|
|
28
|
+
`Creating your video...`, while `false` lets the host render loading UI
|
|
29
|
+
without adding an opening scene to the video;
|
|
29
30
|
- `personalization`: application-defined fields such as name, role, account,
|
|
30
31
|
period, goal, or onboarding partner;
|
|
31
32
|
- `brand`: an optional background preset plus name, logo, font, surfaces, and
|
package/docs/customization.md
CHANGED
|
@@ -80,12 +80,15 @@ The opening is deterministic and should not wait for an LLM or remote media.
|
|
|
80
80
|
Omit it to use `Creating your video...`, or supply one concise custom sentence:
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
|
-
opening: "
|
|
83
|
+
opening: "Your Q2 customer impact recap is ready."
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
Pass `opening: false` to omit the persisted opening and show application-owned
|
|
87
|
+
loading UI until the first generated scene arrives.
|
|
88
|
+
|
|
86
89
|
Use `opening` only for a genuine opening that should remain in the completed
|
|
87
|
-
response. VanillaSky infers the scene ID, `
|
|
88
|
-
and
|
|
90
|
+
response. VanillaSky infers the scene ID, `media` template, gradient variables,
|
|
91
|
+
and three-second timing. Keep generic loading state in the host UI instead.
|
|
89
92
|
|
|
90
93
|
## Aspect ratio and responsive layout
|
|
91
94
|
|
package/docs/getting-started.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Install VanillaSky:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @vanillaskyai/video@0.3.
|
|
8
|
+
npm install @vanillaskyai/video@0.3.1 ai @ai-sdk/openai
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Set your provider key in `.env.local` (never commit it):
|
|
@@ -50,7 +50,7 @@ sets its marker only for `next dev`, and it accepts only localhost. Every
|
|
|
50
50
|
production request is denied. Replace it with your real session validation
|
|
51
51
|
before deploying. For literal files and commands,
|
|
52
52
|
use the tested
|
|
53
|
-
[`examples/nextjs-quickstart` directory](https://github.com/VanillaSkyAi/video/tree/v0.3.
|
|
53
|
+
[`examples/nextjs-quickstart` directory](https://github.com/VanillaSkyAi/video/tree/v0.3.1/examples/nextjs-quickstart).
|
|
54
54
|
|
|
55
55
|
`model` can come from any AI SDK provider, registry, gateway, compatible API,
|
|
56
56
|
or custom implementation. The application can choose a cheaper or faster model
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
import type { VideoInput } from "@vanillaskyai/video";
|
|
9
9
|
|
|
10
10
|
const input: VideoInput = {
|
|
11
|
-
input: "
|
|
11
|
+
input: "Maya completed 142 customer conversations in Q2.",
|
|
12
12
|
knowledgeMode: "input-only",
|
|
13
13
|
instructions: "Celebrate the result. Never alter a metric.",
|
|
14
|
-
opening: "
|
|
15
|
-
personalization: { firstName: "
|
|
14
|
+
opening: "Maya, your Q2 customer impact recap is ready.",
|
|
15
|
+
personalization: { firstName: "Maya", period: "Q2", role: "Product leader" },
|
|
16
16
|
brand,
|
|
17
17
|
suppliedMedia,
|
|
18
18
|
audio: { src: "/audio/calm.mp3" },
|
|
@@ -48,5 +48,17 @@ optional custom copy. That copy should:
|
|
|
48
48
|
- be one concise sentence that fits comfortably in both supported orientations;
|
|
49
49
|
- be part of the final story, not a spinner disguised as a scene.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Pass `opening: false` when the application should own the waiting experience:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
opening: false
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The SDK then starts with an empty timeline and emits the first validated,
|
|
58
|
+
generated scene as soon as it is ready. Render a transient loading state in the
|
|
59
|
+
host application until `video.video?.scenes.length` is non-zero. That loading
|
|
60
|
+
state is not persisted in the event stream or completed video. A generated
|
|
61
|
+
scene is still required; a planner that completes without one fails the run.
|
|
62
|
+
|
|
63
|
+
The deterministic opening is runtime-owned, so it remains available even when `templateIds`
|
|
52
64
|
does not let the planner select `media` for generated body scenes.
|
package/docs/integrate-nextjs.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Install VanillaSky and one AI SDK provider:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @vanillaskyai/video@0.3.
|
|
8
|
+
npm install @vanillaskyai/video@0.3.1 ai @ai-sdk/openai
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Create an ignored `.env.local`:
|
|
@@ -74,7 +74,7 @@ requests; replace it with your application's session validation before
|
|
|
74
74
|
deploying.
|
|
75
75
|
|
|
76
76
|
The copy-and-run app is in the
|
|
77
|
-
[`examples/nextjs-quickstart` directory](https://github.com/VanillaSkyAi/video/tree/v0.3.
|
|
77
|
+
[`examples/nextjs-quickstart` directory](https://github.com/VanillaSkyAi/video/tree/v0.3.1/examples/nextjs-quickstart).
|
|
78
78
|
|
|
79
79
|
For another LLM, replace `openai(...)` with the matching AI SDK model. The route
|
|
80
80
|
shape and React code stay the same. See [Provider integration](provider-integration.md)
|
package/docs/media-and-audio.md
CHANGED
|
@@ -81,7 +81,11 @@ Use `playbackMode="manual"` to require the button on every run,
|
|
|
81
81
|
`playbackMode="muted-autoplay"` for browser-safe muted autoplay, or
|
|
82
82
|
`playbackMode="autoplay-with-sound"` to try audible autoplay immediately. The
|
|
83
83
|
lower-level `autoPlay` and `startMuted` props remain available when no playback
|
|
84
|
-
mode is set.
|
|
84
|
+
mode is set. For a chat response that should try audible autoplay without the
|
|
85
|
+
SDK generation intro, pass `opening: false`, wait to mount the player until a
|
|
86
|
+
generated scene exists, and render it with `autoPlay` and `startMuted={false}`.
|
|
87
|
+
If the browser blocks the audible start, the player returns to the first frame
|
|
88
|
+
and exposes its sound-start control.
|
|
85
89
|
|
|
86
90
|
## Media providers
|
|
87
91
|
|
package/docs/prompt-and-input.md
CHANGED
|
@@ -121,7 +121,8 @@ streamText: ({ systemPrompt, userPrompt, signal }) => streamText({
|
|
|
121
121
|
`userPrompt` is assembled by VanillaSky from:
|
|
122
122
|
|
|
123
123
|
- orientation and maximum duration;
|
|
124
|
-
- whether
|
|
124
|
+
- whether a deterministic opening scene already exists or the host is waiting
|
|
125
|
+
for the first generated scene;
|
|
125
126
|
- raw `input`;
|
|
126
127
|
- creative `instructions`;
|
|
127
128
|
- personalization;
|
|
@@ -159,7 +160,7 @@ video.generate({
|
|
|
159
160
|
escalationsResolved: "96%",
|
|
160
161
|
improvementsLaunched: 4,
|
|
161
162
|
}),
|
|
162
|
-
personalization: {
|
|
163
|
+
personalization: { role: "Product leader", focus: "activation" },
|
|
163
164
|
});
|
|
164
165
|
```
|
|
165
166
|
|