@vanillaskyai/video 0.10.23 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/PUBLIC-API.md +45 -438
- package/README.md +53 -97
- package/dist/builtin-metadata-OT6V7TB4.js +8 -0
- package/dist/{chapter-title-2JVDU62E.js → chapter-title-2RCXX7SN.js} +1 -2
- package/dist/{chunk-PDQFQIQW.js → chunk-3EQ6PVWL.js} +1 -1
- package/dist/chunk-4G4JBMCM.js +37 -0
- package/dist/{chunk-7AA2JWHZ.js → chunk-5DOQTIMD.js} +6 -6
- package/dist/chunk-666HGTVZ.js +41 -0
- package/dist/{chunk-7M56IUUX.js → chunk-6Z3ID54H.js} +0 -18
- package/dist/{chunk-5BSJLT6H.js → chunk-E7FHBLAC.js} +16 -29
- package/dist/chunk-HIQM4J3U.js +30 -0
- package/dist/chunk-ISWQ6T5X.js +36 -0
- package/dist/{chunk-224QNWRA.js → chunk-K5J7ESRO.js} +1 -10
- package/dist/chunk-KMVRBUL5.js +50 -0
- package/dist/chunk-NM4CXXZY.js +25 -0
- package/dist/{chunk-44WND2VP.js → chunk-V2CP7PVY.js} +19 -63
- package/dist/{chunk-3O7OMMMF.js → chunk-WZESNEPT.js} +0 -1
- package/dist/{chunk-RXTN2CW6.js → chunk-Z3DLSLAJ.js} +1 -1
- package/dist/cinema-media-YZ2UANJG.js +365 -0
- package/dist/cli.js +154 -1748
- package/dist/{compose-video-BH5K6XWT.js → compose-video-H2YUQDUY.js} +4 -4
- package/dist/{events-B4YCc4vc.d.ts → events-BZAfl0Dm.d.ts} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -6
- package/dist/preload-media-23JXUFCF.js +77 -0
- package/dist/react.d.ts +39 -15
- package/dist/react.js +1350 -739
- package/dist/scene-validation-SGLLLFY5.js +9 -0
- package/dist/server.d.ts +67 -117
- package/dist/server.js +262 -614
- package/dist/test.d.ts +2 -2
- package/dist/test.js +21 -21
- package/dist/{text-stream-LD364KBI.js → text-stream-SOVLYR2L.js} +2 -2
- package/dist/{types-BqB8zC9u.d.ts → types-CG-kPI81.d.ts} +3 -1
- package/dist/{types-DdZw4GRQ.d.ts → types-ht-Zw3Wv.d.ts} +1 -1
- package/docs/agent-integration.md +19 -64
- package/docs/architecture.md +80 -106
- package/docs/customization.md +49 -66
- package/docs/development.md +18 -17
- package/docs/errors.md +4 -4
- package/docs/getting-started.md +74 -93
- package/docs/media-and-audio.md +9 -7
- package/docs/performance.md +11 -6
- package/docs/persistence.md +3 -7
- package/docs/production.md +4 -15
- package/docs/prompt-and-input.md +1 -2
- package/docs/provider-integration.md +134 -181
- package/docs/reference/protocol.md +5 -14
- package/docs/reference/provider-adapters.md +21 -13
- package/docs/security.md +1 -14
- package/docs/testing.md +56 -123
- package/package.json +4 -25
- package/starters/video-chat/.env.example +12 -4
- package/starters/video-chat/.env.native.example +13 -0
- package/starters/video-chat/README.md +72 -11
- package/starters/video-chat/package.json +1 -1
- package/starters/video-chat/providers/text-native.ts +94 -0
- package/starters/video-chat/providers/text.ts +19 -0
- package/starters/video-chat/providers/transcription.ts +32 -0
- package/starters/video-chat/providers/video-custom.ts +45 -0
- package/starters/video-chat/providers/video-delivery.ts +57 -0
- package/starters/video-chat/providers/video-google.ts +48 -0
- package/starters/video-chat/providers/video-job.ts +103 -0
- package/starters/video-chat/providers/video-runway.ts +45 -0
- package/starters/video-chat/providers/video.ts +39 -63
- package/starters/video-chat/server.ts +3 -29
- package/starters/video-chat/vite.config.ts +2 -8
- package/styles/video-chat.css +8 -105
- package/dist/builtin-server-W4PLJUZ5.js +0 -8
- package/dist/catalog-types-WTbLP6Jh.d.ts +0 -78
- package/dist/check-runtime.d.ts +0 -15
- package/dist/check-runtime.js +0 -96
- package/dist/chunk-2E6T633S.js +0 -27
- package/dist/chunk-4YM2M62S.js +0 -13
- package/dist/chunk-4ZJLPHBV.js +0 -684
- package/dist/chunk-5JBMYQP6.js +0 -156
- package/dist/chunk-73NTSFFI.js +0 -81
- package/dist/chunk-EGVQODKU.js +0 -83
- package/dist/chunk-HFVNAPHZ.js +0 -35
- package/dist/chunk-IIN5M5HW.js +0 -697
- package/dist/chunk-IQMYK5DX.js +0 -133
- package/dist/chunk-IR44XKBI.js +0 -46
- package/dist/chunk-JKVOBTRO.js +0 -145
- package/dist/chunk-LVM5Q2DL.js +0 -68
- package/dist/chunk-M4QTEJTK.js +0 -709
- package/dist/chunk-QSBDB4J2.js +0 -16
- package/dist/chunk-R3XAOMKP.js +0 -29
- package/dist/chunk-SPVTJH3F.js +0 -24
- package/dist/chunk-YAHT3LST.js +0 -663
- package/dist/chunk-ZD2VTUYR.js +0 -28
- package/dist/cinema-media-HYCDG65Z.js +0 -11
- package/dist/comparison-XXAP4S4J.js +0 -36
- package/dist/editorial-timeline-VUQC6KPA.js +0 -42
- package/dist/key-figure-4TJK7HLT.js +0 -29
- package/dist/kit-DrRpdn0p.d.ts +0 -79
- package/dist/mobile-message-FIHAO6XC.js +0 -49
- package/dist/preload-media-LJXWKTWG.js +0 -57
- package/dist/quote-GRPIYKQJ.js +0 -32
- package/dist/system-prompt-AG26KJAA.js +0 -12
- package/dist/template-catalog.d.ts +0 -400
- package/dist/template-catalog.js +0 -6
- package/dist/templates.d.ts +0 -25
- package/dist/templates.js +0 -102
- package/dist/validate-4OUD2CLX.js +0 -10
- package/docs/concepts.md +0 -107
- package/docs/custom-templates.md +0 -346
- package/docs/immersive-interface.md +0 -86
- package/docs/motion-and-effects.md +0 -109
- package/docs/reference/design-system.html +0 -125
- package/docs/responsive-orientation.md +0 -37
- package/docs/streaming-protocol.md +0 -15
- package/examples/custom-template/README.md +0 -19
- package/examples/custom-template/minimal-text.tsx +0 -82
- package/examples/custom-template/structured-data.tsx +0 -104
- package/registry/items/backgrounds.json +0 -70
- package/registry/items/chapterTitle.json +0 -80
- package/registry/items/cinemaMedia.json +0 -136
- package/registry/items/comparison.json +0 -168
- package/registry/items/editorialTimeline.json +0 -172
- package/registry/items/keyFigure.json +0 -162
- package/registry/items/mobileMessage.json +0 -153
- package/registry/items/motion.json +0 -45
- package/registry/items/quote.json +0 -161
- package/registry/items/template-context.json +0 -31
- package/registry/items/theme.json +0 -47
- package/registry/items/typography.json +0 -47
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Video answers for your AI chat
|
|
2
2
|
|
|
3
3
|
[](https://github.com/VanillaSkyAi/video/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@vanillaskyai/video)
|
|
5
|
-
[](package.json)
|
|
6
5
|
[](LICENSE)
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
VanillaSky is an open-source React SDK for spoken video conversations. A short
|
|
8
|
+
chapter introduction starts the answer while footage prepares; narrated clips
|
|
9
|
+
then play in order as the response is generated.
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
Your application owns models, keys, authentication, storage, and spending.
|
|
12
|
+
VanillaSky owns the chat interface, shot-planning prompts, streaming, validation,
|
|
13
|
+
voice timing, and playback. The core has no runtime dependencies.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
Pre-1.0 beta, distributed through npm with [best-effort support](SUPPORT.md).
|
|
16
|
+
Pin an exact version and use its matching docs; an unreleased checkout is not
|
|
17
|
+
the published package. Review [breaking changes](CHANGELOG.md) before upgrading.
|
|
18
18
|
|
|
19
19
|
## Start on localhost
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ trusted templates, validation, streaming, voice timing, and player.
|
|
|
22
22
|
npx @vanillaskyai/video init
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The default starter uses the optional Vercel AI SDK text adapter. Add
|
|
26
26
|
`ANTHROPIC_API_KEY` to the generated, ignored `.env.local`, then run:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
@@ -30,42 +30,16 @@ npx vanillasky doctor
|
|
|
30
30
|
npm run dev
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
Prefer native callbacks? Use `init --native` in a new directory and configure
|
|
34
|
+
`GEMINI_API_KEY`; that starter needs neither `ai` nor `@ai-sdk/anthropic`.
|
|
35
|
+
Both are application examples, not SDK requirements.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
| Pexels video search | No install needed | `PEXELS_API_KEY` |
|
|
44
|
-
|
|
45
|
-
Run the selected command, add its key to `.env.local`, and restart the server.
|
|
46
|
-
Only selected provider packages are installed. `npx vanillasky doctor` reports
|
|
47
|
-
readiness by key name and never prints values. Provider SDKs remain application
|
|
48
|
-
dependencies, outside the core package.
|
|
49
|
-
|
|
50
|
-
For coding agents:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
npx skills add VanillaSkyAi/video@vanillasky
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Then prompt: `Use $vanillasky to set up and verify a general-purpose video chat in this project.`
|
|
57
|
-
|
|
58
|
-
## What init creates
|
|
59
|
-
|
|
60
|
-
The generated application is a thin, editable shell:
|
|
61
|
-
|
|
62
|
-
- `src/main.tsx` mounts the complete SDK-owned `<VideoChat />` interface;
|
|
63
|
-
- `server.ts` connects app-owned text, speech, transcription, stock, and video
|
|
64
|
-
providers through one `createVideoChatHandler`;
|
|
65
|
-
- `vite.config.ts` serves the UI and the single `/api/video-chat` endpoint;
|
|
66
|
-
- `.env.local` holds server-only keys and is ignored by Git.
|
|
67
|
-
|
|
68
|
-
The UI stays this small:
|
|
37
|
+
One text key gives you chat, chapter introductions, subtitles, and browser voice.
|
|
38
|
+
Add stock or generated footage through app-owned adapters. fal, Google, and
|
|
39
|
+
Runway references are included; any vendor can implement the same callback.
|
|
40
|
+
Direct video generation also requires your storage/delivery callback.
|
|
41
|
+
See [Getting started](docs/getting-started.md) for the complete setup and
|
|
42
|
+
[Provider integration](docs/provider-integration.md) for the adapter boundary.
|
|
69
43
|
|
|
70
44
|
```tsx
|
|
71
45
|
import { VideoChat } from "@vanillaskyai/video/react";
|
|
@@ -76,62 +50,44 @@ export function App() {
|
|
|
76
50
|
}
|
|
77
51
|
```
|
|
78
52
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Use `useVideoChat` when you want a custom interface while keeping the SDK-owned
|
|
84
|
-
conversation and playback lifecycle. Edit the generated server when you want a
|
|
85
|
-
different provider. The [provider guide](docs/provider-integration.md) explains
|
|
86
|
-
both boundaries.
|
|
87
|
-
|
|
88
|
-
## An immediate intro, then moving footage
|
|
89
|
-
|
|
90
|
-
The default chat uses a template introduction while its first shot prepares,
|
|
91
|
-
then footage with narration and subtitles. Choose AI video or Pexels in Settings.
|
|
92
|
-
AI mode never substitutes stock, and Pexels mode never calls the AI-video provider.
|
|
93
|
-
Missing or late footage becomes a useful chapter scene. The planner adapts its
|
|
94
|
-
visible actions and spoken beats to explanations, stories, comedy, imagination,
|
|
95
|
-
and practical requests. It does not choose body templates.
|
|
53
|
+
Use `useVideoChat` to build your own interface, and `parseVideo` with
|
|
54
|
+
`VideoPlayer` to replay completed responses. Changing a provider does not
|
|
55
|
+
require changing the React client.
|
|
96
56
|
|
|
97
|
-
|
|
98
|
-
|
|
57
|
+
Already have an assistant? The optional server `resolveAnswer` callback turns
|
|
58
|
+
its completed answer into the source for the video, while your application
|
|
59
|
+
keeps retrieval, tools and answer policy. See [existing-assistant integration](docs/provider-integration.md#use-an-existing-assistant).
|
|
99
60
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Copy template source only when you want to own and edit it:
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
npm install --save-dev tsx
|
|
106
|
-
npx vanillasky templates add chapterTitle
|
|
107
|
-
```
|
|
61
|
+
## How it works
|
|
108
62
|
|
|
109
|
-
|
|
110
|
-
|
|
63
|
+
The model streams an answer brief and shot directions, not component code.
|
|
64
|
+
Footage generation overlaps browser-owned speech preparation. The server
|
|
65
|
+
announces prepared media early and streams validated scenes in order. AI-video mode never silently
|
|
66
|
+
substitutes stock; stock mode never spends on generated video. A failed or late
|
|
67
|
+
clip becomes a narrated chapter. Narration targets a 0.8-second visual tail;
|
|
68
|
+
one short rewrite may fit an oversized beat before generation. If it still
|
|
69
|
+
does not fit, the complete original narration plays over a chapter. Footage
|
|
70
|
+
plays once at native speed; unexpected overruns recover to a chapter without
|
|
71
|
+
cutting off the sentence.
|
|
111
72
|
|
|
112
|
-
|
|
73
|
+
This is progressive **scene** delivery, not real-time frames from every vendor.
|
|
74
|
+
Some generation APIs take minutes; preloading cannot remove that latency.
|
|
75
|
+
Evaluate the model, voice and delivery path you actually deploy.
|
|
113
76
|
|
|
114
|
-
|
|
115
|
-
|
|
77
|
+
The supported visual vocabulary is deliberately small: footage and chapter
|
|
78
|
+
introductions/fallbacks. There is no template-authoring CLI, renderer plugin
|
|
79
|
+
system, or hosted service dependency.
|
|
116
80
|
|
|
117
81
|
## Documentation
|
|
118
82
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
| Deploy securely | [Production](docs/production.md) · [Security](docs/security.md) |
|
|
131
|
-
| Inspect the API contract | [Public API](PUBLIC-API.md) · [Protocol](docs/reference/protocol.md) |
|
|
132
|
-
|
|
133
|
-
Try a keyless template response in the
|
|
134
|
-
[playground](https://vanillasky.ai/playground/), or visit
|
|
135
|
-
[vanillasky.ai](https://vanillasky.ai/).
|
|
136
|
-
|
|
137
|
-
Apache-2.0
|
|
83
|
+
- [Agent integration](docs/agent-integration.md) and the optional
|
|
84
|
+
[integration skill](https://github.com/VanillaSkyAi/video/blob/main/skills/vanillasky/SKILL.md)
|
|
85
|
+
- [Provider integration](docs/provider-integration.md), [adapter reference](docs/reference/provider-adapters.md), and [media and voice](docs/media-and-audio.md)
|
|
86
|
+
- [Customization](docs/customization.md) and [prompt guidance](docs/prompt-and-input.md)
|
|
87
|
+
- [Persistence and replay](docs/persistence.md), [protocol](docs/reference/protocol.md), and [testing](docs/testing.md)
|
|
88
|
+
- [Performance](docs/performance.md), [production](docs/production.md), [security](docs/security.md), and [errors](docs/errors.md)
|
|
89
|
+
- [Architecture](docs/architecture.md), [development](docs/development.md), and [contributing](https://github.com/VanillaSkyAi/video/blob/main/CONTRIBUTING.md)
|
|
90
|
+
|
|
91
|
+
Node 22+; React 18 or 19. The four code entry points are the root package,
|
|
92
|
+
`/server`, `/react`, and `/test`, plus the scoped `/video-chat.css` stylesheet.
|
|
93
|
+
See the [public API contract](PUBLIC-API.md).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/video-chat/deadline.ts
|
|
2
|
+
function withDeadline(operation, timeoutMs, parent) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
const controller = new AbortController();
|
|
5
|
+
let settled = false;
|
|
6
|
+
const timer = setTimeout(() => finish({ error: new DOMException("Optional work exceeded its deadline", "TimeoutError") }), timeoutMs);
|
|
7
|
+
const finish = (result) => {
|
|
8
|
+
if (settled) return;
|
|
9
|
+
settled = true;
|
|
10
|
+
clearTimeout(timer);
|
|
11
|
+
parent?.removeEventListener("abort", cancel);
|
|
12
|
+
if ("error" in result) {
|
|
13
|
+
reject(result.error);
|
|
14
|
+
controller.abort(result.error);
|
|
15
|
+
} else {
|
|
16
|
+
resolve(result.value);
|
|
17
|
+
controller.abort();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const cancel = () => finish({ error: parent?.reason ?? new DOMException("Cancelled", "AbortError") });
|
|
21
|
+
if (parent?.aborted) {
|
|
22
|
+
cancel();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
parent?.addEventListener("abort", cancel, { once: true });
|
|
26
|
+
try {
|
|
27
|
+
const result = operation(controller.signal);
|
|
28
|
+
Promise.resolve(result).then((value) => finish({ value }), (error) => finish({ error }));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
finish({ error });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
withDeadline
|
|
37
|
+
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
editorialFont,
|
|
3
|
-
fade
|
|
4
|
-
} from "./chunk-4YM2M62S.js";
|
|
5
|
-
|
|
6
1
|
// src/visual-system/scene-templates/chapter-title.tsx
|
|
7
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
var editorialFont = '-apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif';
|
|
4
|
+
function ease(value) {
|
|
5
|
+
const t = Math.max(0, Math.min(1, value));
|
|
6
|
+
return t * t * (3 - 2 * t);
|
|
7
|
+
}
|
|
8
8
|
function TitleScene({ variables, width, height, progress, motionProgress }) {
|
|
9
9
|
const title = String(variables.title ?? "A different perspective");
|
|
10
10
|
const unit = Math.min(width, height);
|
|
11
11
|
const presentation = motionProgress ?? progress;
|
|
12
|
-
const opacity =
|
|
12
|
+
const opacity = ease(presentation / 0.22) * (1 - ease((presentation - 0.76) / 0.24));
|
|
13
13
|
return /* @__PURE__ */ jsx("div", { "data-template": "title", "data-title-treatment": "quiet-fade", style: { position: "absolute", inset: 0, background: "#000", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx("div", { "data-title-composition": "centered", style: { width: "76%", textAlign: "center", opacity, fontFamily: editorialFont, fontWeight: 500, fontSize: unit * 0.068, lineHeight: 1.18, letterSpacing: "-.025em", textWrap: "balance", overflowWrap: "anywhere" }, children: title }) });
|
|
14
14
|
}
|
|
15
15
|
var TitleSceneTemplate = TitleScene;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/visual-system/scene-templates/external-video-backdrop.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var DEFAULT = { mode: false, audioMuted: true, audioVolume: 1 };
|
|
5
|
+
var sharedContext = globalThis;
|
|
6
|
+
var BackdropContext = sharedContext.__vanillaskyVideoBackdropContext ??= React.createContext(DEFAULT);
|
|
7
|
+
function ExternalVideoBackdropProvider({
|
|
8
|
+
mode,
|
|
9
|
+
audioMuted = true,
|
|
10
|
+
audioVolume = 1,
|
|
11
|
+
preparingNarration = false,
|
|
12
|
+
onMediaError,
|
|
13
|
+
children
|
|
14
|
+
}) {
|
|
15
|
+
const value = React.useMemo(
|
|
16
|
+
() => ({ mode, audioMuted, audioVolume, preparingNarration, onMediaError }),
|
|
17
|
+
[mode, audioMuted, audioVolume, preparingNarration, onMediaError]
|
|
18
|
+
);
|
|
19
|
+
return /* @__PURE__ */ jsx(BackdropContext.Provider, { value, children });
|
|
20
|
+
}
|
|
21
|
+
function useExternalVideoBackdrop() {
|
|
22
|
+
return React.useContext(BackdropContext).mode;
|
|
23
|
+
}
|
|
24
|
+
function useMediaAudio() {
|
|
25
|
+
const { audioMuted, audioVolume } = React.useContext(BackdropContext);
|
|
26
|
+
return { muted: audioMuted, volume: audioVolume };
|
|
27
|
+
}
|
|
28
|
+
function useNarrationPreroll() {
|
|
29
|
+
return React.useContext(BackdropContext).preparingNarration === true;
|
|
30
|
+
}
|
|
31
|
+
function useMediaFailure() {
|
|
32
|
+
return React.useContext(BackdropContext).onMediaError;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
ExternalVideoBackdropProvider,
|
|
37
|
+
useExternalVideoBackdrop,
|
|
38
|
+
useMediaAudio,
|
|
39
|
+
useNarrationPreroll,
|
|
40
|
+
useMediaFailure
|
|
41
|
+
};
|
|
@@ -33,22 +33,6 @@ function safePublicDiagnostic(value, fallback) {
|
|
|
33
33
|
if (redacted.length <= MAX_PUBLIC_DIAGNOSTIC_LENGTH) return redacted;
|
|
34
34
|
return `${redacted.slice(0, MAX_PUBLIC_DIAGNOSTIC_LENGTH - 1).trimEnd()}\u2026`;
|
|
35
35
|
}
|
|
36
|
-
function createClippedVariableWarning(templateId, fields) {
|
|
37
|
-
return {
|
|
38
|
-
code: "scene_variable_clipped",
|
|
39
|
-
category: VIDEO_WARNING_CATEGORIES.scene_variable_clipped,
|
|
40
|
-
message: `Trimmed ${fields.join(", ")} on ${templateId} to the length the template declares.`,
|
|
41
|
-
recoverable: true
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function createMediaBudgetWarning(limit) {
|
|
45
|
-
return {
|
|
46
|
-
code: "media_budget_reached",
|
|
47
|
-
category: VIDEO_WARNING_CATEGORIES.media_budget_reached,
|
|
48
|
-
message: `Resolved media for ${limit} scene${limit === 1 ? "" : "s"}; later scenes keep their copy on the brand gradient.`,
|
|
49
|
-
recoverable: true
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
36
|
|
|
53
37
|
// src/protocol/json-validation.ts
|
|
54
38
|
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -361,8 +345,6 @@ function parseVideoPlanPart(value) {
|
|
|
361
345
|
|
|
362
346
|
export {
|
|
363
347
|
safePublicDiagnostic,
|
|
364
|
-
createClippedVariableWarning,
|
|
365
|
-
createMediaBudgetWarning,
|
|
366
348
|
record,
|
|
367
349
|
allowedKeys,
|
|
368
350
|
string,
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
attachGenerationLifecycleSink
|
|
3
|
-
} from "./chunk-E7CL7UPB.js";
|
|
4
1
|
import {
|
|
5
2
|
applyVideoEvent,
|
|
6
3
|
checksumVideo,
|
|
7
4
|
createVideoEventFactory,
|
|
8
5
|
createVideoState
|
|
9
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-Z3DLSLAJ.js";
|
|
7
|
+
import {
|
|
8
|
+
getCloserReserve,
|
|
9
|
+
getReadableSceneDuration,
|
|
10
|
+
paceScene
|
|
11
|
+
} from "./chunk-WZESNEPT.js";
|
|
12
|
+
import {
|
|
13
|
+
attachGenerationLifecycleSink
|
|
14
|
+
} from "./chunk-E7CL7UPB.js";
|
|
10
15
|
import {
|
|
11
16
|
parseVideoPlanPart,
|
|
12
17
|
safePublicDiagnostic
|
|
13
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-6Z3ID54H.js";
|
|
14
19
|
import {
|
|
15
20
|
MAX_RETAINED_INSTRUCTIONS_LENGTH,
|
|
16
21
|
MAX_RETAINED_MEDIA_URLS,
|
|
@@ -22,11 +27,6 @@ import {
|
|
|
22
27
|
VIDEO_SCHEMA_VERSION,
|
|
23
28
|
createVideoRequest
|
|
24
29
|
} from "./chunk-7NZOMTAL.js";
|
|
25
|
-
import {
|
|
26
|
-
getCloserReserve,
|
|
27
|
-
getReadableSceneDuration,
|
|
28
|
-
paceScene
|
|
29
|
-
} from "./chunk-3O7OMMMF.js";
|
|
30
30
|
|
|
31
31
|
// src/replayable-stream.ts
|
|
32
32
|
function createReplayableStream(source, onError) {
|
|
@@ -108,23 +108,11 @@ function buildVideoUserPrompt(input, openingDurationSec = 0) {
|
|
|
108
108
|
"Compose a video response from the structured customer input below.",
|
|
109
109
|
`Knowledge mode: ${input.knowledgeMode ?? "input-only"}.`,
|
|
110
110
|
`Maximum duration: ${input.maxDurationSec ?? 30} seconds, including the supplied opening.`,
|
|
111
|
-
input.opening === false ? "The host owns the opening wait. Add the first grounded scene as soon as it is complete
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"Use only claims supported by the factual basis permitted by the trusted system prompt.",
|
|
117
|
-
"Develop the requested answer within the duration: preserve its mechanism, essential actions, story resolution or punchline. A short prompt can need several distinct visual beats; input length is not the answer length.",
|
|
118
|
-
"For a long source, summarize instead of attempting to represent every fact, unless the creative instructions explicitly request complete fact coverage that fits the duration.",
|
|
119
|
-
"When selecting claims from the raw input, preserve their exact wording and numbers. Preserve qualifiers, units, denominators, ranges, and comparison direction; for example, do not shorten 4.8 out of 5 to 4.8.",
|
|
120
|
-
"Choose the scene count from the requested intent, visual progression and duration budget. A joke can be brief; a practical sequence or imaginative story may need more development. Do not stop while an essential step or payoff is missing.",
|
|
121
|
-
"If the creative instructions explicitly require one separate scene per named item, release, section, or list entry, do not merge, group, or omit those required items. Keep related required scenes adjacent in a coherent progression while preserving each item as its own scene.",
|
|
122
|
-
"Before emitting, verify that the explicitly requested structure can fit readably within the maximum duration. If it cannot, preserve readability and the requested separation for the scenes that fit, then finish with plan.complete using finishReason length rather than silently changing the structure.",
|
|
123
|
-
"Before plan.complete, check that the answer fulfills the actual request. Do not mistake a hook and a single supporting statement for a complete explanation, or explain a joke after its punchline.",
|
|
124
|
-
"Emit exactly one final scene.add with placement closer. Land the requested ending without repeating the opening or implying another scene follows.",
|
|
125
|
-
"Reuse the same suitable template when it serves successive beats. Vary the subject action, shot scale, perspective or purposeful detail, not the template merely for variety.",
|
|
126
|
-
"Never add filler to satisfy a count or diversity target.",
|
|
127
|
-
input.suppliedMedia?.length ? input.opening === false ? "Select zero or more relevant opaque supplied-media references for visible scenes. Never invent or transform a reference. Only emit mediaKeyword when the trusted system catalog explicitly exposes it. Never invent mediaUrl or mediaPoster." : "Select zero or more relevant opaque supplied-media references for visible later scenes. Never invent or transform a reference. Only emit mediaKeyword when the trusted system catalog explicitly exposes it, and only on later scenes. Never invent mediaUrl or mediaPoster." : input.opening === false ? "No supplied media URL is available. Use asset-free templates unless the trusted system catalog explicitly permits host-resolved media intent, which may begin on the first scene. Only emit mediaKeyword when the trusted system catalog explicitly exposes it. Never invent mediaUrl or mediaPoster." : "No supplied media URL is available. Use asset-free templates unless the trusted system catalog explicitly permits host-resolved media intent. Only emit mediaKeyword when the trusted system catalog explicitly exposes it, and only on later scenes. Never invent mediaUrl or mediaPoster.",
|
|
111
|
+
input.opening === false ? "The host owns the opening wait. Add the first grounded scene as soon as it is complete." : `The host has already added the opening scene, which consumes ${openingDurationSec} seconds. Do not repeat it.`,
|
|
112
|
+
"Use cinemaMedia for resolved footage and chapterTitle for a chapter opening or recovery.",
|
|
113
|
+
"Use only claims supported by the factual basis permitted by the trusted system prompt. Preserve quantities, qualifications and the answer's conclusion.",
|
|
114
|
+
"Use only the supplied opaque media references or media resolved by the host. Never invent media URLs.",
|
|
115
|
+
"End with one closer scene and plan.complete; do not add filler.",
|
|
128
116
|
"",
|
|
129
117
|
"RAW INPUT",
|
|
130
118
|
input.input.trim(),
|
|
@@ -691,7 +679,7 @@ function createVideo(rawInput, options) {
|
|
|
691
679
|
position
|
|
692
680
|
}));
|
|
693
681
|
}
|
|
694
|
-
const systemPrompt = options.systemPrompt ??
|
|
682
|
+
const systemPrompt = options.systemPrompt ?? "";
|
|
695
683
|
const context = { request, systemPrompt, userPrompt, initialConfig, signal: controller.signal };
|
|
696
684
|
attachGenerationLifecycleSink(context, lifecycle);
|
|
697
685
|
for await (const untrustedPart of options.generate(context)) {
|
|
@@ -864,6 +852,5 @@ function createVideo(rawInput, options) {
|
|
|
864
852
|
}
|
|
865
853
|
|
|
866
854
|
export {
|
|
867
|
-
invokeIsolated,
|
|
868
855
|
createVideo
|
|
869
856
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizeVideoChatMedia
|
|
3
|
+
} from "./chunk-ISWQ6T5X.js";
|
|
4
|
+
|
|
5
|
+
// src/server/scene-validation.ts
|
|
6
|
+
function validateBuiltinScene(scene) {
|
|
7
|
+
const variables = scene.variables;
|
|
8
|
+
const boundedText = (value, maximum) => typeof value === "string" && Boolean(value.trim()) && [...value].length <= maximum;
|
|
9
|
+
if (scene.templateId === "chapterTitle") {
|
|
10
|
+
if (!boundedText(variables.title, 65)) throw new Error("Chapter requires a title of 1\u201365 characters");
|
|
11
|
+
if (Object.keys(variables).some((key) => key !== "title")) throw new Error("Unsupported chapter variable");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (scene.templateId !== "cinemaMedia") throw new Error("Unsupported scene kind");
|
|
15
|
+
if (!sanitizeVideoChatMedia({
|
|
16
|
+
url: variables.mediaUrl,
|
|
17
|
+
type: variables.mediaType === "photo" ? "image" : variables.mediaType,
|
|
18
|
+
...variables.mediaPoster ? { posterUrl: variables.mediaPoster } : {}
|
|
19
|
+
})) throw new Error("Media scene requires a safe media URL and type");
|
|
20
|
+
if (variables.fallbackText !== void 0 && !boundedText(variables.fallbackText, 65)) {
|
|
21
|
+
throw new Error("Media recovery title must contain 1\u201365 characters");
|
|
22
|
+
}
|
|
23
|
+
if (variables.mediaDurationSec !== void 0 && (typeof variables.mediaDurationSec !== "number" || !Number.isFinite(variables.mediaDurationSec) || variables.mediaDurationSec <= 0 || variables.mediaDurationSec > 3600)) throw new Error("Invalid media duration");
|
|
24
|
+
const allowed = /* @__PURE__ */ new Set(["mediaUrl", "mediaType", "mediaPoster", "fallbackText", "mediaDurationSec"]);
|
|
25
|
+
if (Object.keys(variables).some((key) => !allowed.has(key))) throw new Error("Unsupported media variable");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
validateBuiltinScene
|
|
30
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_RETAINED_MEDIA_URL_LENGTH
|
|
3
|
+
} from "./chunk-AUPC6MDK.js";
|
|
4
|
+
|
|
5
|
+
// src/video-chat/media.ts
|
|
6
|
+
function sanitizeVideoChatMedia(value) {
|
|
7
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
8
|
+
const media = value;
|
|
9
|
+
if (media.type !== "image" && media.type !== "video") return null;
|
|
10
|
+
if (typeof media.url !== "string") return null;
|
|
11
|
+
const url = media.url.trim();
|
|
12
|
+
if (!safeMediaUrl(url)) return null;
|
|
13
|
+
if (media.posterUrl != null && typeof media.posterUrl !== "string") return null;
|
|
14
|
+
const posterUrl = typeof media.posterUrl === "string" ? media.posterUrl.trim() : "";
|
|
15
|
+
if (posterUrl && !safeMediaUrl(posterUrl)) return null;
|
|
16
|
+
if (media.durationSec !== void 0 && (typeof media.durationSec !== "number" || !Number.isFinite(media.durationSec) || media.durationSec <= 0 || media.durationSec > 3600)) return null;
|
|
17
|
+
return {
|
|
18
|
+
url,
|
|
19
|
+
type: media.type,
|
|
20
|
+
...posterUrl ? { posterUrl } : {},
|
|
21
|
+
...typeof media.durationSec === "number" ? { durationSec: media.durationSec } : {}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function safeMediaUrl(value) {
|
|
25
|
+
if (!value || value.length > MAX_RETAINED_MEDIA_URL_LENGTH) return false;
|
|
26
|
+
try {
|
|
27
|
+
const protocol = new URL(value).protocol;
|
|
28
|
+
return protocol === "https:" || protocol === "http:";
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
sanitizeVideoChatMedia
|
|
36
|
+
};
|
|
@@ -9,22 +9,13 @@ function isVideoUrl(url) {
|
|
|
9
9
|
return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
function limitsConcurrentVideoDecoders() {
|
|
13
|
-
if (typeof navigator === "undefined") return false;
|
|
14
|
-
return /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
|
|
15
|
-
}
|
|
16
12
|
function resolveMediaType(mediaType, mediaUrl) {
|
|
17
13
|
if (mediaType === "gradient") return "gradient";
|
|
18
14
|
if (mediaType === "video") return "video";
|
|
19
15
|
if (mediaType === "photo") return "photo";
|
|
20
16
|
return mediaUrl && isVideoUrl(mediaUrl) ? "video" : "photo";
|
|
21
17
|
}
|
|
22
|
-
function hasSceneMedia(variables) {
|
|
23
|
-
return String(variables.mediaUrl || "").trim() !== "" && String(variables.mediaType || "auto") !== "gradient";
|
|
24
|
-
}
|
|
25
18
|
|
|
26
19
|
export {
|
|
27
|
-
|
|
28
|
-
resolveMediaType,
|
|
29
|
-
hasSceneMedia
|
|
20
|
+
resolveMediaType
|
|
30
21
|
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getReadableSceneDuration
|
|
3
|
+
} from "./chunk-WZESNEPT.js";
|
|
4
|
+
|
|
5
|
+
// src/protocol/timeline.ts
|
|
6
|
+
function resolveVideoTimeline(config) {
|
|
7
|
+
const ranges = [];
|
|
8
|
+
let cursor = 0;
|
|
9
|
+
for (const scene of config.scenes) {
|
|
10
|
+
const timing = scene.timing ?? {};
|
|
11
|
+
const beatStart = timing.beatStart == null ? void 0 : config.audio?.beatMarkers[timing.beatStart]?.time;
|
|
12
|
+
const beatEnd = timing.beatEnd == null ? void 0 : config.audio?.beatMarkers[timing.beatEnd]?.time;
|
|
13
|
+
const start = timing.startTime ?? beatStart ?? cursor;
|
|
14
|
+
const duration = Math.max(1e-3, timing.fixedDuration ?? 5);
|
|
15
|
+
const end = Math.max(start + 1e-3, timing.endTime ?? beatEnd ?? start + duration);
|
|
16
|
+
ranges.push({ scene, start, end });
|
|
17
|
+
cursor = end;
|
|
18
|
+
}
|
|
19
|
+
return ranges;
|
|
20
|
+
}
|
|
21
|
+
function getVideoDuration(config) {
|
|
22
|
+
return resolveVideoTimeline(config).at(-1)?.end ?? 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/protocol/scene-duration.ts
|
|
26
|
+
var NARRATION_WORDS_PER_SECOND = 2.6;
|
|
27
|
+
var NARRATION_TAIL_SECONDS = 0.6;
|
|
28
|
+
function getSceneDurationBounds(scene, metadata) {
|
|
29
|
+
return {
|
|
30
|
+
minimum: Math.max(1e-3, metadata?.minDuration ?? 1),
|
|
31
|
+
readable: getReadableSceneDuration(scene, metadata)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function getSpokenDuration(narration) {
|
|
35
|
+
const words = narration.trim().split(/\s+/u).filter(Boolean).length;
|
|
36
|
+
if (words === 0) return 0;
|
|
37
|
+
return words / NARRATION_WORDS_PER_SECOND + NARRATION_TAIL_SECONDS;
|
|
38
|
+
}
|
|
39
|
+
function getSceneDuration(scene, metadata) {
|
|
40
|
+
const { readable } = getSceneDurationBounds(scene, metadata);
|
|
41
|
+
return Math.max(readable, scene.narration ? getSpokenDuration(scene.narration) : 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
resolveVideoTimeline,
|
|
46
|
+
getVideoDuration,
|
|
47
|
+
getSceneDurationBounds,
|
|
48
|
+
getSpokenDuration,
|
|
49
|
+
getSceneDuration
|
|
50
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/visual-system/catalog/builtin-metadata.ts
|
|
2
|
+
var SCENE_DEFINITIONS = Object.freeze([
|
|
3
|
+
Object.freeze({
|
|
4
|
+
id: "cinemaMedia",
|
|
5
|
+
defaults: Object.freeze({ mediaUrl: "", mediaType: "video", mediaPoster: "" }),
|
|
6
|
+
minDuration: 3,
|
|
7
|
+
preferredDuration: 6,
|
|
8
|
+
timing: Object.freeze({ contentFields: Object.freeze([]), contentUnit: "words", revealSeconds: 0, holdSeconds: 3, exitSeconds: 0 })
|
|
9
|
+
}),
|
|
10
|
+
Object.freeze({
|
|
11
|
+
id: "chapterTitle",
|
|
12
|
+
defaults: Object.freeze({ title: "A different perspective" }),
|
|
13
|
+
minDuration: 3,
|
|
14
|
+
preferredDuration: 4,
|
|
15
|
+
timing: Object.freeze({ contentFields: Object.freeze(["title"]), contentUnit: "words", revealSeconds: 0.8, holdSeconds: 1.5, exitSeconds: 0.8 })
|
|
16
|
+
})
|
|
17
|
+
]);
|
|
18
|
+
function getBuiltinSceneDefinition(id) {
|
|
19
|
+
return SCENE_DEFINITIONS.find((scene) => scene.id === id);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
SCENE_DEFINITIONS,
|
|
24
|
+
getBuiltinSceneDefinition
|
|
25
|
+
};
|