@vanillaskyai/video 0.8.8 → 0.10.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 +42 -0
- package/PUBLIC-API.md +5 -8
- package/dist/{builtin-server-CUYPIDE7.js → builtin-server-W4PLJUZ5.js} +2 -2
- package/dist/{catalog-types-BIhSpOWK.d.ts → catalog-types-WTbLP6Jh.d.ts} +5 -1
- package/dist/chapter-title-YRLIQY3V.js +7 -0
- package/dist/check-runtime.js +5 -14
- package/dist/{chunk-AITKH6QT.js → chunk-2HFO22K2.js} +15 -40
- package/dist/chunk-4YM2M62S.js +13 -0
- package/dist/chunk-4ZJLPHBV.js +684 -0
- package/dist/chunk-77VY4O7A.js +390 -0
- package/dist/{chunk-MMUXVA47.js → chunk-7M56IUUX.js} +9 -8
- package/dist/chunk-7NZOMTAL.js +63 -0
- package/dist/{chunk-2DOLF4QN.js → chunk-AUPC6MDK.js} +12 -14
- package/dist/{chunk-352ZZCET.js → chunk-EGVQODKU.js} +2 -2
- package/dist/{chunk-2USVB3CY.js → chunk-ELVOT2FQ.js} +284 -177
- package/dist/chunk-EWWCQFTI.js +18 -0
- package/dist/{chunk-KT3M7KDL.js → chunk-HKUI5BTB.js} +3 -3
- package/dist/chunk-IIN5M5HW.js +697 -0
- package/dist/{chunk-LMG3GHLL.js → chunk-IQMYK5DX.js} +29 -3
- package/dist/{chunk-OWBT45EN.js → chunk-IR44XKBI.js} +1 -1
- package/dist/{chunk-MXZSDGZQ.js → chunk-NMFDC2W2.js} +4 -6
- package/dist/{chunk-5SLENAJW.js → chunk-PAJ44TFW.js} +1 -2
- package/dist/{chunk-L7QGGKJ2.js → chunk-PDQFQIQW.js} +1 -1
- package/dist/{chunk-WJ6FLKNC.js → chunk-R3XAOMKP.js} +14 -0
- package/dist/{chunk-2OKB27TG.js → chunk-RXTN2CW6.js} +2 -2
- package/dist/{chunk-XWWLKRNU.js → chunk-TVIU23OM.js} +38 -49
- package/dist/chunk-XX4GXKTX.js +35 -0
- package/dist/chunk-Z67Q5KUW.js +16 -0
- package/dist/{chunk-3VRMKFM7.js → chunk-ZDJNFA52.js} +11 -24
- package/dist/cinema-media-C7OUPNSH.js +12 -0
- package/dist/cli.js +18 -9
- package/dist/comparison-GNZOR7MF.js +37 -0
- package/dist/compose-video-TBFTONUC.js +12 -0
- package/dist/editorial-timeline-EMV5335S.js +43 -0
- package/dist/{events-BQ6z264n.d.ts → events-B4YCc4vc.d.ts} +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.js +3 -7
- package/dist/key-figure-P37VK425.js +30 -0
- package/dist/{kit-tQnCPMOM.d.ts → kit-DrRpdn0p.d.ts} +2 -2
- package/dist/mobile-message-OF7T62B7.js +50 -0
- package/dist/quote-T6WYLN7A.js +33 -0
- package/dist/react.d.ts +21 -11
- package/dist/react.js +585 -826
- package/dist/scene-video-backdrop-PCTARAAJ.js +7 -0
- package/dist/server.d.ts +7 -8
- package/dist/server.js +179 -84
- package/dist/{system-prompt-RRXIWDDD.js → system-prompt-SYRT5DWM.js} +1 -1
- package/dist/template-catalog.d.ts +392 -16
- package/dist/template-catalog.js +1 -1
- package/dist/templates.d.ts +5 -5
- package/dist/test.d.ts +3 -3
- package/dist/test.js +18 -18
- package/dist/text-stream-LD364KBI.js +9 -0
- package/dist/{types-DWFO6qca.d.ts → types-CdBmNTGD.d.ts} +1 -1
- package/dist/{types-CEh9eUFu.d.ts → types-DdZw4GRQ.d.ts} +17 -48
- package/dist/validate-4OUD2CLX.js +10 -0
- package/docs/architecture.md +1 -1
- package/docs/concepts.md +1 -1
- package/docs/custom-templates.md +13 -28
- package/docs/customization.md +11 -59
- package/docs/getting-started.md +2 -2
- package/docs/immersive-interface.md +3 -4
- package/docs/maintainers/cinematic-migration.md +38 -0
- package/docs/media-and-audio.md +14 -14
- package/docs/motion-and-effects.md +6 -14
- package/docs/persistence.md +3 -3
- package/docs/production.md +11 -9
- package/docs/provider-integration.md +9 -16
- package/docs/reference/design-system.html +1 -1
- package/docs/reference/protocol.md +14 -4
- package/docs/testing.md +1 -1
- package/package.json +6 -3
- package/registry/items/backgrounds.json +1 -1
- package/registry/items/chapterTitle.json +80 -0
- package/registry/items/cinemaMedia.json +136 -0
- package/registry/items/comparison.json +168 -0
- package/registry/items/editorialTimeline.json +172 -0
- package/registry/items/keyFigure.json +162 -0
- package/registry/items/mobileMessage.json +153 -0
- package/registry/items/quote.json +161 -0
- package/registry/items/template-context.json +1 -1
- package/registry/items/theme.json +1 -1
- package/starters/video-chat/README.md +24 -26
- package/starters/video-chat/package.json +1 -1
- package/starters/video-chat/providers/video.ts +6 -5
- package/starters/video-chat/stock.ts +35 -151
- package/styles/fonts/OFL.txt +93 -0
- package/styles/fonts/manifest.json +66 -0
- package/styles/fonts/roboto-v51-cyrillic-ext.woff2 +0 -0
- package/styles/fonts/roboto-v51-cyrillic.woff2 +0 -0
- package/styles/fonts/roboto-v51-greek-ext.woff2 +0 -0
- package/styles/fonts/roboto-v51-greek.woff2 +0 -0
- package/styles/fonts/roboto-v51-latin-ext.woff2 +0 -0
- package/styles/fonts/roboto-v51-latin.woff2 +0 -0
- package/styles/fonts/roboto-v51-math.woff2 +0 -0
- package/styles/fonts/roboto-v51-symbols.woff2 +0 -0
- package/styles/fonts/roboto-v51-vietnamese.woff2 +0 -0
- package/styles/fonts/roboto.css +91 -0
- package/styles/video-chat.css +30 -22
- package/dist/bg-confetti-HITCGLGD.js +0 -89
- package/dist/bg-emoji-VU4UZYZN.js +0 -150
- package/dist/bg-media-L34PDQXJ.js +0 -106
- package/dist/brand-message-FUJ4STFO.js +0 -256
- package/dist/chart-bar-EGPHJATL.js +0 -310
- package/dist/chart-counter-5FFWQNZO.js +0 -306
- package/dist/chart-progress-ring-JMXQLMLC.js +0 -302
- package/dist/chunk-4UNKZEAT.js +0 -3575
- package/dist/chunk-5CDAM24P.js +0 -31
- package/dist/chunk-AOWSXU2K.js +0 -18
- package/dist/chunk-CJ7F2CE6.js +0 -104
- package/dist/chunk-FRN6WKHA.js +0 -185
- package/dist/chunk-G5JPHBX7.js +0 -336
- package/dist/chunk-L5BTTCZV.js +0 -97
- package/dist/chunk-P22HHFDK.js +0 -43
- package/dist/chunk-PP3MCPN4.js +0 -1998
- package/dist/chunk-QAAO7OAY.js +0 -19
- package/dist/chunk-S7HFLYNN.js +0 -35
- package/dist/chunk-W3VDIBGR.js +0 -217
- package/dist/chunk-XGRA2MUV.js +0 -242
- package/dist/chunk-XTOXFMLM.js +0 -105
- package/dist/chunk-YJJC4N4D.js +0 -761
- package/dist/compose-video-2NFH4DZY.js +0 -13
- package/dist/cta-logo-YR7LYUR5.js +0 -212
- package/dist/cta-media-DQJBKUO2.js +0 -221
- package/dist/incoming-call-54KQ7O5A.js +0 -323
- package/dist/infographic-before-after-ROS52GOW.js +0 -460
- package/dist/infographic-feature-list-E7MGUDAN.js +0 -272
- package/dist/infographic-problem-solution-3S6NGO5N.js +0 -343
- package/dist/infographic-stat-row-SYB7AB7H.js +0 -247
- package/dist/infographic-steps-KCE6JWE2.js +0 -406
- package/dist/prompt-input-MHX4O42G.js +0 -239
- package/dist/reaction-SLEGR3BE.js +0 -122
- package/dist/scene-video-backdrop-XFPX4O3I.js +0 -10
- package/dist/showcase-code-2HLAVKBM.js +0 -357
- package/dist/showcase-phone-Q4HEO4LH.js +0 -278
- package/dist/showcase-terminal-DL45LUFG.js +0 -312
- package/dist/showcase-web-2QZO73FZ.js +0 -396
- package/dist/social-conversation-75SD2637.js +0 -695
- package/dist/social-milestone-GEFUQSBF.js +0 -296
- package/dist/social-notification-FJ5VHVLK.js +0 -247
- package/dist/social-review-stack-KXQCKFRP.js +0 -242
- package/dist/social-testimonial-JZ7CHC7S.js +0 -287
- package/dist/social-tweet-UCDXM25F.js +0 -298
- package/dist/text-stream-54LXABJW.js +0 -10
- package/dist/validate-T7GBU2YF.js +0 -8
- package/registry/items/barChart.json +0 -245
- package/registry/items/beforeAfter.json +0 -180
- package/registry/items/bigNumber.json +0 -232
- package/registry/items/brandMessage.json +0 -164
- package/registry/items/cardList.json +0 -214
- package/registry/items/chatMessenger.json +0 -151
- package/registry/items/chatWhatsapp.json +0 -151
- package/registry/items/codeEditor.json +0 -191
- package/registry/items/confetti.json +0 -107
- package/registry/items/ctaLogo.json +0 -167
- package/registry/items/ctaMedia.json +0 -200
- package/registry/items/emojiBurst.json +0 -101
- package/registry/items/incomingCall.json +0 -177
- package/registry/items/media.json +0 -187
- package/registry/items/milestone.json +0 -212
- package/registry/items/notification.json +0 -189
- package/registry/items/phoneMockup.json +0 -261
- package/registry/items/problemSolution.json +0 -193
- package/registry/items/progressRing.json +0 -206
- package/registry/items/promptInput.json +0 -155
- package/registry/items/reaction.json +0 -176
- package/registry/items/reviewStack.json +0 -231
- package/registry/items/steps.json +0 -215
- package/registry/items/terminal.json +0 -219
- package/registry/items/testimonial.json +0 -171
- package/registry/items/tripleStats.json +0 -235
- package/registry/items/tweet.json +0 -210
- package/registry/items/webMockup.json +0 -280
package/dist/react.js
CHANGED
|
@@ -1,51 +1,55 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
TitleSceneTemplate
|
|
3
|
+
} from "./chunk-EWWCQFTI.js";
|
|
4
|
+
import "./chunk-4YM2M62S.js";
|
|
5
|
+
import {
|
|
6
|
+
getSceneDuration,
|
|
7
|
+
getSceneDurationBounds
|
|
3
8
|
} from "./chunk-ZD2VTUYR.js";
|
|
4
9
|
import {
|
|
5
10
|
MEDIA_RECOVERY_NOTICE,
|
|
6
11
|
decodeVideoSse,
|
|
7
12
|
sanitizeVideoChatMedia,
|
|
8
13
|
withDeadline
|
|
9
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-HKUI5BTB.js";
|
|
10
15
|
import {
|
|
11
16
|
applyVideoEvent,
|
|
12
17
|
checksumVideo,
|
|
13
18
|
createVideoEventFactory,
|
|
14
19
|
createVideoState
|
|
15
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-RXTN2CW6.js";
|
|
16
21
|
import {
|
|
17
22
|
safePublicDiagnostic
|
|
18
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-7M56IUUX.js";
|
|
19
24
|
import {
|
|
20
25
|
parseVideo
|
|
21
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-AUPC6MDK.js";
|
|
22
27
|
import {
|
|
23
|
-
VIDEO_SCHEMA_VERSION
|
|
24
|
-
|
|
28
|
+
VIDEO_SCHEMA_VERSION,
|
|
29
|
+
validateNarrationGroups
|
|
30
|
+
} from "./chunk-7NZOMTAL.js";
|
|
25
31
|
import {
|
|
26
32
|
getBuiltinTemplateMetadata
|
|
27
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-IIN5M5HW.js";
|
|
28
34
|
import "./chunk-3O7OMMMF.js";
|
|
29
35
|
import {
|
|
30
36
|
assertTemplateTransitionMetadata
|
|
31
37
|
} from "./chunk-2E6T633S.js";
|
|
32
38
|
import {
|
|
39
|
+
MountedReadinessContext,
|
|
33
40
|
VideoFrame,
|
|
34
|
-
getDimensions
|
|
35
|
-
|
|
41
|
+
getDimensions,
|
|
42
|
+
sceneReadinessKey
|
|
43
|
+
} from "./chunk-ELVOT2FQ.js";
|
|
36
44
|
import "./chunk-224QNWRA.js";
|
|
37
|
-
import "./chunk-
|
|
38
|
-
import {
|
|
39
|
-
fontStack
|
|
40
|
-
} from "./chunk-XWWLKRNU.js";
|
|
45
|
+
import "./chunk-TVIU23OM.js";
|
|
41
46
|
import {
|
|
42
47
|
getVideoDuration,
|
|
43
48
|
resolveVideoTimeline
|
|
44
49
|
} from "./chunk-SPVTJH3F.js";
|
|
45
50
|
import {
|
|
46
|
-
|
|
47
|
-
} from "./chunk-
|
|
48
|
-
import "./chunk-WJ6FLKNC.js";
|
|
51
|
+
markExternalVideoBackdropTemplate
|
|
52
|
+
} from "./chunk-R3XAOMKP.js";
|
|
49
53
|
import "./chunk-73NTSFFI.js";
|
|
50
54
|
|
|
51
55
|
// src/player/video-player.tsx
|
|
@@ -89,582 +93,98 @@ import { createElement } from "react";
|
|
|
89
93
|
|
|
90
94
|
// src/visual-system/catalog/builtin-loaders.generated.ts
|
|
91
95
|
var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"phoneMockup": () => import("./showcase-phone-Q4HEO4LH.js").then((module) => ({ default: module.ShowcasePhoneTemplate })),
|
|
100
|
-
"webMockup": () => import("./showcase-web-2QZO73FZ.js").then((module) => ({ default: module.ShowcaseWebTemplate })),
|
|
101
|
-
"codeEditor": () => import("./showcase-code-2HLAVKBM.js").then((module) => ({ default: module.ShowcaseCodeTemplate })),
|
|
102
|
-
"terminal": () => import("./showcase-terminal-DL45LUFG.js").then((module) => ({ default: module.ShowcaseTerminalTemplate })),
|
|
103
|
-
"tweet": () => import("./social-tweet-UCDXM25F.js").then((module) => ({ default: module.SocialTweetTemplate })),
|
|
104
|
-
"notification": () => import("./social-notification-FJ5VHVLK.js").then((module) => ({ default: module.SocialNotificationTemplate })),
|
|
105
|
-
"chatMessenger": () => import("./social-conversation-75SD2637.js").then((module) => ({ default: module.SocialConversationTemplate })),
|
|
106
|
-
"chatWhatsapp": () => import("./social-conversation-75SD2637.js").then((module) => ({ default: module.SocialConversationTemplate })),
|
|
107
|
-
"milestone": () => import("./social-milestone-GEFUQSBF.js").then((module) => ({ default: module.SocialMilestoneTemplate })),
|
|
108
|
-
"reviewStack": () => import("./social-review-stack-KXQCKFRP.js").then((module) => ({ default: module.SocialReviewStackTemplate })),
|
|
109
|
-
"testimonial": () => import("./social-testimonial-JZ7CHC7S.js").then((module) => ({ default: module.SocialTestimonialTemplate })),
|
|
110
|
-
"incomingCall": () => import("./incoming-call-54KQ7O5A.js").then((module) => ({ default: module.IncomingCallTemplate })),
|
|
111
|
-
"brandMessage": () => import("./brand-message-FUJ4STFO.js").then((module) => ({ default: module.BrandMessageTemplate })),
|
|
112
|
-
"promptInput": () => import("./prompt-input-MHX4O42G.js").then((module) => ({ default: module.PromptInputTemplate })),
|
|
113
|
-
"beforeAfter": () => import("./infographic-before-after-ROS52GOW.js").then((module) => ({ default: module.InfographicBeforeAfterTemplate })),
|
|
114
|
-
"tripleStats": () => import("./infographic-stat-row-SYB7AB7H.js").then((module) => ({ default: module.InfographicStatRowTemplate })),
|
|
115
|
-
"problemSolution": () => import("./infographic-problem-solution-3S6NGO5N.js").then((module) => ({ default: module.InfographicProblemSolutionTemplate })),
|
|
116
|
-
"cardList": () => import("./infographic-feature-list-E7MGUDAN.js").then((module) => ({ default: module.InfographicFeatureListTemplate })),
|
|
117
|
-
"steps": () => import("./infographic-steps-KCE6JWE2.js").then((module) => ({ default: module.InfographicStepsTemplate })),
|
|
118
|
-
"ctaLogo": () => import("./cta-logo-YR7LYUR5.js").then((module) => ({ default: module.CtaLogoTemplate })),
|
|
119
|
-
"ctaMedia": () => import("./cta-media-DQJBKUO2.js").then((module) => ({ default: module.CtaMediaTemplate }))
|
|
96
|
+
"cinemaMedia": () => import("./cinema-media-C7OUPNSH.js").then((module) => ({ default: module.MediaSceneTemplate })),
|
|
97
|
+
"chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
|
|
98
|
+
"editorialTimeline": () => import("./editorial-timeline-EMV5335S.js").then((module) => ({ default: module.TimelineSceneTemplate })),
|
|
99
|
+
"mobileMessage": () => import("./mobile-message-OF7T62B7.js").then((module) => ({ default: module.NotificationSceneTemplate })),
|
|
100
|
+
"comparison": () => import("./comparison-GNZOR7MF.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
|
|
101
|
+
"quote": () => import("./quote-T6WYLN7A.js").then((module) => ({ default: module.QuoteSceneTemplate })),
|
|
102
|
+
"keyFigure": () => import("./key-figure-P37VK425.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
|
|
120
103
|
};
|
|
121
104
|
|
|
122
105
|
// src/visual-system/catalog/builtin-player.generated.ts
|
|
123
106
|
var GENERATED_BUILTIN_PLAYER_TEMPLATES = [
|
|
124
107
|
{
|
|
125
|
-
"id": "
|
|
126
|
-
"defaults": {
|
|
127
|
-
"texts": "Make an impact.",
|
|
128
|
-
"mediaUrl": "",
|
|
129
|
-
"mediaKeyword": "",
|
|
130
|
-
"mediaType": "auto",
|
|
131
|
-
"mediaPoster": "",
|
|
132
|
-
"mediaPosition": "center",
|
|
133
|
-
"mediaTreatment": "cinematic",
|
|
134
|
-
"confetti": false
|
|
135
|
-
},
|
|
136
|
-
"usesGlobalTransition": true,
|
|
137
|
-
"transitionTiming": {
|
|
138
|
-
"entryReadyProgress": 0.2,
|
|
139
|
-
"holdProgress": 0.7
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
"id": "reaction",
|
|
108
|
+
"id": "cinemaMedia",
|
|
144
109
|
"defaults": {
|
|
145
|
-
"texts": "Still editing manually?",
|
|
146
|
-
"reactionTag": "manual",
|
|
147
110
|
"mediaUrl": "",
|
|
148
111
|
"mediaType": "video",
|
|
149
112
|
"mediaPoster": ""
|
|
150
113
|
},
|
|
151
|
-
"usesGlobalTransition":
|
|
152
|
-
"transitionTiming": {
|
|
153
|
-
"entryReadyProgress": 0.2,
|
|
154
|
-
"holdProgress": 0.7
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
"id": "confetti",
|
|
159
|
-
"defaults": {
|
|
160
|
-
"texts": "Celebrate."
|
|
161
|
-
},
|
|
162
|
-
"usesGlobalTransition": true,
|
|
163
|
-
"transitionTiming": {
|
|
164
|
-
"entryReadyProgress": 0.2,
|
|
165
|
-
"holdProgress": 0.7
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
"id": "emojiBurst",
|
|
170
|
-
"defaults": {
|
|
171
|
-
"texts": "Let's go!"
|
|
172
|
-
},
|
|
173
|
-
"usesGlobalTransition": true,
|
|
174
|
-
"transitionTiming": {
|
|
175
|
-
"entryReadyProgress": 0.2,
|
|
176
|
-
"holdProgress": 0.7
|
|
177
|
-
}
|
|
114
|
+
"usesGlobalTransition": false
|
|
178
115
|
},
|
|
179
116
|
{
|
|
180
|
-
"id": "
|
|
117
|
+
"id": "chapterTitle",
|
|
181
118
|
"defaults": {
|
|
182
|
-
"
|
|
183
|
-
"value": 1e3,
|
|
184
|
-
"confetti": false,
|
|
185
|
-
"label": "Total users",
|
|
186
|
-
"prefix": "",
|
|
187
|
-
"unit": "",
|
|
188
|
-
"mediaUrl": "",
|
|
189
|
-
"mediaKeyword": "",
|
|
190
|
-
"mediaType": "auto",
|
|
191
|
-
"mediaPoster": "",
|
|
192
|
-
"mediaPosition": "center",
|
|
193
|
-
"mediaTreatment": "cinematic"
|
|
119
|
+
"title": "A different perspective"
|
|
194
120
|
},
|
|
195
|
-
"usesGlobalTransition":
|
|
196
|
-
"transitionTiming": {
|
|
197
|
-
"entryReadyProgress": 0.2,
|
|
198
|
-
"holdProgress": 0.7
|
|
199
|
-
}
|
|
121
|
+
"usesGlobalTransition": false
|
|
200
122
|
},
|
|
201
123
|
{
|
|
202
|
-
"id": "
|
|
124
|
+
"id": "editorialTimeline",
|
|
203
125
|
"defaults": {
|
|
204
|
-
"
|
|
205
|
-
"bars": [
|
|
206
|
-
{
|
|
207
|
-
"label": "Q1",
|
|
208
|
-
"value": 42
|
|
209
|
-
},
|
|
126
|
+
"events": [
|
|
210
127
|
{
|
|
211
|
-
"label": "
|
|
212
|
-
"value": 58
|
|
128
|
+
"label": "Observe"
|
|
213
129
|
},
|
|
214
130
|
{
|
|
215
|
-
"label": "
|
|
216
|
-
"value": 76
|
|
131
|
+
"label": "Understand"
|
|
217
132
|
},
|
|
218
133
|
{
|
|
219
|
-
"label": "
|
|
220
|
-
"value": 91
|
|
134
|
+
"label": "Act"
|
|
221
135
|
}
|
|
222
136
|
],
|
|
223
137
|
"mediaUrl": "",
|
|
224
|
-
"
|
|
225
|
-
"
|
|
226
|
-
"mediaPoster": "",
|
|
227
|
-
"mediaPosition": "center",
|
|
228
|
-
"mediaTreatment": "cinematic"
|
|
229
|
-
},
|
|
230
|
-
"usesGlobalTransition": true,
|
|
231
|
-
"transitionTiming": {
|
|
232
|
-
"entryReadyProgress": 0.2,
|
|
233
|
-
"holdProgress": 0.7
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
"id": "progressRing",
|
|
238
|
-
"defaults": {
|
|
239
|
-
"texts": "Almost there.",
|
|
240
|
-
"value": 75,
|
|
241
|
-
"label": "Complete",
|
|
242
|
-
"unit": "%",
|
|
243
|
-
"mediaUrl": "",
|
|
244
|
-
"mediaKeyword": "",
|
|
245
|
-
"mediaType": "auto",
|
|
246
|
-
"mediaPoster": "",
|
|
247
|
-
"mediaPosition": "center",
|
|
248
|
-
"mediaTreatment": "cinematic"
|
|
249
|
-
},
|
|
250
|
-
"usesGlobalTransition": true,
|
|
251
|
-
"transitionTiming": {
|
|
252
|
-
"entryReadyProgress": 0.2,
|
|
253
|
-
"holdProgress": 0.7
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
"id": "phoneMockup",
|
|
258
|
-
"defaults": {
|
|
259
|
-
"texts": "See it in action.",
|
|
260
|
-
"screenMediaUrl": "",
|
|
261
|
-
"screen1Url": "",
|
|
262
|
-
"screen2Url": "",
|
|
263
|
-
"screenFit": "cover",
|
|
264
|
-
"screenFocusX": 50,
|
|
265
|
-
"screenFocusY": 50,
|
|
266
|
-
"screenMotion": "pushIn",
|
|
267
|
-
"screenCalloutText": "",
|
|
268
|
-
"screenCalloutX": 70,
|
|
269
|
-
"screenCalloutY": 35,
|
|
270
|
-
"mediaUrl": "",
|
|
271
|
-
"mediaKeyword": "",
|
|
272
|
-
"mediaType": "auto",
|
|
273
|
-
"mediaPoster": "",
|
|
274
|
-
"mediaPosition": "center",
|
|
275
|
-
"mediaTreatment": "cinematic"
|
|
276
|
-
},
|
|
277
|
-
"usesGlobalTransition": true,
|
|
278
|
-
"transitionTiming": {
|
|
279
|
-
"entryReadyProgress": 0.2,
|
|
280
|
-
"holdProgress": 0.7
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
"id": "webMockup",
|
|
285
|
-
"defaults": {
|
|
286
|
-
"frame": "browser",
|
|
287
|
-
"texts": "See it in action.",
|
|
288
|
-
"screenMediaUrl": "",
|
|
289
|
-
"screen1Url": "",
|
|
290
|
-
"screen2Url": "",
|
|
291
|
-
"screenFit": "cover",
|
|
292
|
-
"screenFocusX": 50,
|
|
293
|
-
"screenFocusY": 50,
|
|
294
|
-
"screenMotion": "pushIn",
|
|
295
|
-
"screenCalloutText": "",
|
|
296
|
-
"screenCalloutX": 70,
|
|
297
|
-
"screenCalloutY": 35,
|
|
298
|
-
"addressBarUrl": "",
|
|
299
|
-
"mediaUrl": "",
|
|
300
|
-
"mediaKeyword": "",
|
|
301
|
-
"mediaType": "auto",
|
|
302
|
-
"mediaPoster": "",
|
|
303
|
-
"mediaPosition": "center",
|
|
304
|
-
"mediaTreatment": "cinematic"
|
|
305
|
-
},
|
|
306
|
-
"usesGlobalTransition": true,
|
|
307
|
-
"transitionTiming": {
|
|
308
|
-
"entryReadyProgress": 0.2,
|
|
309
|
-
"holdProgress": 0.7
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
"id": "codeEditor",
|
|
314
|
-
"defaults": {
|
|
315
|
-
"texts": "Simple as this.",
|
|
316
|
-
"code": 'import { getVideoDuration } from "@vanillaskyai/video";\nimport type { Video } from "@vanillaskyai/video";\n\nexport function duration(video: Video) {\n return getVideoDuration(video);\n}',
|
|
317
|
-
"filename": "app.ts",
|
|
318
|
-
"mediaUrl": "",
|
|
319
|
-
"mediaKeyword": "",
|
|
320
|
-
"mediaType": "auto",
|
|
321
|
-
"mediaPoster": "",
|
|
322
|
-
"mediaPosition": "center",
|
|
323
|
-
"mediaTreatment": "cinematic"
|
|
324
|
-
},
|
|
325
|
-
"usesGlobalTransition": true,
|
|
326
|
-
"transitionTiming": {
|
|
327
|
-
"entryReadyProgress": 0.2,
|
|
328
|
-
"holdProgress": 0.7
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
{
|
|
332
|
-
"id": "terminal",
|
|
333
|
-
"defaults": {
|
|
334
|
-
"texts": "Ship it.",
|
|
335
|
-
"command": "npx vanillasky templates check",
|
|
336
|
-
"output": [],
|
|
337
|
-
"promptPrefix": "$",
|
|
338
|
-
"mediaUrl": "",
|
|
339
|
-
"mediaKeyword": "",
|
|
340
|
-
"mediaType": "auto",
|
|
341
|
-
"mediaPoster": "",
|
|
342
|
-
"mediaPosition": "center",
|
|
343
|
-
"mediaTreatment": "cinematic"
|
|
344
|
-
},
|
|
345
|
-
"usesGlobalTransition": true,
|
|
346
|
-
"transitionTiming": {
|
|
347
|
-
"entryReadyProgress": 0.2,
|
|
348
|
-
"holdProgress": 0.7
|
|
349
|
-
}
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
"id": "tweet",
|
|
353
|
-
"defaults": {
|
|
354
|
-
"authorName": "Your brand",
|
|
355
|
-
"authorHandle": "",
|
|
356
|
-
"authorVerified": false,
|
|
357
|
-
"message": "Just shipped the new feature \u{1F680}",
|
|
358
|
-
"likes": 0,
|
|
359
|
-
"replies": 0,
|
|
360
|
-
"mediaUrl": "",
|
|
361
|
-
"mediaKeyword": "",
|
|
362
|
-
"mediaType": "auto",
|
|
363
|
-
"mediaPoster": "",
|
|
364
|
-
"mediaPosition": "center",
|
|
365
|
-
"mediaTreatment": "cinematic"
|
|
366
|
-
},
|
|
367
|
-
"usesGlobalTransition": true,
|
|
368
|
-
"transitionTiming": {
|
|
369
|
-
"entryReadyProgress": 0.2,
|
|
370
|
-
"holdProgress": 0.7
|
|
371
|
-
}
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
"id": "notification",
|
|
375
|
-
"defaults": {
|
|
376
|
-
"appName": "Reminder",
|
|
377
|
-
"appIcon": "\u{1F514}",
|
|
378
|
-
"message": "Believe in your dreams, they will lead you.",
|
|
379
|
-
"mediaUrl": "",
|
|
380
|
-
"mediaKeyword": "",
|
|
381
|
-
"mediaType": "auto",
|
|
382
|
-
"mediaPoster": "",
|
|
383
|
-
"mediaPosition": "center",
|
|
384
|
-
"mediaTreatment": "cinematic"
|
|
385
|
-
},
|
|
386
|
-
"usesGlobalTransition": true,
|
|
387
|
-
"transitionTiming": {
|
|
388
|
-
"entryReadyProgress": 0.35,
|
|
389
|
-
"holdProgress": 0.7
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
"id": "chatMessenger",
|
|
394
|
-
"defaults": {
|
|
395
|
-
"msg1": "Have you tried it yet?",
|
|
396
|
-
"msg2": "Yes \u2014 it saves me hours|out",
|
|
397
|
-
"msg3": "Okay, send me the link",
|
|
398
|
-
"msg4": "",
|
|
399
|
-
"dateChip1": "",
|
|
400
|
-
"dateChip2": "",
|
|
401
|
-
"msg5": "",
|
|
402
|
-
"theme": "messenger"
|
|
403
|
-
},
|
|
404
|
-
"usesGlobalTransition": false
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
"id": "chatWhatsapp",
|
|
408
|
-
"defaults": {
|
|
409
|
-
"msg1": "Have you tried it yet?",
|
|
410
|
-
"msg2": "Yes \u2014 it saves me hours|out",
|
|
411
|
-
"msg3": "Okay, send me the link",
|
|
412
|
-
"msg4": "",
|
|
413
|
-
"dateChip1": "",
|
|
414
|
-
"dateChip2": "",
|
|
415
|
-
"msg5": "",
|
|
416
|
-
"theme": "whatsapp"
|
|
417
|
-
},
|
|
418
|
-
"usesGlobalTransition": false
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
"id": "milestone",
|
|
422
|
-
"defaults": {
|
|
423
|
-
"label": "Followers",
|
|
424
|
-
"targetNumber": 1e4,
|
|
425
|
-
"startNumber": 0,
|
|
426
|
-
"badgeText": "",
|
|
427
|
-
"badgeEmoji": "\u{1F389}",
|
|
428
|
-
"mediaUrl": "",
|
|
429
|
-
"mediaKeyword": "",
|
|
430
|
-
"mediaType": "auto",
|
|
431
|
-
"mediaPoster": "",
|
|
432
|
-
"mediaPosition": "center",
|
|
433
|
-
"mediaTreatment": "cinematic"
|
|
434
|
-
},
|
|
435
|
-
"usesGlobalTransition": false
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
"id": "reviewStack",
|
|
439
|
-
"defaults": {
|
|
440
|
-
"review1Title": "Life changing app",
|
|
441
|
-
"review1Body": "",
|
|
442
|
-
"review1Author": "",
|
|
443
|
-
"review2Title": "Best in class",
|
|
444
|
-
"review2Body": "",
|
|
445
|
-
"review2Author": "",
|
|
446
|
-
"review3Title": "Exceeded expectations",
|
|
447
|
-
"review3Body": "",
|
|
448
|
-
"review3Author": "",
|
|
449
|
-
"mediaUrl": "",
|
|
450
|
-
"mediaKeyword": "",
|
|
451
|
-
"mediaType": "auto",
|
|
452
|
-
"mediaPoster": "",
|
|
453
|
-
"mediaPosition": "center",
|
|
454
|
-
"mediaTreatment": "cinematic"
|
|
455
|
-
},
|
|
456
|
-
"usesGlobalTransition": false
|
|
457
|
-
},
|
|
458
|
-
{
|
|
459
|
-
"id": "testimonial",
|
|
460
|
-
"defaults": {
|
|
461
|
-
"quote": "Best decision I made this year. Our conversion rate doubled in the first week.",
|
|
462
|
-
"authorName": "Jessica Torres",
|
|
463
|
-
"authorRole": "",
|
|
464
|
-
"mediaUrl": "",
|
|
465
|
-
"mediaKeyword": "",
|
|
466
|
-
"mediaType": "auto",
|
|
467
|
-
"mediaPoster": "",
|
|
468
|
-
"mediaPosition": "center",
|
|
469
|
-
"mediaTreatment": "cinematic"
|
|
138
|
+
"mediaType": "video",
|
|
139
|
+
"mediaPoster": ""
|
|
470
140
|
},
|
|
471
141
|
"usesGlobalTransition": false
|
|
472
142
|
},
|
|
473
143
|
{
|
|
474
|
-
"id": "
|
|
144
|
+
"id": "mobileMessage",
|
|
475
145
|
"defaults": {
|
|
476
|
-
"callerName": "Your brand",
|
|
477
|
-
"subtitle": "is calling....",
|
|
478
|
-
"declineLabel": "Decline",
|
|
479
|
-
"acceptLabel": "Accept",
|
|
480
146
|
"mediaUrl": "",
|
|
481
|
-
"
|
|
482
|
-
"mediaType": "auto",
|
|
147
|
+
"mediaType": "video",
|
|
483
148
|
"mediaPoster": "",
|
|
484
|
-
"
|
|
485
|
-
"
|
|
149
|
+
"message": "Can we talk?",
|
|
150
|
+
"app": "Messages"
|
|
486
151
|
},
|
|
487
152
|
"usesGlobalTransition": false
|
|
488
153
|
},
|
|
489
154
|
{
|
|
490
|
-
"id": "
|
|
155
|
+
"id": "comparison",
|
|
491
156
|
"defaults": {
|
|
492
|
-
"
|
|
157
|
+
"leftText": "More distractions",
|
|
158
|
+
"rightText": "More room to think",
|
|
493
159
|
"mediaUrl": "",
|
|
494
|
-
"
|
|
495
|
-
"
|
|
496
|
-
"mediaPoster": "",
|
|
497
|
-
"mediaPosition": "center",
|
|
498
|
-
"mediaTreatment": "cinematic"
|
|
160
|
+
"mediaType": "video",
|
|
161
|
+
"mediaPoster": ""
|
|
499
162
|
},
|
|
500
163
|
"usesGlobalTransition": false
|
|
501
164
|
},
|
|
502
165
|
{
|
|
503
|
-
"id": "
|
|
166
|
+
"id": "quote",
|
|
504
167
|
"defaults": {
|
|
505
|
-
"promptText": "Make a launch video for our app",
|
|
506
168
|
"mediaUrl": "",
|
|
507
|
-
"
|
|
508
|
-
"
|
|
509
|
-
"mediaPoster": "",
|
|
510
|
-
"mediaPosition": "center",
|
|
511
|
-
"mediaTreatment": "cinematic"
|
|
512
|
-
},
|
|
513
|
-
"usesGlobalTransition": false
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
"id": "beforeAfter",
|
|
517
|
-
"defaults": {
|
|
518
|
-
"problemLabel": "BEFORE",
|
|
519
|
-
"problemHeadline": "Your calendar today.",
|
|
520
|
-
"solutionLabel": "AFTER",
|
|
521
|
-
"solutionHeadline": "Calmly organized.",
|
|
522
|
-
"problemEmojis": [
|
|
523
|
-
"\u{1F4C5}",
|
|
524
|
-
"\u{1F630}",
|
|
525
|
-
"\u{1F4BC}",
|
|
526
|
-
"\u{1F4CA}",
|
|
527
|
-
"\u23F0",
|
|
528
|
-
"\u{1F4AC}",
|
|
529
|
-
"\u{1F4DE}",
|
|
530
|
-
"\u{1F514}"
|
|
531
|
-
],
|
|
532
|
-
"solutionEmojis": [
|
|
533
|
-
"\u2728",
|
|
534
|
-
"\u{1F4CB}",
|
|
535
|
-
"\u2705",
|
|
536
|
-
"\u{1F3AF}"
|
|
537
|
-
],
|
|
538
|
-
"showEmojis": true
|
|
169
|
+
"mediaType": "video",
|
|
170
|
+
"mediaPoster": ""
|
|
539
171
|
},
|
|
540
172
|
"usesGlobalTransition": false
|
|
541
173
|
},
|
|
542
174
|
{
|
|
543
|
-
"id": "
|
|
175
|
+
"id": "keyFigure",
|
|
544
176
|
"defaults": {
|
|
545
|
-
"texts": "By the numbers.",
|
|
546
|
-
"stat1Value": "10K",
|
|
547
|
-
"stat1Label": "Users",
|
|
548
|
-
"stat2Value": "99.9%",
|
|
549
|
-
"stat2Label": "Uptime",
|
|
550
|
-
"stat3Value": "<50ms",
|
|
551
|
-
"stat3Label": "Latency",
|
|
552
177
|
"mediaUrl": "",
|
|
553
|
-
"
|
|
554
|
-
"
|
|
555
|
-
"mediaPoster": "",
|
|
556
|
-
"mediaPosition": "center",
|
|
557
|
-
"mediaTreatment": "cinematic"
|
|
558
|
-
},
|
|
559
|
-
"usesGlobalTransition": true,
|
|
560
|
-
"transitionTiming": {
|
|
561
|
-
"entryReadyProgress": 0.2,
|
|
562
|
-
"holdProgress": 0.7
|
|
563
|
-
}
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
"id": "problemSolution",
|
|
567
|
-
"defaults": {
|
|
568
|
-
"problemLabel": "THE PROBLEM",
|
|
569
|
-
"problemText": "Teams waste 40% of time in meetings",
|
|
570
|
-
"solutionLabel": "THE SOLUTION",
|
|
571
|
-
"solutionText": "AI that summarizes in seconds",
|
|
572
|
-
"mediaUrl": "",
|
|
573
|
-
"mediaKeyword": "",
|
|
574
|
-
"mediaType": "auto",
|
|
575
|
-
"mediaPoster": "",
|
|
576
|
-
"mediaPosition": "center",
|
|
577
|
-
"mediaTreatment": "cinematic"
|
|
178
|
+
"mediaType": "video",
|
|
179
|
+
"mediaPoster": ""
|
|
578
180
|
},
|
|
579
181
|
"usesGlobalTransition": false
|
|
580
|
-
},
|
|
581
|
-
{
|
|
582
|
-
"id": "cardList",
|
|
583
|
-
"defaults": {
|
|
584
|
-
"texts": "What you get.",
|
|
585
|
-
"items": [
|
|
586
|
-
"Automate your savings on energy bills",
|
|
587
|
-
"Cleaner energy without raising your bill",
|
|
588
|
-
"Same account and service guaranteed"
|
|
589
|
-
],
|
|
590
|
-
"itemEmojis": [],
|
|
591
|
-
"mediaUrl": "",
|
|
592
|
-
"mediaKeyword": "",
|
|
593
|
-
"mediaType": "auto",
|
|
594
|
-
"mediaPoster": "",
|
|
595
|
-
"mediaPosition": "center",
|
|
596
|
-
"mediaTreatment": "cinematic"
|
|
597
|
-
},
|
|
598
|
-
"usesGlobalTransition": true,
|
|
599
|
-
"transitionTiming": {
|
|
600
|
-
"entryReadyProgress": 0.2,
|
|
601
|
-
"holdProgress": 0.7
|
|
602
|
-
}
|
|
603
|
-
},
|
|
604
|
-
{
|
|
605
|
-
"id": "steps",
|
|
606
|
-
"defaults": {
|
|
607
|
-
"texts": "How it works.",
|
|
608
|
-
"steps": [
|
|
609
|
-
"Describe",
|
|
610
|
-
"Preview",
|
|
611
|
-
"Export"
|
|
612
|
-
],
|
|
613
|
-
"stepEmojis": [],
|
|
614
|
-
"mediaUrl": "",
|
|
615
|
-
"mediaKeyword": "",
|
|
616
|
-
"mediaType": "auto",
|
|
617
|
-
"mediaPoster": "",
|
|
618
|
-
"mediaPosition": "center",
|
|
619
|
-
"mediaTreatment": "cinematic"
|
|
620
|
-
},
|
|
621
|
-
"usesGlobalTransition": true,
|
|
622
|
-
"transitionTiming": {
|
|
623
|
-
"entryReadyProgress": 0.2,
|
|
624
|
-
"holdProgress": 0.7
|
|
625
|
-
}
|
|
626
|
-
},
|
|
627
|
-
{
|
|
628
|
-
"id": "ctaLogo",
|
|
629
|
-
"defaults": {
|
|
630
|
-
"url": "",
|
|
631
|
-
"cta": "",
|
|
632
|
-
"mediaUrl": "",
|
|
633
|
-
"mediaKeyword": "",
|
|
634
|
-
"mediaType": "auto",
|
|
635
|
-
"mediaPoster": "",
|
|
636
|
-
"mediaPosition": "center",
|
|
637
|
-
"mediaTreatment": "cinematic"
|
|
638
|
-
},
|
|
639
|
-
"usesGlobalTransition": true,
|
|
640
|
-
"transitionTiming": {
|
|
641
|
-
"entryReadyProgress": 0.2,
|
|
642
|
-
"holdProgress": 0.7
|
|
643
|
-
}
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
"id": "ctaMedia",
|
|
647
|
-
"defaults": {
|
|
648
|
-
"headline": "Make every moment count",
|
|
649
|
-
"url": "",
|
|
650
|
-
"cta": "",
|
|
651
|
-
"mediaUrl": "",
|
|
652
|
-
"mediaKeyword": "",
|
|
653
|
-
"mediaType": "auto",
|
|
654
|
-
"mediaPoster": "",
|
|
655
|
-
"mediaPosition": "center",
|
|
656
|
-
"mediaTreatment": "cinematic"
|
|
657
|
-
},
|
|
658
|
-
"usesGlobalTransition": true,
|
|
659
|
-
"transitionTiming": {
|
|
660
|
-
"entryReadyProgress": 0.2,
|
|
661
|
-
"holdProgress": 0.7
|
|
662
|
-
}
|
|
663
182
|
}
|
|
664
183
|
];
|
|
184
|
+
var GENERATED_BUILTIN_VIDEO_BACKDROP_IDS = ["cinemaMedia", "editorialTimeline", "mobileMessage", "comparison", "quote", "keyFigure"];
|
|
665
185
|
|
|
666
186
|
// src/visual-system/catalog/builtin-player.ts
|
|
667
|
-
function createPreloadableBuiltinTemplate(loader) {
|
|
187
|
+
function createPreloadableBuiltinTemplate(loader, mediaBackdrop) {
|
|
668
188
|
let loaded;
|
|
669
189
|
let failure;
|
|
670
190
|
let pending;
|
|
@@ -687,7 +207,8 @@ function createPreloadableBuiltinTemplate(loader) {
|
|
|
687
207
|
if (failure) throw failure;
|
|
688
208
|
throw preload();
|
|
689
209
|
};
|
|
690
|
-
|
|
210
|
+
const template = { component, preload };
|
|
211
|
+
return mediaBackdrop ? markExternalVideoBackdropTemplate(template) : template;
|
|
691
212
|
}
|
|
692
213
|
function freezeValue(value) {
|
|
693
214
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -699,7 +220,7 @@ function freezeValue(value) {
|
|
|
699
220
|
var preloadableById = new Map(
|
|
700
221
|
GENERATED_BUILTIN_PLAYER_TEMPLATES.map(({ id }) => [
|
|
701
222
|
id,
|
|
702
|
-
createPreloadableBuiltinTemplate(GENERATED_BUILTIN_TEMPLATE_LOADERS[id])
|
|
223
|
+
createPreloadableBuiltinTemplate(GENERATED_BUILTIN_TEMPLATE_LOADERS[id], GENERATED_BUILTIN_VIDEO_BACKDROP_IDS.includes(id))
|
|
703
224
|
])
|
|
704
225
|
);
|
|
705
226
|
var builtinPlayerTemplates = Object.freeze(
|
|
@@ -817,8 +338,6 @@ function GenerationCover({
|
|
|
817
338
|
onStart
|
|
818
339
|
}) {
|
|
819
340
|
if (!visible) return null;
|
|
820
|
-
const background = config?.style.brand.background;
|
|
821
|
-
const backgroundCss = background?.type === "solid" ? background.color : `linear-gradient(135deg, ${background?.colors[0] ?? "#8711C1"}, ${background?.colors[1] ?? "#2167E3"})`;
|
|
822
341
|
return /* @__PURE__ */ jsxs(
|
|
823
342
|
"div",
|
|
824
343
|
{
|
|
@@ -832,9 +351,9 @@ function GenerationCover({
|
|
|
832
351
|
placeItems: "center",
|
|
833
352
|
padding: "12%",
|
|
834
353
|
textAlign: "center",
|
|
835
|
-
color:
|
|
836
|
-
background:
|
|
837
|
-
fontFamily:
|
|
354
|
+
color: "#fff",
|
|
355
|
+
background: "#000",
|
|
356
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif'
|
|
838
357
|
},
|
|
839
358
|
children: [
|
|
840
359
|
/* @__PURE__ */ jsxs("div", { style: { position: "absolute", top: "28%", left: "10%", right: "10%" }, children: [
|
|
@@ -1013,6 +532,8 @@ function usePlaybackClock({
|
|
|
1013
532
|
audioRef,
|
|
1014
533
|
loopRef,
|
|
1015
534
|
sceneIndexRef,
|
|
535
|
+
visualReadyRef,
|
|
536
|
+
posterBridgeKeysRef,
|
|
1016
537
|
callbacksRef,
|
|
1017
538
|
setCurrentTime,
|
|
1018
539
|
setIsPlaying
|
|
@@ -1020,6 +541,16 @@ function usePlaybackClock({
|
|
|
1020
541
|
useEffect(() => {
|
|
1021
542
|
if (!isPlaying) return;
|
|
1022
543
|
let stalled = false;
|
|
544
|
+
let onsetWaitSeconds = 0;
|
|
545
|
+
let clockWaitSeconds = 0;
|
|
546
|
+
let lastNarrationTime;
|
|
547
|
+
const failNarration = (error, state) => {
|
|
548
|
+
setIsPlaying(false);
|
|
549
|
+
try {
|
|
550
|
+
void Promise.resolve(callbacksRef.current.onError?.(error, state)).catch(() => void 0);
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
};
|
|
1023
554
|
const reportStall = (next) => {
|
|
1024
555
|
if (stalled === next) return;
|
|
1025
556
|
stalled = next;
|
|
@@ -1033,14 +564,46 @@ function usePlaybackClock({
|
|
|
1033
564
|
const tick = (now) => {
|
|
1034
565
|
const current = stateRef.current;
|
|
1035
566
|
const config = current.config;
|
|
1036
|
-
const
|
|
567
|
+
const elapsed = Math.max(0, (now - previous) / 1e3);
|
|
568
|
+
let narrationReady = true;
|
|
569
|
+
try {
|
|
570
|
+
narrationReady = callbacksRef.current.narrationReady?.() !== false;
|
|
571
|
+
} catch (cause) {
|
|
572
|
+
failNarration(cause instanceof Error ? cause : new Error("Narration readiness failed"), current);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
onsetWaitSeconds = narrationReady ? 0 : onsetWaitSeconds + elapsed;
|
|
576
|
+
if (onsetWaitSeconds >= 8) {
|
|
577
|
+
failNarration(new Error("Narration did not report audio onset within eight seconds"), current);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const delta = narrationReady ? elapsed : 0;
|
|
1037
581
|
previous = now;
|
|
1038
582
|
const settled = current.status === "complete" || current.status === "error" || current.status === "aborted";
|
|
1039
583
|
const looping = loopRef.current && settled;
|
|
1040
584
|
let wrapped = false;
|
|
1041
585
|
if (config?.scenes.length) {
|
|
1042
586
|
const duration2 = getVideoDuration(config);
|
|
1043
|
-
const
|
|
587
|
+
const ranges = resolveVideoTimeline(config);
|
|
588
|
+
const cued = ranges[sceneIndexRef.current];
|
|
589
|
+
let narrationTime;
|
|
590
|
+
try {
|
|
591
|
+
narrationTime = cued ? callbacksRef.current.narrationTime?.(cued.scene) : void 0;
|
|
592
|
+
} catch (cause) {
|
|
593
|
+
failNarration(cause instanceof Error ? cause : new Error("Narration clock failed"), current);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (narrationTime !== void 0 && (!Number.isFinite(narrationTime) || narrationTime < 0)) {
|
|
597
|
+
failNarration(new Error("Narration clock must return finite nonnegative seconds"), current);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
clockWaitSeconds = narrationTime !== void 0 && narrationTime === lastNarrationTime && narrationReady && !stalled ? clockWaitSeconds + elapsed : 0;
|
|
601
|
+
lastNarrationTime = narrationTime;
|
|
602
|
+
if (clockWaitSeconds >= 8) {
|
|
603
|
+
failNarration(new Error("Narration audio clock did not advance within eight seconds"), current);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const raw = narrationTime !== void 0 && cued ? cued.start - (cued.scene.narrationGroup?.offsetSeconds ?? 0) + narrationTime : timeRef.current + delta;
|
|
1044
607
|
let nextTime;
|
|
1045
608
|
if (looping && duration2 > 0 && raw >= duration2) {
|
|
1046
609
|
nextTime = raw % duration2;
|
|
@@ -1048,6 +611,9 @@ function usePlaybackClock({
|
|
|
1048
611
|
} else {
|
|
1049
612
|
nextTime = Math.min(raw, duration2);
|
|
1050
613
|
}
|
|
614
|
+
const target = ranges.find((range) => nextTime >= range.start && nextTime < range.end) ?? ranges.at(-1);
|
|
615
|
+
const waitingForVisual = Boolean(target && visualReadyRef && visualReadyRef.current !== sceneReadinessKey(target.scene) && !posterBridgeKeysRef?.current.has(sceneReadinessKey(target.scene)));
|
|
616
|
+
if (waitingForVisual && target) nextTime = target.start;
|
|
1051
617
|
if (nextTime !== timeRef.current) {
|
|
1052
618
|
timeRef.current = nextTime;
|
|
1053
619
|
setCurrentTime(nextTime);
|
|
@@ -1065,19 +631,20 @@ function usePlaybackClock({
|
|
|
1065
631
|
if (remaining <= 0 && !looping) audio.pause();
|
|
1066
632
|
}
|
|
1067
633
|
const notify = callbacksRef.current.onSceneChange;
|
|
1068
|
-
if (notify) {
|
|
634
|
+
if (notify && !waitingForVisual) {
|
|
1069
635
|
if (wrapped) sceneIndexRef.current = -1;
|
|
1070
|
-
const
|
|
1071
|
-
const index =
|
|
1072
|
-
const resolved = index === -1 && nextTime >= duration2 ?
|
|
636
|
+
const ranges2 = resolveVideoTimeline(config);
|
|
637
|
+
const index = ranges2.findIndex((range) => nextTime >= range.start && nextTime < range.end);
|
|
638
|
+
const resolved = index === -1 && nextTime >= duration2 ? ranges2.length - 1 : index;
|
|
1073
639
|
if (resolved !== -1 && resolved !== sceneIndexRef.current) {
|
|
1074
640
|
sceneIndexRef.current = resolved;
|
|
1075
|
-
notify(
|
|
641
|
+
notify(ranges2[resolved].scene, resolved);
|
|
1076
642
|
}
|
|
1077
643
|
}
|
|
1078
644
|
}
|
|
1079
645
|
const duration = current.config ? getVideoDuration(current.config) : 0;
|
|
1080
|
-
|
|
646
|
+
const active = current.config ? resolveVideoTimeline(current.config).find((range) => timeRef.current >= range.start && timeRef.current < range.end) : void 0;
|
|
647
|
+
reportStall(Boolean(active && visualReadyRef && visualReadyRef.current !== sceneReadinessKey(active.scene) && !posterBridgeKeysRef?.current.has(sceneReadinessKey(active.scene))) || !settled && Boolean(current.config?.scenes.length) && duration > 0 && timeRef.current >= duration);
|
|
1081
648
|
if (!settled || looping || timeRef.current < duration) frame = requestAnimationFrame(tick);
|
|
1082
649
|
else setIsPlaying(false);
|
|
1083
650
|
};
|
|
@@ -1120,7 +687,10 @@ function VideoPlayerRuntime({
|
|
|
1120
687
|
onComplete,
|
|
1121
688
|
onError,
|
|
1122
689
|
onSceneChange,
|
|
690
|
+
narrationReady,
|
|
691
|
+
narrationTime,
|
|
1123
692
|
onFramePresented,
|
|
693
|
+
onMediaFramePresented,
|
|
1124
694
|
onStallChange,
|
|
1125
695
|
onStateChange
|
|
1126
696
|
}) {
|
|
@@ -1146,6 +716,7 @@ function VideoPlayerRuntime({
|
|
|
1146
716
|
const [state, setState] = useState(() => video ? savedVideoState(video) : createVideoState());
|
|
1147
717
|
const [currentTime, setCurrentTime] = useState(0);
|
|
1148
718
|
const [activeStream, setActiveStream] = useState(stream);
|
|
719
|
+
const [activeSavedVideo, setActiveSavedVideo] = useState(video);
|
|
1149
720
|
const [replacementPending, setReplacementPending] = useState(false);
|
|
1150
721
|
const [startRequested, setStartRequested] = useState(autoStartGeneration);
|
|
1151
722
|
const [introPlaying, setIntroPlaying] = useState(autoStartGeneration);
|
|
@@ -1156,13 +727,35 @@ function VideoPlayerRuntime({
|
|
|
1156
727
|
const timeRef = useRef(currentTime);
|
|
1157
728
|
const audioRef = useRef(null);
|
|
1158
729
|
const introStartedAtRef = useRef(autoStartGeneration ? performance.now() : null);
|
|
1159
|
-
const callbacksRef = useRef({ onComplete, onPlaybackEnd, onError, onSceneChange, onFramePresented, onStallChange, onStateChange });
|
|
730
|
+
const callbacksRef = useRef({ onComplete, onPlaybackEnd, onError, onSceneChange, narrationReady, narrationTime, onFramePresented, onMediaFramePresented, onStallChange, onStateChange });
|
|
1160
731
|
const loopRef = useRef(loop);
|
|
1161
732
|
const sceneIndexRef = useRef(-1);
|
|
733
|
+
const mediaFrameReportedRef = useRef(false);
|
|
734
|
+
const posterBridgeKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
735
|
+
const visualReadyRef = useRef(void 0);
|
|
736
|
+
const reportVisualReady = useMemo(() => (key, error, actualVideoFrame = false, posterBridge = false) => {
|
|
737
|
+
if (posterBridge) {
|
|
738
|
+
posterBridgeKeysRef.current.add(key);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (error) {
|
|
742
|
+
setIsPlaying(false);
|
|
743
|
+
callbacksRef.current.onError?.(error, stateRef.current);
|
|
744
|
+
} else {
|
|
745
|
+
visualReadyRef.current = key;
|
|
746
|
+
if (actualVideoFrame && !mediaFrameReportedRef.current) {
|
|
747
|
+
mediaFrameReportedRef.current = true;
|
|
748
|
+
try {
|
|
749
|
+
void Promise.resolve(callbacksRef.current.onMediaFramePresented?.()).catch(() => void 0);
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}, []);
|
|
1162
755
|
const playbackEndedRef = useRef(false);
|
|
1163
756
|
stateRef.current = state;
|
|
1164
757
|
timeRef.current = currentTime;
|
|
1165
|
-
callbacksRef.current = { onComplete, onPlaybackEnd, onError, onSceneChange, onFramePresented, onStallChange, onStateChange };
|
|
758
|
+
callbacksRef.current = { onComplete, onPlaybackEnd, onError, onSceneChange, narrationReady, narrationTime, onFramePresented, onMediaFramePresented, onStallChange, onStateChange };
|
|
1166
759
|
loopRef.current = loop;
|
|
1167
760
|
const reportFramePresented = useMemo(() => {
|
|
1168
761
|
let reported = false;
|
|
@@ -1191,9 +784,13 @@ function VideoPlayerRuntime({
|
|
|
1191
784
|
output.default(audio, context);
|
|
1192
785
|
}).catch(() => context.close());
|
|
1193
786
|
};
|
|
1194
|
-
if (stream !== activeStream) {
|
|
787
|
+
if (stream !== activeStream || video !== activeSavedVideo) {
|
|
1195
788
|
const autoStartReplacement = Boolean(playbackMode && stream && shouldAutoPlay && !reducedMotion);
|
|
1196
789
|
setActiveStream(stream);
|
|
790
|
+
setActiveSavedVideo(video);
|
|
791
|
+
mediaFrameReportedRef.current = false;
|
|
792
|
+
posterBridgeKeysRef.current.clear();
|
|
793
|
+
sceneIndexRef.current = -1;
|
|
1197
794
|
setReplacementPending(stream != null);
|
|
1198
795
|
setState(video ? savedVideoState(video) : createVideoState());
|
|
1199
796
|
setCurrentTime(0);
|
|
@@ -1310,6 +907,8 @@ function VideoPlayerRuntime({
|
|
|
1310
907
|
audioRef,
|
|
1311
908
|
loopRef,
|
|
1312
909
|
sceneIndexRef,
|
|
910
|
+
visualReadyRef,
|
|
911
|
+
posterBridgeKeysRef,
|
|
1313
912
|
callbacksRef,
|
|
1314
913
|
setCurrentTime,
|
|
1315
914
|
setIsPlaying
|
|
@@ -1522,7 +1121,7 @@ function VideoPlayerRuntime({
|
|
|
1522
1121
|
onStart: armPlayback
|
|
1523
1122
|
}
|
|
1524
1123
|
),
|
|
1525
|
-
!generationCoverVisible && config?.scenes.length ? /* @__PURE__ */ jsx2(
|
|
1124
|
+
!generationCoverVisible && config?.scenes.length ? /* @__PURE__ */ jsx2(MountedReadinessContext.Provider, { value: reportVisualReady, children: /* @__PURE__ */ jsx2(
|
|
1526
1125
|
VideoFrame,
|
|
1527
1126
|
{
|
|
1528
1127
|
kit,
|
|
@@ -1542,7 +1141,7 @@ function VideoPlayerRuntime({
|
|
|
1542
1141
|
transformOrigin: "top left"
|
|
1543
1142
|
}
|
|
1544
1143
|
}
|
|
1545
|
-
) : null,
|
|
1144
|
+
) }) : null,
|
|
1546
1145
|
/* @__PURE__ */ jsx2(
|
|
1547
1146
|
StartPosterButton,
|
|
1548
1147
|
{
|
|
@@ -1748,17 +1347,55 @@ function createSceneTimeline(options) {
|
|
|
1748
1347
|
};
|
|
1749
1348
|
}
|
|
1750
1349
|
|
|
1350
|
+
// src/player/scene-readiness.ts
|
|
1351
|
+
var READY_AHEAD_SECONDS = 8;
|
|
1352
|
+
function canStartPreparedSequence(scenes, complete) {
|
|
1353
|
+
let seconds = 0;
|
|
1354
|
+
let count = 0;
|
|
1355
|
+
for (const scene of scenes) {
|
|
1356
|
+
if (!scene) break;
|
|
1357
|
+
count += 1;
|
|
1358
|
+
seconds += scene.timing.fixedDuration ?? 0;
|
|
1359
|
+
if (seconds >= READY_AHEAD_SECONDS) return true;
|
|
1360
|
+
}
|
|
1361
|
+
return complete && count > 0 && count === scenes.length;
|
|
1362
|
+
}
|
|
1363
|
+
function preparedSceneDuration(scene, spokenSeconds, metadata) {
|
|
1364
|
+
const timing = metadata?.timing;
|
|
1365
|
+
const authored = (timing?.revealSeconds ?? 0) + (timing?.holdSeconds ?? 0) + (timing?.exitSeconds ?? 0);
|
|
1366
|
+
const readable = Math.max(getSceneDurationBounds(scene, metadata).readable, authored);
|
|
1367
|
+
return Number.isFinite(spokenSeconds) && spokenSeconds > 0 ? Math.max(readable, spokenSeconds + 0.8) : Math.max(readable, getSceneDuration(scene, metadata));
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1751
1370
|
// src/player/use-narration.ts
|
|
1752
1371
|
import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
|
|
1753
1372
|
function useNarration(options) {
|
|
1754
1373
|
const [speaking, setSpeaking] = useState2(false);
|
|
1755
1374
|
const optionsRef = useRef2(options);
|
|
1756
1375
|
optionsRef.current = options;
|
|
1376
|
+
const groupRef = useRef2(void 0);
|
|
1377
|
+
const clockRef = useRef2(void 0);
|
|
1378
|
+
const clockSceneRef = useRef2(void 0);
|
|
1379
|
+
const readyRef = useRef2(true);
|
|
1380
|
+
const getTime = useCallback((scene) => {
|
|
1381
|
+
const group = groupRef.current;
|
|
1382
|
+
if (optionsRef.current.enabled === false || (group ? scene.narrationGroup?.id !== group.id : scene.id !== clockSceneRef.current)) return void 0;
|
|
1383
|
+
if (!optionsRef.current.voice.getCurrentTime) return void 0;
|
|
1384
|
+
const time = currentRef.current ? optionsRef.current.voice.getCurrentTime() : void 0;
|
|
1385
|
+
if (currentRef.current && readyRef.current && time === void 0) return void 0;
|
|
1386
|
+
if (time !== void 0 && Number.isFinite(time)) clockRef.current = Math.min(group?.totalSeconds ?? Infinity, Math.max(group?.offsetSeconds ?? 0, time));
|
|
1387
|
+
return clockRef.current;
|
|
1388
|
+
}, []);
|
|
1389
|
+
const isReady = useCallback(() => optionsRef.current.enabled === false || readyRef.current || clockRef.current !== void 0 && clockRef.current >= (groupRef.current?.offsetSeconds ?? 0) + 0.04, []);
|
|
1757
1390
|
const currentRef = useRef2(void 0);
|
|
1758
1391
|
const spokenIndexRef = useRef2(void 0);
|
|
1759
1392
|
const stop = useCallback(() => {
|
|
1393
|
+
readyRef.current = true;
|
|
1760
1394
|
currentRef.current?.abort();
|
|
1761
1395
|
currentRef.current = void 0;
|
|
1396
|
+
groupRef.current = void 0;
|
|
1397
|
+
clockRef.current = void 0;
|
|
1398
|
+
clockSceneRef.current = void 0;
|
|
1762
1399
|
setSpeaking(false);
|
|
1763
1400
|
}, []);
|
|
1764
1401
|
const interrupt = useCallback(() => {
|
|
@@ -1770,37 +1407,103 @@ function useNarration(options) {
|
|
|
1770
1407
|
const { voice, enabled = true } = optionsRef.current;
|
|
1771
1408
|
if (!enabled) return;
|
|
1772
1409
|
if (spokenIndexRef.current === index) return;
|
|
1410
|
+
const group = scene.narrationGroup;
|
|
1411
|
+
if (group && voice.supportsOffsets !== true) {
|
|
1412
|
+
stop();
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (group && currentRef.current && groupRef.current?.id === group.id && groupRef.current.text === group.text && spokenIndexRef.current === index - 1) {
|
|
1416
|
+
spokenIndexRef.current = index;
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1773
1419
|
stop();
|
|
1774
1420
|
spokenIndexRef.current = index;
|
|
1775
|
-
|
|
1776
|
-
|
|
1421
|
+
groupRef.current = group;
|
|
1422
|
+
clockRef.current = group?.offsetSeconds ?? 0;
|
|
1423
|
+
clockSceneRef.current = scene.id;
|
|
1424
|
+
const line = group?.text ?? scene.narration?.trim();
|
|
1425
|
+
if (!line) {
|
|
1426
|
+
clockRef.current = void 0;
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1777
1429
|
const controller = new AbortController();
|
|
1778
1430
|
currentRef.current = controller;
|
|
1431
|
+
readyRef.current = !group && !voice.getCurrentTime;
|
|
1779
1432
|
setSpeaking(true);
|
|
1780
1433
|
void (async () => {
|
|
1781
1434
|
try {
|
|
1782
1435
|
let started = false;
|
|
1783
1436
|
await voice.speak(line, {
|
|
1784
1437
|
signal: controller.signal,
|
|
1438
|
+
...group ? { offsetSeconds: group.offsetSeconds } : {},
|
|
1785
1439
|
onStart: (source) => {
|
|
1786
|
-
if (started || controller.signal.aborted || currentRef.current !== controller || spokenIndexRef.current !== index || optionsRef.current.enabled === false) return;
|
|
1440
|
+
if (started || controller.signal.aborted || currentRef.current !== controller || !group && spokenIndexRef.current !== index || optionsRef.current.enabled === false) return;
|
|
1787
1441
|
started = true;
|
|
1442
|
+
readyRef.current = true;
|
|
1788
1443
|
try {
|
|
1789
1444
|
void Promise.resolve(optionsRef.current.onSpeechStart?.(source)).catch(() => void 0);
|
|
1790
1445
|
} catch {
|
|
1791
1446
|
}
|
|
1792
1447
|
}
|
|
1793
1448
|
});
|
|
1449
|
+
if (currentRef.current === controller && !controller.signal.aborted) {
|
|
1450
|
+
clockRef.current = group && started ? group.totalSeconds : void 0;
|
|
1451
|
+
}
|
|
1794
1452
|
} catch {
|
|
1453
|
+
if (currentRef.current === controller) {
|
|
1454
|
+
groupRef.current = void 0;
|
|
1455
|
+
clockRef.current = void 0;
|
|
1456
|
+
}
|
|
1795
1457
|
} finally {
|
|
1796
1458
|
if (currentRef.current === controller) {
|
|
1797
1459
|
currentRef.current = void 0;
|
|
1460
|
+
readyRef.current = true;
|
|
1798
1461
|
setSpeaking(false);
|
|
1799
1462
|
}
|
|
1800
1463
|
}
|
|
1801
1464
|
})();
|
|
1802
1465
|
}, [stop]);
|
|
1803
|
-
return { onSceneChange, interrupt, speaking };
|
|
1466
|
+
return { onSceneChange, interrupt, speaking, isReady, getTime };
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/player/prepare-scene-media.ts
|
|
1470
|
+
async function prepareSceneMedia(variables, signal) {
|
|
1471
|
+
signal.throwIfAborted();
|
|
1472
|
+
const url = typeof variables.mediaUrl === "string" ? variables.mediaUrl.trim() : "";
|
|
1473
|
+
if (!url || variables.mediaType === "gradient") return "graphic";
|
|
1474
|
+
const video = variables.mediaType === "video" || /\.(mp4|webm|mov)(?:[?#]|$)/i.test(url);
|
|
1475
|
+
const poster = typeof variables.mediaPoster === "string" ? variables.mediaPoster.trim() : "";
|
|
1476
|
+
if (video && !poster) return "awaiting-video-frame";
|
|
1477
|
+
let posterFailed = false;
|
|
1478
|
+
await new Promise((resolve, reject) => {
|
|
1479
|
+
const image = new Image();
|
|
1480
|
+
let settled = false;
|
|
1481
|
+
const finish = (error) => {
|
|
1482
|
+
if (settled) return;
|
|
1483
|
+
settled = true;
|
|
1484
|
+
clearTimeout(timer);
|
|
1485
|
+
signal.removeEventListener("abort", abort);
|
|
1486
|
+
image.onload = null;
|
|
1487
|
+
image.onerror = null;
|
|
1488
|
+
if (error) {
|
|
1489
|
+
image.src = "";
|
|
1490
|
+
reject(error);
|
|
1491
|
+
} else resolve();
|
|
1492
|
+
};
|
|
1493
|
+
const abort = () => finish(signal.reason ?? new DOMException("Cancelled", "AbortError"));
|
|
1494
|
+
const timer = setTimeout(() => finish(new DOMException("Media preparation timed out", "TimeoutError")), 8e3);
|
|
1495
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1496
|
+
image.onload = () => {
|
|
1497
|
+
void (image.decode ? image.decode() : Promise.resolve()).then(() => finish(), () => finish(new Error("Could not prepare scene image")));
|
|
1498
|
+
};
|
|
1499
|
+
image.onerror = () => finish(new Error("Could not prepare scene image"));
|
|
1500
|
+
image.src = video ? poster : url;
|
|
1501
|
+
if (image.complete && image.naturalWidth > 0) image.onload(new Event("load"));
|
|
1502
|
+
}).catch((error) => {
|
|
1503
|
+
if (!video || signal.aborted) throw error;
|
|
1504
|
+
posterFailed = true;
|
|
1505
|
+
});
|
|
1506
|
+
return video ? posterFailed ? "awaiting-video-frame" : "poster-bridge" : "image";
|
|
1804
1507
|
}
|
|
1805
1508
|
|
|
1806
1509
|
// src/video-chat/voice.ts
|
|
@@ -1820,10 +1523,10 @@ async function measureSeconds(bytes) {
|
|
|
1820
1523
|
try {
|
|
1821
1524
|
sharedContext ??= new AudioContext();
|
|
1822
1525
|
const decoded = await sharedContext.decodeAudioData(bytes.slice(0));
|
|
1823
|
-
if (decoded.duration > 0) return decoded.duration;
|
|
1526
|
+
if (decoded.duration > 0) return { seconds: decoded.duration, measured: true };
|
|
1824
1527
|
} catch {
|
|
1825
1528
|
}
|
|
1826
|
-
return bytes.byteLength * 8 / FALLBACK_BITS_PER_SECOND;
|
|
1529
|
+
return { seconds: bytes.byteLength * 8 / FALLBACK_BITS_PER_SECOND, measured: false };
|
|
1827
1530
|
}
|
|
1828
1531
|
function createVideoChatVoice(options = {}) {
|
|
1829
1532
|
const endpoint = options.endpoint ?? "/api/video-chat";
|
|
@@ -1898,7 +1601,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
1898
1601
|
createdSrc = URL.createObjectURL(new Blob([bytes], {
|
|
1899
1602
|
type: response.headers.get("content-type") || "audio/mpeg"
|
|
1900
1603
|
}));
|
|
1901
|
-
return { source: "generated", src: createdSrc, seconds };
|
|
1604
|
+
return { source: "generated", src: createdSrc, ...seconds };
|
|
1902
1605
|
}, SPEECH_PREPARATION_TIMEOUT_MS, controller.signal);
|
|
1903
1606
|
}
|
|
1904
1607
|
} catch (cause) {
|
|
@@ -1944,9 +1647,11 @@ function createVideoChatVoice(options = {}) {
|
|
|
1944
1647
|
browserFinish?.();
|
|
1945
1648
|
};
|
|
1946
1649
|
return {
|
|
1650
|
+
supportsOffsets: true,
|
|
1651
|
+
getCurrentTime: () => sounding?.currentTime,
|
|
1947
1652
|
async prepare(text, preparation = {}) {
|
|
1948
1653
|
const line = await load(text, preparation.signal);
|
|
1949
|
-
return { seconds: line.seconds };
|
|
1654
|
+
return { seconds: line.seconds, ...line.source === "generated" && line.measured === true ? { supportsOffsets: true } : {} };
|
|
1950
1655
|
},
|
|
1951
1656
|
pause() {
|
|
1952
1657
|
held = true;
|
|
@@ -1955,7 +1660,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
1955
1660
|
},
|
|
1956
1661
|
resume() {
|
|
1957
1662
|
held = false;
|
|
1958
|
-
if (sounding
|
|
1663
|
+
if (sounding) void sounding.play().catch(() => playbackFailure?.());
|
|
1959
1664
|
if (!silent) globalThis.speechSynthesis?.resume();
|
|
1960
1665
|
},
|
|
1961
1666
|
setMuted(muted) {
|
|
@@ -1963,7 +1668,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
1963
1668
|
if (sounding) sounding.muted = muted;
|
|
1964
1669
|
if (muted) stopBrowser();
|
|
1965
1670
|
},
|
|
1966
|
-
async speak(text, { signal, onStart }) {
|
|
1671
|
+
async speak(text, { signal, onStart, offsetSeconds }) {
|
|
1967
1672
|
let started = false;
|
|
1968
1673
|
const notifyStart = (source) => {
|
|
1969
1674
|
if (started || disposed || signal.aborted || silent || held) return;
|
|
@@ -1975,17 +1680,21 @@ function createVideoChatVoice(options = {}) {
|
|
|
1975
1680
|
};
|
|
1976
1681
|
const line = await load(text, signal);
|
|
1977
1682
|
if (disposed || signal.aborted || silent) return;
|
|
1683
|
+
if (offsetSeconds !== void 0 && (line.source !== "generated" || !line.measured || !Number.isFinite(offsetSeconds) || offsetSeconds < 0 || offsetSeconds >= line.seconds)) throw new Error("Narration group requires measured, seekable audio");
|
|
1978
1684
|
if (line.source === "browser") {
|
|
1979
1685
|
const synthesis = globalThis.speechSynthesis;
|
|
1980
|
-
if (!synthesis || typeof SpeechSynthesisUtterance === "undefined")
|
|
1686
|
+
if (!synthesis || typeof SpeechSynthesisUtterance === "undefined") throw new Error("Browser voice is unavailable");
|
|
1981
1687
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
1982
1688
|
utterance.rate = 1;
|
|
1689
|
+
let unavailable = false;
|
|
1983
1690
|
await new Promise((resolve) => {
|
|
1984
1691
|
let finished = false;
|
|
1692
|
+
let onsetRemainingMs = 2e3;
|
|
1985
1693
|
const finish = () => {
|
|
1986
1694
|
if (finished) return;
|
|
1987
1695
|
finished = true;
|
|
1988
1696
|
clearWatchdog();
|
|
1697
|
+
clearInterval(onsetTimer);
|
|
1989
1698
|
signal.removeEventListener("abort", stop);
|
|
1990
1699
|
speechStops.delete(stop);
|
|
1991
1700
|
utterance.onstart = null;
|
|
@@ -1995,38 +1704,54 @@ function createVideoChatVoice(options = {}) {
|
|
|
1995
1704
|
resolve();
|
|
1996
1705
|
};
|
|
1997
1706
|
const stop = () => {
|
|
1998
|
-
synthesis.cancel();
|
|
1999
1707
|
finish();
|
|
1708
|
+
synthesis.cancel();
|
|
2000
1709
|
};
|
|
2001
1710
|
const clearWatchdog = watchSpeech(estimatedBrowserSeconds(text), stop);
|
|
2002
1711
|
speechStops.add(stop);
|
|
2003
1712
|
browserFinish = finish;
|
|
1713
|
+
const fail = () => {
|
|
1714
|
+
unavailable = true;
|
|
1715
|
+
stop();
|
|
1716
|
+
};
|
|
1717
|
+
const onsetTimer = setInterval(() => {
|
|
1718
|
+
if (held || finished) return;
|
|
1719
|
+
onsetRemainingMs -= 250;
|
|
1720
|
+
if (onsetRemainingMs <= 0) fail();
|
|
1721
|
+
}, 250);
|
|
2004
1722
|
utterance.onstart = () => {
|
|
2005
|
-
if (
|
|
1723
|
+
if (finished) return;
|
|
1724
|
+
clearInterval(onsetTimer);
|
|
1725
|
+
notifyStart("browser");
|
|
2006
1726
|
};
|
|
2007
1727
|
utterance.onend = finish;
|
|
2008
|
-
utterance.onerror =
|
|
1728
|
+
utterance.onerror = fail;
|
|
2009
1729
|
signal.addEventListener("abort", stop, { once: true });
|
|
2010
1730
|
try {
|
|
2011
1731
|
synthesis.speak(utterance);
|
|
2012
1732
|
if (held) synthesis.pause();
|
|
2013
1733
|
} catch {
|
|
2014
|
-
|
|
1734
|
+
fail();
|
|
2015
1735
|
}
|
|
2016
1736
|
});
|
|
1737
|
+
if (unavailable) throw new Error("Browser voice is unavailable");
|
|
2017
1738
|
return;
|
|
2018
1739
|
}
|
|
2019
1740
|
let playbackFailed = false;
|
|
2020
1741
|
try {
|
|
2021
1742
|
const element = new Audio(line.src);
|
|
2022
1743
|
element.muted = silent;
|
|
1744
|
+
if (offsetSeconds !== void 0) element.currentTime = offsetSeconds;
|
|
2023
1745
|
sounding = element;
|
|
2024
1746
|
await new Promise((resolve) => {
|
|
2025
1747
|
let finished = false;
|
|
1748
|
+
let onsetTimer;
|
|
1749
|
+
const initialTime = offsetSeconds ?? 0;
|
|
2026
1750
|
const finish = () => {
|
|
2027
1751
|
if (finished) return;
|
|
2028
1752
|
finished = true;
|
|
2029
1753
|
clearWatchdog();
|
|
1754
|
+
clearTimeout(onsetTimer);
|
|
2030
1755
|
signal.removeEventListener("abort", stop);
|
|
2031
1756
|
speechStops.delete(stop);
|
|
2032
1757
|
element.onplaying = null;
|
|
@@ -2049,9 +1774,13 @@ function createVideoChatVoice(options = {}) {
|
|
|
2049
1774
|
const clearWatchdog = watchSpeech(Math.max(line.seconds, estimatedBrowserSeconds(text)), fail);
|
|
2050
1775
|
speechStops.add(stop);
|
|
2051
1776
|
playbackFailure = fail;
|
|
2052
|
-
|
|
2053
|
-
|
|
1777
|
+
const observeClock = () => {
|
|
1778
|
+
clearTimeout(onsetTimer);
|
|
1779
|
+
if (finished || started) return;
|
|
1780
|
+
if (!held && element.currentTime >= initialTime + 0.04) notifyStart("generated");
|
|
1781
|
+
if (!started) onsetTimer = setTimeout(observeClock, 16);
|
|
2054
1782
|
};
|
|
1783
|
+
element.onplaying = observeClock;
|
|
2055
1784
|
element.onended = finish;
|
|
2056
1785
|
element.onerror = fail;
|
|
2057
1786
|
signal.addEventListener("abort", stop, { once: true });
|
|
@@ -2064,6 +1793,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
2064
1793
|
} catch {
|
|
2065
1794
|
playbackFailed = true;
|
|
2066
1795
|
}
|
|
1796
|
+
if (playbackFailed && offsetSeconds !== void 0) throw new Error("Grouped narration playback failed");
|
|
2067
1797
|
if (playbackFailed && !disposed && !signal.aborted && !silent) {
|
|
2068
1798
|
notifyFallback();
|
|
2069
1799
|
URL.revokeObjectURL(line.src);
|
|
@@ -2324,7 +2054,11 @@ async function responseError(response) {
|
|
|
2324
2054
|
});
|
|
2325
2055
|
}
|
|
2326
2056
|
function pacedScene(scene, spokenSeconds, templates) {
|
|
2327
|
-
const held =
|
|
2057
|
+
const held = preparedSceneDuration(
|
|
2058
|
+
scene,
|
|
2059
|
+
spokenSeconds,
|
|
2060
|
+
templates?.getTemplateMetadata(scene.templateId) ?? getBuiltinTemplateMetadata(scene.templateId)
|
|
2061
|
+
);
|
|
2328
2062
|
const {
|
|
2329
2063
|
startTime: _startTime,
|
|
2330
2064
|
endTime: _endTime,
|
|
@@ -2334,21 +2068,6 @@ function pacedScene(scene, spokenSeconds, templates) {
|
|
|
2334
2068
|
} = scene.timing ?? {};
|
|
2335
2069
|
return { ...scene, timing: { ...timing, fixedDuration: held } };
|
|
2336
2070
|
}
|
|
2337
|
-
function prepareVisualScene(scene, mode) {
|
|
2338
|
-
const overMedia = typeof scene.variables.mediaUrl === "string";
|
|
2339
|
-
const filmed = mode === "full" && overMedia;
|
|
2340
|
-
const shown = filmed ? {
|
|
2341
|
-
...scene,
|
|
2342
|
-
backgroundEffect: "static",
|
|
2343
|
-
variables: {
|
|
2344
|
-
...scene.variables,
|
|
2345
|
-
texts: "",
|
|
2346
|
-
mediaTreatment: "none",
|
|
2347
|
-
confetti: false
|
|
2348
|
-
}
|
|
2349
|
-
} : scene;
|
|
2350
|
-
return overMedia ? { ...shown, textArchetype: "subtle" } : shown;
|
|
2351
|
-
}
|
|
2352
2071
|
function useVideoChat(options = {}) {
|
|
2353
2072
|
return useVideoChatSession(options).chat;
|
|
2354
2073
|
}
|
|
@@ -2372,7 +2091,17 @@ function useVideoChatSession(options = {}) {
|
|
|
2372
2091
|
const unavailableVoiceLines = useRef3(/* @__PURE__ */ new Set());
|
|
2373
2092
|
const speechStartRef = useRef3(() => void 0);
|
|
2374
2093
|
const narration = useNarration({ onSpeechStart: (source) => speechStartRef.current(source), voice: {
|
|
2375
|
-
|
|
2094
|
+
supportsOffsets: voice.supportsOffsets,
|
|
2095
|
+
...voice.getCurrentTime ? { getCurrentTime: () => voice.getCurrentTime() } : {},
|
|
2096
|
+
speak: async (text, options2) => {
|
|
2097
|
+
if (unavailableVoiceLines.current.has(text)) return;
|
|
2098
|
+
try {
|
|
2099
|
+
await voice.speak(text, options2);
|
|
2100
|
+
} catch (error) {
|
|
2101
|
+
if (!options2.signal.aborted) voiceWarningRef.current("Voice playback is unavailable. Continuing with subtitles.");
|
|
2102
|
+
throw error;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2376
2105
|
} });
|
|
2377
2106
|
const narrationRef = useRef3(narration);
|
|
2378
2107
|
narrationRef.current = narration;
|
|
@@ -2511,9 +2240,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2511
2240
|
throw new VideoError("timeoutMs must be positive", { code: "invalid_option" });
|
|
2512
2241
|
}
|
|
2513
2242
|
const timeout = setTimeout(() => controller.abort(new DOMException("Video chat timed out", "TimeoutError")), timeoutMs);
|
|
2514
|
-
const
|
|
2515
|
-
const knownModes = stateRef.current.capabilities?.modes;
|
|
2516
|
-
const mode = knownModes && !knownModes.includes(requestedMode) ? "templates" : requestedMode;
|
|
2243
|
+
const mode = "cinematic";
|
|
2517
2244
|
const orientation = currentOptions.orientation ?? "landscape";
|
|
2518
2245
|
const id = (currentOptions.createTurnId ?? defaultTurnId)();
|
|
2519
2246
|
const conversation = conversationFor(stateRef.current.turns);
|
|
@@ -2524,7 +2251,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2524
2251
|
prompt,
|
|
2525
2252
|
completed: false,
|
|
2526
2253
|
orientation,
|
|
2527
|
-
fixedOrientation:
|
|
2254
|
+
fixedOrientation: true,
|
|
2528
2255
|
suggestions: [],
|
|
2529
2256
|
...openingMedia ? { openingMedia } : {}
|
|
2530
2257
|
};
|
|
@@ -2547,11 +2274,23 @@ function useVideoChatSession(options = {}) {
|
|
|
2547
2274
|
const warn = (message) => {
|
|
2548
2275
|
if (isCurrent()) dispatch({ type: "warning", id, message });
|
|
2549
2276
|
};
|
|
2550
|
-
voiceWarningRef.current = (
|
|
2277
|
+
voiceWarningRef.current = (message = "Using browser voice for this response.") => warn(message);
|
|
2551
2278
|
const flush = () => {
|
|
2552
2279
|
if (!isCurrent()) return;
|
|
2553
2280
|
let available = appended;
|
|
2554
|
-
while (ready[available])
|
|
2281
|
+
while (ready[available]) {
|
|
2282
|
+
const group = ready[available].narrationGroup;
|
|
2283
|
+
if (!group) {
|
|
2284
|
+
available++;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
let end = available;
|
|
2288
|
+
while (ready[end]?.narrationGroup?.id === group.id) end++;
|
|
2289
|
+
const last = ready[end - 1].narrationGroup;
|
|
2290
|
+
if (Math.abs(last.offsetSeconds + last.durationSeconds - group.totalSeconds) > 0.02) break;
|
|
2291
|
+
validateNarrationGroups(ready.slice(available, end));
|
|
2292
|
+
available = end;
|
|
2293
|
+
}
|
|
2555
2294
|
if (available === appended && timeline) {
|
|
2556
2295
|
if (planDone && !timelineCompleted) {
|
|
2557
2296
|
timelineCompleted = true;
|
|
@@ -2561,18 +2300,18 @@ function useVideoChatSession(options = {}) {
|
|
|
2561
2300
|
return;
|
|
2562
2301
|
}
|
|
2563
2302
|
if (!timeline) {
|
|
2564
|
-
if (!style || openingActive || heldRef.current || available === appended) return;
|
|
2303
|
+
if (!style || openingActive || heldRef.current || available === appended || !canStartPreparedSequence(ready.slice(0, available), planDone)) return;
|
|
2565
2304
|
timeline = createSceneTimeline({ style, orientation });
|
|
2566
2305
|
timelineRef.current = timeline;
|
|
2567
2306
|
openingController.abort(new DOMException("Opening replaced by response", "AbortError"));
|
|
2568
2307
|
if (openingRef.current === openingController) openingRef.current = void 0;
|
|
2569
2308
|
dispatch({ type: "player", id, stream: timeline.stream });
|
|
2570
2309
|
}
|
|
2571
|
-
while (
|
|
2310
|
+
while (appended < available) timeline.add(ready[appended++]);
|
|
2572
2311
|
dispatch({
|
|
2573
2312
|
type: "partial",
|
|
2574
2313
|
id,
|
|
2575
|
-
video: { schemaVersion:
|
|
2314
|
+
video: { schemaVersion: VIDEO_SCHEMA_VERSION, orientation, scenes: ready.slice(0, appended), style }
|
|
2576
2315
|
});
|
|
2577
2316
|
if (planDone && !timelineCompleted) {
|
|
2578
2317
|
timelineCompleted = true;
|
|
@@ -2634,7 +2373,6 @@ function useVideoChatSession(options = {}) {
|
|
|
2634
2373
|
mode,
|
|
2635
2374
|
orientation,
|
|
2636
2375
|
conversation,
|
|
2637
|
-
...currentOptions.brand ? { brand: currentOptions.brand } : {},
|
|
2638
2376
|
...currentOptions.style ? { style: currentOptions.style } : {}
|
|
2639
2377
|
})
|
|
2640
2378
|
}, controller.signal);
|
|
@@ -2649,7 +2387,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2649
2387
|
let terminalError;
|
|
2650
2388
|
try {
|
|
2651
2389
|
for await (const event of decodeVideoSse(response.body)) {
|
|
2652
|
-
if (!isCurrent() || currentAttempt !== attempt) return { video: { schemaVersion:
|
|
2390
|
+
if (!isCurrent() || currentAttempt !== attempt) return { video: { schemaVersion: VIDEO_SCHEMA_VERSION, orientation, scenes: [], style }, lines: [] };
|
|
2653
2391
|
if (event.type === "response.start") style = event.data.style;
|
|
2654
2392
|
if (event.type === "response.warning" || event.type === "response.error" && !event.data.terminal) {
|
|
2655
2393
|
warn(event.type === "response.warning" && event.data.warning.message === MEDIA_RECOVERY_NOTICE ? MEDIA_RECOVERY_NOTICE : "Some parts were simplified so the response could continue.");
|
|
@@ -2683,10 +2421,12 @@ function useVideoChatSession(options = {}) {
|
|
|
2683
2421
|
const position = event.data.position;
|
|
2684
2422
|
const plannedScene = event.data.scene;
|
|
2685
2423
|
planned[position] = plannedScene;
|
|
2686
|
-
received[position] =
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2424
|
+
received[position] = plannedScene;
|
|
2425
|
+
const rendererReady = currentOptions.templates?.getTemplate(plannedScene.templateId) ? Promise.resolve() : Promise.resolve(preloadBuiltinTemplate(plannedScene.templateId));
|
|
2426
|
+
const visualPreparation = Promise.all([rendererReady, prepareSceneMedia(plannedScene.variables, controller.signal)]).then(() => void 0, () => {
|
|
2427
|
+
throw new VideoError("Scene could not prepare its visual", { code: "media_not_ready" });
|
|
2428
|
+
});
|
|
2429
|
+
void visualPreparation.catch(() => void 0);
|
|
2690
2430
|
warmSceneMedia(plannedScene.variables);
|
|
2691
2431
|
const narrated = narrating.then(async () => {
|
|
2692
2432
|
const supplied = plannedScene.narration?.trim();
|
|
@@ -2713,18 +2453,25 @@ function useVideoChatSession(options = {}) {
|
|
|
2713
2453
|
narrating = narrated.catch(() => "");
|
|
2714
2454
|
pending.push(narrated.then(async (line) => {
|
|
2715
2455
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2716
|
-
const visual =
|
|
2456
|
+
const visual = plannedScene;
|
|
2717
2457
|
const withNarration = line ? { ...visual, narration: line } : visual;
|
|
2718
|
-
const spoken = line ? await prepareSpeech(line, controller.signal).catch((cause) => {
|
|
2458
|
+
const spoken = line ? await prepareSpeech(plannedScene.narrationGroup?.text ?? line, controller.signal).catch((cause) => {
|
|
2719
2459
|
if (controller.signal.aborted) throw cause;
|
|
2720
2460
|
warn("Some narration is unavailable; the response will continue.");
|
|
2721
2461
|
return void 0;
|
|
2722
2462
|
}) : void 0;
|
|
2723
2463
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2724
|
-
|
|
2464
|
+
await visualPreparation;
|
|
2465
|
+
const group = plannedScene.narrationGroup;
|
|
2466
|
+
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" });
|
|
2467
|
+
ready[position] = group ? withNarration : pacedScene(withNarration, spoken?.seconds, currentOptions.templates);
|
|
2725
2468
|
flush();
|
|
2726
|
-
}).catch(() => {
|
|
2469
|
+
}).catch((cause) => {
|
|
2727
2470
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2471
|
+
if (cause instanceof VideoError && ["media_not_ready", "narration_group_invalid"].includes(cause.code)) {
|
|
2472
|
+
terminalError = cause;
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2728
2475
|
ready[position] = { ...received[position], timing: { fixedDuration: 5 } };
|
|
2729
2476
|
warn("Some parts were simplified so the response could continue.");
|
|
2730
2477
|
flush();
|
|
@@ -2735,6 +2482,8 @@ function useVideoChatSession(options = {}) {
|
|
|
2735
2482
|
terminalError = errorFrom(cause);
|
|
2736
2483
|
}
|
|
2737
2484
|
await Promise.all(pending);
|
|
2485
|
+
if (terminalError && ["media_not_ready", "narration_group_invalid"].includes(terminalError.code)) throw terminalError;
|
|
2486
|
+
validateNarrationGroups(ready.filter((scene) => Boolean(scene)));
|
|
2738
2487
|
if (terminalError && ready.some(Boolean) && style) {
|
|
2739
2488
|
warn("The response was interrupted; completed scenes are still available.");
|
|
2740
2489
|
} else if (terminalError) throw terminalError;
|
|
@@ -2743,7 +2492,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2743
2492
|
}
|
|
2744
2493
|
return {
|
|
2745
2494
|
video: {
|
|
2746
|
-
schemaVersion:
|
|
2495
|
+
schemaVersion: VIDEO_SCHEMA_VERSION,
|
|
2747
2496
|
orientation,
|
|
2748
2497
|
scenes: ready.filter((entry) => entry != null),
|
|
2749
2498
|
style
|
|
@@ -2756,7 +2505,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2756
2505
|
try {
|
|
2757
2506
|
response = await untilAborted(runAttempt(attempt), controller.signal);
|
|
2758
2507
|
} catch (cause) {
|
|
2759
|
-
if (controller.signal.aborted || timeline || spokenHook) throw cause;
|
|
2508
|
+
if (controller.signal.aborted || timeline || spokenHook || cause instanceof VideoError && ["media_not_ready", "narration_group_invalid"].includes(cause.code) || received.some((scene) => scene?.narrationGroup)) throw cause;
|
|
2760
2509
|
attempt += 1;
|
|
2761
2510
|
ready = [];
|
|
2762
2511
|
received = [];
|
|
@@ -2792,9 +2541,9 @@ function useVideoChatSession(options = {}) {
|
|
|
2792
2541
|
return void 0;
|
|
2793
2542
|
}
|
|
2794
2543
|
terminal = true;
|
|
2795
|
-
const recovered = received.flatMap((scene, index) => scene ? [ready[index] ?? { ...scene, timing: { fixedDuration: 5 } }] : []);
|
|
2544
|
+
const recovered = received.some((scene) => scene?.narrationGroup) || cause instanceof VideoError && ["media_not_ready", "narration_group_invalid"].includes(cause.code) ? [] : received.flatMap((scene, index) => scene ? [ready[index] ?? { ...scene, timing: { fixedDuration: 5 } }] : []);
|
|
2796
2545
|
if (recovered.length > 0) {
|
|
2797
|
-
style ??= {
|
|
2546
|
+
style ??= { density: "normal", motion: "normal", defaultBackgroundEffect: "static", defaultTextArchetype: "subtle", defaultTransition: "crossfade" };
|
|
2798
2547
|
openingController.abort(new DOMException("Continuing completed response", "AbortError"));
|
|
2799
2548
|
if (!timeline) {
|
|
2800
2549
|
timeline = createSceneTimeline({ style, orientation });
|
|
@@ -2803,7 +2552,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2803
2552
|
for (const scene of recovered.slice(appended)) timeline.add(scene);
|
|
2804
2553
|
timeline.complete();
|
|
2805
2554
|
if (timelineRef.current === timeline) timelineRef.current = void 0;
|
|
2806
|
-
const video = { schemaVersion:
|
|
2555
|
+
const video = { schemaVersion: VIDEO_SCHEMA_VERSION, orientation, style, scenes: recovered };
|
|
2807
2556
|
dispatch({ type: "warning", id, message: "The response was interrupted; completed scenes are still available." });
|
|
2808
2557
|
dispatch({ type: "complete", id, video, suggestions: [] });
|
|
2809
2558
|
return video;
|
|
@@ -2831,9 +2580,18 @@ function useVideoChatSession(options = {}) {
|
|
|
2831
2580
|
dispatch({ type: "resume" });
|
|
2832
2581
|
flushRef.current?.();
|
|
2833
2582
|
}, []);
|
|
2583
|
+
const prepareSavedGroups = useCallback2(async (video) => {
|
|
2584
|
+
validateNarrationGroups(video.scenes);
|
|
2585
|
+
const groups = new Map(video.scenes.flatMap((scene) => scene.narrationGroup ? [[scene.narrationGroup.id, scene.narrationGroup]] : []));
|
|
2586
|
+
for (const group of groups.values()) {
|
|
2587
|
+
const prepared = await withDeadline((signal) => voiceRef.current.prepare(group.text, { signal }), 3e3);
|
|
2588
|
+
if (voiceRef.current.supportsOffsets !== true || prepared.supportsOffsets !== true || Math.abs(prepared.seconds - group.totalSeconds) > 0.1) throw new VideoError("Saved narration group requires matching measured audio", { code: "narration_group_invalid" });
|
|
2589
|
+
}
|
|
2590
|
+
}, []);
|
|
2834
2591
|
const replay = useCallback2(() => {
|
|
2835
2592
|
const turn = stateRef.current.turns.find((entry) => entry.id === stateRef.current.shownTurnId);
|
|
2836
2593
|
if (!turn?.completed || !turn.video) return;
|
|
2594
|
+
runRef.current += 1;
|
|
2837
2595
|
endTiming();
|
|
2838
2596
|
if (inFlightRef.current) {
|
|
2839
2597
|
runRef.current += 1;
|
|
@@ -2847,11 +2605,20 @@ function useVideoChatSession(options = {}) {
|
|
|
2847
2605
|
narrationRef.current.interrupt();
|
|
2848
2606
|
heldRef.current = false;
|
|
2849
2607
|
voiceRef.current.resume();
|
|
2850
|
-
|
|
2851
|
-
|
|
2608
|
+
if (turn.video.scenes.some((scene) => scene.narrationGroup)) {
|
|
2609
|
+
const selection = ++runRef.current;
|
|
2610
|
+
dispatch({ type: "pause" });
|
|
2611
|
+
void prepareSavedGroups(turn.video).then(() => {
|
|
2612
|
+
if (mountedRef.current && runRef.current === selection) dispatch({ type: "replay" });
|
|
2613
|
+
}).catch((cause) => {
|
|
2614
|
+
if (mountedRef.current && runRef.current === selection) dispatch({ type: "error", id: turn.id, error: errorFrom(cause) });
|
|
2615
|
+
});
|
|
2616
|
+
} else dispatch({ type: "replay" });
|
|
2617
|
+
}, [endTiming, prepareSavedGroups]);
|
|
2852
2618
|
const selectTurn = useCallback2((id) => {
|
|
2853
2619
|
const turn = stateRef.current.turns.find((entry) => entry.id === id);
|
|
2854
2620
|
if (!turn?.completed || !turn.video) return;
|
|
2621
|
+
runRef.current += 1;
|
|
2855
2622
|
endTiming();
|
|
2856
2623
|
if (inFlightRef.current) {
|
|
2857
2624
|
runRef.current += 1;
|
|
@@ -2865,8 +2632,16 @@ function useVideoChatSession(options = {}) {
|
|
|
2865
2632
|
narrationRef.current.interrupt();
|
|
2866
2633
|
heldRef.current = false;
|
|
2867
2634
|
voiceRef.current.resume();
|
|
2868
|
-
|
|
2869
|
-
|
|
2635
|
+
if (turn.video.scenes.some((scene) => scene.narrationGroup)) {
|
|
2636
|
+
const selection = ++runRef.current;
|
|
2637
|
+
dispatch({ type: "pause" });
|
|
2638
|
+
void prepareSavedGroups(turn.video).then(() => {
|
|
2639
|
+
if (mountedRef.current && runRef.current === selection) dispatch({ type: "select", id });
|
|
2640
|
+
}).catch((cause) => {
|
|
2641
|
+
if (mountedRef.current && runRef.current === selection) dispatch({ type: "error", id, error: errorFrom(cause) });
|
|
2642
|
+
});
|
|
2643
|
+
} else dispatch({ type: "select", id });
|
|
2644
|
+
}, [endTiming, prepareSavedGroups]);
|
|
2870
2645
|
const reset = useCallback2(() => {
|
|
2871
2646
|
cancel("Session reset");
|
|
2872
2647
|
heldRef.current = false;
|
|
@@ -2882,7 +2657,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2882
2657
|
const setMuted = useCallback2((muted) => dispatch({ type: "mute", value: muted }), []);
|
|
2883
2658
|
const currentTurn = state.turns.at(-1);
|
|
2884
2659
|
const shownTurn = state.turns.find((turn) => turn.id === state.shownTurnId) ?? currentTurn;
|
|
2885
|
-
const availableModes = state.capabilities?.modes ?? ["
|
|
2660
|
+
const availableModes = state.capabilities?.modes ?? ["cinematic"];
|
|
2886
2661
|
const suggestions = shownTurn?.suggestions ?? [];
|
|
2887
2662
|
const fullTranscript = shownTurn ? transcriptFor(shownTurn) : [];
|
|
2888
2663
|
const transcript = shownTurn && shownTurn === currentTurn && state.playback?.kind !== "video" ? fullTranscript.slice(0, (shownTurn.opening ? 1 : 0) + state.spokenUpTo + 1) : fullTranscript;
|
|
@@ -2893,6 +2668,8 @@ function useVideoChatSession(options = {}) {
|
|
|
2893
2668
|
autoPlay: true,
|
|
2894
2669
|
paused: state.status === "paused",
|
|
2895
2670
|
controls: false,
|
|
2671
|
+
narrationReady: narration.isReady,
|
|
2672
|
+
narrationTime: narration.getTime,
|
|
2896
2673
|
orientation: shownTurn?.fixedOrientation ? shownTurn.orientation : "auto",
|
|
2897
2674
|
onFramePresented: () => {
|
|
2898
2675
|
const timing = firstFrameRef.current;
|
|
@@ -2902,8 +2679,22 @@ function useVideoChatSession(options = {}) {
|
|
|
2902
2679
|
observe(() => optionsRef.current.onFirstFrame?.({ turnId: timing.turnId, mode: timing.mode, timeToFirstFrameMs: elapsedMs }));
|
|
2903
2680
|
reportMetric({ type: "first-frame", turnId: timing.turnId, mode: timing.mode, elapsedMs });
|
|
2904
2681
|
},
|
|
2682
|
+
onMediaFramePresented: () => {
|
|
2683
|
+
const timing = firstFrameRef.current;
|
|
2684
|
+
if (stateRef.current.playerKey !== playbackKey || state.playback?.kind !== "stream" || !timing?.active || timing.mediaReported) return;
|
|
2685
|
+
timing.mediaReported = true;
|
|
2686
|
+
reportMetric({
|
|
2687
|
+
type: "first-media-frame",
|
|
2688
|
+
turnId: timing.turnId,
|
|
2689
|
+
mode: timing.mode,
|
|
2690
|
+
elapsedMs: Math.max(0, Math.round(monotonicNow() - timing.startedAt))
|
|
2691
|
+
});
|
|
2692
|
+
},
|
|
2905
2693
|
onStallChange: (stalled) => {
|
|
2906
|
-
if (stateRef.current.playerKey !== playbackKey
|
|
2694
|
+
if (stateRef.current.playerKey !== playbackKey) return;
|
|
2695
|
+
if (stalled) voiceRef.current.pause();
|
|
2696
|
+
else if (!heldRef.current) voiceRef.current.resume();
|
|
2697
|
+
if (state.playback?.kind !== "stream") return;
|
|
2907
2698
|
const timing = firstFrameRef.current;
|
|
2908
2699
|
if (!timing?.active || !timing.reported) return;
|
|
2909
2700
|
if (!stalled) finishStall();
|
|
@@ -2921,6 +2712,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2921
2712
|
},
|
|
2922
2713
|
onError: (cause) => {
|
|
2923
2714
|
if (stateRef.current.playerKey !== playbackKey) return;
|
|
2715
|
+
narrationRef.current.interrupt();
|
|
2924
2716
|
const id = stateRef.current.turns.at(-1)?.id;
|
|
2925
2717
|
if (id) dispatch({ type: "error", id, error: errorFrom(cause) });
|
|
2926
2718
|
}
|
|
@@ -2956,68 +2748,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2956
2748
|
}
|
|
2957
2749
|
|
|
2958
2750
|
// src/video-chat/video-chat.tsx
|
|
2959
|
-
import { useCallback as useCallback6, useEffect as useEffect9, useId, useLayoutEffect, useMemo as useMemo2, useRef as
|
|
2960
|
-
|
|
2961
|
-
// src/video-chat/modes.ts
|
|
2962
|
-
var visualModes = [
|
|
2963
|
-
{ id: "templates", label: "Templates only", note: "Animated text, diagrams and charts" },
|
|
2964
|
-
{ id: "full", label: "Full AI video", note: "AI-generated footage \xB7 billed per clip" }
|
|
2965
|
-
];
|
|
2966
|
-
var defaultMode = visualModes[0];
|
|
2967
|
-
var modeById = (id) => visualModes.find((mode) => mode.id === id) ?? defaultMode;
|
|
2968
|
-
|
|
2969
|
-
// src/video-chat/themes.ts
|
|
2970
|
-
var GROUND = {
|
|
2971
|
-
font: "Inter",
|
|
2972
|
-
scriptFont: "Caveat",
|
|
2973
|
-
// The brand's indigo into blue. Deeper than it looks like it wants to be:
|
|
2974
|
-
// the renderer holds captions to 4.5:1 against this ground, and the brand's
|
|
2975
|
-
// own bright blue (#00A5FF) measures 2.68:1 under white - so the light end
|
|
2976
|
-
// stays at a blue that clears the bar (5.17:1) and the bright one is spent
|
|
2977
|
-
// on accents instead, where it sits on the dark ground rather than under
|
|
2978
|
-
// white type.
|
|
2979
|
-
background: { colors: ["#1C0277", "#2563eb"] },
|
|
2980
|
-
foreground: "#ffffff",
|
|
2981
|
-
surface: "#1C0277",
|
|
2982
|
-
surfaceElevated: "#2f4fc4",
|
|
2983
|
-
// Not the brand's #7065A5: muted text still has to be read, and that violet
|
|
2984
|
-
// measures 3.66:1 on the dark end of this ground and worse on the light one.
|
|
2985
|
-
// The brand violet earns its place as a surface, not as type.
|
|
2986
|
-
muted: "#d7d3f0"
|
|
2987
|
-
};
|
|
2988
|
-
function look(primary, secondary) {
|
|
2989
|
-
const { font, scriptFont, background, ...colors } = GROUND;
|
|
2990
|
-
return { font, scriptFont, background, colors: { ...colors, primary, secondary } };
|
|
2991
|
-
}
|
|
2992
|
-
var themes = [
|
|
2993
|
-
{
|
|
2994
|
-
id: "documentary",
|
|
2995
|
-
label: "Documentary",
|
|
2996
|
-
brand: look("#FF3A71", "#FFB100"),
|
|
2997
|
-
generatedLook: "Natural daylight, documentary realism, shallow depth of field, fine 35mm grain, muted natural palette. Photoreal, no illustration."
|
|
2998
|
-
},
|
|
2999
|
-
{
|
|
3000
|
-
id: "illustrated",
|
|
3001
|
-
label: "Illustrated",
|
|
3002
|
-
brand: look("#00A5FF", "#FF006F"),
|
|
3003
|
-
generatedLook: "Hand-drawn cel animation, rough ink contours, visible paper grain, flat cobalt, crimson and ochre against a deep ground. No photorealism."
|
|
3004
|
-
},
|
|
3005
|
-
{
|
|
3006
|
-
id: "blueprint",
|
|
3007
|
-
label: "Blueprint",
|
|
3008
|
-
brand: look("#00A5FF", "#FFB100"),
|
|
3009
|
-
generatedLook: "Technical blueprint drawing, precise white linework on deep cyanotype blue, faint measurement grid, one warm accent. Schematic, no photorealism."
|
|
3010
|
-
}
|
|
3011
|
-
];
|
|
3012
|
-
function themeBackground(brand) {
|
|
3013
|
-
const background = brand.background;
|
|
3014
|
-
if (background?.colors && background.colors.length >= 2) {
|
|
3015
|
-
return `linear-gradient(175deg, ${background.colors[0]} 0%, ${background.colors[1]} 100%)`;
|
|
3016
|
-
}
|
|
3017
|
-
return background?.color ?? "#4c2ba8";
|
|
3018
|
-
}
|
|
3019
|
-
var defaultTheme = themes[0];
|
|
3020
|
-
var themeById = (id) => themes.find((theme) => theme.id === id) ?? defaultTheme;
|
|
2751
|
+
import { useCallback as useCallback6, useEffect as useEffect9, useId, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef8, useState as useState7 } from "react";
|
|
3021
2752
|
|
|
3022
2753
|
// src/video-chat/icons.tsx
|
|
3023
2754
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -3129,7 +2860,7 @@ function useFocusTrap(active, surface) {
|
|
|
3129
2860
|
// src/video-chat/suggestion-cards.tsx
|
|
3130
2861
|
import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef4, useState as useState3 } from "react";
|
|
3131
2862
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3132
|
-
function Frame({ media, poster, playing }) {
|
|
2863
|
+
function Frame({ media, poster, playing, onReady, onError, revealWhenReady = false }) {
|
|
3133
2864
|
const video = useRef4(null);
|
|
3134
2865
|
const [playingUrl, setPlayingUrl] = useState3(null);
|
|
3135
2866
|
useEffect6(() => {
|
|
@@ -3139,7 +2870,12 @@ function Frame({ media, poster, playing }) {
|
|
|
3139
2870
|
else element.pause();
|
|
3140
2871
|
}, [playing]);
|
|
3141
2872
|
if (!media) return null;
|
|
3142
|
-
|
|
2873
|
+
const ready = () => {
|
|
2874
|
+
setPlayingUrl(media.url);
|
|
2875
|
+
onReady?.();
|
|
2876
|
+
};
|
|
2877
|
+
const appearance = revealWhenReady ? { opacity: playingUrl === media.url ? 1 : 0, transition: "opacity 200ms ease" } : void 0;
|
|
2878
|
+
if (media.type === "image") return /* @__PURE__ */ jsx4("img", { className: "frame-media", src: media.url, alt: "", style: appearance, onLoad: ready, onError });
|
|
3143
2879
|
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
3144
2880
|
/* @__PURE__ */ jsx4(
|
|
3145
2881
|
"video",
|
|
@@ -3148,8 +2884,12 @@ function Frame({ media, poster, playing }) {
|
|
|
3148
2884
|
className: "frame-media",
|
|
3149
2885
|
src: media.url,
|
|
3150
2886
|
poster: poster ? media.posterUrl : void 0,
|
|
3151
|
-
|
|
3152
|
-
|
|
2887
|
+
style: appearance,
|
|
2888
|
+
onPlaying: ready,
|
|
2889
|
+
onError: () => {
|
|
2890
|
+
setPlayingUrl(null);
|
|
2891
|
+
onError?.();
|
|
2892
|
+
},
|
|
3153
2893
|
autoPlay: playing !== false,
|
|
3154
2894
|
muted: true,
|
|
3155
2895
|
loop: true,
|
|
@@ -3158,7 +2898,7 @@ function Frame({ media, poster, playing }) {
|
|
|
3158
2898
|
"aria-hidden": "true"
|
|
3159
2899
|
}
|
|
3160
2900
|
),
|
|
3161
|
-
poster && media.posterUrl && playingUrl !== media.url && /* @__PURE__ */ jsx4("img", { className: "frame-media frame-poster", src: media.posterUrl, alt: "" })
|
|
2901
|
+
poster && media.posterUrl && playingUrl !== media.url && /* @__PURE__ */ jsx4("img", { className: "frame-media frame-poster", src: media.posterUrl, alt: "", onLoad: onReady })
|
|
3162
2902
|
] });
|
|
3163
2903
|
}
|
|
3164
2904
|
function SuggestionCards({ suggestions, label, onAsk }) {
|
|
@@ -3231,8 +2971,41 @@ function Welcome({ data, onAsk, title }) {
|
|
|
3231
2971
|
] });
|
|
3232
2972
|
}
|
|
3233
2973
|
|
|
2974
|
+
// src/video-chat/opening-chapter.tsx
|
|
2975
|
+
import { useLayoutEffect, useRef as useRef5, useState as useState4 } from "react";
|
|
2976
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
2977
|
+
function OpeningChapter({ title }) {
|
|
2978
|
+
const root = useRef5(null);
|
|
2979
|
+
const [size, setSize] = useState4({ width: 1080, height: 1080 });
|
|
2980
|
+
useLayoutEffect(() => {
|
|
2981
|
+
const element = root.current;
|
|
2982
|
+
if (!element) return;
|
|
2983
|
+
const measure = () => {
|
|
2984
|
+
const { width, height } = element.getBoundingClientRect();
|
|
2985
|
+
if (width > 0 && height > 0) setSize({ width, height });
|
|
2986
|
+
};
|
|
2987
|
+
measure();
|
|
2988
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
2989
|
+
const observer = new ResizeObserver(measure);
|
|
2990
|
+
observer.observe(element);
|
|
2991
|
+
return () => observer.disconnect();
|
|
2992
|
+
}, []);
|
|
2993
|
+
return /* @__PURE__ */ jsx6("div", { ref: root, "data-opening-chapter": true, className: "opening-chapter", children: /* @__PURE__ */ jsx6(
|
|
2994
|
+
TitleSceneTemplate,
|
|
2995
|
+
{
|
|
2996
|
+
variables: { title },
|
|
2997
|
+
style: {},
|
|
2998
|
+
width: size.width,
|
|
2999
|
+
height: size.height,
|
|
3000
|
+
progress: 0.3,
|
|
3001
|
+
beatIntensity: 0,
|
|
3002
|
+
safeZone: { top: 0, right: 0, bottom: 0, left: 0 }
|
|
3003
|
+
}
|
|
3004
|
+
) });
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3234
3007
|
// src/video-chat/use-voice-input.ts
|
|
3235
|
-
import { useCallback as useCallback4, useEffect as useEffect7, useRef as
|
|
3008
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef6, useState as useState5 } from "react";
|
|
3236
3009
|
function recognitionConstructor() {
|
|
3237
3010
|
if (typeof window === "undefined") return void 0;
|
|
3238
3011
|
const holder = window;
|
|
@@ -3302,14 +3075,14 @@ async function recordAndTranscribe(signal, ready, captured, options) {
|
|
|
3302
3075
|
return clip ? transcribeRecording(clip, signal, options) : "";
|
|
3303
3076
|
}
|
|
3304
3077
|
function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {}) {
|
|
3305
|
-
const [supported, setSupported] =
|
|
3306
|
-
const [listening, setListening] =
|
|
3307
|
-
const [error, setError] =
|
|
3308
|
-
const [thinking, setThinking] =
|
|
3309
|
-
const recognitionRef =
|
|
3310
|
-
const useRecorderRef =
|
|
3311
|
-
const operationRef =
|
|
3312
|
-
const handlerRef =
|
|
3078
|
+
const [supported, setSupported] = useState5(false);
|
|
3079
|
+
const [listening, setListening] = useState5(false);
|
|
3080
|
+
const [error, setError] = useState5();
|
|
3081
|
+
const [thinking, setThinking] = useState5(false);
|
|
3082
|
+
const recognitionRef = useRef6(void 0);
|
|
3083
|
+
const useRecorderRef = useRef6(false);
|
|
3084
|
+
const operationRef = useRef6(void 0);
|
|
3085
|
+
const handlerRef = useRef6(onTranscript);
|
|
3313
3086
|
handlerRef.current = onTranscript;
|
|
3314
3087
|
useEffect7(() => {
|
|
3315
3088
|
setSupported(supportsVoiceInput(transcriptionAvailable));
|
|
@@ -3436,13 +3209,13 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
|
|
|
3436
3209
|
}
|
|
3437
3210
|
|
|
3438
3211
|
// src/video-chat/use-immersive-controls.ts
|
|
3439
|
-
import { useCallback as useCallback5, useEffect as useEffect8, useRef as
|
|
3212
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
|
|
3440
3213
|
function useImmersiveControls(playing, pinned, hasCaptions = false) {
|
|
3441
|
-
const [visible, setVisible] =
|
|
3442
|
-
const hovered =
|
|
3443
|
-
const focused =
|
|
3444
|
-
const previousCaptions =
|
|
3445
|
-
const timer =
|
|
3214
|
+
const [visible, setVisible] = useState6(true);
|
|
3215
|
+
const hovered = useRef7(false);
|
|
3216
|
+
const focused = useRef7(false);
|
|
3217
|
+
const previousCaptions = useRef7(false);
|
|
3218
|
+
const timer = useRef7(null);
|
|
3446
3219
|
const clearTimer = useCallback5(() => {
|
|
3447
3220
|
if (timer.current !== null) clearTimeout(timer.current);
|
|
3448
3221
|
timer.current = null;
|
|
@@ -3493,16 +3266,16 @@ function useImmersiveControls(playing, pinned, hasCaptions = false) {
|
|
|
3493
3266
|
}
|
|
3494
3267
|
|
|
3495
3268
|
// src/video-chat/logo.tsx
|
|
3496
|
-
import { jsx as
|
|
3269
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
3497
3270
|
function Logo() {
|
|
3498
|
-
return /* @__PURE__ */
|
|
3271
|
+
return /* @__PURE__ */ jsx7("img", { className: "brand-logo", src: new URL("./assets/vanillasky-logo.svg", import.meta.url).href, alt: "VanillaSky", width: 144, height: 36 });
|
|
3499
3272
|
}
|
|
3500
3273
|
|
|
3501
3274
|
// src/video-chat/video-chat.tsx
|
|
3502
|
-
import { Fragment as Fragment4, jsx as
|
|
3275
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3503
3276
|
var DESKTOP_WIDTH = 900;
|
|
3504
3277
|
function useViewportOrientation() {
|
|
3505
|
-
const [portrait, setPortrait] =
|
|
3278
|
+
const [portrait, setPortrait] = useState7(() => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px) and (orientation: portrait)`).matches : false);
|
|
3506
3279
|
useEffect9(() => {
|
|
3507
3280
|
if (typeof window.matchMedia !== "function") return;
|
|
3508
3281
|
const query = window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px) and (orientation: portrait)`);
|
|
@@ -3520,7 +3293,7 @@ var STEPS = [
|
|
|
3520
3293
|
"Still filming\u2026"
|
|
3521
3294
|
];
|
|
3522
3295
|
function useFilmingStep(active, key) {
|
|
3523
|
-
const [step, setStep] =
|
|
3296
|
+
const [step, setStep] = useState7(0);
|
|
3524
3297
|
useEffect9(() => {
|
|
3525
3298
|
if (!active) return;
|
|
3526
3299
|
setStep(0);
|
|
@@ -3530,47 +3303,38 @@ function useFilmingStep(active, key) {
|
|
|
3530
3303
|
return STEPS[Math.min(step, STEPS.length - 1)];
|
|
3531
3304
|
}
|
|
3532
3305
|
function Waveform({ active, listening }) {
|
|
3533
|
-
return /* @__PURE__ */
|
|
3306
|
+
return /* @__PURE__ */ jsx8("span", { className: `waveform${active ? " on" : ""}${listening ? " hearing" : ""}`, "aria-hidden": "true", children: [0, 1, 2, 3, 4].map((bar) => /* @__PURE__ */ jsx8("span", { style: { animationDelay: `${bar * 140}ms`, animationDuration: `${900 + bar * 130}ms` } }, bar)) });
|
|
3534
3307
|
}
|
|
3535
|
-
function VideoChat({ options = {}, className, welcomeTitle,
|
|
3536
|
-
const [dismissedNoticeTurn, setDismissedNoticeTurn] =
|
|
3537
|
-
const [draft, setDraft] =
|
|
3538
|
-
const [savedSessions, setSavedSessions] =
|
|
3539
|
-
const [
|
|
3540
|
-
const [
|
|
3541
|
-
const [
|
|
3542
|
-
const [
|
|
3543
|
-
const [
|
|
3544
|
-
const [
|
|
3545
|
-
const [
|
|
3546
|
-
const
|
|
3547
|
-
const
|
|
3548
|
-
const
|
|
3549
|
-
const panelRef = useRef7(null);
|
|
3550
|
-
const composerRef = useRef7(null);
|
|
3308
|
+
function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice = false }) {
|
|
3309
|
+
const [dismissedNoticeTurn, setDismissedNoticeTurn] = useState7();
|
|
3310
|
+
const [draft, setDraft] = useState7("");
|
|
3311
|
+
const [savedSessions, setSavedSessions] = useState7([]);
|
|
3312
|
+
const [historyOpen, setHistoryOpen] = useState7(false);
|
|
3313
|
+
const [settingsOpen, setSettingsOpen] = useState7(false);
|
|
3314
|
+
const [aboutOpen, setAboutOpen] = useState7(false);
|
|
3315
|
+
const [captionsOn, setCaptionsOn] = useState7(true);
|
|
3316
|
+
const [captionsExpanded, setCaptionsExpanded] = useState7(false);
|
|
3317
|
+
const [alwaysShowControls, setAlwaysShowControls] = useState7(false);
|
|
3318
|
+
const [editing, setEditing] = useState7(false);
|
|
3319
|
+
const resumeAfterInput = useRef8(false);
|
|
3320
|
+
const panelRef = useRef8(null);
|
|
3321
|
+
const composerRef = useRef8(null);
|
|
3551
3322
|
const viewportOrientation = useViewportOrientation();
|
|
3552
|
-
const theme = themeById(themeId);
|
|
3553
3323
|
const sessionOrientation = options.orientation ?? viewportOrientation;
|
|
3554
|
-
const { brand: configuredBrand, style: configuredStyle, ...sessionOptions } = options;
|
|
3555
|
-
const presentationBrand = configuredBrand ?? theme.brand;
|
|
3556
3324
|
const { chat, restoreSession } = useVideoChatSession({
|
|
3557
|
-
...
|
|
3558
|
-
mode: modeId,
|
|
3325
|
+
...options,
|
|
3559
3326
|
orientation: sessionOrientation,
|
|
3560
|
-
|
|
3561
|
-
style: { generatedLook: theme.generatedLook, ...configuredStyle }
|
|
3327
|
+
style: { generatedLook: "Natural light, restrained camera movement, documentary realism, consistent natural color. No embedded text.", ...options.style }
|
|
3562
3328
|
});
|
|
3563
3329
|
const instanceId = useId();
|
|
3564
3330
|
const historyId = `${instanceId}-history`;
|
|
3565
3331
|
const settingsId = `${instanceId}-settings`;
|
|
3566
3332
|
const promptId = `${instanceId}-prompt`;
|
|
3567
|
-
const
|
|
3568
|
-
const
|
|
3569
|
-
const
|
|
3570
|
-
const
|
|
3571
|
-
const
|
|
3572
|
-
const settingsRef = useRef7(null);
|
|
3573
|
-
const settingsButtonRef = useRef7(null);
|
|
3333
|
+
const inputRef = useRef8(null);
|
|
3334
|
+
const historyRef = useRef8(null);
|
|
3335
|
+
const historyButtonRef = useRef8(null);
|
|
3336
|
+
const settingsRef = useRef8(null);
|
|
3337
|
+
const settingsButtonRef = useRef8(null);
|
|
3574
3338
|
const listen = useVoiceInput(setDraft, chat.capabilities?.transcription ?? false, {
|
|
3575
3339
|
endpoint: options.endpoint,
|
|
3576
3340
|
headers: options.headers,
|
|
@@ -3623,17 +3387,19 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3623
3387
|
const current = chat.currentTurn;
|
|
3624
3388
|
const shown = chat.shownTurn;
|
|
3625
3389
|
const showing = chat.playerProps != null;
|
|
3626
|
-
const
|
|
3390
|
+
const [openingAsset, setOpeningAsset] = useState7();
|
|
3391
|
+
const openingAssetKey = `${shown?.id ?? ""}:${shown?.openingMedia?.url ?? ""}`;
|
|
3392
|
+
const openingMediaFailed = openingAsset?.key === openingAssetKey && openingAsset.state === "failed";
|
|
3393
|
+
const openingMediaReady = openingAsset?.key === openingAssetKey && openingAsset.state === "ready";
|
|
3394
|
+
const openingChapter = !showing && Boolean(shown?.opening) && (!shown?.openingMedia || openingMediaFailed || !openingMediaReady);
|
|
3627
3395
|
const waitingForPicture = chat.turns.length > 0 && !showing && (chat.status === "composing" || chat.status === "playing" || chat.status === "paused");
|
|
3628
3396
|
const filmingStep = useFilmingStep(waitingForPicture, current?.id);
|
|
3629
3397
|
const status = chat.turns.length === 0 ? "idle" : chat.status === "composing" ? "drawing" : chat.status === "playing" ? "narrating" : chat.status === "error" || chat.status === "cancelled" ? "ended" : chat.status;
|
|
3630
3398
|
const shownOrientation = shown?.orientation ?? sessionOrientation;
|
|
3631
3399
|
const stageOrientation = shown?.fixedOrientation ? shownOrientation : sessionOrientation;
|
|
3632
|
-
const availableModes = visualModes.filter((option) => chat.availableModes.includes(option.id));
|
|
3633
|
-
const selectedMode = modeById(modeId);
|
|
3634
3400
|
const line = chat.caption ?? "";
|
|
3635
3401
|
const fullTranscript = shown?.video ? [shown.opening, ...shown.video.scenes.map((scene) => scene.narration)].filter((entry) => Boolean(entry)) : chat.transcript;
|
|
3636
|
-
const transport = status === "narrating" ? { label: "Pause", action: chat.pause, icon: /* @__PURE__ */
|
|
3402
|
+
const transport = status === "narrating" ? { label: "Pause", action: chat.pause, icon: /* @__PURE__ */ jsx8(Stop, {}) } : status === "paused" ? { label: "Continue", action: chat.resume, icon: /* @__PURE__ */ jsx8(Play, {}) } : status === "ended" && shown?.completed && shown.video ? { label: "Play again", action: chat.replay, icon: /* @__PURE__ */ jsx8(Replay, {}) } : void 0;
|
|
3637
3403
|
const cancelInput = useCallback6(() => {
|
|
3638
3404
|
listen.stop();
|
|
3639
3405
|
setDraft("");
|
|
@@ -3664,7 +3430,7 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3664
3430
|
onFocusCapture: controls.onFocusCapture,
|
|
3665
3431
|
onBlurCapture: controls.onBlurCapture
|
|
3666
3432
|
};
|
|
3667
|
-
|
|
3433
|
+
useLayoutEffect2(() => {
|
|
3668
3434
|
const composer = composerRef.current;
|
|
3669
3435
|
if (!composer) return;
|
|
3670
3436
|
const measure = () => panelRef.current?.style.setProperty("--composer-height", `${composer.getBoundingClientRect().height}px`);
|
|
@@ -3687,7 +3453,7 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3687
3453
|
onKeyDownCapture: controls.reveal,
|
|
3688
3454
|
children: [
|
|
3689
3455
|
/* @__PURE__ */ jsxs6("header", { className: "chrome", ...controlEvents, children: [
|
|
3690
|
-
/* @__PURE__ */
|
|
3456
|
+
/* @__PURE__ */ jsx8("div", { className: "session-brand", children: /* @__PURE__ */ jsx8(Logo, {}) }),
|
|
3691
3457
|
/* @__PURE__ */ jsxs6("div", { className: "group", children: [
|
|
3692
3458
|
/* @__PURE__ */ jsxs6(
|
|
3693
3459
|
"button",
|
|
@@ -3704,12 +3470,12 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3704
3470
|
setSettingsOpen(false);
|
|
3705
3471
|
},
|
|
3706
3472
|
children: [
|
|
3707
|
-
/* @__PURE__ */
|
|
3708
|
-
/* @__PURE__ */
|
|
3473
|
+
/* @__PURE__ */ jsx8(Sessions, {}),
|
|
3474
|
+
/* @__PURE__ */ jsx8("span", { className: "nav-label", children: "Sessions" })
|
|
3709
3475
|
]
|
|
3710
3476
|
}
|
|
3711
3477
|
),
|
|
3712
|
-
/* @__PURE__ */
|
|
3478
|
+
/* @__PURE__ */ jsx8(
|
|
3713
3479
|
"button",
|
|
3714
3480
|
{
|
|
3715
3481
|
ref: settingsButtonRef,
|
|
@@ -3723,10 +3489,10 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3723
3489
|
setSettingsOpen((open) => !open);
|
|
3724
3490
|
setHistoryOpen(false);
|
|
3725
3491
|
},
|
|
3726
|
-
children: /* @__PURE__ */
|
|
3492
|
+
children: /* @__PURE__ */ jsx8(Gear, {})
|
|
3727
3493
|
}
|
|
3728
3494
|
),
|
|
3729
|
-
/* @__PURE__ */
|
|
3495
|
+
/* @__PURE__ */ jsx8(
|
|
3730
3496
|
"button",
|
|
3731
3497
|
{
|
|
3732
3498
|
type: "button",
|
|
@@ -3734,27 +3500,37 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3734
3500
|
"aria-label": chat.muted ? "Turn the voice on" : "Turn the voice off",
|
|
3735
3501
|
"aria-pressed": chat.muted,
|
|
3736
3502
|
onClick: () => chat.setMuted(!chat.muted),
|
|
3737
|
-
children: chat.muted ? /* @__PURE__ */
|
|
3503
|
+
children: chat.muted ? /* @__PURE__ */ jsx8(Muted, {}) : /* @__PURE__ */ jsx8(Sound, {})
|
|
3738
3504
|
}
|
|
3739
3505
|
)
|
|
3740
3506
|
] })
|
|
3741
3507
|
] }),
|
|
3742
3508
|
/* @__PURE__ */ jsxs6("div", { className: "stage-area", children: [
|
|
3743
|
-
/* @__PURE__ */ jsxs6("div", { className: "stage", style: { background:
|
|
3744
|
-
showing && /* @__PURE__ */ jsx7("div", { className: "ambient-media", "aria-hidden": "true", children: ambientPoster && /* @__PURE__ */ jsx7("img", { className: "frame-media", src: ambientPoster, alt: "" }) }),
|
|
3509
|
+
/* @__PURE__ */ jsxs6("div", { className: "stage", style: { background: "#000" }, children: [
|
|
3745
3510
|
!showing && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
3746
|
-
/* @__PURE__ */
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
/* @__PURE__ */
|
|
3511
|
+
/* @__PURE__ */ jsx8("div", { className: "ground", "aria-hidden": "true" }),
|
|
3512
|
+
openingChapter && /* @__PURE__ */ jsx8(OpeningChapter, { title: shown.opening }, shown.id),
|
|
3513
|
+
shown?.openingMedia && !openingMediaFailed && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
3514
|
+
/* @__PURE__ */ jsx8(
|
|
3515
|
+
Frame,
|
|
3516
|
+
{
|
|
3517
|
+
media: shown.openingMedia,
|
|
3518
|
+
poster: true,
|
|
3519
|
+
revealWhenReady: true,
|
|
3520
|
+
onReady: () => setOpeningAsset({ key: openingAssetKey, state: "ready" }),
|
|
3521
|
+
onError: () => setOpeningAsset({ key: openingAssetKey, state: "failed" })
|
|
3522
|
+
},
|
|
3523
|
+
openingAssetKey
|
|
3524
|
+
),
|
|
3525
|
+
openingMediaReady && /* @__PURE__ */ jsx8("div", { className: "opening-wash", "aria-hidden": "true" })
|
|
3750
3526
|
] }),
|
|
3751
|
-
chat.turns.length === 0 && /* @__PURE__ */
|
|
3752
|
-
shown?.prompt && /* @__PURE__ */ jsxs6("div", { className: "asked", children: [
|
|
3753
|
-
/* @__PURE__ */
|
|
3754
|
-
waitingForPicture && /* @__PURE__ */
|
|
3527
|
+
chat.turns.length === 0 && /* @__PURE__ */ jsx8(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle }),
|
|
3528
|
+
shown?.prompt && !shown.opening && /* @__PURE__ */ jsxs6("div", { className: "asked", children: [
|
|
3529
|
+
/* @__PURE__ */ jsx8("p", { className: "asked-prompt", children: shown.prompt }),
|
|
3530
|
+
waitingForPicture && /* @__PURE__ */ jsx8("p", { className: "asked-step", "aria-live": "polite", children: filmingStep })
|
|
3755
3531
|
] })
|
|
3756
3532
|
] }),
|
|
3757
|
-
chat.playerProps && /* @__PURE__ */
|
|
3533
|
+
chat.playerProps && /* @__PURE__ */ jsx8("div", { className: "player-fit", style: { width: stageOrientation === "portrait" ? "min(100cqw, 56.25cqh)" : "min(100cqw, 177.7778cqh)" }, children: /* @__PURE__ */ jsx8(
|
|
3758
3534
|
VideoPlayer,
|
|
3759
3535
|
{
|
|
3760
3536
|
...chat.playerProps,
|
|
@@ -3766,27 +3542,27 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3766
3542
|
chat.playerKey
|
|
3767
3543
|
) }),
|
|
3768
3544
|
showing && chat.playbackEnded && chat.suggestions.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "ending", children: [
|
|
3769
|
-
/* @__PURE__ */
|
|
3545
|
+
/* @__PURE__ */ jsx8("div", { className: "ending-wash", "aria-hidden": "true" }),
|
|
3770
3546
|
/* @__PURE__ */ jsxs6("div", { className: "ending-body", children: [
|
|
3771
|
-
/* @__PURE__ */
|
|
3772
|
-
/* @__PURE__ */
|
|
3547
|
+
/* @__PURE__ */ jsx8("p", { className: "ending-label", children: "Ask next" }),
|
|
3548
|
+
/* @__PURE__ */ jsx8(SuggestionCards, { suggestions: [...chat.suggestions], label: "Follow-up prompts", onAsk: ask })
|
|
3773
3549
|
] })
|
|
3774
3550
|
] })
|
|
3775
3551
|
] }),
|
|
3776
3552
|
historyOpen && /* @__PURE__ */ jsxs6("nav", { ref: historyRef, id: historyId, className: "sheet-popover history", role: "dialog", "aria-modal": "true", "aria-label": "Sessions", children: [
|
|
3777
3553
|
/* @__PURE__ */ jsxs6("div", { className: "popover-heading", children: [
|
|
3778
|
-
/* @__PURE__ */
|
|
3779
|
-
/* @__PURE__ */
|
|
3554
|
+
/* @__PURE__ */ jsx8("h2", { children: "Sessions" }),
|
|
3555
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Close sessions", onClick: closeHistory, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3780
3556
|
] }),
|
|
3781
3557
|
/* @__PURE__ */ jsxs6("button", { type: "button", className: "history-row session-new", "aria-label": "New session", onClick: newSession, children: [
|
|
3782
|
-
/* @__PURE__ */
|
|
3558
|
+
/* @__PURE__ */ jsx8(Plus, {}),
|
|
3783
3559
|
/* @__PURE__ */ jsxs6("span", { className: "prompt", children: [
|
|
3784
3560
|
"New session",
|
|
3785
|
-
/* @__PURE__ */
|
|
3561
|
+
/* @__PURE__ */ jsx8("small", { children: "Start a fresh conversation" })
|
|
3786
3562
|
] })
|
|
3787
3563
|
] }),
|
|
3788
|
-
/* @__PURE__ */
|
|
3789
|
-
chat.turns.length === 0 && /* @__PURE__ */
|
|
3564
|
+
/* @__PURE__ */ jsx8("h3", { className: "section-label", children: "Current session" }),
|
|
3565
|
+
chat.turns.length === 0 && /* @__PURE__ */ jsx8("p", { className: "history-empty", children: "Your questions will appear here." }),
|
|
3790
3566
|
chat.turns.map((turn, index) => /* @__PURE__ */ jsxs6(
|
|
3791
3567
|
"button",
|
|
3792
3568
|
{
|
|
@@ -3803,17 +3579,17 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3803
3579
|
setHistoryOpen(false);
|
|
3804
3580
|
},
|
|
3805
3581
|
children: [
|
|
3806
|
-
/* @__PURE__ */
|
|
3582
|
+
/* @__PURE__ */ jsx8("span", { className: "index", children: String(index + 1).padStart(2, "0") }),
|
|
3807
3583
|
/* @__PURE__ */ jsxs6("span", { className: "prompt", children: [
|
|
3808
3584
|
turn.prompt,
|
|
3809
|
-
/* @__PURE__ */
|
|
3585
|
+
/* @__PURE__ */ jsx8("small", { children: turn.id === shown?.id ? "Now showing" : turn.completed ? "Play answer" : "Unfinished answer" })
|
|
3810
3586
|
] })
|
|
3811
3587
|
]
|
|
3812
3588
|
},
|
|
3813
3589
|
turn.id
|
|
3814
3590
|
)),
|
|
3815
3591
|
savedSessions.length > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
3816
|
-
/* @__PURE__ */
|
|
3592
|
+
/* @__PURE__ */ jsx8("h3", { className: "section-label", children: "Earlier sessions" }),
|
|
3817
3593
|
savedSessions.map((session) => /* @__PURE__ */ jsxs6("button", { type: "button", className: "history-row", onClick: () => {
|
|
3818
3594
|
listen.stop();
|
|
3819
3595
|
setDraft("");
|
|
@@ -3825,7 +3601,7 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3825
3601
|
setHistoryOpen(false);
|
|
3826
3602
|
setCaptionsExpanded(false);
|
|
3827
3603
|
}, children: [
|
|
3828
|
-
/* @__PURE__ */
|
|
3604
|
+
/* @__PURE__ */ jsx8(Replay, {}),
|
|
3829
3605
|
/* @__PURE__ */ jsxs6("span", { className: "prompt", children: [
|
|
3830
3606
|
session.turns[0]?.prompt,
|
|
3831
3607
|
/* @__PURE__ */ jsxs6("small", { children: [
|
|
@@ -3848,72 +3624,55 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3848
3624
|
"aria-modal": "true",
|
|
3849
3625
|
children: [
|
|
3850
3626
|
/* @__PURE__ */ jsxs6("div", { className: "popover-heading", children: [
|
|
3851
|
-
/* @__PURE__ */
|
|
3852
|
-
/* @__PURE__ */
|
|
3627
|
+
/* @__PURE__ */ jsx8("h2", { children: "Settings" }),
|
|
3628
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Close settings", onClick: closeSettings, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3853
3629
|
] }),
|
|
3854
3630
|
/* @__PURE__ */ jsxs6("fieldset", { className: "playback-options", children: [
|
|
3855
|
-
/* @__PURE__ */
|
|
3631
|
+
/* @__PURE__ */ jsx8("legend", { children: "Watching" }),
|
|
3856
3632
|
/* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
|
|
3857
3633
|
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3858
|
-
/* @__PURE__ */
|
|
3859
|
-
/* @__PURE__ */
|
|
3634
|
+
/* @__PURE__ */ jsx8("strong", { children: "Subtitles" }),
|
|
3635
|
+
/* @__PURE__ */ jsx8("small", { children: "Read along with the answer" })
|
|
3860
3636
|
] }),
|
|
3861
|
-
/* @__PURE__ */
|
|
3637
|
+
/* @__PURE__ */ jsx8("input", { type: "checkbox", role: "switch", checked: captionsOn, onChange: (event) => {
|
|
3862
3638
|
setCaptionsOn(event.target.checked);
|
|
3863
3639
|
setCaptionsExpanded(false);
|
|
3864
3640
|
} })
|
|
3865
3641
|
] }),
|
|
3866
3642
|
/* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
|
|
3867
3643
|
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3868
|
-
/* @__PURE__ */
|
|
3869
|
-
/* @__PURE__ */
|
|
3644
|
+
/* @__PURE__ */ jsx8("strong", { children: "Keep controls visible" }),
|
|
3645
|
+
/* @__PURE__ */ jsx8("small", { children: "Keep the input bar on screen" })
|
|
3870
3646
|
] }),
|
|
3871
|
-
/* @__PURE__ */
|
|
3647
|
+
/* @__PURE__ */ jsx8("input", { type: "checkbox", role: "switch", checked: alwaysShowControls, onChange: (event) => setAlwaysShowControls(event.target.checked) })
|
|
3872
3648
|
] })
|
|
3873
3649
|
] }),
|
|
3874
|
-
/* @__PURE__ */ jsxs6("fieldset", { className: "visual-options", children: [
|
|
3875
|
-
/* @__PURE__ */ jsx7("legend", { children: "Video creation" }),
|
|
3876
|
-
/* @__PURE__ */ jsx7("div", { className: "visual-choices", children: availableModes.map((option) => /* @__PURE__ */ jsxs6("label", { className: "choice-row", children: [
|
|
3877
|
-
/* @__PURE__ */ jsx7("input", { type: "radio", name: visualsName, checked: option.id === selectedMode.id, onChange: () => setModeId(option.id) }),
|
|
3878
|
-
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3879
|
-
/* @__PURE__ */ jsx7("strong", { children: option.id === "full" ? generatedVideoLabel ?? option.label : option.label }),
|
|
3880
|
-
/* @__PURE__ */ jsx7("small", { children: option.id === "full" ? generatedVideoDescription ?? option.note : option.note })
|
|
3881
|
-
] })
|
|
3882
|
-
] }, option.id)) })
|
|
3883
|
-
] }),
|
|
3884
|
-
/* @__PURE__ */ jsxs6("fieldset", { className: "style-options", children: [
|
|
3885
|
-
/* @__PURE__ */ jsx7("legend", { children: "Video style" }),
|
|
3886
|
-
/* @__PURE__ */ jsx7("div", { className: "style-choices", children: themes.map((option) => /* @__PURE__ */ jsxs6("label", { children: [
|
|
3887
|
-
/* @__PURE__ */ jsx7("input", { type: "radio", name: styleName, checked: option.id === themeId, onChange: () => setThemeId(option.id) }),
|
|
3888
|
-
/* @__PURE__ */ jsx7("span", { children: /* @__PURE__ */ jsx7("strong", { children: option.label }) })
|
|
3889
|
-
] }, option.id)) })
|
|
3890
|
-
] }),
|
|
3891
|
-
/* @__PURE__ */ jsx7("p", { className: "settings-note", children: "Creation and style changes apply to your next question." }),
|
|
3892
3650
|
/* @__PURE__ */ jsxs6("nav", { className: "developer-links", "aria-label": "Build with VanillaSky", children: [
|
|
3893
|
-
/* @__PURE__ */
|
|
3651
|
+
/* @__PURE__ */ jsx8("p", { className: "section-label", children: "Build with VanillaSky" }),
|
|
3894
3652
|
/* @__PURE__ */ jsxs6("a", { href: "https://github.com/VanillaSkyAi/video/blob/main/docs/getting-started.md", target: "_blank", rel: "noopener noreferrer", children: [
|
|
3895
3653
|
"Docs",
|
|
3896
|
-
/* @__PURE__ */
|
|
3654
|
+
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2197" })
|
|
3897
3655
|
] }),
|
|
3898
3656
|
/* @__PURE__ */ jsxs6("button", { type: "button", "aria-expanded": aboutOpen, "aria-controls": `${instanceId}-about`, onClick: () => setAboutOpen((open) => !open), children: [
|
|
3899
3657
|
"About",
|
|
3900
|
-
/* @__PURE__ */
|
|
3658
|
+
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: aboutOpen ? "\u2212" : "+" })
|
|
3901
3659
|
] }),
|
|
3902
|
-
/* @__PURE__ */
|
|
3660
|
+
/* @__PURE__ */ jsx8("div", { id: `${instanceId}-about`, className: "developer-about", role: "region", "aria-label": "About VanillaSky", hidden: !aboutOpen, children: /* @__PURE__ */ jsx8("p", { children: "VanillaSky is an open-source SDK for conversations that answer in video. Developers connect their own AI providers through their application server." }) }),
|
|
3903
3661
|
/* @__PURE__ */ jsxs6("a", { href: "https://github.com/VanillaSkyAi/video", target: "_blank", rel: "noopener noreferrer", children: [
|
|
3904
3662
|
"GitHub",
|
|
3905
|
-
/* @__PURE__ */
|
|
3663
|
+
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2197" })
|
|
3906
3664
|
] })
|
|
3907
3665
|
] })
|
|
3908
3666
|
]
|
|
3909
3667
|
}
|
|
3910
3668
|
)
|
|
3911
3669
|
] }),
|
|
3912
|
-
/* @__PURE__ */
|
|
3913
|
-
/* @__PURE__ */
|
|
3670
|
+
/* @__PURE__ */ jsx8("div", { ref: panelRef, className: "panel", "data-input-visible": controls.visible || !captionsOn || !line, children: /* @__PURE__ */ jsxs6("div", { className: "panel-inner", children: [
|
|
3671
|
+
/* @__PURE__ */ jsx8("div", { className: "caption-slot", "data-captions": captionsOn && Boolean(line), "aria-hidden": !captionsOn || !line, children: /* @__PURE__ */ jsx8("div", { className: "caption-clip", children: /* @__PURE__ */ jsxs6(
|
|
3914
3672
|
"div",
|
|
3915
3673
|
{
|
|
3916
3674
|
className: "line-row",
|
|
3675
|
+
"data-opening-copy": openingChapter && line === shown?.opening && !captionsExpanded,
|
|
3917
3676
|
"data-expanded": captionsExpanded,
|
|
3918
3677
|
"data-actions-visible": captionControls.visible,
|
|
3919
3678
|
onPointerMove: captionControls.onPointerEnter,
|
|
@@ -3925,41 +3684,41 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3925
3684
|
captionsOn && line && /* @__PURE__ */ jsxs6("div", { className: "caption-actions", children: [
|
|
3926
3685
|
/* @__PURE__ */ jsxs6("button", { type: "button", className: "caption-action", "aria-label": captionsExpanded ? "Collapse subtitles" : "Expand subtitles", "aria-expanded": captionsExpanded, onClick: () => setCaptionsExpanded((open) => !open), children: [
|
|
3927
3686
|
captionsExpanded ? "Collapse" : "Expand",
|
|
3928
|
-
/* @__PURE__ */
|
|
3687
|
+
/* @__PURE__ */ jsx8(ChevronUp, {})
|
|
3929
3688
|
] }),
|
|
3930
|
-
/* @__PURE__ */
|
|
3689
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "caption-action", "aria-label": "Hide subtitles", onClick: () => {
|
|
3931
3690
|
setCaptionsOn(false);
|
|
3932
3691
|
setCaptionsExpanded(false);
|
|
3933
|
-
}, children: /* @__PURE__ */
|
|
3692
|
+
}, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3934
3693
|
] }),
|
|
3935
|
-
captionsExpanded ? /* @__PURE__ */
|
|
3694
|
+
captionsExpanded ? /* @__PURE__ */ jsx8("div", { className: "expanded-captions", role: "region", tabIndex: 0, "aria-label": "Expanded subtitles", children: fullTranscript.map((entry, index) => /* @__PURE__ */ jsx8("p", { children: entry }, index)) }) : /* @__PURE__ */ jsx8("p", { className: "line", "aria-live": "polite", children: openingChapter && line === shown?.opening ? "" : line })
|
|
3936
3695
|
]
|
|
3937
3696
|
}
|
|
3938
3697
|
) }) }),
|
|
3939
3698
|
showRecoveryNotice && chat.shownTurn && dismissedNoticeTurn !== chat.shownTurn.id && chat.warnings.includes(MEDIA_RECOVERY_NOTICE) && !chat.error && /* @__PURE__ */ jsxs6("div", { className: "recovery-notice", children: [
|
|
3940
|
-
/* @__PURE__ */
|
|
3941
|
-
/* @__PURE__ */
|
|
3699
|
+
/* @__PURE__ */ jsx8("p", { role: "status", children: MEDIA_RECOVERY_NOTICE }),
|
|
3700
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Dismiss notice", onClick: () => setDismissedNoticeTurn(chat.shownTurn?.id), children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3942
3701
|
] }),
|
|
3943
3702
|
(chat.error || listen.error) && /* @__PURE__ */ jsxs6("p", { className: "error", role: "status", children: [
|
|
3944
|
-
/* @__PURE__ */
|
|
3945
|
-
/* @__PURE__ */
|
|
3703
|
+
/* @__PURE__ */ jsx8(Warning, {}),
|
|
3704
|
+
/* @__PURE__ */ jsx8("span", { children: chat.error?.message ?? listen.error })
|
|
3946
3705
|
] }),
|
|
3947
3706
|
/* @__PURE__ */ jsxs6("div", { ref: composerRef, className: "conversation-composer", "data-editing": editing, ...controlEvents, children: [
|
|
3948
|
-
(listen.listening || listen.thinking) && /* @__PURE__ */
|
|
3707
|
+
(listen.listening || listen.thinking) && /* @__PURE__ */ jsx8("div", { className: "composer-meta", children: /* @__PURE__ */ jsx8("span", { role: "status", children: listen.listening ? "Listening\u2026 Tap the mic to finish, then review and send." : "Turning your words into a draft\u2026" }) }),
|
|
3949
3708
|
/* @__PURE__ */ jsxs6("form", { className: "composer", "aria-label": "Ask a question", onSubmit: (event) => {
|
|
3950
3709
|
event.preventDefault();
|
|
3951
3710
|
ask(draft);
|
|
3952
3711
|
}, children: [
|
|
3953
|
-
transport && /* @__PURE__ */
|
|
3712
|
+
transport && /* @__PURE__ */ jsx8("button", { type: "button", className: "ghost transport", "aria-label": transport.label, onClick: () => {
|
|
3954
3713
|
listen.stop();
|
|
3955
3714
|
setEditing(false);
|
|
3956
3715
|
resumeAfterInput.current = false;
|
|
3957
3716
|
inputRef.current?.blur();
|
|
3958
3717
|
transport.action();
|
|
3959
3718
|
}, children: transport.icon }),
|
|
3960
|
-
/* @__PURE__ */
|
|
3961
|
-
/* @__PURE__ */
|
|
3962
|
-
/* @__PURE__ */
|
|
3719
|
+
/* @__PURE__ */ jsx8(Waveform, { active: listen.listening || chat.speaking && !chat.muted && status !== "paused", listening: listen.listening }),
|
|
3720
|
+
/* @__PURE__ */ jsx8("label", { className: "sr-only", htmlFor: promptId, children: "Prompt" }),
|
|
3721
|
+
/* @__PURE__ */ jsx8(
|
|
3963
3722
|
"textarea",
|
|
3964
3723
|
{
|
|
3965
3724
|
id: promptId,
|
|
@@ -3987,12 +3746,12 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3987
3746
|
}
|
|
3988
3747
|
}
|
|
3989
3748
|
),
|
|
3990
|
-
listen.supported && /* @__PURE__ */
|
|
3991
|
-
/* @__PURE__ */
|
|
3992
|
-
editing && /* @__PURE__ */
|
|
3749
|
+
listen.supported && /* @__PURE__ */ jsx8("button", { type: "button", className: `ghost${listen.listening ? " listening" : ""}`, "aria-label": listen.listening ? "Stop listening" : "Ask by voice", "aria-pressed": listen.listening, disabled: listen.thinking, onClick: () => beginInput(true), children: /* @__PURE__ */ jsx8(Mic, {}) }),
|
|
3750
|
+
/* @__PURE__ */ jsx8("button", { type: "submit", className: "send", "aria-label": "Ask", disabled: !draft.trim() || listen.thinking, children: /* @__PURE__ */ jsx8(Send, {}) }),
|
|
3751
|
+
editing && /* @__PURE__ */ jsx8("button", { type: "button", className: "ghost", "aria-label": "Cancel question", onClick: cancelInput, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3993
3752
|
] })
|
|
3994
3753
|
] }),
|
|
3995
|
-
!showing && chat.turns.length === 0 && /* @__PURE__ */
|
|
3754
|
+
!showing && chat.turns.length === 0 && /* @__PURE__ */ jsx8("p", { className: "dock-hint", children: "Speak or type. See where it takes you." })
|
|
3996
3755
|
] }) })
|
|
3997
3756
|
]
|
|
3998
3757
|
}
|