@vanillaskyai/video 0.8.8 → 0.9.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 +30 -0
- package/PUBLIC-API.md +5 -8
- package/dist/{builtin-server-CUYPIDE7.js → builtin-server-HW6HGXCZ.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 +6 -15
- package/dist/{chunk-2USVB3CY.js → chunk-3ZTEFJIR.js} +285 -178
- package/dist/chunk-4YM2M62S.js +13 -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-EWWCQFTI.js +18 -0
- package/dist/{chunk-MXZSDGZQ.js → chunk-G7IZBBWF.js} +14 -9
- package/dist/{chunk-KT3M7KDL.js → chunk-HKUI5BTB.js} +3 -3
- package/dist/{chunk-LMG3GHLL.js → chunk-IQMYK5DX.js} +29 -3
- package/dist/chunk-LH4BPALA.js +564 -0
- package/dist/{chunk-OWBT45EN.js → chunk-MQILEEXC.js} +1 -1
- package/dist/chunk-MXEYPK7M.js +552 -0
- package/dist/{chunk-AITKH6QT.js → chunk-OR52UMSF.js} +13 -39
- 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-224QNWRA.js → chunk-UDIODEDK.js} +1 -5
- package/dist/{chunk-3VRMKFM7.js → chunk-ZDJNFA52.js} +11 -24
- package/dist/cinema-media-YQHSIFDA.js +11 -0
- package/dist/cli.js +18 -9
- package/dist/comparison-RZMMHZR2.js +27 -0
- package/dist/compose-video-TBFTONUC.js +12 -0
- package/dist/editorial-timeline-OZX7OS6G.js +36 -0
- package/dist/{events-BQ6z264n.d.ts → events-B4YCc4vc.d.ts} +1 -1
- package/dist/focus-cards-TJ72BT7U.js +20 -0
- package/dist/index.d.ts +4 -6
- package/dist/index.js +3 -7
- package/dist/key-figure-GYENXKH3.js +20 -0
- package/dist/{kit-tQnCPMOM.d.ts → kit-DrRpdn0p.d.ts} +2 -2
- package/dist/mobile-message-UZUPIKHD.js +49 -0
- package/dist/{preload-media-LJXWKTWG.js → preload-media-CFTZIMZL.js} +1 -1
- package/dist/quote-EW5HQA6H.js +23 -0
- package/dist/react.d.ts +21 -11
- package/dist/react.js +590 -833
- package/dist/scene-video-backdrop-PCTARAAJ.js +7 -0
- package/dist/server.d.ts +7 -8
- package/dist/server.js +91 -74
- package/dist/{system-prompt-RRXIWDDD.js → system-prompt-SYRT5DWM.js} +1 -1
- package/dist/template-catalog.d.ts +257 -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 +34 -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 +101 -0
- package/registry/items/editorialTimeline.json +105 -0
- package/registry/items/focusCards.json +90 -0
- package/registry/items/keyFigure.json +97 -0
- package/registry/items/mobileMessage.json +153 -0
- package/registry/items/quote.json +94 -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-LH4BPALA.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
|
-
|
|
36
|
-
|
|
37
|
-
import "./chunk-
|
|
38
|
-
import
|
|
39
|
-
fontStack
|
|
40
|
-
} from "./chunk-XWWLKRNU.js";
|
|
41
|
+
getDimensions,
|
|
42
|
+
sceneReadinessKey
|
|
43
|
+
} from "./chunk-3ZTEFJIR.js";
|
|
44
|
+
import "./chunk-UDIODEDK.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,96 @@ 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
|
-
"
|
|
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-YQHSIFDA.js").then((module) => ({ default: module.MediaSceneTemplate })),
|
|
97
|
+
"chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
|
|
98
|
+
"focusCards": () => import("./focus-cards-TJ72BT7U.js").then((module) => ({ default: module.CardsSceneTemplate })),
|
|
99
|
+
"editorialTimeline": () => import("./editorial-timeline-OZX7OS6G.js").then((module) => ({ default: module.TimelineSceneTemplate })),
|
|
100
|
+
"mobileMessage": () => import("./mobile-message-UZUPIKHD.js").then((module) => ({ default: module.NotificationSceneTemplate })),
|
|
101
|
+
"comparison": () => import("./comparison-RZMMHZR2.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
|
|
102
|
+
"quote": () => import("./quote-EW5HQA6H.js").then((module) => ({ default: module.QuoteSceneTemplate })),
|
|
103
|
+
"keyFigure": () => import("./key-figure-GYENXKH3.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
|
|
120
104
|
};
|
|
121
105
|
|
|
122
106
|
// src/visual-system/catalog/builtin-player.generated.ts
|
|
123
107
|
var GENERATED_BUILTIN_PLAYER_TEMPLATES = [
|
|
124
108
|
{
|
|
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",
|
|
109
|
+
"id": "cinemaMedia",
|
|
144
110
|
"defaults": {
|
|
145
|
-
"texts": "Still editing manually?",
|
|
146
|
-
"reactionTag": "manual",
|
|
147
111
|
"mediaUrl": "",
|
|
148
112
|
"mediaType": "video",
|
|
149
113
|
"mediaPoster": ""
|
|
150
114
|
},
|
|
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
|
-
}
|
|
115
|
+
"usesGlobalTransition": false
|
|
167
116
|
},
|
|
168
117
|
{
|
|
169
|
-
"id": "
|
|
118
|
+
"id": "chapterTitle",
|
|
170
119
|
"defaults": {
|
|
171
|
-
"
|
|
120
|
+
"title": "A different perspective"
|
|
172
121
|
},
|
|
173
|
-
"usesGlobalTransition":
|
|
174
|
-
"transitionTiming": {
|
|
175
|
-
"entryReadyProgress": 0.2,
|
|
176
|
-
"holdProgress": 0.7
|
|
177
|
-
}
|
|
122
|
+
"usesGlobalTransition": false
|
|
178
123
|
},
|
|
179
124
|
{
|
|
180
|
-
"id": "
|
|
125
|
+
"id": "focusCards",
|
|
181
126
|
"defaults": {
|
|
182
|
-
"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"unit": "",
|
|
188
|
-
"mediaUrl": "",
|
|
189
|
-
"mediaKeyword": "",
|
|
190
|
-
"mediaType": "auto",
|
|
191
|
-
"mediaPoster": "",
|
|
192
|
-
"mediaPosition": "center",
|
|
193
|
-
"mediaTreatment": "cinematic"
|
|
127
|
+
"items": [
|
|
128
|
+
"Listen closely",
|
|
129
|
+
"Notice the pattern",
|
|
130
|
+
"Make room for change"
|
|
131
|
+
]
|
|
194
132
|
},
|
|
195
|
-
"usesGlobalTransition":
|
|
196
|
-
"transitionTiming": {
|
|
197
|
-
"entryReadyProgress": 0.2,
|
|
198
|
-
"holdProgress": 0.7
|
|
199
|
-
}
|
|
133
|
+
"usesGlobalTransition": false
|
|
200
134
|
},
|
|
201
135
|
{
|
|
202
|
-
"id": "
|
|
136
|
+
"id": "editorialTimeline",
|
|
203
137
|
"defaults": {
|
|
204
|
-
"
|
|
205
|
-
"bars": [
|
|
206
|
-
{
|
|
207
|
-
"label": "Q1",
|
|
208
|
-
"value": 42
|
|
209
|
-
},
|
|
138
|
+
"events": [
|
|
210
139
|
{
|
|
211
|
-
"label": "
|
|
212
|
-
"value": 58
|
|
140
|
+
"label": "Observe"
|
|
213
141
|
},
|
|
214
142
|
{
|
|
215
|
-
"label": "
|
|
216
|
-
"value": 76
|
|
143
|
+
"label": "Understand"
|
|
217
144
|
},
|
|
218
145
|
{
|
|
219
|
-
"label": "
|
|
220
|
-
"value": 91
|
|
146
|
+
"label": "Act"
|
|
221
147
|
}
|
|
222
|
-
]
|
|
223
|
-
"mediaUrl": "",
|
|
224
|
-
"mediaKeyword": "",
|
|
225
|
-
"mediaType": "auto",
|
|
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"
|
|
470
|
-
},
|
|
471
|
-
"usesGlobalTransition": false
|
|
472
|
-
},
|
|
473
|
-
{
|
|
474
|
-
"id": "incomingCall",
|
|
475
|
-
"defaults": {
|
|
476
|
-
"callerName": "Your brand",
|
|
477
|
-
"subtitle": "is calling....",
|
|
478
|
-
"declineLabel": "Decline",
|
|
479
|
-
"acceptLabel": "Accept",
|
|
480
|
-
"mediaUrl": "",
|
|
481
|
-
"mediaKeyword": "",
|
|
482
|
-
"mediaType": "auto",
|
|
483
|
-
"mediaPoster": "",
|
|
484
|
-
"mediaPosition": "center",
|
|
485
|
-
"mediaTreatment": "cinematic"
|
|
148
|
+
]
|
|
486
149
|
},
|
|
487
150
|
"usesGlobalTransition": false
|
|
488
151
|
},
|
|
489
152
|
{
|
|
490
|
-
"id": "
|
|
153
|
+
"id": "mobileMessage",
|
|
491
154
|
"defaults": {
|
|
492
|
-
"message": "We built this for you.",
|
|
493
155
|
"mediaUrl": "",
|
|
494
|
-
"
|
|
495
|
-
"mediaType": "auto",
|
|
156
|
+
"mediaType": "video",
|
|
496
157
|
"mediaPoster": "",
|
|
497
|
-
"
|
|
498
|
-
"
|
|
158
|
+
"message": "Can we talk?",
|
|
159
|
+
"app": "Messages"
|
|
499
160
|
},
|
|
500
161
|
"usesGlobalTransition": false
|
|
501
162
|
},
|
|
502
163
|
{
|
|
503
|
-
"id": "
|
|
164
|
+
"id": "comparison",
|
|
504
165
|
"defaults": {
|
|
505
|
-
"
|
|
506
|
-
"
|
|
507
|
-
"mediaKeyword": "",
|
|
508
|
-
"mediaType": "auto",
|
|
509
|
-
"mediaPoster": "",
|
|
510
|
-
"mediaPosition": "center",
|
|
511
|
-
"mediaTreatment": "cinematic"
|
|
166
|
+
"leftText": "More distractions",
|
|
167
|
+
"rightText": "More room to think"
|
|
512
168
|
},
|
|
513
169
|
"usesGlobalTransition": false
|
|
514
170
|
},
|
|
515
171
|
{
|
|
516
|
-
"id": "
|
|
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
|
|
539
|
-
},
|
|
172
|
+
"id": "quote",
|
|
173
|
+
"defaults": {},
|
|
540
174
|
"usesGlobalTransition": false
|
|
541
175
|
},
|
|
542
176
|
{
|
|
543
|
-
"id": "
|
|
544
|
-
"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
|
-
"mediaUrl": "",
|
|
553
|
-
"mediaKeyword": "",
|
|
554
|
-
"mediaType": "auto",
|
|
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"
|
|
578
|
-
},
|
|
177
|
+
"id": "keyFigure",
|
|
178
|
+
"defaults": {},
|
|
579
179
|
"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
180
|
}
|
|
664
181
|
];
|
|
182
|
+
var GENERATED_BUILTIN_VIDEO_BACKDROP_IDS = ["cinemaMedia", "mobileMessage"];
|
|
665
183
|
|
|
666
184
|
// src/visual-system/catalog/builtin-player.ts
|
|
667
|
-
function createPreloadableBuiltinTemplate(loader) {
|
|
185
|
+
function createPreloadableBuiltinTemplate(loader, mediaBackdrop) {
|
|
668
186
|
let loaded;
|
|
669
187
|
let failure;
|
|
670
188
|
let pending;
|
|
@@ -687,7 +205,8 @@ function createPreloadableBuiltinTemplate(loader) {
|
|
|
687
205
|
if (failure) throw failure;
|
|
688
206
|
throw preload();
|
|
689
207
|
};
|
|
690
|
-
|
|
208
|
+
const template = { component, preload };
|
|
209
|
+
return mediaBackdrop ? markExternalVideoBackdropTemplate(template) : template;
|
|
691
210
|
}
|
|
692
211
|
function freezeValue(value) {
|
|
693
212
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -699,7 +218,7 @@ function freezeValue(value) {
|
|
|
699
218
|
var preloadableById = new Map(
|
|
700
219
|
GENERATED_BUILTIN_PLAYER_TEMPLATES.map(({ id }) => [
|
|
701
220
|
id,
|
|
702
|
-
createPreloadableBuiltinTemplate(GENERATED_BUILTIN_TEMPLATE_LOADERS[id])
|
|
221
|
+
createPreloadableBuiltinTemplate(GENERATED_BUILTIN_TEMPLATE_LOADERS[id], GENERATED_BUILTIN_VIDEO_BACKDROP_IDS.includes(id))
|
|
703
222
|
])
|
|
704
223
|
);
|
|
705
224
|
var builtinPlayerTemplates = Object.freeze(
|
|
@@ -716,7 +235,7 @@ function preloadBuiltinTemplate(id) {
|
|
|
716
235
|
|
|
717
236
|
// src/player/warm-scene-media.ts
|
|
718
237
|
function warmSceneMedia(variables) {
|
|
719
|
-
void import("./preload-media-
|
|
238
|
+
void import("./preload-media-CFTZIMZL.js").then((module) => module.preloadSceneMedia(variables)).catch(() => {
|
|
720
239
|
});
|
|
721
240
|
}
|
|
722
241
|
|
|
@@ -817,8 +336,6 @@ function GenerationCover({
|
|
|
817
336
|
onStart
|
|
818
337
|
}) {
|
|
819
338
|
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
339
|
return /* @__PURE__ */ jsxs(
|
|
823
340
|
"div",
|
|
824
341
|
{
|
|
@@ -832,9 +349,9 @@ function GenerationCover({
|
|
|
832
349
|
placeItems: "center",
|
|
833
350
|
padding: "12%",
|
|
834
351
|
textAlign: "center",
|
|
835
|
-
color:
|
|
836
|
-
background:
|
|
837
|
-
fontFamily:
|
|
352
|
+
color: "#fff",
|
|
353
|
+
background: "#000",
|
|
354
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif'
|
|
838
355
|
},
|
|
839
356
|
children: [
|
|
840
357
|
/* @__PURE__ */ jsxs("div", { style: { position: "absolute", top: "28%", left: "10%", right: "10%" }, children: [
|
|
@@ -1013,6 +530,8 @@ function usePlaybackClock({
|
|
|
1013
530
|
audioRef,
|
|
1014
531
|
loopRef,
|
|
1015
532
|
sceneIndexRef,
|
|
533
|
+
visualReadyRef,
|
|
534
|
+
posterBridgeKeysRef,
|
|
1016
535
|
callbacksRef,
|
|
1017
536
|
setCurrentTime,
|
|
1018
537
|
setIsPlaying
|
|
@@ -1020,6 +539,16 @@ function usePlaybackClock({
|
|
|
1020
539
|
useEffect(() => {
|
|
1021
540
|
if (!isPlaying) return;
|
|
1022
541
|
let stalled = false;
|
|
542
|
+
let onsetWaitSeconds = 0;
|
|
543
|
+
let clockWaitSeconds = 0;
|
|
544
|
+
let lastNarrationTime;
|
|
545
|
+
const failNarration = (error, state) => {
|
|
546
|
+
setIsPlaying(false);
|
|
547
|
+
try {
|
|
548
|
+
void Promise.resolve(callbacksRef.current.onError?.(error, state)).catch(() => void 0);
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
551
|
+
};
|
|
1023
552
|
const reportStall = (next) => {
|
|
1024
553
|
if (stalled === next) return;
|
|
1025
554
|
stalled = next;
|
|
@@ -1033,14 +562,46 @@ function usePlaybackClock({
|
|
|
1033
562
|
const tick = (now) => {
|
|
1034
563
|
const current = stateRef.current;
|
|
1035
564
|
const config = current.config;
|
|
1036
|
-
const
|
|
565
|
+
const elapsed = Math.max(0, (now - previous) / 1e3);
|
|
566
|
+
let narrationReady = true;
|
|
567
|
+
try {
|
|
568
|
+
narrationReady = callbacksRef.current.narrationReady?.() !== false;
|
|
569
|
+
} catch (cause) {
|
|
570
|
+
failNarration(cause instanceof Error ? cause : new Error("Narration readiness failed"), current);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
onsetWaitSeconds = narrationReady ? 0 : onsetWaitSeconds + elapsed;
|
|
574
|
+
if (onsetWaitSeconds >= 8) {
|
|
575
|
+
failNarration(new Error("Narration did not report audio onset within eight seconds"), current);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const delta = narrationReady ? elapsed : 0;
|
|
1037
579
|
previous = now;
|
|
1038
580
|
const settled = current.status === "complete" || current.status === "error" || current.status === "aborted";
|
|
1039
581
|
const looping = loopRef.current && settled;
|
|
1040
582
|
let wrapped = false;
|
|
1041
583
|
if (config?.scenes.length) {
|
|
1042
584
|
const duration2 = getVideoDuration(config);
|
|
1043
|
-
const
|
|
585
|
+
const ranges = resolveVideoTimeline(config);
|
|
586
|
+
const cued = ranges[sceneIndexRef.current];
|
|
587
|
+
let narrationTime;
|
|
588
|
+
try {
|
|
589
|
+
narrationTime = cued ? callbacksRef.current.narrationTime?.(cued.scene) : void 0;
|
|
590
|
+
} catch (cause) {
|
|
591
|
+
failNarration(cause instanceof Error ? cause : new Error("Narration clock failed"), current);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (narrationTime !== void 0 && (!Number.isFinite(narrationTime) || narrationTime < 0)) {
|
|
595
|
+
failNarration(new Error("Narration clock must return finite nonnegative seconds"), current);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
clockWaitSeconds = narrationTime !== void 0 && narrationTime === lastNarrationTime && narrationReady && !stalled ? clockWaitSeconds + elapsed : 0;
|
|
599
|
+
lastNarrationTime = narrationTime;
|
|
600
|
+
if (clockWaitSeconds >= 8) {
|
|
601
|
+
failNarration(new Error("Narration audio clock did not advance within eight seconds"), current);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const raw = narrationTime !== void 0 && cued ? cued.start - (cued.scene.narrationGroup?.offsetSeconds ?? 0) + narrationTime : timeRef.current + delta;
|
|
1044
605
|
let nextTime;
|
|
1045
606
|
if (looping && duration2 > 0 && raw >= duration2) {
|
|
1046
607
|
nextTime = raw % duration2;
|
|
@@ -1048,6 +609,9 @@ function usePlaybackClock({
|
|
|
1048
609
|
} else {
|
|
1049
610
|
nextTime = Math.min(raw, duration2);
|
|
1050
611
|
}
|
|
612
|
+
const target = ranges.find((range) => nextTime >= range.start && nextTime < range.end) ?? ranges.at(-1);
|
|
613
|
+
const waitingForVisual = Boolean(target && visualReadyRef && visualReadyRef.current !== sceneReadinessKey(target.scene) && !posterBridgeKeysRef?.current.has(sceneReadinessKey(target.scene)));
|
|
614
|
+
if (waitingForVisual && target) nextTime = target.start;
|
|
1051
615
|
if (nextTime !== timeRef.current) {
|
|
1052
616
|
timeRef.current = nextTime;
|
|
1053
617
|
setCurrentTime(nextTime);
|
|
@@ -1065,19 +629,20 @@ function usePlaybackClock({
|
|
|
1065
629
|
if (remaining <= 0 && !looping) audio.pause();
|
|
1066
630
|
}
|
|
1067
631
|
const notify = callbacksRef.current.onSceneChange;
|
|
1068
|
-
if (notify) {
|
|
632
|
+
if (notify && !waitingForVisual) {
|
|
1069
633
|
if (wrapped) sceneIndexRef.current = -1;
|
|
1070
|
-
const
|
|
1071
|
-
const index =
|
|
1072
|
-
const resolved = index === -1 && nextTime >= duration2 ?
|
|
634
|
+
const ranges2 = resolveVideoTimeline(config);
|
|
635
|
+
const index = ranges2.findIndex((range) => nextTime >= range.start && nextTime < range.end);
|
|
636
|
+
const resolved = index === -1 && nextTime >= duration2 ? ranges2.length - 1 : index;
|
|
1073
637
|
if (resolved !== -1 && resolved !== sceneIndexRef.current) {
|
|
1074
638
|
sceneIndexRef.current = resolved;
|
|
1075
|
-
notify(
|
|
639
|
+
notify(ranges2[resolved].scene, resolved);
|
|
1076
640
|
}
|
|
1077
641
|
}
|
|
1078
642
|
}
|
|
1079
643
|
const duration = current.config ? getVideoDuration(current.config) : 0;
|
|
1080
|
-
|
|
644
|
+
const active = current.config ? resolveVideoTimeline(current.config).find((range) => timeRef.current >= range.start && timeRef.current < range.end) : void 0;
|
|
645
|
+
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
646
|
if (!settled || looping || timeRef.current < duration) frame = requestAnimationFrame(tick);
|
|
1082
647
|
else setIsPlaying(false);
|
|
1083
648
|
};
|
|
@@ -1120,7 +685,10 @@ function VideoPlayerRuntime({
|
|
|
1120
685
|
onComplete,
|
|
1121
686
|
onError,
|
|
1122
687
|
onSceneChange,
|
|
688
|
+
narrationReady,
|
|
689
|
+
narrationTime,
|
|
1123
690
|
onFramePresented,
|
|
691
|
+
onMediaFramePresented,
|
|
1124
692
|
onStallChange,
|
|
1125
693
|
onStateChange
|
|
1126
694
|
}) {
|
|
@@ -1146,6 +714,7 @@ function VideoPlayerRuntime({
|
|
|
1146
714
|
const [state, setState] = useState(() => video ? savedVideoState(video) : createVideoState());
|
|
1147
715
|
const [currentTime, setCurrentTime] = useState(0);
|
|
1148
716
|
const [activeStream, setActiveStream] = useState(stream);
|
|
717
|
+
const [activeSavedVideo, setActiveSavedVideo] = useState(video);
|
|
1149
718
|
const [replacementPending, setReplacementPending] = useState(false);
|
|
1150
719
|
const [startRequested, setStartRequested] = useState(autoStartGeneration);
|
|
1151
720
|
const [introPlaying, setIntroPlaying] = useState(autoStartGeneration);
|
|
@@ -1156,13 +725,35 @@ function VideoPlayerRuntime({
|
|
|
1156
725
|
const timeRef = useRef(currentTime);
|
|
1157
726
|
const audioRef = useRef(null);
|
|
1158
727
|
const introStartedAtRef = useRef(autoStartGeneration ? performance.now() : null);
|
|
1159
|
-
const callbacksRef = useRef({ onComplete, onPlaybackEnd, onError, onSceneChange, onFramePresented, onStallChange, onStateChange });
|
|
728
|
+
const callbacksRef = useRef({ onComplete, onPlaybackEnd, onError, onSceneChange, narrationReady, narrationTime, onFramePresented, onMediaFramePresented, onStallChange, onStateChange });
|
|
1160
729
|
const loopRef = useRef(loop);
|
|
1161
730
|
const sceneIndexRef = useRef(-1);
|
|
731
|
+
const mediaFrameReportedRef = useRef(false);
|
|
732
|
+
const posterBridgeKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
733
|
+
const visualReadyRef = useRef(void 0);
|
|
734
|
+
const reportVisualReady = useMemo(() => (key, error, actualVideoFrame = false, posterBridge = false) => {
|
|
735
|
+
if (posterBridge) {
|
|
736
|
+
posterBridgeKeysRef.current.add(key);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (error) {
|
|
740
|
+
setIsPlaying(false);
|
|
741
|
+
callbacksRef.current.onError?.(error, stateRef.current);
|
|
742
|
+
} else {
|
|
743
|
+
visualReadyRef.current = key;
|
|
744
|
+
if (actualVideoFrame && !mediaFrameReportedRef.current) {
|
|
745
|
+
mediaFrameReportedRef.current = true;
|
|
746
|
+
try {
|
|
747
|
+
void Promise.resolve(callbacksRef.current.onMediaFramePresented?.()).catch(() => void 0);
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}, []);
|
|
1162
753
|
const playbackEndedRef = useRef(false);
|
|
1163
754
|
stateRef.current = state;
|
|
1164
755
|
timeRef.current = currentTime;
|
|
1165
|
-
callbacksRef.current = { onComplete, onPlaybackEnd, onError, onSceneChange, onFramePresented, onStallChange, onStateChange };
|
|
756
|
+
callbacksRef.current = { onComplete, onPlaybackEnd, onError, onSceneChange, narrationReady, narrationTime, onFramePresented, onMediaFramePresented, onStallChange, onStateChange };
|
|
1166
757
|
loopRef.current = loop;
|
|
1167
758
|
const reportFramePresented = useMemo(() => {
|
|
1168
759
|
let reported = false;
|
|
@@ -1191,9 +782,13 @@ function VideoPlayerRuntime({
|
|
|
1191
782
|
output.default(audio, context);
|
|
1192
783
|
}).catch(() => context.close());
|
|
1193
784
|
};
|
|
1194
|
-
if (stream !== activeStream) {
|
|
785
|
+
if (stream !== activeStream || video !== activeSavedVideo) {
|
|
1195
786
|
const autoStartReplacement = Boolean(playbackMode && stream && shouldAutoPlay && !reducedMotion);
|
|
1196
787
|
setActiveStream(stream);
|
|
788
|
+
setActiveSavedVideo(video);
|
|
789
|
+
mediaFrameReportedRef.current = false;
|
|
790
|
+
posterBridgeKeysRef.current.clear();
|
|
791
|
+
sceneIndexRef.current = -1;
|
|
1197
792
|
setReplacementPending(stream != null);
|
|
1198
793
|
setState(video ? savedVideoState(video) : createVideoState());
|
|
1199
794
|
setCurrentTime(0);
|
|
@@ -1310,6 +905,8 @@ function VideoPlayerRuntime({
|
|
|
1310
905
|
audioRef,
|
|
1311
906
|
loopRef,
|
|
1312
907
|
sceneIndexRef,
|
|
908
|
+
visualReadyRef,
|
|
909
|
+
posterBridgeKeysRef,
|
|
1313
910
|
callbacksRef,
|
|
1314
911
|
setCurrentTime,
|
|
1315
912
|
setIsPlaying
|
|
@@ -1522,7 +1119,7 @@ function VideoPlayerRuntime({
|
|
|
1522
1119
|
onStart: armPlayback
|
|
1523
1120
|
}
|
|
1524
1121
|
),
|
|
1525
|
-
!generationCoverVisible && config?.scenes.length ? /* @__PURE__ */ jsx2(
|
|
1122
|
+
!generationCoverVisible && config?.scenes.length ? /* @__PURE__ */ jsx2(MountedReadinessContext.Provider, { value: reportVisualReady, children: /* @__PURE__ */ jsx2(
|
|
1526
1123
|
VideoFrame,
|
|
1527
1124
|
{
|
|
1528
1125
|
kit,
|
|
@@ -1542,7 +1139,7 @@ function VideoPlayerRuntime({
|
|
|
1542
1139
|
transformOrigin: "top left"
|
|
1543
1140
|
}
|
|
1544
1141
|
}
|
|
1545
|
-
) : null,
|
|
1142
|
+
) }) : null,
|
|
1546
1143
|
/* @__PURE__ */ jsx2(
|
|
1547
1144
|
StartPosterButton,
|
|
1548
1145
|
{
|
|
@@ -1748,17 +1345,55 @@ function createSceneTimeline(options) {
|
|
|
1748
1345
|
};
|
|
1749
1346
|
}
|
|
1750
1347
|
|
|
1348
|
+
// src/player/scene-readiness.ts
|
|
1349
|
+
var READY_AHEAD_SECONDS = 8;
|
|
1350
|
+
function canStartPreparedSequence(scenes, complete) {
|
|
1351
|
+
let seconds = 0;
|
|
1352
|
+
let count = 0;
|
|
1353
|
+
for (const scene of scenes) {
|
|
1354
|
+
if (!scene) break;
|
|
1355
|
+
count += 1;
|
|
1356
|
+
seconds += scene.timing.fixedDuration ?? 0;
|
|
1357
|
+
if (seconds >= READY_AHEAD_SECONDS) return true;
|
|
1358
|
+
}
|
|
1359
|
+
return complete && count > 0 && count === scenes.length;
|
|
1360
|
+
}
|
|
1361
|
+
function preparedSceneDuration(scene, spokenSeconds, metadata) {
|
|
1362
|
+
const timing = metadata?.timing;
|
|
1363
|
+
const authored = (timing?.revealSeconds ?? 0) + (timing?.holdSeconds ?? 0) + (timing?.exitSeconds ?? 0);
|
|
1364
|
+
const readable = Math.max(getSceneDurationBounds(scene, metadata).readable, authored);
|
|
1365
|
+
return Number.isFinite(spokenSeconds) && spokenSeconds > 0 ? Math.max(readable, spokenSeconds + 0.8) : Math.max(readable, getSceneDuration(scene, metadata));
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1751
1368
|
// src/player/use-narration.ts
|
|
1752
1369
|
import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
|
|
1753
1370
|
function useNarration(options) {
|
|
1754
1371
|
const [speaking, setSpeaking] = useState2(false);
|
|
1755
1372
|
const optionsRef = useRef2(options);
|
|
1756
1373
|
optionsRef.current = options;
|
|
1374
|
+
const groupRef = useRef2(void 0);
|
|
1375
|
+
const clockRef = useRef2(void 0);
|
|
1376
|
+
const clockSceneRef = useRef2(void 0);
|
|
1377
|
+
const readyRef = useRef2(true);
|
|
1378
|
+
const getTime = useCallback((scene) => {
|
|
1379
|
+
const group = groupRef.current;
|
|
1380
|
+
if (optionsRef.current.enabled === false || (group ? scene.narrationGroup?.id !== group.id : scene.id !== clockSceneRef.current)) return void 0;
|
|
1381
|
+
if (!optionsRef.current.voice.getCurrentTime) return void 0;
|
|
1382
|
+
const time = currentRef.current ? optionsRef.current.voice.getCurrentTime() : void 0;
|
|
1383
|
+
if (currentRef.current && readyRef.current && time === void 0) return void 0;
|
|
1384
|
+
if (time !== void 0 && Number.isFinite(time)) clockRef.current = Math.min(group?.totalSeconds ?? Infinity, Math.max(group?.offsetSeconds ?? 0, time));
|
|
1385
|
+
return clockRef.current;
|
|
1386
|
+
}, []);
|
|
1387
|
+
const isReady = useCallback(() => optionsRef.current.enabled === false || readyRef.current || clockRef.current !== void 0 && clockRef.current >= (groupRef.current?.offsetSeconds ?? 0) + 0.04, []);
|
|
1757
1388
|
const currentRef = useRef2(void 0);
|
|
1758
1389
|
const spokenIndexRef = useRef2(void 0);
|
|
1759
1390
|
const stop = useCallback(() => {
|
|
1391
|
+
readyRef.current = true;
|
|
1760
1392
|
currentRef.current?.abort();
|
|
1761
1393
|
currentRef.current = void 0;
|
|
1394
|
+
groupRef.current = void 0;
|
|
1395
|
+
clockRef.current = void 0;
|
|
1396
|
+
clockSceneRef.current = void 0;
|
|
1762
1397
|
setSpeaking(false);
|
|
1763
1398
|
}, []);
|
|
1764
1399
|
const interrupt = useCallback(() => {
|
|
@@ -1770,37 +1405,103 @@ function useNarration(options) {
|
|
|
1770
1405
|
const { voice, enabled = true } = optionsRef.current;
|
|
1771
1406
|
if (!enabled) return;
|
|
1772
1407
|
if (spokenIndexRef.current === index) return;
|
|
1408
|
+
const group = scene.narrationGroup;
|
|
1409
|
+
if (group && voice.supportsOffsets !== true) {
|
|
1410
|
+
stop();
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (group && currentRef.current && groupRef.current?.id === group.id && groupRef.current.text === group.text && spokenIndexRef.current === index - 1) {
|
|
1414
|
+
spokenIndexRef.current = index;
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1773
1417
|
stop();
|
|
1774
1418
|
spokenIndexRef.current = index;
|
|
1775
|
-
|
|
1776
|
-
|
|
1419
|
+
groupRef.current = group;
|
|
1420
|
+
clockRef.current = group?.offsetSeconds ?? 0;
|
|
1421
|
+
clockSceneRef.current = scene.id;
|
|
1422
|
+
const line = group?.text ?? scene.narration?.trim();
|
|
1423
|
+
if (!line) {
|
|
1424
|
+
clockRef.current = void 0;
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1777
1427
|
const controller = new AbortController();
|
|
1778
1428
|
currentRef.current = controller;
|
|
1429
|
+
readyRef.current = !group && !voice.getCurrentTime;
|
|
1779
1430
|
setSpeaking(true);
|
|
1780
1431
|
void (async () => {
|
|
1781
1432
|
try {
|
|
1782
1433
|
let started = false;
|
|
1783
1434
|
await voice.speak(line, {
|
|
1784
1435
|
signal: controller.signal,
|
|
1436
|
+
...group ? { offsetSeconds: group.offsetSeconds } : {},
|
|
1785
1437
|
onStart: (source) => {
|
|
1786
|
-
if (started || controller.signal.aborted || currentRef.current !== controller || spokenIndexRef.current !== index || optionsRef.current.enabled === false) return;
|
|
1438
|
+
if (started || controller.signal.aborted || currentRef.current !== controller || !group && spokenIndexRef.current !== index || optionsRef.current.enabled === false) return;
|
|
1787
1439
|
started = true;
|
|
1440
|
+
readyRef.current = true;
|
|
1788
1441
|
try {
|
|
1789
1442
|
void Promise.resolve(optionsRef.current.onSpeechStart?.(source)).catch(() => void 0);
|
|
1790
1443
|
} catch {
|
|
1791
1444
|
}
|
|
1792
1445
|
}
|
|
1793
1446
|
});
|
|
1447
|
+
if (currentRef.current === controller && !controller.signal.aborted) {
|
|
1448
|
+
clockRef.current = group && started ? group.totalSeconds : void 0;
|
|
1449
|
+
}
|
|
1794
1450
|
} catch {
|
|
1451
|
+
if (currentRef.current === controller) {
|
|
1452
|
+
groupRef.current = void 0;
|
|
1453
|
+
clockRef.current = void 0;
|
|
1454
|
+
}
|
|
1795
1455
|
} finally {
|
|
1796
1456
|
if (currentRef.current === controller) {
|
|
1797
1457
|
currentRef.current = void 0;
|
|
1458
|
+
readyRef.current = true;
|
|
1798
1459
|
setSpeaking(false);
|
|
1799
1460
|
}
|
|
1800
1461
|
}
|
|
1801
1462
|
})();
|
|
1802
1463
|
}, [stop]);
|
|
1803
|
-
return { onSceneChange, interrupt, speaking };
|
|
1464
|
+
return { onSceneChange, interrupt, speaking, isReady, getTime };
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// src/player/prepare-scene-media.ts
|
|
1468
|
+
async function prepareSceneMedia(variables, signal) {
|
|
1469
|
+
signal.throwIfAborted();
|
|
1470
|
+
const url = typeof variables.mediaUrl === "string" ? variables.mediaUrl.trim() : "";
|
|
1471
|
+
if (!url || variables.mediaType === "gradient") return "graphic";
|
|
1472
|
+
const video = variables.mediaType === "video" || /\.(mp4|webm|mov)(?:[?#]|$)/i.test(url);
|
|
1473
|
+
const poster = typeof variables.mediaPoster === "string" ? variables.mediaPoster.trim() : "";
|
|
1474
|
+
if (video && !poster) return "awaiting-video-frame";
|
|
1475
|
+
let posterFailed = false;
|
|
1476
|
+
await new Promise((resolve, reject) => {
|
|
1477
|
+
const image = new Image();
|
|
1478
|
+
let settled = false;
|
|
1479
|
+
const finish = (error) => {
|
|
1480
|
+
if (settled) return;
|
|
1481
|
+
settled = true;
|
|
1482
|
+
clearTimeout(timer);
|
|
1483
|
+
signal.removeEventListener("abort", abort);
|
|
1484
|
+
image.onload = null;
|
|
1485
|
+
image.onerror = null;
|
|
1486
|
+
if (error) {
|
|
1487
|
+
image.src = "";
|
|
1488
|
+
reject(error);
|
|
1489
|
+
} else resolve();
|
|
1490
|
+
};
|
|
1491
|
+
const abort = () => finish(signal.reason ?? new DOMException("Cancelled", "AbortError"));
|
|
1492
|
+
const timer = setTimeout(() => finish(new DOMException("Media preparation timed out", "TimeoutError")), 8e3);
|
|
1493
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1494
|
+
image.onload = () => {
|
|
1495
|
+
void (image.decode ? image.decode() : Promise.resolve()).then(() => finish(), () => finish(new Error("Could not prepare scene image")));
|
|
1496
|
+
};
|
|
1497
|
+
image.onerror = () => finish(new Error("Could not prepare scene image"));
|
|
1498
|
+
image.src = video ? poster : url;
|
|
1499
|
+
if (image.complete && image.naturalWidth > 0) image.onload(new Event("load"));
|
|
1500
|
+
}).catch((error) => {
|
|
1501
|
+
if (!video || signal.aborted) throw error;
|
|
1502
|
+
posterFailed = true;
|
|
1503
|
+
});
|
|
1504
|
+
return video ? posterFailed ? "awaiting-video-frame" : "poster-bridge" : "image";
|
|
1804
1505
|
}
|
|
1805
1506
|
|
|
1806
1507
|
// src/video-chat/voice.ts
|
|
@@ -1820,10 +1521,10 @@ async function measureSeconds(bytes) {
|
|
|
1820
1521
|
try {
|
|
1821
1522
|
sharedContext ??= new AudioContext();
|
|
1822
1523
|
const decoded = await sharedContext.decodeAudioData(bytes.slice(0));
|
|
1823
|
-
if (decoded.duration > 0) return decoded.duration;
|
|
1524
|
+
if (decoded.duration > 0) return { seconds: decoded.duration, measured: true };
|
|
1824
1525
|
} catch {
|
|
1825
1526
|
}
|
|
1826
|
-
return bytes.byteLength * 8 / FALLBACK_BITS_PER_SECOND;
|
|
1527
|
+
return { seconds: bytes.byteLength * 8 / FALLBACK_BITS_PER_SECOND, measured: false };
|
|
1827
1528
|
}
|
|
1828
1529
|
function createVideoChatVoice(options = {}) {
|
|
1829
1530
|
const endpoint = options.endpoint ?? "/api/video-chat";
|
|
@@ -1898,7 +1599,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
1898
1599
|
createdSrc = URL.createObjectURL(new Blob([bytes], {
|
|
1899
1600
|
type: response.headers.get("content-type") || "audio/mpeg"
|
|
1900
1601
|
}));
|
|
1901
|
-
return { source: "generated", src: createdSrc, seconds };
|
|
1602
|
+
return { source: "generated", src: createdSrc, ...seconds };
|
|
1902
1603
|
}, SPEECH_PREPARATION_TIMEOUT_MS, controller.signal);
|
|
1903
1604
|
}
|
|
1904
1605
|
} catch (cause) {
|
|
@@ -1944,9 +1645,11 @@ function createVideoChatVoice(options = {}) {
|
|
|
1944
1645
|
browserFinish?.();
|
|
1945
1646
|
};
|
|
1946
1647
|
return {
|
|
1648
|
+
supportsOffsets: true,
|
|
1649
|
+
getCurrentTime: () => sounding?.currentTime,
|
|
1947
1650
|
async prepare(text, preparation = {}) {
|
|
1948
1651
|
const line = await load(text, preparation.signal);
|
|
1949
|
-
return { seconds: line.seconds };
|
|
1652
|
+
return { seconds: line.seconds, ...line.source === "generated" && line.measured === true ? { supportsOffsets: true } : {} };
|
|
1950
1653
|
},
|
|
1951
1654
|
pause() {
|
|
1952
1655
|
held = true;
|
|
@@ -1955,7 +1658,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
1955
1658
|
},
|
|
1956
1659
|
resume() {
|
|
1957
1660
|
held = false;
|
|
1958
|
-
if (sounding
|
|
1661
|
+
if (sounding) void sounding.play().catch(() => playbackFailure?.());
|
|
1959
1662
|
if (!silent) globalThis.speechSynthesis?.resume();
|
|
1960
1663
|
},
|
|
1961
1664
|
setMuted(muted) {
|
|
@@ -1963,7 +1666,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
1963
1666
|
if (sounding) sounding.muted = muted;
|
|
1964
1667
|
if (muted) stopBrowser();
|
|
1965
1668
|
},
|
|
1966
|
-
async speak(text, { signal, onStart }) {
|
|
1669
|
+
async speak(text, { signal, onStart, offsetSeconds }) {
|
|
1967
1670
|
let started = false;
|
|
1968
1671
|
const notifyStart = (source) => {
|
|
1969
1672
|
if (started || disposed || signal.aborted || silent || held) return;
|
|
@@ -1975,17 +1678,21 @@ function createVideoChatVoice(options = {}) {
|
|
|
1975
1678
|
};
|
|
1976
1679
|
const line = await load(text, signal);
|
|
1977
1680
|
if (disposed || signal.aborted || silent) return;
|
|
1681
|
+
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
1682
|
if (line.source === "browser") {
|
|
1979
1683
|
const synthesis = globalThis.speechSynthesis;
|
|
1980
|
-
if (!synthesis || typeof SpeechSynthesisUtterance === "undefined")
|
|
1684
|
+
if (!synthesis || typeof SpeechSynthesisUtterance === "undefined") throw new Error("Browser voice is unavailable");
|
|
1981
1685
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
1982
1686
|
utterance.rate = 1;
|
|
1687
|
+
let unavailable = false;
|
|
1983
1688
|
await new Promise((resolve) => {
|
|
1984
1689
|
let finished = false;
|
|
1690
|
+
let onsetRemainingMs = 2e3;
|
|
1985
1691
|
const finish = () => {
|
|
1986
1692
|
if (finished) return;
|
|
1987
1693
|
finished = true;
|
|
1988
1694
|
clearWatchdog();
|
|
1695
|
+
clearInterval(onsetTimer);
|
|
1989
1696
|
signal.removeEventListener("abort", stop);
|
|
1990
1697
|
speechStops.delete(stop);
|
|
1991
1698
|
utterance.onstart = null;
|
|
@@ -1995,38 +1702,54 @@ function createVideoChatVoice(options = {}) {
|
|
|
1995
1702
|
resolve();
|
|
1996
1703
|
};
|
|
1997
1704
|
const stop = () => {
|
|
1998
|
-
synthesis.cancel();
|
|
1999
1705
|
finish();
|
|
1706
|
+
synthesis.cancel();
|
|
2000
1707
|
};
|
|
2001
1708
|
const clearWatchdog = watchSpeech(estimatedBrowserSeconds(text), stop);
|
|
2002
1709
|
speechStops.add(stop);
|
|
2003
1710
|
browserFinish = finish;
|
|
1711
|
+
const fail = () => {
|
|
1712
|
+
unavailable = true;
|
|
1713
|
+
stop();
|
|
1714
|
+
};
|
|
1715
|
+
const onsetTimer = setInterval(() => {
|
|
1716
|
+
if (held || finished) return;
|
|
1717
|
+
onsetRemainingMs -= 250;
|
|
1718
|
+
if (onsetRemainingMs <= 0) fail();
|
|
1719
|
+
}, 250);
|
|
2004
1720
|
utterance.onstart = () => {
|
|
2005
|
-
if (
|
|
1721
|
+
if (finished) return;
|
|
1722
|
+
clearInterval(onsetTimer);
|
|
1723
|
+
notifyStart("browser");
|
|
2006
1724
|
};
|
|
2007
1725
|
utterance.onend = finish;
|
|
2008
|
-
utterance.onerror =
|
|
1726
|
+
utterance.onerror = fail;
|
|
2009
1727
|
signal.addEventListener("abort", stop, { once: true });
|
|
2010
1728
|
try {
|
|
2011
1729
|
synthesis.speak(utterance);
|
|
2012
1730
|
if (held) synthesis.pause();
|
|
2013
1731
|
} catch {
|
|
2014
|
-
|
|
1732
|
+
fail();
|
|
2015
1733
|
}
|
|
2016
1734
|
});
|
|
1735
|
+
if (unavailable) throw new Error("Browser voice is unavailable");
|
|
2017
1736
|
return;
|
|
2018
1737
|
}
|
|
2019
1738
|
let playbackFailed = false;
|
|
2020
1739
|
try {
|
|
2021
1740
|
const element = new Audio(line.src);
|
|
2022
1741
|
element.muted = silent;
|
|
1742
|
+
if (offsetSeconds !== void 0) element.currentTime = offsetSeconds;
|
|
2023
1743
|
sounding = element;
|
|
2024
1744
|
await new Promise((resolve) => {
|
|
2025
1745
|
let finished = false;
|
|
1746
|
+
let onsetTimer;
|
|
1747
|
+
const initialTime = offsetSeconds ?? 0;
|
|
2026
1748
|
const finish = () => {
|
|
2027
1749
|
if (finished) return;
|
|
2028
1750
|
finished = true;
|
|
2029
1751
|
clearWatchdog();
|
|
1752
|
+
clearTimeout(onsetTimer);
|
|
2030
1753
|
signal.removeEventListener("abort", stop);
|
|
2031
1754
|
speechStops.delete(stop);
|
|
2032
1755
|
element.onplaying = null;
|
|
@@ -2049,9 +1772,13 @@ function createVideoChatVoice(options = {}) {
|
|
|
2049
1772
|
const clearWatchdog = watchSpeech(Math.max(line.seconds, estimatedBrowserSeconds(text)), fail);
|
|
2050
1773
|
speechStops.add(stop);
|
|
2051
1774
|
playbackFailure = fail;
|
|
2052
|
-
|
|
2053
|
-
|
|
1775
|
+
const observeClock = () => {
|
|
1776
|
+
clearTimeout(onsetTimer);
|
|
1777
|
+
if (finished || started) return;
|
|
1778
|
+
if (!held && element.currentTime >= initialTime + 0.04) notifyStart("generated");
|
|
1779
|
+
if (!started) onsetTimer = setTimeout(observeClock, 16);
|
|
2054
1780
|
};
|
|
1781
|
+
element.onplaying = observeClock;
|
|
2055
1782
|
element.onended = finish;
|
|
2056
1783
|
element.onerror = fail;
|
|
2057
1784
|
signal.addEventListener("abort", stop, { once: true });
|
|
@@ -2064,6 +1791,7 @@ function createVideoChatVoice(options = {}) {
|
|
|
2064
1791
|
} catch {
|
|
2065
1792
|
playbackFailed = true;
|
|
2066
1793
|
}
|
|
1794
|
+
if (playbackFailed && offsetSeconds !== void 0) throw new Error("Grouped narration playback failed");
|
|
2067
1795
|
if (playbackFailed && !disposed && !signal.aborted && !silent) {
|
|
2068
1796
|
notifyFallback();
|
|
2069
1797
|
URL.revokeObjectURL(line.src);
|
|
@@ -2324,7 +2052,11 @@ async function responseError(response) {
|
|
|
2324
2052
|
});
|
|
2325
2053
|
}
|
|
2326
2054
|
function pacedScene(scene, spokenSeconds, templates) {
|
|
2327
|
-
const held =
|
|
2055
|
+
const held = preparedSceneDuration(
|
|
2056
|
+
scene,
|
|
2057
|
+
spokenSeconds,
|
|
2058
|
+
templates?.getTemplateMetadata(scene.templateId) ?? getBuiltinTemplateMetadata(scene.templateId)
|
|
2059
|
+
);
|
|
2328
2060
|
const {
|
|
2329
2061
|
startTime: _startTime,
|
|
2330
2062
|
endTime: _endTime,
|
|
@@ -2334,21 +2066,6 @@ function pacedScene(scene, spokenSeconds, templates) {
|
|
|
2334
2066
|
} = scene.timing ?? {};
|
|
2335
2067
|
return { ...scene, timing: { ...timing, fixedDuration: held } };
|
|
2336
2068
|
}
|
|
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
2069
|
function useVideoChat(options = {}) {
|
|
2353
2070
|
return useVideoChatSession(options).chat;
|
|
2354
2071
|
}
|
|
@@ -2372,7 +2089,17 @@ function useVideoChatSession(options = {}) {
|
|
|
2372
2089
|
const unavailableVoiceLines = useRef3(/* @__PURE__ */ new Set());
|
|
2373
2090
|
const speechStartRef = useRef3(() => void 0);
|
|
2374
2091
|
const narration = useNarration({ onSpeechStart: (source) => speechStartRef.current(source), voice: {
|
|
2375
|
-
|
|
2092
|
+
supportsOffsets: voice.supportsOffsets,
|
|
2093
|
+
...voice.getCurrentTime ? { getCurrentTime: () => voice.getCurrentTime() } : {},
|
|
2094
|
+
speak: async (text, options2) => {
|
|
2095
|
+
if (unavailableVoiceLines.current.has(text)) return;
|
|
2096
|
+
try {
|
|
2097
|
+
await voice.speak(text, options2);
|
|
2098
|
+
} catch (error) {
|
|
2099
|
+
if (!options2.signal.aborted) voiceWarningRef.current("Voice playback is unavailable. Continuing with subtitles.");
|
|
2100
|
+
throw error;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2376
2103
|
} });
|
|
2377
2104
|
const narrationRef = useRef3(narration);
|
|
2378
2105
|
narrationRef.current = narration;
|
|
@@ -2511,9 +2238,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2511
2238
|
throw new VideoError("timeoutMs must be positive", { code: "invalid_option" });
|
|
2512
2239
|
}
|
|
2513
2240
|
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;
|
|
2241
|
+
const mode = "cinematic";
|
|
2517
2242
|
const orientation = currentOptions.orientation ?? "landscape";
|
|
2518
2243
|
const id = (currentOptions.createTurnId ?? defaultTurnId)();
|
|
2519
2244
|
const conversation = conversationFor(stateRef.current.turns);
|
|
@@ -2524,7 +2249,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2524
2249
|
prompt,
|
|
2525
2250
|
completed: false,
|
|
2526
2251
|
orientation,
|
|
2527
|
-
fixedOrientation:
|
|
2252
|
+
fixedOrientation: true,
|
|
2528
2253
|
suggestions: [],
|
|
2529
2254
|
...openingMedia ? { openingMedia } : {}
|
|
2530
2255
|
};
|
|
@@ -2547,11 +2272,23 @@ function useVideoChatSession(options = {}) {
|
|
|
2547
2272
|
const warn = (message) => {
|
|
2548
2273
|
if (isCurrent()) dispatch({ type: "warning", id, message });
|
|
2549
2274
|
};
|
|
2550
|
-
voiceWarningRef.current = (
|
|
2275
|
+
voiceWarningRef.current = (message = "Using browser voice for this response.") => warn(message);
|
|
2551
2276
|
const flush = () => {
|
|
2552
2277
|
if (!isCurrent()) return;
|
|
2553
2278
|
let available = appended;
|
|
2554
|
-
while (ready[available])
|
|
2279
|
+
while (ready[available]) {
|
|
2280
|
+
const group = ready[available].narrationGroup;
|
|
2281
|
+
if (!group) {
|
|
2282
|
+
available++;
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
let end = available;
|
|
2286
|
+
while (ready[end]?.narrationGroup?.id === group.id) end++;
|
|
2287
|
+
const last = ready[end - 1].narrationGroup;
|
|
2288
|
+
if (Math.abs(last.offsetSeconds + last.durationSeconds - group.totalSeconds) > 0.02) break;
|
|
2289
|
+
validateNarrationGroups(ready.slice(available, end));
|
|
2290
|
+
available = end;
|
|
2291
|
+
}
|
|
2555
2292
|
if (available === appended && timeline) {
|
|
2556
2293
|
if (planDone && !timelineCompleted) {
|
|
2557
2294
|
timelineCompleted = true;
|
|
@@ -2561,18 +2298,18 @@ function useVideoChatSession(options = {}) {
|
|
|
2561
2298
|
return;
|
|
2562
2299
|
}
|
|
2563
2300
|
if (!timeline) {
|
|
2564
|
-
if (!style || openingActive || heldRef.current || available === appended) return;
|
|
2301
|
+
if (!style || openingActive || heldRef.current || available === appended || !canStartPreparedSequence(ready.slice(0, available), planDone)) return;
|
|
2565
2302
|
timeline = createSceneTimeline({ style, orientation });
|
|
2566
2303
|
timelineRef.current = timeline;
|
|
2567
2304
|
openingController.abort(new DOMException("Opening replaced by response", "AbortError"));
|
|
2568
2305
|
if (openingRef.current === openingController) openingRef.current = void 0;
|
|
2569
2306
|
dispatch({ type: "player", id, stream: timeline.stream });
|
|
2570
2307
|
}
|
|
2571
|
-
while (
|
|
2308
|
+
while (appended < available) timeline.add(ready[appended++]);
|
|
2572
2309
|
dispatch({
|
|
2573
2310
|
type: "partial",
|
|
2574
2311
|
id,
|
|
2575
|
-
video: { schemaVersion:
|
|
2312
|
+
video: { schemaVersion: VIDEO_SCHEMA_VERSION, orientation, scenes: ready.slice(0, appended), style }
|
|
2576
2313
|
});
|
|
2577
2314
|
if (planDone && !timelineCompleted) {
|
|
2578
2315
|
timelineCompleted = true;
|
|
@@ -2634,7 +2371,6 @@ function useVideoChatSession(options = {}) {
|
|
|
2634
2371
|
mode,
|
|
2635
2372
|
orientation,
|
|
2636
2373
|
conversation,
|
|
2637
|
-
...currentOptions.brand ? { brand: currentOptions.brand } : {},
|
|
2638
2374
|
...currentOptions.style ? { style: currentOptions.style } : {}
|
|
2639
2375
|
})
|
|
2640
2376
|
}, controller.signal);
|
|
@@ -2649,7 +2385,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2649
2385
|
let terminalError;
|
|
2650
2386
|
try {
|
|
2651
2387
|
for await (const event of decodeVideoSse(response.body)) {
|
|
2652
|
-
if (!isCurrent() || currentAttempt !== attempt) return { video: { schemaVersion:
|
|
2388
|
+
if (!isCurrent() || currentAttempt !== attempt) return { video: { schemaVersion: VIDEO_SCHEMA_VERSION, orientation, scenes: [], style }, lines: [] };
|
|
2653
2389
|
if (event.type === "response.start") style = event.data.style;
|
|
2654
2390
|
if (event.type === "response.warning" || event.type === "response.error" && !event.data.terminal) {
|
|
2655
2391
|
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 +2419,12 @@ function useVideoChatSession(options = {}) {
|
|
|
2683
2419
|
const position = event.data.position;
|
|
2684
2420
|
const plannedScene = event.data.scene;
|
|
2685
2421
|
planned[position] = plannedScene;
|
|
2686
|
-
received[position] =
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2422
|
+
received[position] = plannedScene;
|
|
2423
|
+
const rendererReady = currentOptions.templates?.getTemplate(plannedScene.templateId) ? Promise.resolve() : Promise.resolve(preloadBuiltinTemplate(plannedScene.templateId));
|
|
2424
|
+
const visualPreparation = Promise.all([rendererReady, prepareSceneMedia(plannedScene.variables, controller.signal)]).then(() => void 0, () => {
|
|
2425
|
+
throw new VideoError("Scene could not prepare its visual", { code: "media_not_ready" });
|
|
2426
|
+
});
|
|
2427
|
+
void visualPreparation.catch(() => void 0);
|
|
2690
2428
|
warmSceneMedia(plannedScene.variables);
|
|
2691
2429
|
const narrated = narrating.then(async () => {
|
|
2692
2430
|
const supplied = plannedScene.narration?.trim();
|
|
@@ -2713,18 +2451,25 @@ function useVideoChatSession(options = {}) {
|
|
|
2713
2451
|
narrating = narrated.catch(() => "");
|
|
2714
2452
|
pending.push(narrated.then(async (line) => {
|
|
2715
2453
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2716
|
-
const visual =
|
|
2454
|
+
const visual = plannedScene;
|
|
2717
2455
|
const withNarration = line ? { ...visual, narration: line } : visual;
|
|
2718
|
-
const spoken = line ? await prepareSpeech(line, controller.signal).catch((cause) => {
|
|
2456
|
+
const spoken = line ? await prepareSpeech(plannedScene.narrationGroup?.text ?? line, controller.signal).catch((cause) => {
|
|
2719
2457
|
if (controller.signal.aborted) throw cause;
|
|
2720
2458
|
warn("Some narration is unavailable; the response will continue.");
|
|
2721
2459
|
return void 0;
|
|
2722
2460
|
}) : void 0;
|
|
2723
2461
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2724
|
-
|
|
2462
|
+
await visualPreparation;
|
|
2463
|
+
const group = plannedScene.narrationGroup;
|
|
2464
|
+
if (group && (spoken?.supportsOffsets !== true || voiceRef.current.supportsOffsets !== true || Math.abs(spoken.seconds - group.totalSeconds) > 0.1)) throw new VideoError("Narration group requires matching measured audio with offset support", { code: "narration_group_invalid" });
|
|
2465
|
+
ready[position] = group ? withNarration : pacedScene(withNarration, spoken?.seconds, currentOptions.templates);
|
|
2725
2466
|
flush();
|
|
2726
|
-
}).catch(() => {
|
|
2467
|
+
}).catch((cause) => {
|
|
2727
2468
|
if (!isCurrent() || currentAttempt !== attempt) return;
|
|
2469
|
+
if (cause instanceof VideoError && ["media_not_ready", "narration_group_invalid"].includes(cause.code)) {
|
|
2470
|
+
terminalError = cause;
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2728
2473
|
ready[position] = { ...received[position], timing: { fixedDuration: 5 } };
|
|
2729
2474
|
warn("Some parts were simplified so the response could continue.");
|
|
2730
2475
|
flush();
|
|
@@ -2735,6 +2480,8 @@ function useVideoChatSession(options = {}) {
|
|
|
2735
2480
|
terminalError = errorFrom(cause);
|
|
2736
2481
|
}
|
|
2737
2482
|
await Promise.all(pending);
|
|
2483
|
+
if (terminalError && ["media_not_ready", "narration_group_invalid"].includes(terminalError.code)) throw terminalError;
|
|
2484
|
+
validateNarrationGroups(ready.filter((scene) => Boolean(scene)));
|
|
2738
2485
|
if (terminalError && ready.some(Boolean) && style) {
|
|
2739
2486
|
warn("The response was interrupted; completed scenes are still available.");
|
|
2740
2487
|
} else if (terminalError) throw terminalError;
|
|
@@ -2743,7 +2490,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2743
2490
|
}
|
|
2744
2491
|
return {
|
|
2745
2492
|
video: {
|
|
2746
|
-
schemaVersion:
|
|
2493
|
+
schemaVersion: VIDEO_SCHEMA_VERSION,
|
|
2747
2494
|
orientation,
|
|
2748
2495
|
scenes: ready.filter((entry) => entry != null),
|
|
2749
2496
|
style
|
|
@@ -2756,7 +2503,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2756
2503
|
try {
|
|
2757
2504
|
response = await untilAborted(runAttempt(attempt), controller.signal);
|
|
2758
2505
|
} catch (cause) {
|
|
2759
|
-
if (controller.signal.aborted || timeline || spokenHook) throw cause;
|
|
2506
|
+
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
2507
|
attempt += 1;
|
|
2761
2508
|
ready = [];
|
|
2762
2509
|
received = [];
|
|
@@ -2792,9 +2539,9 @@ function useVideoChatSession(options = {}) {
|
|
|
2792
2539
|
return void 0;
|
|
2793
2540
|
}
|
|
2794
2541
|
terminal = true;
|
|
2795
|
-
const recovered = received.flatMap((scene, index) => scene ? [ready[index] ?? { ...scene, timing: { fixedDuration: 5 } }] : []);
|
|
2542
|
+
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
2543
|
if (recovered.length > 0) {
|
|
2797
|
-
style ??= {
|
|
2544
|
+
style ??= { density: "normal", motion: "normal", defaultBackgroundEffect: "static", defaultTextArchetype: "subtle", defaultTransition: "crossfade" };
|
|
2798
2545
|
openingController.abort(new DOMException("Continuing completed response", "AbortError"));
|
|
2799
2546
|
if (!timeline) {
|
|
2800
2547
|
timeline = createSceneTimeline({ style, orientation });
|
|
@@ -2803,7 +2550,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2803
2550
|
for (const scene of recovered.slice(appended)) timeline.add(scene);
|
|
2804
2551
|
timeline.complete();
|
|
2805
2552
|
if (timelineRef.current === timeline) timelineRef.current = void 0;
|
|
2806
|
-
const video = { schemaVersion:
|
|
2553
|
+
const video = { schemaVersion: VIDEO_SCHEMA_VERSION, orientation, style, scenes: recovered };
|
|
2807
2554
|
dispatch({ type: "warning", id, message: "The response was interrupted; completed scenes are still available." });
|
|
2808
2555
|
dispatch({ type: "complete", id, video, suggestions: [] });
|
|
2809
2556
|
return video;
|
|
@@ -2831,9 +2578,18 @@ function useVideoChatSession(options = {}) {
|
|
|
2831
2578
|
dispatch({ type: "resume" });
|
|
2832
2579
|
flushRef.current?.();
|
|
2833
2580
|
}, []);
|
|
2581
|
+
const prepareSavedGroups = useCallback2(async (video) => {
|
|
2582
|
+
validateNarrationGroups(video.scenes);
|
|
2583
|
+
const groups = new Map(video.scenes.flatMap((scene) => scene.narrationGroup ? [[scene.narrationGroup.id, scene.narrationGroup]] : []));
|
|
2584
|
+
for (const group of groups.values()) {
|
|
2585
|
+
const prepared = await withDeadline((signal) => voiceRef.current.prepare(group.text, { signal }), 3e3);
|
|
2586
|
+
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" });
|
|
2587
|
+
}
|
|
2588
|
+
}, []);
|
|
2834
2589
|
const replay = useCallback2(() => {
|
|
2835
2590
|
const turn = stateRef.current.turns.find((entry) => entry.id === stateRef.current.shownTurnId);
|
|
2836
2591
|
if (!turn?.completed || !turn.video) return;
|
|
2592
|
+
runRef.current += 1;
|
|
2837
2593
|
endTiming();
|
|
2838
2594
|
if (inFlightRef.current) {
|
|
2839
2595
|
runRef.current += 1;
|
|
@@ -2847,11 +2603,20 @@ function useVideoChatSession(options = {}) {
|
|
|
2847
2603
|
narrationRef.current.interrupt();
|
|
2848
2604
|
heldRef.current = false;
|
|
2849
2605
|
voiceRef.current.resume();
|
|
2850
|
-
|
|
2851
|
-
|
|
2606
|
+
if (turn.video.scenes.some((scene) => scene.narrationGroup)) {
|
|
2607
|
+
const selection = ++runRef.current;
|
|
2608
|
+
dispatch({ type: "pause" });
|
|
2609
|
+
void prepareSavedGroups(turn.video).then(() => {
|
|
2610
|
+
if (mountedRef.current && runRef.current === selection) dispatch({ type: "replay" });
|
|
2611
|
+
}).catch((cause) => {
|
|
2612
|
+
if (mountedRef.current && runRef.current === selection) dispatch({ type: "error", id: turn.id, error: errorFrom(cause) });
|
|
2613
|
+
});
|
|
2614
|
+
} else dispatch({ type: "replay" });
|
|
2615
|
+
}, [endTiming, prepareSavedGroups]);
|
|
2852
2616
|
const selectTurn = useCallback2((id) => {
|
|
2853
2617
|
const turn = stateRef.current.turns.find((entry) => entry.id === id);
|
|
2854
2618
|
if (!turn?.completed || !turn.video) return;
|
|
2619
|
+
runRef.current += 1;
|
|
2855
2620
|
endTiming();
|
|
2856
2621
|
if (inFlightRef.current) {
|
|
2857
2622
|
runRef.current += 1;
|
|
@@ -2865,8 +2630,16 @@ function useVideoChatSession(options = {}) {
|
|
|
2865
2630
|
narrationRef.current.interrupt();
|
|
2866
2631
|
heldRef.current = false;
|
|
2867
2632
|
voiceRef.current.resume();
|
|
2868
|
-
|
|
2869
|
-
|
|
2633
|
+
if (turn.video.scenes.some((scene) => scene.narrationGroup)) {
|
|
2634
|
+
const selection = ++runRef.current;
|
|
2635
|
+
dispatch({ type: "pause" });
|
|
2636
|
+
void prepareSavedGroups(turn.video).then(() => {
|
|
2637
|
+
if (mountedRef.current && runRef.current === selection) dispatch({ type: "select", id });
|
|
2638
|
+
}).catch((cause) => {
|
|
2639
|
+
if (mountedRef.current && runRef.current === selection) dispatch({ type: "error", id, error: errorFrom(cause) });
|
|
2640
|
+
});
|
|
2641
|
+
} else dispatch({ type: "select", id });
|
|
2642
|
+
}, [endTiming, prepareSavedGroups]);
|
|
2870
2643
|
const reset = useCallback2(() => {
|
|
2871
2644
|
cancel("Session reset");
|
|
2872
2645
|
heldRef.current = false;
|
|
@@ -2882,7 +2655,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2882
2655
|
const setMuted = useCallback2((muted) => dispatch({ type: "mute", value: muted }), []);
|
|
2883
2656
|
const currentTurn = state.turns.at(-1);
|
|
2884
2657
|
const shownTurn = state.turns.find((turn) => turn.id === state.shownTurnId) ?? currentTurn;
|
|
2885
|
-
const availableModes = state.capabilities?.modes ?? ["
|
|
2658
|
+
const availableModes = state.capabilities?.modes ?? ["cinematic"];
|
|
2886
2659
|
const suggestions = shownTurn?.suggestions ?? [];
|
|
2887
2660
|
const fullTranscript = shownTurn ? transcriptFor(shownTurn) : [];
|
|
2888
2661
|
const transcript = shownTurn && shownTurn === currentTurn && state.playback?.kind !== "video" ? fullTranscript.slice(0, (shownTurn.opening ? 1 : 0) + state.spokenUpTo + 1) : fullTranscript;
|
|
@@ -2893,6 +2666,8 @@ function useVideoChatSession(options = {}) {
|
|
|
2893
2666
|
autoPlay: true,
|
|
2894
2667
|
paused: state.status === "paused",
|
|
2895
2668
|
controls: false,
|
|
2669
|
+
narrationReady: narration.isReady,
|
|
2670
|
+
narrationTime: narration.getTime,
|
|
2896
2671
|
orientation: shownTurn?.fixedOrientation ? shownTurn.orientation : "auto",
|
|
2897
2672
|
onFramePresented: () => {
|
|
2898
2673
|
const timing = firstFrameRef.current;
|
|
@@ -2902,8 +2677,22 @@ function useVideoChatSession(options = {}) {
|
|
|
2902
2677
|
observe(() => optionsRef.current.onFirstFrame?.({ turnId: timing.turnId, mode: timing.mode, timeToFirstFrameMs: elapsedMs }));
|
|
2903
2678
|
reportMetric({ type: "first-frame", turnId: timing.turnId, mode: timing.mode, elapsedMs });
|
|
2904
2679
|
},
|
|
2680
|
+
onMediaFramePresented: () => {
|
|
2681
|
+
const timing = firstFrameRef.current;
|
|
2682
|
+
if (stateRef.current.playerKey !== playbackKey || state.playback?.kind !== "stream" || !timing?.active || timing.mediaReported) return;
|
|
2683
|
+
timing.mediaReported = true;
|
|
2684
|
+
reportMetric({
|
|
2685
|
+
type: "first-media-frame",
|
|
2686
|
+
turnId: timing.turnId,
|
|
2687
|
+
mode: timing.mode,
|
|
2688
|
+
elapsedMs: Math.max(0, Math.round(monotonicNow() - timing.startedAt))
|
|
2689
|
+
});
|
|
2690
|
+
},
|
|
2905
2691
|
onStallChange: (stalled) => {
|
|
2906
|
-
if (stateRef.current.playerKey !== playbackKey
|
|
2692
|
+
if (stateRef.current.playerKey !== playbackKey) return;
|
|
2693
|
+
if (stalled) voiceRef.current.pause();
|
|
2694
|
+
else if (!heldRef.current) voiceRef.current.resume();
|
|
2695
|
+
if (state.playback?.kind !== "stream") return;
|
|
2907
2696
|
const timing = firstFrameRef.current;
|
|
2908
2697
|
if (!timing?.active || !timing.reported) return;
|
|
2909
2698
|
if (!stalled) finishStall();
|
|
@@ -2921,6 +2710,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2921
2710
|
},
|
|
2922
2711
|
onError: (cause) => {
|
|
2923
2712
|
if (stateRef.current.playerKey !== playbackKey) return;
|
|
2713
|
+
narrationRef.current.interrupt();
|
|
2924
2714
|
const id = stateRef.current.turns.at(-1)?.id;
|
|
2925
2715
|
if (id) dispatch({ type: "error", id, error: errorFrom(cause) });
|
|
2926
2716
|
}
|
|
@@ -2956,68 +2746,7 @@ function useVideoChatSession(options = {}) {
|
|
|
2956
2746
|
}
|
|
2957
2747
|
|
|
2958
2748
|
// 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;
|
|
2749
|
+
import { useCallback as useCallback6, useEffect as useEffect9, useId, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef8, useState as useState7 } from "react";
|
|
3021
2750
|
|
|
3022
2751
|
// src/video-chat/icons.tsx
|
|
3023
2752
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -3129,7 +2858,7 @@ function useFocusTrap(active, surface) {
|
|
|
3129
2858
|
// src/video-chat/suggestion-cards.tsx
|
|
3130
2859
|
import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef4, useState as useState3 } from "react";
|
|
3131
2860
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3132
|
-
function Frame({ media, poster, playing }) {
|
|
2861
|
+
function Frame({ media, poster, playing, onReady, onError, revealWhenReady = false }) {
|
|
3133
2862
|
const video = useRef4(null);
|
|
3134
2863
|
const [playingUrl, setPlayingUrl] = useState3(null);
|
|
3135
2864
|
useEffect6(() => {
|
|
@@ -3139,7 +2868,12 @@ function Frame({ media, poster, playing }) {
|
|
|
3139
2868
|
else element.pause();
|
|
3140
2869
|
}, [playing]);
|
|
3141
2870
|
if (!media) return null;
|
|
3142
|
-
|
|
2871
|
+
const ready = () => {
|
|
2872
|
+
setPlayingUrl(media.url);
|
|
2873
|
+
onReady?.();
|
|
2874
|
+
};
|
|
2875
|
+
const appearance = revealWhenReady ? { opacity: playingUrl === media.url ? 1 : 0, transition: "opacity 200ms ease" } : void 0;
|
|
2876
|
+
if (media.type === "image") return /* @__PURE__ */ jsx4("img", { className: "frame-media", src: media.url, alt: "", style: appearance, onLoad: ready, onError });
|
|
3143
2877
|
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
3144
2878
|
/* @__PURE__ */ jsx4(
|
|
3145
2879
|
"video",
|
|
@@ -3148,8 +2882,12 @@ function Frame({ media, poster, playing }) {
|
|
|
3148
2882
|
className: "frame-media",
|
|
3149
2883
|
src: media.url,
|
|
3150
2884
|
poster: poster ? media.posterUrl : void 0,
|
|
3151
|
-
|
|
3152
|
-
|
|
2885
|
+
style: appearance,
|
|
2886
|
+
onPlaying: ready,
|
|
2887
|
+
onError: () => {
|
|
2888
|
+
setPlayingUrl(null);
|
|
2889
|
+
onError?.();
|
|
2890
|
+
},
|
|
3153
2891
|
autoPlay: playing !== false,
|
|
3154
2892
|
muted: true,
|
|
3155
2893
|
loop: true,
|
|
@@ -3158,7 +2896,7 @@ function Frame({ media, poster, playing }) {
|
|
|
3158
2896
|
"aria-hidden": "true"
|
|
3159
2897
|
}
|
|
3160
2898
|
),
|
|
3161
|
-
poster && media.posterUrl && playingUrl !== media.url && /* @__PURE__ */ jsx4("img", { className: "frame-media frame-poster", src: media.posterUrl, alt: "" })
|
|
2899
|
+
poster && media.posterUrl && playingUrl !== media.url && /* @__PURE__ */ jsx4("img", { className: "frame-media frame-poster", src: media.posterUrl, alt: "", onLoad: onReady })
|
|
3162
2900
|
] });
|
|
3163
2901
|
}
|
|
3164
2902
|
function SuggestionCards({ suggestions, label, onAsk }) {
|
|
@@ -3231,8 +2969,41 @@ function Welcome({ data, onAsk, title }) {
|
|
|
3231
2969
|
] });
|
|
3232
2970
|
}
|
|
3233
2971
|
|
|
2972
|
+
// src/video-chat/opening-chapter.tsx
|
|
2973
|
+
import { useLayoutEffect, useRef as useRef5, useState as useState4 } from "react";
|
|
2974
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
2975
|
+
function OpeningChapter({ title }) {
|
|
2976
|
+
const root = useRef5(null);
|
|
2977
|
+
const [size, setSize] = useState4({ width: 1080, height: 1080 });
|
|
2978
|
+
useLayoutEffect(() => {
|
|
2979
|
+
const element = root.current;
|
|
2980
|
+
if (!element) return;
|
|
2981
|
+
const measure = () => {
|
|
2982
|
+
const { width, height } = element.getBoundingClientRect();
|
|
2983
|
+
if (width > 0 && height > 0) setSize({ width, height });
|
|
2984
|
+
};
|
|
2985
|
+
measure();
|
|
2986
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
2987
|
+
const observer = new ResizeObserver(measure);
|
|
2988
|
+
observer.observe(element);
|
|
2989
|
+
return () => observer.disconnect();
|
|
2990
|
+
}, []);
|
|
2991
|
+
return /* @__PURE__ */ jsx6("div", { ref: root, "data-opening-chapter": true, className: "opening-chapter", children: /* @__PURE__ */ jsx6(
|
|
2992
|
+
TitleSceneTemplate,
|
|
2993
|
+
{
|
|
2994
|
+
variables: { title },
|
|
2995
|
+
style: {},
|
|
2996
|
+
width: size.width,
|
|
2997
|
+
height: size.height,
|
|
2998
|
+
progress: 0.3,
|
|
2999
|
+
beatIntensity: 0,
|
|
3000
|
+
safeZone: { top: 0, right: 0, bottom: 0, left: 0 }
|
|
3001
|
+
}
|
|
3002
|
+
) });
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3234
3005
|
// src/video-chat/use-voice-input.ts
|
|
3235
|
-
import { useCallback as useCallback4, useEffect as useEffect7, useRef as
|
|
3006
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef6, useState as useState5 } from "react";
|
|
3236
3007
|
function recognitionConstructor() {
|
|
3237
3008
|
if (typeof window === "undefined") return void 0;
|
|
3238
3009
|
const holder = window;
|
|
@@ -3302,14 +3073,14 @@ async function recordAndTranscribe(signal, ready, captured, options) {
|
|
|
3302
3073
|
return clip ? transcribeRecording(clip, signal, options) : "";
|
|
3303
3074
|
}
|
|
3304
3075
|
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 =
|
|
3076
|
+
const [supported, setSupported] = useState5(false);
|
|
3077
|
+
const [listening, setListening] = useState5(false);
|
|
3078
|
+
const [error, setError] = useState5();
|
|
3079
|
+
const [thinking, setThinking] = useState5(false);
|
|
3080
|
+
const recognitionRef = useRef6(void 0);
|
|
3081
|
+
const useRecorderRef = useRef6(false);
|
|
3082
|
+
const operationRef = useRef6(void 0);
|
|
3083
|
+
const handlerRef = useRef6(onTranscript);
|
|
3313
3084
|
handlerRef.current = onTranscript;
|
|
3314
3085
|
useEffect7(() => {
|
|
3315
3086
|
setSupported(supportsVoiceInput(transcriptionAvailable));
|
|
@@ -3436,13 +3207,13 @@ function useVoiceInput(onTranscript, transcriptionAvailable = false, options = {
|
|
|
3436
3207
|
}
|
|
3437
3208
|
|
|
3438
3209
|
// src/video-chat/use-immersive-controls.ts
|
|
3439
|
-
import { useCallback as useCallback5, useEffect as useEffect8, useRef as
|
|
3210
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
|
|
3440
3211
|
function useImmersiveControls(playing, pinned, hasCaptions = false) {
|
|
3441
|
-
const [visible, setVisible] =
|
|
3442
|
-
const hovered =
|
|
3443
|
-
const focused =
|
|
3444
|
-
const previousCaptions =
|
|
3445
|
-
const timer =
|
|
3212
|
+
const [visible, setVisible] = useState6(true);
|
|
3213
|
+
const hovered = useRef7(false);
|
|
3214
|
+
const focused = useRef7(false);
|
|
3215
|
+
const previousCaptions = useRef7(false);
|
|
3216
|
+
const timer = useRef7(null);
|
|
3446
3217
|
const clearTimer = useCallback5(() => {
|
|
3447
3218
|
if (timer.current !== null) clearTimeout(timer.current);
|
|
3448
3219
|
timer.current = null;
|
|
@@ -3493,16 +3264,16 @@ function useImmersiveControls(playing, pinned, hasCaptions = false) {
|
|
|
3493
3264
|
}
|
|
3494
3265
|
|
|
3495
3266
|
// src/video-chat/logo.tsx
|
|
3496
|
-
import { jsx as
|
|
3267
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
3497
3268
|
function Logo() {
|
|
3498
|
-
return /* @__PURE__ */
|
|
3269
|
+
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
3270
|
}
|
|
3500
3271
|
|
|
3501
3272
|
// src/video-chat/video-chat.tsx
|
|
3502
|
-
import { Fragment as Fragment4, jsx as
|
|
3273
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3503
3274
|
var DESKTOP_WIDTH = 900;
|
|
3504
3275
|
function useViewportOrientation() {
|
|
3505
|
-
const [portrait, setPortrait] =
|
|
3276
|
+
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
3277
|
useEffect9(() => {
|
|
3507
3278
|
if (typeof window.matchMedia !== "function") return;
|
|
3508
3279
|
const query = window.matchMedia(`(max-width: ${DESKTOP_WIDTH - 1}px) and (orientation: portrait)`);
|
|
@@ -3520,7 +3291,7 @@ var STEPS = [
|
|
|
3520
3291
|
"Still filming\u2026"
|
|
3521
3292
|
];
|
|
3522
3293
|
function useFilmingStep(active, key) {
|
|
3523
|
-
const [step, setStep] =
|
|
3294
|
+
const [step, setStep] = useState7(0);
|
|
3524
3295
|
useEffect9(() => {
|
|
3525
3296
|
if (!active) return;
|
|
3526
3297
|
setStep(0);
|
|
@@ -3530,47 +3301,38 @@ function useFilmingStep(active, key) {
|
|
|
3530
3301
|
return STEPS[Math.min(step, STEPS.length - 1)];
|
|
3531
3302
|
}
|
|
3532
3303
|
function Waveform({ active, listening }) {
|
|
3533
|
-
return /* @__PURE__ */
|
|
3304
|
+
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
3305
|
}
|
|
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);
|
|
3306
|
+
function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice = false }) {
|
|
3307
|
+
const [dismissedNoticeTurn, setDismissedNoticeTurn] = useState7();
|
|
3308
|
+
const [draft, setDraft] = useState7("");
|
|
3309
|
+
const [savedSessions, setSavedSessions] = useState7([]);
|
|
3310
|
+
const [historyOpen, setHistoryOpen] = useState7(false);
|
|
3311
|
+
const [settingsOpen, setSettingsOpen] = useState7(false);
|
|
3312
|
+
const [aboutOpen, setAboutOpen] = useState7(false);
|
|
3313
|
+
const [captionsOn, setCaptionsOn] = useState7(true);
|
|
3314
|
+
const [captionsExpanded, setCaptionsExpanded] = useState7(false);
|
|
3315
|
+
const [alwaysShowControls, setAlwaysShowControls] = useState7(false);
|
|
3316
|
+
const [editing, setEditing] = useState7(false);
|
|
3317
|
+
const resumeAfterInput = useRef8(false);
|
|
3318
|
+
const panelRef = useRef8(null);
|
|
3319
|
+
const composerRef = useRef8(null);
|
|
3551
3320
|
const viewportOrientation = useViewportOrientation();
|
|
3552
|
-
const theme = themeById(themeId);
|
|
3553
3321
|
const sessionOrientation = options.orientation ?? viewportOrientation;
|
|
3554
|
-
const { brand: configuredBrand, style: configuredStyle, ...sessionOptions } = options;
|
|
3555
|
-
const presentationBrand = configuredBrand ?? theme.brand;
|
|
3556
3322
|
const { chat, restoreSession } = useVideoChatSession({
|
|
3557
|
-
...
|
|
3558
|
-
mode: modeId,
|
|
3323
|
+
...options,
|
|
3559
3324
|
orientation: sessionOrientation,
|
|
3560
|
-
|
|
3561
|
-
style: { generatedLook: theme.generatedLook, ...configuredStyle }
|
|
3325
|
+
style: { generatedLook: "Natural light, restrained camera movement, documentary realism, consistent natural color. No embedded text.", ...options.style }
|
|
3562
3326
|
});
|
|
3563
3327
|
const instanceId = useId();
|
|
3564
3328
|
const historyId = `${instanceId}-history`;
|
|
3565
3329
|
const settingsId = `${instanceId}-settings`;
|
|
3566
3330
|
const promptId = `${instanceId}-prompt`;
|
|
3567
|
-
const
|
|
3568
|
-
const
|
|
3569
|
-
const
|
|
3570
|
-
const
|
|
3571
|
-
const
|
|
3572
|
-
const settingsRef = useRef7(null);
|
|
3573
|
-
const settingsButtonRef = useRef7(null);
|
|
3331
|
+
const inputRef = useRef8(null);
|
|
3332
|
+
const historyRef = useRef8(null);
|
|
3333
|
+
const historyButtonRef = useRef8(null);
|
|
3334
|
+
const settingsRef = useRef8(null);
|
|
3335
|
+
const settingsButtonRef = useRef8(null);
|
|
3574
3336
|
const listen = useVoiceInput(setDraft, chat.capabilities?.transcription ?? false, {
|
|
3575
3337
|
endpoint: options.endpoint,
|
|
3576
3338
|
headers: options.headers,
|
|
@@ -3623,17 +3385,19 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3623
3385
|
const current = chat.currentTurn;
|
|
3624
3386
|
const shown = chat.shownTurn;
|
|
3625
3387
|
const showing = chat.playerProps != null;
|
|
3626
|
-
const
|
|
3388
|
+
const [openingAsset, setOpeningAsset] = useState7();
|
|
3389
|
+
const openingAssetKey = `${shown?.id ?? ""}:${shown?.openingMedia?.url ?? ""}`;
|
|
3390
|
+
const openingMediaFailed = openingAsset?.key === openingAssetKey && openingAsset.state === "failed";
|
|
3391
|
+
const openingMediaReady = openingAsset?.key === openingAssetKey && openingAsset.state === "ready";
|
|
3392
|
+
const openingChapter = !showing && Boolean(shown?.opening) && (!shown?.openingMedia || openingMediaFailed || !openingMediaReady);
|
|
3627
3393
|
const waitingForPicture = chat.turns.length > 0 && !showing && (chat.status === "composing" || chat.status === "playing" || chat.status === "paused");
|
|
3628
3394
|
const filmingStep = useFilmingStep(waitingForPicture, current?.id);
|
|
3629
3395
|
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
3396
|
const shownOrientation = shown?.orientation ?? sessionOrientation;
|
|
3631
3397
|
const stageOrientation = shown?.fixedOrientation ? shownOrientation : sessionOrientation;
|
|
3632
|
-
const availableModes = visualModes.filter((option) => chat.availableModes.includes(option.id));
|
|
3633
|
-
const selectedMode = modeById(modeId);
|
|
3634
3398
|
const line = chat.caption ?? "";
|
|
3635
3399
|
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__ */
|
|
3400
|
+
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
3401
|
const cancelInput = useCallback6(() => {
|
|
3638
3402
|
listen.stop();
|
|
3639
3403
|
setDraft("");
|
|
@@ -3664,7 +3428,7 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3664
3428
|
onFocusCapture: controls.onFocusCapture,
|
|
3665
3429
|
onBlurCapture: controls.onBlurCapture
|
|
3666
3430
|
};
|
|
3667
|
-
|
|
3431
|
+
useLayoutEffect2(() => {
|
|
3668
3432
|
const composer = composerRef.current;
|
|
3669
3433
|
if (!composer) return;
|
|
3670
3434
|
const measure = () => panelRef.current?.style.setProperty("--composer-height", `${composer.getBoundingClientRect().height}px`);
|
|
@@ -3687,7 +3451,7 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3687
3451
|
onKeyDownCapture: controls.reveal,
|
|
3688
3452
|
children: [
|
|
3689
3453
|
/* @__PURE__ */ jsxs6("header", { className: "chrome", ...controlEvents, children: [
|
|
3690
|
-
/* @__PURE__ */
|
|
3454
|
+
/* @__PURE__ */ jsx8("div", { className: "session-brand", children: /* @__PURE__ */ jsx8(Logo, {}) }),
|
|
3691
3455
|
/* @__PURE__ */ jsxs6("div", { className: "group", children: [
|
|
3692
3456
|
/* @__PURE__ */ jsxs6(
|
|
3693
3457
|
"button",
|
|
@@ -3704,12 +3468,12 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3704
3468
|
setSettingsOpen(false);
|
|
3705
3469
|
},
|
|
3706
3470
|
children: [
|
|
3707
|
-
/* @__PURE__ */
|
|
3708
|
-
/* @__PURE__ */
|
|
3471
|
+
/* @__PURE__ */ jsx8(Sessions, {}),
|
|
3472
|
+
/* @__PURE__ */ jsx8("span", { className: "nav-label", children: "Sessions" })
|
|
3709
3473
|
]
|
|
3710
3474
|
}
|
|
3711
3475
|
),
|
|
3712
|
-
/* @__PURE__ */
|
|
3476
|
+
/* @__PURE__ */ jsx8(
|
|
3713
3477
|
"button",
|
|
3714
3478
|
{
|
|
3715
3479
|
ref: settingsButtonRef,
|
|
@@ -3723,10 +3487,10 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3723
3487
|
setSettingsOpen((open) => !open);
|
|
3724
3488
|
setHistoryOpen(false);
|
|
3725
3489
|
},
|
|
3726
|
-
children: /* @__PURE__ */
|
|
3490
|
+
children: /* @__PURE__ */ jsx8(Gear, {})
|
|
3727
3491
|
}
|
|
3728
3492
|
),
|
|
3729
|
-
/* @__PURE__ */
|
|
3493
|
+
/* @__PURE__ */ jsx8(
|
|
3730
3494
|
"button",
|
|
3731
3495
|
{
|
|
3732
3496
|
type: "button",
|
|
@@ -3734,27 +3498,37 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3734
3498
|
"aria-label": chat.muted ? "Turn the voice on" : "Turn the voice off",
|
|
3735
3499
|
"aria-pressed": chat.muted,
|
|
3736
3500
|
onClick: () => chat.setMuted(!chat.muted),
|
|
3737
|
-
children: chat.muted ? /* @__PURE__ */
|
|
3501
|
+
children: chat.muted ? /* @__PURE__ */ jsx8(Muted, {}) : /* @__PURE__ */ jsx8(Sound, {})
|
|
3738
3502
|
}
|
|
3739
3503
|
)
|
|
3740
3504
|
] })
|
|
3741
3505
|
] }),
|
|
3742
3506
|
/* @__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: "" }) }),
|
|
3507
|
+
/* @__PURE__ */ jsxs6("div", { className: "stage", style: { background: "#000" }, children: [
|
|
3745
3508
|
!showing && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
3746
|
-
/* @__PURE__ */
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
/* @__PURE__ */
|
|
3509
|
+
/* @__PURE__ */ jsx8("div", { className: "ground", "aria-hidden": "true" }),
|
|
3510
|
+
openingChapter && /* @__PURE__ */ jsx8(OpeningChapter, { title: shown.opening }, shown.id),
|
|
3511
|
+
shown?.openingMedia && !openingMediaFailed && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
3512
|
+
/* @__PURE__ */ jsx8(
|
|
3513
|
+
Frame,
|
|
3514
|
+
{
|
|
3515
|
+
media: shown.openingMedia,
|
|
3516
|
+
poster: true,
|
|
3517
|
+
revealWhenReady: true,
|
|
3518
|
+
onReady: () => setOpeningAsset({ key: openingAssetKey, state: "ready" }),
|
|
3519
|
+
onError: () => setOpeningAsset({ key: openingAssetKey, state: "failed" })
|
|
3520
|
+
},
|
|
3521
|
+
openingAssetKey
|
|
3522
|
+
),
|
|
3523
|
+
openingMediaReady && /* @__PURE__ */ jsx8("div", { className: "opening-wash", "aria-hidden": "true" })
|
|
3750
3524
|
] }),
|
|
3751
|
-
chat.turns.length === 0 && /* @__PURE__ */
|
|
3752
|
-
shown?.prompt && /* @__PURE__ */ jsxs6("div", { className: "asked", children: [
|
|
3753
|
-
/* @__PURE__ */
|
|
3754
|
-
waitingForPicture && /* @__PURE__ */
|
|
3525
|
+
chat.turns.length === 0 && /* @__PURE__ */ jsx8(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle }),
|
|
3526
|
+
shown?.prompt && !shown.opening && /* @__PURE__ */ jsxs6("div", { className: "asked", children: [
|
|
3527
|
+
/* @__PURE__ */ jsx8("p", { className: "asked-prompt", children: shown.prompt }),
|
|
3528
|
+
waitingForPicture && /* @__PURE__ */ jsx8("p", { className: "asked-step", "aria-live": "polite", children: filmingStep })
|
|
3755
3529
|
] })
|
|
3756
3530
|
] }),
|
|
3757
|
-
chat.playerProps && /* @__PURE__ */
|
|
3531
|
+
chat.playerProps && /* @__PURE__ */ jsx8("div", { className: "player-fit", style: { width: stageOrientation === "portrait" ? "min(100cqw, 56.25cqh)" : "min(100cqw, 177.7778cqh)" }, children: /* @__PURE__ */ jsx8(
|
|
3758
3532
|
VideoPlayer,
|
|
3759
3533
|
{
|
|
3760
3534
|
...chat.playerProps,
|
|
@@ -3766,27 +3540,27 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3766
3540
|
chat.playerKey
|
|
3767
3541
|
) }),
|
|
3768
3542
|
showing && chat.playbackEnded && chat.suggestions.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "ending", children: [
|
|
3769
|
-
/* @__PURE__ */
|
|
3543
|
+
/* @__PURE__ */ jsx8("div", { className: "ending-wash", "aria-hidden": "true" }),
|
|
3770
3544
|
/* @__PURE__ */ jsxs6("div", { className: "ending-body", children: [
|
|
3771
|
-
/* @__PURE__ */
|
|
3772
|
-
/* @__PURE__ */
|
|
3545
|
+
/* @__PURE__ */ jsx8("p", { className: "ending-label", children: "Ask next" }),
|
|
3546
|
+
/* @__PURE__ */ jsx8(SuggestionCards, { suggestions: [...chat.suggestions], label: "Follow-up prompts", onAsk: ask })
|
|
3773
3547
|
] })
|
|
3774
3548
|
] })
|
|
3775
3549
|
] }),
|
|
3776
3550
|
historyOpen && /* @__PURE__ */ jsxs6("nav", { ref: historyRef, id: historyId, className: "sheet-popover history", role: "dialog", "aria-modal": "true", "aria-label": "Sessions", children: [
|
|
3777
3551
|
/* @__PURE__ */ jsxs6("div", { className: "popover-heading", children: [
|
|
3778
|
-
/* @__PURE__ */
|
|
3779
|
-
/* @__PURE__ */
|
|
3552
|
+
/* @__PURE__ */ jsx8("h2", { children: "Sessions" }),
|
|
3553
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Close sessions", onClick: closeHistory, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3780
3554
|
] }),
|
|
3781
3555
|
/* @__PURE__ */ jsxs6("button", { type: "button", className: "history-row session-new", "aria-label": "New session", onClick: newSession, children: [
|
|
3782
|
-
/* @__PURE__ */
|
|
3556
|
+
/* @__PURE__ */ jsx8(Plus, {}),
|
|
3783
3557
|
/* @__PURE__ */ jsxs6("span", { className: "prompt", children: [
|
|
3784
3558
|
"New session",
|
|
3785
|
-
/* @__PURE__ */
|
|
3559
|
+
/* @__PURE__ */ jsx8("small", { children: "Start a fresh conversation" })
|
|
3786
3560
|
] })
|
|
3787
3561
|
] }),
|
|
3788
|
-
/* @__PURE__ */
|
|
3789
|
-
chat.turns.length === 0 && /* @__PURE__ */
|
|
3562
|
+
/* @__PURE__ */ jsx8("h3", { className: "section-label", children: "Current session" }),
|
|
3563
|
+
chat.turns.length === 0 && /* @__PURE__ */ jsx8("p", { className: "history-empty", children: "Your questions will appear here." }),
|
|
3790
3564
|
chat.turns.map((turn, index) => /* @__PURE__ */ jsxs6(
|
|
3791
3565
|
"button",
|
|
3792
3566
|
{
|
|
@@ -3803,17 +3577,17 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3803
3577
|
setHistoryOpen(false);
|
|
3804
3578
|
},
|
|
3805
3579
|
children: [
|
|
3806
|
-
/* @__PURE__ */
|
|
3580
|
+
/* @__PURE__ */ jsx8("span", { className: "index", children: String(index + 1).padStart(2, "0") }),
|
|
3807
3581
|
/* @__PURE__ */ jsxs6("span", { className: "prompt", children: [
|
|
3808
3582
|
turn.prompt,
|
|
3809
|
-
/* @__PURE__ */
|
|
3583
|
+
/* @__PURE__ */ jsx8("small", { children: turn.id === shown?.id ? "Now showing" : turn.completed ? "Play answer" : "Unfinished answer" })
|
|
3810
3584
|
] })
|
|
3811
3585
|
]
|
|
3812
3586
|
},
|
|
3813
3587
|
turn.id
|
|
3814
3588
|
)),
|
|
3815
3589
|
savedSessions.length > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
3816
|
-
/* @__PURE__ */
|
|
3590
|
+
/* @__PURE__ */ jsx8("h3", { className: "section-label", children: "Earlier sessions" }),
|
|
3817
3591
|
savedSessions.map((session) => /* @__PURE__ */ jsxs6("button", { type: "button", className: "history-row", onClick: () => {
|
|
3818
3592
|
listen.stop();
|
|
3819
3593
|
setDraft("");
|
|
@@ -3825,7 +3599,7 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3825
3599
|
setHistoryOpen(false);
|
|
3826
3600
|
setCaptionsExpanded(false);
|
|
3827
3601
|
}, children: [
|
|
3828
|
-
/* @__PURE__ */
|
|
3602
|
+
/* @__PURE__ */ jsx8(Replay, {}),
|
|
3829
3603
|
/* @__PURE__ */ jsxs6("span", { className: "prompt", children: [
|
|
3830
3604
|
session.turns[0]?.prompt,
|
|
3831
3605
|
/* @__PURE__ */ jsxs6("small", { children: [
|
|
@@ -3848,72 +3622,55 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3848
3622
|
"aria-modal": "true",
|
|
3849
3623
|
children: [
|
|
3850
3624
|
/* @__PURE__ */ jsxs6("div", { className: "popover-heading", children: [
|
|
3851
|
-
/* @__PURE__ */
|
|
3852
|
-
/* @__PURE__ */
|
|
3625
|
+
/* @__PURE__ */ jsx8("h2", { children: "Settings" }),
|
|
3626
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Close settings", onClick: closeSettings, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3853
3627
|
] }),
|
|
3854
3628
|
/* @__PURE__ */ jsxs6("fieldset", { className: "playback-options", children: [
|
|
3855
|
-
/* @__PURE__ */
|
|
3629
|
+
/* @__PURE__ */ jsx8("legend", { children: "Watching" }),
|
|
3856
3630
|
/* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
|
|
3857
3631
|
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3858
|
-
/* @__PURE__ */
|
|
3859
|
-
/* @__PURE__ */
|
|
3632
|
+
/* @__PURE__ */ jsx8("strong", { children: "Subtitles" }),
|
|
3633
|
+
/* @__PURE__ */ jsx8("small", { children: "Read along with the answer" })
|
|
3860
3634
|
] }),
|
|
3861
|
-
/* @__PURE__ */
|
|
3635
|
+
/* @__PURE__ */ jsx8("input", { type: "checkbox", role: "switch", checked: captionsOn, onChange: (event) => {
|
|
3862
3636
|
setCaptionsOn(event.target.checked);
|
|
3863
3637
|
setCaptionsExpanded(false);
|
|
3864
3638
|
} })
|
|
3865
3639
|
] }),
|
|
3866
3640
|
/* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
|
|
3867
3641
|
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3868
|
-
/* @__PURE__ */
|
|
3869
|
-
/* @__PURE__ */
|
|
3642
|
+
/* @__PURE__ */ jsx8("strong", { children: "Keep controls visible" }),
|
|
3643
|
+
/* @__PURE__ */ jsx8("small", { children: "Keep the input bar on screen" })
|
|
3870
3644
|
] }),
|
|
3871
|
-
/* @__PURE__ */
|
|
3645
|
+
/* @__PURE__ */ jsx8("input", { type: "checkbox", role: "switch", checked: alwaysShowControls, onChange: (event) => setAlwaysShowControls(event.target.checked) })
|
|
3872
3646
|
] })
|
|
3873
3647
|
] }),
|
|
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
3648
|
/* @__PURE__ */ jsxs6("nav", { className: "developer-links", "aria-label": "Build with VanillaSky", children: [
|
|
3893
|
-
/* @__PURE__ */
|
|
3649
|
+
/* @__PURE__ */ jsx8("p", { className: "section-label", children: "Build with VanillaSky" }),
|
|
3894
3650
|
/* @__PURE__ */ jsxs6("a", { href: "https://github.com/VanillaSkyAi/video/blob/main/docs/getting-started.md", target: "_blank", rel: "noopener noreferrer", children: [
|
|
3895
3651
|
"Docs",
|
|
3896
|
-
/* @__PURE__ */
|
|
3652
|
+
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2197" })
|
|
3897
3653
|
] }),
|
|
3898
3654
|
/* @__PURE__ */ jsxs6("button", { type: "button", "aria-expanded": aboutOpen, "aria-controls": `${instanceId}-about`, onClick: () => setAboutOpen((open) => !open), children: [
|
|
3899
3655
|
"About",
|
|
3900
|
-
/* @__PURE__ */
|
|
3656
|
+
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: aboutOpen ? "\u2212" : "+" })
|
|
3901
3657
|
] }),
|
|
3902
|
-
/* @__PURE__ */
|
|
3658
|
+
/* @__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
3659
|
/* @__PURE__ */ jsxs6("a", { href: "https://github.com/VanillaSkyAi/video", target: "_blank", rel: "noopener noreferrer", children: [
|
|
3904
3660
|
"GitHub",
|
|
3905
|
-
/* @__PURE__ */
|
|
3661
|
+
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u2197" })
|
|
3906
3662
|
] })
|
|
3907
3663
|
] })
|
|
3908
3664
|
]
|
|
3909
3665
|
}
|
|
3910
3666
|
)
|
|
3911
3667
|
] }),
|
|
3912
|
-
/* @__PURE__ */
|
|
3913
|
-
/* @__PURE__ */
|
|
3668
|
+
/* @__PURE__ */ jsx8("div", { ref: panelRef, className: "panel", "data-input-visible": controls.visible || !captionsOn || !line, children: /* @__PURE__ */ jsxs6("div", { className: "panel-inner", children: [
|
|
3669
|
+
/* @__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
3670
|
"div",
|
|
3915
3671
|
{
|
|
3916
3672
|
className: "line-row",
|
|
3673
|
+
"data-opening-copy": openingChapter && line === shown?.opening && !captionsExpanded,
|
|
3917
3674
|
"data-expanded": captionsExpanded,
|
|
3918
3675
|
"data-actions-visible": captionControls.visible,
|
|
3919
3676
|
onPointerMove: captionControls.onPointerEnter,
|
|
@@ -3925,41 +3682,41 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3925
3682
|
captionsOn && line && /* @__PURE__ */ jsxs6("div", { className: "caption-actions", children: [
|
|
3926
3683
|
/* @__PURE__ */ jsxs6("button", { type: "button", className: "caption-action", "aria-label": captionsExpanded ? "Collapse subtitles" : "Expand subtitles", "aria-expanded": captionsExpanded, onClick: () => setCaptionsExpanded((open) => !open), children: [
|
|
3927
3684
|
captionsExpanded ? "Collapse" : "Expand",
|
|
3928
|
-
/* @__PURE__ */
|
|
3685
|
+
/* @__PURE__ */ jsx8(ChevronUp, {})
|
|
3929
3686
|
] }),
|
|
3930
|
-
/* @__PURE__ */
|
|
3687
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "caption-action", "aria-label": "Hide subtitles", onClick: () => {
|
|
3931
3688
|
setCaptionsOn(false);
|
|
3932
3689
|
setCaptionsExpanded(false);
|
|
3933
|
-
}, children: /* @__PURE__ */
|
|
3690
|
+
}, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3934
3691
|
] }),
|
|
3935
|
-
captionsExpanded ? /* @__PURE__ */
|
|
3692
|
+
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
3693
|
]
|
|
3937
3694
|
}
|
|
3938
3695
|
) }) }),
|
|
3939
3696
|
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__ */
|
|
3697
|
+
/* @__PURE__ */ jsx8("p", { role: "status", children: MEDIA_RECOVERY_NOTICE }),
|
|
3698
|
+
/* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Dismiss notice", onClick: () => setDismissedNoticeTurn(chat.shownTurn?.id), children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3942
3699
|
] }),
|
|
3943
3700
|
(chat.error || listen.error) && /* @__PURE__ */ jsxs6("p", { className: "error", role: "status", children: [
|
|
3944
|
-
/* @__PURE__ */
|
|
3945
|
-
/* @__PURE__ */
|
|
3701
|
+
/* @__PURE__ */ jsx8(Warning, {}),
|
|
3702
|
+
/* @__PURE__ */ jsx8("span", { children: chat.error?.message ?? listen.error })
|
|
3946
3703
|
] }),
|
|
3947
3704
|
/* @__PURE__ */ jsxs6("div", { ref: composerRef, className: "conversation-composer", "data-editing": editing, ...controlEvents, children: [
|
|
3948
|
-
(listen.listening || listen.thinking) && /* @__PURE__ */
|
|
3705
|
+
(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
3706
|
/* @__PURE__ */ jsxs6("form", { className: "composer", "aria-label": "Ask a question", onSubmit: (event) => {
|
|
3950
3707
|
event.preventDefault();
|
|
3951
3708
|
ask(draft);
|
|
3952
3709
|
}, children: [
|
|
3953
|
-
transport && /* @__PURE__ */
|
|
3710
|
+
transport && /* @__PURE__ */ jsx8("button", { type: "button", className: "ghost transport", "aria-label": transport.label, onClick: () => {
|
|
3954
3711
|
listen.stop();
|
|
3955
3712
|
setEditing(false);
|
|
3956
3713
|
resumeAfterInput.current = false;
|
|
3957
3714
|
inputRef.current?.blur();
|
|
3958
3715
|
transport.action();
|
|
3959
3716
|
}, children: transport.icon }),
|
|
3960
|
-
/* @__PURE__ */
|
|
3961
|
-
/* @__PURE__ */
|
|
3962
|
-
/* @__PURE__ */
|
|
3717
|
+
/* @__PURE__ */ jsx8(Waveform, { active: listen.listening || chat.speaking && !chat.muted && status !== "paused", listening: listen.listening }),
|
|
3718
|
+
/* @__PURE__ */ jsx8("label", { className: "sr-only", htmlFor: promptId, children: "Prompt" }),
|
|
3719
|
+
/* @__PURE__ */ jsx8(
|
|
3963
3720
|
"textarea",
|
|
3964
3721
|
{
|
|
3965
3722
|
id: promptId,
|
|
@@ -3987,12 +3744,12 @@ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel,
|
|
|
3987
3744
|
}
|
|
3988
3745
|
}
|
|
3989
3746
|
),
|
|
3990
|
-
listen.supported && /* @__PURE__ */
|
|
3991
|
-
/* @__PURE__ */
|
|
3992
|
-
editing && /* @__PURE__ */
|
|
3747
|
+
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, {}) }),
|
|
3748
|
+
/* @__PURE__ */ jsx8("button", { type: "submit", className: "send", "aria-label": "Ask", disabled: !draft.trim() || listen.thinking, children: /* @__PURE__ */ jsx8(Send, {}) }),
|
|
3749
|
+
editing && /* @__PURE__ */ jsx8("button", { type: "button", className: "ghost", "aria-label": "Cancel question", onClick: cancelInput, children: /* @__PURE__ */ jsx8(Close, {}) })
|
|
3993
3750
|
] })
|
|
3994
3751
|
] }),
|
|
3995
|
-
!showing && chat.turns.length === 0 && /* @__PURE__ */
|
|
3752
|
+
!showing && chat.turns.length === 0 && /* @__PURE__ */ jsx8("p", { className: "dock-hint", children: "Speak or type. See where it takes you." })
|
|
3996
3753
|
] }) })
|
|
3997
3754
|
]
|
|
3998
3755
|
}
|