@vanillaskyai/video 0.9.0 → 0.10.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 +19 -0
- package/dist/{builtin-server-HW6HGXCZ.js → builtin-server-W4PLJUZ5.js} +2 -2
- package/dist/check-runtime.js +2 -2
- package/dist/{chunk-UDIODEDK.js → chunk-224QNWRA.js} +5 -1
- package/dist/{chunk-MXEYPK7M.js → chunk-4ZJLPHBV.js} +194 -62
- package/dist/{chunk-ZDJNFA52.js → chunk-5BSJLT6H.js} +6 -6
- package/dist/{chunk-G7IZBBWF.js → chunk-5C6HZNHY.js} +12 -14
- package/dist/{chunk-3ZTEFJIR.js → chunk-6KZGF63O.js} +47 -12
- package/dist/{chunk-LH4BPALA.js → chunk-IIN5M5HW.js} +194 -61
- package/dist/{chunk-MQILEEXC.js → chunk-IR44XKBI.js} +1 -1
- package/dist/{chunk-OR52UMSF.js → chunk-JKVOBTRO.js} +3 -2
- package/dist/chunk-KHLO5OHT.js +35 -0
- package/dist/{chunk-PAJ44TFW.js → chunk-LVM5Q2DL.js} +8 -6
- package/dist/chunk-VQH3JTQC.js +16 -0
- package/dist/{cinema-media-YQHSIFDA.js → cinema-media-APDJS7SC.js} +3 -2
- package/dist/cli.js +4 -4
- package/dist/{comparison-RZMMHZR2.js → comparison-ZVE2DE7U.js} +14 -4
- package/dist/{compose-video-TBFTONUC.js → compose-video-BH5K6XWT.js} +1 -1
- package/dist/{editorial-timeline-OZX7OS6G.js → editorial-timeline-YIXXVJUV.js} +10 -3
- package/dist/key-figure-I2C37FWV.js +30 -0
- package/dist/{mobile-message-UZUPIKHD.js → mobile-message-JSGBU6EX.js} +3 -2
- package/dist/{preload-media-CFTZIMZL.js → preload-media-LJXWKTWG.js} +1 -1
- package/dist/quote-XH54G3FS.js +33 -0
- package/dist/react.js +58 -33
- package/dist/server.js +106 -27
- package/dist/{system-prompt-SYRT5DWM.js → system-prompt-AG26KJAA.js} +1 -1
- package/dist/template-catalog.d.ts +154 -19
- package/dist/template-catalog.js +1 -1
- package/dist/test.js +2 -2
- package/docs/customization.md +1 -1
- package/docs/maintainers/cinematic-migration.md +4 -0
- package/docs/media-and-audio.md +3 -3
- package/docs/provider-integration.md +1 -1
- package/package.json +1 -1
- package/registry/items/cinemaMedia.json +1 -1
- package/registry/items/comparison.json +71 -4
- package/registry/items/editorialTimeline.json +71 -4
- package/registry/items/keyFigure.json +69 -4
- package/registry/items/mobileMessage.json +1 -1
- package/registry/items/quote.json +71 -4
- package/starters/video-chat/package.json +1 -1
- package/dist/focus-cards-TJ72BT7U.js +0 -20
- package/dist/key-figure-GYENXKH3.js +0 -20
- package/dist/quote-EW5HQA6H.js +0 -23
- package/registry/items/focusCards.json +0 -90
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EditorialSurface
|
|
3
|
+
} from "./chunk-KHLO5OHT.js";
|
|
4
|
+
import "./chunk-5C6HZNHY.js";
|
|
5
|
+
import "./chunk-77VY4O7A.js";
|
|
6
|
+
import {
|
|
7
|
+
fade
|
|
8
|
+
} from "./chunk-4YM2M62S.js";
|
|
9
|
+
import {
|
|
10
|
+
hasSceneMedia
|
|
11
|
+
} from "./chunk-224QNWRA.js";
|
|
12
|
+
import "./chunk-TVIU23OM.js";
|
|
13
|
+
|
|
14
|
+
// src/visual-system/scene-templates/quote.tsx
|
|
15
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
function QuoteScene(props) {
|
|
17
|
+
const { variables, width, height, progress, motionProgress = progress, safeZone } = props;
|
|
18
|
+
const overMedia = hasSceneMedia(variables);
|
|
19
|
+
const u = Math.min(width, height), top = Math.max(height * 0.16, safeZone.top), bottom = height - Math.max(height * 0.23, safeZone.bottom);
|
|
20
|
+
const side = Math.max(width * 0.13, safeZone.left, safeZone.right);
|
|
21
|
+
return /* @__PURE__ */ jsx(EditorialSurface, { ...props, template: "quote", children: /* @__PURE__ */ jsxs("div", { style: { position: "absolute", left: side, top: (top + bottom) / 2, width: width - side * 2, transform: "translateY(-50%)", textAlign: "center", opacity: fade((motionProgress - 0.03) / 0.24) }, children: [
|
|
22
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: u * 0.067, fontWeight: 500, lineHeight: 1.25, letterSpacing: "-.025em", textWrap: "balance", overflowWrap: "anywhere" }, children: [
|
|
23
|
+
"\u201C",
|
|
24
|
+
String(variables.quote ?? ""),
|
|
25
|
+
"\u201D"
|
|
26
|
+
] }),
|
|
27
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: u * 0.041, fontWeight: 400, color: overMedia ? "#fff" : "#b7b7bc", lineHeight: 1.3, marginTop: u * 0.065, textWrap: "balance", overflowWrap: "anywhere" }, children: String(variables.attribution ?? "") })
|
|
28
|
+
] }) });
|
|
29
|
+
}
|
|
30
|
+
var QuoteSceneTemplate = QuoteScene;
|
|
31
|
+
export {
|
|
32
|
+
QuoteSceneTemplate
|
|
33
|
+
};
|
package/dist/react.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from "./chunk-7NZOMTAL.js";
|
|
31
31
|
import {
|
|
32
32
|
getBuiltinTemplateMetadata
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-IIN5M5HW.js";
|
|
34
34
|
import "./chunk-3O7OMMMF.js";
|
|
35
35
|
import {
|
|
36
36
|
assertTemplateTransitionMetadata
|
|
@@ -40,15 +40,16 @@ import {
|
|
|
40
40
|
VideoFrame,
|
|
41
41
|
getDimensions,
|
|
42
42
|
sceneReadinessKey
|
|
43
|
-
} from "./chunk-
|
|
44
|
-
import "./chunk-
|
|
43
|
+
} from "./chunk-6KZGF63O.js";
|
|
44
|
+
import "./chunk-224QNWRA.js";
|
|
45
45
|
import "./chunk-TVIU23OM.js";
|
|
46
46
|
import {
|
|
47
47
|
getVideoDuration,
|
|
48
48
|
resolveVideoTimeline
|
|
49
49
|
} from "./chunk-SPVTJH3F.js";
|
|
50
50
|
import {
|
|
51
|
-
markExternalVideoBackdropTemplate
|
|
51
|
+
markExternalVideoBackdropTemplate,
|
|
52
|
+
supportsExternalVideoBackdrop
|
|
52
53
|
} from "./chunk-R3XAOMKP.js";
|
|
53
54
|
import "./chunk-73NTSFFI.js";
|
|
54
55
|
|
|
@@ -93,14 +94,13 @@ import { createElement } from "react";
|
|
|
93
94
|
|
|
94
95
|
// src/visual-system/catalog/builtin-loaders.generated.ts
|
|
95
96
|
var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
|
|
96
|
-
"cinemaMedia": () => import("./cinema-media-
|
|
97
|
+
"cinemaMedia": () => import("./cinema-media-APDJS7SC.js").then((module) => ({ default: module.MediaSceneTemplate })),
|
|
97
98
|
"chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"keyFigure": () => import("./key-figure-GYENXKH3.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
|
|
99
|
+
"editorialTimeline": () => import("./editorial-timeline-YIXXVJUV.js").then((module) => ({ default: module.TimelineSceneTemplate })),
|
|
100
|
+
"mobileMessage": () => import("./mobile-message-JSGBU6EX.js").then((module) => ({ default: module.NotificationSceneTemplate })),
|
|
101
|
+
"comparison": () => import("./comparison-ZVE2DE7U.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
|
|
102
|
+
"quote": () => import("./quote-XH54G3FS.js").then((module) => ({ default: module.QuoteSceneTemplate })),
|
|
103
|
+
"keyFigure": () => import("./key-figure-I2C37FWV.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
// src/visual-system/catalog/builtin-player.generated.ts
|
|
@@ -121,17 +121,6 @@ var GENERATED_BUILTIN_PLAYER_TEMPLATES = [
|
|
|
121
121
|
},
|
|
122
122
|
"usesGlobalTransition": false
|
|
123
123
|
},
|
|
124
|
-
{
|
|
125
|
-
"id": "focusCards",
|
|
126
|
-
"defaults": {
|
|
127
|
-
"items": [
|
|
128
|
-
"Listen closely",
|
|
129
|
-
"Notice the pattern",
|
|
130
|
-
"Make room for change"
|
|
131
|
-
]
|
|
132
|
-
},
|
|
133
|
-
"usesGlobalTransition": false
|
|
134
|
-
},
|
|
135
124
|
{
|
|
136
125
|
"id": "editorialTimeline",
|
|
137
126
|
"defaults": {
|
|
@@ -145,7 +134,10 @@ var GENERATED_BUILTIN_PLAYER_TEMPLATES = [
|
|
|
145
134
|
{
|
|
146
135
|
"label": "Act"
|
|
147
136
|
}
|
|
148
|
-
]
|
|
137
|
+
],
|
|
138
|
+
"mediaUrl": "",
|
|
139
|
+
"mediaType": "video",
|
|
140
|
+
"mediaPoster": ""
|
|
149
141
|
},
|
|
150
142
|
"usesGlobalTransition": false
|
|
151
143
|
},
|
|
@@ -164,22 +156,33 @@ var GENERATED_BUILTIN_PLAYER_TEMPLATES = [
|
|
|
164
156
|
"id": "comparison",
|
|
165
157
|
"defaults": {
|
|
166
158
|
"leftText": "More distractions",
|
|
167
|
-
"rightText": "More room to think"
|
|
159
|
+
"rightText": "More room to think",
|
|
160
|
+
"mediaUrl": "",
|
|
161
|
+
"mediaType": "video",
|
|
162
|
+
"mediaPoster": ""
|
|
168
163
|
},
|
|
169
164
|
"usesGlobalTransition": false
|
|
170
165
|
},
|
|
171
166
|
{
|
|
172
167
|
"id": "quote",
|
|
173
|
-
"defaults": {
|
|
168
|
+
"defaults": {
|
|
169
|
+
"mediaUrl": "",
|
|
170
|
+
"mediaType": "video",
|
|
171
|
+
"mediaPoster": ""
|
|
172
|
+
},
|
|
174
173
|
"usesGlobalTransition": false
|
|
175
174
|
},
|
|
176
175
|
{
|
|
177
176
|
"id": "keyFigure",
|
|
178
|
-
"defaults": {
|
|
177
|
+
"defaults": {
|
|
178
|
+
"mediaUrl": "",
|
|
179
|
+
"mediaType": "video",
|
|
180
|
+
"mediaPoster": ""
|
|
181
|
+
},
|
|
179
182
|
"usesGlobalTransition": false
|
|
180
183
|
}
|
|
181
184
|
];
|
|
182
|
-
var GENERATED_BUILTIN_VIDEO_BACKDROP_IDS = ["cinemaMedia", "mobileMessage"];
|
|
185
|
+
var GENERATED_BUILTIN_VIDEO_BACKDROP_IDS = ["cinemaMedia", "editorialTimeline", "mobileMessage", "comparison", "quote", "keyFigure"];
|
|
183
186
|
|
|
184
187
|
// src/visual-system/catalog/builtin-player.ts
|
|
185
188
|
function createPreloadableBuiltinTemplate(loader, mediaBackdrop) {
|
|
@@ -235,7 +238,7 @@ function preloadBuiltinTemplate(id) {
|
|
|
235
238
|
|
|
236
239
|
// src/player/warm-scene-media.ts
|
|
237
240
|
function warmSceneMedia(variables) {
|
|
238
|
-
void import("./preload-media-
|
|
241
|
+
void import("./preload-media-LJXWKTWG.js").then((module) => module.preloadSceneMedia(variables)).catch(() => {
|
|
239
242
|
});
|
|
240
243
|
}
|
|
241
244
|
|
|
@@ -1236,6 +1239,19 @@ var VideoError = class extends Error {
|
|
|
1236
1239
|
}
|
|
1237
1240
|
};
|
|
1238
1241
|
|
|
1242
|
+
// src/player/recover-scene-media.ts
|
|
1243
|
+
function recoverSceneMedia(scene) {
|
|
1244
|
+
if (scene.templateId === "cinemaMedia") {
|
|
1245
|
+
const title = scene.variables.fallbackText;
|
|
1246
|
+
if (typeof title !== "string" || !title.trim() || [...title].length > 65) return void 0;
|
|
1247
|
+
return { ...scene, templateId: "chapterTitle", variables: { title: title.trim() } };
|
|
1248
|
+
}
|
|
1249
|
+
if (!["comparison", "editorialTimeline", "quote", "keyFigure"].includes(scene.templateId)) return void 0;
|
|
1250
|
+
const variables = { ...scene.variables };
|
|
1251
|
+
for (const key of ["mediaUrl", "mediaPoster", "mediaType", "mediaKeyword", "mediaSource"]) delete variables[key];
|
|
1252
|
+
return { ...scene, variables };
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1239
1255
|
// src/video-chat/use-video-chat.ts
|
|
1240
1256
|
import { useCallback as useCallback2, useEffect as useEffect4, useReducer, useRef as useRef3 } from "react";
|
|
1241
1257
|
|
|
@@ -2421,8 +2437,18 @@ function useVideoChatSession(options = {}) {
|
|
|
2421
2437
|
planned[position] = plannedScene;
|
|
2422
2438
|
received[position] = plannedScene;
|
|
2423
2439
|
const rendererReady = currentOptions.templates?.getTemplate(plannedScene.templateId) ? Promise.resolve() : Promise.resolve(preloadBuiltinTemplate(plannedScene.templateId));
|
|
2424
|
-
const visualPreparation =
|
|
2425
|
-
|
|
2440
|
+
const visualPreparation = rendererReady.then(async () => {
|
|
2441
|
+
try {
|
|
2442
|
+
await prepareSceneMedia(plannedScene.variables, controller.signal);
|
|
2443
|
+
return plannedScene;
|
|
2444
|
+
} catch (cause) {
|
|
2445
|
+
if (controller.signal.aborted) throw cause;
|
|
2446
|
+
const override = currentOptions.templates?.getTemplate(plannedScene.templateId);
|
|
2447
|
+
const fallback = !override || supportsExternalVideoBackdrop(override) ? recoverSceneMedia(plannedScene) : void 0;
|
|
2448
|
+
if (!fallback) throw new VideoError("Scene could not prepare its visual", { code: "media_not_ready" });
|
|
2449
|
+
warn(MEDIA_RECOVERY_NOTICE);
|
|
2450
|
+
return fallback;
|
|
2451
|
+
}
|
|
2426
2452
|
});
|
|
2427
2453
|
void visualPreparation.catch(() => void 0);
|
|
2428
2454
|
warmSceneMedia(plannedScene.variables);
|
|
@@ -2451,15 +2477,14 @@ function useVideoChatSession(options = {}) {
|
|
|
2451
2477
|
narrating = narrated.catch(() => "");
|
|
2452
2478
|
pending.push(narrated.then(async (line) => {
|
|
2453
2479
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2454
|
-
const visual = plannedScene;
|
|
2455
|
-
const withNarration = line ? { ...visual, narration: line } : visual;
|
|
2456
2480
|
const spoken = line ? await prepareSpeech(plannedScene.narrationGroup?.text ?? line, controller.signal).catch((cause) => {
|
|
2457
2481
|
if (controller.signal.aborted) throw cause;
|
|
2458
2482
|
warn("Some narration is unavailable; the response will continue.");
|
|
2459
2483
|
return void 0;
|
|
2460
2484
|
}) : void 0;
|
|
2461
2485
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2462
|
-
await visualPreparation;
|
|
2486
|
+
const visual = await visualPreparation;
|
|
2487
|
+
const withNarration = line ? { ...visual, narration: line } : visual;
|
|
2463
2488
|
const group = plannedScene.narrationGroup;
|
|
2464
2489
|
if (group && (spoken?.supportsOffsets !== true || voiceRef.current.supportsOffsets !== true || Math.abs(spoken.seconds - group.totalSeconds) > 0.1)) throw new VideoError("Narration group requires matching measured audio with offset support", { code: "narration_group_invalid" });
|
|
2465
2490
|
ready[position] = group ? withNarration : pacedScene(withNarration, spoken?.seconds, currentOptions.templates);
|
package/dist/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createVideo,
|
|
6
6
|
invokeIsolated
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5BSJLT6H.js";
|
|
8
8
|
import {
|
|
9
9
|
getGenerationLifecycleSink
|
|
10
10
|
} from "./chunk-E7CL7UPB.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
BUILTIN_SERVER_TEMPLATE_KIT,
|
|
13
13
|
createServerTemplateRegistry,
|
|
14
14
|
overlayServerTemplateRegistry
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-IR44XKBI.js";
|
|
16
16
|
import {
|
|
17
17
|
createTemplateSceneValidator,
|
|
18
18
|
validateTemplateSceneStructure
|
|
@@ -47,14 +47,77 @@ import {
|
|
|
47
47
|
import {
|
|
48
48
|
createTemplateSystemPrompt,
|
|
49
49
|
getStandardMediaResolverContract
|
|
50
|
-
} from "./chunk-
|
|
51
|
-
import "./chunk-
|
|
50
|
+
} from "./chunk-JKVOBTRO.js";
|
|
51
|
+
import "./chunk-LVM5Q2DL.js";
|
|
52
52
|
import "./chunk-EGVQODKU.js";
|
|
53
|
-
import "./chunk-
|
|
53
|
+
import "./chunk-IIN5M5HW.js";
|
|
54
54
|
import "./chunk-3O7OMMMF.js";
|
|
55
55
|
import "./chunk-2E6T633S.js";
|
|
56
56
|
import "./chunk-73NTSFFI.js";
|
|
57
57
|
|
|
58
|
+
// src/server/opening-continuity.ts
|
|
59
|
+
function continueAfterOpening(narration, earlier) {
|
|
60
|
+
const words = (value) => Array.from(value.matchAll(/[\p{L}\p{N}]+(?:['’][\p{L}\p{N}]+)*/gu));
|
|
61
|
+
let remaining = narration;
|
|
62
|
+
for (const previous of [...earlier].reverse()) {
|
|
63
|
+
const known = words(previous), current = words(remaining);
|
|
64
|
+
if (known.length < 5 || known.length > current.length) continue;
|
|
65
|
+
if (!known.every((word, index) => word[0].toLowerCase() === current[index][0].toLowerCase())) continue;
|
|
66
|
+
if (known.length === current.length) return "";
|
|
67
|
+
const last = current[known.length - 1];
|
|
68
|
+
const suffix = remaining.slice(last.index + last[0].length);
|
|
69
|
+
const boundary = suffix.match(/^[”"'’\])]*[.!?…]+[\s”"'’\])]*/u);
|
|
70
|
+
if (boundary) remaining = suffix.slice(boundary[0].length).trim();
|
|
71
|
+
}
|
|
72
|
+
return remaining;
|
|
73
|
+
}
|
|
74
|
+
function createOpeningContinuation(initialOpening) {
|
|
75
|
+
const earlier = initialOpening ? [initialOpening] : [];
|
|
76
|
+
let bodyStarted = false;
|
|
77
|
+
const copy = (authored, narration) => {
|
|
78
|
+
if (continueAfterOpening(authored, earlier) !== "") return authored;
|
|
79
|
+
const next = continueAfterOpening(narration, earlier).trim();
|
|
80
|
+
return next && [...next].length <= 65 && /[.!?…][”"’')\]]*$/u.test(next) ? next : authored;
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
copy,
|
|
84
|
+
remember(line) {
|
|
85
|
+
if (line.trim()) earlier.push(line);
|
|
86
|
+
},
|
|
87
|
+
narration(line) {
|
|
88
|
+
return continueAfterOpening(line, earlier);
|
|
89
|
+
},
|
|
90
|
+
line(rawLine) {
|
|
91
|
+
if (bodyStarted || earlier.length === 0) return rawLine;
|
|
92
|
+
let part;
|
|
93
|
+
try {
|
|
94
|
+
part = JSON.parse(rawLine);
|
|
95
|
+
} catch {
|
|
96
|
+
return rawLine;
|
|
97
|
+
}
|
|
98
|
+
if (!part || part.type !== "scene.add" || part.placement === "closer") return rawLine;
|
|
99
|
+
const scene = part.scene;
|
|
100
|
+
if (!scene) return rawLine;
|
|
101
|
+
if (typeof scene.narration !== "string") {
|
|
102
|
+
bodyStarted = true;
|
|
103
|
+
return rawLine;
|
|
104
|
+
}
|
|
105
|
+
const narration = continueAfterOpening(scene.narration, earlier);
|
|
106
|
+
if (!narration && scene.templateId === "cinemaMedia") return null;
|
|
107
|
+
const variables = scene.variables;
|
|
108
|
+
if (!narration && scene.templateId === "chapterTitle" && typeof variables?.title === "string" && !continueAfterOpening(variables.title, earlier)) return null;
|
|
109
|
+
bodyStarted = true;
|
|
110
|
+
if (!narration) return rawLine;
|
|
111
|
+
const field = scene.templateId === "cinemaMedia" ? "fallbackText" : scene.templateId === "chapterTitle" ? "title" : void 0;
|
|
112
|
+
const authored = field && variables?.[field];
|
|
113
|
+
const updated = typeof authored === "string" ? copy(authored, narration) : void 0;
|
|
114
|
+
const copyChanged = typeof authored === "string" && updated !== authored;
|
|
115
|
+
if (narration === scene.narration && !copyChanged) return rawLine;
|
|
116
|
+
return JSON.stringify({ ...part, scene: { ...scene, narration, ...copyChanged ? { variables: { ...variables, [field]: updated } } : {} } });
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
58
121
|
// src/server/request-validation.ts
|
|
59
122
|
var MAX_GENERATED_LOOK_LENGTH = 1e3;
|
|
60
123
|
function boundedString(value, path) {
|
|
@@ -566,7 +629,7 @@ async function resolvePartVariables(options) {
|
|
|
566
629
|
}
|
|
567
630
|
const rendered = { ...variables };
|
|
568
631
|
delete rendered.mediaSource;
|
|
569
|
-
delete rendered.fallbackText;
|
|
632
|
+
if (options.templateId !== "cinemaMedia") delete rendered.fallbackText;
|
|
570
633
|
delete rendered.shotDirection;
|
|
571
634
|
return { ...options.part, scene: { ...options.part.scene, variables: rendered } };
|
|
572
635
|
}
|
|
@@ -865,16 +928,17 @@ function createVideoHandler(options) {
|
|
|
865
928
|
function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlreadyProvided = false, maxGeneratedVideos = 5) {
|
|
866
929
|
return [
|
|
867
930
|
"Respond as a coherent short film: establish, develop, then land a useful or emotional payoff. Match the user's requested form.",
|
|
868
|
-
generatedVideoAvailable ? 'First emit one host-consumed opening JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject","firstShot":{"text":"short grounded fallback","narration":"
|
|
931
|
+
generatedVideoAvailable ? 'First emit one host-consumed opening JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject","firstShot":{"text":"short grounded fallback","narration":"a natural spoken beat advancing the hook","mediaKeyword":"2-8 concrete words","shotDirection":"optional framing and action"}}. Include firstShot only when a distinctive illustrative shot serves the story; omit it for an abstract explanation best opened with graphics. When present, the host inserts it once and begins generation immediately; do not emit it again.' : openingAlreadyProvided ? "Start directly with scene.add; the opening has already been supplied." : 'First emit {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject"}. This is host-consumed, not a scene.',
|
|
869
932
|
openingAlreadyProvided ? "Preserve the supplied opening exactly; continue it without repeating its words or claim." : void 0,
|
|
933
|
+
"The spokenHook is already heard before the first scene. Start firstShot.narration with the next fact, cause, action or consequence; never restate the hook. The first ordinary scene must then advance beyond firstShot rather than repeat its narration or opening claim. If firstShot is omitted, that first scene must advance the hook directly. firstShot.text, fallbackText and chapter titles must also express that new beat, not reuse the hook or prior shot copy; write a concise visual anchor of roughly 2\u20136 words within the schema budget, never a full narration sentence, clipped statistic or qualifying clause.",
|
|
870
934
|
"Continue with only as many scenes as the story and duration need. Never pad to a fixed count. Every emitted scene carries narration beside variables and timing; no additional narration request should be needed.",
|
|
871
|
-
"
|
|
872
|
-
"Use the installed catalog:
|
|
935
|
+
"Make most of the film relevant footage: use full-bleed media for action, subjects and atmosphere, with graphics only to clarify a specific relationship or piece of evidence. Maintain consistent setting, lighting and subject while varying shot scale. Avoid unrelated cinematic montages.",
|
|
936
|
+
"Use the installed catalog: ordered events, comparison, exact quote, one key figure, chapter or a message only when its narrative job fits. For an ordinary explanation, roughly one graphic-led explanatory beat per 30 seconds is a starting point, plus a brief chapter opening when needed. Let other intents use the visual form they need. Do not place graphic beats consecutively unless no honest relevant media is available. Give media-capable graphics a relevant background when it supports the meaning; keep black when no matching media exists. Do not force every template into a video or use lists of bullets.",
|
|
873
937
|
`At most ${generatedVideoAvailable ? maxGeneratedVideos : 0} generated-video attempts are available. Media scenes choose mediaSource=generate for distinctive illustrative shots or mediaSource=stock for generic verified imagery. A stock miss is not permission to broaden essential details. Do not spend generation on every scene.`,
|
|
874
|
-
"Each mediaKeyword is a literal filmable subject/action, 2\u20138 words, maximum 80 characters. Use optional shotDirection for action, camera framing and continuity; it does not change the literal stock subject. Supply a short grounded fallbackText when the schema declares it. Do not put visible headline text over full-bleed footage.",
|
|
938
|
+
"Each mediaKeyword is a literal filmable subject/action, 2\u20138 words, maximum 80 characters. Use optional shotDirection for action, camera framing and continuity; it does not change the literal stock subject. Supply a short grounded fallbackText when the schema declares it: one useful visual anchor, not a second explanation competing with narration or a promise of footage. It must still make sense when the media is absent. Do not put visible headline text over full-bleed footage.",
|
|
875
939
|
"No invented quotations, attribution, statistics, personal evidence or URLs. Creative stories may be invented when requested; do not present generated illustration as historical evidence.",
|
|
876
|
-
'Emit exactly one final placement:"closer" scene using a catalog template with a suitable payoff or ask job. It should land the meaning, not recap the whole answer.',
|
|
877
|
-
"Every scene needs timing, even an empty object. Narration
|
|
940
|
+
'Emit exactly one final placement:"closer" scene using a catalog template with a suitable payoff or ask job. It should land the meaning, not recap the whole answer. Prefer the final relevant action or visual reveal with narration; a closer does not require a black chapter card.',
|
|
941
|
+
"Every scene needs timing, even an empty object. Narration preserves the requested form and tone, uses natural sentence lengths, and does not read every on-screen word back. Finish with plan.complete."
|
|
878
942
|
].filter((line) => line != null).join("\n");
|
|
879
943
|
}
|
|
880
944
|
var VIDEO_CHAT_NARRATION_PROMPT = [
|
|
@@ -882,7 +946,7 @@ var VIDEO_CHAT_NARRATION_PROMPT = [
|
|
|
882
946
|
"You are given the scene about to be shown, and the lines already said before it.",
|
|
883
947
|
"",
|
|
884
948
|
"Return only the line to say over this scene. No JSON, no quotes, no preamble.",
|
|
885
|
-
"
|
|
949
|
+
"Use the requested tone and natural spoken language. Keep this beat concise enough for its planned duration; preserve dialogue, a short punchline or practical instructions when appropriate.",
|
|
886
950
|
"Say only what this scene contributes. Preserve the requested form: narrate a story as a story, give recommendations directly, and explain only when the user asked for an explanation.",
|
|
887
951
|
"Never jump ahead to a later scene or repeat a line already said.",
|
|
888
952
|
"Never read the on-screen text back word for word - the viewer can already see it.",
|
|
@@ -893,7 +957,8 @@ var VIDEO_CHAT_SUGGESTIONS_PROMPT = [
|
|
|
893
957
|
'Return JSON only: {"suggestions": [{"prompt": string, "keyword": string}]} with exactly four entries.',
|
|
894
958
|
"prompt: one clear, specific follow-up in 4\u20138 words. Never exceed 8 words or 60 characters, including spaces. These are small mobile cards, not full instructions.",
|
|
895
959
|
"Ask about one idea. Use context from the answer instead of restating it. No combined questions, preambles, lists of examples, or requests to visualize multiple things.",
|
|
896
|
-
"
|
|
960
|
+
"Treat the entire VIDEO RESPONSE as already answered, including its causes, mechanisms and conclusions. Reject any candidate whose answer is already given there, even as a paraphrase or a narrower question. Before returning, compare all four candidates with that coverage and with one another; replace overlaps with a new consequence, application, counterfactual or adjacent unanswered idea.",
|
|
961
|
+
"Match the response intent: continue or change a fictional story, try a different comic premise, explore another imagined setting, apply a practical method, or deepen an unanswered part of an explanation. Never add a factual explanation to a joke by default.",
|
|
897
962
|
"keyword: two to four words naming something filmable that stands for the prompt, for stock footage search. Concrete subjects only - no abstractions, no text on screen."
|
|
898
963
|
].join("\n");
|
|
899
964
|
function createNarrationUserPrompt(prompt, scene, earlier) {
|
|
@@ -1111,6 +1176,19 @@ function interceptOpeningPlan(source, options) {
|
|
|
1111
1176
|
const textStream = (async function* () {
|
|
1112
1177
|
let buffer = "";
|
|
1113
1178
|
let decided = !options.expectOpening;
|
|
1179
|
+
const continuation = createOpeningContinuation(options.openingLine);
|
|
1180
|
+
const acceptOpening = (opening) => {
|
|
1181
|
+
if (!options.openingProvided) {
|
|
1182
|
+
options.publish(opening.line ? opening : void 0);
|
|
1183
|
+
continuation.remember(opening.line);
|
|
1184
|
+
}
|
|
1185
|
+
if (!options.generatedVideoAvailable || !opening.firstShot) return void 0;
|
|
1186
|
+
const narration = continuation.narration(opening.firstShot.narration);
|
|
1187
|
+
if (!narration) return void 0;
|
|
1188
|
+
const text = continuation.copy(opening.firstShot.text, narration);
|
|
1189
|
+
continuation.remember(narration);
|
|
1190
|
+
return JSON.stringify(reservedFirstScene(options.requestId, { ...opening.firstShot, text, narration }));
|
|
1191
|
+
};
|
|
1114
1192
|
try {
|
|
1115
1193
|
for await (const delta of upstream) {
|
|
1116
1194
|
if (typeof delta !== "string") throw new Error("The LLM adapter returned a non-text delta");
|
|
@@ -1124,17 +1202,16 @@ function interceptOpeningPlan(source, options) {
|
|
|
1124
1202
|
const opening = readOpeningPlanLine(line);
|
|
1125
1203
|
decided = true;
|
|
1126
1204
|
if (opening) {
|
|
1127
|
-
|
|
1128
|
-
if (
|
|
1129
|
-
yield `${JSON.stringify(reservedFirstScene(options.requestId, opening.firstShot))}
|
|
1205
|
+
const firstScene = acceptOpening(opening);
|
|
1206
|
+
if (firstScene) yield `${firstScene}
|
|
1130
1207
|
`;
|
|
1131
|
-
}
|
|
1132
1208
|
newline = buffer.indexOf("\n");
|
|
1133
1209
|
continue;
|
|
1134
1210
|
}
|
|
1135
1211
|
options.publish(void 0);
|
|
1136
1212
|
}
|
|
1137
|
-
|
|
1213
|
+
const continued = continuation.line(rawLine);
|
|
1214
|
+
if (continued != null) yield `${continued}
|
|
1138
1215
|
`;
|
|
1139
1216
|
newline = buffer.indexOf("\n");
|
|
1140
1217
|
}
|
|
@@ -1145,17 +1222,17 @@ function interceptOpeningPlan(source, options) {
|
|
|
1145
1222
|
const opening = readOpeningPlanLine(line);
|
|
1146
1223
|
decided = true;
|
|
1147
1224
|
if (opening) {
|
|
1148
|
-
|
|
1149
|
-
if (
|
|
1150
|
-
yield `${JSON.stringify(reservedFirstScene(options.requestId, opening.firstShot))}
|
|
1225
|
+
const firstScene = acceptOpening(opening);
|
|
1226
|
+
if (firstScene) yield `${firstScene}
|
|
1151
1227
|
`;
|
|
1152
|
-
}
|
|
1153
1228
|
} else {
|
|
1154
1229
|
options.publish(void 0);
|
|
1155
|
-
|
|
1230
|
+
const continued = continuation.line(buffer);
|
|
1231
|
+
if (continued != null) yield continued;
|
|
1156
1232
|
}
|
|
1157
1233
|
} else {
|
|
1158
|
-
|
|
1234
|
+
const continued = continuation.line(buffer);
|
|
1235
|
+
if (continued != null) yield continued;
|
|
1159
1236
|
}
|
|
1160
1237
|
}
|
|
1161
1238
|
} finally {
|
|
@@ -1332,7 +1409,7 @@ function createVideoChatHandler(options) {
|
|
|
1332
1409
|
const heroQuery = welcomeOptions?.heroQuery;
|
|
1333
1410
|
let welcomeResponse;
|
|
1334
1411
|
let requestSequence = 0;
|
|
1335
|
-
const responseHandler = (requestId, openingProvided, openingChannel) => {
|
|
1412
|
+
const responseHandler = (requestId, openingProvided, openingChannel, openingLine) => {
|
|
1336
1413
|
const generatedVideoAvailable = generateVideo != null && maxGeneratedVideos > 0;
|
|
1337
1414
|
let lifecycle;
|
|
1338
1415
|
let generatedAttempts = 0;
|
|
@@ -1400,6 +1477,7 @@ function createVideoChatHandler(options) {
|
|
|
1400
1477
|
return interceptOpeningPlan(videoOptions.streamText(context), {
|
|
1401
1478
|
expectOpening: generatedVideoAvailable || !openingProvided,
|
|
1402
1479
|
openingProvided,
|
|
1480
|
+
openingLine,
|
|
1403
1481
|
requestId,
|
|
1404
1482
|
generatedVideoAvailable,
|
|
1405
1483
|
publish: openingChannel.publish
|
|
@@ -1569,7 +1647,8 @@ function createVideoChatHandler(options) {
|
|
|
1569
1647
|
const response = await responseHandler(
|
|
1570
1648
|
requestId,
|
|
1571
1649
|
input.opening != null,
|
|
1572
|
-
openingChannel
|
|
1650
|
+
openingChannel,
|
|
1651
|
+
input.opening
|
|
1573
1652
|
)(videoRequest);
|
|
1574
1653
|
return streamVideoChatOpening(response, openingChannel.ready);
|
|
1575
1654
|
}
|