@vanillaskyai/video 0.8.2 → 0.8.4

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 CHANGED
@@ -4,6 +4,14 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.8.4
8
+
9
+ - Use a curated cloud timelapse for the default welcome screen, while preserving host-selected welcome searches.
10
+
11
+ ## 0.8.3
12
+
13
+ - Allow hosts to bound generated-video waiting up to two minutes, describe limited generated-video offerings in Settings, and opt into a dismissible media fallback notice.
14
+
7
15
  ## 0.8.2
8
16
 
9
17
  - Adds Docs and GitHub links plus an inline About section to the default VideoChat Settings, keeping developer discovery within the shared interface.
package/PUBLIC-API.md CHANGED
@@ -450,3 +450,16 @@ and audio `playing` events, not request completion or preparation estimates.
450
450
  `VideoPlayerProps.onFramePresented` and `onStallChange` expose the underlying
451
451
  presentation and stream-starvation signals for custom players. They do not
452
452
  measure media-decoder buffering. See [Performance](docs/performance.md).
453
+
454
+ ### Bounded generated-video previews
455
+
456
+ `VideoChatHandlerOptions.generateVideoTimeoutMs` optionally sets the generated media
457
+ resolver deadline (integer 1–120000 milliseconds, default 15000). The host still owns
458
+ provider cancellation, authorization and spend limits; a timeout does not prove the
459
+ provider avoided a charge. `maxGeneratedVideos` continues to count attempted clips.
460
+
461
+ `VideoChatProps.generatedVideoLabel` and `generatedVideoDescription` customize the
462
+ existing generated-video Settings choice without changing server capabilities.
463
+ `showRecoveryNotice` opts into a dismissible, fixed SDK media-fallback notice. It
464
+ never displays arbitrary provider messages. All three default to existing behavior.
465
+ These are additive members; no new entry points or exports are introduced.
@@ -8,6 +8,9 @@ import {
8
8
  VIDEO_PROTOCOL_VERSION
9
9
  } from "./chunk-AOWSXU2K.js";
10
10
 
11
+ // src/video-chat/recovery.ts
12
+ var MEDIA_RECOVERY_NOTICE = "Some visuals were replaced so your response can continue.";
13
+
11
14
  // src/protocol/sse.ts
12
15
  var VIDEO_STREAM_HEADER = "x-vanillasky-video-stream";
13
16
  var VIDEO_STREAM_CONTENT_TYPE = "text/event-stream; charset=utf-8";
@@ -157,6 +160,7 @@ function safeMediaUrl(value) {
157
160
  }
158
161
 
159
162
  export {
163
+ MEDIA_RECOVERY_NOTICE,
160
164
  encodeVideoSseEvent,
161
165
  decodeVideoSse,
162
166
  videoSseHeaders,
package/dist/react.d.ts CHANGED
@@ -256,8 +256,14 @@ interface VideoChatProps {
256
256
  className?: string;
257
257
  /** Replaces the default two-line welcome heading without changing the interaction. */
258
258
  welcomeTitle?: ReactNode;
259
+ /** Host-owned name for the generated-video choice in Settings. Does not change server capabilities. */
260
+ generatedVideoLabel?: string;
261
+ /** Explain the host's generated-video offering, including preview limits. */
262
+ generatedVideoDescription?: string;
263
+ /** Show a dismissible safe notice when generated visuals fall back. Defaults to false. */
264
+ showRecoveryNotice?: boolean;
259
265
  }
260
266
  /** A complete voice-and-video chat interface backed by createVideoChatHandler. */
261
- declare function VideoChat({ options, className, welcomeTitle }: VideoChatProps): react.JSX.Element;
267
+ declare function VideoChat({ options, className, welcomeTitle, generatedVideoLabel, generatedVideoDescription, showRecoveryNotice }: VideoChatProps): react.JSX.Element;
262
268
 
263
269
  export { type CreateVideoChatVoiceOptions, type UseVideoChatOptions, type UseVideoChatResult, VideoChat, VideoChatAskOptions, VideoChatCapabilities, type VideoChatFirstFrameMetric, VideoChatMedia, VideoChatMode, type VideoChatPlaybackMetric, type VideoChatPreparedSpeech, type VideoChatProps, type VideoChatStatus, VideoChatSuggestion, type VideoChatTurn, type VideoChatVoice, VideoChatWelcome, VideoError, type VideoErrorOptions, type VideoPlaybackMode, VideoPlayer, type VideoPlayerProps, createVideoChatVoice, useVideoChat };
package/dist/react.js CHANGED
@@ -2,10 +2,11 @@ import {
2
2
  getSceneDuration
3
3
  } from "./chunk-ZD2VTUYR.js";
4
4
  import {
5
+ MEDIA_RECOVERY_NOTICE,
5
6
  decodeVideoSse,
6
7
  sanitizeVideoChatMedia,
7
8
  withDeadline
8
- } from "./chunk-APQKY6P3.js";
9
+ } from "./chunk-KT3M7KDL.js";
9
10
  import {
10
11
  applyVideoEvent,
11
12
  checksumVideo,
@@ -2651,7 +2652,7 @@ function useVideoChatSession(options = {}) {
2651
2652
  if (!isCurrent() || currentAttempt !== attempt) return { video: { schemaVersion: "0.1", orientation, scenes: [], style }, lines: [] };
2652
2653
  if (event.type === "response.start") style = event.data.style;
2653
2654
  if (event.type === "response.warning" || event.type === "response.error" && !event.data.terminal) {
2654
- warn("Some parts were simplified so the response could continue.");
2655
+ warn(event.type === "response.warning" && event.data.warning.message === MEDIA_RECOVERY_NOTICE ? MEDIA_RECOVERY_NOTICE : "Some parts were simplified so the response could continue.");
2655
2656
  }
2656
2657
  if (event.type === "data.video-chat-opening") {
2657
2658
  const payload = event.data && typeof event.data === "object" && !Array.isArray(event.data) ? event.data : {};
@@ -3524,7 +3525,8 @@ function useFilmingStep(active, key) {
3524
3525
  function Waveform({ active, listening }) {
3525
3526
  return /* @__PURE__ */ jsx7("span", { className: `waveform${active ? " on" : ""}${listening ? " hearing" : ""}`, "aria-hidden": "true", children: [0, 1, 2, 3, 4].map((bar) => /* @__PURE__ */ jsx7("span", { style: { animationDelay: `${bar * 140}ms`, animationDuration: `${900 + bar * 130}ms` } }, bar)) });
3526
3527
  }
3527
- function VideoChat({ options = {}, className, welcomeTitle }) {
3528
+ function VideoChat({ options = {}, className, welcomeTitle, generatedVideoLabel, generatedVideoDescription, showRecoveryNotice = false }) {
3529
+ const [dismissedNoticeTurn, setDismissedNoticeTurn] = useState6();
3528
3530
  const [draft, setDraft] = useState6("");
3529
3531
  const [savedSessions, setSavedSessions] = useState6([]);
3530
3532
  const [themeId, setThemeId] = useState6(defaultTheme.id);
@@ -3873,8 +3875,8 @@ function VideoChat({ options = {}, className, welcomeTitle }) {
3873
3875
  availableModes.map((option) => /* @__PURE__ */ jsxs6("label", { className: "choice-row", children: [
3874
3876
  /* @__PURE__ */ jsx7("input", { type: "radio", name: visualsName, checked: option.id === selectedMode.id, onChange: () => setModeId(option.id) }),
3875
3877
  /* @__PURE__ */ jsxs6("span", { children: [
3876
- /* @__PURE__ */ jsx7("strong", { children: option.label }),
3877
- /* @__PURE__ */ jsx7("small", { children: option.note })
3878
+ /* @__PURE__ */ jsx7("strong", { children: option.id === "full" ? generatedVideoLabel ?? option.label : option.label }),
3879
+ /* @__PURE__ */ jsx7("small", { children: option.id === "full" ? generatedVideoDescription ?? option.note : option.note })
3878
3880
  ] })
3879
3881
  ] }, option.id))
3880
3882
  ] }),
@@ -3933,6 +3935,10 @@ function VideoChat({ options = {}, className, welcomeTitle }) {
3933
3935
  ]
3934
3936
  }
3935
3937
  ) }) }),
3938
+ showRecoveryNotice && chat.shownTurn && dismissedNoticeTurn !== chat.shownTurn.id && chat.warnings.includes(MEDIA_RECOVERY_NOTICE) && !chat.error && /* @__PURE__ */ jsxs6("div", { className: "recovery-notice", children: [
3939
+ /* @__PURE__ */ jsx7("p", { role: "status", children: MEDIA_RECOVERY_NOTICE }),
3940
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "round", "aria-label": "Dismiss notice", onClick: () => setDismissedNoticeTurn(chat.shownTurn?.id), children: /* @__PURE__ */ jsx7(Close, {}) })
3941
+ ] }),
3936
3942
  (chat.error || listen.error) && /* @__PURE__ */ jsxs6("p", { className: "error", role: "status", children: [
3937
3943
  /* @__PURE__ */ jsx7(Warning, {}),
3938
3944
  /* @__PURE__ */ jsx7("span", { children: chat.error?.message ?? listen.error })
package/dist/server.d.ts CHANGED
@@ -235,6 +235,8 @@ interface VideoChatHandlerOptions extends Pick<VideoHandlerOptions, "templates"
235
235
  generateVideo?: VideoChatMediaResolver;
236
236
  /** Maximum generated-video attempts per response, including failures. Defaults to 5. */
237
237
  maxGeneratedVideos?: number;
238
+ /** Generated media deadline in milliseconds, 1–120000. Defaults to 15000. Host providers must honor cancellation. */
239
+ generateVideoTimeoutMs?: number;
238
240
  /** Trusted application guidance appended to the general-purpose response brief. */
239
241
  instructions?: string;
240
242
  /** Application-owned prompts and visual searches shown before the first turn. */
package/dist/server.js CHANGED
@@ -17,12 +17,13 @@ import {
17
17
  createTemplateSceneValidator
18
18
  } from "./chunk-LMG3GHLL.js";
19
19
  import {
20
+ MEDIA_RECOVERY_NOTICE,
20
21
  decodeVideoSse,
21
22
  encodeVideoSseEvent,
22
23
  sanitizeVideoChatMedia,
23
24
  videoSseHeaders,
24
25
  withDeadline
25
- } from "./chunk-APQKY6P3.js";
26
+ } from "./chunk-KT3M7KDL.js";
26
27
  import "./chunk-2OKB27TG.js";
27
28
  import {
28
29
  allowedKeys,
@@ -885,6 +886,11 @@ THIS SCENE: [${scene.templateId}] ${JSON.stringify(scene.variables).slice(0, 4e3
885
886
 
886
887
  // src/server/create-video-chat-handler.ts
887
888
  var DEFAULT_MAX_BODY_BYTES2 = 1024 * 1024;
889
+ var DEFAULT_WELCOME_HERO = {
890
+ url: "https://videos.pexels.com/video-files/11335959/11335959-hd_1920_1080_30fps.mp4",
891
+ type: "video",
892
+ posterUrl: "https://images.pexels.com/videos/11335959/pexels-photo-11335959.jpeg?auto=compress&fit=crop&w=1920"
893
+ };
888
894
  var DEFAULT_MAX_AUDIO_BYTES = 8 * 1024 * 1024;
889
895
  var MAX_PROMPT_CHARACTERS = 8e3;
890
896
  var MAX_CONVERSATION_TURNS = 12;
@@ -1269,7 +1275,8 @@ function createVideoChatHandler(options) {
1269
1275
  allowedOrigins,
1270
1276
  allowCredentials,
1271
1277
  mediaConcurrency = 5,
1272
- maxGeneratedVideos = 5
1278
+ maxGeneratedVideos = 5,
1279
+ generateVideoTimeoutMs = 15e3
1273
1280
  } = options;
1274
1281
  const videoOptions = {
1275
1282
  templates: options.templates,
@@ -1284,6 +1291,7 @@ function createVideoChatHandler(options) {
1284
1291
  };
1285
1292
  if (!Number.isFinite(maxAudioBytes) || maxAudioBytes <= 0) throw new Error("maxAudioBytes must be positive");
1286
1293
  if (!Number.isFinite(maxBodyBytes) || maxBodyBytes <= 0) throw new Error("maxBodyBytes must be positive");
1294
+ if (!Number.isSafeInteger(generateVideoTimeoutMs) || generateVideoTimeoutMs < 1 || generateVideoTimeoutMs > 12e4) throw new Error("generateVideoTimeoutMs must be an integer from 1 to 120000");
1287
1295
  if (!Number.isSafeInteger(maxGeneratedVideos) || maxGeneratedVideos < 0) throw new Error("maxGeneratedVideos must be a nonnegative safe integer");
1288
1296
  const capabilities2 = {
1289
1297
  templates: true,
@@ -1294,7 +1302,7 @@ function createVideoChatHandler(options) {
1294
1302
  modes: generateVideo ? ["templates", "full"] : ["templates"]
1295
1303
  };
1296
1304
  const welcomePrompts = (welcomeOptions?.prompts ?? DEFAULT_WELCOME_PROMPTS).slice(0, 4);
1297
- const heroQuery = welcomeOptions?.heroQuery ?? "underwater ocean sunlight";
1305
+ const heroQuery = welcomeOptions?.heroQuery;
1298
1306
  let welcomeResponse;
1299
1307
  let requestSequence = 0;
1300
1308
  const responseHandler = (requestedMode, requestId, openingProvided, openingChannel) => {
@@ -1338,12 +1346,12 @@ function createVideoChatHandler(options) {
1338
1346
  }
1339
1347
  };
1340
1348
  if (fullAiVideo) {
1341
- const generated = generatedAttempts < maxGeneratedVideos ? (generatedAttempts++, await attempt(generateVideo, 15e3)) : null;
1349
+ const generated = generatedAttempts < maxGeneratedVideos ? (generatedAttempts++, await attempt(generateVideo, generateVideoTimeoutMs)) : null;
1342
1350
  if (generated) return remember(generated);
1343
1351
  lifecycle?.reportWarning?.({
1344
1352
  code: "provider_warning",
1345
1353
  category: "provider",
1346
- message: "Some visuals were replaced so your response can continue.",
1354
+ message: MEDIA_RECOVERY_NOTICE,
1347
1355
  recoverable: true
1348
1356
  });
1349
1357
  }
@@ -1448,7 +1456,7 @@ function createVideoChatHandler(options) {
1448
1456
  }
1449
1457
  };
1450
1458
  const [hero, ...cards] = await Promise.all([
1451
- resolve(heroQuery),
1459
+ heroQuery === void 0 ? DEFAULT_WELCOME_HERO : resolve(heroQuery),
1452
1460
  ...welcomePrompts.map((entry) => resolve(entry.mediaQuery))
1453
1461
  ]);
1454
1462
  return {
@@ -88,8 +88,9 @@ body { background: #080b16; }
88
88
  <section class="doc-section" id="surfaces"><div class="section-heading"><span class="number">01</span><h2>Present without competing</h2></div><p class="section-copy">Dark glass, soft edges and restrained lavender. Navigation sits above the picture, with the logo and a quiet cluster of actions.</p>
89
89
  <div class="doc-preview"><header class="chrome"><div class="session-brand"><img class="brand-logo" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI4LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjE1NSAxMTMgMTY5MCA0MjIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMTU1IDExMyAxNjkwIDQyMjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOnVybCgjU1ZHSURfMV8pO30KCS5zdDF7ZmlsbDp1cmwoI1NWR0lEXzAwMDAwMDEzMTc5MjQ3NzkzMzEzNjczNjYwMDAwMDA5NTAyNDY0NTI0MDQ3MTc5Njg2Xyk7fQoJLnN0MntmaWxsOnVybCgjU1ZHSURfMDAwMDAxMTQwNzU2MTAxNzA2MjA3NDYzMDAwMDAwMTMwNzY4NTczMjc1MDMwNTk4NTJfKTt9Cgkuc3Qze2ZpbGw6dXJsKCNTVkdJRF8wMDAwMDAzOTg0Mzk1Mzc4NTIyOTI0Mjg4MDAwMDAwODQwMzMzMzkyMDEyMjcwNTMyOF8pO30KCS5zdDR7ZmlsbDp1cmwoI1NWR0lEXzAwMDAwMDI3NTkyNTEzNTc2NDg3NDgzMzAwMDAwMDE2NzQwODQ2NzM4OTM4NjE5NzkyXyk7fQoJLnN0NXtmaWxsOnVybCgjU1ZHSURfMDAwMDAwNzM2ODI1Njc2NTkxMzUzNzk0NjAwMDAwMDQyOTE4NTk2MTM4MzIzNjcyNDZfKTt9Cgkuc3Q2e2ZpbGw6dXJsKCNTVkdJRF8wMDAwMDA0MzQyOTI4Mzg1Nzk2OTQ0NDE5MDAwMDAwNzg0MjM5Nzg0MTEwMTYxNjAxN18pO30KCS5zdDd7ZmlsbDojRkZGRkZGO30KPC9zdHlsZT4KPGc+Cgk8Zz4KCQk8Zz4KCQkJCgkJCQk8bGluZWFyR3JhZGllbnQgaWQ9IlNWR0lEXzFfIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjE0NC43Mzg4IiB5MT0iMzYxLjU0MjIiIHgyPSIxMTQ1LjM4NjYiIHkyPSIzMDguNTkwNyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAwIDY1MCkiPgoJCQkJPHN0b3AgIG9mZnNldD0iMCIgc3R5bGU9InN0b3AtY29sb3I6I0UwNEY4QSIvPgoJCQkJPHN0b3AgIG9mZnNldD0iMSIgc3R5bGU9InN0b3AtY29sb3I6I0VDOUEyQyIvPgoJCQk8L2xpbmVhckdyYWRpZW50PgoJCQk8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDMxLjkzLDI5OC42N2MtMC42Mi0wLjYtMS4zNi0xLjExLTIuMTktMS41N2MtMi40Mi0xLjMyLTUuNjQtMS45OC05LjctMS45OAoJCQkJYy0xNi4xMiwwLTMwLjQ0LTEuMDYtNDIuOTctMy4xOWMxMS44My0zMi4yLDE3Ljc2LTYxLjkyLDE3Ljc2LTg5LjE1YzAtMTguOTMtNC40NS0zMy44NC0xMy4zMi00NC43NAoJCQkJYy04Ljg5LTEwLjg3LTIwLjg5LTE2LjMyLTM2LjA1LTE2LjMyYy0xMi4zMiwwLTIzLjUsMy4xOS0zMy41NSw5LjU5Yy0xMC4wOCw2LjQtMTgsMTYuMDQtMjMuOCwyOC45NQoJCQkJYy01LjgxLDEyLjg5LTguNywyOC43LTguNyw0Ny40MWMwLDE4LjcsMy42NiwzNS4yMSwxMSw0OS41NGM3LjM0LDE0LjMyLDE4LjIzLDI1Ljk5LDMyLjY3LDM0Ljk3CgkJCQljLTEyLjA2LDI1LjgyLTI1LjMzLDQ3LjI0LTM5Ljc2LDY0LjI4Yy0xNC40NiwxNy4wNi0yNy44MiwyNS41Ny00MC4xNCwyNS41N2MtMTIuMDYsMC0yMS4xMi05LjQtMjcuMTYtMjguMjMKCQkJCWMtNi4wNC0xOC44Mi05LjA2LTQ2LjMzLTkuMDYtODIuNTZjMC0zNC41NSwzLjU1LTczLjE1LDEwLjY2LTExNS43NmMwLjQ3LTMuNzksMC43LTYuNCwwLjctNy44MWMwLTEzLjUxLTcuODEtMjAuMjUtMjMuNDQtMjAuMjUKCQkJCWMtOS40NywwLTE2LjE1LDIuMTktMjAuMDYsNi41N3MtNi45MywxMi43NC05LjA2LDI1LjA0Yy01LjkzLDMzLjE0LTguODcsNzAuNTQtOC44NywxMTIuMjFjMCw0OC43OCw2LjA0LDg3LjQ5LDE4LjEsMTE2LjEyCgkJCQljMTIuMDgsMjguNjcsMzAuNDIsNDIuOTcsNTUuMDUsNDIuOTdjMjMuOTEsMCw0Ny43Ni0xMS42NCw3MS41Ni0zNC45N2MyMy44LTIzLjMzLDQ0LjA4LTUyLjYxLDYwLjktODcuODkKCQkJCWMxMC42MSwyLjQsMjIuMzMsMy43OSwzNS4xNCw0LjIxYzQuOTMsMC4xNywxMC4wMiwwLjE3LDE1LjI5LDAuMDRjNy44MS0wLjIzLDEzLjU1LTIuNzIsMTcuMjMtNy40NQoJCQkJYzMuNjYtNC43NCw1LjUxLTEwLjE3LDUuNTEtMTYuMzJDNDM1LjY3LDMwNC4xMiw0MzQuNDIsMzAxLjA0LDQzMS45MywyOTguNjd6IE0zMzcuNjUsMjc2LjY2CgkJCQljLTE0LjkxLTExLjEzLTIyLjM4LTI4LjI5LTIyLjM4LTUxLjVjMC0xMy43MiwyLjAyLTI0LjMzLDYuMDQtMzEuNzhjNC4wNC03LjQ1LDkuMjUtMTEuMTksMTUuNjMtMTEuMTkKCQkJCWM0LjQ5LDAsOC4wNiwyLjMsMTAuNjYsNi45M2MyLjYsNC42MiwzLjkxLDExLjg5LDMuOTEsMjEuODNDMzUxLjUsMjI4LjI0LDM0Ni44NywyNTAuMTUsMzM3LjY1LDI3Ni42NnoiLz4KCQkJCgkJCQk8bGluZWFyR3JhZGllbnQgaWQ9IlNWR0lEXzAwMDAwMDg5NTUyODM1Mzg3NjQwNjA5ODkwMDAwMDA0ODExOTA0NjIyNjQ2MDYyNzMyXyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIxMzcuMjUzNSIgeTE9IjMwOC44OTY2IiB4Mj0iMTEzNy45MDE0IiB5Mj0iMjU1Ljk0NTEiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMCA2NTApIj4KCQkJCTxzdG9wICBvZmZzZXQ9IjAiIHN0eWxlPSJzdG9wLWNvbG9yOiNFMDRGOEEiLz4KCQkJCTxzdG9wICBvZmZzZXQ9IjEiIHN0eWxlPSJzdG9wLWNvbG9yOiNFQzlBMkMiLz4KCQkJPC9saW5lYXJHcmFkaWVudD4KCQkJPHBhdGggc3R5bGU9ImZpbGw6dXJsKCNTVkdJRF8wMDAwMDA4OTU1MjgzNTM4NzY0MDYwOTg5MDAwMDAwNDgxMTkwNDYyMjY0NjA2MjczMl8pOyIgZD0iTTc0Ny45LDM3My42MQoJCQkJYy0xLjc3LTIuODUtNC4yMS00LjI3LTcuMjgtNC4yN2MtMy41NSwwLTYuNzQsMS42Ni05LjU5LDQuOTZjLTQuMSw0LjkxLTcuODcsOS4yNy0xMS4zMiwxMy4xM2MtNS43Nyw2LjQ1LTEwLjcsMTEuNDctMTQuNzgsMTUuMQoJCQkJYy02LjUxLDUuODEtMTIuMTMsOC43LTE2Ljg3LDguN2MtNi44NywwLTEwLjMtNC43Mi0xMC4zLTE0LjE5YzAtNi42MiwyLjI2LTIwLjI1LDYuNzYtNDAuODRjNS4yMS0yNC4xNiw3LjgxLTQwLjg0LDcuODEtNTAuMDkKCQkJCWMwLTE1LjM4LTcuNy0yMy4wOC0yMy4wOC0yMy4wOGMtNy44MSwwLTE2LjQsMi43NC0yNS43NCw4LjE3Yy05LjM2LDUuNDUtMTguODksMTQuNTctMjguNTksMjcuMzUKCQkJCWMtOS43MiwxMi43OC0xOC40OCwyOS4zNi0yNi4yOSw0OS43MWMwLjk0LTEwLjQsMi4xMy0xOS4xNywzLjU1LTI2LjI3YzEuNDMtNy4xLDMuMzItMTUuMzgsNS43LTI0Ljg1CgkJCQljMy41NS0xMy4yNyw1LjMyLTIxLjc4LDUuMzItMjUuNTdjMC0zLjMyLTAuOTYtNS41Ny0yLjg1LTYuNzZjLTEuODktMS4xNy01LjIxLTEuNzctOS45NS0xLjc3Yy04Ljc2LDAtMTUuNTcsMS42LTIwLjQyLDQuNzkKCQkJCWMtNC44NSwzLjIxLTguMzQsOC4yMy0xMC40NywxNS4xYy04LjM0LDI2LjM4LTEyLjcsNTQuMTYtMTMuMSw4My4zNGMtMC4wMiwxLjQ1LTAuMDQsMi45MS0wLjA0LDQuMzhjMCwxNiwxLjA4LDI4LjY3LDMuMjYsMzcuOTcKCQkJCWMwLjY0LDIuNzksMS40LDUuMywyLjI1LDcuNDljMy42Niw5LjQ3LDkuOTgsMTQuMTksMTguOTgsMTQuMTljNy4zNCwwLDEyLjc0LTEuOTQsMTYuMTctNS44NWMzLjQyLTMuOTEsNi40NC0xMS40Miw5LjA0LTIyLjU1CgkJCQljMy41Ny0xNC40NCw4LjIzLTI4LjE4LDE0LjA0LTQxLjJjNS43OS0xMy4wMiwxMS40Ny0yMy41LDE3LjA0LTMxLjQyYzUuNTctNy45Myw5Ljg5LTExLjkxLDEyLjk2LTExLjkxCgkJCQljMi42LDAsMy45MSwxLjkxLDMuOTEsNS42OGMwLDUuNy0xLjU1LDE1Ljk4LTQuNjIsMzAuOTFjLTMuMzIsMTcuMDQtNC45NiwyOC44Ny00Ljk2LDM1LjVjMCwxMy4yNywzLjA4LDIzLjM4LDkuMjMsMzAuMzYKCQkJCWM2LjE1LDYuOTgsMTUuMjcsMTAuNDcsMjcuMzUsMTAuNDdjMTQuOTEsMCwyOC4yMy00LjAyLDM5Ljk1LTEyLjA2YzUuMTctMy41NywxMC4xOS03LjU1LDE1LjA4LTExLjk2CgkJCQljNi4xNS01LjYyLDEyLjA4LTExLjkzLDE3Ljc4LTE4Ljk1YzQuNDktNS4xOSw2Ljc0LTEyLjUzLDYuNzQtMjJDNzUwLjU2LDM4MC4zNCw3NDkuNjcsMzc2LjQ0LDc0Ny45LDM3My42MXoiLz4KCQkJCgkJCQk8bGluZWFyR3JhZGllbnQgaWQ9IlNWR0lEXzAwMDAwMDM2OTY2Mjg4NDUyMDI4MTAzOTgwMDAwMDA2MTQ5NTUyMTE1MTU0NzIxOTQ4XyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIxMzkuNzQyOCIgeTE9IjM1NS45MzgyIiB4Mj0iMTE0MC4zOTA3IiB5Mj0iMzAyLjk4NjciIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMCA2NTApIj4KCQkJCTxzdG9wICBvZmZzZXQ9IjAiIHN0eWxlPSJzdG9wLWNvbG9yOiNFMDRGOEEiLz4KCQkJCTxzdG9wICBvZmZzZXQ9IjEiIHN0eWxlPSJzdG9wLWNvbG9yOiNFQzlBMkMiLz4KCQkJPC9saW5lYXJHcmFkaWVudD4KCQkJPHBhdGggc3R5bGU9ImZpbGw6dXJsKCNTVkdJRF8wMDAwMDAzNjk2NjI4ODQ1MjAyODEwMzk4MDAwMDAwNjE0OTU1MjExNTE1NDcyMTk0OF8pOyIgZD0iTTgzMy40OCwzNzMuNjEKCQkJCWMtMS43Ny0yLjg1LTQuMjEtNC4yNy03LjI4LTQuMjdjLTMuNTUsMC02Ljc2LDEuNjYtOS41OSw0Ljk2Yy0yLjEzLDIuNTctNC4xNyw0Ljk4LTYuMTMsNy4yN2MtNy44NSw5LjEzLTE0LjMyLDE2LjA4LTE5LjQ0LDIwLjgKCQkJCWMtNi40LDUuOTMtMTEuNzIsOC44Ny0xNS45OCw4Ljg3Yy00LjQ5LDAtNy43LTEuODktOS41OS01LjY4Yy0xLjg5LTMuNzctMi44My05LjIzLTIuODMtMTYuMzJjMC05LjI1LDIuODMtMjYuODcsOC41MS01Mi45MgoJCQkJYzQuNDktMjEuNTMsNi43Ni0zNS4yNyw2Ljc2LTQxLjJjMC04LjA0LTYuMDQtMTIuMDgtMTguMTItMTIuMDhjLTcuODEsMC0xMy40OSwxLjQzLTE3LjA0LDQuMjdjLTMuNTUsMi44NS02LjQsOC4wNi04LjUzLDE1LjYzCgkJCQljLTQuMjcsMTQuNDQtNy43NiwzMC4wMS0xMC40Nyw0Ni43MWMtMi40NSwxNS4wNi0zLjc5LDI3LjY1LTQuMDQsMzcuOGMtMC4wMiwxLjA5LTAuMDQsMi4xNy0wLjA0LDMuMjEKCQkJCWMwLDE0LjA0LDIuMTMsMjUuOTEsNi4zOCwzNS42M2MxLjE5LDIuNzQsMi41Nyw1LjMsNC4xLDcuN2M2Ljk4LDEwLjg5LDE4LjE1LDE2LjMyLDMzLjU1LDE2LjMyYzE3Ljg3LDAsMzYuOTEtMTEuMDIsNTcuMTYtMzMuMQoJCQkJYzIuODEtMy4wOCw1LjY2LTYuMzYsOC41My05Ljg3YzQuNTEtNS4xOSw2Ljc2LTEyLjUzLDYuNzYtMjJDODM2LjE0LDM4MC4zNCw4MzUuMjYsMzc2LjQ0LDgzMy40OCwzNzMuNjF6IE03OTIuMTIsMjEwLjYxCgkJCQljLTUuNDUtNC4yNy0xMi41Ny02LjQtMjEuMzItNi40Yy05LjcsMC0xNy44MiwyLjc3LTI0LjMzLDguMzRjLTYuNTEsNS41Ny05Ljc2LDEyLjQ5LTkuNzYsMjAuNzhjMCw4LjI4LDIuNDksMTQuNzQsNy40NSwxOS4zNAoJCQkJYzQuOTgsNC42MiwxMi40NCw2LjkzLDIyLjM4LDYuOTNjMTAuMTcsMCwxOC4zNC0yLjc3LDI0LjUtOC4zNGM2LjE1LTUuNTUsOS4yMy0xMy4wOCw5LjIzLTIyLjU1CgkJCQlDODAwLjI3LDIyMC45LDc5Ny41NSwyMTQuODYsNzkyLjEyLDIxMC42MXoiLz4KCQkJCgkJCQk8bGluZWFyR3JhZGllbnQgaWQ9IlNWR0lEXzAwMDAwMTY5NTQ0MDU3OTMxOTgyMTQwNjIwMDAwMDAzNjMwOTA4MjUzMDQ2NjA0NDcxXyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIxNDIuNDM4MiIgeTE9IjQwNi44NzQ3IiB4Mj0iMTE0My4wODYxIiB5Mj0iMzUzLjkyMzIiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMSAwIDAgLTEgMCA2NTApIj4KCQkJCTxzdG9wICBvZmZzZXQ9IjAiIHN0eWxlPSJzdG9wLWNvbG9yOiNFMDRGOEEiLz4KCQkJCTxzdG9wICBvZmZzZXQ9IjEiIHN0eWxlPSJzdG9wLWNvbG9yOiNFQzlBMkMiLz4KCQkJPC9saW5lYXJHcmFkaWVudD4KCQkJPHBhdGggc3R5bGU9ImZpbGw6dXJsKCNTVkdJRF8wMDAwMDE2OTU0NDA1NzkzMTk4MjE0MDYyMDAwMDAwMzYzMDkwODI1MzA0NjYwNDQ3MV8pOyIgZD0iTTk0Ny40NywzNzMuNjEKCQkJCWMtMS43Ny0yLjg1LTQuMjEtNC4yNy03LjI4LTQuMjdjLTMuNTUsMC02Ljc0LDEuNjYtOS41OSw0Ljk2Yy0yLjExLDIuNTMtNC4xNyw0LjkxLTYuMTUsNy4xMwoJCQkJYy04LjYyLDkuNzktMTUuOTMsMTYuOTUtMjEuODksMjEuNDZjLTcuMzQsNS41Ny0xNS44Nyw4LjM0LTI1LjU3LDguMzRjLTYuMTcsMC0xMS4xMy0yLjA2LTE0LjkzLTYuMjEKCQkJCWMtMy43OS00LjEzLTYuNTEtMTEuMDYtOC4xNy0yMC43OGMyNi43Ni0zNi40NCw0Ny45NS03My4yNiw2My41OC0xMTAuNDRjMTUuNjMtMzcuMTYsMjMuNDQtNjkuODQsMjMuNDQtOTguMDIKCQkJCWMwLTE5LjY1LTMuMjYtMzQuNzQtOS43OC00NS4yN2MtNi41MS0xMC41My0xNS40NC0xNS44LTI2LjgtMTUuOGMtMjAuMTQsMC0zNy40OCwxMy4yNS01Mi4wMywzOS43NgoJCQkJYy0xNC41NywyNi41MS0yNS41Nyw1OC4yNC0zMy4wMyw5NS4xN2MtNy40NSwzNi45My0xMS4xOSw2OS44NC0xMS4xOSw5OC43NGMwLDExLjk1LDAuNzksMjMsMi40LDMzLjE4CgkJCQljMi4wNiwxMy40LDUuNTMsMjUuMjcsMTAuMzgsMzUuNjNjMC43NCwxLjYsMS41MSwzLjE3LDIuMzIsNC43YzEwLjA2LDE4Ljk1LDI1LjUsMjguNCw0Ni4zNSwyOC40CgkJCQljMTUuMTQsMCwyOC42OS00LjAyLDQwLjY1LTEyLjA2YzguNjgtNS44NSwxNi44OS0xMi44NywyNC42NS0yMS4xYzIuOTEtMy4wOSw1Ljc3LTYuMzgsOC41Ny05LjgxYzQuNDktNS4xOSw2Ljc0LTEyLjUzLDYuNzQtMjIKCQkJCUM5NTAuMTMsMzgwLjM0LDk0OS4yNCwzNzYuNDQsOTQ3LjQ3LDM3My42MXogTTg1OS45MiwyNTIuNTFjNS4yMS0yOS4xMiwxMS4yNS01My41LDE4LjEyLTczLjE1CgkJCQljNi44Ny0xOS42NiwxMy4wMi0yOS40OCwxOC40OC0yOS40OGM2LjYyLDAsOS45NSw5LDkuOTUsMjYuOTljMCwyMi45Ny00Ljk4LDQ4LjM1LTE0LjkzLDc2LjE3cy0yMy4zMyw1NS41OC00MC4xMiw4My4yOAoJCQkJQzg1MS44OCwzMDkuNTUsODU0LjcxLDI4MS42Miw4NTkuOTIsMjUyLjUxeiIvPgoJCQkKCQkJCTxsaW5lYXJHcmFkaWVudCBpZD0iU1ZHSURfMDAwMDAwOTYwMzk0OTc1NDgzNzkyMzk0NjAwMDAwMTc2ODAwODc0NTg3MzgyMjU1NjVfIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjE0Mi43NTY1IiB5MT0iNDEyLjg4ODMiIHgyPSIxMTQzLjQwNDMiIHkyPSIzNTkuOTM2OCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAwIDY1MCkiPgoJCQkJPHN0b3AgIG9mZnNldD0iMCIgc3R5bGU9InN0b3AtY29sb3I6I0UwNEY4QSIvPgoJCQkJPHN0b3AgIG9mZnNldD0iMSIgc3R5bGU9InN0b3AtY29sb3I6I0VDOUEyQyIvPgoJCQk8L2xpbmVhckdyYWRpZW50PgoJCQk8cGF0aCBzdHlsZT0iZmlsbDp1cmwoI1NWR0lEXzAwMDAwMDk2MDM5NDk3NTQ4Mzc5MjM5NDYwMDAwMDE3NjgwMDg3NDU4NzM4MjI1NTY1Xyk7IiBkPSJNMTA2MS40NSwzNzMuNjEKCQkJCWMtMS43Ny0yLjg1LTQuMjEtNC4yNy03LjI3LTQuMjdjLTMuNTcsMC02Ljc2LDEuNjYtOS42MSw0Ljk2Yy03LjE3LDguNTMtMTMuNTUsMTUuNDctMTkuMTIsMjAuODMKCQkJCWMtMy4yNSwzLjEzLTYuMjMsNS43Mi04LjkzLDcuNzZjLTcuMzQsNS41Ny0xNS44Nyw4LjM0LTI1LjU3LDguMzRjLTYuMTUsMC0xMS4xMy0yLjA2LTE0LjkxLTYuMjEKCQkJCWMtMy43OS00LjEzLTYuNTMtMTEuMDYtOC4xNy0yMC43OGMyNi43NC0zNi40NCw0Ny45My03My4yNiw2My41Ni0xMTAuNDRjMTUuNjMtMzcuMTYsMjMuNDQtNjkuODQsMjMuNDQtOTguMDIKCQkJCWMwLTE5LjY1LTMuMjUtMzQuNzQtOS43Ni00NS4yN2MtNi41My0xMC41My0xNS40Ni0xNS44LTI2LjgyLTE1LjhjLTIwLjEyLDAtMzcuNDYsMTMuMjUtNTIuMDMsMzkuNzYKCQkJCWMtMTQuNTUsMjYuNTEtMjUuNTcsNTguMjQtMzMuMDMsOTUuMTdjLTcuNDUsMzYuOTMtMTEuMTksNjkuODQtMTEuMTksOTguNzRjMCwxMS44OSwwLjc5LDIyLjkxLDIuMzgsMzMuMDQKCQkJCWMyLjA2LDEzLjQyLDUuNTMsMjUuMzMsMTAuMzgsMzUuNzFjMC43NCwxLjYyLDEuNTMsMy4yMSwyLjM0LDQuNzZjMTAuMDYsMTguOTUsMjUuNTEsMjguNCw0Ni4zNSwyOC40CgkJCQljMTUuMTUsMCwyOC43LTQuMDIsNDAuNjUtMTIuMDZjMy43OS0yLjU3LDcuNTEtNS4zNCwxMS4xMi04LjM2YzcuNzgtNi40NSwxNS4xNC0xMy45NywyMi4xLTIyLjU1YzQuNDktNS4xOSw2Ljc0LTEyLjUzLDYuNzQtMjIKCQkJCUMxMDY0LjExLDM4MC4zNCwxMDYzLjIzLDM3Ni40NCwxMDYxLjQ1LDM3My42MXogTTk3My45MywyNTIuNTFjNS4xOS0yOS4xMiwxMS4yMy01My41LDE4LjEtNzMuMTUKCQkJCWM2Ljg3LTE5LjY2LDEzLjAyLTI5LjQ4LDE4LjQ4LTI5LjQ4YzYuNjIsMCw5Ljk1LDksOS45NSwyNi45OWMwLDIyLjk3LTQuOTgsNDguMzUtMTQuOTEsNzYuMTcKCQkJCWMtOS45NSwyNy44Mi0yMy4zMyw1NS41OC00MC4xNCw4My4yOEM5NjUuODcsMzA5LjU1LDk2OC43MiwyODEuNjIsOTczLjkzLDI1Mi41MXoiLz4KCQkJCgkJCQk8bGluZWFyR3JhZGllbnQgaWQ9IlNWR0lEXzAwMDAwMDQ0ODY0NDU5ODE4NDk5ODM0NDgwMDAwMDE0NzUwODc5MzcyMTAyNDYzMzk3XyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIxMzguNTg5NCIgeTE9IjMzNC4xNDA1IiB4Mj0iMTEzOS4yMzcyIiB5Mj0iMjgxLjE4OSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAwIDY1MCkiPgoJCQkJPHN0b3AgIG9mZnNldD0iMCIgc3R5bGU9InN0b3AtY29sb3I6I0UwNEY4QSIvPgoJCQkJPHN0b3AgIG9mZnNldD0iMSIgc3R5bGU9InN0b3AtY29sb3I6I0VDOUEyQyIvPgoJCQk8L2xpbmVhckdyYWRpZW50PgoJCQk8cGF0aCBzdHlsZT0iZmlsbDp1cmwoI1NWR0lEXzAwMDAwMDQ0ODY0NDU5ODE4NDk5ODM0NDgwMDAwMDE0NzUwODc5MzcyMTAyNDYzMzk3Xyk7IiBkPSJNMTIwMi41MiwzODIuNzQKCQkJCWMtNC40Myw1Ljc2LTEwLjMsMTIuNTctMTcuMjMsMTguN2MwLDAtMTEuMTIsOS44NS0yMS4zOCw5Ljc5Yy0yLjMsMC00LjA5LTAuOTEtNC4wOS0wLjkxYy0xLjA2LTAuNTMtMi0xLjI4LTIuODMtMi4yOAoJCQkJYy0xLjc3LTIuMTMtMi42Ni00Ljk2LTIuNjYtOC41M2MwLTEwLjY0LDIuNzItMjkuNTksOC4xNy01Ni44YzQuMDYtMjAuMjcsNy40NC0zMC4xMSw1LjMyLTM1LjUyCgkJCQljLTcuMjYtMTguNTctMzcuNDUtMjcuMDctNjAuNy0yMy43N2MtMTUuNTgsMi4yMS0yNi40Myw5LjMtMzMuNTgsMTQuMzZjLTE0LjU3LDEwLjMtMjYuMjMsMjMuOTEtMzQuOTksNDAuODQKCQkJCXMtMTMuMTMsMzQuODgtMTMuMTMsNTMuOGMwLDAuOTEsMC4wMiwxLjgxLDAuMDYsMi43MmMwLjM2LDEzLjc4LDMuNjIsMjUuMzYsOS44MSwzNC43NGMxLDEuNTUsMi4wOSwzLjAyLDMuMjYsNC40NQoJCQkJYzguNzYsMTAuNjQsMjAuNDgsMTUuOTcsMzUuMTYsMTUuOTdjMTguNywwLDM0LjIxLTkuOTUsNDYuNTItMjkuODJjMy4zMiw5LjIzLDguMjMsMTYuNTEsMTQuNzQsMjEuODMKCQkJCWM2LjUxLDUuMzIsMTMuNjgsNy45OCwyMS40OSw3Ljk4YzEwLjE3LDAsMjAuNy00LjEzLDMxLjYxLTEyLjQyYzguNC02LjM4LDE2LjQyLTEzLjg5LDI0LjEtMjIuNTMKCQkJCUMxMjA1LjYyLDQwMy4xNiwxMjAzLjI4LDM5MS4yMywxMjAyLjUyLDM4Mi43NHogTTExMTEuNzEsMzgwLjdjLTIuMTMsMTAuMTktNS41NywxOC4zNC0xMC4zLDI0LjUxCgkJCQljLTQuNzQsNi4xNS05LjcsOS4yMy0xNC45MSw5LjIzYy01LjIxLDAtOS4zLTEuNzctMTIuMjUtNS4zMmMtMi45Ni0zLjU3LTQuNDUtOS00LjQ1LTE2LjM0YzAtMTMuMjcsMi40OS0yNS44LDcuNDUtMzcuNjUKCQkJCWM0Ljk4LTExLjgzLDExLjY2LTIxLjM2LDIwLjA4LTI4LjU5YzguNC03LjIxLDE3LjU3LTEwLjk1LDI3LjUxLTExLjE3TDExMTEuNzEsMzgwLjd6Ii8+CgkJCQoJCQkJPGxpbmVhckdyYWRpZW50IGlkPSJTVkdJRF8wMDAwMDAxMzg4NDE0OTU1MTMwMDcxMDUxMDAwMDAwOTQ1NjEzMzk1NjE1Mjg5NDYxMl8iIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMTMyLjA3ODgiIHkxPSIyOTkuOTE3OCIgeDI9IjExMzIuNzI2NyIgeTI9IjI0Ni45NjY0IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIDAgNjUwKSI+CgkJCQk8c3RvcCAgb2Zmc2V0PSIwIiBzdHlsZT0ic3RvcC1jb2xvcjojRTA0RjhBIi8+CgkJCQk8c3RvcCAgb2Zmc2V0PSIxIiBzdHlsZT0ic3RvcC1jb2xvcjojRUM5QTJDIi8+CgkJCTwvbGluZWFyR3JhZGllbnQ+CgkJCTxwYXRoIHN0eWxlPSJmaWxsOnVybCgjU1ZHSURfMDAwMDAwMTM4ODQxNDk1NTEzMDA3MTA1MTAwMDAwMDk0NTYxMzM5NTYxNTI4OTQ2MTJfKTsiIGQ9Ik01NDkuMjksMzgyLjc0CgkJCQljLTQuNDMsNS43Ni0xMC4zLDEyLjU3LTE3LjIzLDE4LjdjMCwwLTExLjEyLDkuODUtMjEuMzgsOS43OWMtMi4zLDAtNC4wOS0wLjkxLTQuMDktMC45MWMtMS4wNi0wLjUzLTItMS4yOC0yLjgzLTIuMjgKCQkJCWMtMS43Ny0yLjEzLTIuNjYtNC45Ni0yLjY2LTguNTNjMC0xMC42NCwyLjcyLTI5LjU5LDguMTctNTYuOGM0LjA2LTIwLjI3LDcuNDQtMzAuMTEsNS4zMi0zNS41MgoJCQkJYy03LjI2LTE4LjU3LTM3LjQ1LTI3LjA3LTYwLjctMjMuNzdjLTE1LjU4LDIuMjEtMjYuNDMsOS4zLTMzLjU4LDE0LjM2Yy0xNC41NywxMC4zLTI2LjIzLDIzLjkxLTM0Ljk5LDQwLjg0CgkJCQljLTguNzYsMTYuOTMtMTMuMTMsMzQuODgtMTMuMTMsNTMuOGMwLDAuOTEsMC4wMiwxLjgxLDAuMDYsMi43MmMwLjM2LDEzLjc4LDMuNjIsMjUuMzYsOS44MSwzNC43NGMxLDEuNTUsMi4wOSwzLjAyLDMuMjYsNC40NQoJCQkJYzguNzYsMTAuNjQsMjAuNDgsMTUuOTcsMzUuMTYsMTUuOTdjMTguNywwLDM0LjIxLTkuOTUsNDYuNTItMjkuODJjMy4zMiw5LjIzLDguMjMsMTYuNTEsMTQuNzQsMjEuODMKCQkJCWM2LjUxLDUuMzIsMTMuNjgsNy45OCwyMS40OSw3Ljk4YzEwLjE3LDAsMjAuNy00LjEzLDMxLjYxLTEyLjQyYzguNC02LjM4LDE2LjQyLTEzLjg5LDI0LjEtMjIuNTMKCQkJCUM1NTIuMzksNDAzLjE2LDU1MC4wNSwzOTEuMjMsNTQ5LjI5LDM4Mi43NHogTTQ1OC40OCwzODAuN2MtMi4xMywxMC4xOS01LjU3LDE4LjM0LTEwLjMsMjQuNTFjLTQuNzQsNi4xNS05LjcsOS4yMy0xNC45MSw5LjIzCgkJCQlzLTkuMy0xLjc3LTEyLjI1LTUuMzJjLTIuOTYtMy41Ny00LjQ1LTktNC40NS0xNi4zNGMwLTEzLjI3LDIuNDktMjUuOCw3LjQ1LTM3LjY1YzQuOTgtMTEuODMsMTEuNjYtMjEuMzYsMjAuMDgtMjguNTkKCQkJCWM4LjQtNy4yMSwxNy41Ny0xMC45NSwyNy41MS0xMS4xN0w0NTguNDgsMzgwLjd6Ii8+CgkJPC9nPgoJPC9nPgoJPGc+CgkJPHBhdGggY2xhc3M9InN0NyIgZD0iTTEyODguNTksMjMwLjcyYzMuNjQtMi45MSw3LjktNS4xLDEyLjc2LTYuNTZjNC44Ni0xLjQ2LDkuOTktMi4xOSwxNS4zMi0yLjE5YzguNTMsMCwxNS44OCwxLjE1LDIyLjA5LDMuNDUKCQkJYzYuMiwyLjMzLDExLjg0LDUuNDMsMTYuOTcsOS4zYzUuMSwzLjksOS43Nyw4LjQ2LDE0LjAzLDEzLjY4YzQuMjUsNS4yNCw4LjY5LDEwLjksMTMuMzIsMTYuOTdsMzcuMjItMzIuODMKCQkJYy03LjMxLTguNTEtMTQuNTktMTYuMDUtMjEuOS0yMi42M2MtNy4yOC02LjU2LTE1LjA5LTEyLjE1LTIzLjMzLTE2Ljc4Yy04LjI3LTQuNjEtMTcuMTEtOC4xMy0yNi40Ni0xMC41NwoJCQljLTkuMzgtMi40Mi0xOS44OC0zLjY0LTMxLjU2LTMuNjRjLTExLjkxLDAtMjMuNTksMS42OS0zNS4wNCw1LjFjLTExLjQyLDMuNDEtMjEuNjQsOC4zOS0zMC42NCwxNC45NwoJCQljLTksNi41Ni0xNi4yNCwxNC44My0yMS43MSwyNC43OWMtNS40NSw5Ljk5LTguMiwyMS41Mi04LjIsMzQuNjZjMCwxMy42MywzLjQxLDI1LjA1LDEwLjIyLDM0LjMxCgkJCWM2LjgxLDkuMjMsMTUuMzcsMTcuMDgsMjUuNzEsMjMuNTJjMTAuMzQsNi40NiwyMS41NSwxMS45MSwzMy41OCwxNi40MmMxMi4wMyw0LjQ5LDIzLjIyLDguOTMsMzMuNTUsMTMuMzIKCQkJYzEwLjM0LDQuMzcsMTguOTIsOS4wNSwyNS43MywxNC4wM2M2Ljc5LDUsMTAuMjIsMTEuMjYsMTAuMjIsMTguOGMwLDUuNTktMS40MSwxMC4zOS00LjIxLDE0LjRjLTIuOCw0LjAyLTYuMjcsNy4zNS0xMC4zOSwxMC4wMwoJCQljLTQuMTYsMi42OC04Ljc2LDQuNy0xMy44Niw2LjAyYy01LjEyLDEuMzQtMTAuMSwyLjAyLTE0Ljk3LDIuMDJjLTksMC0xNy4wOC0xLjI5LTI0LjI3LTMuODNjLTEuMzYtMC40OS0xMC41NS0zLjY5LTE5LjUtMTAuMjIKCQkJYy0xOS42OS0xNC4zOC0yNi45Ny0zNi42My0yOS4xMS00NC4xM2MtMTAuOTcsOS40LTIxLjkyLDE4LjgtMzIuOSwyOC4yYzEuMzYsOS44NSw1LjQsMjYuNjcsMTguNTksNDAuNzQKCQkJYzguMjcsOC44MSwxNy41MywxMy42LDI0LjYzLDE3LjUxYzguNjUsNC43NSwxNy45Myw4LjI3LDI3LjkyLDEwLjU3YzkuOTYsMi4zLDIxLjE1LDMuNDgsMzMuNTUsMy40OAoJCQljMTAuNzEsMCwyMS45LTEuNzIsMzMuNTgtNS4xMmMxMS42OC0zLjM4LDIyLjM3LTguMzksMzIuMS0xNC45NGM5LjczLTYuNTgsMTcuNzYtMTQuNjYsMjQuMDgtMjQuMjcKCQkJYzYuMzItOS41OSw5LjQ3LTIwLjYxLDkuNDctMzMuMDFjMC0xMy42MS0zLjQxLTI1LjA1LTEwLjItMzQuMjhjLTYuODEtOS4yNi0xNS4zOS0xNy4wNC0yNS43My0yMy4zNgoJCQljLTEwLjM0LTYuMzItMjEuNDgtMTEuNjgtMzMuMzctMTYuMDVjLTExLjk0LTQuMzctMjMuMDUtOC42OS0zMy4zOS0xMi45NWMtMTAuMzQtNC4yNS0xOC45Mi04Ljk1LTI1LjczLTE0LjA1CgkJCWMtNi44MS01LjEtMTAuMi0xMS40Mi0xMC4yLTE4Ljk2YzAtNS41OSwxLjA4LTEwLjYsMy4yNy0xNC45N0MxMjgyLjAxLDIzNy4zLDEyODQuOTUsMjMzLjY1LDEyODguNTksMjMwLjcyeiIvPgoJCTxwYXRoIGNsYXNzPSJzdDciIGQ9Ik0xNzg3LjY1LDI1NC44bC01My4yNywxMTkuM2wtNTMuNjItMTE5LjNoLTM1LjM5aC0yMC44aC00NS4yM2wtNzguMDYsNjIuMzljMCwwLTAuODItMTQ2LjU1LTEuMTUtMTU0LjE3CgkJCWMtOC43Miw3LjYxLTE3LjQxLDE1LjIzLTI2LjEzLDIyLjg0Yy04LjY3LDcuNTktMTcuMzIsMTUuMTYtMjUuOTksMjIuNzJ2MzAuNTJ2MjA3Ljk1aDUzLjI3VjM3NC4xbDEyLjM4LTEwLjIybDYzLjQ5LDgzLjE4aDYxLjI4CgkJCWwtODUuNzItMTE1LjI4bDc3LjQ3LTY0LjM2bDc1LjczLDE3MC41MmwtNDEuNTksOTQuODZoNTUuNDVsMTIzLjMxLTI3OEwxNzg3LjY1LDI1NC44TDE3ODcuNjUsMjU0Ljh6Ii8+Cgk8L2c+CjwvZz4KPC9zdmc+Cg==" alt="VanillaSky"></div><div class="group"><button class="round" type="button" data-go="conversation" aria-label="New session"><svg data-icon="plus"></svg></button><button class="pill" type="button" data-go="history-demo"><svg data-icon="history"></svg><span class="nav-label">History</span></button></div><div class="group"><button class="round" type="button" id="voice-demo" aria-label="Turn voice off" aria-pressed="true"><svg data-icon="sound"></svg></button><button class="round" type="button" data-go="settings-demo" aria-label="Settings"><svg data-icon="settings"></svg></button></div></header></div>
90
90
  <div class="surface-grid"><div class="surface" style="background:var(--vs-media-ground)"><strong>Canvas</strong><code>--vs-media-ground</code></div><div class="surface" style="background:var(--vs-media-glass)"><strong>Glass</strong><code>--vs-media-glass</code></div><div class="surface" style="background:var(--vs-media-hover)"><strong>Hover</strong><code>--vs-media-hover</code></div><div class="surface" style="background:var(--vs-media-selected)"><strong>Selected</strong><code>--vs-media-selected</code></div></div></section>
91
+ <p class="doc-note">The default welcome film is <a href="https://www.pexels.com/video/timelapse-of-clouds-11335959/">Timelapse of Clouds by Dmitry Marchenkov</a>. A poster appears while the muted cloud film loads; custom host welcome searches remain supported.</p>
91
92
  <section class="doc-section" id="panels"><div class="section-heading"><span class="number">02</span><h2>A place for the details</h2></div><p class="section-copy">Settings and history share the same glass surface. Switches describe the current experience; visuals and style apply to the next question. Try the controls below.</p><div class="doc-grid">
92
- <div class="doc-preview popover-demo" id="settings-demo"><div class="sheet-popover settings"><header class="popover-heading"><h2>Settings</h2><button class="round" type="button" data-go="panels" aria-label="Back to section heading"><svg data-icon="close"></svg></button></header><fieldset class="playback-options"><legend>Watching</legend><label class="switch-row"><span><strong>Subtitles</strong><small>Read along with the answer</small></span><input type="checkbox" role="switch" checked id="show-captions"></label><label class="switch-row"><span><strong>Keep controls visible</strong><small>Keep the input bar on screen</small></span><input type="checkbox" role="switch"></label></fieldset><fieldset class="visual-options"><legend>Video creation</legend><label class="choice-row"><input type="radio" name="visuals" checked><span><strong>Templates only</strong><small>Animated text, diagrams and charts</small></span></label><label class="choice-row"><input type="radio" name="visuals"><span><strong>Some AI video</strong><small>Animation with one AI-generated scene</small></span></label><label class="choice-row"><input type="radio" name="visuals"><span><strong>Full AI video</strong><small>AI-generated footage · billed per clip</small></span></label></fieldset><fieldset class="style-options"><legend>Video style</legend><label><input type="radio" name="style" checked><span>Documentary</span></label><label><input type="radio" name="style"><span>Illustrated</span></label><label><input type="radio" name="style"><span>Blueprint</span></label></fieldset><p class="settings-note">Creation and style changes apply to your next question.</p>
93
+ <div class="doc-preview popover-demo" id="settings-demo"><div class="sheet-popover settings"><header class="popover-heading"><h2>Settings</h2><button class="round" type="button" data-go="panels" aria-label="Back to section heading"><svg data-icon="close"></svg></button></header><fieldset class="playback-options"><legend>Watching</legend><label class="switch-row"><span><strong>Subtitles</strong><small>Read along with the answer</small></span><input type="checkbox" role="switch" checked id="show-captions"></label><label class="switch-row"><span><strong>Keep controls visible</strong><small>Keep the input bar on screen</small></span><input type="checkbox" role="switch"></label></fieldset><fieldset class="visual-options"><legend>Video creation</legend><label class="choice-row"><input type="radio" name="visuals" checked><span><strong>Templates only</strong><small>Animated text, diagrams and charts</small></span></label><label class="choice-row"><input type="radio" name="visuals"><span><strong>Full AI video</strong><small>AI-generated footage · billed per clip</small></span></label></fieldset><fieldset class="style-options"><legend>Video style</legend><label><input type="radio" name="style" checked><span>Documentary</span></label><label><input type="radio" name="style"><span>Illustrated</span></label><label><input type="radio" name="style"><span>Blueprint</span></label></fieldset><p class="settings-note">Creation and style changes apply to your next question.</p>
93
94
  <nav class="developer-links" aria-label="Build with VanillaSky">
94
95
  <p class="section-label">Build with VanillaSky</p>
95
96
  <a href="https://github.com/VanillaSkyAi/video/blob/main/docs/getting-started.md" target="_blank" rel="noopener noreferrer">Docs<span aria-hidden="true">↗</span></a>
@@ -97,7 +98,7 @@ body { background: #080b16; }
97
98
  <div id="about-demo" class="developer-about" role="region" aria-label="About VanillaSky" hidden><p>VanillaSky is an open-source SDK for conversations that answer in video. Developers connect their own AI providers through their application server.</p></div>
98
99
  <a href="https://github.com/VanillaSkyAi/video" target="_blank" rel="noopener noreferrer">GitHub<span aria-hidden="true">↗</span></a>
99
100
  </nav></div></div>
100
- <div class="doc-stack"><div class="doc-preview popover-demo" id="history-demo"><nav class="sheet-popover history" aria-label="Example session history"><div class="popover-heading"><h2>History</h2><button class="round" type="button" data-go="panels" aria-label="Back to section heading"><svg data-icon="close"></svg></button></div><section><h3 class="section-label">Current session</h3><button class="history-row current" type="button"><span class="index">01</span><span class="prompt">Why does the ocean look blue?<small>Now playing</small></span></button><button class="history-row" type="button"><span class="index">02</span><span class="prompt">What lives in the deepest part?</span></button></section><section><h3 class="section-label">Earlier sessions</h3><button class="history-row" type="button"><svg data-icon="history"></svg><span class="prompt">A different way to see the stars<small>3 completed answers</small></span></button></section></nav></div><p class="doc-note">Select a history row to try its selected state. In the experience, selecting an answer replays its video and clears the previous draft. History retains up to ten sessions in memory, including completed answers. The host owns durable persistence.</p></div>
101
+ <div class="doc-stack"><div class="doc-preview"><div class="recovery-notice"><p role="status">Some visuals were replaced so your response can continue.</p><button type="button" class="round" aria-label="Dismiss notice" onclick="this.parentElement.hidden=true"><svg data-icon="close"></svg></button></div><p class="doc-note">Optional recovery notice. One calm, dismissible message per answer; it never blocks playback. Hosts can describe their generated-video preview in Settings.</p></div><div class="doc-preview popover-demo" id="history-demo"><nav class="sheet-popover history" aria-label="Example session history"><div class="popover-heading"><h2>History</h2><button class="round" type="button" data-go="panels" aria-label="Back to section heading"><svg data-icon="close"></svg></button></div><section><h3 class="section-label">Current session</h3><button class="history-row current" type="button"><span class="index">01</span><span class="prompt">Why does the ocean look blue?<small>Now playing</small></span></button><button class="history-row" type="button"><span class="index">02</span><span class="prompt">What lives in the deepest part?</span></button></section><section><h3 class="section-label">Earlier sessions</h3><button class="history-row" type="button"><svg data-icon="history"></svg><span class="prompt">A different way to see the stars<small>3 completed answers</small></span></button></section></nav></div><p class="doc-note">Select a history row to try its selected state. In the experience, selecting an answer replays its video and clears the previous draft. History retains up to ten sessions in memory, including completed answers. The host owns durable persistence.</p></div>
101
102
  </div></section>
102
103
  <section class="doc-section" id="conversation"><div class="section-heading"><span class="number">03</span><h2>One field. Every follow-up.</h2></div><p class="section-copy">Speak or type directly over the picture. Beginning a question pauses playback. Sending starts the next answer; canceling returns to the previous playback state.</p><div class="doc-preview"><div class="demo-toolbar" aria-label="Composer preview states"><button type="button" data-state="ready" aria-pressed="true">Ready</button><button type="button" data-state="listening" aria-pressed="false">Listening</button><button type="button" data-state="thinking" aria-pressed="false">Transcribing</button></div><div class="conversation-composer"><div class="composer-meta"><span id="composer-status" role="status">Interactive preview · no microphone or model call</span></div><form class="composer" id="composer-demo"><button class="ghost transport" type="button" aria-label="Play preview" id="play-demo"><svg data-icon="play"></svg></button><label class="sr-only" for="demo-question">Question</label><textarea id="demo-question" rows="1" placeholder="Ask a follow-up…"></textarea><button class="ghost" type="button" id="mic-demo" aria-label="Preview listening state" aria-pressed="false"><svg data-icon="mic"></svg></button><button class="send" type="submit" aria-label="Preview send" disabled><svg data-icon="send"></svg></button></form></div></div><p class="doc-note">44px targets. 10px between microphone and send. The field grows to 120px and keeps the controls within reach.</p></section>
103
104
  <section class="doc-section" id="captions"><div class="section-heading"><span class="number">04</span><h2>Words stay with the picture</h2></div><p class="section-copy">The first subtitle takes the input’s bottom baseline. The complete cue remains readable without truncation. Reveal the input to move subtitles upward; expand for the transcript or restore hidden subtitles in Settings.</p><div class="doc-preview"><div class="demo-toolbar"><button type="button" id="restore-caption">Show subtitles</button><button type="button" id="toggle-transcript">Expand transcript</button></div><div class="line-row" id="caption-demo" data-expanded="false"><div class="caption-actions"><button class="caption-action" type="button" id="expand-action" aria-label="Expand subtitles" aria-expanded="false">Expand <svg data-icon="up"></svg></button><button class="caption-action" type="button" id="hide-action" aria-label="Hide subtitles"><svg data-icon="close"></svg></button></div><p class="line" id="compact-caption">Water absorbs the warm colors of sunlight,<br>leaving more blue light to reach your eyes.</p><div class="expanded-captions" id="transcript-demo" tabindex="0" aria-label="Expanded subtitles" hidden><span class="eyebrow">Full answer</span><p>The ocean’s color begins with sunlight.</p><p>Water absorbs the warm colors of sunlight, leaving more blue light to reach your eyes.</p><p>Depth, particles and the sky change what we see. That is why a shallow lagoon and the open ocean can look so different.</p></div></div></div></section>
@@ -99,6 +99,20 @@ stock footage. Stock lookup is independent of this limit. Retries inside your
99
99
  provider callback can incur additional charges; bound those separately. Never
100
100
  copy an untrusted request value into this application-owned option.
101
101
 
102
+ Use `generateVideoTimeoutMs` for slower providers (integer `1`–`120000`, default
103
+ `15000`). Poll queued jobs only until the supplied signal aborts; submit once,
104
+ record the provider job identifier, and cancel that job best-effort on abort.
105
+ A disconnected browser or timed-out request does not prove the job was free.
106
+ Reserve host-owned quotas before submission and retain uncertain attempts.
107
+ Keep the host request deadline and client `timeoutMs` longer than the provider
108
+ deadline. The SDK does not resume a completed queued job into a finished answer.
109
+
110
+ For a limited offering, `VideoChat` accepts `generatedVideoLabel` and
111
+ `generatedVideoDescription` to explain the generated-video choice in Settings.
112
+ Set `showRecoveryNotice` to opt into a brief dismissible message when generated
113
+ visuals fall back. These presentation props do not enforce limits or enable
114
+ provider capabilities; the server remains authoritative.
115
+
102
116
  ## Application retrieval
103
117
 
104
118
  Fetch or search approved context in the application before asking the chat.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillaskyai/video",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "description": "Open-source voice-and-video chat SDK for AI applications.",
5
5
  "keywords": [
6
6
  "video-chat",
@@ -9,7 +9,7 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@vanillaskyai/video": "0.8.2",
12
+ "@vanillaskyai/video": "0.8.4",
13
13
  "react": "^19.2.8",
14
14
  "react-dom": "^19.2.8",
15
15
  "@ai-sdk/anthropic": "^3.0.0",
@@ -46,7 +46,8 @@ export const handleVideoChat = createVideoChatHandler({
46
46
  : undefined,
47
47
  ...providers,
48
48
  welcome: {
49
- heroQuery: process.env.VIDEO_CHAT_WELCOME_KEYWORD ?? "underwater ocean sunlight",
49
+ // Omit the query to use the SDK's curated cloud welcome.
50
+ ...(process.env.VIDEO_CHAT_WELCOME_KEYWORD ? { heroQuery: process.env.VIDEO_CHAT_WELCOME_KEYWORD } : {}),
50
51
  prompts: [
51
52
  {
52
53
  prompt: "Why does the Moon always show one face?",
@@ -1124,3 +1124,8 @@
1124
1124
  @media (prefers-reduced-transparency: reduce) {
1125
1125
  .vanillasky-video-chat .caption-actions { background: #171a28; backdrop-filter: none; -webkit-backdrop-filter: none; }
1126
1126
  }
1127
+
1128
+ /* Recoverable media notice: opt-in, distinct from errors, never blocks playback. */
1129
+ .vanillasky-video-chat .recovery-notice { pointer-events: auto; display: flex; align-items: center; gap: 12px; width: min(560px, 100%); padding: 8px 10px 8px 18px; color: var(--vs-fg); background: var(--vs-surface-pop); border: 1px solid var(--vs-border); border-radius: 24px; backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); }
1130
+ .vanillasky-video-chat .recovery-notice p { flex: 1; margin: 0; font-size: 14px; line-height: 1.5; }
1131
+ .vanillasky-video-chat .recovery-notice .round { flex: 0 0 44px; width: 44px; height: 44px; }