@burdenoff/website-sdk 2026.1105.1 → 2026.1105.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.
- package/README.md +94 -1
- package/dist/avatar-live/index.d.mts +2 -2
- package/dist/avatar-live/index.d.ts +2 -2
- package/dist/avatar-live/index.js +11 -1
- package/dist/avatar-live/index.js.map +1 -1
- package/dist/avatar-live/index.mjs +11 -1
- package/dist/avatar-live/index.mjs.map +1 -1
- package/dist/avatar-live-view/index.d.mts +2 -2
- package/dist/avatar-live-view/index.d.ts +2 -2
- package/dist/avatar-live-view/index.js +915 -103
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +908 -96
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/avatar-mic/index.d.mts +187 -62
- package/dist/avatar-mic/index.d.ts +187 -62
- package/dist/avatar-mic/index.js +457 -5
- package/dist/avatar-mic/index.js.map +1 -1
- package/dist/avatar-mic/index.mjs +457 -6
- package/dist/avatar-mic/index.mjs.map +1 -1
- package/dist/{chunk-XIZE37IB.js → chunk-3AIAJ2L4.js} +2 -2
- package/dist/chunk-3AIAJ2L4.js.map +1 -0
- package/dist/{chunk-MVNN6LLO.mjs → chunk-JGU6JW4N.mjs} +2 -2
- package/dist/chunk-JGU6JW4N.mjs.map +1 -0
- package/dist/{explore-avatar-live-config-C-OwXNje.d.ts → explore-avatar-live-config-1zeXdMer.d.ts} +7 -4
- package/dist/{explore-avatar-live-config-OO0hvQCK.d.mts → explore-avatar-live-config-BxiE66Im.d.mts} +7 -4
- package/dist/index.d.mts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{types-CXqaLlNB.d.mts → types-CTUJNvQu.d.mts} +10 -0
- package/dist/{types-CXqaLlNB.d.ts → types-CTUJNvQu.d.ts} +10 -0
- package/package.json +1 -1
- package/dist/chunk-MVNN6LLO.mjs.map +0 -1
- package/dist/chunk-XIZE37IB.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { turnstileSiteKeyOf, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion } from '../chunk-
|
|
1
|
+
import { turnstileSiteKeyOf, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion } from '../chunk-JGU6JW4N.mjs';
|
|
2
2
|
import { answerDisplayText, fill } from '../chunk-JZS6FQT6.mjs';
|
|
3
3
|
import { cn } from '../chunk-R7G6TWIS.mjs';
|
|
4
4
|
import '../chunk-VNS5WDWO.mjs';
|
|
@@ -9,6 +9,8 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
|
9
9
|
// src/components/explore-avatar-api.ts
|
|
10
10
|
var AVAILABILITY_TIMEOUT_MS = 8e3;
|
|
11
11
|
var CREATE_TIMEOUT_MS = 3e4;
|
|
12
|
+
var TURN_TIMEOUT_MS = 8e3;
|
|
13
|
+
var TURN_ANSWER_TIMEOUT_MS = 25e3;
|
|
12
14
|
var MESSAGE_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
13
15
|
var STT_TOKEN = /^[A-Za-z0-9._~-]{1,4096}$/;
|
|
14
16
|
var CONVERSATION_TOKEN = /^[A-Za-z0-9._~+/=-]{16,512}$/;
|
|
@@ -67,6 +69,17 @@ function readSttOffer(value) {
|
|
|
67
69
|
}
|
|
68
70
|
return { token, expiresAt };
|
|
69
71
|
}
|
|
72
|
+
var MIC_MAX_PRESS_SECONDS = 30;
|
|
73
|
+
var UNREADABLE_MIC = { mode: "unreadable" };
|
|
74
|
+
function readMicOffer(value) {
|
|
75
|
+
if (!isRecord(value)) return UNREADABLE_MIC;
|
|
76
|
+
const keys = Object.keys(value).sort();
|
|
77
|
+
const { mode, maxPressSeconds } = value;
|
|
78
|
+
if (keys.length !== 2 || keys[0] !== "maxPressSeconds" || keys[1] !== "mode" || mode !== "browser" || maxPressSeconds !== MIC_MAX_PRESS_SECONDS) {
|
|
79
|
+
return UNREADABLE_MIC;
|
|
80
|
+
}
|
|
81
|
+
return { mode: "browser", maxPressSeconds: MIC_MAX_PRESS_SECONDS };
|
|
82
|
+
}
|
|
70
83
|
function readAvailability(status, body) {
|
|
71
84
|
if (status !== 200 || !isRecord(body)) return INERT;
|
|
72
85
|
const { available, reason } = body;
|
|
@@ -81,6 +94,7 @@ function readAvailability(status, body) {
|
|
|
81
94
|
return INERT;
|
|
82
95
|
}
|
|
83
96
|
const stt = readSttOffer(body.stt);
|
|
97
|
+
const mic = Object.prototype.hasOwnProperty.call(body, "mic") ? readMicOffer(body.mic) : void 0;
|
|
84
98
|
return {
|
|
85
99
|
kind: "available",
|
|
86
100
|
offer: {
|
|
@@ -88,7 +102,8 @@ function readAvailability(status, body) {
|
|
|
88
102
|
maxSessionSeconds,
|
|
89
103
|
botCheck,
|
|
90
104
|
questions,
|
|
91
|
-
...stt === void 0 ? {} : { stt }
|
|
105
|
+
...stt === void 0 ? {} : { stt },
|
|
106
|
+
...mic === void 0 ? {} : { mic }
|
|
92
107
|
}
|
|
93
108
|
};
|
|
94
109
|
}
|
|
@@ -113,6 +128,21 @@ function errorCode(body) {
|
|
|
113
128
|
const { code } = body.error;
|
|
114
129
|
return typeof code === "string" ? code : null;
|
|
115
130
|
}
|
|
131
|
+
var MAX_FOLLOW_UP_ANSWERS = 16;
|
|
132
|
+
var MAX_FOLLOW_UP_SECONDS = 600;
|
|
133
|
+
var FOLLOW_UP_KEYS = ["idleSeconds", "maxAnswerWaitSeconds", "maxAnswers"];
|
|
134
|
+
function isWholeBetween(value, max) {
|
|
135
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= max;
|
|
136
|
+
}
|
|
137
|
+
function readFollowUps(value) {
|
|
138
|
+
if (!isRecord(value)) return void 0;
|
|
139
|
+
const keys = Object.keys(value).sort();
|
|
140
|
+
const { maxAnswers, idleSeconds, maxAnswerWaitSeconds } = value;
|
|
141
|
+
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)) {
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
return { maxAnswers, idleSeconds, maxAnswerWaitSeconds };
|
|
145
|
+
}
|
|
116
146
|
function readCreated(body) {
|
|
117
147
|
if (!isRecord(body)) return FAILED;
|
|
118
148
|
const { sessionId, endSecret } = body;
|
|
@@ -130,6 +160,7 @@ function readCreated(body) {
|
|
|
130
160
|
if (typeof livekitUrl !== "string" || !livekitUrl.startsWith("wss://") || typeof participantToken !== "string" || participantToken === "" || typeof workerIdentity !== "string" || workerIdentity === "" || typeof locale !== "string" || typeof text !== "string") {
|
|
131
161
|
return unusable;
|
|
132
162
|
}
|
|
163
|
+
const followUps = readFollowUps(body.followUps);
|
|
133
164
|
return {
|
|
134
165
|
kind: "created",
|
|
135
166
|
session: {
|
|
@@ -138,7 +169,8 @@ function readCreated(body) {
|
|
|
138
169
|
endsAt,
|
|
139
170
|
join: { livekitUrl, participantToken, workerIdentity },
|
|
140
171
|
captions: { locale, text },
|
|
141
|
-
excerpt: body.excerpt === true
|
|
172
|
+
excerpt: body.excerpt === true,
|
|
173
|
+
...followUps === void 0 ? {} : { followUps }
|
|
142
174
|
}
|
|
143
175
|
};
|
|
144
176
|
}
|
|
@@ -269,6 +301,96 @@ function sendEnd(target) {
|
|
|
269
301
|
} catch {
|
|
270
302
|
}
|
|
271
303
|
}
|
|
304
|
+
var TURN_REFUSALS = /* @__PURE__ */ new Map([
|
|
305
|
+
["no_time", "no_time"],
|
|
306
|
+
["max_answers", "max_answers"],
|
|
307
|
+
["not_eligible", "not_eligible"],
|
|
308
|
+
["answer_claimed", "answer_claimed"],
|
|
309
|
+
["limits", "limits"],
|
|
310
|
+
["paused", "paused"],
|
|
311
|
+
["busy", "busy"]
|
|
312
|
+
]);
|
|
313
|
+
var MAX_TURN_RETRY_AFTER_SECONDS = 600;
|
|
314
|
+
function retryAfterOf(body) {
|
|
315
|
+
if (!isRecord(body) || !isRecord(body.error)) return void 0;
|
|
316
|
+
const { retryAfterSeconds } = body.error;
|
|
317
|
+
return isWholeBetween(retryAfterSeconds, MAX_TURN_RETRY_AFTER_SECONDS) ? retryAfterSeconds : void 0;
|
|
318
|
+
}
|
|
319
|
+
var TURN_KEY = /^[A-Za-z0-9_-]{8,64}$/;
|
|
320
|
+
var LOST = { kind: "failed", lost: true };
|
|
321
|
+
var TURN_FAILED = { kind: "failed", lost: false };
|
|
322
|
+
function readAccepted(body) {
|
|
323
|
+
if (!isRecord(body) || body.accepted !== true || !isRecord(body.captions)) {
|
|
324
|
+
return LOST;
|
|
325
|
+
}
|
|
326
|
+
const { locale, text } = body.captions;
|
|
327
|
+
if (typeof locale !== "string" || typeof text !== "string") return LOST;
|
|
328
|
+
return {
|
|
329
|
+
kind: "accepted",
|
|
330
|
+
captions: { locale, text },
|
|
331
|
+
excerpt: body.excerpt === true
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function readTurn(kind, status, body) {
|
|
335
|
+
if (status === 200) {
|
|
336
|
+
if (kind === "answer") return readAccepted(body);
|
|
337
|
+
return isRecord(body) && body.ok === true ? { kind: "ok" } : LOST;
|
|
338
|
+
}
|
|
339
|
+
const code = errorCode(body);
|
|
340
|
+
const refusal = code === null ? void 0 : TURN_REFUSALS.get(code);
|
|
341
|
+
if (refusal !== void 0) {
|
|
342
|
+
const retryAfterSeconds = retryAfterOf(body);
|
|
343
|
+
return retryAfterSeconds === void 0 ? { kind: "refused", reason: refusal } : { kind: "refused", reason: refusal, retryAfterSeconds };
|
|
344
|
+
}
|
|
345
|
+
if (status === 404) return { kind: "gone" };
|
|
346
|
+
return status >= 500 ? LOST : TURN_FAILED;
|
|
347
|
+
}
|
|
348
|
+
function requestTurn(target) {
|
|
349
|
+
const { kind, messageId, idempotencyKey } = target;
|
|
350
|
+
const nonce = newIdempotencyKey();
|
|
351
|
+
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)) {
|
|
352
|
+
return Promise.resolve(TURN_FAILED);
|
|
353
|
+
}
|
|
354
|
+
const payload = JSON.stringify({
|
|
355
|
+
secret: target.secret,
|
|
356
|
+
kind,
|
|
357
|
+
nonce,
|
|
358
|
+
...messageId === void 0 ? {} : { messageId },
|
|
359
|
+
...idempotencyKey === void 0 ? {} : { idempotencyKey }
|
|
360
|
+
});
|
|
361
|
+
const controller = new AbortController();
|
|
362
|
+
const answer = (async () => {
|
|
363
|
+
try {
|
|
364
|
+
const response = await fetch(
|
|
365
|
+
`${target.base}/sessions/${encodeURIComponent(target.sessionId)}/turn`,
|
|
366
|
+
{
|
|
367
|
+
method: "POST",
|
|
368
|
+
credentials: "omit",
|
|
369
|
+
cache: "no-store",
|
|
370
|
+
headers: { ...target.headers },
|
|
371
|
+
body: payload,
|
|
372
|
+
signal: controller.signal
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
return readTurn(kind, response.status, await readJson(response));
|
|
376
|
+
} catch {
|
|
377
|
+
return LOST;
|
|
378
|
+
}
|
|
379
|
+
})();
|
|
380
|
+
return new Promise((resolve) => {
|
|
381
|
+
const timer = setTimeout(
|
|
382
|
+
() => {
|
|
383
|
+
controller.abort();
|
|
384
|
+
resolve(LOST);
|
|
385
|
+
},
|
|
386
|
+
kind === "answer" ? TURN_ANSWER_TIMEOUT_MS : TURN_TIMEOUT_MS
|
|
387
|
+
);
|
|
388
|
+
void answer.then((result) => {
|
|
389
|
+
clearTimeout(timer);
|
|
390
|
+
resolve(result);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
272
394
|
var BOT_BINDING_PREFIX = "boff-web-avatar-cdata/v1";
|
|
273
395
|
function webCryptoSubtle() {
|
|
274
396
|
const subtle = globalThis.crypto?.subtle;
|
|
@@ -366,7 +488,14 @@ var EN = {
|
|
|
366
488
|
usedToday: "You've had today's avatar session. Answers will show here as text.",
|
|
367
489
|
needsAnswerAny: "Ask a question here, and the avatar reads the answer aloud.",
|
|
368
490
|
startNewChatDetailAny: "The avatar reads aloud only the first answer in a new chat started in the Avatar tab, provided it passes the speech checks.",
|
|
369
|
-
notEligibleAny: "The avatar can't read this answer aloud. The answer and sources are in Chat."
|
|
491
|
+
notEligibleAny: "The avatar can't read this answer aloud. The answer and sources are in Chat.",
|
|
492
|
+
followUpIdle: "The avatar ends after about thirty seconds without a question.",
|
|
493
|
+
followUpNoTime: "There isn't enough time left in this session to read that answer. It's in Chat.",
|
|
494
|
+
followUpMaxAnswers: "The avatar has read all the answers it can in this session. This one is in Chat.",
|
|
495
|
+
followUpLast: "That was the last answer the avatar can read in this session.",
|
|
496
|
+
followUpLimits: "The live avatar has reached its limit for now. This answer is in Chat.",
|
|
497
|
+
followUpPaused: "The live avatar is paused right now. This answer is in Chat.",
|
|
498
|
+
followUpUnread: "The avatar couldn't read that answer. It's in Chat."
|
|
370
499
|
};
|
|
371
500
|
var ZH = {
|
|
372
501
|
checking: "\u6B63\u5728\u68C0\u67E5\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u662F\u5426\u53EF\u7528\u2026",
|
|
@@ -427,7 +556,16 @@ var ZH = {
|
|
|
427
556
|
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",
|
|
428
557
|
needsAnswerAny: "\u5728\u8FD9\u91CC\u63D0\u95EE\uFF0C\u865A\u62DF\u5F62\u8C61\u4F1A\u6717\u8BFB\u56DE\u7B54\u3002",
|
|
429
558
|
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",
|
|
430
|
-
notEligibleAny: "\u865A\u62DF\u5F62\u8C61\u65E0\u6CD5\u6717\u8BFB\u6B64\u56DE\u7B54\u3002\u56DE\u7B54\u548C\u6765\u6E90\u5728\u804A\u5929\u4E2D\u3002"
|
|
559
|
+
notEligibleAny: "\u865A\u62DF\u5F62\u8C61\u65E0\u6CD5\u6717\u8BFB\u6B64\u56DE\u7B54\u3002\u56DE\u7B54\u548C\u6765\u6E90\u5728\u804A\u5929\u4E2D\u3002",
|
|
560
|
+
// TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
|
|
561
|
+
// (Stage D).
|
|
562
|
+
followUpIdle: "\u5982\u679C\u7EA6\u4E09\u5341\u79D2\u5185\u6CA1\u6709\u65B0\u95EE\u9898\uFF0C\u865A\u62DF\u5F62\u8C61\u5C31\u4F1A\u7ED3\u675F\u3002",
|
|
563
|
+
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",
|
|
564
|
+
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",
|
|
565
|
+
followUpLast: "\u8FD9\u662F\u865A\u62DF\u5F62\u8C61\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u53EF\u6717\u8BFB\u7684\u6700\u540E\u4E00\u4E2A\u56DE\u7B54\u3002",
|
|
566
|
+
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",
|
|
567
|
+
followUpPaused: "\u5B9E\u65F6\u865A\u62DF\u5F62\u8C61\u76EE\u524D\u5DF2\u6682\u505C\u3002\u6B64\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002",
|
|
568
|
+
followUpUnread: "\u865A\u62DF\u5F62\u8C61\u65E0\u6CD5\u6717\u8BFB\u8BE5\u56DE\u7B54\u3002\u56DE\u7B54\u5728\u804A\u5929\u4E2D\u3002"
|
|
431
569
|
};
|
|
432
570
|
var HI = {
|
|
433
571
|
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",
|
|
@@ -488,7 +626,16 @@ var HI = {
|
|
|
488
626
|
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",
|
|
489
627
|
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",
|
|
490
628
|
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",
|
|
491
|
-
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"
|
|
629
|
+
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",
|
|
630
|
+
// TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
|
|
631
|
+
// (Stage D).
|
|
632
|
+
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",
|
|
633
|
+
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",
|
|
634
|
+
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",
|
|
635
|
+
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",
|
|
636
|
+
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",
|
|
637
|
+
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",
|
|
638
|
+
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"
|
|
492
639
|
};
|
|
493
640
|
var ES = {
|
|
494
641
|
checking: "Comprobando si el avatar en vivo est\xE1 disponible\u2026",
|
|
@@ -549,7 +696,16 @@ var ES = {
|
|
|
549
696
|
usedToday: "Ya has usado la sesi\xF3n de avatar de hoy. Las respuestas se mostrar\xE1n aqu\xED como texto.",
|
|
550
697
|
needsAnswerAny: "Haz una pregunta aqu\xED y el avatar leer\xE1 la respuesta en voz alta.",
|
|
551
698
|
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.",
|
|
552
|
-
notEligibleAny: "El avatar no puede leer esta respuesta en voz alta. La respuesta y las fuentes est\xE1n en el chat."
|
|
699
|
+
notEligibleAny: "El avatar no puede leer esta respuesta en voz alta. La respuesta y las fuentes est\xE1n en el chat.",
|
|
700
|
+
// TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
|
|
701
|
+
// (Stage D).
|
|
702
|
+
followUpIdle: "El avatar termina tras unos treinta segundos sin preguntas.",
|
|
703
|
+
followUpNoTime: "No queda tiempo suficiente en esta sesi\xF3n para leer esa respuesta. Est\xE1 en el chat.",
|
|
704
|
+
followUpMaxAnswers: "El avatar ya ha le\xEDdo todas las respuestas que puede en esta sesi\xF3n. Esta est\xE1 en el chat.",
|
|
705
|
+
followUpLast: "Esa fue la \xFAltima respuesta que el avatar puede leer en esta sesi\xF3n.",
|
|
706
|
+
followUpLimits: "El avatar en vivo ha alcanzado su l\xEDmite por ahora. Esta respuesta est\xE1 en el chat.",
|
|
707
|
+
followUpPaused: "El avatar en vivo est\xE1 en pausa ahora mismo. Esta respuesta est\xE1 en el chat.",
|
|
708
|
+
followUpUnread: "El avatar no pudo leer esa respuesta. Est\xE1 en el chat."
|
|
553
709
|
};
|
|
554
710
|
var AR = {
|
|
555
711
|
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",
|
|
@@ -610,7 +766,16 @@ var AR = {
|
|
|
610
766
|
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.",
|
|
611
767
|
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.",
|
|
612
768
|
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.",
|
|
613
|
-
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."
|
|
769
|
+
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.",
|
|
770
|
+
// TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
|
|
771
|
+
// (Stage D).
|
|
772
|
+
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.",
|
|
773
|
+
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.",
|
|
774
|
+
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.",
|
|
775
|
+
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.",
|
|
776
|
+
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.",
|
|
777
|
+
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.",
|
|
778
|
+
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."
|
|
614
779
|
};
|
|
615
780
|
var TA = {
|
|
616
781
|
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",
|
|
@@ -671,7 +836,16 @@ var TA = {
|
|
|
671
836
|
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.",
|
|
672
837
|
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.",
|
|
673
838
|
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.",
|
|
674
|
-
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."
|
|
839
|
+
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.",
|
|
840
|
+
// TRANSLATION REVIEW PENDING (BOFF-9797): draft, the lines of a session that reads the answers after the first
|
|
841
|
+
// (Stage D).
|
|
842
|
+
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.",
|
|
843
|
+
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.",
|
|
844
|
+
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.",
|
|
845
|
+
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.",
|
|
846
|
+
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.",
|
|
847
|
+
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.",
|
|
848
|
+
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."
|
|
675
849
|
};
|
|
676
850
|
var STRINGS = {
|
|
677
851
|
en: EN,
|
|
@@ -684,6 +858,7 @@ var STRINGS = {
|
|
|
684
858
|
function avatarLiveStrings(locale) {
|
|
685
859
|
return STRINGS[locale] ?? EN;
|
|
686
860
|
}
|
|
861
|
+
var BROWSER_MIC_NOTICE_VERSION = "web-mic-2026-11-21-c";
|
|
687
862
|
var CONSENT = /* @__PURE__ */ new Map([
|
|
688
863
|
[
|
|
689
864
|
"web-consent-2026-11",
|
|
@@ -821,6 +996,91 @@ var CONSENT = /* @__PURE__ */ new Map([
|
|
|
821
996
|
questions: "any"
|
|
822
997
|
}
|
|
823
998
|
}
|
|
999
|
+
],
|
|
1000
|
+
[
|
|
1001
|
+
// Stage D (BOFF-9797), a DRAFT for the operator's review that no environment uses yet (global-speech-svc reserves the
|
|
1002
|
+
// same two versions): web-consent-2026-11-12-b with only its intro changed. The avatar reads the answer to each
|
|
1003
|
+
// question asked in the Avatar tab, in one session, waits about thirty seconds after an answer for the next question,
|
|
1004
|
+
// then ends; the session lasts at most three minutes, and an answer that does not fit the time left is read in part
|
|
1005
|
+
// (and said so) or not read, and stays in Chat. The notice files of record and their digests are a gitops change of
|
|
1006
|
+
// their own, made with the review.
|
|
1007
|
+
"web-consent-2026-11-19-d",
|
|
1008
|
+
{
|
|
1009
|
+
en: {
|
|
1010
|
+
title: "Live AI avatar (beta)",
|
|
1011
|
+
introProduct: null,
|
|
1012
|
+
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.",
|
|
1013
|
+
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.",
|
|
1014
|
+
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.",
|
|
1015
|
+
privacyLink: "How we handle this",
|
|
1016
|
+
botCheck: "turnstile",
|
|
1017
|
+
questions: "any",
|
|
1018
|
+
followUps: true
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
],
|
|
1022
|
+
[
|
|
1023
|
+
// The words of web-consent-2026-11-19-d without "When you start, Cloudflare checks for bots.", for a server that runs
|
|
1024
|
+
// no bot check. A DRAFT, like it.
|
|
1025
|
+
"web-consent-2026-11-20-d",
|
|
1026
|
+
{
|
|
1027
|
+
en: {
|
|
1028
|
+
title: "Live AI avatar (beta)",
|
|
1029
|
+
introProduct: null,
|
|
1030
|
+
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.",
|
|
1031
|
+
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.",
|
|
1032
|
+
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.",
|
|
1033
|
+
privacyLink: "How we handle this",
|
|
1034
|
+
botCheck: "off",
|
|
1035
|
+
questions: "any",
|
|
1036
|
+
followUps: true
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
],
|
|
1040
|
+
[
|
|
1041
|
+
// Stage C (BOFF-9797, the operator's decision of 2026-11-05): the mic through the browser's own speech recognition.
|
|
1042
|
+
// A DRAFT for the operator's privacy review that no environment uses yet (global-speech-svc reserves the same two
|
|
1043
|
+
// versions, and pairs them with its AVATAR_WEB_MIC=browser): web-consent-2026-11-19-d with three changes only. The
|
|
1044
|
+
// intro warns against personal details said into the microphone and says a tap on it stops the answer being read;
|
|
1045
|
+
// the processing says which speech service turns the voice into text (the browser's own, under its own terms, with
|
|
1046
|
+
// the vendors as examples) and that a live session is told of a tap and of a recording, never of the words; the
|
|
1047
|
+
// storage says the camera is not used and that the voice never reaches us, nor the text until Send, in place of
|
|
1048
|
+
// "Your microphone and camera are not used.", which is true only with no mic. The notice files of record and their
|
|
1049
|
+
// digests are a gitops change of their own, made with the review.
|
|
1050
|
+
"web-consent-2026-11-21-c",
|
|
1051
|
+
{
|
|
1052
|
+
en: {
|
|
1053
|
+
title: "Live AI avatar (beta)",
|
|
1054
|
+
introProduct: null,
|
|
1055
|
+
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. Don't say them into the microphone either. Tapping the microphone while the avatar speaks stops that answer.",
|
|
1056
|
+
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. If you use the microphone, the speech service your browser uses turns your voice into text and may receive the audio: for example Google for Chrome on a computer or Android phone, Apple for Safari and on iPhone and iPad, and Microsoft for Edge on a computer, each under its own privacy terms. During a live session, our server is told when you tap the microphone and while you record (never what you say), so the avatar stops and waits.",
|
|
1057
|
+
storage: "Your camera is not used. We never receive your voice: the text appears in the question box for you to check, and none of what you said reaches us until you press Send. 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.",
|
|
1058
|
+
privacyLink: "How we handle this",
|
|
1059
|
+
botCheck: "turnstile",
|
|
1060
|
+
questions: "any",
|
|
1061
|
+
followUps: true,
|
|
1062
|
+
micNotice: BROWSER_MIC_NOTICE_VERSION
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
],
|
|
1066
|
+
[
|
|
1067
|
+
// The words of web-consent-2026-11-21-c without "When you start, Cloudflare checks for bots.", for a server that runs
|
|
1068
|
+
// no bot check. A DRAFT, like it.
|
|
1069
|
+
"web-consent-2026-11-22-c",
|
|
1070
|
+
{
|
|
1071
|
+
en: {
|
|
1072
|
+
title: "Live AI avatar (beta)",
|
|
1073
|
+
introProduct: null,
|
|
1074
|
+
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. Don't say them into the microphone either. Tapping the microphone while the avatar speaks stops that answer.",
|
|
1075
|
+
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. If you use the microphone, the speech service your browser uses turns your voice into text and may receive the audio: for example Google for Chrome on a computer or Android phone, Apple for Safari and on iPhone and iPad, and Microsoft for Edge on a computer, each under its own privacy terms. During a live session, our server is told when you tap the microphone and while you record (never what you say), so the avatar stops and waits.",
|
|
1076
|
+
storage: "Your camera is not used. We never receive your voice: the text appears in the question box for you to check, and none of what you said reaches us until you press Send. 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.",
|
|
1077
|
+
privacyLink: "How we handle this",
|
|
1078
|
+
botCheck: "off",
|
|
1079
|
+
questions: "any",
|
|
1080
|
+
followUps: true,
|
|
1081
|
+
micNotice: BROWSER_MIC_NOTICE_VERSION
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
824
1084
|
]
|
|
825
1085
|
]);
|
|
826
1086
|
function avatarConsentCopy(version, locale) {
|
|
@@ -865,6 +1125,22 @@ var MIC_NOTICE = /* @__PURE__ */ new Map([
|
|
|
865
1125
|
privacyLink: "How we handle this"
|
|
866
1126
|
}
|
|
867
1127
|
}
|
|
1128
|
+
],
|
|
1129
|
+
[
|
|
1130
|
+
// The browser's own speech recognition (Stage C): the browser's speech service, named by example where each is
|
|
1131
|
+
// true, under its own terms; nothing of the voice reaches us, nor the text before Send; a live session told only of
|
|
1132
|
+
// a tap and of a recording. No daily limits: nothing is counted on the server.
|
|
1133
|
+
BROWSER_MIC_NOTICE_VERSION,
|
|
1134
|
+
{
|
|
1135
|
+
en: {
|
|
1136
|
+
title: "Ask with your voice",
|
|
1137
|
+
processing: "The speech service your browser uses turns your voice into text and may receive the audio: for example Google for Chrome on a computer or Android phone, Apple for Safari and on iPhone and iPad, and Microsoft for Edge on a computer. Its own privacy terms apply.",
|
|
1138
|
+
storage: "We never receive your voice, and your camera is not used.",
|
|
1139
|
+
text: "The text appears in the box for you to check. None of what you said reaches us until you press Send; once you send it, it is a chat message, kept like any other. During a live session, we are told when you tap the microphone and while you record (never what you say), so the avatar stops and waits.",
|
|
1140
|
+
limits: "Each recording stops after thirty seconds at the latest.",
|
|
1141
|
+
privacyLink: "How we handle this"
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
868
1144
|
]
|
|
869
1145
|
]);
|
|
870
1146
|
function avatarMicNotice(version, locale) {
|
|
@@ -875,10 +1151,12 @@ var PRIVACY_HREF = "/privacy#live-avatar";
|
|
|
875
1151
|
var LIMIT_SECONDS = 30;
|
|
876
1152
|
var TICK_MS = 250;
|
|
877
1153
|
var IDLE = { name: "idle" };
|
|
878
|
-
var
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
])
|
|
1154
|
+
var EN_IN = { lang: "en-IN", fallbackLang: "en-US" };
|
|
1155
|
+
var BROWSER_MIC_LANGUAGES = { en: EN_IN };
|
|
1156
|
+
var FOR_GOOD = {
|
|
1157
|
+
stream: /* @__PURE__ */ new Set(["unavailable", "limit"]),
|
|
1158
|
+
browser: /* @__PURE__ */ new Set(["unsupported"])
|
|
1159
|
+
};
|
|
882
1160
|
var FAILURE_TEXT = {
|
|
883
1161
|
denied: "micDenied",
|
|
884
1162
|
unsupported: "micUnsupported",
|
|
@@ -887,14 +1165,32 @@ var FAILURE_TEXT = {
|
|
|
887
1165
|
busy: "micBusy",
|
|
888
1166
|
failed: "micFailed"
|
|
889
1167
|
};
|
|
1168
|
+
var loadedMic = null;
|
|
890
1169
|
function loadAvatarMic() {
|
|
891
|
-
return import('../avatar-mic/index.mjs').then((loaded) =>
|
|
892
|
-
|
|
893
|
-
|
|
1170
|
+
return import('../avatar-mic/index.mjs').then((loaded) => {
|
|
1171
|
+
const module = {
|
|
1172
|
+
createAvatarMic: loaded.createAvatarMic,
|
|
1173
|
+
createBrowserAvatarMic: loaded.createBrowserAvatarMic
|
|
1174
|
+
};
|
|
1175
|
+
loadedMic = module;
|
|
1176
|
+
return module;
|
|
1177
|
+
});
|
|
894
1178
|
}
|
|
895
1179
|
function canUseAvatarMic() {
|
|
896
1180
|
return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function" && typeof AudioContext !== "undefined" && typeof AudioWorkletNode !== "undefined" && typeof WebSocket !== "undefined";
|
|
897
1181
|
}
|
|
1182
|
+
function canUseBrowserMic() {
|
|
1183
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
if (window.isSecureContext !== true) return false;
|
|
1187
|
+
const recognizer = Reflect.get(window, "SpeechRecognition") ?? Reflect.get(window, "webkitSpeechRecognition");
|
|
1188
|
+
if (typeof recognizer !== "function") return false;
|
|
1189
|
+
const agent = navigator.userAgent ?? "";
|
|
1190
|
+
if (/\bFirefox\//.test(agent) || /\bFxiOS\//.test(agent)) return false;
|
|
1191
|
+
return !("brave" in navigator);
|
|
1192
|
+
}
|
|
1193
|
+
var REPLAY_PRIVATE = "rr-block rr-mask";
|
|
898
1194
|
function clock(seconds) {
|
|
899
1195
|
const whole = Math.max(0, Math.floor(seconds));
|
|
900
1196
|
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
|
|
@@ -905,36 +1201,51 @@ function withHeardText(current, heard, maxLength) {
|
|
|
905
1201
|
const joined = current.trim() === "" ? text : `${current.replace(/\s+$/, "")} ${text}`;
|
|
906
1202
|
return joined.slice(0, Math.max(0, maxLength));
|
|
907
1203
|
}
|
|
1204
|
+
function noticeOf(source) {
|
|
1205
|
+
return source.kind === "stream" ? AVATAR_MIC_NOTICE_VERSION : source.notice;
|
|
1206
|
+
}
|
|
1207
|
+
function micKeyOf(source, locale) {
|
|
1208
|
+
return source.kind === "stream" ? `stream ${source.base}` : `browser ${BROWSER_MIC_LANGUAGES[locale]?.lang ?? ""}`;
|
|
1209
|
+
}
|
|
908
1210
|
function useAvatarMic({
|
|
909
1211
|
enabled,
|
|
910
1212
|
locale,
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
onText
|
|
1213
|
+
source,
|
|
1214
|
+
onText,
|
|
1215
|
+
onStart
|
|
915
1216
|
}) {
|
|
916
1217
|
const S = avatarMicStrings(locale);
|
|
917
|
-
const
|
|
1218
|
+
const noticeVersion = source === null ? null : noticeOf(source);
|
|
1219
|
+
const copy = noticeVersion === null ? null : avatarMicNotice(noticeVersion, locale);
|
|
1220
|
+
const kind = source?.kind ?? null;
|
|
1221
|
+
const micKey = source === null ? null : micKeyOf(source, locale);
|
|
918
1222
|
const uid = useId();
|
|
919
1223
|
const noticeTitleId = `${uid}-mic-notice`;
|
|
920
|
-
const [withdrawn, setWithdrawn] = useState(
|
|
921
|
-
const [supported, setSupported] = useState(false);
|
|
922
|
-
useEffect(
|
|
923
|
-
|
|
924
|
-
|
|
1224
|
+
const [withdrawn, setWithdrawn] = useState({});
|
|
1225
|
+
const [supported, setSupported] = useState({ stream: false, browser: false });
|
|
1226
|
+
useEffect(
|
|
1227
|
+
() => setSupported({ stream: canUseAvatarMic(), browser: canUseBrowserMic() }),
|
|
1228
|
+
[]
|
|
1229
|
+
);
|
|
1230
|
+
const usable = source === null ? false : source.kind === "stream" ? supported.stream : supported.browser && BROWSER_MIC_LANGUAGES[locale] !== void 0;
|
|
1231
|
+
const possible = enabled && kind !== null && usable && S !== null && copy !== null;
|
|
1232
|
+
const withdrawnReason = kind === null ? null : withdrawn[kind] ?? null;
|
|
1233
|
+
const offered = possible && withdrawnReason === null;
|
|
925
1234
|
const [state, setState] = useState(IDLE);
|
|
926
1235
|
const [noticeOpen, setNoticeOpen] = useState(false);
|
|
927
|
-
const agreedRef = useRef(
|
|
1236
|
+
const agreedRef = useRef(null);
|
|
928
1237
|
const [loading2, setLoading] = useState(false);
|
|
929
1238
|
const [loadFailed, setLoadFailed] = useState(false);
|
|
930
1239
|
const [now, setNow] = useState(() => Date.now());
|
|
931
|
-
const micRef = useRef(
|
|
1240
|
+
const micRef = useRef(
|
|
1241
|
+
null
|
|
1242
|
+
);
|
|
932
1243
|
const seqRef = useRef(0);
|
|
933
1244
|
const buttonRef = useRef(null);
|
|
934
1245
|
const allowRef = useRef(null);
|
|
935
|
-
const
|
|
936
|
-
const latest = useRef({
|
|
937
|
-
latest.current = {
|
|
1246
|
+
const usedRef = useRef(null);
|
|
1247
|
+
const latest = useRef({ source, locale, noticeVersion, onText, onStart });
|
|
1248
|
+
latest.current = { source, locale, noticeVersion, onText, onStart };
|
|
938
1249
|
const cancel = useCallback(() => {
|
|
939
1250
|
seqRef.current += 1;
|
|
940
1251
|
setLoading(false);
|
|
@@ -945,6 +1256,12 @@ function useAvatarMic({
|
|
|
945
1256
|
useEffect(() => {
|
|
946
1257
|
if (!offered) cancel();
|
|
947
1258
|
}, [offered, cancel]);
|
|
1259
|
+
const keyRef = useRef(micKey);
|
|
1260
|
+
useEffect(() => {
|
|
1261
|
+
if (keyRef.current === micKey) return;
|
|
1262
|
+
keyRef.current = micKey;
|
|
1263
|
+
cancel();
|
|
1264
|
+
}, [micKey, cancel]);
|
|
948
1265
|
useEffect(
|
|
949
1266
|
() => () => {
|
|
950
1267
|
seqRef.current += 1;
|
|
@@ -952,29 +1269,55 @@ function useAvatarMic({
|
|
|
952
1269
|
},
|
|
953
1270
|
[]
|
|
954
1271
|
);
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1272
|
+
const micOf = useCallback(
|
|
1273
|
+
(loaded, wanted) => {
|
|
1274
|
+
const key = micKeyOf(wanted, latest.current.locale);
|
|
1275
|
+
const held = micRef.current;
|
|
1276
|
+
if (held !== null && held.key === key) return held.mic;
|
|
1277
|
+
held?.mic.cancel();
|
|
1278
|
+
let mic;
|
|
1279
|
+
if (wanted.kind === "stream") {
|
|
1280
|
+
const stream = () => {
|
|
1281
|
+
const current = latest.current.source;
|
|
1282
|
+
return current?.kind === "stream" ? current : null;
|
|
1283
|
+
};
|
|
1284
|
+
mic = loaded.createAvatarMic({
|
|
1285
|
+
base: wanted.base,
|
|
1286
|
+
notice: AVATAR_MIC_NOTICE_VERSION,
|
|
1287
|
+
offer: () => stream()?.offer ?? null,
|
|
1288
|
+
refreshOffer: () => stream()?.refreshOffer() ?? Promise.resolve(null),
|
|
1289
|
+
onState: setState
|
|
1290
|
+
});
|
|
1291
|
+
} else {
|
|
1292
|
+
const language = BROWSER_MIC_LANGUAGES[latest.current.locale] ?? EN_IN;
|
|
1293
|
+
mic = loaded.createBrowserAvatarMic({
|
|
1294
|
+
lang: language.lang,
|
|
1295
|
+
fallbackLang: language.fallbackLang,
|
|
1296
|
+
maxPressMs: LIMIT_SECONDS * 1e3,
|
|
1297
|
+
onState: setState
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
micRef.current = { key, kind: wanted.kind, mic };
|
|
1301
|
+
return mic;
|
|
1302
|
+
},
|
|
1303
|
+
[]
|
|
1304
|
+
);
|
|
970
1305
|
const press = useCallback(() => {
|
|
971
|
-
const
|
|
972
|
-
if (
|
|
1306
|
+
const wanted = latest.current.source;
|
|
1307
|
+
if (wanted === null) return;
|
|
973
1308
|
seqRef.current += 1;
|
|
974
1309
|
const seq = seqRef.current;
|
|
975
1310
|
setLoadFailed(false);
|
|
1311
|
+
const held = micRef.current;
|
|
1312
|
+
const ready = wanted.kind !== "browser" ? null : held !== null && held.key === micKeyOf(wanted, latest.current.locale) ? held.mic : loadedMic !== null ? micOf(loadedMic, wanted) : null;
|
|
1313
|
+
if (ready !== null) {
|
|
1314
|
+
setLoading(false);
|
|
1315
|
+
ready.start();
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
976
1318
|
setLoading(true);
|
|
977
|
-
|
|
1319
|
+
const arriving = held !== null && held.key === micKeyOf(wanted, latest.current.locale) ? Promise.resolve(held.mic) : loadAvatarMic().then((loaded) => micOf(loaded, wanted));
|
|
1320
|
+
void arriving.then(
|
|
978
1321
|
(mic) => {
|
|
979
1322
|
if (seqRef.current !== seq) return;
|
|
980
1323
|
setLoading(false);
|
|
@@ -986,8 +1329,9 @@ function useAvatarMic({
|
|
|
986
1329
|
setLoadFailed(true);
|
|
987
1330
|
}
|
|
988
1331
|
);
|
|
989
|
-
}, [
|
|
1332
|
+
}, [micOf]);
|
|
990
1333
|
const recording = loading2 || state.name === "starting" || state.name === "listening";
|
|
1334
|
+
const finishing = kind === "browser" && state.name === "finishing";
|
|
991
1335
|
const tap = useCallback(() => {
|
|
992
1336
|
if (state.name === "listening" || state.name === "starting") {
|
|
993
1337
|
micRef.current?.mic.stop();
|
|
@@ -997,15 +1341,26 @@ function useAvatarMic({
|
|
|
997
1341
|
cancel();
|
|
998
1342
|
return;
|
|
999
1343
|
}
|
|
1000
|
-
if (
|
|
1344
|
+
if (finishing) return;
|
|
1345
|
+
latest.current.onStart?.();
|
|
1346
|
+
if (agreedRef.current !== latest.current.noticeVersion) {
|
|
1001
1347
|
setNoticeOpen(true);
|
|
1002
|
-
|
|
1348
|
+
const seq = seqRef.current;
|
|
1349
|
+
void loadAvatarMic().then(
|
|
1350
|
+
(loaded) => {
|
|
1351
|
+
const wanted = latest.current.source;
|
|
1352
|
+
if (seqRef.current === seq && wanted?.kind === "browser") {
|
|
1353
|
+
micOf(loaded, wanted);
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
() => void 0
|
|
1357
|
+
);
|
|
1003
1358
|
return;
|
|
1004
1359
|
}
|
|
1005
1360
|
press();
|
|
1006
|
-
}, [cancel, loading2, press, state.name]);
|
|
1361
|
+
}, [cancel, finishing, loading2, micOf, press, state.name]);
|
|
1007
1362
|
const allow = useCallback(() => {
|
|
1008
|
-
agreedRef.current =
|
|
1363
|
+
agreedRef.current = latest.current.noticeVersion;
|
|
1009
1364
|
setNoticeOpen(false);
|
|
1010
1365
|
buttonRef.current?.focus();
|
|
1011
1366
|
press();
|
|
@@ -1025,17 +1380,27 @@ function useAvatarMic({
|
|
|
1025
1380
|
return () => clearInterval(timer);
|
|
1026
1381
|
}, [listeningSince]);
|
|
1027
1382
|
useEffect(() => {
|
|
1028
|
-
if (state.name === "done" && state
|
|
1029
|
-
|
|
1383
|
+
if (state.name === "done" && state !== usedRef.current) {
|
|
1384
|
+
usedRef.current = state;
|
|
1030
1385
|
if (state.text !== "") latest.current.onText(state.text);
|
|
1031
|
-
|
|
1032
|
-
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
const pressed = micRef.current?.kind;
|
|
1389
|
+
if (state.name === "failed" && pressed !== void 0 && FOR_GOOD[pressed].has(state.reason)) {
|
|
1390
|
+
const reason = state.reason;
|
|
1391
|
+
setWithdrawn((held) => ({ ...held, [pressed]: reason }));
|
|
1033
1392
|
}
|
|
1034
1393
|
}, [state]);
|
|
1035
1394
|
if (!possible || S === null || copy === null) {
|
|
1036
|
-
return {
|
|
1395
|
+
return {
|
|
1396
|
+
button: null,
|
|
1397
|
+
notice: null,
|
|
1398
|
+
lines: null,
|
|
1399
|
+
cancel,
|
|
1400
|
+
recording: false
|
|
1401
|
+
};
|
|
1037
1402
|
}
|
|
1038
|
-
const status =
|
|
1403
|
+
const status = withdrawnReason !== null ? S[FAILURE_TEXT[withdrawnReason]] : 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]] : "";
|
|
1039
1404
|
const heard = offered && (state.name === "listening" || state.name === "finishing") ? state.text : "";
|
|
1040
1405
|
const button = !offered ? null : /* @__PURE__ */ jsx(
|
|
1041
1406
|
"button",
|
|
@@ -1044,12 +1409,15 @@ function useAvatarMic({
|
|
|
1044
1409
|
type: "button",
|
|
1045
1410
|
"aria-label": recording ? S.micStop : S.micStart,
|
|
1046
1411
|
"aria-pressed": recording,
|
|
1412
|
+
"aria-disabled": finishing ? true : void 0,
|
|
1047
1413
|
title: recording ? S.micStop : S.micStart,
|
|
1048
1414
|
"data-boff-explore": "avatar-live-mic",
|
|
1049
1415
|
"data-mic": loading2 ? "starting" : state.name,
|
|
1416
|
+
"data-mic-source": kind ?? void 0,
|
|
1050
1417
|
className: cn(
|
|
1051
1418
|
"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",
|
|
1052
|
-
recording ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1419
|
+
recording ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
1420
|
+
finishing && "cursor-wait opacity-50"
|
|
1053
1421
|
),
|
|
1054
1422
|
onClick: tap,
|
|
1055
1423
|
children: recording ? /* @__PURE__ */ jsx(Square, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Mic, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
@@ -1132,12 +1500,22 @@ function useAvatarMic({
|
|
|
1132
1500
|
{
|
|
1133
1501
|
"data-boff-explore": "avatar-live-mic-heard",
|
|
1134
1502
|
dir: "auto",
|
|
1135
|
-
className:
|
|
1503
|
+
className: cn(
|
|
1504
|
+
// rrweb's default block and mask classes: a site's session replay records none of what is heard.
|
|
1505
|
+
REPLAY_PRIVATE,
|
|
1506
|
+
"line-clamp-3 break-words text-sm italic text-muted-foreground"
|
|
1507
|
+
),
|
|
1136
1508
|
children: heard
|
|
1137
1509
|
}
|
|
1138
1510
|
)
|
|
1139
1511
|
] });
|
|
1140
|
-
return {
|
|
1512
|
+
return {
|
|
1513
|
+
button,
|
|
1514
|
+
notice,
|
|
1515
|
+
lines,
|
|
1516
|
+
cancel,
|
|
1517
|
+
recording: offered && state.name === "listening"
|
|
1518
|
+
};
|
|
1141
1519
|
}
|
|
1142
1520
|
|
|
1143
1521
|
// src/components/explore-avatar-turnstile.ts
|
|
@@ -1260,6 +1638,13 @@ var END_GRACE_MS = 1e4;
|
|
|
1260
1638
|
var CLOCK_TOLERANCE_MS = 5e3;
|
|
1261
1639
|
var UNHEARD_GRACE_MS = 5e3;
|
|
1262
1640
|
var PLAYOUT_TAIL_MS = 5e3;
|
|
1641
|
+
var QUIET_PLAYED_OUT_MS = 2500;
|
|
1642
|
+
var OWED_VOICE_WAIT_MS = 15e3;
|
|
1643
|
+
var LISTENING_MARK_MS = 1e4;
|
|
1644
|
+
var LISTENING_MARK_SPACING_MS = 5e3;
|
|
1645
|
+
var BUSY_RETRY_MS = 2e3;
|
|
1646
|
+
var BUSY_RETRY_MAX_MS = 8e3;
|
|
1647
|
+
var MAX_BUSY_RETRIES = 24;
|
|
1263
1648
|
var SERVICE_MAX_AGE_MS = 15 * 6e4;
|
|
1264
1649
|
var ANSWER_OFFER_MS = SERVICE_MAX_AGE_MS - 6e4;
|
|
1265
1650
|
var MAX_TIMER_MS = 2147483647;
|
|
@@ -1274,6 +1659,12 @@ function stopAttending(handle) {
|
|
|
1274
1659
|
if (handle.tail !== null) clearTimeout(handle.tail);
|
|
1275
1660
|
handle.unheard = null;
|
|
1276
1661
|
handle.tail = null;
|
|
1662
|
+
const run = handle.followUps;
|
|
1663
|
+
if (run === null) return;
|
|
1664
|
+
if (run.quiet !== null) clearTimeout(run.quiet);
|
|
1665
|
+
if (run.retry !== null) clearTimeout(run.retry);
|
|
1666
|
+
run.quiet = null;
|
|
1667
|
+
run.retry = null;
|
|
1277
1668
|
}
|
|
1278
1669
|
var REFUSAL_TEXT = {
|
|
1279
1670
|
busy: "busy",
|
|
@@ -1284,6 +1675,18 @@ var REFUSAL_TEXT = {
|
|
|
1284
1675
|
not_eligible: "notEligible",
|
|
1285
1676
|
answer_claimed: "answerClaimed"
|
|
1286
1677
|
};
|
|
1678
|
+
var FOLLOW_UP_TEXT = {
|
|
1679
|
+
no_time: "followUpNoTime",
|
|
1680
|
+
max_answers: "followUpMaxAnswers",
|
|
1681
|
+
// The same line as a first answer the checks refuse: it stays in Chat as text.
|
|
1682
|
+
not_eligible: "notSpoken",
|
|
1683
|
+
answer_claimed: "answerClaimed",
|
|
1684
|
+
limits: "followUpLimits",
|
|
1685
|
+
paused: "followUpPaused",
|
|
1686
|
+
busy: "followUpUnread",
|
|
1687
|
+
answer_failed: "answerFailed",
|
|
1688
|
+
failed: "followUpUnread"
|
|
1689
|
+
};
|
|
1287
1690
|
var EMPTY = {
|
|
1288
1691
|
kind: "empty",
|
|
1289
1692
|
answerId: null,
|
|
@@ -1358,10 +1761,21 @@ function inFlight(conversation) {
|
|
|
1358
1761
|
return conversation?.phase === "sending" || conversation?.phase === "streaming";
|
|
1359
1762
|
}
|
|
1360
1763
|
function acceptsQuestion(slot) {
|
|
1361
|
-
|
|
1764
|
+
if (!slot.any || slot.textMode || slot.reason !== null) return false;
|
|
1765
|
+
return slot.phase === "needs-answer" ? slot.kind === "empty" : slot.phase === "live" && slot.followUp;
|
|
1362
1766
|
}
|
|
1363
|
-
function
|
|
1364
|
-
return
|
|
1767
|
+
function followUpBox(phase, run, token) {
|
|
1768
|
+
return phase.name === "live" && run !== null && phase.session.followUp !== null && !phase.session.followUp.closed && token === run.token;
|
|
1769
|
+
}
|
|
1770
|
+
var NO_MIC = { kind: "none" };
|
|
1771
|
+
function micChoiceOf(result, locale) {
|
|
1772
|
+
if (result.kind !== "available") return NO_MIC;
|
|
1773
|
+
const { offer } = result;
|
|
1774
|
+
const micNotice = avatarConsentCopy(offer.consentVersion, locale)?.micNotice;
|
|
1775
|
+
if (offer.mic !== void 0) {
|
|
1776
|
+
return offer.mic.mode === "browser" && micNotice !== void 0 ? { kind: "browser", notice: micNotice } : NO_MIC;
|
|
1777
|
+
}
|
|
1778
|
+
return offer.stt !== void 0 && micNotice === void 0 ? { kind: "stream", offer: { ...offer.stt, receivedAt: Date.now() } } : NO_MIC;
|
|
1365
1779
|
}
|
|
1366
1780
|
function utcToday() {
|
|
1367
1781
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -1511,8 +1925,9 @@ function AvatarLiveView({
|
|
|
1511
1925
|
const waitRef = useRef(null);
|
|
1512
1926
|
const sendingRef = useRef(false);
|
|
1513
1927
|
const textBoxRef = useRef(false);
|
|
1514
|
-
const [
|
|
1928
|
+
const [micChoice, setMicChoice] = useState(NO_MIC);
|
|
1515
1929
|
const micCancelRef = useRef(() => void 0);
|
|
1930
|
+
const sendDueRef = useRef(() => void 0);
|
|
1516
1931
|
const checkRef = useRef(null);
|
|
1517
1932
|
const askedLocale = useRef(locale);
|
|
1518
1933
|
const startRef = useRef(null);
|
|
@@ -1601,10 +2016,13 @@ function AvatarLiveView({
|
|
|
1601
2016
|
if (!handle || handle.done || latest.current.live.endUnattended !== true) {
|
|
1602
2017
|
return;
|
|
1603
2018
|
}
|
|
1604
|
-
const
|
|
1605
|
-
if (
|
|
2019
|
+
const shown2 = phaseRef.current;
|
|
2020
|
+
if (shown2.name !== "pending" && shown2.name !== "live") return;
|
|
2021
|
+
const current = shown2.name;
|
|
1606
2022
|
const away = !inSight();
|
|
1607
|
-
|
|
2023
|
+
const followUp2 = shown2.session.followUp;
|
|
2024
|
+
const asking = followUp2 !== null && followUp2.pending !== null;
|
|
2025
|
+
if (away && handle.playedOut && current === "live" && !asking) {
|
|
1608
2026
|
finish("finished", "user");
|
|
1609
2027
|
return;
|
|
1610
2028
|
}
|
|
@@ -1623,6 +2041,184 @@ function AvatarLiveView({
|
|
|
1623
2041
|
}
|
|
1624
2042
|
}, UNHEARD_GRACE_MS);
|
|
1625
2043
|
}, [finish, inSight]);
|
|
2044
|
+
const showFollowUp = useCallback(
|
|
2045
|
+
(handle, next, spoken) => {
|
|
2046
|
+
const current = phaseRef.current;
|
|
2047
|
+
if (sessionRef.current !== handle || handle.done || current.name !== "pending" && current.name !== "live" || current.session.followUp === null) {
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
const session = {
|
|
2051
|
+
...current.session,
|
|
2052
|
+
...spoken,
|
|
2053
|
+
followUp: { ...current.session.followUp, ...next }
|
|
2054
|
+
};
|
|
2055
|
+
go(
|
|
2056
|
+
current.name === "live" ? { name: "live", session } : { name: "pending", session }
|
|
2057
|
+
);
|
|
2058
|
+
},
|
|
2059
|
+
[go]
|
|
2060
|
+
);
|
|
2061
|
+
const tell = useCallback(
|
|
2062
|
+
(handle, kind2) => {
|
|
2063
|
+
if (handle.done || handle.followUps === null) return;
|
|
2064
|
+
void requestTurn({
|
|
2065
|
+
base: handle.base,
|
|
2066
|
+
headers: handle.headers,
|
|
2067
|
+
sessionId: handle.session.sessionId,
|
|
2068
|
+
secret: handle.session.endSecret,
|
|
2069
|
+
kind: kind2
|
|
2070
|
+
});
|
|
2071
|
+
},
|
|
2072
|
+
[]
|
|
2073
|
+
);
|
|
2074
|
+
const whenQuiet = useCallback(
|
|
2075
|
+
(handle, delay) => {
|
|
2076
|
+
const run = handle.followUps;
|
|
2077
|
+
if (run === null) return;
|
|
2078
|
+
const wait = (ms) => {
|
|
2079
|
+
if (run.quiet !== null) clearTimeout(run.quiet);
|
|
2080
|
+
run.quiet = setTimeout(() => {
|
|
2081
|
+
run.quiet = null;
|
|
2082
|
+
if (sessionRef.current !== handle || handle.done || run.voice) {
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
if (run.owed) {
|
|
2086
|
+
run.owed = false;
|
|
2087
|
+
wait(OWED_VOICE_WAIT_MS);
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
handle.playedOut = true;
|
|
2091
|
+
showFollowUp(handle, { speaking: false });
|
|
2092
|
+
attend();
|
|
2093
|
+
sendDueRef.current(handle);
|
|
2094
|
+
}, ms);
|
|
2095
|
+
};
|
|
2096
|
+
wait(delay);
|
|
2097
|
+
},
|
|
2098
|
+
[attend, showFollowUp]
|
|
2099
|
+
);
|
|
2100
|
+
const heardVoice = useCallback(
|
|
2101
|
+
(handle, speaking) => {
|
|
2102
|
+
const run = handle.followUps;
|
|
2103
|
+
if (run === null) return;
|
|
2104
|
+
run.voice = speaking;
|
|
2105
|
+
if (!speaking) {
|
|
2106
|
+
whenQuiet(handle, QUIET_PLAYED_OUT_MS);
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
if (run.quiet !== null) clearTimeout(run.quiet);
|
|
2110
|
+
run.quiet = null;
|
|
2111
|
+
run.heard = true;
|
|
2112
|
+
run.starts += 1;
|
|
2113
|
+
run.owed = false;
|
|
2114
|
+
handle.playedOut = false;
|
|
2115
|
+
showFollowUp(handle, { speaking: true });
|
|
2116
|
+
},
|
|
2117
|
+
[showFollowUp, whenQuiet]
|
|
2118
|
+
);
|
|
2119
|
+
const sendDue = useCallback(
|
|
2120
|
+
(handle) => {
|
|
2121
|
+
const run = handle.followUps;
|
|
2122
|
+
const due = run?.due ?? null;
|
|
2123
|
+
if (run === null || due === null || due.sending || run.retry !== null || handle.done || sessionRef.current !== handle || run.heard && !handle.playedOut) {
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
due.sending = true;
|
|
2127
|
+
const starts = run.starts;
|
|
2128
|
+
const read = () => requestTurn({
|
|
2129
|
+
base: handle.base,
|
|
2130
|
+
headers: handle.headers,
|
|
2131
|
+
sessionId: handle.session.sessionId,
|
|
2132
|
+
secret: handle.session.endSecret,
|
|
2133
|
+
kind: "answer",
|
|
2134
|
+
messageId: due.messageId,
|
|
2135
|
+
idempotencyKey: due.idempotencyKey
|
|
2136
|
+
});
|
|
2137
|
+
const notRead = (unread, closed) => {
|
|
2138
|
+
run.due = null;
|
|
2139
|
+
showFollowUp(handle, {
|
|
2140
|
+
pending: null,
|
|
2141
|
+
unread: due.known ?? unread,
|
|
2142
|
+
...closed ? { closed } : {}
|
|
2143
|
+
});
|
|
2144
|
+
attend();
|
|
2145
|
+
};
|
|
2146
|
+
void (async () => {
|
|
2147
|
+
let result = await read();
|
|
2148
|
+
if (result.kind === "failed" && result.lost && !handle.done) {
|
|
2149
|
+
result = await read();
|
|
2150
|
+
}
|
|
2151
|
+
due.sending = false;
|
|
2152
|
+
if (sessionRef.current !== handle || handle.done || run.due !== due) {
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
if (result.kind === "refused" && result.reason === "busy" && due.busy < MAX_BUSY_RETRIES) {
|
|
2156
|
+
due.busy += 1;
|
|
2157
|
+
if (run.heard && !handle.playedOut) return;
|
|
2158
|
+
const delay = Math.max(
|
|
2159
|
+
result.retryAfterSeconds === void 0 ? BUSY_RETRY_MS : result.retryAfterSeconds * 1e3,
|
|
2160
|
+
Math.min(BUSY_RETRY_MAX_MS, BUSY_RETRY_MS * 2 ** (due.busy - 1))
|
|
2161
|
+
);
|
|
2162
|
+
if (Date.now() + delay >= handle.endsAt) {
|
|
2163
|
+
notRead("no_time", false);
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
run.retry = setTimeout(() => {
|
|
2167
|
+
run.retry = null;
|
|
2168
|
+
sendDueRef.current(handle);
|
|
2169
|
+
}, delay);
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
if (result.kind !== "accepted") {
|
|
2173
|
+
notRead(
|
|
2174
|
+
result.kind === "refused" ? result.reason : "failed",
|
|
2175
|
+
result.kind === "gone" || result.kind === "refused" && result.reason === "max_answers"
|
|
2176
|
+
);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
run.due = null;
|
|
2180
|
+
run.answers += 1;
|
|
2181
|
+
const heardSince = run.starts !== starts;
|
|
2182
|
+
run.owed = !heardSince;
|
|
2183
|
+
const playing = !heardSince || run.voice || run.quiet !== null;
|
|
2184
|
+
if (playing) handle.playedOut = false;
|
|
2185
|
+
showFollowUp(
|
|
2186
|
+
handle,
|
|
2187
|
+
{
|
|
2188
|
+
speaking: playing,
|
|
2189
|
+
pending: null,
|
|
2190
|
+
unread: null,
|
|
2191
|
+
closed: run.answers >= run.limits.maxAnswers
|
|
2192
|
+
},
|
|
2193
|
+
{ captions: result.captions, excerpt: result.excerpt }
|
|
2194
|
+
);
|
|
2195
|
+
if (!playing) {
|
|
2196
|
+
attend();
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
if (run.owed && run.heard && !run.voice) whenQuiet(handle, 0);
|
|
2200
|
+
})();
|
|
2201
|
+
},
|
|
2202
|
+
[attend, showFollowUp, whenQuiet]
|
|
2203
|
+
);
|
|
2204
|
+
sendDueRef.current = sendDue;
|
|
2205
|
+
const readFollowUp = useCallback(
|
|
2206
|
+
(handle, messageId, known) => {
|
|
2207
|
+
const run = handle.followUps;
|
|
2208
|
+
if (run === null) return;
|
|
2209
|
+
run.sent.add(messageId);
|
|
2210
|
+
const idempotencyKey = newIdempotencyKey();
|
|
2211
|
+
if (idempotencyKey === null) {
|
|
2212
|
+
showFollowUp(handle, { pending: null, unread: known ?? "failed" });
|
|
2213
|
+
attend();
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
run.due = { messageId, idempotencyKey, known, busy: 0, sending: false };
|
|
2217
|
+
showFollowUp(handle, { pending: "reading", unread: null });
|
|
2218
|
+
sendDue(handle);
|
|
2219
|
+
},
|
|
2220
|
+
[attend, sendDue, showFollowUp]
|
|
2221
|
+
);
|
|
1626
2222
|
const cancelStart = useCallback((reason) => {
|
|
1627
2223
|
const run = startRef.current;
|
|
1628
2224
|
if (!run) return;
|
|
@@ -1633,7 +2229,7 @@ function AvatarLiveView({
|
|
|
1633
2229
|
const applyAvailability = useCallback(
|
|
1634
2230
|
(result, locale2) => {
|
|
1635
2231
|
const { live: live2 } = latest.current;
|
|
1636
|
-
|
|
2232
|
+
setMicChoice(micChoiceOf(result, locale2));
|
|
1637
2233
|
if (result.kind === "inert") {
|
|
1638
2234
|
setMode("suggested");
|
|
1639
2235
|
go(INERT2);
|
|
@@ -1686,16 +2282,33 @@ function AvatarLiveView({
|
|
|
1686
2282
|
});
|
|
1687
2283
|
}, [applyAvailability, go]);
|
|
1688
2284
|
const begin = useCallback(
|
|
1689
|
-
(session,
|
|
2285
|
+
(session, context) => {
|
|
2286
|
+
const { offer } = context;
|
|
1690
2287
|
const received = Date.now();
|
|
1691
2288
|
const longest = offer.maxSessionSeconds * 1e3;
|
|
1692
2289
|
const ahead = session.endsAt - received;
|
|
1693
2290
|
const endsAt = ahead > 0 && ahead <= longest + CLOCK_TOLERANCE_MS ? session.endsAt : received + longest;
|
|
2291
|
+
const followUps = session.followUps !== void 0 && offer.copy.followUps === true && offer.questions === "any" ? {
|
|
2292
|
+
limits: session.followUps,
|
|
2293
|
+
token: context.conversationToken,
|
|
2294
|
+
answers: 1,
|
|
2295
|
+
voice: false,
|
|
2296
|
+
heard: false,
|
|
2297
|
+
starts: 0,
|
|
2298
|
+
owed: true,
|
|
2299
|
+
quiet: null,
|
|
2300
|
+
asked: null,
|
|
2301
|
+
due: null,
|
|
2302
|
+
retry: null,
|
|
2303
|
+
sent: /* @__PURE__ */ new Set([context.messageId]),
|
|
2304
|
+
seq: 0,
|
|
2305
|
+
listenedAt: null
|
|
2306
|
+
} : null;
|
|
1694
2307
|
sessionRef.current = {
|
|
1695
2308
|
session,
|
|
1696
|
-
base:
|
|
1697
|
-
headers:
|
|
1698
|
-
module,
|
|
2309
|
+
base: context.base,
|
|
2310
|
+
headers: context.headers,
|
|
2311
|
+
module: context.module,
|
|
1699
2312
|
room: null,
|
|
1700
2313
|
joined: null,
|
|
1701
2314
|
endsAt,
|
|
@@ -1705,6 +2318,7 @@ function AvatarLiveView({
|
|
|
1705
2318
|
playedOut: false,
|
|
1706
2319
|
unheard: null,
|
|
1707
2320
|
tail: null,
|
|
2321
|
+
followUps,
|
|
1708
2322
|
done: false
|
|
1709
2323
|
};
|
|
1710
2324
|
setBlocked(false);
|
|
@@ -1714,7 +2328,13 @@ function AvatarLiveView({
|
|
|
1714
2328
|
session: {
|
|
1715
2329
|
endsAt,
|
|
1716
2330
|
captions: session.captions,
|
|
1717
|
-
excerpt: session.excerpt
|
|
2331
|
+
excerpt: session.excerpt,
|
|
2332
|
+
followUp: followUps === null ? null : {
|
|
2333
|
+
speaking: true,
|
|
2334
|
+
pending: null,
|
|
2335
|
+
unread: null,
|
|
2336
|
+
closed: followUps.answers >= followUps.limits.maxAnswers
|
|
2337
|
+
}
|
|
1718
2338
|
}
|
|
1719
2339
|
});
|
|
1720
2340
|
},
|
|
@@ -1746,7 +2366,7 @@ function AvatarLiveView({
|
|
|
1746
2366
|
}
|
|
1747
2367
|
switch (result.kind) {
|
|
1748
2368
|
case "created":
|
|
1749
|
-
begin(result.session, context
|
|
2369
|
+
begin(result.session, context);
|
|
1750
2370
|
return;
|
|
1751
2371
|
case "unusable":
|
|
1752
2372
|
sendEnd({
|
|
@@ -1844,7 +2464,14 @@ function AvatarLiveView({
|
|
|
1844
2464
|
return;
|
|
1845
2465
|
}
|
|
1846
2466
|
if (turnstile !== null) go({ name: "creating" });
|
|
1847
|
-
const context = {
|
|
2467
|
+
const context = {
|
|
2468
|
+
module: loaded,
|
|
2469
|
+
offer,
|
|
2470
|
+
messageId,
|
|
2471
|
+
conversationToken,
|
|
2472
|
+
base: base2,
|
|
2473
|
+
headers: headers2
|
|
2474
|
+
};
|
|
1848
2475
|
const result = await requestCreate({
|
|
1849
2476
|
base: base2,
|
|
1850
2477
|
headers: headers2,
|
|
@@ -2149,6 +2776,9 @@ function AvatarLiveView({
|
|
|
2149
2776
|
attend();
|
|
2150
2777
|
}, PLAYOUT_TAIL_MS);
|
|
2151
2778
|
return;
|
|
2779
|
+
case "speaking":
|
|
2780
|
+
heardVoice(handle, event.speaking);
|
|
2781
|
+
return;
|
|
2152
2782
|
case "speaker-left":
|
|
2153
2783
|
finish(
|
|
2154
2784
|
wasLive ? "finished" : "failed",
|
|
@@ -2161,7 +2791,7 @@ function AvatarLiveView({
|
|
|
2161
2791
|
return;
|
|
2162
2792
|
}
|
|
2163
2793
|
},
|
|
2164
|
-
[attend, finish, goLive, inSight]
|
|
2794
|
+
[attend, finish, goLive, heardVoice, inSight]
|
|
2165
2795
|
);
|
|
2166
2796
|
useEffect(() => {
|
|
2167
2797
|
if (phase.name !== "pending") return;
|
|
@@ -2297,20 +2927,28 @@ function AvatarLiveView({
|
|
|
2297
2927
|
const textMode = any && (usedToday && (phase.name === "needs-answer" || phase.name === "consent" || phase.name === "declined" || phase.name === "ended") || phase.name === "refused" && phase.reason === "limits");
|
|
2298
2928
|
textBoxRef.current = textMode;
|
|
2299
2929
|
const askState = !any ? void 0 : textMode ? "text" : phase.name === "needs-answer" ? kind === "empty" ? "ready" : kind : phase.name;
|
|
2300
|
-
const
|
|
2930
|
+
const followUp = phase.name === "live" ? phase.session.followUp : null;
|
|
2931
|
+
const followUpAsks = followUpBox(
|
|
2932
|
+
phase,
|
|
2933
|
+
sessionRef.current?.followUps ?? null,
|
|
2934
|
+
token
|
|
2935
|
+
);
|
|
2936
|
+
const askReason = inFlight(conversation) || followUp !== null && followUp.pending !== null ? S.askBusy : conversation?.canSend ? null : S.askUnavailable;
|
|
2301
2937
|
const refreshMicOffer = useCallback(async () => {
|
|
2302
2938
|
const { live: live2, locale: locale2, base: base2, headers: headers2 } = latest.current;
|
|
2303
2939
|
if (base2 === null) return null;
|
|
2304
|
-
const next =
|
|
2940
|
+
const next = micChoiceOf(
|
|
2305
2941
|
await requestAvailabilityAnswer({
|
|
2306
2942
|
base: base2,
|
|
2307
2943
|
headers: headers2,
|
|
2308
2944
|
site: live2.site,
|
|
2309
2945
|
locale: locale2
|
|
2310
|
-
})
|
|
2946
|
+
}),
|
|
2947
|
+
locale2
|
|
2311
2948
|
);
|
|
2312
|
-
if (next !==
|
|
2313
|
-
|
|
2949
|
+
if (next.kind !== "stream") return null;
|
|
2950
|
+
setMicChoice(next);
|
|
2951
|
+
return next.offer;
|
|
2314
2952
|
}, []);
|
|
2315
2953
|
const onMicText = useCallback(
|
|
2316
2954
|
(text) => {
|
|
@@ -2322,21 +2960,66 @@ function AvatarLiveView({
|
|
|
2322
2960
|
},
|
|
2323
2961
|
[askMaxLength, touchFirst]
|
|
2324
2962
|
);
|
|
2963
|
+
const onMicStart = useCallback(() => {
|
|
2964
|
+
const handle = sessionRef.current;
|
|
2965
|
+
const current = phaseRef.current;
|
|
2966
|
+
if (handle === null || handle.followUps === null || handle.done || current.name !== "live" || current.session.followUp?.speaking !== true) {
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
const run = handle.followUps;
|
|
2970
|
+
tell(handle, "interrupt");
|
|
2971
|
+
if (run.quiet !== null) clearTimeout(run.quiet);
|
|
2972
|
+
run.quiet = null;
|
|
2973
|
+
run.owed = false;
|
|
2974
|
+
handle.playedOut = true;
|
|
2975
|
+
showFollowUp(handle, { speaking: false });
|
|
2976
|
+
}, [showFollowUp, tell]);
|
|
2977
|
+
const micSource = micChoice.kind === "browser" ? { kind: "browser", notice: micChoice.notice } : micChoice.kind === "stream" && base !== null ? {
|
|
2978
|
+
kind: "stream",
|
|
2979
|
+
base,
|
|
2980
|
+
offer: micChoice.offer,
|
|
2981
|
+
refreshOffer: refreshMicOffer
|
|
2982
|
+
} : null;
|
|
2325
2983
|
const mic = useAvatarMic({
|
|
2326
|
-
enabled: live.mic === true &&
|
|
2984
|
+
enabled: live.mic === true && micSource !== null && !off && acceptsQuestion({
|
|
2327
2985
|
any,
|
|
2328
2986
|
phase: phase.name,
|
|
2329
2987
|
kind,
|
|
2330
2988
|
textMode,
|
|
2331
|
-
reason: askReason
|
|
2989
|
+
reason: askReason,
|
|
2990
|
+
followUp: followUpAsks
|
|
2332
2991
|
}),
|
|
2333
2992
|
locale,
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
onText: onMicText
|
|
2993
|
+
source: micSource,
|
|
2994
|
+
onText: onMicText,
|
|
2995
|
+
onStart: onMicStart
|
|
2338
2996
|
});
|
|
2339
2997
|
micCancelRef.current = mic.cancel;
|
|
2998
|
+
const micRecording = mic.recording;
|
|
2999
|
+
useEffect(() => {
|
|
3000
|
+
const handle = sessionRef.current;
|
|
3001
|
+
if (!micRecording || handle === null || handle.followUps === null || handle.done) {
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
const run = handle.followUps;
|
|
3005
|
+
const every = Math.min(
|
|
3006
|
+
LISTENING_MARK_MS,
|
|
3007
|
+
Math.max(1e3, run.limits.idleSeconds * 1e3 / 3)
|
|
3008
|
+
);
|
|
3009
|
+
const spacing = Math.min(LISTENING_MARK_SPACING_MS, every / 2);
|
|
3010
|
+
const mark = () => {
|
|
3011
|
+
const at = Date.now();
|
|
3012
|
+
if (run.listenedAt !== null && at - run.listenedAt < spacing) return;
|
|
3013
|
+
run.listenedAt = at;
|
|
3014
|
+
tell(handle, "listening");
|
|
3015
|
+
};
|
|
3016
|
+
mark();
|
|
3017
|
+
const timer = setInterval(mark, every);
|
|
3018
|
+
return () => {
|
|
3019
|
+
clearInterval(timer);
|
|
3020
|
+
if (sessionRef.current === handle) mark();
|
|
3021
|
+
};
|
|
3022
|
+
}, [micRecording, tell]);
|
|
2340
3023
|
const notNow = useCallback(() => {
|
|
2341
3024
|
const current = phaseRef.current;
|
|
2342
3025
|
if ((current.name === "consent" || current.name === "needs-answer") && current.offer.questions === "any") {
|
|
@@ -2345,6 +3028,43 @@ function AvatarLiveView({
|
|
|
2345
3028
|
}
|
|
2346
3029
|
onContinueInChat();
|
|
2347
3030
|
}, [go, onContinueInChat]);
|
|
3031
|
+
const askFollowUp = useCallback(
|
|
3032
|
+
(handle, conversation2, question) => {
|
|
3033
|
+
const run = handle.followUps;
|
|
3034
|
+
const current = phaseRef.current;
|
|
3035
|
+
if (run === null || handle.done || current.name !== "live" || current.session.followUp?.pending !== null || !followUpBox(current, run, conversation2.getConversationToken())) {
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
sendingRef.current = true;
|
|
3039
|
+
run.seq += 1;
|
|
3040
|
+
const seq = run.seq;
|
|
3041
|
+
setDraft("");
|
|
3042
|
+
tell(handle, "asking");
|
|
3043
|
+
showFollowUp(handle, { pending: "asking", unread: null });
|
|
3044
|
+
void conversation2.send(question, [], { surface: "AVATAR" }).then((added) => {
|
|
3045
|
+
if (added === null) {
|
|
3046
|
+
setDraft((current2) => current2 === "" ? question : current2);
|
|
3047
|
+
}
|
|
3048
|
+
if (sessionRef.current !== handle || handle.done || run.seq !== seq) {
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
if (added === null || added.conversationToken !== run.token) {
|
|
3052
|
+
showFollowUp(handle, { pending: null });
|
|
3053
|
+
attend();
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
run.asked = {
|
|
3057
|
+
messageId: added.userMessageId,
|
|
3058
|
+
token: added.conversationToken,
|
|
3059
|
+
answerId: null
|
|
3060
|
+
};
|
|
3061
|
+
showFollowUp(handle, { pending: "asking" });
|
|
3062
|
+
}).finally(() => {
|
|
3063
|
+
sendingRef.current = false;
|
|
3064
|
+
});
|
|
3065
|
+
},
|
|
3066
|
+
[attend, showFollowUp, tell]
|
|
3067
|
+
);
|
|
2348
3068
|
const ask = useCallback(
|
|
2349
3069
|
(text) => {
|
|
2350
3070
|
const conversation2 = conversationRef.current;
|
|
@@ -2353,6 +3073,11 @@ function AvatarLiveView({
|
|
|
2353
3073
|
return;
|
|
2354
3074
|
}
|
|
2355
3075
|
const spoken = !textBoxRef.current;
|
|
3076
|
+
const handle = sessionRef.current;
|
|
3077
|
+
if (spoken && handle !== null && handle.followUps !== null) {
|
|
3078
|
+
askFollowUp(handle, conversation2, question);
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
2356
3081
|
if (spoken && conversation2.messages.length > 0) return;
|
|
2357
3082
|
sendingRef.current = true;
|
|
2358
3083
|
setAskedHere(null);
|
|
@@ -2374,8 +3099,64 @@ function AvatarLiveView({
|
|
|
2374
3099
|
sendingRef.current = false;
|
|
2375
3100
|
});
|
|
2376
3101
|
},
|
|
2377
|
-
[setAskedHere]
|
|
3102
|
+
[askFollowUp, setAskedHere]
|
|
2378
3103
|
);
|
|
3104
|
+
useEffect(() => {
|
|
3105
|
+
const handle = sessionRef.current;
|
|
3106
|
+
const run = handle?.followUps ?? null;
|
|
3107
|
+
if (handle === null || run === null || handle.done || run.asked === null) {
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
const asked = run.asked;
|
|
3111
|
+
const chat = conversationRef.current;
|
|
3112
|
+
const messages = chat?.messages ?? [];
|
|
3113
|
+
const at = messages.findIndex((message) => message.id === asked.messageId);
|
|
3114
|
+
const question = at === -1 ? void 0 : messages[at];
|
|
3115
|
+
const answer = at === -1 ? void 0 : messages[at + 1];
|
|
3116
|
+
const drop = (unread2) => {
|
|
3117
|
+
run.asked = null;
|
|
3118
|
+
showFollowUp(handle, { pending: null, unread: unread2 });
|
|
3119
|
+
attend();
|
|
3120
|
+
};
|
|
3121
|
+
const unread = (id, reason) => {
|
|
3122
|
+
if (id === null || run.sent.has(id) || !isAvatarMessageId(id)) {
|
|
3123
|
+
drop(reason);
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
run.asked = null;
|
|
3127
|
+
readFollowUp(handle, id, reason);
|
|
3128
|
+
};
|
|
3129
|
+
if (question === void 0 || (chat?.getConversationToken() ?? null) !== run.token) {
|
|
3130
|
+
drop(null);
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
if (answer === void 0) {
|
|
3134
|
+
if (!inFlight(chat)) unread(asked.answerId, "answer_failed");
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
if (answer.status === "PENDING" || answer.status === "RUNNING") {
|
|
3138
|
+
if (answer.role === "ASSISTANT") asked.answerId = answer.id;
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
3141
|
+
if (answer.role !== "ASSISTANT") {
|
|
3142
|
+
unread(asked.answerId, "answer_failed");
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
if (answer.status !== "COMPLETED" || answer.content.trim() === "") {
|
|
3146
|
+
unread(answer.id, "answer_failed");
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
if (run.sent.has(answer.id) || !isAvatarMessageId(answer.id)) {
|
|
3150
|
+
drop("not_eligible");
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
if (at + 2 !== messages.length || !readable(question, answer)) {
|
|
3154
|
+
unread(answer.id, "not_eligible");
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
run.asked = null;
|
|
3158
|
+
readFollowUp(handle, answer.id, null);
|
|
3159
|
+
});
|
|
2379
3160
|
const newChatForAvatar = useCallback(() => {
|
|
2380
3161
|
const conversation2 = conversationRef.current;
|
|
2381
3162
|
if (conversation2 === null) return;
|
|
@@ -2399,7 +3180,14 @@ function AvatarLiveView({
|
|
|
2399
3180
|
const focused = document.activeElement;
|
|
2400
3181
|
if (focused && focused !== document.body && focused.isConnected) return;
|
|
2401
3182
|
rootRef.current?.querySelector(PRIMARY)?.focus();
|
|
2402
|
-
}, [
|
|
3183
|
+
}, [
|
|
3184
|
+
active,
|
|
3185
|
+
phase.name,
|
|
3186
|
+
askState,
|
|
3187
|
+
shown.pendingAnswerId,
|
|
3188
|
+
blocked,
|
|
3189
|
+
followUpAsks
|
|
3190
|
+
]);
|
|
2403
3191
|
const onPlaying = goLive;
|
|
2404
3192
|
const enablePlayback = useCallback(() => {
|
|
2405
3193
|
void sessionRef.current?.joined?.enablePlayback().catch(() => void 0);
|
|
@@ -2799,13 +3587,32 @@ function AvatarLiveView({
|
|
|
2799
3587
|
actions2 = stopButton;
|
|
2800
3588
|
break;
|
|
2801
3589
|
case "pending":
|
|
2802
|
-
case "live":
|
|
2803
|
-
status = phase.name === "live" ? speakingLine(phase.session) : S.connecting;
|
|
3590
|
+
case "live": {
|
|
2804
3591
|
top2 = badge;
|
|
2805
3592
|
media2 = liveMedia(phase.name === "live");
|
|
2806
3593
|
body2 = liveBody(phase.session);
|
|
2807
3594
|
actions2 = liveActions(readInChatButton(false));
|
|
3595
|
+
if (followUp === null) {
|
|
3596
|
+
status = phase.name === "live" ? speakingLine(phase.session) : S.connecting;
|
|
3597
|
+
break;
|
|
3598
|
+
}
|
|
3599
|
+
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;
|
|
3600
|
+
if (!followUp.speaking && followUp.pending === null && followUp.unread !== null && !followUp.closed) {
|
|
3601
|
+
body2 = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3602
|
+
body2,
|
|
3603
|
+
/* @__PURE__ */ jsx(
|
|
3604
|
+
"p",
|
|
3605
|
+
{
|
|
3606
|
+
"data-boff-explore": "avatar-live-idle",
|
|
3607
|
+
className: "text-xs text-muted-foreground",
|
|
3608
|
+
children: S.followUpIdle
|
|
3609
|
+
}
|
|
3610
|
+
)
|
|
3611
|
+
] });
|
|
3612
|
+
}
|
|
3613
|
+
if (followUpAsks) box = "spoken";
|
|
2808
3614
|
break;
|
|
3615
|
+
}
|
|
2809
3616
|
case "ended":
|
|
2810
3617
|
status = S[phase.how];
|
|
2811
3618
|
outcome2 = true;
|
|
@@ -2853,7 +3660,7 @@ function AvatarLiveView({
|
|
|
2853
3660
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: S.newChatNote })
|
|
2854
3661
|
] }) : null,
|
|
2855
3662
|
chips,
|
|
2856
|
-
box !== null && usedToday ? /* @__PURE__ */ jsx(
|
|
3663
|
+
box !== null && usedToday && phase.name !== "live" ? /* @__PURE__ */ jsx(
|
|
2857
3664
|
"p",
|
|
2858
3665
|
{
|
|
2859
3666
|
"data-boff-explore": "avatar-live-used-today",
|
|
@@ -3082,7 +3889,12 @@ function AskBox({
|
|
|
3082
3889
|
dir: "auto",
|
|
3083
3890
|
"data-avatar-primary": touchFirst ? void 0 : "",
|
|
3084
3891
|
"data-boff-explore": "avatar-live-ask-input",
|
|
3085
|
-
className:
|
|
3892
|
+
className: cn(
|
|
3893
|
+
// Kept out of a site's session replay before Send (rrweb's default classes; `rr-ignore` for its input
|
|
3894
|
+
// events): the text may be what the mic heard, or what the visitor typed.
|
|
3895
|
+
REPLAY_PRIVATE,
|
|
3896
|
+
"rr-ignore max-h-32 min-h-11 flex-1 resize-none rounded-md border border-border bg-background px-3 py-2.5 text-base text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:text-sm"
|
|
3897
|
+
),
|
|
3086
3898
|
onChange: (event) => {
|
|
3087
3899
|
setRefusedFiles(false);
|
|
3088
3900
|
onChange(event.target.value);
|