@burdenoff/website-sdk 2026.1104.1 → 2026.1104.3

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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk7O2I32ZN_js = require('../chunk-7O2I32ZN.js');
3
+ var chunkWSEQ2I6I_js = require('../chunk-WSEQ2I6I.js');
4
4
  var chunkO4GS3XPZ_js = require('../chunk-O4GS3XPZ.js');
5
5
  var chunkC6OJIHNO_js = require('../chunk-C6OJIHNO.js');
6
6
  require('../chunk-ZSTFX7FV.js');
@@ -68,7 +68,7 @@ function readAvailability(status, body) {
68
68
  const { consentVersion, maxSessionSeconds } = body;
69
69
  const botCheck = readBotCheck(body.botCheck);
70
70
  const questions = readQuestions(body.questions);
71
- if (available !== true || typeof consentVersion !== "string" || !chunk7O2I32ZN_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
71
+ if (available !== true || typeof consentVersion !== "string" || !chunkWSEQ2I6I_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
72
72
  return INERT;
73
73
  }
74
74
  return {
@@ -121,7 +121,8 @@ function readCreated(body) {
121
121
  endSecret,
122
122
  endsAt,
123
123
  join: { livekitUrl, participantToken, workerIdentity },
124
- captions: { locale, text }
124
+ captions: { locale, text },
125
+ excerpt: body.excerpt === true
125
126
  }
126
127
  };
127
128
  }
@@ -160,7 +161,7 @@ function deadline(timeoutMs, outer) {
160
161
  };
161
162
  }
162
163
  async function requestAvailability(target) {
163
- if (!chunk7O2I32ZN_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
164
+ if (!chunkWSEQ2I6I_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
164
165
  const query = new URLSearchParams({
165
166
  site: target.site,
166
167
  locale: target.locale
@@ -294,6 +295,7 @@ var EN = {
294
295
  failed: "The live avatar couldn't start.",
295
296
  interrupted: "The live avatar stopped early.",
296
297
  answerInChat: "The full answer is in Chat.",
298
+ excerpt: "The avatar reads the first part of this answer. The full answer is in Chat.",
297
299
  backToChat: "Back to Chat",
298
300
  busy: "The live avatar is busy right now. Please try again in a few minutes.",
299
301
  limits: "The live avatar has reached its limit for now. Please try again later.",
@@ -351,6 +353,8 @@ var ZH = {
351
353
  failed: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u65E0\u6CD5\u542F\u52A8\u3002",
352
354
  interrupted: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u63D0\u524D\u505C\u6B62\u4E86\u3002",
353
355
  answerInChat: "\u5B8C\u6574\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002",
356
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
357
+ excerpt: "\u865A\u62DF\u5F62\u8C61\u6717\u8BFB\u8FD9\u4E2A\u56DE\u7B54\u7684\u7B2C\u4E00\u90E8\u5206\u3002\u5B8C\u6574\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002",
354
358
  backToChat: "\u8FD4\u56DE\u804A\u5929",
355
359
  busy: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u76EE\u524D\u7E41\u5FD9\uFF0C\u8BF7\u51E0\u5206\u949F\u540E\u518D\u8BD5\u3002",
356
360
  limits: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u6682\u65F6\u5DF2\u8FBE\u5230\u4F7F\u7528\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
@@ -410,6 +414,8 @@ var HI = {
410
414
  failed: "\u0932\u093E\u0907\u0935 \u0905\u0935\u0924\u093E\u0930 \u0936\u0941\u0930\u0942 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u093E\u0964",
411
415
  interrupted: "\u0932\u093E\u0907\u0935 \u0905\u0935\u0924\u093E\u0930 \u092C\u0940\u091A \u092E\u0947\u0902 \u0939\u0940 \u0930\u0941\u0915 \u0917\u092F\u093E\u0964",
412
416
  answerInChat: "\u092A\u0942\u0930\u093E \u0909\u0924\u094D\u0924\u0930 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0964",
417
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
418
+ excerpt: "\u0905\u0935\u0924\u093E\u0930 \u0907\u0938 \u0909\u0924\u094D\u0924\u0930 \u0915\u093E \u092A\u0939\u0932\u093E \u092D\u093E\u0917 \u092A\u0922\u093C\u0924\u093E \u0939\u0948\u0964 \u092A\u0942\u0930\u093E \u0909\u0924\u094D\u0924\u0930 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0964",
413
419
  backToChat: "\u091A\u0948\u091F \u092A\u0930 \u0935\u093E\u092A\u0938 \u091C\u093E\u090F\u0901",
414
420
  busy: "\u0932\u093E\u0907\u0935 \u0905\u0935\u0924\u093E\u0930 \u0905\u092D\u0940 \u0935\u094D\u092F\u0938\u094D\u0924 \u0939\u0948\u0964 \u0915\u0943\u092A\u092F\u093E \u0915\u0941\u091B \u092E\u093F\u0928\u091F \u092C\u093E\u0926 \u092B\u093F\u0930 \u0915\u094B\u0936\u093F\u0936 \u0915\u0930\u0947\u0902\u0964",
415
421
  limits: "\u0932\u093E\u0907\u0935 \u0905\u0935\u0924\u093E\u0930 \u0905\u092D\u0940 \u0905\u092A\u0928\u0940 \u0938\u0940\u092E\u093E \u0924\u0915 \u092A\u0939\u0941\u0901\u091A \u0917\u092F\u093E \u0939\u0948\u0964 \u0915\u0943\u092A\u092F\u093E \u092C\u093E\u0926 \u092E\u0947\u0902 \u092B\u093F\u0930 \u0915\u094B\u0936\u093F\u0936 \u0915\u0930\u0947\u0902\u0964",
@@ -469,6 +475,8 @@ var ES = {
469
475
  failed: "No se pudo iniciar el avatar en vivo.",
470
476
  interrupted: "El avatar en vivo se detuvo antes de tiempo.",
471
477
  answerInChat: "La respuesta completa est\xE1 en el chat.",
478
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
479
+ excerpt: "El avatar lee la primera parte de esta respuesta. La respuesta completa est\xE1 en el chat.",
472
480
  backToChat: "Volver al chat",
473
481
  busy: "El avatar en vivo est\xE1 ocupado en este momento. Vuelve a intentarlo en unos minutos.",
474
482
  limits: "El avatar en vivo ha alcanzado su l\xEDmite por ahora. Vuelve a intentarlo m\xE1s tarde.",
@@ -528,6 +536,8 @@ var AR = {
528
536
  failed: "\u062A\u0639\u0630\u0651\u0631 \u0628\u062F\u0621 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0645\u0628\u0627\u0634\u0631.",
529
537
  interrupted: "\u062A\u0648\u0642\u0641 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0645\u0628\u0627\u0634\u0631 \u0642\u0628\u0644 \u0623\u0646 \u064A\u0646\u062A\u0647\u064A.",
530
538
  answerInChat: "\u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0627\u0644\u0643\u0627\u0645\u0644\u0629 \u0645\u0648\u062C\u0648\u062F\u0629 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629.",
539
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
540
+ excerpt: "\u064A\u0642\u0631\u0623 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u062C\u0632\u0621 \u0627\u0644\u0623\u0648\u0644 \u0645\u0646 \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629. \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0627\u0644\u0643\u0627\u0645\u0644\u0629 \u0645\u0648\u062C\u0648\u062F\u0629 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629.",
531
541
  backToChat: "\u0627\u0644\u0639\u0648\u062F\u0629 \u0625\u0644\u0649 \u0627\u0644\u062F\u0631\u062F\u0634\u0629",
532
542
  busy: "\u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0645\u0628\u0627\u0634\u0631 \u0645\u0634\u063A\u0648\u0644 \u0627\u0644\u0622\u0646. \u064A\u064F\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649 \u0628\u0639\u062F \u0628\u0636\u0639 \u062F\u0642\u0627\u0626\u0642.",
533
543
  limits: "\u0628\u0644\u063A \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0645\u0628\u0627\u0634\u0631 \u062D\u062F\u0651\u0647 \u0641\u064A \u0627\u0644\u0648\u0642\u062A \u0627\u0644\u062D\u0627\u0644\u064A. \u064A\u064F\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0644\u0627\u062D\u0642\u064B\u0627.",
@@ -587,6 +597,8 @@ var TA = {
587
597
  failed: "\u0BA8\u0BC7\u0BB0\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BC8\u0BA4\u0BCD \u0BA4\u0BCA\u0B9F\u0B99\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8.",
588
598
  interrupted: "\u0BA8\u0BC7\u0BB0\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0B87\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BC7\u0BAF\u0BC7 \u0BA8\u0BBF\u0BA9\u0BCD\u0BB1\u0BC1\u0BB5\u0BBF\u0B9F\u0BCD\u0B9F\u0BA4\u0BC1.",
589
599
  answerInChat: "\u0BAE\u0BC1\u0BB4\u0BC1\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1.",
600
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
601
+ excerpt: "\u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BBF\u0BA9\u0BCD \u0BAE\u0BC1\u0BA4\u0BB2\u0BCD \u0BAA\u0B95\u0BC1\u0BA4\u0BBF\u0BAF\u0BC8\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95\u0BBF\u0BB1\u0BA4\u0BC1. \u0BAE\u0BC1\u0BB4\u0BC1\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1.",
590
602
  backToChat: "\u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0B95\u0BCD\u0B95\u0BC1\u0BA4\u0BCD \u0BA4\u0BBF\u0BB0\u0BC1\u0BAE\u0BCD\u0BAA\u0BC1",
591
603
  busy: "\u0BA8\u0BC7\u0BB0\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0B87\u0BAA\u0BCD\u0BAA\u0BCB\u0BA4\u0BC1 \u0BB5\u0BC7\u0BB1\u0BC1 \u0BAA\u0BAF\u0BA9\u0BCD\u0BAA\u0BBE\u0B9F\u0BCD\u0B9F\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1. \u0B9A\u0BBF\u0BB2 \u0BA8\u0BBF\u0BAE\u0BBF\u0B9F\u0B99\u0BCD\u0B95\u0BB3\u0BBF\u0BB2\u0BCD \u0BAE\u0BC0\u0BA3\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD \u0BAE\u0BC1\u0BAF\u0BB2\u0BB5\u0BC1\u0BAE\u0BCD.",
592
604
  limits: "\u0BA8\u0BC7\u0BB0\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0BA4\u0BB1\u0BCD\u0BAA\u0BCB\u0BA4\u0BC8\u0B95\u0BCD\u0B95\u0BC1\u0BA4\u0BCD \u0BA4\u0BA9\u0BCD \u0BB5\u0BB0\u0BAE\u0BCD\u0BAA\u0BC8 \u0B8E\u0B9F\u0BCD\u0B9F\u0BBF\u0BB5\u0BBF\u0B9F\u0BCD\u0B9F\u0BA4\u0BC1. \u0BAA\u0BBF\u0BB1\u0B95\u0BC1 \u0BAE\u0BC0\u0BA3\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD \u0BAE\u0BC1\u0BAF\u0BB2\u0BB5\u0BC1\u0BAE\u0BCD.",
@@ -734,10 +746,46 @@ var CONSENT = /* @__PURE__ */ new Map([
734
746
  questions: "any"
735
747
  }
736
748
  }
749
+ ],
750
+ [
751
+ // Stage B (operator decision 2026-11-04): web-consent-2026-11-05 with only its timing sentence changed. A session
752
+ // lasts up to three minutes, sized by the server to the answer's audio; an answer longer than that is read in part,
753
+ // which the avatar says and the Avatar tab notes (`excerpt`), and the full answer stays in Chat. A cap still
754
+ // applies, so the words never say every answer is read whole (gitops docs/avatar-web/en-web-consent-2026-11-12-b.txt).
755
+ "web-consent-2026-11-12-b",
756
+ {
757
+ en: {
758
+ title: "Live AI avatar (beta)",
759
+ introProduct: null,
760
+ intro: "This is an AI avatar, not a real person. It reads aloud the first answer in a new chat started in the Avatar tab, provided it passes the speech checks, whether you chose a suggested question or typed your own. It reads for up to three minutes; if an answer is longer, it reads the first part, says so, and the full answer stays in Chat. The answer is AI-generated and can be wrong. Don't put personal details in a question you type.",
761
+ processing: "When you start, Cloudflare checks for bots. The answer text goes to Google to create the voice. Our server sends that voice through LiveKit to Protoface, which makes the avatar video streamed through LiveKit. Your browser connects to LiveKit, which sees your IP address.",
762
+ storage: "Your microphone and camera are not used. We do not record the live session or store the generated voice. We keep a record of this consent. The privacy notice explains our safeguards, storage periods and the providers' processing. For people aged eighteen and over.",
763
+ privacyLink: "How we handle this",
764
+ botCheck: "turnstile",
765
+ questions: "any"
766
+ }
767
+ }
768
+ ],
769
+ [
770
+ // The words of web-consent-2026-11-12-b without "When you start, Cloudflare checks for bots.", for a server that runs
771
+ // no bot check (gitops docs/avatar-web/en-web-consent-2026-11-13-b.txt).
772
+ "web-consent-2026-11-13-b",
773
+ {
774
+ en: {
775
+ title: "Live AI avatar (beta)",
776
+ introProduct: null,
777
+ intro: "This is an AI avatar, not a real person. It reads aloud the first answer in a new chat started in the Avatar tab, provided it passes the speech checks, whether you chose a suggested question or typed your own. It reads for up to three minutes; if an answer is longer, it reads the first part, says so, and the full answer stays in Chat. The answer is AI-generated and can be wrong. Don't put personal details in a question you type.",
778
+ processing: "The answer text goes to Google to create the voice. Our server sends that voice through LiveKit to Protoface, which makes the avatar video streamed through LiveKit. Your browser connects to LiveKit, which sees your IP address.",
779
+ storage: "Your microphone and camera are not used. We do not record the live session or store the generated voice. We keep a record of this consent. The privacy notice explains our safeguards, storage periods and the providers' processing. For people aged eighteen and over.",
780
+ privacyLink: "How we handle this",
781
+ botCheck: "off",
782
+ questions: "any"
783
+ }
784
+ }
737
785
  ]
738
786
  ]);
739
787
  function avatarConsentCopy(version, locale) {
740
- if (!chunk7O2I32ZN_js.isAvatarLiveLocaleReviewed(locale)) return null;
788
+ if (!chunkWSEQ2I6I_js.isAvatarLiveLocaleReviewed(locale)) return null;
741
789
  return CONSENT.get(version)?.[locale] ?? null;
742
790
  }
743
791
 
@@ -859,6 +907,8 @@ var PRIVACY_HREF = "/privacy#live-avatar";
859
907
  var SPEAKER_WAIT_MS = 3e4;
860
908
  var END_GRACE_MS = 1e4;
861
909
  var CLOCK_TOLERANCE_MS = 5e3;
910
+ var UNHEARD_GRACE_MS = 5e3;
911
+ var PLAYOUT_TAIL_MS = 5e3;
862
912
  var SERVICE_MAX_AGE_MS = 15 * 6e4;
863
913
  var ANSWER_OFFER_MS = SERVICE_MAX_AGE_MS - 6e4;
864
914
  var MAX_TIMER_MS = 2147483647;
@@ -868,6 +918,12 @@ var USED_ON_KEY = "boff.explore.avatarUsedOn";
868
918
  var PRIMARY = '[data-avatar-primary], [data-boff-explore="panel-continue-chat"]';
869
919
  var IDLE = { name: "idle" };
870
920
  var INERT2 = { name: "inert" };
921
+ function stopAttending(handle) {
922
+ if (handle.unheard !== null) clearTimeout(handle.unheard);
923
+ if (handle.tail !== null) clearTimeout(handle.tail);
924
+ handle.unheard = null;
925
+ handle.tail = null;
926
+ }
871
927
  var REFUSAL_TEXT = {
872
928
  busy: "busy",
873
929
  limits: "limits",
@@ -1151,6 +1207,7 @@ function AvatarLiveView({
1151
1207
  const handle = sessionRef.current;
1152
1208
  if (!handle || handle.done) return;
1153
1209
  handle.done = true;
1210
+ stopAttending(handle);
1154
1211
  sessionRef.current = null;
1155
1212
  if (reason !== null) {
1156
1213
  sendEnd({
@@ -1180,6 +1237,33 @@ function AvatarLiveView({
1180
1237
  }
1181
1238
  go({ name: "live", session: current.session });
1182
1239
  }, [go]);
1240
+ const attend = react.useCallback(() => {
1241
+ const handle = sessionRef.current;
1242
+ if (!handle || handle.done || latest.current.live.endUnattended !== true) {
1243
+ return;
1244
+ }
1245
+ const current = phaseRef.current.name;
1246
+ if (current !== "pending" && current !== "live") return;
1247
+ const away = !inSight();
1248
+ if (away && handle.playedOut && current === "live") {
1249
+ finish("finished", "user");
1250
+ return;
1251
+ }
1252
+ if (!(away && handle.soundHeld && handle.voiceArrived)) {
1253
+ if (handle.unheard !== null) {
1254
+ clearTimeout(handle.unheard);
1255
+ handle.unheard = null;
1256
+ }
1257
+ return;
1258
+ }
1259
+ if (handle.unheard !== null) return;
1260
+ handle.unheard = setTimeout(() => {
1261
+ handle.unheard = null;
1262
+ if (sessionRef.current === handle && !handle.done && latest.current.live.endUnattended === true && handle.soundHeld && handle.voiceArrived && !inSight()) {
1263
+ finish("interrupted", "user");
1264
+ }
1265
+ }, UNHEARD_GRACE_MS);
1266
+ }, [finish, inSight]);
1183
1267
  const cancelStart = react.useCallback((reason) => {
1184
1268
  const run = startRef.current;
1185
1269
  if (!run) return;
@@ -1207,7 +1291,7 @@ function AvatarLiveView({
1207
1291
  go(INERT2);
1208
1292
  return;
1209
1293
  }
1210
- if (result.offer.botCheck === "turnstile" && chunk7O2I32ZN_js.turnstileSiteKeyOf(live2) === null) {
1294
+ if (result.offer.botCheck === "turnstile" && chunkWSEQ2I6I_js.turnstileSiteKeyOf(live2) === null) {
1211
1295
  setMode("suggested");
1212
1296
  go(INERT2);
1213
1297
  return;
@@ -1256,13 +1340,22 @@ function AvatarLiveView({
1256
1340
  joined: null,
1257
1341
  endsAt,
1258
1342
  speakerArrived: false,
1343
+ voiceArrived: false,
1344
+ soundHeld: false,
1345
+ playedOut: false,
1346
+ unheard: null,
1347
+ tail: null,
1259
1348
  done: false
1260
1349
  };
1261
1350
  setBlocked(false);
1262
1351
  setNow(received);
1263
1352
  go({
1264
1353
  name: "pending",
1265
- session: { endsAt, captions: session.captions }
1354
+ session: {
1355
+ endsAt,
1356
+ captions: session.captions,
1357
+ excerpt: session.excerpt
1358
+ }
1266
1359
  });
1267
1360
  },
1268
1361
  [go]
@@ -1335,7 +1428,7 @@ function AvatarLiveView({
1335
1428
  return;
1336
1429
  }
1337
1430
  const refusedFrom = exchangeAtStart.offerUntil - ANSWER_OFFER_MS + SERVICE_MAX_AGE_MS;
1338
- const siteKey = offer.botCheck === "turnstile" ? chunk7O2I32ZN_js.turnstileSiteKeyOf(live2) : null;
1431
+ const siteKey = offer.botCheck === "turnstile" ? chunkWSEQ2I6I_js.turnstileSiteKeyOf(live2) : null;
1339
1432
  if (offer.botCheck === "turnstile" && (siteKey === null || !canBindBotCheck())) {
1340
1433
  go(INERT2);
1341
1434
  return;
@@ -1581,6 +1674,9 @@ function AvatarLiveView({
1581
1674
  check();
1582
1675
  }
1583
1676
  }, [showing, behindChat, check, leave, offeredNow]);
1677
+ react.useEffect(() => {
1678
+ attend();
1679
+ }, [showing, attend]);
1584
1680
  react.useEffect(() => {
1585
1681
  if (askedLocale.current === locale) return;
1586
1682
  askedLocale.current = locale;
@@ -1667,12 +1763,29 @@ function AvatarLiveView({
1667
1763
  const wasLive = phaseRef.current.name === "live";
1668
1764
  switch (event.type) {
1669
1765
  case "speaker-track":
1670
- if (event.kind !== "video") return;
1766
+ if (event.kind !== "video") {
1767
+ handle.voiceArrived = true;
1768
+ attend();
1769
+ return;
1770
+ }
1671
1771
  handle.speakerArrived = true;
1672
1772
  if (!inSight()) goLive();
1673
1773
  return;
1674
1774
  case "playback-blocked":
1675
1775
  setBlocked(event.blocked);
1776
+ handle.soundHeld = event.sound ?? event.blocked;
1777
+ attend();
1778
+ return;
1779
+ case "speech-ended":
1780
+ if (latest.current.live.endUnattended !== true || handle.tail !== null || handle.playedOut) {
1781
+ return;
1782
+ }
1783
+ handle.tail = setTimeout(() => {
1784
+ handle.tail = null;
1785
+ if (sessionRef.current !== handle || handle.done) return;
1786
+ handle.playedOut = true;
1787
+ attend();
1788
+ }, PLAYOUT_TAIL_MS);
1676
1789
  return;
1677
1790
  case "speaker-left":
1678
1791
  finish(
@@ -1686,7 +1799,7 @@ function AvatarLiveView({
1686
1799
  return;
1687
1800
  }
1688
1801
  },
1689
- [finish, goLive, inSight]
1802
+ [attend, finish, goLive, inSight]
1690
1803
  );
1691
1804
  react.useEffect(() => {
1692
1805
  if (phase.name !== "pending") return;
@@ -1773,10 +1886,12 @@ function AvatarLiveView({
1773
1886
  const onVisibility = () => {
1774
1887
  if (document.visibilityState === "hidden") {
1775
1888
  leave("hidden");
1889
+ attend();
1776
1890
  return;
1777
1891
  }
1778
1892
  const current = phaseRef.current.name;
1779
1893
  if (current === "pending" || current === "live") setNow(Date.now());
1894
+ attend();
1780
1895
  };
1781
1896
  window.addEventListener("pagehide", onPageHide);
1782
1897
  document.addEventListener("visibilitychange", onVisibility);
@@ -1784,7 +1899,7 @@ function AvatarLiveView({
1784
1899
  window.removeEventListener("pagehide", onPageHide);
1785
1900
  document.removeEventListener("visibilitychange", onVisibility);
1786
1901
  };
1787
- }, [leave]);
1902
+ }, [attend, leave]);
1788
1903
  react.useEffect(
1789
1904
  () => () => {
1790
1905
  const controller = checkRef.current;
@@ -1803,6 +1918,7 @@ function AvatarLiveView({
1803
1918
  sessionRef.current = null;
1804
1919
  if (handle && !handle.done) {
1805
1920
  handle.done = true;
1921
+ stopAttending(handle);
1806
1922
  sendEnd({
1807
1923
  base: handle.base,
1808
1924
  headers: handle.headers,
@@ -2061,6 +2177,7 @@ function AvatarLiveView({
2061
2177
  ]
2062
2178
  }
2063
2179
  );
2180
+ const speakingLine = (session) => session.excerpt ? S.excerpt : S.speaking;
2064
2181
  const liveBody = (session) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2065
2182
  /* @__PURE__ */ jsxRuntime.jsx(
2066
2183
  "section",
@@ -2274,7 +2391,7 @@ function AvatarLiveView({
2274
2391
  break;
2275
2392
  case "pending":
2276
2393
  case "live":
2277
- status = phase.name === "live" ? S.speaking : S.connecting;
2394
+ status = phase.name === "live" ? speakingLine(phase.session) : S.connecting;
2278
2395
  top2 = badge;
2279
2396
  media2 = liveMedia(phase.name === "live");
2280
2397
  body2 = liveBody(phase.session);
@@ -2400,7 +2517,7 @@ function AvatarLiveView({
2400
2517
  "bot-check": S.botCheck,
2401
2518
  creating: S.connecting,
2402
2519
  pending: S.connecting,
2403
- live: S.speaking,
2520
+ live: phase.name === "live" ? speakingLine(phase.session) : "",
2404
2521
  ended: phase.name === "ended" ? S[phase.how] : ""
2405
2522
  };
2406
2523
  const outcome = phase.name === "needs-answer" || phase.name === "refused" || phase.name === "ended";