@burdenoff/website-sdk 2026.1104.4 → 2026.1105.2

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.
Files changed (36) hide show
  1. package/README.md +67 -3
  2. package/dist/avatar-live/index.d.mts +2 -2
  3. package/dist/avatar-live/index.d.ts +2 -2
  4. package/dist/avatar-live/index.js +11 -1
  5. package/dist/avatar-live/index.js.map +1 -1
  6. package/dist/avatar-live/index.mjs +11 -1
  7. package/dist/avatar-live/index.mjs.map +1 -1
  8. package/dist/avatar-live-view/index.d.mts +2 -2
  9. package/dist/avatar-live-view/index.d.ts +2 -2
  10. package/dist/avatar-live-view/index.js +1059 -45
  11. package/dist/avatar-live-view/index.js.map +1 -1
  12. package/dist/avatar-live-view/index.mjs +1054 -40
  13. package/dist/avatar-live-view/index.mjs.map +1 -1
  14. package/dist/avatar-mic/index.d.mts +193 -0
  15. package/dist/avatar-mic/index.d.ts +193 -0
  16. package/dist/avatar-mic/index.js +738 -0
  17. package/dist/avatar-mic/index.js.map +1 -0
  18. package/dist/avatar-mic/index.mjs +736 -0
  19. package/dist/avatar-mic/index.mjs.map +1 -0
  20. package/dist/{chunk-7MSB3EUI.mjs → chunk-MVNN6LLO.mjs} +4 -3
  21. package/dist/chunk-MVNN6LLO.mjs.map +1 -0
  22. package/dist/{chunk-WSEQ2I6I.js → chunk-XIZE37IB.js} +4 -3
  23. package/dist/chunk-XIZE37IB.js.map +1 -0
  24. package/dist/{explore-avatar-live-config-B6_p0q1c.d.mts → explore-avatar-live-config-BL8FTm0I.d.mts} +9 -1
  25. package/dist/{explore-avatar-live-config-DRpS-mqQ.d.ts → explore-avatar-live-config-auLKxSzx.d.ts} +9 -1
  26. package/dist/index.d.mts +9 -4
  27. package/dist/index.d.ts +9 -4
  28. package/dist/index.js +9 -7
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +5 -3
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/{types-CXqaLlNB.d.mts → types-CTUJNvQu.d.mts} +10 -0
  33. package/dist/{types-CXqaLlNB.d.ts → types-CTUJNvQu.d.ts} +10 -0
  34. package/package.json +1 -1
  35. package/dist/chunk-7MSB3EUI.mjs.map +0 -1
  36. package/dist/chunk-WSEQ2I6I.js.map +0 -1
@@ -1,16 +1,20 @@
1
1
  'use strict';
2
2
 
3
- var chunkWSEQ2I6I_js = require('../chunk-WSEQ2I6I.js');
3
+ var chunkXIZE37IB_js = require('../chunk-XIZE37IB.js');
4
4
  var chunkN7E6Z2GF_js = require('../chunk-N7E6Z2GF.js');
5
5
  var chunkC6OJIHNO_js = require('../chunk-C6OJIHNO.js');
6
6
  require('../chunk-ZSTFX7FV.js');
7
7
  var react = require('react');
8
+ var lucideReact = require('lucide-react');
8
9
  var jsxRuntime = require('react/jsx-runtime');
9
10
 
10
11
  // src/components/explore-avatar-api.ts
11
12
  var AVAILABILITY_TIMEOUT_MS = 8e3;
12
13
  var CREATE_TIMEOUT_MS = 3e4;
14
+ var TURN_TIMEOUT_MS = 8e3;
15
+ var TURN_ANSWER_TIMEOUT_MS = 25e3;
13
16
  var MESSAGE_ID = /^[A-Za-z0-9_-]{1,64}$/;
17
+ var STT_TOKEN = /^[A-Za-z0-9._~-]{1,4096}$/;
14
18
  var CONVERSATION_TOKEN = /^[A-Za-z0-9._~+/=-]{16,512}$/;
15
19
  var LOCALE = /^[A-Za-z0-9-]{1,16}$/;
16
20
  var SESSION_ID = /^avs_[0-9a-f]{32}$/;
@@ -58,6 +62,15 @@ function readQuestions(value) {
58
62
  if (typeof value !== "string" || !QUESTION_MODES.has(value)) return null;
59
63
  return value === "any" ? "any" : "suggested";
60
64
  }
65
+ function readSttOffer(value) {
66
+ if (!isRecord(value)) return void 0;
67
+ const keys = Object.keys(value).sort();
68
+ const { token, expiresAt } = value;
69
+ if (keys.length !== 2 || keys[0] !== "expiresAt" || keys[1] !== "token" || typeof token !== "string" || !STT_TOKEN.test(token) || typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt) || expiresAt <= 0) {
70
+ return void 0;
71
+ }
72
+ return { token, expiresAt };
73
+ }
61
74
  function readAvailability(status, body) {
62
75
  if (status !== 200 || !isRecord(body)) return INERT;
63
76
  const { available, reason } = body;
@@ -68,12 +81,19 @@ function readAvailability(status, body) {
68
81
  const { consentVersion, maxSessionSeconds } = body;
69
82
  const botCheck = readBotCheck(body.botCheck);
70
83
  const questions = readQuestions(body.questions);
71
- if (available !== true || typeof consentVersion !== "string" || !chunkWSEQ2I6I_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
84
+ if (available !== true || typeof consentVersion !== "string" || !chunkXIZE37IB_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
72
85
  return INERT;
73
86
  }
87
+ const stt = readSttOffer(body.stt);
74
88
  return {
75
89
  kind: "available",
76
- offer: { consentVersion, maxSessionSeconds, botCheck, questions }
90
+ offer: {
91
+ consentVersion,
92
+ maxSessionSeconds,
93
+ botCheck,
94
+ questions,
95
+ ...stt === void 0 ? {} : { stt }
96
+ }
77
97
  };
78
98
  }
79
99
  const meaning = available ? void 0 : AVAILABILITY_REASONS.get(reason);
@@ -97,6 +117,21 @@ function errorCode(body) {
97
117
  const { code } = body.error;
98
118
  return typeof code === "string" ? code : null;
99
119
  }
120
+ var MAX_FOLLOW_UP_ANSWERS = 16;
121
+ var MAX_FOLLOW_UP_SECONDS = 600;
122
+ var FOLLOW_UP_KEYS = ["idleSeconds", "maxAnswerWaitSeconds", "maxAnswers"];
123
+ function isWholeBetween(value, max) {
124
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= max;
125
+ }
126
+ function readFollowUps(value) {
127
+ if (!isRecord(value)) return void 0;
128
+ const keys = Object.keys(value).sort();
129
+ const { maxAnswers, idleSeconds, maxAnswerWaitSeconds } = value;
130
+ if (keys.length !== FOLLOW_UP_KEYS.length || keys.some((key, at) => key !== FOLLOW_UP_KEYS[at]) || !isWholeBetween(maxAnswers, MAX_FOLLOW_UP_ANSWERS) || !isWholeBetween(idleSeconds, MAX_FOLLOW_UP_SECONDS) || !isWholeBetween(maxAnswerWaitSeconds, MAX_FOLLOW_UP_SECONDS)) {
131
+ return void 0;
132
+ }
133
+ return { maxAnswers, idleSeconds, maxAnswerWaitSeconds };
134
+ }
100
135
  function readCreated(body) {
101
136
  if (!isRecord(body)) return FAILED;
102
137
  const { sessionId, endSecret } = body;
@@ -114,6 +149,7 @@ function readCreated(body) {
114
149
  if (typeof livekitUrl !== "string" || !livekitUrl.startsWith("wss://") || typeof participantToken !== "string" || participantToken === "" || typeof workerIdentity !== "string" || workerIdentity === "" || typeof locale !== "string" || typeof text !== "string") {
115
150
  return unusable;
116
151
  }
152
+ const followUps = readFollowUps(body.followUps);
117
153
  return {
118
154
  kind: "created",
119
155
  session: {
@@ -122,7 +158,8 @@ function readCreated(body) {
122
158
  endsAt,
123
159
  join: { livekitUrl, participantToken, workerIdentity },
124
160
  captions: { locale, text },
125
- excerpt: body.excerpt === true
161
+ excerpt: body.excerpt === true,
162
+ ...followUps === void 0 ? {} : { followUps }
126
163
  }
127
164
  };
128
165
  }
@@ -161,7 +198,14 @@ function deadline(timeoutMs, outer) {
161
198
  };
162
199
  }
163
200
  async function requestAvailability(target) {
164
- if (!chunkWSEQ2I6I_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
201
+ try {
202
+ return await requestAvailabilityAnswer(target);
203
+ } catch {
204
+ return INERT;
205
+ }
206
+ }
207
+ async function requestAvailabilityAnswer(target) {
208
+ if (!chunkXIZE37IB_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
165
209
  const query = new URLSearchParams({
166
210
  site: target.site,
167
211
  locale: target.locale
@@ -176,8 +220,6 @@ async function requestAvailability(target) {
176
220
  signal: timer.signal
177
221
  });
178
222
  return readAvailability(response.status, await readJson(response));
179
- } catch {
180
- return INERT;
181
223
  } finally {
182
224
  timer.done();
183
225
  }
@@ -248,6 +290,96 @@ function sendEnd(target) {
248
290
  } catch {
249
291
  }
250
292
  }
293
+ var TURN_REFUSALS = /* @__PURE__ */ new Map([
294
+ ["no_time", "no_time"],
295
+ ["max_answers", "max_answers"],
296
+ ["not_eligible", "not_eligible"],
297
+ ["answer_claimed", "answer_claimed"],
298
+ ["limits", "limits"],
299
+ ["paused", "paused"],
300
+ ["busy", "busy"]
301
+ ]);
302
+ var MAX_TURN_RETRY_AFTER_SECONDS = 600;
303
+ function retryAfterOf(body) {
304
+ if (!isRecord(body) || !isRecord(body.error)) return void 0;
305
+ const { retryAfterSeconds } = body.error;
306
+ return isWholeBetween(retryAfterSeconds, MAX_TURN_RETRY_AFTER_SECONDS) ? retryAfterSeconds : void 0;
307
+ }
308
+ var TURN_KEY = /^[A-Za-z0-9_-]{8,64}$/;
309
+ var LOST = { kind: "failed", lost: true };
310
+ var TURN_FAILED = { kind: "failed", lost: false };
311
+ function readAccepted(body) {
312
+ if (!isRecord(body) || body.accepted !== true || !isRecord(body.captions)) {
313
+ return LOST;
314
+ }
315
+ const { locale, text } = body.captions;
316
+ if (typeof locale !== "string" || typeof text !== "string") return LOST;
317
+ return {
318
+ kind: "accepted",
319
+ captions: { locale, text },
320
+ excerpt: body.excerpt === true
321
+ };
322
+ }
323
+ function readTurn(kind, status, body) {
324
+ if (status === 200) {
325
+ if (kind === "answer") return readAccepted(body);
326
+ return isRecord(body) && body.ok === true ? { kind: "ok" } : LOST;
327
+ }
328
+ const code = errorCode(body);
329
+ const refusal = code === null ? void 0 : TURN_REFUSALS.get(code);
330
+ if (refusal !== void 0) {
331
+ const retryAfterSeconds = retryAfterOf(body);
332
+ return retryAfterSeconds === void 0 ? { kind: "refused", reason: refusal } : { kind: "refused", reason: refusal, retryAfterSeconds };
333
+ }
334
+ if (status === 404) return { kind: "gone" };
335
+ return status >= 500 ? LOST : TURN_FAILED;
336
+ }
337
+ function requestTurn(target) {
338
+ const { kind, messageId, idempotencyKey } = target;
339
+ const nonce = newIdempotencyKey();
340
+ if (!SESSION_ID.test(target.sessionId) || !END_SECRET.test(target.secret) || nonce === null || (kind === "answer" ? messageId === void 0 || !MESSAGE_ID.test(messageId) : messageId !== void 0 || idempotencyKey !== void 0) || idempotencyKey !== void 0 && !TURN_KEY.test(idempotencyKey)) {
341
+ return Promise.resolve(TURN_FAILED);
342
+ }
343
+ const payload = JSON.stringify({
344
+ secret: target.secret,
345
+ kind,
346
+ nonce,
347
+ ...messageId === void 0 ? {} : { messageId },
348
+ ...idempotencyKey === void 0 ? {} : { idempotencyKey }
349
+ });
350
+ const controller = new AbortController();
351
+ const answer = (async () => {
352
+ try {
353
+ const response = await fetch(
354
+ `${target.base}/sessions/${encodeURIComponent(target.sessionId)}/turn`,
355
+ {
356
+ method: "POST",
357
+ credentials: "omit",
358
+ cache: "no-store",
359
+ headers: { ...target.headers },
360
+ body: payload,
361
+ signal: controller.signal
362
+ }
363
+ );
364
+ return readTurn(kind, response.status, await readJson(response));
365
+ } catch {
366
+ return LOST;
367
+ }
368
+ })();
369
+ return new Promise((resolve) => {
370
+ const timer = setTimeout(
371
+ () => {
372
+ controller.abort();
373
+ resolve(LOST);
374
+ },
375
+ kind === "answer" ? TURN_ANSWER_TIMEOUT_MS : TURN_TIMEOUT_MS
376
+ );
377
+ void answer.then((result) => {
378
+ clearTimeout(timer);
379
+ resolve(result);
380
+ });
381
+ });
382
+ }
251
383
  var BOT_BINDING_PREFIX = "boff-web-avatar-cdata/v1";
252
384
  function webCryptoSubtle() {
253
385
  const subtle = globalThis.crypto?.subtle;
@@ -345,7 +477,14 @@ var EN = {
345
477
  usedToday: "You've had today's avatar session. Answers will show here as text.",
346
478
  needsAnswerAny: "Ask a question here, and the avatar reads the answer aloud.",
347
479
  startNewChatDetailAny: "The avatar reads aloud only the first answer in a new chat started in the Avatar tab, provided it passes the speech checks.",
348
- notEligibleAny: "The avatar can't read this answer aloud. The answer and sources are in Chat."
480
+ notEligibleAny: "The avatar can't read this answer aloud. The answer and sources are in Chat.",
481
+ followUpIdle: "The avatar ends after about thirty seconds without a question.",
482
+ followUpNoTime: "There isn't enough time left in this session to read that answer. It's in Chat.",
483
+ followUpMaxAnswers: "The avatar has read all the answers it can in this session. This one is in Chat.",
484
+ followUpLast: "That was the last answer the avatar can read in this session.",
485
+ followUpLimits: "The live avatar has reached its limit for now. This answer is in Chat.",
486
+ followUpPaused: "The live avatar is paused right now. This answer is in Chat.",
487
+ followUpUnread: "The avatar couldn't read that answer. It's in Chat."
349
488
  };
350
489
  var ZH = {
351
490
  checking: "\u6B63\u5728\u68C0\u67E5\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u662F\u5426\u53EF\u7528\u2026",
@@ -406,7 +545,16 @@ var ZH = {
406
545
  usedToday: "\u60A8\u4ECA\u5929\u5DF2\u4F7F\u7528\u8FC7\u865A\u62DF\u5F62\u8C61\u3002\u56DE\u7B54\u5C06\u4EE5\u6587\u5B57\u663E\u793A\u5728\u8FD9\u91CC\u3002",
407
546
  needsAnswerAny: "\u5728\u8FD9\u91CC\u63D0\u95EE\uFF0C\u865A\u62DF\u5F62\u8C61\u4F1A\u6717\u8BFB\u56DE\u7B54\u3002",
408
547
  startNewChatDetailAny: "\u865A\u62DF\u5F62\u8C61\u53EA\u6717\u8BFB\u5728\u201C\u865A\u62DF\u5F62\u8C61\u201D\u6807\u7B7E\u9875\u4E2D\u5F00\u59CB\u7684\u65B0\u804A\u5929\u7684\u7B2C\u4E00\u4E2A\u56DE\u7B54\uFF0C\u4E14\u8BE5\u56DE\u7B54\u987B\u901A\u8FC7\u8BED\u97F3\u68C0\u67E5\u3002",
409
- notEligibleAny: "\u865A\u62DF\u5F62\u8C61\u65E0\u6CD5\u6717\u8BFB\u6B64\u56DE\u7B54\u3002\u56DE\u7B54\u548C\u6765\u6E90\u5728\u804A\u5929\u4E2D\u3002"
548
+ notEligibleAny: "\u865A\u62DF\u5F62\u8C61\u65E0\u6CD5\u6717\u8BFB\u6B64\u56DE\u7B54\u3002\u56DE\u7B54\u548C\u6765\u6E90\u5728\u804A\u5929\u4E2D\u3002",
549
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
550
+ // (Stage D).
551
+ followUpIdle: "\u5982\u679C\u7EA6\u4E09\u5341\u79D2\u5185\u6CA1\u6709\u65B0\u95EE\u9898\uFF0C\u865A\u62DF\u5F62\u8C61\u5C31\u4F1A\u7ED3\u675F\u3002",
552
+ followUpNoTime: "\u672C\u6B21\u4F1A\u8BDD\u5269\u4F59\u65F6\u95F4\u4E0D\u8DB3\uFF0C\u65E0\u6CD5\u6717\u8BFB\u8BE5\u56DE\u7B54\u3002\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002",
553
+ followUpMaxAnswers: "\u865A\u62DF\u5F62\u8C61\u5DF2\u6717\u8BFB\u672C\u6B21\u4F1A\u8BDD\u53EF\u6717\u8BFB\u7684\u5168\u90E8\u56DE\u7B54\u3002\u6B64\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002",
554
+ followUpLast: "\u8FD9\u662F\u865A\u62DF\u5F62\u8C61\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u53EF\u6717\u8BFB\u7684\u6700\u540E\u4E00\u4E2A\u56DE\u7B54\u3002",
555
+ followUpLimits: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u6682\u65F6\u5DF2\u8FBE\u5230\u4F7F\u7528\u4E0A\u9650\u3002\u6B64\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002",
556
+ followUpPaused: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u76EE\u524D\u5DF2\u6682\u505C\u3002\u6B64\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002",
557
+ followUpUnread: "\u865A\u62DF\u5F62\u8C61\u65E0\u6CD5\u6717\u8BFB\u8BE5\u56DE\u7B54\u3002\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002"
410
558
  };
411
559
  var HI = {
412
560
  checking: "\u091C\u093E\u0901\u091A \u0939\u094B \u0930\u0939\u0940 \u0939\u0948 \u0915\u093F \u0932\u093E\u0907\u0935 \u0905\u0935\u0924\u093E\u0930 \u0909\u092A\u0932\u092C\u094D\u0927 \u0939\u0948 \u092F\u093E \u0928\u0939\u0940\u0902\u2026",
@@ -467,7 +615,16 @@ var HI = {
467
615
  usedToday: "\u0906\u091C \u0915\u093E \u0905\u0935\u0924\u093E\u0930 \u0938\u0924\u094D\u0930 \u0906\u092A \u0932\u0947 \u091A\u0941\u0915\u0947 \u0939\u0948\u0902\u0964 \u0909\u0924\u094D\u0924\u0930 \u092F\u0939\u093E\u0901 \u091F\u0947\u0915\u094D\u0938\u094D\u091F \u0915\u0947 \u0930\u0942\u092A \u092E\u0947\u0902 \u0926\u093F\u0916\u0947\u0902\u0917\u0947\u0964",
468
616
  needsAnswerAny: "\u092F\u0939\u093E\u0901 \u092A\u094D\u0930\u0936\u094D\u0928 \u092A\u0942\u091B\u0947\u0902, \u0914\u0930 \u0905\u0935\u0924\u093E\u0930 \u0909\u0924\u094D\u0924\u0930 \u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0947\u0917\u093E\u0964",
469
617
  startNewChatDetailAny: "\u0905\u0935\u0924\u093E\u0930 \u0915\u0947\u0935\u0932 \u0905\u0935\u0924\u093E\u0930 \u091F\u0948\u092C \u092E\u0947\u0902 \u0936\u0941\u0930\u0942 \u0915\u0940 \u0917\u0908 \u0928\u0908 \u091A\u0948\u091F \u0915\u093E \u092A\u0939\u0932\u093E \u0909\u0924\u094D\u0924\u0930 \u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0924\u093E \u0939\u0948, \u092C\u0936\u0930\u094D\u0924\u0947 \u0935\u0939 \u0935\u093E\u0923\u0940 \u091C\u093E\u0901\u091A \u092E\u0947\u0902 \u092A\u093E\u0938 \u0939\u094B\u0964",
470
- notEligibleAny: "\u0905\u0935\u0924\u093E\u0930 \u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u091C\u093C\u094B\u0930 \u0938\u0947 \u0928\u0939\u0940\u0902 \u092A\u0922\u093C \u0938\u0915\u0924\u093E\u0964 \u0909\u0924\u094D\u0924\u0930 \u0914\u0930 \u0938\u094D\u0930\u094B\u0924 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0902\u0964"
618
+ notEligibleAny: "\u0905\u0935\u0924\u093E\u0930 \u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u091C\u093C\u094B\u0930 \u0938\u0947 \u0928\u0939\u0940\u0902 \u092A\u0922\u093C \u0938\u0915\u0924\u093E\u0964 \u0909\u0924\u094D\u0924\u0930 \u0914\u0930 \u0938\u094D\u0930\u094B\u0924 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0902\u0964",
619
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
620
+ // (Stage D).
621
+ followUpIdle: "\u092C\u093F\u0928\u093E \u0915\u093F\u0938\u0940 \u092A\u094D\u0930\u0936\u094D\u0928 \u0915\u0947 \u0932\u0917\u092D\u0917 \u0924\u0940\u0938 \u0938\u0947\u0915\u0902\u0921 \u092C\u093E\u0926 \u0905\u0935\u0924\u093E\u0930 \u0938\u092E\u093E\u092A\u094D\u0924 \u0939\u094B \u091C\u093E\u0924\u093E \u0939\u0948\u0964",
622
+ followUpNoTime: "\u0907\u0938 \u0938\u0924\u094D\u0930 \u092E\u0947\u0902 \u0935\u0939 \u0909\u0924\u094D\u0924\u0930 \u092A\u0922\u093C\u0928\u0947 \u0915\u0947 \u0932\u093F\u090F \u092A\u0930\u094D\u092F\u093E\u092A\u094D\u0924 \u0938\u092E\u092F \u0928\u0939\u0940\u0902 \u092C\u091A\u093E \u0939\u0948\u0964 \u0909\u0924\u094D\u0924\u0930 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0964",
623
+ followUpMaxAnswers: "\u0905\u0935\u0924\u093E\u0930 \u0907\u0938 \u0938\u0924\u094D\u0930 \u092E\u0947\u0902 \u091C\u093F\u0924\u0928\u0947 \u0909\u0924\u094D\u0924\u0930 \u092A\u0922\u093C \u0938\u0915\u0924\u093E \u0939\u0948, \u0938\u092C \u092A\u0922\u093C \u091A\u0941\u0915\u093E \u0939\u0948\u0964 \u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0964",
624
+ followUpLast: "\u092F\u0939 \u0907\u0938 \u0938\u0924\u094D\u0930 \u092E\u0947\u0902 \u0905\u0935\u0924\u093E\u0930 \u0926\u094D\u0935\u093E\u0930\u093E \u092A\u0922\u093C\u093E \u091C\u093E \u0938\u0915\u0928\u0947 \u0935\u093E\u0932\u093E \u0905\u0902\u0924\u093F\u092E \u0909\u0924\u094D\u0924\u0930 \u0925\u093E\u0964",
625
+ followUpLimits: "\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 \u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0964",
626
+ followUpPaused: "\u0932\u093E\u0907\u0935 \u0905\u0935\u0924\u093E\u0930 \u0905\u092D\u0940 \u0930\u0941\u0915\u093E \u0939\u0941\u0906 \u0939\u0948\u0964 \u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0964",
627
+ followUpUnread: "\u0905\u0935\u0924\u093E\u0930 \u0935\u0939 \u0909\u0924\u094D\u0924\u0930 \u0928\u0939\u0940\u0902 \u092A\u0922\u093C \u0938\u0915\u093E\u0964 \u0909\u0924\u094D\u0924\u0930 \u091A\u0948\u091F \u092E\u0947\u0902 \u0939\u0948\u0964"
471
628
  };
472
629
  var ES = {
473
630
  checking: "Comprobando si el avatar en vivo est\xE1 disponible\u2026",
@@ -528,7 +685,16 @@ var ES = {
528
685
  usedToday: "Ya has usado la sesi\xF3n de avatar de hoy. Las respuestas se mostrar\xE1n aqu\xED como texto.",
529
686
  needsAnswerAny: "Haz una pregunta aqu\xED y el avatar leer\xE1 la respuesta en voz alta.",
530
687
  startNewChatDetailAny: "El avatar solo lee en voz alta la primera respuesta de un chat nuevo iniciado en la pesta\xF1a Avatar, si supera las comprobaciones de voz.",
531
- notEligibleAny: "El avatar no puede leer esta respuesta en voz alta. La respuesta y las fuentes est\xE1n en el chat."
688
+ notEligibleAny: "El avatar no puede leer esta respuesta en voz alta. La respuesta y las fuentes est\xE1n en el chat.",
689
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
690
+ // (Stage D).
691
+ followUpIdle: "El avatar termina tras unos treinta segundos sin preguntas.",
692
+ followUpNoTime: "No queda tiempo suficiente en esta sesi\xF3n para leer esa respuesta. Est\xE1 en el chat.",
693
+ followUpMaxAnswers: "El avatar ya ha le\xEDdo todas las respuestas que puede en esta sesi\xF3n. Esta est\xE1 en el chat.",
694
+ followUpLast: "Esa fue la \xFAltima respuesta que el avatar puede leer en esta sesi\xF3n.",
695
+ followUpLimits: "El avatar en vivo ha alcanzado su l\xEDmite por ahora. Esta respuesta est\xE1 en el chat.",
696
+ followUpPaused: "El avatar en vivo est\xE1 en pausa ahora mismo. Esta respuesta est\xE1 en el chat.",
697
+ followUpUnread: "El avatar no pudo leer esa respuesta. Est\xE1 en el chat."
532
698
  };
533
699
  var AR = {
534
700
  checking: "\u062C\u0627\u0631\u064D \u0627\u0644\u062A\u062D\u0642\u0642 \u0645\u0646 \u062A\u0648\u0641\u0631 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0645\u0628\u0627\u0634\u0631\u2026",
@@ -589,7 +755,16 @@ var AR = {
589
755
  usedToday: "\u0644\u0642\u062F \u0627\u0633\u062A\u062E\u062F\u0645\u062A \u062C\u0644\u0633\u0629 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0644\u0647\u0630\u0627 \u0627\u0644\u064A\u0648\u0645. \u0633\u062A\u0638\u0647\u0631 \u0627\u0644\u0625\u062C\u0627\u0628\u0627\u062A \u0647\u0646\u0627 \u0646\u0635\u064B\u0627.",
590
756
  needsAnswerAny: "\u0627\u0637\u0631\u062D \u0633\u0624\u0627\u0644\u064B\u0627 \u0647\u0646\u0627\u060C \u0648\u0633\u064A\u0642\u0631\u0623 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D.",
591
757
  startNewChatDetailAny: "\u064A\u0642\u0631\u0623 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0627\u0644\u0623\u0648\u0644\u0649 \u0641\u0642\u0637 \u0641\u064A \u062F\u0631\u062F\u0634\u0629 \u062C\u062F\u064A\u062F\u0629 \u0628\u062F\u0623\u062A \u0641\u064A \u0639\u0644\u0627\u0645\u0629 \u062A\u0628\u0648\u064A\u0628 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631\u060C \u0628\u0634\u0631\u0637 \u0623\u0646 \u062A\u062C\u062A\u0627\u0632 \u0641\u062D\u0648\u0635 \u0627\u0644\u0646\u0637\u0642.",
592
- notEligibleAny: "\u0644\u0627 \u064A\u0645\u0643\u0646 \u0644\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0642\u0631\u0627\u0621\u0629 \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D. \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0648\u0627\u0644\u0645\u0635\u0627\u062F\u0631 \u0645\u0648\u062C\u0648\u062F\u0629 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629."
758
+ notEligibleAny: "\u0644\u0627 \u064A\u0645\u0643\u0646 \u0644\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0642\u0631\u0627\u0621\u0629 \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D. \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0648\u0627\u0644\u0645\u0635\u0627\u062F\u0631 \u0645\u0648\u062C\u0648\u062F\u0629 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629.",
759
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
760
+ // (Stage D).
761
+ followUpIdle: "\u064A\u0646\u062A\u0647\u064A \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0628\u0639\u062F \u0646\u062D\u0648 \u062B\u0644\u0627\u062B\u064A\u0646 \u062B\u0627\u0646\u064A\u0629 \u0645\u0646 \u062F\u0648\u0646 \u0633\u0624\u0627\u0644.",
762
+ followUpNoTime: "\u0644\u0645 \u064A\u062A\u0628\u0642\u064E\u0651 \u0648\u0642\u062A \u0643\u0627\u0641\u064D \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u062C\u0644\u0633\u0629 \u0644\u0642\u0631\u0627\u0621\u0629 \u062A\u0644\u0643 \u0627\u0644\u0625\u062C\u0627\u0628\u0629. \u0625\u0646\u0647\u0627 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629.",
763
+ followUpMaxAnswers: "\u0642\u0631\u0623 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0643\u0644 \u0627\u0644\u0625\u062C\u0627\u0628\u0627\u062A \u0627\u0644\u062A\u064A \u064A\u0645\u0643\u0646\u0647 \u0642\u0631\u0627\u0621\u062A\u0647\u0627 \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u062C\u0644\u0633\u0629. \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629.",
764
+ followUpLast: "\u0643\u0627\u0646\u062A \u062A\u0644\u0643 \u0622\u062E\u0631 \u0625\u062C\u0627\u0628\u0629 \u064A\u0645\u0643\u0646 \u0644\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0642\u0631\u0627\u0621\u062A\u0647\u0627 \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u062C\u0644\u0633\u0629.",
765
+ followUpLimits: "\u0628\u0644\u063A \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0645\u0628\u0627\u0634\u0631 \u062D\u062F\u0647 \u0627\u0644\u0622\u0646. \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629.",
766
+ followUpPaused: "\u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0627\u0644\u0645\u0628\u0627\u0634\u0631 \u0645\u062A\u0648\u0642\u0641 \u0645\u0624\u0642\u062A\u064B\u0627 \u0627\u0644\u0622\u0646. \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629.",
767
+ followUpUnread: "\u062A\u0639\u0630\u0651\u0631 \u0639\u0644\u0649 \u0627\u0644\u0623\u0641\u0627\u062A\u0627\u0631 \u0642\u0631\u0627\u0621\u0629 \u062A\u0644\u0643 \u0627\u0644\u0625\u062C\u0627\u0628\u0629. \u0625\u0646\u0647\u0627 \u0641\u064A \u0627\u0644\u062F\u0631\u062F\u0634\u0629."
593
768
  };
594
769
  var TA = {
595
770
  checking: "\u0BA8\u0BC7\u0BB0\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0B95\u0BBF\u0B9F\u0BC8\u0B95\u0BCD\u0B95\u0BBF\u0BB1\u0BA4\u0BBE \u0B8E\u0BA9\u0B9A\u0BCD \u0B9A\u0BB0\u0BBF\u0BAA\u0BBE\u0BB0\u0BCD\u0B95\u0BCD\u0B95\u0BBF\u0BB1\u0BA4\u0BC1\u2026",
@@ -650,7 +825,16 @@ var TA = {
650
825
  usedToday: "\u0B87\u0BA9\u0BCD\u0BB1\u0BC8\u0BAF \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0B85\u0BAE\u0BB0\u0BCD\u0BB5\u0BC8 \u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BAA\u0BAF\u0BA9\u0BCD\u0BAA\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4\u0BBF\u0BB5\u0BBF\u0B9F\u0BCD\u0B9F\u0BC0\u0BB0\u0BCD\u0B95\u0BB3\u0BCD. \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD\u0B95\u0BB3\u0BCD \u0B87\u0B99\u0BCD\u0B95\u0BC7 \u0B89\u0BB0\u0BC8\u0BAF\u0BBE\u0B95\u0B95\u0BCD \u0B95\u0BBE\u0B9F\u0BCD\u0B9F\u0BAA\u0BCD\u0BAA\u0B9F\u0BC1\u0BAE\u0BCD.",
651
826
  needsAnswerAny: "\u0B87\u0B99\u0BCD\u0B95\u0BC7 \u0B92\u0BB0\u0BC1 \u0B95\u0BC7\u0BB3\u0BCD\u0BB5\u0BBF\u0BAF\u0BC8\u0B95\u0BCD \u0B95\u0BC7\u0BB3\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD, \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0B89\u0BB0\u0B95\u0BCD\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95\u0BC1\u0BAE\u0BCD.",
652
827
  startNewChatDetailAny: "\u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0BA4\u0BBE\u0BB5\u0BB2\u0BBF\u0BB2\u0BCD \u0BA4\u0BCA\u0B9F\u0B99\u0BCD\u0B95\u0BBF\u0BAF \u0BAA\u0BC1\u0BA4\u0BBF\u0BAF \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BA9\u0BCD \u0BAE\u0BC1\u0BA4\u0BB2\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0BAE\u0B9F\u0BCD\u0B9F\u0BC1\u0BAE\u0BC7, \u0B85\u0BA4\u0BC1 \u0BAA\u0BC7\u0B9A\u0BCD\u0B9A\u0BC1\u0B9A\u0BCD \u0B9A\u0BB0\u0BBF\u0BAA\u0BBE\u0BB0\u0BCD\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BBF\u0BB2\u0BCD \u0BA4\u0BC7\u0BB1\u0BBF\u0BA9\u0BBE\u0BB2\u0BCD, \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0B89\u0BB0\u0B95\u0BCD\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95\u0BC1\u0BAE\u0BCD.",
653
- notEligibleAny: "\u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BBE\u0BB2\u0BCD \u0B89\u0BB0\u0B95\u0BCD\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BBE\u0BA4\u0BC1. \u0BAA\u0BA4\u0BBF\u0BB2\u0BC1\u0BAE\u0BCD \u0B86\u0BA4\u0BBE\u0BB0\u0B99\u0BCD\u0B95\u0BB3\u0BC1\u0BAE\u0BCD \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA9."
828
+ notEligibleAny: "\u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BBE\u0BB2\u0BCD \u0B89\u0BB0\u0B95\u0BCD\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BBE\u0BA4\u0BC1. \u0BAA\u0BA4\u0BBF\u0BB2\u0BC1\u0BAE\u0BCD \u0B86\u0BA4\u0BBE\u0BB0\u0B99\u0BCD\u0B95\u0BB3\u0BC1\u0BAE\u0BCD \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA9.",
829
+ // TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
830
+ // (Stage D).
831
+ followUpIdle: "\u0B95\u0BC7\u0BB3\u0BCD\u0BB5\u0BBF \u0B87\u0BB2\u0BCD\u0BB2\u0BBE\u0BAE\u0BB2\u0BCD \u0B9A\u0BC1\u0BAE\u0BBE\u0BB0\u0BCD \u0BAE\u0BC1\u0BAA\u0BCD\u0BAA\u0BA4\u0BC1 \u0BB5\u0BBF\u0BA9\u0BBE\u0B9F\u0BBF\u0B95\u0BB3\u0BCD \u0B95\u0BB4\u0BBF\u0BA4\u0BCD\u0BA4\u0BC1 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0BAE\u0BC1\u0B9F\u0BBF\u0BB5\u0B9F\u0BC8\u0BAF\u0BC1\u0BAE\u0BCD.",
832
+ followUpNoTime: "\u0B85\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BC8\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95 \u0B87\u0BA8\u0BCD\u0BA4 \u0B85\u0BAE\u0BB0\u0BCD\u0BB5\u0BBF\u0BB2\u0BCD \u0BAA\u0BCB\u0BA4\u0BC1\u0BAE\u0BBE\u0BA9 \u0BA8\u0BC7\u0BB0\u0BAE\u0BCD \u0B87\u0BB2\u0BCD\u0BB2\u0BC8. \u0B85\u0BA4\u0BC1 \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1.",
833
+ followUpMaxAnswers: "\u0B87\u0BA8\u0BCD\u0BA4 \u0B85\u0BAE\u0BB0\u0BCD\u0BB5\u0BBF\u0BB2\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95\u0B95\u0BCD\u0B95\u0BC2\u0B9F\u0BBF\u0BAF \u0B8E\u0BB2\u0BCD\u0BB2\u0BBE\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD\u0B95\u0BB3\u0BC8\u0BAF\u0BC1\u0BAE\u0BCD \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0BAA\u0B9F\u0BBF\u0BA4\u0BCD\u0BA4\u0BC1\u0BB5\u0BBF\u0B9F\u0BCD\u0B9F\u0BA4\u0BC1. \u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1.",
834
+ followUpLast: "\u0B87\u0BA8\u0BCD\u0BA4 \u0B85\u0BAE\u0BB0\u0BCD\u0BB5\u0BBF\u0BB2\u0BCD \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95\u0B95\u0BCD\u0B95\u0BC2\u0B9F\u0BBF\u0BAF \u0B95\u0B9F\u0BC8\u0B9A\u0BBF \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B85\u0BA4\u0BC1\u0BA4\u0BBE\u0BA9\u0BCD.",
835
+ followUpLimits: "\u0BA8\u0BC7\u0BB0\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0BA4\u0BB1\u0BCD\u0BAA\u0BCB\u0BA4\u0BC1 \u0B85\u0BA4\u0BA9\u0BCD \u0BB5\u0BB0\u0BAE\u0BCD\u0BAA\u0BC8 \u0B8E\u0B9F\u0BCD\u0B9F\u0BBF\u0BB5\u0BBF\u0B9F\u0BCD\u0B9F\u0BA4\u0BC1. \u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1.",
836
+ followUpPaused: "\u0BA8\u0BC7\u0BB0\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BCD \u0BA4\u0BB1\u0BCD\u0BAA\u0BCB\u0BA4\u0BC1 \u0B87\u0B9F\u0BC8\u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BAA\u0BCD\u0BAA\u0B9F\u0BCD\u0B9F\u0BC1\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1. \u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1.",
837
+ followUpUnread: "\u0B85\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0B85\u0BB5\u0BA4\u0BBE\u0BB0\u0BBE\u0BB2\u0BCD \u0BAA\u0B9F\u0BBF\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8. \u0B85\u0BA4\u0BC1 \u0B85\u0BB0\u0B9F\u0BCD\u0B9F\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1."
654
838
  };
655
839
  var STRINGS = {
656
840
  en: EN,
@@ -800,12 +984,378 @@ var CONSENT = /* @__PURE__ */ new Map([
800
984
  questions: "any"
801
985
  }
802
986
  }
987
+ ],
988
+ [
989
+ // Stage D (BOFF-9797), a DRAFT for the operator's review that no environment uses yet (global-speech-svc reserves the
990
+ // same two versions): web-consent-2026-11-12-b with only its intro changed. The avatar reads the answer to each
991
+ // question asked in the Avatar tab, in one session, waits about thirty seconds after an answer for the next question,
992
+ // then ends; the session lasts at most three minutes, and an answer that does not fit the time left is read in part
993
+ // (and said so) or not read, and stays in Chat. The notice files of record and their digests are a gitops change of
994
+ // their own, made with the review.
995
+ "web-consent-2026-11-19-d",
996
+ {
997
+ en: {
998
+ title: "Live AI avatar (beta)",
999
+ introProduct: null,
1000
+ intro: "This is an AI avatar, not a real person. It reads aloud the answers to the questions you ask in the Avatar tab, provided each passes the speech checks. After an answer, it waits about thirty seconds for your next question, then ends. A session lasts at most three minutes; if an answer doesn't fit in the time left, it reads only the first part and says so, or doesn't read it, and the full answer stays in Chat. The answers are AI-generated and can be wrong. Don't put personal details in a question you type.",
1001
+ 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.",
1002
+ 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.",
1003
+ privacyLink: "How we handle this",
1004
+ botCheck: "turnstile",
1005
+ questions: "any",
1006
+ followUps: true
1007
+ }
1008
+ }
1009
+ ],
1010
+ [
1011
+ // The words of web-consent-2026-11-19-d without "When you start, Cloudflare checks for bots.", for a server that runs
1012
+ // no bot check. A DRAFT, like it.
1013
+ "web-consent-2026-11-20-d",
1014
+ {
1015
+ en: {
1016
+ title: "Live AI avatar (beta)",
1017
+ introProduct: null,
1018
+ intro: "This is an AI avatar, not a real person. It reads aloud the answers to the questions you ask in the Avatar tab, provided each passes the speech checks. After an answer, it waits about thirty seconds for your next question, then ends. A session lasts at most three minutes; if an answer doesn't fit in the time left, it reads only the first part and says so, or doesn't read it, and the full answer stays in Chat. The answers are AI-generated and can be wrong. Don't put personal details in a question you type.",
1019
+ 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.",
1020
+ 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.",
1021
+ privacyLink: "How we handle this",
1022
+ botCheck: "off",
1023
+ questions: "any",
1024
+ followUps: true
1025
+ }
1026
+ }
803
1027
  ]
804
1028
  ]);
805
1029
  function avatarConsentCopy(version, locale) {
806
- if (!chunkWSEQ2I6I_js.isAvatarLiveLocaleReviewed(locale)) return null;
1030
+ if (!chunkXIZE37IB_js.isAvatarLiveLocaleReviewed(locale)) return null;
807
1031
  return CONSENT.get(version)?.[locale] ?? null;
808
1032
  }
1033
+ var MIC_EN = {
1034
+ micStart: "Ask by voice",
1035
+ micStop: "Stop recording",
1036
+ micAllow: "Use the microphone",
1037
+ micNotNow: "Not now",
1038
+ micStarting: "Starting the microphone\u2026",
1039
+ micListening: "Listening\u2026",
1040
+ micElapsed: "{elapsed} of {limit}",
1041
+ micFinishing: "Getting the text\u2026",
1042
+ micDone: "Check the text, then press Send.",
1043
+ micDoneLimit: "The recording stopped at thirty seconds. Check the text, then press Send.",
1044
+ micNoSpeech: "No words were heard. Try again, or type your question.",
1045
+ micDenied: "The microphone is blocked in this browser. You can type your question instead.",
1046
+ micUnsupported: "The microphone can't be used here. You can type your question instead.",
1047
+ micUnavailable: "Voice questions aren't available right now. You can type your question instead.",
1048
+ micLimit: "Voice questions have reached their limit for now. You can type your question instead.",
1049
+ micBusy: "Voice questions are busy right now. Try again in a moment, or type your question.",
1050
+ micFailed: "The microphone stopped before the text came. Try again, or type your question."
1051
+ };
1052
+ var MIC_STRINGS = { en: MIC_EN };
1053
+ function avatarMicStrings(locale) {
1054
+ if (!chunkXIZE37IB_js.isAvatarLiveLocaleReviewed(locale)) return null;
1055
+ return MIC_STRINGS[locale] ?? null;
1056
+ }
1057
+ var AVATAR_MIC_NOTICE_VERSION = "web-mic-2026-11-05";
1058
+ var MIC_NOTICE = /* @__PURE__ */ new Map([
1059
+ [
1060
+ AVATAR_MIC_NOTICE_VERSION,
1061
+ {
1062
+ en: {
1063
+ title: "Ask with your voice",
1064
+ processing: "While you speak, your voice is streamed to our speech service, and Google Cloud Speech-to-Text turns it into text in Google's EU region.",
1065
+ storage: "We don't store or log your audio, or the text made from it.",
1066
+ text: "The text appears in the box and stays in this browser until you press Send. Nothing is sent by itself. Once you send it, it is a chat message, kept like any other.",
1067
+ limits: "Each recording stops after thirty seconds, and voice questions have daily limits.",
1068
+ privacyLink: "How we handle this"
1069
+ }
1070
+ }
1071
+ ]
1072
+ ]);
1073
+ function avatarMicNotice(version, locale) {
1074
+ if (!chunkXIZE37IB_js.isAvatarLiveLocaleReviewed(locale)) return null;
1075
+ return MIC_NOTICE.get(version)?.[locale] ?? null;
1076
+ }
1077
+ var PRIVACY_HREF = "/privacy#live-avatar";
1078
+ var LIMIT_SECONDS = 30;
1079
+ var TICK_MS = 250;
1080
+ var IDLE = { name: "idle" };
1081
+ var FOR_GOOD = /* @__PURE__ */ new Set([
1082
+ "unavailable",
1083
+ "limit"
1084
+ ]);
1085
+ var FAILURE_TEXT = {
1086
+ denied: "micDenied",
1087
+ unsupported: "micUnsupported",
1088
+ unavailable: "micUnavailable",
1089
+ limit: "micLimit",
1090
+ busy: "micBusy",
1091
+ failed: "micFailed"
1092
+ };
1093
+ function loadAvatarMic() {
1094
+ return import('../avatar-mic/index.js').then((loaded) => ({
1095
+ createAvatarMic: loaded.createAvatarMic
1096
+ }));
1097
+ }
1098
+ function canUseAvatarMic() {
1099
+ return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function" && typeof AudioContext !== "undefined" && typeof AudioWorkletNode !== "undefined" && typeof WebSocket !== "undefined";
1100
+ }
1101
+ function clock(seconds) {
1102
+ const whole = Math.max(0, Math.floor(seconds));
1103
+ return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
1104
+ }
1105
+ function withHeardText(current, heard, maxLength) {
1106
+ const text = heard.trim();
1107
+ if (text === "") return current;
1108
+ const joined = current.trim() === "" ? text : `${current.replace(/\s+$/, "")} ${text}`;
1109
+ return joined.slice(0, Math.max(0, maxLength));
1110
+ }
1111
+ function useAvatarMic({
1112
+ enabled,
1113
+ locale,
1114
+ base,
1115
+ offer,
1116
+ refreshOffer,
1117
+ onText,
1118
+ onStart
1119
+ }) {
1120
+ const S = avatarMicStrings(locale);
1121
+ const copy = avatarMicNotice(AVATAR_MIC_NOTICE_VERSION, locale);
1122
+ const uid = react.useId();
1123
+ const noticeTitleId = `${uid}-mic-notice`;
1124
+ const [withdrawn, setWithdrawn] = react.useState(null);
1125
+ const [supported, setSupported] = react.useState(false);
1126
+ react.useEffect(() => setSupported(canUseAvatarMic()), []);
1127
+ const possible = enabled && supported && base !== null && S !== null && copy !== null;
1128
+ const offered = possible && withdrawn === null;
1129
+ const [state, setState] = react.useState(IDLE);
1130
+ const [noticeOpen, setNoticeOpen] = react.useState(false);
1131
+ const agreedRef = react.useRef(false);
1132
+ const [loading2, setLoading] = react.useState(false);
1133
+ const [loadFailed, setLoadFailed] = react.useState(false);
1134
+ const [now, setNow] = react.useState(() => Date.now());
1135
+ const micRef = react.useRef(null);
1136
+ const seqRef = react.useRef(0);
1137
+ const buttonRef = react.useRef(null);
1138
+ const allowRef = react.useRef(null);
1139
+ const usedEpochRef = react.useRef(0);
1140
+ const latest = react.useRef({ offer, refreshOffer, onText, onStart, base });
1141
+ latest.current = { offer, refreshOffer, onText, onStart, base };
1142
+ const cancel = react.useCallback(() => {
1143
+ seqRef.current += 1;
1144
+ setLoading(false);
1145
+ setLoadFailed(false);
1146
+ setNoticeOpen(false);
1147
+ micRef.current?.mic.cancel();
1148
+ }, []);
1149
+ react.useEffect(() => {
1150
+ if (!offered) cancel();
1151
+ }, [offered, cancel]);
1152
+ react.useEffect(
1153
+ () => () => {
1154
+ seqRef.current += 1;
1155
+ micRef.current?.mic.cancel();
1156
+ },
1157
+ []
1158
+ );
1159
+ const micFor = react.useCallback(async (target) => {
1160
+ const held = micRef.current;
1161
+ if (held !== null && held.base === target) return held.mic;
1162
+ held?.mic.cancel();
1163
+ const loaded = await loadAvatarMic();
1164
+ const mic = loaded.createAvatarMic({
1165
+ base: target,
1166
+ notice: AVATAR_MIC_NOTICE_VERSION,
1167
+ offer: () => latest.current.offer,
1168
+ refreshOffer: () => latest.current.refreshOffer(),
1169
+ onState: setState
1170
+ });
1171
+ micRef.current = { base: target, mic };
1172
+ return mic;
1173
+ }, []);
1174
+ const press = react.useCallback(() => {
1175
+ const target = latest.current.base;
1176
+ if (target === null) return;
1177
+ seqRef.current += 1;
1178
+ const seq = seqRef.current;
1179
+ setLoadFailed(false);
1180
+ setLoading(true);
1181
+ void micFor(target).then(
1182
+ (mic) => {
1183
+ if (seqRef.current !== seq) return;
1184
+ setLoading(false);
1185
+ mic.start();
1186
+ },
1187
+ () => {
1188
+ if (seqRef.current !== seq) return;
1189
+ setLoading(false);
1190
+ setLoadFailed(true);
1191
+ }
1192
+ );
1193
+ }, [micFor]);
1194
+ const recording = loading2 || state.name === "starting" || state.name === "listening";
1195
+ const tap = react.useCallback(() => {
1196
+ if (state.name === "listening" || state.name === "starting") {
1197
+ micRef.current?.mic.stop();
1198
+ return;
1199
+ }
1200
+ if (loading2) {
1201
+ cancel();
1202
+ return;
1203
+ }
1204
+ latest.current.onStart?.();
1205
+ if (!agreedRef.current) {
1206
+ setNoticeOpen(true);
1207
+ void loadAvatarMic().catch(() => void 0);
1208
+ return;
1209
+ }
1210
+ press();
1211
+ }, [cancel, loading2, press, state.name]);
1212
+ const allow = react.useCallback(() => {
1213
+ agreedRef.current = true;
1214
+ setNoticeOpen(false);
1215
+ buttonRef.current?.focus();
1216
+ press();
1217
+ }, [press]);
1218
+ const notNow = react.useCallback(() => {
1219
+ setNoticeOpen(false);
1220
+ buttonRef.current?.focus();
1221
+ }, []);
1222
+ react.useEffect(() => {
1223
+ if (noticeOpen) allowRef.current?.focus();
1224
+ }, [noticeOpen]);
1225
+ const listeningSince = state.name === "listening" ? state.startedAt : null;
1226
+ react.useEffect(() => {
1227
+ if (listeningSince === null) return;
1228
+ setNow(Date.now());
1229
+ const timer = setInterval(() => setNow(Date.now()), TICK_MS);
1230
+ return () => clearInterval(timer);
1231
+ }, [listeningSince]);
1232
+ react.useEffect(() => {
1233
+ if (state.name === "done" && state.epoch !== usedEpochRef.current) {
1234
+ usedEpochRef.current = state.epoch;
1235
+ if (state.text !== "") latest.current.onText(state.text);
1236
+ } else if (state.name === "failed" && FOR_GOOD.has(state.reason)) {
1237
+ setWithdrawn(state.reason);
1238
+ }
1239
+ }, [state]);
1240
+ if (!possible || S === null || copy === null) {
1241
+ return {
1242
+ button: null,
1243
+ notice: null,
1244
+ lines: null,
1245
+ cancel,
1246
+ recording: false
1247
+ };
1248
+ }
1249
+ const status = withdrawn !== null ? S[FAILURE_TEXT[withdrawn]] : loading2 ? S.micStarting : loadFailed ? S.micFailed : state.name === "starting" ? S.micStarting : state.name === "listening" ? S.micListening : state.name === "finishing" ? S.micFinishing : state.name === "done" ? state.text === "" ? S.micNoSpeech : state.limit ? S.micDoneLimit : S.micDone : state.name === "failed" ? S[FAILURE_TEXT[state.reason]] : "";
1250
+ const heard = offered && (state.name === "listening" || state.name === "finishing") ? state.text : "";
1251
+ const button = !offered ? null : /* @__PURE__ */ jsxRuntime.jsx(
1252
+ "button",
1253
+ {
1254
+ ref: buttonRef,
1255
+ type: "button",
1256
+ "aria-label": recording ? S.micStop : S.micStart,
1257
+ "aria-pressed": recording,
1258
+ title: recording ? S.micStop : S.micStart,
1259
+ "data-boff-explore": "avatar-live-mic",
1260
+ "data-mic": loading2 ? "starting" : state.name,
1261
+ className: chunkC6OJIHNO_js.cn(
1262
+ "inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-md border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-reduce:transition-none",
1263
+ recording ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground"
1264
+ ),
1265
+ onClick: tap,
1266
+ children: recording ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { "aria-hidden": "true", className: "h-4 w-4" })
1267
+ }
1268
+ );
1269
+ const notice = offered && noticeOpen ? /* @__PURE__ */ jsxRuntime.jsxs(
1270
+ "section",
1271
+ {
1272
+ "aria-labelledby": noticeTitleId,
1273
+ "data-boff-explore": "avatar-live-mic-notice",
1274
+ className: "w-full rounded-lg border border-border p-3 text-start",
1275
+ children: [
1276
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { id: noticeTitleId, className: "text-sm font-semibold", children: copy.title }),
1277
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.processing }),
1278
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.storage }),
1279
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.text }),
1280
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.limits }),
1281
+ /* @__PURE__ */ jsxRuntime.jsx(
1282
+ "a",
1283
+ {
1284
+ href: PRIVACY_HREF,
1285
+ target: "_blank",
1286
+ rel: "noopener noreferrer",
1287
+ "data-boff-explore": "avatar-live-mic-privacy",
1288
+ className: "mt-1 inline-block text-xs font-medium text-primary underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1289
+ children: copy.privacyLink
1290
+ }
1291
+ ),
1292
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
1293
+ /* @__PURE__ */ jsxRuntime.jsx(
1294
+ "button",
1295
+ {
1296
+ ref: allowRef,
1297
+ type: "button",
1298
+ "data-boff-explore": "avatar-live-mic-allow",
1299
+ className: "inline-flex min-h-11 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-reduce:transition-none",
1300
+ onClick: allow,
1301
+ children: S.micAllow
1302
+ }
1303
+ ),
1304
+ /* @__PURE__ */ jsxRuntime.jsx(
1305
+ "button",
1306
+ {
1307
+ type: "button",
1308
+ "data-boff-explore": "avatar-live-mic-not-now",
1309
+ className: "inline-flex min-h-11 items-center justify-center rounded-md border border-border bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-reduce:transition-none",
1310
+ onClick: notNow,
1311
+ children: S.micNotNow
1312
+ }
1313
+ )
1314
+ ] })
1315
+ ]
1316
+ }
1317
+ ) : null;
1318
+ const listening = offered && state.name === "listening";
1319
+ const lines = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1320
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
1321
+ listening ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-hidden": "true", className: "relative flex h-2 w-2 shrink-0", children: [
1322
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75 motion-reduce:animate-none" }),
1323
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
1324
+ ] }) : null,
1325
+ /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", "data-boff-explore": "avatar-live-mic-status", children: status }),
1326
+ listening ? /* @__PURE__ */ jsxRuntime.jsx(
1327
+ "span",
1328
+ {
1329
+ role: "timer",
1330
+ "data-boff-explore": "avatar-live-mic-elapsed",
1331
+ className: "tabular-nums",
1332
+ children: chunkN7E6Z2GF_js.fill(S.micElapsed, {
1333
+ elapsed: clock(
1334
+ Math.min(LIMIT_SECONDS, (now - (listeningSince ?? now)) / 1e3)
1335
+ ),
1336
+ limit: clock(LIMIT_SECONDS)
1337
+ })
1338
+ }
1339
+ ) : null
1340
+ ] }),
1341
+ heard === "" ? null : /* @__PURE__ */ jsxRuntime.jsx(
1342
+ "p",
1343
+ {
1344
+ "data-boff-explore": "avatar-live-mic-heard",
1345
+ dir: "auto",
1346
+ className: "line-clamp-3 break-words text-sm italic text-muted-foreground",
1347
+ children: heard
1348
+ }
1349
+ )
1350
+ ] });
1351
+ return {
1352
+ button,
1353
+ notice,
1354
+ lines,
1355
+ cancel,
1356
+ recording: offered && state.name === "listening"
1357
+ };
1358
+ }
809
1359
 
810
1360
  // src/components/explore-avatar-turnstile.ts
811
1361
  var SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
@@ -921,12 +1471,18 @@ function runTurnstile(container, siteKey, action, cData) {
921
1471
  }
922
1472
  };
923
1473
  }
924
- var PRIVACY_HREF = "/privacy#live-avatar";
1474
+ var PRIVACY_HREF2 = "/privacy#live-avatar";
925
1475
  var SPEAKER_WAIT_MS = 3e4;
926
1476
  var END_GRACE_MS = 1e4;
927
1477
  var CLOCK_TOLERANCE_MS = 5e3;
928
1478
  var UNHEARD_GRACE_MS = 5e3;
929
1479
  var PLAYOUT_TAIL_MS = 5e3;
1480
+ var QUIET_PLAYED_OUT_MS = 2500;
1481
+ var OWED_VOICE_WAIT_MS = 15e3;
1482
+ var LISTENING_MARK_MS = 1e4;
1483
+ var BUSY_RETRY_MS = 2e3;
1484
+ var BUSY_RETRY_MAX_MS = 8e3;
1485
+ var MAX_BUSY_RETRIES = 24;
930
1486
  var SERVICE_MAX_AGE_MS = 15 * 6e4;
931
1487
  var ANSWER_OFFER_MS = SERVICE_MAX_AGE_MS - 6e4;
932
1488
  var MAX_TIMER_MS = 2147483647;
@@ -934,13 +1490,19 @@ var ASK_MAX_LENGTH = 300;
934
1490
  var MAX_REFERENCES = 64;
935
1491
  var USED_ON_KEY = "boff.explore.avatarUsedOn";
936
1492
  var PRIMARY = '[data-avatar-primary], [data-boff-explore="panel-continue-chat"]';
937
- var IDLE = { name: "idle" };
1493
+ var IDLE2 = { name: "idle" };
938
1494
  var INERT2 = { name: "inert" };
939
1495
  function stopAttending(handle) {
940
1496
  if (handle.unheard !== null) clearTimeout(handle.unheard);
941
1497
  if (handle.tail !== null) clearTimeout(handle.tail);
942
1498
  handle.unheard = null;
943
1499
  handle.tail = null;
1500
+ const run = handle.followUps;
1501
+ if (run === null) return;
1502
+ if (run.quiet !== null) clearTimeout(run.quiet);
1503
+ if (run.retry !== null) clearTimeout(run.retry);
1504
+ run.quiet = null;
1505
+ run.retry = null;
944
1506
  }
945
1507
  var REFUSAL_TEXT = {
946
1508
  busy: "busy",
@@ -951,6 +1513,18 @@ var REFUSAL_TEXT = {
951
1513
  not_eligible: "notEligible",
952
1514
  answer_claimed: "answerClaimed"
953
1515
  };
1516
+ var FOLLOW_UP_TEXT = {
1517
+ no_time: "followUpNoTime",
1518
+ max_answers: "followUpMaxAnswers",
1519
+ // The same line as a first answer the checks refuse: it stays in Chat as text.
1520
+ not_eligible: "notSpoken",
1521
+ answer_claimed: "answerClaimed",
1522
+ limits: "followUpLimits",
1523
+ paused: "followUpPaused",
1524
+ busy: "followUpUnread",
1525
+ answer_failed: "answerFailed",
1526
+ failed: "followUpUnread"
1527
+ };
954
1528
  var EMPTY = {
955
1529
  kind: "empty",
956
1530
  answerId: null,
@@ -1024,6 +1598,16 @@ function firstExchange(messages, rules = SUGGESTED_RULES) {
1024
1598
  function inFlight(conversation) {
1025
1599
  return conversation?.phase === "sending" || conversation?.phase === "streaming";
1026
1600
  }
1601
+ function acceptsQuestion(slot) {
1602
+ if (!slot.any || slot.textMode || slot.reason !== null) return false;
1603
+ return slot.phase === "needs-answer" ? slot.kind === "empty" : slot.phase === "live" && slot.followUp;
1604
+ }
1605
+ function followUpBox(phase, run, token) {
1606
+ return phase.name === "live" && run !== null && phase.session.followUp !== null && !phase.session.followUp.closed && token === run.token;
1607
+ }
1608
+ function micOfferOf(result) {
1609
+ return result.kind === "available" && result.offer.stt !== void 0 ? { ...result.offer.stt, receivedAt: Date.now() } : null;
1610
+ }
1027
1611
  function utcToday() {
1028
1612
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1029
1613
  }
@@ -1093,7 +1677,7 @@ async function loadModule(load) {
1093
1677
  }
1094
1678
  return loaded;
1095
1679
  }
1096
- function clock(seconds) {
1680
+ function clock2(seconds) {
1097
1681
  const whole = Math.max(0, Math.ceil(seconds));
1098
1682
  return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
1099
1683
  }
@@ -1146,7 +1730,7 @@ function AvatarLiveView({
1146
1730
  const shown = offeredAt(exchange, Date.now());
1147
1731
  const { answerId, startOver } = shown;
1148
1732
  const offerUntil = answerId === null ? null : exchange.offerUntil;
1149
- const [phase, setPhase] = react.useState(IDLE);
1733
+ const [phase, setPhase] = react.useState(IDLE2);
1150
1734
  const [blocked, setBlocked] = react.useState(false);
1151
1735
  const [now, setNow] = react.useState(() => Date.now());
1152
1736
  const [draft, setDraft] = react.useState("");
@@ -1156,7 +1740,7 @@ function AvatarLiveView({
1156
1740
  const [usedToday, setUsedToday] = react.useState(false);
1157
1741
  const showing = active && !off;
1158
1742
  const behindChat = open && !active && !off;
1159
- const phaseRef = react.useRef(IDLE);
1743
+ const phaseRef = react.useRef(IDLE2);
1160
1744
  const latest = react.useRef({
1161
1745
  live,
1162
1746
  locale,
@@ -1172,6 +1756,9 @@ function AvatarLiveView({
1172
1756
  const waitRef = react.useRef(null);
1173
1757
  const sendingRef = react.useRef(false);
1174
1758
  const textBoxRef = react.useRef(false);
1759
+ const [micOffer, setMicOffer] = react.useState(null);
1760
+ const micCancelRef = react.useRef(() => void 0);
1761
+ const sendDueRef = react.useRef(() => void 0);
1175
1762
  const checkRef = react.useRef(null);
1176
1763
  const askedLocale = react.useRef(locale);
1177
1764
  const startRef = react.useRef(null);
@@ -1260,10 +1847,13 @@ function AvatarLiveView({
1260
1847
  if (!handle || handle.done || latest.current.live.endUnattended !== true) {
1261
1848
  return;
1262
1849
  }
1263
- const current = phaseRef.current.name;
1264
- if (current !== "pending" && current !== "live") return;
1850
+ const shown2 = phaseRef.current;
1851
+ if (shown2.name !== "pending" && shown2.name !== "live") return;
1852
+ const current = shown2.name;
1265
1853
  const away = !inSight();
1266
- if (away && handle.playedOut && current === "live") {
1854
+ const followUp2 = shown2.session.followUp;
1855
+ const asking = followUp2 !== null && followUp2.pending !== null;
1856
+ if (away && handle.playedOut && current === "live" && !asking) {
1267
1857
  finish("finished", "user");
1268
1858
  return;
1269
1859
  }
@@ -1282,6 +1872,184 @@ function AvatarLiveView({
1282
1872
  }
1283
1873
  }, UNHEARD_GRACE_MS);
1284
1874
  }, [finish, inSight]);
1875
+ const showFollowUp = react.useCallback(
1876
+ (handle, next, spoken) => {
1877
+ const current = phaseRef.current;
1878
+ if (sessionRef.current !== handle || handle.done || current.name !== "pending" && current.name !== "live" || current.session.followUp === null) {
1879
+ return;
1880
+ }
1881
+ const session = {
1882
+ ...current.session,
1883
+ ...spoken,
1884
+ followUp: { ...current.session.followUp, ...next }
1885
+ };
1886
+ go(
1887
+ current.name === "live" ? { name: "live", session } : { name: "pending", session }
1888
+ );
1889
+ },
1890
+ [go]
1891
+ );
1892
+ const tell = react.useCallback(
1893
+ (handle, kind2) => {
1894
+ if (handle.done || handle.followUps === null) return;
1895
+ void requestTurn({
1896
+ base: handle.base,
1897
+ headers: handle.headers,
1898
+ sessionId: handle.session.sessionId,
1899
+ secret: handle.session.endSecret,
1900
+ kind: kind2
1901
+ });
1902
+ },
1903
+ []
1904
+ );
1905
+ const whenQuiet = react.useCallback(
1906
+ (handle, delay) => {
1907
+ const run = handle.followUps;
1908
+ if (run === null) return;
1909
+ const wait = (ms) => {
1910
+ if (run.quiet !== null) clearTimeout(run.quiet);
1911
+ run.quiet = setTimeout(() => {
1912
+ run.quiet = null;
1913
+ if (sessionRef.current !== handle || handle.done || run.voice) {
1914
+ return;
1915
+ }
1916
+ if (run.owed) {
1917
+ run.owed = false;
1918
+ wait(OWED_VOICE_WAIT_MS);
1919
+ return;
1920
+ }
1921
+ handle.playedOut = true;
1922
+ showFollowUp(handle, { speaking: false });
1923
+ attend();
1924
+ sendDueRef.current(handle);
1925
+ }, ms);
1926
+ };
1927
+ wait(delay);
1928
+ },
1929
+ [attend, showFollowUp]
1930
+ );
1931
+ const heardVoice = react.useCallback(
1932
+ (handle, speaking) => {
1933
+ const run = handle.followUps;
1934
+ if (run === null) return;
1935
+ run.voice = speaking;
1936
+ if (!speaking) {
1937
+ whenQuiet(handle, QUIET_PLAYED_OUT_MS);
1938
+ return;
1939
+ }
1940
+ if (run.quiet !== null) clearTimeout(run.quiet);
1941
+ run.quiet = null;
1942
+ run.heard = true;
1943
+ run.starts += 1;
1944
+ run.owed = false;
1945
+ handle.playedOut = false;
1946
+ showFollowUp(handle, { speaking: true });
1947
+ },
1948
+ [showFollowUp, whenQuiet]
1949
+ );
1950
+ const sendDue = react.useCallback(
1951
+ (handle) => {
1952
+ const run = handle.followUps;
1953
+ const due = run?.due ?? null;
1954
+ if (run === null || due === null || due.sending || run.retry !== null || handle.done || sessionRef.current !== handle || run.heard && !handle.playedOut) {
1955
+ return;
1956
+ }
1957
+ due.sending = true;
1958
+ const starts = run.starts;
1959
+ const read = () => requestTurn({
1960
+ base: handle.base,
1961
+ headers: handle.headers,
1962
+ sessionId: handle.session.sessionId,
1963
+ secret: handle.session.endSecret,
1964
+ kind: "answer",
1965
+ messageId: due.messageId,
1966
+ idempotencyKey: due.idempotencyKey
1967
+ });
1968
+ const notRead = (unread, closed) => {
1969
+ run.due = null;
1970
+ showFollowUp(handle, {
1971
+ pending: null,
1972
+ unread: due.known ?? unread,
1973
+ ...closed ? { closed } : {}
1974
+ });
1975
+ attend();
1976
+ };
1977
+ void (async () => {
1978
+ let result = await read();
1979
+ if (result.kind === "failed" && result.lost && !handle.done) {
1980
+ result = await read();
1981
+ }
1982
+ due.sending = false;
1983
+ if (sessionRef.current !== handle || handle.done || run.due !== due) {
1984
+ return;
1985
+ }
1986
+ if (result.kind === "refused" && result.reason === "busy" && due.busy < MAX_BUSY_RETRIES) {
1987
+ due.busy += 1;
1988
+ if (run.heard && !handle.playedOut) return;
1989
+ const delay = Math.max(
1990
+ result.retryAfterSeconds === void 0 ? BUSY_RETRY_MS : result.retryAfterSeconds * 1e3,
1991
+ Math.min(BUSY_RETRY_MAX_MS, BUSY_RETRY_MS * 2 ** (due.busy - 1))
1992
+ );
1993
+ if (Date.now() + delay >= handle.endsAt) {
1994
+ notRead("no_time", false);
1995
+ return;
1996
+ }
1997
+ run.retry = setTimeout(() => {
1998
+ run.retry = null;
1999
+ sendDueRef.current(handle);
2000
+ }, delay);
2001
+ return;
2002
+ }
2003
+ if (result.kind !== "accepted") {
2004
+ notRead(
2005
+ result.kind === "refused" ? result.reason : "failed",
2006
+ result.kind === "gone" || result.kind === "refused" && result.reason === "max_answers"
2007
+ );
2008
+ return;
2009
+ }
2010
+ run.due = null;
2011
+ run.answers += 1;
2012
+ const heardSince = run.starts !== starts;
2013
+ run.owed = !heardSince;
2014
+ const playing = !heardSince || run.voice || run.quiet !== null;
2015
+ if (playing) handle.playedOut = false;
2016
+ showFollowUp(
2017
+ handle,
2018
+ {
2019
+ speaking: playing,
2020
+ pending: null,
2021
+ unread: null,
2022
+ closed: run.answers >= run.limits.maxAnswers
2023
+ },
2024
+ { captions: result.captions, excerpt: result.excerpt }
2025
+ );
2026
+ if (!playing) {
2027
+ attend();
2028
+ return;
2029
+ }
2030
+ if (run.owed && run.heard && !run.voice) whenQuiet(handle, 0);
2031
+ })();
2032
+ },
2033
+ [attend, showFollowUp, whenQuiet]
2034
+ );
2035
+ sendDueRef.current = sendDue;
2036
+ const readFollowUp = react.useCallback(
2037
+ (handle, messageId, known) => {
2038
+ const run = handle.followUps;
2039
+ if (run === null) return;
2040
+ run.sent.add(messageId);
2041
+ const idempotencyKey = newIdempotencyKey();
2042
+ if (idempotencyKey === null) {
2043
+ showFollowUp(handle, { pending: null, unread: known ?? "failed" });
2044
+ attend();
2045
+ return;
2046
+ }
2047
+ run.due = { messageId, idempotencyKey, known, busy: 0, sending: false };
2048
+ showFollowUp(handle, { pending: "reading", unread: null });
2049
+ sendDue(handle);
2050
+ },
2051
+ [attend, sendDue, showFollowUp]
2052
+ );
1285
2053
  const cancelStart = react.useCallback((reason) => {
1286
2054
  const run = startRef.current;
1287
2055
  if (!run) return;
@@ -1292,6 +2060,7 @@ function AvatarLiveView({
1292
2060
  const applyAvailability = react.useCallback(
1293
2061
  (result, locale2) => {
1294
2062
  const { live: live2 } = latest.current;
2063
+ setMicOffer(micOfferOf(result));
1295
2064
  if (result.kind === "inert") {
1296
2065
  setMode("suggested");
1297
2066
  go(INERT2);
@@ -1309,7 +2078,7 @@ function AvatarLiveView({
1309
2078
  go(INERT2);
1310
2079
  return;
1311
2080
  }
1312
- if (result.offer.botCheck === "turnstile" && chunkWSEQ2I6I_js.turnstileSiteKeyOf(live2) === null) {
2081
+ if (result.offer.botCheck === "turnstile" && chunkXIZE37IB_js.turnstileSiteKeyOf(live2) === null) {
1313
2082
  setMode("suggested");
1314
2083
  go(INERT2);
1315
2084
  return;
@@ -1344,16 +2113,32 @@ function AvatarLiveView({
1344
2113
  });
1345
2114
  }, [applyAvailability, go]);
1346
2115
  const begin = react.useCallback(
1347
- (session, module, offer, target) => {
2116
+ (session, context) => {
2117
+ const { offer } = context;
1348
2118
  const received = Date.now();
1349
2119
  const longest = offer.maxSessionSeconds * 1e3;
1350
2120
  const ahead = session.endsAt - received;
1351
2121
  const endsAt = ahead > 0 && ahead <= longest + CLOCK_TOLERANCE_MS ? session.endsAt : received + longest;
2122
+ const followUps = session.followUps !== void 0 && offer.copy.followUps === true && offer.questions === "any" ? {
2123
+ limits: session.followUps,
2124
+ token: context.conversationToken,
2125
+ answers: 1,
2126
+ voice: false,
2127
+ heard: false,
2128
+ starts: 0,
2129
+ owed: true,
2130
+ quiet: null,
2131
+ asked: null,
2132
+ due: null,
2133
+ retry: null,
2134
+ sent: /* @__PURE__ */ new Set([context.messageId]),
2135
+ seq: 0
2136
+ } : null;
1352
2137
  sessionRef.current = {
1353
2138
  session,
1354
- base: target.base,
1355
- headers: target.headers,
1356
- module,
2139
+ base: context.base,
2140
+ headers: context.headers,
2141
+ module: context.module,
1357
2142
  room: null,
1358
2143
  joined: null,
1359
2144
  endsAt,
@@ -1363,6 +2148,7 @@ function AvatarLiveView({
1363
2148
  playedOut: false,
1364
2149
  unheard: null,
1365
2150
  tail: null,
2151
+ followUps,
1366
2152
  done: false
1367
2153
  };
1368
2154
  setBlocked(false);
@@ -1372,7 +2158,13 @@ function AvatarLiveView({
1372
2158
  session: {
1373
2159
  endsAt,
1374
2160
  captions: session.captions,
1375
- excerpt: session.excerpt
2161
+ excerpt: session.excerpt,
2162
+ followUp: followUps === null ? null : {
2163
+ speaking: true,
2164
+ pending: null,
2165
+ unread: null,
2166
+ closed: followUps.answers >= followUps.limits.maxAnswers
2167
+ }
1376
2168
  }
1377
2169
  });
1378
2170
  },
@@ -1404,7 +2196,7 @@ function AvatarLiveView({
1404
2196
  }
1405
2197
  switch (result.kind) {
1406
2198
  case "created":
1407
- begin(result.session, context.module, context.offer, context);
2199
+ begin(result.session, context);
1408
2200
  return;
1409
2201
  case "unusable":
1410
2202
  sendEnd({
@@ -1446,7 +2238,7 @@ function AvatarLiveView({
1446
2238
  return;
1447
2239
  }
1448
2240
  const refusedFrom = exchangeAtStart.offerUntil - ANSWER_OFFER_MS + SERVICE_MAX_AGE_MS;
1449
- const siteKey = offer.botCheck === "turnstile" ? chunkWSEQ2I6I_js.turnstileSiteKeyOf(live2) : null;
2241
+ const siteKey = offer.botCheck === "turnstile" ? chunkXIZE37IB_js.turnstileSiteKeyOf(live2) : null;
1450
2242
  if (offer.botCheck === "turnstile" && (siteKey === null || !canBindBotCheck())) {
1451
2243
  go(INERT2);
1452
2244
  return;
@@ -1502,7 +2294,14 @@ function AvatarLiveView({
1502
2294
  return;
1503
2295
  }
1504
2296
  if (turnstile !== null) go({ name: "creating" });
1505
- const context = { module: loaded, offer, messageId, base: base2, headers: headers2 };
2297
+ const context = {
2298
+ module: loaded,
2299
+ offer,
2300
+ messageId,
2301
+ conversationToken,
2302
+ base: base2,
2303
+ headers: headers2
2304
+ };
1506
2305
  const result = await requestCreate({
1507
2306
  base: base2,
1508
2307
  headers: headers2,
@@ -1607,6 +2406,7 @@ function AvatarLiveView({
1607
2406
  [applyAvailability, bindingHolds, cancelWaiting, exchangeNow, launch]
1608
2407
  );
1609
2408
  const stop = react.useCallback(() => {
2409
+ micCancelRef.current();
1610
2410
  const current = phaseRef.current;
1611
2411
  if (current.name === "waiting") {
1612
2412
  cancelWaiting(false);
@@ -1628,6 +2428,7 @@ function AvatarLiveView({
1628
2428
  }, [cancelStart, cancelWaiting, finish, go, reoffer]);
1629
2429
  const leave = react.useCallback(
1630
2430
  (event) => {
2431
+ micCancelRef.current();
1631
2432
  const current = phaseRef.current;
1632
2433
  const reason = event === "user" || event === "chat" ? "user" : "page_hide";
1633
2434
  const how = reason === "user" ? "stopped" : "interrupted";
@@ -1638,7 +2439,7 @@ function AvatarLiveView({
1638
2439
  const controller = checkRef.current;
1639
2440
  checkRef.current = null;
1640
2441
  controller?.abort();
1641
- go(IDLE);
2442
+ go(IDLE2);
1642
2443
  return;
1643
2444
  }
1644
2445
  case "waiting":
@@ -1646,7 +2447,7 @@ function AvatarLiveView({
1646
2447
  return;
1647
2448
  case "bot-check":
1648
2449
  cancelStart(reason);
1649
- go(reason === "user" ? IDLE : reoffer(current.offer));
2450
+ go(reason === "user" ? IDLE2 : reoffer(current.offer));
1650
2451
  return;
1651
2452
  case "creating":
1652
2453
  if (keepsSession) return;
@@ -1708,7 +2509,7 @@ function AvatarLiveView({
1708
2509
  } else {
1709
2510
  checkRef.current?.abort();
1710
2511
  checkRef.current = null;
1711
- go(IDLE);
2512
+ go(IDLE2);
1712
2513
  }
1713
2514
  }, [active, cancelWaiting, check, go, locale]);
1714
2515
  const kind = shown.kind;
@@ -1805,6 +2606,9 @@ function AvatarLiveView({
1805
2606
  attend();
1806
2607
  }, PLAYOUT_TAIL_MS);
1807
2608
  return;
2609
+ case "speaking":
2610
+ heardVoice(handle, event.speaking);
2611
+ return;
1808
2612
  case "speaker-left":
1809
2613
  finish(
1810
2614
  wasLive ? "finished" : "failed",
@@ -1817,7 +2621,7 @@ function AvatarLiveView({
1817
2621
  return;
1818
2622
  }
1819
2623
  },
1820
- [attend, finish, goLive, inSight]
2624
+ [attend, finish, goLive, heardVoice, inSight]
1821
2625
  );
1822
2626
  react.useEffect(() => {
1823
2627
  if (phase.name !== "pending") return;
@@ -1953,6 +2757,85 @@ function AvatarLiveView({
1953
2757
  const textMode = any && (usedToday && (phase.name === "needs-answer" || phase.name === "consent" || phase.name === "declined" || phase.name === "ended") || phase.name === "refused" && phase.reason === "limits");
1954
2758
  textBoxRef.current = textMode;
1955
2759
  const askState = !any ? void 0 : textMode ? "text" : phase.name === "needs-answer" ? kind === "empty" ? "ready" : kind : phase.name;
2760
+ const followUp = phase.name === "live" ? phase.session.followUp : null;
2761
+ const followUpAsks = followUpBox(
2762
+ phase,
2763
+ sessionRef.current?.followUps ?? null,
2764
+ token
2765
+ );
2766
+ const askReason = inFlight(conversation) || followUp !== null && followUp.pending !== null ? S.askBusy : conversation?.canSend ? null : S.askUnavailable;
2767
+ const refreshMicOffer = react.useCallback(async () => {
2768
+ const { live: live2, locale: locale2, base: base2, headers: headers2 } = latest.current;
2769
+ if (base2 === null) return null;
2770
+ const next = micOfferOf(
2771
+ await requestAvailabilityAnswer({
2772
+ base: base2,
2773
+ headers: headers2,
2774
+ site: live2.site,
2775
+ locale: locale2
2776
+ })
2777
+ );
2778
+ if (next !== null) setMicOffer(next);
2779
+ return next;
2780
+ }, []);
2781
+ const onMicText = react.useCallback(
2782
+ (text) => {
2783
+ setDraft((current) => withHeardText(current, text, askMaxLength));
2784
+ if (touchFirst) return;
2785
+ rootRef.current?.querySelector(
2786
+ '[data-boff-explore="avatar-live-ask-input"]'
2787
+ )?.focus();
2788
+ },
2789
+ [askMaxLength, touchFirst]
2790
+ );
2791
+ const onMicStart = react.useCallback(() => {
2792
+ const handle = sessionRef.current;
2793
+ const current = phaseRef.current;
2794
+ if (handle === null || handle.followUps === null || handle.done || current.name !== "live" || current.session.followUp?.speaking !== true) {
2795
+ return;
2796
+ }
2797
+ const run = handle.followUps;
2798
+ tell(handle, "interrupt");
2799
+ if (run.quiet !== null) clearTimeout(run.quiet);
2800
+ run.quiet = null;
2801
+ run.owed = false;
2802
+ handle.playedOut = true;
2803
+ showFollowUp(handle, { speaking: false });
2804
+ }, [showFollowUp, tell]);
2805
+ const mic = useAvatarMic({
2806
+ enabled: live.mic === true && micOffer !== null && !off && acceptsQuestion({
2807
+ any,
2808
+ phase: phase.name,
2809
+ kind,
2810
+ textMode,
2811
+ reason: askReason,
2812
+ followUp: followUpAsks
2813
+ }),
2814
+ locale,
2815
+ base,
2816
+ offer: micOffer,
2817
+ refreshOffer: refreshMicOffer,
2818
+ onText: onMicText,
2819
+ onStart: onMicStart
2820
+ });
2821
+ micCancelRef.current = mic.cancel;
2822
+ const micRecording = mic.recording;
2823
+ react.useEffect(() => {
2824
+ const handle = sessionRef.current;
2825
+ if (!micRecording || handle === null || handle.followUps === null || handle.done) {
2826
+ return;
2827
+ }
2828
+ tell(handle, "listening");
2829
+ const every = Math.min(
2830
+ LISTENING_MARK_MS,
2831
+ Math.max(1e3, handle.followUps.limits.idleSeconds * 1e3 / 3)
2832
+ );
2833
+ const timer = setInterval(() => tell(handle, "listening"), every);
2834
+ return () => {
2835
+ clearInterval(timer);
2836
+ if (sessionRef.current === handle) tell(handle, "listening");
2837
+ };
2838
+ }, [micRecording, tell]);
1956
2839
  const notNow = react.useCallback(() => {
1957
2840
  const current = phaseRef.current;
1958
2841
  if ((current.name === "consent" || current.name === "needs-answer") && current.offer.questions === "any") {
@@ -1961,6 +2844,43 @@ function AvatarLiveView({
1961
2844
  }
1962
2845
  onContinueInChat();
1963
2846
  }, [go, onContinueInChat]);
2847
+ const askFollowUp = react.useCallback(
2848
+ (handle, conversation2, question) => {
2849
+ const run = handle.followUps;
2850
+ const current = phaseRef.current;
2851
+ if (run === null || handle.done || current.name !== "live" || current.session.followUp?.pending !== null || !followUpBox(current, run, conversation2.getConversationToken())) {
2852
+ return;
2853
+ }
2854
+ sendingRef.current = true;
2855
+ run.seq += 1;
2856
+ const seq = run.seq;
2857
+ setDraft("");
2858
+ tell(handle, "asking");
2859
+ showFollowUp(handle, { pending: "asking", unread: null });
2860
+ void conversation2.send(question, [], { surface: "AVATAR" }).then((added) => {
2861
+ if (added === null) {
2862
+ setDraft((current2) => current2 === "" ? question : current2);
2863
+ }
2864
+ if (sessionRef.current !== handle || handle.done || run.seq !== seq) {
2865
+ return;
2866
+ }
2867
+ if (added === null || added.conversationToken !== run.token) {
2868
+ showFollowUp(handle, { pending: null });
2869
+ attend();
2870
+ return;
2871
+ }
2872
+ run.asked = {
2873
+ messageId: added.userMessageId,
2874
+ token: added.conversationToken,
2875
+ answerId: null
2876
+ };
2877
+ showFollowUp(handle, { pending: "asking" });
2878
+ }).finally(() => {
2879
+ sendingRef.current = false;
2880
+ });
2881
+ },
2882
+ [attend, showFollowUp, tell]
2883
+ );
1964
2884
  const ask = react.useCallback(
1965
2885
  (text) => {
1966
2886
  const conversation2 = conversationRef.current;
@@ -1969,6 +2889,11 @@ function AvatarLiveView({
1969
2889
  return;
1970
2890
  }
1971
2891
  const spoken = !textBoxRef.current;
2892
+ const handle = sessionRef.current;
2893
+ if (spoken && handle !== null && handle.followUps !== null) {
2894
+ askFollowUp(handle, conversation2, question);
2895
+ return;
2896
+ }
1972
2897
  if (spoken && conversation2.messages.length > 0) return;
1973
2898
  sendingRef.current = true;
1974
2899
  setAskedHere(null);
@@ -1990,8 +2915,64 @@ function AvatarLiveView({
1990
2915
  sendingRef.current = false;
1991
2916
  });
1992
2917
  },
1993
- [setAskedHere]
2918
+ [askFollowUp, setAskedHere]
1994
2919
  );
2920
+ react.useEffect(() => {
2921
+ const handle = sessionRef.current;
2922
+ const run = handle?.followUps ?? null;
2923
+ if (handle === null || run === null || handle.done || run.asked === null) {
2924
+ return;
2925
+ }
2926
+ const asked = run.asked;
2927
+ const chat = conversationRef.current;
2928
+ const messages = chat?.messages ?? [];
2929
+ const at = messages.findIndex((message) => message.id === asked.messageId);
2930
+ const question = at === -1 ? void 0 : messages[at];
2931
+ const answer = at === -1 ? void 0 : messages[at + 1];
2932
+ const drop = (unread2) => {
2933
+ run.asked = null;
2934
+ showFollowUp(handle, { pending: null, unread: unread2 });
2935
+ attend();
2936
+ };
2937
+ const unread = (id, reason) => {
2938
+ if (id === null || run.sent.has(id) || !isAvatarMessageId(id)) {
2939
+ drop(reason);
2940
+ return;
2941
+ }
2942
+ run.asked = null;
2943
+ readFollowUp(handle, id, reason);
2944
+ };
2945
+ if (question === void 0 || (chat?.getConversationToken() ?? null) !== run.token) {
2946
+ drop(null);
2947
+ return;
2948
+ }
2949
+ if (answer === void 0) {
2950
+ if (!inFlight(chat)) unread(asked.answerId, "answer_failed");
2951
+ return;
2952
+ }
2953
+ if (answer.status === "PENDING" || answer.status === "RUNNING") {
2954
+ if (answer.role === "ASSISTANT") asked.answerId = answer.id;
2955
+ return;
2956
+ }
2957
+ if (answer.role !== "ASSISTANT") {
2958
+ unread(asked.answerId, "answer_failed");
2959
+ return;
2960
+ }
2961
+ if (answer.status !== "COMPLETED" || answer.content.trim() === "") {
2962
+ unread(answer.id, "answer_failed");
2963
+ return;
2964
+ }
2965
+ if (run.sent.has(answer.id) || !isAvatarMessageId(answer.id)) {
2966
+ drop("not_eligible");
2967
+ return;
2968
+ }
2969
+ if (at + 2 !== messages.length || !readable(question, answer)) {
2970
+ unread(answer.id, "not_eligible");
2971
+ return;
2972
+ }
2973
+ run.asked = null;
2974
+ readFollowUp(handle, answer.id, null);
2975
+ });
1995
2976
  const newChatForAvatar = react.useCallback(() => {
1996
2977
  const conversation2 = conversationRef.current;
1997
2978
  if (conversation2 === null) return;
@@ -2015,7 +2996,14 @@ function AvatarLiveView({
2015
2996
  const focused = document.activeElement;
2016
2997
  if (focused && focused !== document.body && focused.isConnected) return;
2017
2998
  rootRef.current?.querySelector(PRIMARY)?.focus();
2018
- }, [active, phase.name, askState, shown.pendingAnswerId, blocked]);
2999
+ }, [
3000
+ active,
3001
+ phase.name,
3002
+ askState,
3003
+ shown.pendingAnswerId,
3004
+ blocked,
3005
+ followUpAsks
3006
+ ]);
2019
3007
  const onPlaying = goLive;
2020
3008
  const enablePlayback = react.useCallback(() => {
2021
3009
  void sessionRef.current?.joined?.enablePlayback().catch(() => void 0);
@@ -2121,7 +3109,7 @@ function AvatarLiveView({
2121
3109
  /* @__PURE__ */ jsxRuntime.jsxs(
2122
3110
  "a",
2123
3111
  {
2124
- href: PRIVACY_HREF,
3112
+ href: PRIVACY_HREF2,
2125
3113
  target: "_blank",
2126
3114
  rel: "noopener noreferrer",
2127
3115
  "data-boff-explore": "avatar-live-privacy",
@@ -2213,7 +3201,7 @@ function AvatarLiveView({
2213
3201
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm tabular-nums text-muted-foreground", children: [
2214
3202
  S.timeLeft,
2215
3203
  " ",
2216
- /* @__PURE__ */ jsxRuntime.jsx("span", { role: "timer", "data-boff-explore": "avatar-live-countdown", children: clock(Math.max(0, (session.endsAt - now) / 1e3)) })
3204
+ /* @__PURE__ */ jsxRuntime.jsx("span", { role: "timer", "data-boff-explore": "avatar-live-countdown", children: clock2(Math.max(0, (session.endsAt - now) / 1e3)) })
2217
3205
  ] })
2218
3206
  ] });
2219
3207
  const liveActions = (back) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
@@ -2415,13 +3403,32 @@ function AvatarLiveView({
2415
3403
  actions2 = stopButton;
2416
3404
  break;
2417
3405
  case "pending":
2418
- case "live":
2419
- status = phase.name === "live" ? speakingLine(phase.session) : S.connecting;
3406
+ case "live": {
2420
3407
  top2 = badge;
2421
3408
  media2 = liveMedia(phase.name === "live");
2422
3409
  body2 = liveBody(phase.session);
2423
3410
  actions2 = liveActions(readInChatButton(false));
3411
+ if (followUp === null) {
3412
+ status = phase.name === "live" ? speakingLine(phase.session) : S.connecting;
3413
+ break;
3414
+ }
3415
+ status = followUp.speaking ? speakingLine(phase.session) : followUp.pending !== null ? (followUp.pending === "asking" ? activity : null) ?? S.gettingAnswer : followUp.unread !== null ? S[FOLLOW_UP_TEXT[followUp.unread]] : followUp.closed ? S.followUpLast : S.followUpIdle;
3416
+ if (!followUp.speaking && followUp.pending === null && followUp.unread !== null && !followUp.closed) {
3417
+ body2 = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3418
+ body2,
3419
+ /* @__PURE__ */ jsxRuntime.jsx(
3420
+ "p",
3421
+ {
3422
+ "data-boff-explore": "avatar-live-idle",
3423
+ className: "text-xs text-muted-foreground",
3424
+ children: S.followUpIdle
3425
+ }
3426
+ )
3427
+ ] });
3428
+ }
3429
+ if (followUpAsks) box = "spoken";
2424
3430
  break;
3431
+ }
2425
3432
  case "ended":
2426
3433
  status = S[phase.how];
2427
3434
  outcome2 = true;
@@ -2430,7 +3437,7 @@ function AvatarLiveView({
2430
3437
  break;
2431
3438
  }
2432
3439
  }
2433
- const reason = inFlight(conversation) ? S.askBusy : conversation?.canSend ? null : S.askUnavailable;
3440
+ const reason = askReason;
2434
3441
  const chips = box !== null && shown.kind === "empty" && prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
2435
3442
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-muted-foreground", children: tryAsking }),
2436
3443
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-h-28 flex-wrap gap-2 overflow-y-auto", children: prompts.map((prompt) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -2469,7 +3476,7 @@ function AvatarLiveView({
2469
3476
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: S.newChatNote })
2470
3477
  ] }) : null,
2471
3478
  chips,
2472
- box !== null && usedToday ? /* @__PURE__ */ jsxRuntime.jsx(
3479
+ box !== null && usedToday && phase.name !== "live" ? /* @__PURE__ */ jsxRuntime.jsx(
2473
3480
  "p",
2474
3481
  {
2475
3482
  "data-boff-explore": "avatar-live-used-today",
@@ -2477,6 +3484,7 @@ function AvatarLiveView({
2477
3484
  children: S.usedToday
2478
3485
  }
2479
3486
  ) : null,
3487
+ box === "spoken" ? mic.notice : null,
2480
3488
  box === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
2481
3489
  AskBox,
2482
3490
  {
@@ -2491,7 +3499,9 @@ function AvatarLiveView({
2491
3499
  footer: S.askFooter,
2492
3500
  noFiles: S.askNoFiles,
2493
3501
  hint: box === "spoken" ? S.needsAnswerAny : null,
2494
- touchFirst
3502
+ touchFirst,
3503
+ mic: box === "spoken" ? mic.button : null,
3504
+ micLines: box === "spoken" ? mic.lines : null
2495
3505
  }
2496
3506
  )
2497
3507
  ]
@@ -2638,7 +3648,9 @@ function AskBox({
2638
3648
  footer,
2639
3649
  noFiles,
2640
3650
  hint,
2641
- touchFirst
3651
+ touchFirst,
3652
+ mic,
3653
+ micLines
2642
3654
  }) {
2643
3655
  const uid = react.useId();
2644
3656
  const reasonId = `${uid}-reason`;
@@ -2703,6 +3715,7 @@ function AskBox({
2703
3715
  onDrop
2704
3716
  }
2705
3717
  ),
3718
+ mic,
2706
3719
  /* @__PURE__ */ jsxRuntime.jsx(
2707
3720
  "button",
2708
3721
  {
@@ -2716,6 +3729,7 @@ function AskBox({
2716
3729
  }
2717
3730
  )
2718
3731
  ] }),
3732
+ micLines,
2719
3733
  reason === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
2720
3734
  "p",
2721
3735
  {