@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,4 +1,4 @@
1
- import { turnstileSiteKeyOf, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion } from '../chunk-K3QZGJEH.mjs';
1
+ import { turnstileSiteKeyOf, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion } from '../chunk-7MSB3EUI.mjs';
2
2
  import { fill } from '../chunk-TT3MHN42.mjs';
3
3
  import { cn } from '../chunk-R7G6TWIS.mjs';
4
4
  import '../chunk-VNS5WDWO.mjs';
@@ -119,7 +119,8 @@ function readCreated(body) {
119
119
  endSecret,
120
120
  endsAt,
121
121
  join: { livekitUrl, participantToken, workerIdentity },
122
- captions: { locale, text }
122
+ captions: { locale, text },
123
+ excerpt: body.excerpt === true
123
124
  }
124
125
  };
125
126
  }
@@ -292,6 +293,7 @@ var EN = {
292
293
  failed: "The live avatar couldn't start.",
293
294
  interrupted: "The live avatar stopped early.",
294
295
  answerInChat: "The full answer is in Chat.",
296
+ excerpt: "The avatar reads the first part of this answer. The full answer is in Chat.",
295
297
  backToChat: "Back to Chat",
296
298
  busy: "The live avatar is busy right now. Please try again in a few minutes.",
297
299
  limits: "The live avatar has reached its limit for now. Please try again later.",
@@ -349,6 +351,8 @@ var ZH = {
349
351
  failed: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u65E0\u6CD5\u542F\u52A8\u3002",
350
352
  interrupted: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u63D0\u524D\u505C\u6B62\u4E86\u3002",
351
353
  answerInChat: "\u5B8C\u6574\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002",
354
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
355
+ 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",
352
356
  backToChat: "\u8FD4\u56DE\u804A\u5929",
353
357
  busy: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u76EE\u524D\u7E41\u5FD9\uFF0C\u8BF7\u51E0\u5206\u949F\u540E\u518D\u8BD5\u3002",
354
358
  limits: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u6682\u65F6\u5DF2\u8FBE\u5230\u4F7F\u7528\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002",
@@ -408,6 +412,8 @@ var HI = {
408
412
  failed: "\u0932\u093E\u0907\u0935 \u0905\u0935\u0924\u093E\u0930 \u0936\u0941\u0930\u0942 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u093E\u0964",
409
413
  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",
410
414
  answerInChat: "\u092A\u0942\u0930\u093E \u0909\u0924\u094D\u0924\u0930 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0964",
415
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
416
+ 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",
411
417
  backToChat: "\u091A\u0948\u091F \u092A\u0930 \u0935\u093E\u092A\u0938 \u091C\u093E\u090F\u0901",
412
418
  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",
413
419
  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",
@@ -467,6 +473,8 @@ var ES = {
467
473
  failed: "No se pudo iniciar el avatar en vivo.",
468
474
  interrupted: "El avatar en vivo se detuvo antes de tiempo.",
469
475
  answerInChat: "La respuesta completa est\xE1 en el chat.",
476
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
477
+ excerpt: "El avatar lee la primera parte de esta respuesta. La respuesta completa est\xE1 en el chat.",
470
478
  backToChat: "Volver al chat",
471
479
  busy: "El avatar en vivo est\xE1 ocupado en este momento. Vuelve a intentarlo en unos minutos.",
472
480
  limits: "El avatar en vivo ha alcanzado su l\xEDmite por ahora. Vuelve a intentarlo m\xE1s tarde.",
@@ -526,6 +534,8 @@ var AR = {
526
534
  failed: "\u062A\u0639\u0630\u0651\u0631 \u0628\u062F\u0621 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0645\u0628\u0627\u0634\u0631.",
527
535
  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.",
528
536
  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.",
537
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
538
+ 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.",
529
539
  backToChat: "\u0627\u0644\u0639\u0648\u062F\u0629 \u0625\u0644\u0649 \u0627\u0644\u062F\u0631\u062F\u0634\u0629",
530
540
  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.",
531
541
  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.",
@@ -585,6 +595,8 @@ var TA = {
585
595
  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.",
586
596
  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.",
587
597
  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.",
598
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the note for a part-read answer (Stage B).
599
+ 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.",
588
600
  backToChat: "\u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0B95\u0BCD\u0B95\u0BC1\u0BA4\u0BCD \u0BA4\u0BBF\u0BB0\u0BC1\u0BAE\u0BCD\u0BAA\u0BC1",
589
601
  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.",
590
602
  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.",
@@ -732,6 +744,42 @@ var CONSENT = /* @__PURE__ */ new Map([
732
744
  questions: "any"
733
745
  }
734
746
  }
747
+ ],
748
+ [
749
+ // Stage B (operator decision 2026-11-04): web-consent-2026-11-05 with only its timing sentence changed. A session
750
+ // lasts up to three minutes, sized by the server to the answer's audio; an answer longer than that is read in part,
751
+ // which the avatar says and the Avatar tab notes (`excerpt`), and the full answer stays in Chat. A cap still
752
+ // applies, so the words never say every answer is read whole (gitops docs/avatar-web/en-web-consent-2026-11-12-b.txt).
753
+ "web-consent-2026-11-12-b",
754
+ {
755
+ en: {
756
+ title: "Live AI avatar (beta)",
757
+ introProduct: null,
758
+ 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.",
759
+ 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.",
760
+ 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.",
761
+ privacyLink: "How we handle this",
762
+ botCheck: "turnstile",
763
+ questions: "any"
764
+ }
765
+ }
766
+ ],
767
+ [
768
+ // The words of web-consent-2026-11-12-b without "When you start, Cloudflare checks for bots.", for a server that runs
769
+ // no bot check (gitops docs/avatar-web/en-web-consent-2026-11-13-b.txt).
770
+ "web-consent-2026-11-13-b",
771
+ {
772
+ en: {
773
+ title: "Live AI avatar (beta)",
774
+ introProduct: null,
775
+ 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.",
776
+ 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.",
777
+ 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.",
778
+ privacyLink: "How we handle this",
779
+ botCheck: "off",
780
+ questions: "any"
781
+ }
782
+ }
735
783
  ]
736
784
  ]);
737
785
  function avatarConsentCopy(version, locale) {
@@ -857,6 +905,8 @@ var PRIVACY_HREF = "/privacy#live-avatar";
857
905
  var SPEAKER_WAIT_MS = 3e4;
858
906
  var END_GRACE_MS = 1e4;
859
907
  var CLOCK_TOLERANCE_MS = 5e3;
908
+ var UNHEARD_GRACE_MS = 5e3;
909
+ var PLAYOUT_TAIL_MS = 5e3;
860
910
  var SERVICE_MAX_AGE_MS = 15 * 6e4;
861
911
  var ANSWER_OFFER_MS = SERVICE_MAX_AGE_MS - 6e4;
862
912
  var MAX_TIMER_MS = 2147483647;
@@ -866,6 +916,12 @@ var USED_ON_KEY = "boff.explore.avatarUsedOn";
866
916
  var PRIMARY = '[data-avatar-primary], [data-boff-explore="panel-continue-chat"]';
867
917
  var IDLE = { name: "idle" };
868
918
  var INERT2 = { name: "inert" };
919
+ function stopAttending(handle) {
920
+ if (handle.unheard !== null) clearTimeout(handle.unheard);
921
+ if (handle.tail !== null) clearTimeout(handle.tail);
922
+ handle.unheard = null;
923
+ handle.tail = null;
924
+ }
869
925
  var REFUSAL_TEXT = {
870
926
  busy: "busy",
871
927
  limits: "limits",
@@ -1149,6 +1205,7 @@ function AvatarLiveView({
1149
1205
  const handle = sessionRef.current;
1150
1206
  if (!handle || handle.done) return;
1151
1207
  handle.done = true;
1208
+ stopAttending(handle);
1152
1209
  sessionRef.current = null;
1153
1210
  if (reason !== null) {
1154
1211
  sendEnd({
@@ -1178,6 +1235,33 @@ function AvatarLiveView({
1178
1235
  }
1179
1236
  go({ name: "live", session: current.session });
1180
1237
  }, [go]);
1238
+ const attend = useCallback(() => {
1239
+ const handle = sessionRef.current;
1240
+ if (!handle || handle.done || latest.current.live.endUnattended !== true) {
1241
+ return;
1242
+ }
1243
+ const current = phaseRef.current.name;
1244
+ if (current !== "pending" && current !== "live") return;
1245
+ const away = !inSight();
1246
+ if (away && handle.playedOut && current === "live") {
1247
+ finish("finished", "user");
1248
+ return;
1249
+ }
1250
+ if (!(away && handle.soundHeld && handle.voiceArrived)) {
1251
+ if (handle.unheard !== null) {
1252
+ clearTimeout(handle.unheard);
1253
+ handle.unheard = null;
1254
+ }
1255
+ return;
1256
+ }
1257
+ if (handle.unheard !== null) return;
1258
+ handle.unheard = setTimeout(() => {
1259
+ handle.unheard = null;
1260
+ if (sessionRef.current === handle && !handle.done && latest.current.live.endUnattended === true && handle.soundHeld && handle.voiceArrived && !inSight()) {
1261
+ finish("interrupted", "user");
1262
+ }
1263
+ }, UNHEARD_GRACE_MS);
1264
+ }, [finish, inSight]);
1181
1265
  const cancelStart = useCallback((reason) => {
1182
1266
  const run = startRef.current;
1183
1267
  if (!run) return;
@@ -1254,13 +1338,22 @@ function AvatarLiveView({
1254
1338
  joined: null,
1255
1339
  endsAt,
1256
1340
  speakerArrived: false,
1341
+ voiceArrived: false,
1342
+ soundHeld: false,
1343
+ playedOut: false,
1344
+ unheard: null,
1345
+ tail: null,
1257
1346
  done: false
1258
1347
  };
1259
1348
  setBlocked(false);
1260
1349
  setNow(received);
1261
1350
  go({
1262
1351
  name: "pending",
1263
- session: { endsAt, captions: session.captions }
1352
+ session: {
1353
+ endsAt,
1354
+ captions: session.captions,
1355
+ excerpt: session.excerpt
1356
+ }
1264
1357
  });
1265
1358
  },
1266
1359
  [go]
@@ -1579,6 +1672,9 @@ function AvatarLiveView({
1579
1672
  check();
1580
1673
  }
1581
1674
  }, [showing, behindChat, check, leave, offeredNow]);
1675
+ useEffect(() => {
1676
+ attend();
1677
+ }, [showing, attend]);
1582
1678
  useEffect(() => {
1583
1679
  if (askedLocale.current === locale) return;
1584
1680
  askedLocale.current = locale;
@@ -1665,12 +1761,29 @@ function AvatarLiveView({
1665
1761
  const wasLive = phaseRef.current.name === "live";
1666
1762
  switch (event.type) {
1667
1763
  case "speaker-track":
1668
- if (event.kind !== "video") return;
1764
+ if (event.kind !== "video") {
1765
+ handle.voiceArrived = true;
1766
+ attend();
1767
+ return;
1768
+ }
1669
1769
  handle.speakerArrived = true;
1670
1770
  if (!inSight()) goLive();
1671
1771
  return;
1672
1772
  case "playback-blocked":
1673
1773
  setBlocked(event.blocked);
1774
+ handle.soundHeld = event.sound ?? event.blocked;
1775
+ attend();
1776
+ return;
1777
+ case "speech-ended":
1778
+ if (latest.current.live.endUnattended !== true || handle.tail !== null || handle.playedOut) {
1779
+ return;
1780
+ }
1781
+ handle.tail = setTimeout(() => {
1782
+ handle.tail = null;
1783
+ if (sessionRef.current !== handle || handle.done) return;
1784
+ handle.playedOut = true;
1785
+ attend();
1786
+ }, PLAYOUT_TAIL_MS);
1674
1787
  return;
1675
1788
  case "speaker-left":
1676
1789
  finish(
@@ -1684,7 +1797,7 @@ function AvatarLiveView({
1684
1797
  return;
1685
1798
  }
1686
1799
  },
1687
- [finish, goLive, inSight]
1800
+ [attend, finish, goLive, inSight]
1688
1801
  );
1689
1802
  useEffect(() => {
1690
1803
  if (phase.name !== "pending") return;
@@ -1771,10 +1884,12 @@ function AvatarLiveView({
1771
1884
  const onVisibility = () => {
1772
1885
  if (document.visibilityState === "hidden") {
1773
1886
  leave("hidden");
1887
+ attend();
1774
1888
  return;
1775
1889
  }
1776
1890
  const current = phaseRef.current.name;
1777
1891
  if (current === "pending" || current === "live") setNow(Date.now());
1892
+ attend();
1778
1893
  };
1779
1894
  window.addEventListener("pagehide", onPageHide);
1780
1895
  document.addEventListener("visibilitychange", onVisibility);
@@ -1782,7 +1897,7 @@ function AvatarLiveView({
1782
1897
  window.removeEventListener("pagehide", onPageHide);
1783
1898
  document.removeEventListener("visibilitychange", onVisibility);
1784
1899
  };
1785
- }, [leave]);
1900
+ }, [attend, leave]);
1786
1901
  useEffect(
1787
1902
  () => () => {
1788
1903
  const controller = checkRef.current;
@@ -1801,6 +1916,7 @@ function AvatarLiveView({
1801
1916
  sessionRef.current = null;
1802
1917
  if (handle && !handle.done) {
1803
1918
  handle.done = true;
1919
+ stopAttending(handle);
1804
1920
  sendEnd({
1805
1921
  base: handle.base,
1806
1922
  headers: handle.headers,
@@ -2059,6 +2175,7 @@ function AvatarLiveView({
2059
2175
  ]
2060
2176
  }
2061
2177
  );
2178
+ const speakingLine = (session) => session.excerpt ? S.excerpt : S.speaking;
2062
2179
  const liveBody = (session) => /* @__PURE__ */ jsxs(Fragment, { children: [
2063
2180
  /* @__PURE__ */ jsx(
2064
2181
  "section",
@@ -2272,7 +2389,7 @@ function AvatarLiveView({
2272
2389
  break;
2273
2390
  case "pending":
2274
2391
  case "live":
2275
- status = phase.name === "live" ? S.speaking : S.connecting;
2392
+ status = phase.name === "live" ? speakingLine(phase.session) : S.connecting;
2276
2393
  top2 = badge;
2277
2394
  media2 = liveMedia(phase.name === "live");
2278
2395
  body2 = liveBody(phase.session);
@@ -2398,7 +2515,7 @@ function AvatarLiveView({
2398
2515
  "bot-check": S.botCheck,
2399
2516
  creating: S.connecting,
2400
2517
  pending: S.connecting,
2401
- live: S.speaking,
2518
+ live: phase.name === "live" ? speakingLine(phase.session) : "",
2402
2519
  ended: phase.name === "ended" ? S[phase.how] : ""
2403
2520
  };
2404
2521
  const outcome = phase.name === "needs-answer" || phase.name === "refused" || phase.name === "ended";