@vanillaskyai/video 0.6.0 → 0.7.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.
Files changed (130) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/PUBLIC-API.md +123 -9
  3. package/README.md +69 -115
  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-G4XOQEAS.js → bg-media-L34PDQXJ.js} +2 -2
  7. package/dist/{brand-message-XHONEDNW.js → brand-message-FUJ4STFO.js} +1 -1
  8. package/dist/{builtin-server-YHEZ2JRF.js → builtin-server-KB7FKF6A.js} +2 -2
  9. package/dist/{chart-bar-HRHD57ML.js → chart-bar-EGPHJATL.js} +2 -2
  10. package/dist/{chart-counter-KFPV44RB.js → chart-counter-5FFWQNZO.js} +2 -2
  11. package/dist/{chart-progress-ring-OR2VOIHV.js → chart-progress-ring-JMXQLMLC.js} +2 -2
  12. package/dist/{chunk-66MNRUCR.js → chunk-2PYO6VAC.js} +51 -3
  13. package/dist/{chunk-ZQQQAAKP.js → chunk-2XT4MZ76.js} +33 -2
  14. package/dist/{chunk-PNS52FL4.js → chunk-5SLENAJW.js} +19 -6
  15. package/dist/{chunk-EFL34TXF.js → chunk-AITKH6QT.js} +24 -8
  16. package/dist/{chunk-FZHMQFG3.js → chunk-GAIOHGNR.js} +5 -21
  17. package/dist/{chunk-3RV4YKB3.js → chunk-JNN3EYVP.js} +51 -26
  18. package/dist/{chunk-HVCPEAQF.js → chunk-LKBZX7GV.js} +9 -6
  19. package/dist/{chunk-34O5BY6X.js → chunk-LLPMVDJZ.js} +21 -4
  20. package/dist/{chunk-G66Z5CWR.js → chunk-MMUXVA47.js} +27 -3
  21. package/dist/{chunk-7BGAU6C3.js → chunk-MXZSDGZQ.js} +2 -1
  22. package/dist/{chunk-3OU7HIEB.js → chunk-RE4IMWJR.js} +15 -31
  23. package/dist/{chunk-FVTMYS6U.js → chunk-RGF452LL.js} +1 -1
  24. package/dist/chunk-SKRGRKHY.js +142 -0
  25. package/dist/{chunk-TCEHK2BW.js → chunk-YJJC4N4D.js} +2 -1
  26. package/dist/cli.js +438 -30
  27. package/dist/{compose-video-JWIEDTEJ.js → compose-video-PD6LKKRK.js} +4 -4
  28. package/dist/{cta-logo-JJOGK4UC.js → cta-logo-YR7LYUR5.js} +1 -1
  29. package/dist/{cta-media-IQ3MAWEH.js → cta-media-DQJBKUO2.js} +2 -2
  30. package/dist/{events-BBP30j3c.d.ts → events-B6qS1Lsb.d.ts} +2 -2
  31. package/dist/{incoming-call-KXF67OT2.js → incoming-call-54KQ7O5A.js} +1 -1
  32. package/dist/index.d.ts +89 -3
  33. package/dist/index.js +12 -1
  34. package/dist/{infographic-before-after-2I7YZVDD.js → infographic-before-after-ROS52GOW.js} +1 -1
  35. package/dist/{infographic-feature-list-QDM4I7XE.js → infographic-feature-list-E7MGUDAN.js} +2 -2
  36. package/dist/{infographic-problem-solution-FMLJCPG6.js → infographic-problem-solution-3S6NGO5N.js} +2 -2
  37. package/dist/{infographic-stat-row-VFTIXS5F.js → infographic-stat-row-SYB7AB7H.js} +2 -2
  38. package/dist/{infographic-steps-L7W24Z35.js → infographic-steps-KCE6JWE2.js} +2 -2
  39. package/dist/{kit-DEG3fcaL.d.ts → kit-8g46H2RZ.d.ts} +1 -1
  40. package/dist/{prompt-input-F3CYU2XH.js → prompt-input-MHX4O42G.js} +1 -1
  41. package/dist/react.d.ts +194 -6
  42. package/dist/react.js +1849 -40
  43. package/dist/{reaction-2XCLOJOK.js → reaction-SLEGR3BE.js} +2 -2
  44. package/dist/server.d.ts +102 -4
  45. package/dist/server.js +972 -27
  46. package/dist/{showcase-code-KHSMIPG6.js → showcase-code-2HLAVKBM.js} +2 -2
  47. package/dist/{showcase-phone-M4CEANIW.js → showcase-phone-Q4HEO4LH.js} +2 -2
  48. package/dist/{showcase-terminal-26I3DYF7.js → showcase-terminal-DL45LUFG.js} +2 -2
  49. package/dist/{showcase-web-TY6CZV2Z.js → showcase-web-2QZO73FZ.js} +2 -2
  50. package/dist/{social-milestone-TF6MX7ET.js → social-milestone-GEFUQSBF.js} +1 -1
  51. package/dist/{social-notification-2RJDAZSF.js → social-notification-FJ5VHVLK.js} +1 -1
  52. package/dist/{social-review-stack-2ICKJQG7.js → social-review-stack-KXQCKFRP.js} +1 -1
  53. package/dist/{social-testimonial-X7OGADA3.js → social-testimonial-JZ7CHC7S.js} +1 -1
  54. package/dist/{social-tweet-TK7SH2F6.js → social-tweet-UCDXM25F.js} +1 -1
  55. package/dist/{system-prompt-4I6Z5HK3.js → system-prompt-RRXIWDDD.js} +5 -3
  56. package/dist/template-catalog.js +1 -1
  57. package/dist/templates.d.ts +3 -3
  58. package/dist/test.d.ts +2 -2
  59. package/dist/test.js +3 -3
  60. package/dist/{text-stream-J6EDDJP4.js → text-stream-ROTNGSD4.js} +2 -2
  61. package/dist/{types-BV9IqExh.d.ts → types-2wHBqtg8.d.ts} +29 -1
  62. package/dist/types-DrABlRa7.d.ts +46 -0
  63. package/docs/agent-integration.md +49 -25
  64. package/docs/architecture.md +37 -22
  65. package/docs/branding-and-personalization.md +1 -1
  66. package/docs/concepts.md +14 -0
  67. package/docs/custom-templates.md +9 -9
  68. package/docs/customization.md +41 -5
  69. package/docs/getting-started.md +81 -84
  70. package/docs/media-and-audio.md +103 -166
  71. package/docs/persistence.md +1 -1
  72. package/docs/production.md +81 -92
  73. package/docs/prompt-and-input.md +71 -161
  74. package/docs/provider-integration.md +91 -49
  75. package/docs/responsive-orientation.md +1 -1
  76. package/docs/security.md +6 -5
  77. package/docs/streaming-protocol.md +1 -1
  78. package/docs/testing.md +51 -43
  79. package/examples/custom-template/README.md +1 -1
  80. package/package.json +20 -22
  81. package/registry/items/barChart.json +6 -4
  82. package/registry/items/beforeAfter.json +1 -1
  83. package/registry/items/bigNumber.json +4 -3
  84. package/registry/items/brandMessage.json +3 -2
  85. package/registry/items/cardList.json +6 -4
  86. package/registry/items/codeEditor.json +4 -3
  87. package/registry/items/confetti.json +1 -1
  88. package/registry/items/ctaLogo.json +3 -2
  89. package/registry/items/ctaMedia.json +4 -3
  90. package/registry/items/emojiBurst.json +1 -1
  91. package/registry/items/incomingCall.json +3 -2
  92. package/registry/items/media.json +4 -3
  93. package/registry/items/milestone.json +3 -2
  94. package/registry/items/notification.json +3 -2
  95. package/registry/items/phoneMockup.json +4 -3
  96. package/registry/items/problemSolution.json +4 -3
  97. package/registry/items/progressRing.json +4 -3
  98. package/registry/items/promptInput.json +3 -2
  99. package/registry/items/reaction.json +2 -2
  100. package/registry/items/reviewStack.json +3 -2
  101. package/registry/items/steps.json +6 -4
  102. package/registry/items/terminal.json +5 -4
  103. package/registry/items/testimonial.json +3 -2
  104. package/registry/items/tripleStats.json +4 -3
  105. package/registry/items/tweet.json +3 -2
  106. package/registry/items/webMockup.json +4 -3
  107. package/starters/video-chat/.env.example +16 -0
  108. package/starters/video-chat/README.md +63 -0
  109. package/starters/video-chat/index.html +12 -0
  110. package/starters/video-chat/package.json +28 -0
  111. package/starters/video-chat/server.ts +152 -0
  112. package/starters/video-chat/src/main.tsx +8 -0
  113. package/starters/video-chat/stock.ts +139 -0
  114. package/starters/video-chat/tsconfig.json +20 -0
  115. package/starters/video-chat/vite.config.ts +81 -0
  116. package/styles/video-chat.css +713 -0
  117. package/docs/input-and-first-scene.md +0 -64
  118. package/docs/integrate-nextjs.md +0 -86
  119. package/docs/live-channels.md +0 -149
  120. package/docs/use-cases.md +0 -59
  121. package/examples/nextjs-quickstart/.env.example +0 -2
  122. package/examples/nextjs-quickstart/README.md +0 -27
  123. package/examples/nextjs-quickstart/next-env.d.ts +0 -4
  124. package/examples/nextjs-quickstart/package.json +0 -25
  125. package/examples/nextjs-quickstart/src/app/api/video/route.ts +0 -33
  126. package/examples/nextjs-quickstart/src/app/layout.tsx +0 -5
  127. package/examples/nextjs-quickstart/src/app/page.tsx +0 -31
  128. package/examples/nextjs-quickstart/tsconfig.json +0 -26
  129. package/examples/server-integrations/README.md +0 -20
  130. package/examples/server-integrations/src/ai-sdk-media.ts +0 -90
@@ -1,64 +0,0 @@
1
- [← Documentation home](../README.md) · [Previous: Provider integration](provider-integration.md) · [Next: Branding and personalization →](branding-and-personalization.md)
2
-
3
- # Raw input and the opening
4
-
5
- `VideoInput` is the small boundary between application truth and creative planning.
6
-
7
- ```ts
8
- import type { VideoInput } from "@vanillaskyai/video";
9
-
10
- const input: VideoInput = {
11
- input: "Maya completed 142 customer conversations in Q2.",
12
- knowledgeMode: "input-only",
13
- instructions: "Celebrate the result. Never alter a metric.",
14
- opening: "Maya, your Q2 customer impact recap is ready.",
15
- personalization: { firstName: "Maya", period: "Q2", role: "Product leader" },
16
- brand,
17
- suppliedMedia,
18
- audio: { src: "/audio/calm.mp3" },
19
- orientation: "portrait",
20
- maxDurationSec: 24,
21
- };
22
- ```
23
-
24
- ## Raw source
25
-
26
- In the default `input-only` mode, put every fact the response may claim in
27
- `input`. Use plain text, compact JSON, or a server-produced digest. Include
28
- units, periods, comparison bases, and provenance identifiers where ambiguity
29
- is possible. Choose `knowledgeMode: "general"` only when the model should use
30
- stable general knowledge to answer a question or develop content. Creative
31
- direction in `instructions` never changes that setting.
32
-
33
- Bound request bytes and reject secret-shaped fields on the server. Do not pass provider keys, authorization headers, internal prompt fragments, or storage credentials as source material.
34
-
35
- ## Opening
36
-
37
- `opening` becomes a deterministic `media` scene emitted before provider work.
38
- When callers omit it, the SDK uses `Creating your video...`; a supplied value
39
- replaces that fallback. The scene contains only that sentence and explicitly
40
- uses the brand gradient without stock media. The SDK requests a three-second
41
- opening, then applies its normal readability and overall-duration budget. It
42
- owns the scene ID, template, variables, and timing so callers only provide
43
- optional custom copy. That copy should:
44
-
45
- - be personal or situational enough to feel intentional;
46
- - require no network media lookup;
47
- - remain true if generation later fails;
48
- - be one concise sentence that fits comfortably in both supported orientations;
49
- - be part of the final story, not a spinner disguised as a scene.
50
-
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`
64
- does not let the planner select `media` for generated body scenes.
@@ -1,86 +0,0 @@
1
- [← Documentation home](../README.md) · [Previous: Getting started](getting-started.md) · [Next: Provider integration →](provider-integration.md)
2
-
3
- # Next.js integration
4
-
5
- After installing VanillaSky, choose the provider and model for your server. This
6
- complete example uses Anthropic and asks you to select a current Claude Sonnet
7
- model explicitly instead of baking a model ID into the SDK docs:
8
-
9
- ```bash
10
- npm install @vanillaskyai/video ai @ai-sdk/anthropic
11
- ```
12
-
13
- Create an ignored `.env.local`:
14
-
15
- ```bash
16
- ANTHROPIC_API_KEY=your-key
17
- ANTHROPIC_MODEL=your-current-sonnet-model
18
- ```
19
-
20
- Create `app/api/video/route.ts`:
21
-
22
- ```ts
23
- import { anthropic } from "@ai-sdk/anthropic";
24
- import { streamText } from "ai";
25
- import { createVideoHandler } from "@vanillaskyai/video/server";
26
-
27
- const modelId = process.env.ANTHROPIC_MODEL;
28
- if (!modelId) throw new Error("Set ANTHROPIC_MODEL in the server environment");
29
- const model = anthropic(modelId);
30
-
31
- const handle = createVideoHandler({
32
- // Local development only. Replace with your session check before deploying.
33
- authorize: (request) => {
34
- if (process.env.VANILLASKY_LOCAL_DEMO !== "1") return false;
35
- const hostname = new URL(request.url).hostname;
36
- return hostname === "localhost" || hostname === "127.0.0.1";
37
- },
38
- streamText: ({ systemPrompt, userPrompt, signal }) => streamText({
39
- model,
40
- system: systemPrompt,
41
- prompt: userPrompt,
42
- abortSignal: signal,
43
- }),
44
- });
45
-
46
- export const POST = handle;
47
- export const OPTIONS = handle;
48
- ```
49
-
50
- The packaged `npm run dev` command supplies this non-secret marker only to
51
- `next dev`. Production builds and `next start` never receive it.
52
-
53
- Create a Client Component:
54
-
55
- ```tsx
56
- "use client";
57
-
58
- import { VideoPlayer, useVideo } from "@vanillaskyai/video/react";
59
-
60
- export function Video() {
61
- const video = useVideo();
62
- return <>
63
- <button onClick={() => { void video.generate({
64
- input: "Activation increased from 41% to 58%.",
65
- personalization: { firstName: "Maya" },
66
- }); }}>
67
- Generate
68
- </button>
69
- {video.error && <p role="alert">Could not generate the video.</p>}
70
- <VideoPlayer {...video.playerProps} />
71
- </>;
72
- }
73
- ```
74
-
75
- That is the complete first path. Built-in templates need no registry setup.
76
- The development authorization accepts only local requests and denies production
77
- requests; replace it with your application's session validation before
78
- deploying.
79
-
80
- The copy-and-run app is in the
81
- [`examples/nextjs-quickstart` directory](../examples/nextjs-quickstart).
82
-
83
- For another LLM, replace `anthropic(...)` with the matching AI SDK model. The route
84
- shape and React code stay the same. See [Provider integration](provider-integration.md)
85
- for adapters, authentication, diagnostics, and production controls. Add media,
86
- audio, persistence, or custom templates only after the default path works.
@@ -1,149 +0,0 @@
1
- [← Documentation home](../README.md) · [Previous: Persistence and replay](persistence.md) · [Next: Streaming protocol →](streaming-protocol.md)
2
-
3
- # Live channels
4
-
5
- A live channel is a video that plays continuously and rebuilds itself on a
6
- schedule: a market ticker, a news wall, a status board in an office, a loop
7
- behind a conference stand. Nobody presses play, nobody watches from the start,
8
- and the content changes on its own.
9
-
10
- This is a different shape from a generated response. A response is produced for
11
- one person, once, in reply to something they asked. A channel is produced ahead
12
- of time for nobody in particular, and then played to whoever is looking.
13
-
14
- That difference is what makes it cheap. Playback of a saved `Video` performs no
15
- generation request and no model call — the player is DOM and CSS advanced by a
16
- `requestAnimationFrame` clock — so a channel that loops all day costs exactly as
17
- much as a channel that loops once. All of the cost lives in building the
18
- configuration, which happens on your schedule rather than per viewer.
19
-
20
- ## The shape
21
-
22
- ```
23
- scheduled job browser
24
- ───────────── ───────
25
- fetch your data ┌────────► fetch the stored Video
26
- map it to scenes │ parseVideo(...)
27
- parseVideo(...) ────────┘ <VideoPlayer video={...} loop />
28
- store the JSON plays forever
29
- ```
30
-
31
- The job owns the data and the mapping. The player owns nothing but playback.
32
-
33
- ## Play without stopping
34
-
35
- Pass `loop` alongside a saved video. The player restarts from the beginning
36
- instead of showing its replay affordance and loops its soundtrack continuously.
37
- A track shorter than the channel repeats as soon as it ends; when the visual
38
- timeline wraps, the soundtrack restarts from the beginning with it.
39
-
40
- <!-- verify:live-channel-example:start -->
41
- ```tsx
42
- import { useEffect, useState } from "react";
43
- import { parseVideo, type Video, type VideoScene } from "@vanillaskyai/video";
44
- import { VideoPlayer } from "@vanillaskyai/video/react";
45
-
46
- export function Channel({ endpoint }: { endpoint: string }) {
47
- const [video, setVideo] = useState<Video | undefined>(undefined);
48
- const [onScreen, setOnScreen] = useState<VideoScene | undefined>(undefined);
49
-
50
- const load = async () => {
51
- const stored: unknown = await fetch(endpoint).then((response) => response.json());
52
- setVideo(parseVideo(stored));
53
- };
54
-
55
- useEffect(() => { void load(); }, []);
56
- if (!video) return null;
57
-
58
- return <>
59
- <VideoPlayer
60
- video={video}
61
- loop
62
- autoPlay
63
- playbackMode="muted-autoplay"
64
- onSceneChange={(scene, index) => {
65
- setOnScreen(scene);
66
- // Wrapping back to the start is a natural moment to pick up a rebuild,
67
- // so a screen left running all day never serves a stale channel.
68
- if (index === 0) void load();
69
- }}
70
- />
71
- <p>{onScreen ? onScreen.templateId : "starting"}</p>
72
- </>;
73
- }
74
- ```
75
- <!-- verify:live-channel-example:end -->
76
-
77
- `loop` applies to saved videos. A streaming response still ends when the stream
78
- ends, because there is nothing to loop back to until it completes.
79
-
80
- ## Follow what is on screen
81
-
82
- `onSceneChange` fires whenever the scene under the playhead changes, and again
83
- on index `0` each time a loop wraps. Use it to drive anything that has to stay
84
- in step with the video: a caption rail, a source panel, chapter markers, an
85
- analytics event per scene.
86
-
87
- Do not run your own timer for this. A parallel clock drifts from the player's
88
- as soon as playback is paused, throttled in a background tab, or delayed by a
89
- slow asset, and the drift is silent.
90
-
91
- `onComplete` is not an alternative here: it reports the end of a *stream*, so it
92
- never fires for a saved video.
93
-
94
- ## Build the configuration yourself
95
-
96
- A channel's scenes usually come from your own data rather than from a model.
97
- `resolveVideoBrand` fills a partial brand with the documented defaults, so a
98
- hand-authored `Video` satisfies `parseVideo` without copying a defaults blob
99
- into your application:
100
-
101
- ```ts
102
- import { parseVideo, resolveVideoBrand } from "@vanillaskyai/video";
103
-
104
- const channel = parseVideo({
105
- schemaVersion: "0.1",
106
- orientation: "landscape",
107
- scenes: rows.map((row, index) => ({
108
- id: `row-${index}`,
109
- templateId: "bigNumber",
110
- variables: { texts: row.label, value: row.value, unit: row.unit },
111
- timing: { fixedDuration: 5 },
112
- })),
113
- style: { brand: resolveVideoBrand({ name: "Acme", background: "midnight" }) },
114
- });
115
- ```
116
-
117
- Validate with `parseVideo` inside the job, before storing. A channel that fails
118
- validation in the browser is a channel nobody can watch; one that fails in the
119
- job can simply leave the previous version in place.
120
-
121
- ## Keep motion independent of scene length
122
-
123
- Templates receive `sceneDuration`. Express motion in seconds against it rather
124
- than as a fraction of `progress`, or lengthening a scene to give viewers more
125
- reading time will stretch its animation instead and give them none.
126
-
127
- ## Practical notes
128
-
129
- - **Rebuild on a schedule, not per request.** Viewers should read a stored
130
- configuration. If every viewer triggers generation, a channel becomes the most
131
- expensive surface you own rather than the cheapest.
132
- - **Never replace a good channel with a broken one.** Validate first, and on
133
- failure leave the previous configuration in place.
134
- - **Autoplay needs `playbackMode="muted-autoplay"`.** Browsers refuse unmuted
135
- autoplay. A channel can still carry a looping soundtrack: it starts muted and
136
- becomes audible when a viewer uses the player's sound control.
137
- - **Host your media.** Loops re-request assets over long sessions; serving them
138
- from your own origin keeps a content policy simple and avoids depending on
139
- someone else's CDN.
140
- - **Long channels are ordinary videos.** Ten minutes of scenes is one `Video`
141
- with more entries in `scenes`; there is no separate playlist concept.
142
-
143
- ## Related
144
-
145
- - [Persistence and replay](persistence.md) — the storage boundary these
146
- configurations pass through
147
- - [Custom templates](custom-templates.md) — templates for your own data shapes
148
- - [Responsive orientation](responsive-orientation.md) — the same channel on a
149
- portrait screen
package/docs/use-cases.md DELETED
@@ -1,59 +0,0 @@
1
- [← Documentation home](../README.md)
2
-
3
- # Video response use cases
4
-
5
- These four inputs show the primary product shape: AI answers and live
6
- application context become embedded video responses. The application decides
7
- which context matters and keeps it current. VanillaSky grounds, validates,
8
- composes, and plays the visual response.
9
-
10
- ## News summary
11
-
12
- ```ts
13
- video.generate({
14
- input: `Headline: North Sea wind output reached a record on August 17.
15
- Source summary: Grid operators reported 18.2 GW at 14:00 CET, 12% above the
16
- previous record. Operators said lower demand helped reduce wholesale prices.`,
17
- instructions: "Lead with the record, explain the comparison, then the impact.",
18
- maxDurationSec: 24,
19
- });
20
- ```
21
-
22
- ## Contextual help
23
-
24
- ```ts
25
- video.generate({
26
- input: `The user is configuring an alert. The current threshold is 80%.
27
- Alerts evaluate every 15 minutes. Email and Slack are enabled. The Save button
28
- applies the rule to 42 selected accounts.`,
29
- instructions: "Explain what will happen if the user saves this configuration.",
30
- opening: "Here is what this alert will do.",
31
- maxDurationSec: 24,
32
- });
33
- ```
34
-
35
- ## Personalized briefing
36
-
37
- ```ts
38
- video.generate({
39
- input: `The Acme account has 1,240 weekly active users, up 18% month over
40
- month. Three renewals are due in September. Support response time improved from
41
- 4.2 hours to 2.8 hours.`,
42
- personalization: { firstName: "Maya", accountName: "Acme" },
43
- instructions: "Create a concise morning account briefing.",
44
- maxDurationSec: 30,
45
- });
46
- ```
47
-
48
- ## Dynamic personalized page content
49
-
50
- ```ts
51
- video.generate({
52
- input: `The page shows the Q2 onboarding funnel. 8,420 users started setup,
53
- 6,315 connected a data source, and 4,884 invited a teammate. The largest drop
54
- is between connecting a data source and inviting a teammate.`,
55
- instructions: "Respond to the visible funnel and focus on the largest drop.",
56
- orientation: "landscape",
57
- maxDurationSec: 24,
58
- });
59
- ```
@@ -1,2 +0,0 @@
1
- ANTHROPIC_API_KEY=replace-me
2
- ANTHROPIC_MODEL=replace-with-current-sonnet-model
@@ -1,27 +0,0 @@
1
- # Next.js quickstart
2
-
3
- This is the smallest complete VanillaSky app: one server route, one React page,
4
- and the built-in templates.
5
-
6
- <!-- verify:start -->
7
- ```bash
8
- npm install
9
- cp .env.example .env.local
10
- npm run build
11
- npm run dev
12
- ```
13
- <!-- verify:end -->
14
-
15
- Before selecting **Generate video**, add your Anthropic API key and replace the
16
- model placeholder with a current Claude Sonnet model ID available to your
17
- account. Provider and model selection happen here, after SDK installation, so
18
- VanillaSky never silently chooses either one.
19
-
20
- Open <http://localhost:3000> and select **Generate video**.
21
-
22
- The packaged development command supplies a non-secret marker only to
23
- `next dev`, so the example authorizes local development and denies production
24
- requests.
25
- Replace the local-only authorization before deploying. See the
26
- [Next.js guide](../../docs/integrate-nextjs.md) for production and optional
27
- configuration.
@@ -1,4 +0,0 @@
1
- /// <reference types="next" />
2
- /// <reference types="next/image-types/global" />
3
-
4
- // This file is generated and maintained by Next.js.
@@ -1,25 +0,0 @@
1
- {
2
- "name": "vanillasky-nextjs-quickstart",
3
- "private": true,
4
- "version": "0.0.0",
5
- "scripts": {
6
- "dev": "cross-env VANILLASKY_LOCAL_DEMO=1 next dev",
7
- "build": "next build",
8
- "start": "next start"
9
- },
10
- "dependencies": {
11
- "@ai-sdk/anthropic": "^4.0.41",
12
- "@vanillaskyai/video": "0.6.0",
13
- "ai": "^7.0.77",
14
- "next": "16.3.2",
15
- "react": "19.2.8",
16
- "react-dom": "19.2.8"
17
- },
18
- "devDependencies": {
19
- "@types/node": "^24.10.1",
20
- "@types/react": "^19",
21
- "@types/react-dom": "^19",
22
- "cross-env": "10.1.0",
23
- "typescript": "^5.9.3"
24
- }
25
- }
@@ -1,33 +0,0 @@
1
- import { anthropic } from "@ai-sdk/anthropic";
2
- import { streamText } from "ai";
3
- import { createVideoHandler } from "@vanillaskyai/video/server";
4
-
5
- // Resolved per request so the app still builds with the shipped placeholder.
6
- function resolveModel() {
7
- const modelId = process.env.ANTHROPIC_MODEL;
8
- if (!modelId || modelId === "replace-with-current-sonnet-model") {
9
- throw new Error(
10
- "Set ANTHROPIC_MODEL in .env.local to a current Claude Sonnet model ID available to your " +
11
- "account. The shipped value is a placeholder: VanillaSky never selects a provider or " +
12
- "model for you.",
13
- );
14
- }
15
- return anthropic(modelId);
16
- }
17
-
18
- const handle = createVideoHandler({
19
- authorize: (request) => {
20
- if (process.env.VANILLASKY_LOCAL_DEMO !== "1") return false;
21
- const hostname = new URL(request.url).hostname;
22
- return hostname === "localhost" || hostname === "127.0.0.1";
23
- },
24
- streamText: ({ systemPrompt, userPrompt, signal }) => streamText({
25
- model: resolveModel(),
26
- system: systemPrompt,
27
- prompt: userPrompt,
28
- abortSignal: signal,
29
- }),
30
- });
31
-
32
- export const POST = handle;
33
- export const OPTIONS = handle;
@@ -1,5 +0,0 @@
1
- import type { ReactNode } from "react";
2
-
3
- export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
4
- return <html lang="en"><body>{children}</body></html>;
5
- }
@@ -1,31 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { VideoPlayer, useVideo } from "@vanillaskyai/video/react";
5
-
6
- export default function Page() {
7
- const [input, setInput] = useState(
8
- "Activation increased from 41% to 58% after guided onboarding.",
9
- );
10
- const video = useVideo();
11
-
12
- return <main>
13
- <h1>VanillaSky quickstart</h1>
14
- <label>
15
- Grounded input
16
- <textarea value={input} onChange={(event) => setInput(event.target.value)} />
17
- </label>
18
- <button
19
- disabled={!input.trim() || video.status === "streaming"}
20
- onClick={() => { void video.generate({
21
- input,
22
- personalization: { firstName: "Maya" },
23
- }); }}
24
- >
25
- Generate video
26
- </button>
27
- <output data-testid="status">{video.status}</output>
28
- {video.error && <p role="alert">{video.error.message}</p>}
29
- <VideoPlayer {...video.playerProps} />
30
- </main>;
31
- }
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2017",
4
- "lib": ["dom", "dom.iterable", "esnext"],
5
- "allowJs": true,
6
- "skipLibCheck": true,
7
- "strict": true,
8
- "noEmit": true,
9
- "incremental": true,
10
- "esModuleInterop": true,
11
- "module": "esnext",
12
- "moduleResolution": "bundler",
13
- "resolveJsonModule": true,
14
- "isolatedModules": true,
15
- "jsx": "react-jsx",
16
- "plugins": [{ "name": "next" }]
17
- },
18
- "include": [
19
- "next-env.d.ts",
20
- "**/*.ts",
21
- "**/*.tsx",
22
- ".next/types/**/*.ts",
23
- ".next/dev/types/**/*.ts"
24
- ],
25
- "exclude": ["node_modules"]
26
- }
@@ -1,20 +0,0 @@
1
- # Server examples
2
-
3
- The normal example uses the AI SDK's `streamText`, so any AI SDK `LanguageModel`
4
- works without VanillaSky owning the provider, model, or credentials. Replace
5
- `openai(modelId)` in `src/ai-sdk.ts` with an official provider, community
6
- provider, AI Gateway model, OpenAI-compatible provider, or custom model.
7
-
8
- `src/provider-neutral.ts` and `src/openai.ts` show the lower-level text-delta
9
- escape hatch for applications that need to own the native provider loop.
10
-
11
- <!-- verify:start -->
12
- ```bash
13
- npm install
14
- npm run typecheck
15
- npm run ai-sdk:compat
16
- ```
17
- <!-- verify:end -->
18
-
19
- Use the returned Web `Request` → `Response` handler directly in Next.js, Hono,
20
- Remix, Cloudflare Workers, or another standards-based server runtime.
@@ -1,90 +0,0 @@
1
- // App-owned media generation for VanillaSky scenes.
2
- //
3
- // VanillaSky never generates media and never selects a provider. It calls
4
- // `resolveMedia` with a bounded query and the scene being resolved; the
5
- // application decides what to generate, where to store it, and what it costs.
6
- //
7
- // This adapter is provider-neutral: it takes AI SDK model instances rather
8
- // than naming a vendor, so the same code runs against any image or video
9
- // model the AI SDK supports. Choose the provider where you build the models,
10
- // not here.
11
- import { experimental_generateVideo, generateImage, type ImageModel } from "ai";
12
- import type { MediaResolver, MediaResolverContext, ResolvedMedia } from "@vanillaskyai/video/server";
13
-
14
- // `ai` declares its video model and provider-option types internally without
15
- // exporting them, so derive them from the call signatures instead of naming
16
- // them. This keeps the adapter correct across AI SDK releases.
17
- type VideoModel = Parameters<typeof experimental_generateVideo>[0]["model"];
18
- type ProviderOptions = Parameters<typeof generateImage>[0]["providerOptions"];
19
-
20
- /** Persist generated bytes and return a URL the player can load. */
21
- export type StoreGeneratedMedia = (media: {
22
- data: Uint8Array;
23
- mediaType: string;
24
- kind: "image" | "video";
25
- /** Stable per scene, so a retried request reuses the stored object. */
26
- idempotencyKey: string;
27
- }) => Promise<{ url: string }>;
28
-
29
- export interface GeneratedMediaOptions {
30
- imageModel: ImageModel;
31
- /** Omit to resolve every scene as an image. */
32
- videoModel?: VideoModel;
33
- store: StoreGeneratedMedia;
34
- /** Provider-specific settings, passed straight through to the AI SDK. */
35
- providerOptions?: ProviderOptions;
36
- }
37
-
38
- function aspectRatio(context: MediaResolverContext): "16:9" | "9:16" {
39
- return context.input.orientation === "portrait" ? "9:16" : "16:9";
40
- }
41
-
42
- // One generated asset per scene, keyed so a retry of the same request cannot
43
- // be billed twice.
44
- function idempotencyKey(context: MediaResolverContext): string {
45
- return `${context.requestId}:${context.scene.id}`;
46
- }
47
-
48
- export function createGeneratedMediaResolver(options: GeneratedMediaOptions): MediaResolver {
49
- return async (query, context): Promise<ResolvedMedia | null> => {
50
- const wantsVideo = context.preferredType === "video" && options.videoModel !== undefined;
51
-
52
- if (wantsVideo && options.videoModel) {
53
- const result = await experimental_generateVideo({
54
- model: options.videoModel,
55
- prompt: query,
56
- aspectRatio: aspectRatio(context),
57
- // Generated media is billable. A silent retry can produce a second
58
- // charge for one scene, so retries stay an explicit, budget-aware
59
- // decision in the application rather than an SDK default.
60
- maxRetries: 0,
61
- abortSignal: context.signal,
62
- providerOptions: options.providerOptions,
63
- });
64
- const stored = await options.store({
65
- data: result.video.uint8Array,
66
- mediaType: result.video.mediaType,
67
- kind: "video",
68
- idempotencyKey: idempotencyKey(context),
69
- });
70
- return { url: stored.url, type: "video" };
71
- }
72
-
73
- const result = await generateImage({
74
- model: options.imageModel,
75
- prompt: query,
76
- aspectRatio: aspectRatio(context),
77
- n: 1,
78
- maxRetries: 0,
79
- abortSignal: context.signal,
80
- providerOptions: options.providerOptions,
81
- });
82
- const stored = await options.store({
83
- data: result.image.uint8Array,
84
- mediaType: result.image.mediaType,
85
- kind: "image",
86
- idempotencyKey: idempotencyKey(context),
87
- });
88
- return { url: stored.url, type: "image" };
89
- };
90
- }