@burdenoff/website-sdk 2026.1105.3 → 2026.1106.1
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 +79 -0
- package/dist/avatar-live-view/index.d.mts +1 -1
- package/dist/avatar-live-view/index.d.ts +1 -1
- package/dist/avatar-live-view/index.js +850 -175
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +843 -168
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/avatar-mic/index.d.mts +54 -60
- package/dist/avatar-mic/index.d.ts +54 -60
- package/dist/avatar-mic/index.js +283 -23
- package/dist/avatar-mic/index.js.map +1 -1
- package/dist/avatar-mic/index.mjs +283 -23
- package/dist/avatar-mic/index.mjs.map +1 -1
- package/dist/{chunk-JGU6JW4N.mjs → chunk-BHEHF3Y5.mjs} +2 -2
- package/dist/{chunk-JGU6JW4N.mjs.map → chunk-BHEHF3Y5.mjs.map} +1 -1
- package/dist/{chunk-3AIAJ2L4.js → chunk-Z2UP22GV.js} +2 -2
- package/dist/{chunk-3AIAJ2L4.js.map → chunk-Z2UP22GV.js.map} +1 -1
- package/dist/{explore-avatar-live-config-1zeXdMer.d.ts → explore-avatar-live-config-CJstiSAN.d.ts} +5 -1
- package/dist/{explore-avatar-live-config-BxiE66Im.d.mts → explore-avatar-live-config-D2PGF8fG.d.mts} +5 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -5
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { turnstileSiteKeyOf, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion } from '../chunk-
|
|
1
|
+
import { turnstileSiteKeyOf, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion } from '../chunk-BHEHF3Y5.mjs';
|
|
2
2
|
import { answerDisplayText, fill } from '../chunk-JZS6FQT6.mjs';
|
|
3
3
|
import { cn } from '../chunk-R7G6TWIS.mjs';
|
|
4
4
|
import '../chunk-VNS5WDWO.mjs';
|
|
@@ -70,15 +70,23 @@ function readSttOffer(value) {
|
|
|
70
70
|
return { token, expiresAt };
|
|
71
71
|
}
|
|
72
72
|
var MIC_MAX_PRESS_SECONDS = 30;
|
|
73
|
+
var AVATAR_MIC_PAUSE_MIN_MS = 2e3;
|
|
74
|
+
var AVATAR_MIC_PAUSE_MAX_MS = 2500;
|
|
73
75
|
var UNREADABLE_MIC = { mode: "unreadable" };
|
|
74
76
|
function readMicOffer(value) {
|
|
75
77
|
if (!isRecord(value)) return UNREADABLE_MIC;
|
|
76
78
|
const keys = Object.keys(value).sort();
|
|
77
|
-
const { mode, maxPressSeconds } = value;
|
|
78
|
-
if (keys
|
|
79
|
+
const { mode, maxPressSeconds, pauseMs } = value;
|
|
80
|
+
if (keys[0] !== "maxPressSeconds" || keys[1] !== "mode" || mode !== "browser" || maxPressSeconds !== MIC_MAX_PRESS_SECONDS) {
|
|
79
81
|
return UNREADABLE_MIC;
|
|
80
82
|
}
|
|
81
|
-
|
|
83
|
+
if (keys.length === 2) {
|
|
84
|
+
return { mode: "browser", maxPressSeconds: MIC_MAX_PRESS_SECONDS };
|
|
85
|
+
}
|
|
86
|
+
if (keys.length !== 3 || keys[2] !== "pauseMs" || typeof pauseMs !== "number" || !Number.isSafeInteger(pauseMs) || pauseMs < AVATAR_MIC_PAUSE_MIN_MS || pauseMs > AVATAR_MIC_PAUSE_MAX_MS) {
|
|
87
|
+
return UNREADABLE_MIC;
|
|
88
|
+
}
|
|
89
|
+
return { mode: "browser", maxPressSeconds: MIC_MAX_PRESS_SECONDS, pauseMs };
|
|
82
90
|
}
|
|
83
91
|
function readAvailability(status, body) {
|
|
84
92
|
if (status !== 200 || !isRecord(body)) return INERT;
|
|
@@ -108,10 +116,13 @@ function readAvailability(status, body) {
|
|
|
108
116
|
};
|
|
109
117
|
}
|
|
110
118
|
const meaning = available ? void 0 : AVAILABILITY_REASONS.get(reason);
|
|
111
|
-
|
|
119
|
+
if (meaning === void 0 || meaning === "inert") return INERT;
|
|
120
|
+
const named = body.consentVersion;
|
|
121
|
+
return {
|
|
112
122
|
kind: "refused",
|
|
113
123
|
reason: meaning,
|
|
114
|
-
questions: body.questions === "any" ? "any" : "suggested"
|
|
124
|
+
questions: body.questions === "any" ? "any" : "suggested",
|
|
125
|
+
...typeof named === "string" && isAvatarVersion(named) ? { consentVersion: named } : {}
|
|
115
126
|
};
|
|
116
127
|
}
|
|
117
128
|
var CREATE_REFUSALS = /* @__PURE__ */ new Map([
|
|
@@ -1081,6 +1092,27 @@ var CONSENT = /* @__PURE__ */ new Map([
|
|
|
1081
1092
|
micNotice: BROWSER_MIC_NOTICE_VERSION
|
|
1082
1093
|
}
|
|
1083
1094
|
}
|
|
1095
|
+
],
|
|
1096
|
+
[
|
|
1097
|
+
// Stage C3 (BOFF-9797, the operator's final decision of 2026-11-06, "nothing at all"): the voice conversation with NO
|
|
1098
|
+
// notice in the Avatar tab. Typed questions, Stage B's three minutes, Stage D's answers after the first, the
|
|
1099
|
+
// browser's own recognizer (AVATAR_WEB_MIC=browser) with the server's pause, and no bot check (alpha and prod both
|
|
1100
|
+
// run it with AVATAR_WEB_BOT_CHECK=off). No words, so nothing to agree to: the server must record no consent for it
|
|
1101
|
+
// and hold no notice digest for it, and the create names it as a protocol token only. Speech processing and the
|
|
1102
|
+
// question sent by itself are the site's privacy policy's to describe. PROD waits for the privacy reviewer's
|
|
1103
|
+
// approval of this experience. (The
|
|
1104
|
+
// notice versions web-consent-2026-11-23-v and -24-v, which no environment ever ran, are retired with the dialog
|
|
1105
|
+
// that showed them: this SDK has no copy for them, so a server naming one gets the Avatar tab as it was.)
|
|
1106
|
+
"web-voice-2026-11-25-n",
|
|
1107
|
+
{
|
|
1108
|
+
en: {
|
|
1109
|
+
notice: "none",
|
|
1110
|
+
botCheck: "off",
|
|
1111
|
+
questions: "any",
|
|
1112
|
+
followUps: true,
|
|
1113
|
+
voice: { pauseMs: 2e3 }
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1084
1116
|
]
|
|
1085
1117
|
]);
|
|
1086
1118
|
function avatarConsentCopy(version, locale) {
|
|
@@ -1111,6 +1143,30 @@ function avatarMicStrings(locale) {
|
|
|
1111
1143
|
if (!isAvatarLiveLocaleReviewed(locale)) return null;
|
|
1112
1144
|
return MIC_STRINGS[locale] ?? null;
|
|
1113
1145
|
}
|
|
1146
|
+
var VOICE_EN = {
|
|
1147
|
+
talk: "Talk",
|
|
1148
|
+
talkInterrupt: "Stop the avatar and talk",
|
|
1149
|
+
stopListening: "Stop listening",
|
|
1150
|
+
start: "Start",
|
|
1151
|
+
cancelled: "The avatar didn't start. Press Start to try again.",
|
|
1152
|
+
starting: "Starting the microphone\u2026",
|
|
1153
|
+
listening: "Listening\u2026",
|
|
1154
|
+
pausing: "Still listening \u2014 take your time",
|
|
1155
|
+
sending: "Sending your question\u2026",
|
|
1156
|
+
again: "The mic listens again after the answer.",
|
|
1157
|
+
off: "Voice is off. Tap Talk to speak, or type your question.",
|
|
1158
|
+
inBox: "Voice is off. Your words are in the box: edit them or press Send.",
|
|
1159
|
+
atLimit: "The mic stopped at thirty seconds. Your words are in the box: press Send to ask.",
|
|
1160
|
+
noQuestion: "No question was heard. Tap Talk to try again, or type your question.",
|
|
1161
|
+
tapNext: "Tap Talk to ask your next question.",
|
|
1162
|
+
headline: "Tap Talk and ask. The avatar answers aloud.",
|
|
1163
|
+
placeholder: "Or type\u2026"
|
|
1164
|
+
};
|
|
1165
|
+
var VOICE_STRINGS = { en: VOICE_EN };
|
|
1166
|
+
function avatarVoiceStrings(locale) {
|
|
1167
|
+
if (!isAvatarLiveLocaleReviewed(locale)) return null;
|
|
1168
|
+
return VOICE_STRINGS[locale] ?? null;
|
|
1169
|
+
}
|
|
1114
1170
|
var AVATAR_MIC_NOTICE_VERSION = "web-mic-2026-11-05";
|
|
1115
1171
|
var MIC_NOTICE = /* @__PURE__ */ new Map([
|
|
1116
1172
|
[
|
|
@@ -1188,6 +1244,7 @@ function canUseBrowserMic() {
|
|
|
1188
1244
|
if (typeof recognizer !== "function") return false;
|
|
1189
1245
|
const agent = navigator.userAgent ?? "";
|
|
1190
1246
|
if (/\bFirefox\//.test(agent) || /\bFxiOS\//.test(agent)) return false;
|
|
1247
|
+
if (/\bOPR\//.test(agent)) return false;
|
|
1191
1248
|
return !("brave" in navigator);
|
|
1192
1249
|
}
|
|
1193
1250
|
var REPLAY_PRIVATE = "rr-block rr-mask";
|
|
@@ -1205,20 +1262,33 @@ function noticeOf(source) {
|
|
|
1205
1262
|
return source.kind === "stream" ? AVATAR_MIC_NOTICE_VERSION : source.notice;
|
|
1206
1263
|
}
|
|
1207
1264
|
function micKeyOf(source, locale) {
|
|
1208
|
-
|
|
1265
|
+
if (source.kind === "stream") return `stream ${source.base}`;
|
|
1266
|
+
const lang = BROWSER_MIC_LANGUAGES[locale]?.lang ?? "";
|
|
1267
|
+
return source.pauseMs === void 0 ? `browser ${lang}` : `browser ${lang} pause ${source.pauseMs}`;
|
|
1268
|
+
}
|
|
1269
|
+
function underWay(state) {
|
|
1270
|
+
return state.name === "starting" || state.name === "listening" || state.name === "finishing";
|
|
1209
1271
|
}
|
|
1210
1272
|
function useAvatarMic({
|
|
1211
1273
|
enabled,
|
|
1212
1274
|
locale,
|
|
1213
1275
|
source,
|
|
1214
1276
|
onText,
|
|
1215
|
-
onStart
|
|
1277
|
+
onStart,
|
|
1278
|
+
onTurn,
|
|
1279
|
+
onPress,
|
|
1280
|
+
echoOf,
|
|
1281
|
+
interruptible = false,
|
|
1282
|
+
note = null
|
|
1216
1283
|
}) {
|
|
1217
1284
|
const S = avatarMicStrings(locale);
|
|
1218
1285
|
const noticeVersion = source === null ? null : noticeOf(source);
|
|
1219
|
-
const copy = noticeVersion === null ? null : avatarMicNotice(noticeVersion, locale);
|
|
1220
1286
|
const kind = source?.kind ?? null;
|
|
1221
1287
|
const micKey = source === null ? null : micKeyOf(source, locale);
|
|
1288
|
+
const paused = source?.kind === "browser" && source.pauseMs !== void 0;
|
|
1289
|
+
const conversation = paused && noticeVersion !== null && avatarConsentCopy(noticeVersion, locale)?.notice === "none";
|
|
1290
|
+
const copy = noticeVersion === null || paused ? null : avatarMicNotice(noticeVersion, locale);
|
|
1291
|
+
const V = conversation ? avatarVoiceStrings(locale) : null;
|
|
1222
1292
|
const uid = useId();
|
|
1223
1293
|
const noticeTitleId = `${uid}-mic-notice`;
|
|
1224
1294
|
const [withdrawn, setWithdrawn] = useState({});
|
|
@@ -1228,13 +1298,19 @@ function useAvatarMic({
|
|
|
1228
1298
|
[]
|
|
1229
1299
|
);
|
|
1230
1300
|
const usable = source === null ? false : source.kind === "stream" ? supported.stream : supported.browser && BROWSER_MIC_LANGUAGES[locale] !== void 0;
|
|
1231
|
-
const
|
|
1301
|
+
const showable = kind !== null && usable && S !== null && (paused ? conversation && V !== null : copy !== null);
|
|
1302
|
+
const possible = enabled && showable;
|
|
1232
1303
|
const withdrawnReason = kind === null ? null : withdrawn[kind] ?? null;
|
|
1233
1304
|
const offered = possible && withdrawnReason === null;
|
|
1234
1305
|
const [state, setState] = useState(IDLE);
|
|
1235
1306
|
const [noticeOpen, setNoticeOpen] = useState(false);
|
|
1236
1307
|
const agreedRef = useRef(null);
|
|
1237
|
-
const [loading2,
|
|
1308
|
+
const [loading2, setLoadingState] = useState(false);
|
|
1309
|
+
const loadingRef = useRef(false);
|
|
1310
|
+
const setLoading = useCallback((value) => {
|
|
1311
|
+
loadingRef.current = value;
|
|
1312
|
+
setLoadingState(value);
|
|
1313
|
+
}, []);
|
|
1238
1314
|
const [loadFailed, setLoadFailed] = useState(false);
|
|
1239
1315
|
const [now, setNow] = useState(() => Date.now());
|
|
1240
1316
|
const micRef = useRef(
|
|
@@ -1244,15 +1320,39 @@ function useAvatarMic({
|
|
|
1244
1320
|
const buttonRef = useRef(null);
|
|
1245
1321
|
const allowRef = useRef(null);
|
|
1246
1322
|
const usedRef = useRef(null);
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1323
|
+
const gestureRef = useRef(false);
|
|
1324
|
+
const [, setUsedTick] = useState(0);
|
|
1325
|
+
const latest = useRef({
|
|
1326
|
+
source,
|
|
1327
|
+
locale,
|
|
1328
|
+
noticeVersion,
|
|
1329
|
+
onText,
|
|
1330
|
+
onStart,
|
|
1331
|
+
onTurn,
|
|
1332
|
+
onPress,
|
|
1333
|
+
echoOf,
|
|
1334
|
+
conversation,
|
|
1335
|
+
offered
|
|
1336
|
+
});
|
|
1337
|
+
latest.current = {
|
|
1338
|
+
source,
|
|
1339
|
+
locale,
|
|
1340
|
+
noticeVersion,
|
|
1341
|
+
onText,
|
|
1342
|
+
onStart,
|
|
1343
|
+
onTurn,
|
|
1344
|
+
onPress,
|
|
1345
|
+
echoOf,
|
|
1346
|
+
conversation,
|
|
1347
|
+
offered
|
|
1348
|
+
};
|
|
1249
1349
|
const cancel = useCallback(() => {
|
|
1250
1350
|
seqRef.current += 1;
|
|
1251
1351
|
setLoading(false);
|
|
1252
1352
|
setLoadFailed(false);
|
|
1253
1353
|
setNoticeOpen(false);
|
|
1254
1354
|
micRef.current?.mic.cancel();
|
|
1255
|
-
}, []);
|
|
1355
|
+
}, [setLoading]);
|
|
1256
1356
|
useEffect(() => {
|
|
1257
1357
|
if (!offered) cancel();
|
|
1258
1358
|
}, [offered, cancel]);
|
|
@@ -1290,11 +1390,22 @@ function useAvatarMic({
|
|
|
1290
1390
|
});
|
|
1291
1391
|
} else {
|
|
1292
1392
|
const language = BROWSER_MIC_LANGUAGES[latest.current.locale] ?? EN_IN;
|
|
1393
|
+
const { pauseMs } = wanted;
|
|
1293
1394
|
mic = loaded.createBrowserAvatarMic({
|
|
1294
1395
|
lang: language.lang,
|
|
1295
1396
|
fallbackLang: language.fallbackLang,
|
|
1296
1397
|
maxPressMs: LIMIT_SECONDS * 1e3,
|
|
1297
|
-
onState: setState
|
|
1398
|
+
onState: setState,
|
|
1399
|
+
// The voice conversation: each press ends itself on the server's pause. The avatar's own words heard back
|
|
1400
|
+
// are looked for only in a press the conversation started by itself: one the visitor's own tap started runs
|
|
1401
|
+
// while the avatar is quiet, or muted here by that tap (a Talk while it speaks interrupts it), so its words are
|
|
1402
|
+
// the visitor's, whatever of the answer they repeat ("Delivery is free across Tamil Nadu?").
|
|
1403
|
+
...pauseMs === void 0 ? {} : {
|
|
1404
|
+
endpoint: {
|
|
1405
|
+
pauseMs,
|
|
1406
|
+
echoOf: () => gestureRef.current ? null : latest.current.echoOf?.() ?? null
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1298
1409
|
});
|
|
1299
1410
|
}
|
|
1300
1411
|
micRef.current = { key, kind: wanted.kind, mic };
|
|
@@ -1302,37 +1413,90 @@ function useAvatarMic({
|
|
|
1302
1413
|
},
|
|
1303
1414
|
[]
|
|
1304
1415
|
);
|
|
1305
|
-
const press = useCallback(
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
ready.
|
|
1316
|
-
|
|
1317
|
-
}
|
|
1318
|
-
setLoading(true);
|
|
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(
|
|
1321
|
-
(mic) => {
|
|
1322
|
-
if (seqRef.current !== seq) return;
|
|
1323
|
-
setLoading(false);
|
|
1324
|
-
mic.start();
|
|
1325
|
-
},
|
|
1326
|
-
() => {
|
|
1327
|
-
if (seqRef.current !== seq) return;
|
|
1416
|
+
const press = useCallback(
|
|
1417
|
+
(gesture) => {
|
|
1418
|
+
const wanted = latest.current.source;
|
|
1419
|
+
if (wanted === null) return;
|
|
1420
|
+
seqRef.current += 1;
|
|
1421
|
+
const seq = seqRef.current;
|
|
1422
|
+
setLoadFailed(false);
|
|
1423
|
+
gestureRef.current = gesture;
|
|
1424
|
+
if (latest.current.conversation) latest.current.onPress?.(gesture);
|
|
1425
|
+
const held = micRef.current;
|
|
1426
|
+
const ready = wanted.kind !== "browser" ? null : held !== null && held.key === micKeyOf(wanted, latest.current.locale) ? held.mic : loadedMic !== null ? micOf(loadedMic, wanted) : null;
|
|
1427
|
+
if (ready !== null) {
|
|
1328
1428
|
setLoading(false);
|
|
1329
|
-
|
|
1429
|
+
ready.start();
|
|
1430
|
+
return;
|
|
1330
1431
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1432
|
+
setLoading(true);
|
|
1433
|
+
const arriving = held !== null && held.key === micKeyOf(wanted, latest.current.locale) ? Promise.resolve(held.mic) : loadAvatarMic().then((loaded) => micOf(loaded, wanted));
|
|
1434
|
+
void arriving.then(
|
|
1435
|
+
(mic) => {
|
|
1436
|
+
if (seqRef.current !== seq) return;
|
|
1437
|
+
setLoading(false);
|
|
1438
|
+
mic.start();
|
|
1439
|
+
},
|
|
1440
|
+
() => {
|
|
1441
|
+
if (seqRef.current !== seq) return;
|
|
1442
|
+
setLoading(false);
|
|
1443
|
+
setLoadFailed(true);
|
|
1444
|
+
if (latest.current.conversation) {
|
|
1445
|
+
latest.current.onTurn?.({
|
|
1446
|
+
by: "failed",
|
|
1447
|
+
text: "",
|
|
1448
|
+
echo: false,
|
|
1449
|
+
gesture,
|
|
1450
|
+
failure: "failed"
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
);
|
|
1455
|
+
},
|
|
1456
|
+
[micOf, setLoading]
|
|
1457
|
+
);
|
|
1333
1458
|
const recording = loading2 || state.name === "starting" || state.name === "listening";
|
|
1334
1459
|
const finishing = kind === "browser" && state.name === "finishing";
|
|
1460
|
+
const preload = useCallback(() => {
|
|
1461
|
+
const seq = seqRef.current;
|
|
1462
|
+
void loadAvatarMic().then(
|
|
1463
|
+
(loaded) => {
|
|
1464
|
+
const wanted = latest.current.source;
|
|
1465
|
+
if (seqRef.current === seq && wanted?.kind === "browser") {
|
|
1466
|
+
micOf(loaded, wanted);
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
() => void 0
|
|
1470
|
+
);
|
|
1471
|
+
}, [micOf]);
|
|
1472
|
+
const pressing = useCallback(() => {
|
|
1473
|
+
const held = micRef.current?.mic.state() ?? IDLE;
|
|
1474
|
+
return loadingRef.current || underWay(held) || (held.name === "done" || held.name === "failed") && held !== usedRef.current;
|
|
1475
|
+
}, []);
|
|
1476
|
+
const stopListening = useCallback(() => {
|
|
1477
|
+
const held = micRef.current?.mic.state() ?? IDLE;
|
|
1478
|
+
let text = "";
|
|
1479
|
+
if (held.name === "listening" || held.name === "finishing") {
|
|
1480
|
+
text = held.text;
|
|
1481
|
+
} else if ((held.name === "done" || held.name === "failed") && held !== usedRef.current) {
|
|
1482
|
+
usedRef.current = held;
|
|
1483
|
+
text = held.text;
|
|
1484
|
+
}
|
|
1485
|
+
const gesture = gestureRef.current;
|
|
1486
|
+
cancel();
|
|
1487
|
+
latest.current.onTurn?.({ by: "tap", text, echo: false, gesture });
|
|
1488
|
+
}, [cancel]);
|
|
1335
1489
|
const tap = useCallback(() => {
|
|
1490
|
+
if (latest.current.conversation) {
|
|
1491
|
+
if (pressing()) {
|
|
1492
|
+
stopListening();
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
latest.current.onStart?.();
|
|
1496
|
+
agreedRef.current = latest.current.noticeVersion;
|
|
1497
|
+
press(true);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1336
1500
|
if (state.name === "listening" || state.name === "starting") {
|
|
1337
1501
|
micRef.current?.mic.stop();
|
|
1338
1502
|
return;
|
|
@@ -1345,30 +1509,56 @@ function useAvatarMic({
|
|
|
1345
1509
|
latest.current.onStart?.();
|
|
1346
1510
|
if (agreedRef.current !== latest.current.noticeVersion) {
|
|
1347
1511
|
setNoticeOpen(true);
|
|
1348
|
-
|
|
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
|
-
);
|
|
1512
|
+
preload();
|
|
1358
1513
|
return;
|
|
1359
1514
|
}
|
|
1360
|
-
press();
|
|
1361
|
-
}, [
|
|
1515
|
+
press(true);
|
|
1516
|
+
}, [
|
|
1517
|
+
cancel,
|
|
1518
|
+
finishing,
|
|
1519
|
+
loading2,
|
|
1520
|
+
preload,
|
|
1521
|
+
press,
|
|
1522
|
+
pressing,
|
|
1523
|
+
state.name,
|
|
1524
|
+
stopListening
|
|
1525
|
+
]);
|
|
1362
1526
|
const allow = useCallback(() => {
|
|
1363
1527
|
agreedRef.current = latest.current.noticeVersion;
|
|
1364
1528
|
setNoticeOpen(false);
|
|
1365
1529
|
buttonRef.current?.focus();
|
|
1366
|
-
press();
|
|
1530
|
+
press(true);
|
|
1367
1531
|
}, [press]);
|
|
1368
1532
|
const notNow = useCallback(() => {
|
|
1369
1533
|
setNoticeOpen(false);
|
|
1370
1534
|
buttonRef.current?.focus();
|
|
1371
1535
|
}, []);
|
|
1536
|
+
const preparing = conversation && offered;
|
|
1537
|
+
useEffect(() => {
|
|
1538
|
+
if (preparing) preload();
|
|
1539
|
+
}, [preparing, preload]);
|
|
1540
|
+
const listen = useCallback(() => {
|
|
1541
|
+
if (!latest.current.conversation || !latest.current.offered || // Never before the visitor's own Talk on this page, under this version.
|
|
1542
|
+
agreedRef.current !== latest.current.noticeVersion || // A press under way, or one that has ended and whose outcome has not gone to the view yet (still that press: a
|
|
1543
|
+
// question that ended on its pause is about to be sent): the mic must not open over it.
|
|
1544
|
+
pressing()) {
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1547
|
+
press(false);
|
|
1548
|
+
return true;
|
|
1549
|
+
}, [press, pressing]);
|
|
1550
|
+
const heard = useCallback(() => {
|
|
1551
|
+
const held = micRef.current?.mic.state() ?? IDLE;
|
|
1552
|
+
return held.name === "listening" || held.name === "finishing" ? held.text : "";
|
|
1553
|
+
}, []);
|
|
1554
|
+
const dropLoopPress = useCallback(() => {
|
|
1555
|
+
const held = micRef.current?.mic.state() ?? IDLE;
|
|
1556
|
+
if (!latest.current.conversation || gestureRef.current || !loadingRef.current && !underWay(held)) {
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
cancel();
|
|
1560
|
+
return true;
|
|
1561
|
+
}, [cancel]);
|
|
1372
1562
|
useEffect(() => {
|
|
1373
1563
|
if (noticeOpen) allowRef.current?.focus();
|
|
1374
1564
|
}, [noticeOpen]);
|
|
@@ -1380,28 +1570,71 @@ function useAvatarMic({
|
|
|
1380
1570
|
return () => clearInterval(timer);
|
|
1381
1571
|
}, [listeningSince]);
|
|
1382
1572
|
useEffect(() => {
|
|
1573
|
+
const voice = latest.current.conversation;
|
|
1383
1574
|
if (state.name === "done" && state !== usedRef.current) {
|
|
1384
1575
|
usedRef.current = state;
|
|
1385
|
-
if (
|
|
1576
|
+
if (voice) {
|
|
1577
|
+
latest.current.onTurn?.({
|
|
1578
|
+
by: state.by ?? (state.limit ? "limit" : "tap"),
|
|
1579
|
+
text: state.text,
|
|
1580
|
+
echo: state.echo === true,
|
|
1581
|
+
gesture: gestureRef.current
|
|
1582
|
+
});
|
|
1583
|
+
setUsedTick((tick) => tick + 1);
|
|
1584
|
+
} else if (state.text !== "") {
|
|
1585
|
+
latest.current.onText(state.text);
|
|
1586
|
+
}
|
|
1386
1587
|
return;
|
|
1387
1588
|
}
|
|
1388
1589
|
const pressed = micRef.current?.kind;
|
|
1389
|
-
if (state.name === "failed" &&
|
|
1590
|
+
if (state.name === "failed" && voice && state !== usedRef.current) {
|
|
1591
|
+
usedRef.current = state;
|
|
1592
|
+
const gesture = gestureRef.current;
|
|
1593
|
+
if (gesture && pressed !== void 0 && FOR_GOOD[pressed].has(state.reason)) {
|
|
1594
|
+
const reason = state.reason;
|
|
1595
|
+
setWithdrawn((held) => ({ ...held, [pressed]: reason }));
|
|
1596
|
+
}
|
|
1597
|
+
latest.current.onTurn?.({
|
|
1598
|
+
by: "failed",
|
|
1599
|
+
text: state.text,
|
|
1600
|
+
echo: false,
|
|
1601
|
+
gesture,
|
|
1602
|
+
failure: state.reason
|
|
1603
|
+
});
|
|
1604
|
+
setUsedTick((tick) => tick + 1);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
if (state.name === "failed" && !voice && pressed !== void 0 && FOR_GOOD[pressed].has(state.reason)) {
|
|
1390
1608
|
const reason = state.reason;
|
|
1391
1609
|
setWithdrawn((held) => ({ ...held, [pressed]: reason }));
|
|
1392
1610
|
}
|
|
1393
1611
|
}, [state]);
|
|
1612
|
+
const outcomeDue = (state.name === "done" || state.name === "failed") && state !== usedRef.current;
|
|
1613
|
+
const busy = loading2 || state.name === "starting" || state.name === "listening" || state.name === "finishing" || outcomeDue;
|
|
1614
|
+
const voiceControls = {
|
|
1615
|
+
mode: conversation ? "conversation" : "editable",
|
|
1616
|
+
speaking: conversation && offered && state.name === "listening" && state.genuine === true,
|
|
1617
|
+
busy,
|
|
1618
|
+
pressing,
|
|
1619
|
+
listen,
|
|
1620
|
+
heard,
|
|
1621
|
+
dropLoopPress
|
|
1622
|
+
};
|
|
1623
|
+
if (conversation && showable && V !== null && S !== null) {
|
|
1624
|
+
return conversationControls(V, S);
|
|
1625
|
+
}
|
|
1394
1626
|
if (!possible || S === null || copy === null) {
|
|
1395
1627
|
return {
|
|
1396
1628
|
button: null,
|
|
1397
1629
|
notice: null,
|
|
1398
1630
|
lines: null,
|
|
1399
1631
|
cancel,
|
|
1400
|
-
recording: false
|
|
1632
|
+
recording: false,
|
|
1633
|
+
...voiceControls
|
|
1401
1634
|
};
|
|
1402
1635
|
}
|
|
1403
1636
|
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]] : "";
|
|
1404
|
-
const
|
|
1637
|
+
const heardText = offered && (state.name === "listening" || state.name === "finishing") ? state.text : "";
|
|
1405
1638
|
const button = !offered ? null : /* @__PURE__ */ jsx(
|
|
1406
1639
|
"button",
|
|
1407
1640
|
{
|
|
@@ -1423,62 +1656,11 @@ function useAvatarMic({
|
|
|
1423
1656
|
children: recording ? /* @__PURE__ */ jsx(Square, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Mic, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
1424
1657
|
}
|
|
1425
1658
|
);
|
|
1426
|
-
const notice = offered && noticeOpen ?
|
|
1427
|
-
"section",
|
|
1428
|
-
{
|
|
1429
|
-
"aria-labelledby": noticeTitleId,
|
|
1430
|
-
"data-boff-explore": "avatar-live-mic-notice",
|
|
1431
|
-
className: "w-full rounded-lg border border-border p-3 text-start",
|
|
1432
|
-
children: [
|
|
1433
|
-
/* @__PURE__ */ jsx("h3", { id: noticeTitleId, className: "text-sm font-semibold", children: copy.title }),
|
|
1434
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.processing }),
|
|
1435
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.storage }),
|
|
1436
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.text }),
|
|
1437
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.limits }),
|
|
1438
|
-
/* @__PURE__ */ jsx(
|
|
1439
|
-
"a",
|
|
1440
|
-
{
|
|
1441
|
-
href: PRIVACY_HREF,
|
|
1442
|
-
target: "_blank",
|
|
1443
|
-
rel: "noopener noreferrer",
|
|
1444
|
-
"data-boff-explore": "avatar-live-mic-privacy",
|
|
1445
|
-
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",
|
|
1446
|
-
children: copy.privacyLink
|
|
1447
|
-
}
|
|
1448
|
-
),
|
|
1449
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
|
|
1450
|
-
/* @__PURE__ */ jsx(
|
|
1451
|
-
"button",
|
|
1452
|
-
{
|
|
1453
|
-
ref: allowRef,
|
|
1454
|
-
type: "button",
|
|
1455
|
-
"data-boff-explore": "avatar-live-mic-allow",
|
|
1456
|
-
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",
|
|
1457
|
-
onClick: allow,
|
|
1458
|
-
children: S.micAllow
|
|
1459
|
-
}
|
|
1460
|
-
),
|
|
1461
|
-
/* @__PURE__ */ jsx(
|
|
1462
|
-
"button",
|
|
1463
|
-
{
|
|
1464
|
-
type: "button",
|
|
1465
|
-
"data-boff-explore": "avatar-live-mic-not-now",
|
|
1466
|
-
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",
|
|
1467
|
-
onClick: notNow,
|
|
1468
|
-
children: S.micNotNow
|
|
1469
|
-
}
|
|
1470
|
-
)
|
|
1471
|
-
] })
|
|
1472
|
-
]
|
|
1473
|
-
}
|
|
1474
|
-
) : null;
|
|
1659
|
+
const notice = offered && noticeOpen ? noticeCard(copy, S.micAllow, S.micNotNow) : null;
|
|
1475
1660
|
const listening = offered && state.name === "listening";
|
|
1476
1661
|
const lines = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1477
1662
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
1478
|
-
listening ?
|
|
1479
|
-
/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75 motion-reduce:animate-none" }),
|
|
1480
|
-
/* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
|
|
1481
|
-
] }) : null,
|
|
1663
|
+
listening ? listeningDot : null,
|
|
1482
1664
|
/* @__PURE__ */ jsx("p", { role: "status", "data-boff-explore": "avatar-live-mic-status", children: status }),
|
|
1483
1665
|
listening ? /* @__PURE__ */ jsx(
|
|
1484
1666
|
"span",
|
|
@@ -1495,7 +1677,69 @@ function useAvatarMic({
|
|
|
1495
1677
|
}
|
|
1496
1678
|
) : null
|
|
1497
1679
|
] }),
|
|
1498
|
-
|
|
1680
|
+
heardLine(heardText)
|
|
1681
|
+
] });
|
|
1682
|
+
return {
|
|
1683
|
+
button,
|
|
1684
|
+
notice,
|
|
1685
|
+
lines,
|
|
1686
|
+
cancel,
|
|
1687
|
+
recording: offered && state.name === "listening",
|
|
1688
|
+
...voiceControls
|
|
1689
|
+
};
|
|
1690
|
+
function noticeCard(words, allowLabel, notNowLabel) {
|
|
1691
|
+
return /* @__PURE__ */ jsxs(
|
|
1692
|
+
"section",
|
|
1693
|
+
{
|
|
1694
|
+
"aria-labelledby": noticeTitleId,
|
|
1695
|
+
"data-boff-explore": "avatar-live-mic-notice",
|
|
1696
|
+
className: "w-full rounded-lg border border-border p-3 text-start",
|
|
1697
|
+
children: [
|
|
1698
|
+
/* @__PURE__ */ jsx("h3", { id: noticeTitleId, className: "text-sm font-semibold", children: words.title }),
|
|
1699
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: words.processing }),
|
|
1700
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: words.storage }),
|
|
1701
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: words.text }),
|
|
1702
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: words.limits }),
|
|
1703
|
+
/* @__PURE__ */ jsx(
|
|
1704
|
+
"a",
|
|
1705
|
+
{
|
|
1706
|
+
href: PRIVACY_HREF,
|
|
1707
|
+
target: "_blank",
|
|
1708
|
+
rel: "noopener noreferrer",
|
|
1709
|
+
"data-boff-explore": "avatar-live-mic-privacy",
|
|
1710
|
+
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",
|
|
1711
|
+
children: words.privacyLink
|
|
1712
|
+
}
|
|
1713
|
+
),
|
|
1714
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
|
|
1715
|
+
/* @__PURE__ */ jsx(
|
|
1716
|
+
"button",
|
|
1717
|
+
{
|
|
1718
|
+
ref: allowRef,
|
|
1719
|
+
type: "button",
|
|
1720
|
+
"data-boff-explore": "avatar-live-mic-allow",
|
|
1721
|
+
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",
|
|
1722
|
+
onClick: allow,
|
|
1723
|
+
children: allowLabel
|
|
1724
|
+
}
|
|
1725
|
+
),
|
|
1726
|
+
/* @__PURE__ */ jsx(
|
|
1727
|
+
"button",
|
|
1728
|
+
{
|
|
1729
|
+
type: "button",
|
|
1730
|
+
"data-boff-explore": "avatar-live-mic-not-now",
|
|
1731
|
+
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",
|
|
1732
|
+
onClick: notNow,
|
|
1733
|
+
children: notNowLabel
|
|
1734
|
+
}
|
|
1735
|
+
)
|
|
1736
|
+
] })
|
|
1737
|
+
]
|
|
1738
|
+
}
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
function heardLine(text) {
|
|
1742
|
+
return text === "" ? null : /* @__PURE__ */ jsx(
|
|
1499
1743
|
"p",
|
|
1500
1744
|
{
|
|
1501
1745
|
"data-boff-explore": "avatar-live-mic-heard",
|
|
@@ -1505,18 +1749,70 @@ function useAvatarMic({
|
|
|
1505
1749
|
REPLAY_PRIVATE,
|
|
1506
1750
|
"line-clamp-3 break-words text-sm italic text-muted-foreground"
|
|
1507
1751
|
),
|
|
1508
|
-
children:
|
|
1752
|
+
children: text
|
|
1509
1753
|
}
|
|
1510
|
-
)
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
function conversationControls(V2, S2) {
|
|
1757
|
+
const going = loading2 || state.name === "starting" || state.name === "listening" || state.name === "finishing";
|
|
1758
|
+
const label = busy ? V2.stopListening : V2.talk;
|
|
1759
|
+
const name = busy ? V2.stopListening : interruptible ? V2.talkInterrupt : V2.talk;
|
|
1760
|
+
const talk = !offered ? null : /* @__PURE__ */ jsxs(
|
|
1761
|
+
"button",
|
|
1762
|
+
{
|
|
1763
|
+
ref: buttonRef,
|
|
1764
|
+
type: "button",
|
|
1765
|
+
"aria-label": name,
|
|
1766
|
+
title: name,
|
|
1767
|
+
"data-boff-explore": "avatar-live-mic",
|
|
1768
|
+
"data-mic": loading2 ? "starting" : state.name,
|
|
1769
|
+
"data-mic-source": "browser",
|
|
1770
|
+
"data-mic-mode": "conversation",
|
|
1771
|
+
className: cn(
|
|
1772
|
+
"inline-flex h-11 shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md border px-3 text-sm font-medium 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",
|
|
1773
|
+
busy ? "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground" : "border-primary bg-primary text-primary-foreground hover:opacity-90"
|
|
1774
|
+
),
|
|
1775
|
+
onClick: tap,
|
|
1776
|
+
children: [
|
|
1777
|
+
busy ? /* @__PURE__ */ jsx(Square, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Mic, { "aria-hidden": "true", className: "h-4 w-4" }),
|
|
1778
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
1779
|
+
]
|
|
1780
|
+
}
|
|
1781
|
+
);
|
|
1782
|
+
const pressLine = !offered ? null : loading2 || state.name === "starting" ? V2.starting : state.name === "listening" ? state.pausing === true ? V2.pausing : V2.listening : state.name === "finishing" ? state.limit ? S2.micFinishing : V2.sending : null;
|
|
1783
|
+
const noteLine = note === null ? "" : note.kind === "line" ? V2[note.line] : S2[FAILURE_TEXT[note.reason]];
|
|
1784
|
+
const line = withdrawnReason !== null ? S2[FAILURE_TEXT[withdrawnReason]] : pressLine ?? noteLine;
|
|
1785
|
+
const heardNow = offered && (state.name === "listening" || state.name === "finishing") ? state.text : "";
|
|
1786
|
+
const listeningNow = offered && state.name === "listening";
|
|
1787
|
+
const shown = possible || line !== "";
|
|
1788
|
+
return {
|
|
1789
|
+
button: talk,
|
|
1790
|
+
notice: null,
|
|
1791
|
+
lines: !shown ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1792
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
1793
|
+
listeningNow ? listeningDot : null,
|
|
1794
|
+
/* @__PURE__ */ jsx(
|
|
1795
|
+
"p",
|
|
1796
|
+
{
|
|
1797
|
+
role: "status",
|
|
1798
|
+
"data-boff-explore": "avatar-live-mic-status",
|
|
1799
|
+
"data-voice": going ? "pressing" : note?.kind ?? "idle",
|
|
1800
|
+
children: line
|
|
1801
|
+
}
|
|
1802
|
+
)
|
|
1803
|
+
] }),
|
|
1804
|
+
heardLine(heardNow)
|
|
1805
|
+
] }),
|
|
1806
|
+
cancel,
|
|
1807
|
+
recording: offered && state.name === "listening",
|
|
1808
|
+
...voiceControls
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1519
1811
|
}
|
|
1812
|
+
var listeningDot = /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "relative flex h-2 w-2 shrink-0", children: [
|
|
1813
|
+
/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75 motion-reduce:animate-none" }),
|
|
1814
|
+
/* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
|
|
1815
|
+
] });
|
|
1520
1816
|
|
|
1521
1817
|
// src/components/explore-avatar-turnstile.ts
|
|
1522
1818
|
var SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
@@ -1645,6 +1941,9 @@ var LISTENING_MARK_SPACING_MS = 5e3;
|
|
|
1645
1941
|
var BUSY_RETRY_MS = 2e3;
|
|
1646
1942
|
var BUSY_RETRY_MAX_MS = 8e3;
|
|
1647
1943
|
var MAX_BUSY_RETRIES = 24;
|
|
1944
|
+
var PAUSE_NOTICE_TOLERANCE_MS = 500;
|
|
1945
|
+
var MAX_ECHO_DISCARDS = 2;
|
|
1946
|
+
var LISTEN_AGAIN_MIN_LEFT_MS = 2e4;
|
|
1648
1947
|
var SERVICE_MAX_AGE_MS = 15 * 6e4;
|
|
1649
1948
|
var ANSWER_OFFER_MS = SERVICE_MAX_AGE_MS - 6e4;
|
|
1650
1949
|
var MAX_TIMER_MS = 2147483647;
|
|
@@ -1654,6 +1953,29 @@ var USED_ON_KEY = "boff.explore.avatarUsedOn";
|
|
|
1654
1953
|
var PRIMARY = '[data-avatar-primary], [data-boff-explore="panel-continue-chat"]';
|
|
1655
1954
|
var IDLE2 = { name: "idle" };
|
|
1656
1955
|
var INERT2 = { name: "inert" };
|
|
1956
|
+
var VOICE_OFF = {
|
|
1957
|
+
on: false,
|
|
1958
|
+
arm: null,
|
|
1959
|
+
echoes: 0,
|
|
1960
|
+
note: null
|
|
1961
|
+
};
|
|
1962
|
+
function lineNote(line) {
|
|
1963
|
+
return { kind: "line", line };
|
|
1964
|
+
}
|
|
1965
|
+
function sameNote(a, b) {
|
|
1966
|
+
if (a === null || b === null) return a === b;
|
|
1967
|
+
return a.kind === "line" ? b.kind === "line" && a.line === b.line : b.kind === "failed" && a.reason === b.reason;
|
|
1968
|
+
}
|
|
1969
|
+
var SESSION_REFUSALS = /* @__PURE__ */ new Set([
|
|
1970
|
+
"no_time",
|
|
1971
|
+
"max_answers",
|
|
1972
|
+
"limits",
|
|
1973
|
+
"paused"
|
|
1974
|
+
]);
|
|
1975
|
+
function playedOutNow(handle) {
|
|
1976
|
+
const run = handle.followUps;
|
|
1977
|
+
return run !== null && !run.voice && run.quiet === null && handle.playedOut && !run.owed;
|
|
1978
|
+
}
|
|
1657
1979
|
function stopAttending(handle) {
|
|
1658
1980
|
if (handle.unheard !== null) clearTimeout(handle.unheard);
|
|
1659
1981
|
if (handle.tail !== null) clearTimeout(handle.tail);
|
|
@@ -1771,9 +2093,15 @@ var NO_MIC = { kind: "none" };
|
|
|
1771
2093
|
function micChoiceOf(result, locale) {
|
|
1772
2094
|
if (result.kind !== "available") return NO_MIC;
|
|
1773
2095
|
const { offer } = result;
|
|
1774
|
-
const
|
|
1775
|
-
|
|
1776
|
-
|
|
2096
|
+
const { mic } = offer;
|
|
2097
|
+
const copy = avatarConsentCopy(offer.consentVersion, locale);
|
|
2098
|
+
if (copy?.notice === "none") {
|
|
2099
|
+
const pauseMs = mic?.mode === "browser" ? mic.pauseMs : void 0;
|
|
2100
|
+
return pauseMs !== void 0 && Math.abs(pauseMs - copy.voice.pauseMs) <= PAUSE_NOTICE_TOLERANCE_MS && avatarVoiceStrings(locale) !== null ? { kind: "browser", notice: offer.consentVersion, pauseMs } : NO_MIC;
|
|
2101
|
+
}
|
|
2102
|
+
const micNotice = copy?.micNotice;
|
|
2103
|
+
if (mic !== void 0) {
|
|
2104
|
+
return mic.mode === "browser" && micNotice !== void 0 && mic.pauseMs === void 0 ? { kind: "browser", notice: micNotice } : NO_MIC;
|
|
1777
2105
|
}
|
|
1778
2106
|
return offer.stt !== void 0 && micNotice === void 0 ? { kind: "stream", offer: { ...offer.stt, receivedAt: Date.now() } } : NO_MIC;
|
|
1779
2107
|
}
|
|
@@ -1873,6 +2201,7 @@ function AvatarLiveView({
|
|
|
1873
2201
|
conversation
|
|
1874
2202
|
}) {
|
|
1875
2203
|
const S = avatarLiveStrings(locale);
|
|
2204
|
+
const voiceWords = avatarVoiceStrings(locale);
|
|
1876
2205
|
const uid = useId();
|
|
1877
2206
|
const consentTitleId = `${uid}-consent`;
|
|
1878
2207
|
const [questions, setQuestions] = useState("suggested");
|
|
@@ -1901,12 +2230,16 @@ function AvatarLiveView({
|
|
|
1901
2230
|
const offerUntil = answerId === null ? null : exchange.offerUntil;
|
|
1902
2231
|
const [phase, setPhase] = useState(IDLE2);
|
|
1903
2232
|
const [blocked, setBlocked] = useState(false);
|
|
2233
|
+
const blockedRef = useRef(false);
|
|
2234
|
+
blockedRef.current = blocked;
|
|
1904
2235
|
const [now, setNow] = useState(() => Date.now());
|
|
1905
2236
|
const [draft, setDraft] = useState("");
|
|
1906
2237
|
const chatMaxLength = conversation?.catalog.limits.maxMessageLength;
|
|
1907
2238
|
const askMaxLength = chatMaxLength !== void 0 && Number.isFinite(chatMaxLength) && chatMaxLength >= 1 ? Math.min(ASK_MAX_LENGTH, Math.floor(chatMaxLength)) : ASK_MAX_LENGTH;
|
|
1908
2239
|
const [note, setNote] = useState(null);
|
|
1909
2240
|
const [usedToday, setUsedToday] = useState(false);
|
|
2241
|
+
const [voiceOffered, setVoiceOffered] = useState(false);
|
|
2242
|
+
const [noNotice, setNoNotice] = useState(false);
|
|
1910
2243
|
const showing = active && !off;
|
|
1911
2244
|
const behindChat = open && !active && !off;
|
|
1912
2245
|
const phaseRef = useRef(IDLE2);
|
|
@@ -1927,6 +2260,34 @@ function AvatarLiveView({
|
|
|
1927
2260
|
const textBoxRef = useRef(false);
|
|
1928
2261
|
const [micChoice, setMicChoice] = useState(NO_MIC);
|
|
1929
2262
|
const micCancelRef = useRef(() => void 0);
|
|
2263
|
+
const micControlRef = useRef(null);
|
|
2264
|
+
const voiceRef = useRef(VOICE_OFF);
|
|
2265
|
+
const [, setVoiceTick] = useState(0);
|
|
2266
|
+
const voiceSet = useCallback((next) => {
|
|
2267
|
+
voiceRef.current = { ...voiceRef.current, ...next };
|
|
2268
|
+
setVoiceTick((tick) => tick + 1);
|
|
2269
|
+
}, []);
|
|
2270
|
+
const askMaxLengthRef = useRef(ASK_MAX_LENGTH);
|
|
2271
|
+
const voiceOff = useCallback(
|
|
2272
|
+
(note2, keep) => {
|
|
2273
|
+
const run = voiceRef.current;
|
|
2274
|
+
if (!run.on && run.arm === null) {
|
|
2275
|
+
if (!sameNote(run.note, note2)) voiceSet({ note: note2 });
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (keep) {
|
|
2279
|
+
const heard = micControlRef.current?.heard() ?? "";
|
|
2280
|
+
if (heard !== "") {
|
|
2281
|
+
setDraft(
|
|
2282
|
+
(current) => withHeardText(current, heard, askMaxLengthRef.current)
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
micCancelRef.current();
|
|
2287
|
+
voiceSet({ ...VOICE_OFF, note: note2 });
|
|
2288
|
+
},
|
|
2289
|
+
[voiceSet]
|
|
2290
|
+
);
|
|
1930
2291
|
const sendDueRef = useRef(() => void 0);
|
|
1931
2292
|
const checkRef = useRef(null);
|
|
1932
2293
|
const askedLocale = useRef(locale);
|
|
@@ -1983,6 +2344,8 @@ function AvatarLiveView({
|
|
|
1983
2344
|
handle.done = true;
|
|
1984
2345
|
stopAttending(handle);
|
|
1985
2346
|
sessionRef.current = null;
|
|
2347
|
+
voiceOff(null, true);
|
|
2348
|
+
if (audioRef.current !== null) audioRef.current.muted = false;
|
|
1986
2349
|
if (reason !== null) {
|
|
1987
2350
|
sendEnd({
|
|
1988
2351
|
base: handle.base,
|
|
@@ -1998,7 +2361,7 @@ function AvatarLiveView({
|
|
|
1998
2361
|
setBlocked(false);
|
|
1999
2362
|
go({ name: "ended", how });
|
|
2000
2363
|
},
|
|
2001
|
-
[go]
|
|
2364
|
+
[go, voiceOff]
|
|
2002
2365
|
);
|
|
2003
2366
|
const goLive = useCallback(() => {
|
|
2004
2367
|
const current = phaseRef.current;
|
|
@@ -2071,6 +2434,18 @@ function AvatarLiveView({
|
|
|
2071
2434
|
},
|
|
2072
2435
|
[]
|
|
2073
2436
|
);
|
|
2437
|
+
const unmute = useCallback((handle) => {
|
|
2438
|
+
const run = handle.followUps;
|
|
2439
|
+
if (run === null || !run.muted) return;
|
|
2440
|
+
run.muted = false;
|
|
2441
|
+
if (audioRef.current !== null) audioRef.current.muted = false;
|
|
2442
|
+
}, []);
|
|
2443
|
+
const holdMute = useCallback(() => {
|
|
2444
|
+
const run = sessionRef.current?.followUps ?? null;
|
|
2445
|
+
const audio = audioRef.current;
|
|
2446
|
+
if (run === null || !run.muted || audio === null || audio.muted) return;
|
|
2447
|
+
audio.muted = true;
|
|
2448
|
+
}, []);
|
|
2074
2449
|
const whenQuiet = useCallback(
|
|
2075
2450
|
(handle, delay) => {
|
|
2076
2451
|
const run = handle.followUps;
|
|
@@ -2084,10 +2459,12 @@ function AvatarLiveView({
|
|
|
2084
2459
|
}
|
|
2085
2460
|
if (run.owed) {
|
|
2086
2461
|
run.owed = false;
|
|
2462
|
+
run.owedWait = true;
|
|
2087
2463
|
wait(OWED_VOICE_WAIT_MS);
|
|
2088
2464
|
return;
|
|
2089
2465
|
}
|
|
2090
2466
|
handle.playedOut = true;
|
|
2467
|
+
if (!run.mutedUnheard) unmute(handle);
|
|
2091
2468
|
showFollowUp(handle, { speaking: false });
|
|
2092
2469
|
attend();
|
|
2093
2470
|
sendDueRef.current(handle);
|
|
@@ -2095,7 +2472,7 @@ function AvatarLiveView({
|
|
|
2095
2472
|
};
|
|
2096
2473
|
wait(delay);
|
|
2097
2474
|
},
|
|
2098
|
-
[attend, showFollowUp]
|
|
2475
|
+
[attend, showFollowUp, unmute]
|
|
2099
2476
|
);
|
|
2100
2477
|
const heardVoice = useCallback(
|
|
2101
2478
|
(handle, speaking) => {
|
|
@@ -2111,7 +2488,10 @@ function AvatarLiveView({
|
|
|
2111
2488
|
run.heard = true;
|
|
2112
2489
|
run.starts += 1;
|
|
2113
2490
|
run.owed = false;
|
|
2491
|
+
run.owedWait = false;
|
|
2492
|
+
run.mutedUnheard = false;
|
|
2114
2493
|
handle.playedOut = false;
|
|
2494
|
+
if (voiceRef.current.on) micControlRef.current?.dropLoopPress();
|
|
2115
2495
|
showFollowUp(handle, { speaking: true });
|
|
2116
2496
|
},
|
|
2117
2497
|
[showFollowUp, whenQuiet]
|
|
@@ -2178,6 +2558,7 @@ function AvatarLiveView({
|
|
|
2178
2558
|
}
|
|
2179
2559
|
run.due = null;
|
|
2180
2560
|
run.answers += 1;
|
|
2561
|
+
unmute(handle);
|
|
2181
2562
|
const heardSince = run.starts !== starts;
|
|
2182
2563
|
run.owed = !heardSince;
|
|
2183
2564
|
const playing = !heardSince || run.voice || run.quiet !== null;
|
|
@@ -2199,7 +2580,7 @@ function AvatarLiveView({
|
|
|
2199
2580
|
if (run.owed && run.heard && !run.voice) whenQuiet(handle, 0);
|
|
2200
2581
|
})();
|
|
2201
2582
|
},
|
|
2202
|
-
[attend, showFollowUp, whenQuiet]
|
|
2583
|
+
[attend, showFollowUp, unmute, whenQuiet]
|
|
2203
2584
|
);
|
|
2204
2585
|
sendDueRef.current = sendDue;
|
|
2205
2586
|
const readFollowUp = useCallback(
|
|
@@ -2236,12 +2617,19 @@ function AvatarLiveView({
|
|
|
2236
2617
|
return;
|
|
2237
2618
|
}
|
|
2238
2619
|
if (result.kind === "refused") {
|
|
2620
|
+
if (result.consentVersion !== void 0) {
|
|
2621
|
+
setNoNotice(
|
|
2622
|
+
avatarConsentCopy(result.consentVersion, locale2)?.notice === "none"
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2239
2625
|
setMode(result.questions);
|
|
2240
2626
|
go({ name: "refused", reason: result.reason });
|
|
2241
2627
|
return;
|
|
2242
2628
|
}
|
|
2243
2629
|
setUsedToday(usedTodayHere());
|
|
2244
2630
|
const copy = avatarConsentCopy(result.offer.consentVersion, locale2);
|
|
2631
|
+
setVoiceOffered(copy?.voice !== void 0);
|
|
2632
|
+
setNoNotice(copy?.notice === "none");
|
|
2245
2633
|
if (copy === null || copy.botCheck !== result.offer.botCheck || copy.questions !== result.offer.questions) {
|
|
2246
2634
|
setMode("suggested");
|
|
2247
2635
|
go(INERT2);
|
|
@@ -2296,13 +2684,16 @@ function AvatarLiveView({
|
|
|
2296
2684
|
heard: false,
|
|
2297
2685
|
starts: 0,
|
|
2298
2686
|
owed: true,
|
|
2687
|
+
owedWait: false,
|
|
2299
2688
|
quiet: null,
|
|
2300
2689
|
asked: null,
|
|
2301
2690
|
due: null,
|
|
2302
2691
|
retry: null,
|
|
2303
2692
|
sent: /* @__PURE__ */ new Set([context.messageId]),
|
|
2304
2693
|
seq: 0,
|
|
2305
|
-
listenedAt: null
|
|
2694
|
+
listenedAt: null,
|
|
2695
|
+
muted: false,
|
|
2696
|
+
mutedUnheard: false
|
|
2306
2697
|
} : null;
|
|
2307
2698
|
sessionRef.current = {
|
|
2308
2699
|
session,
|
|
@@ -2321,6 +2712,7 @@ function AvatarLiveView({
|
|
|
2321
2712
|
followUps,
|
|
2322
2713
|
done: false
|
|
2323
2714
|
};
|
|
2715
|
+
if (audioRef.current !== null) audioRef.current.muted = false;
|
|
2324
2716
|
setBlocked(false);
|
|
2325
2717
|
setNow(received);
|
|
2326
2718
|
go({
|
|
@@ -2576,6 +2968,7 @@ function AvatarLiveView({
|
|
|
2576
2968
|
[applyAvailability, bindingHolds, cancelWaiting, exchangeNow, launch]
|
|
2577
2969
|
);
|
|
2578
2970
|
const stop = useCallback(() => {
|
|
2971
|
+
voiceOff(null, true);
|
|
2579
2972
|
micCancelRef.current();
|
|
2580
2973
|
const current = phaseRef.current;
|
|
2581
2974
|
if (current.name === "waiting") {
|
|
@@ -2595,9 +2988,10 @@ function AvatarLiveView({
|
|
|
2595
2988
|
if (current.name === "pending" || current.name === "live") {
|
|
2596
2989
|
finish("stopped", "user");
|
|
2597
2990
|
}
|
|
2598
|
-
}, [cancelStart, cancelWaiting, finish, go, reoffer]);
|
|
2991
|
+
}, [cancelStart, cancelWaiting, finish, go, reoffer, voiceOff]);
|
|
2599
2992
|
const leave = useCallback(
|
|
2600
2993
|
(event) => {
|
|
2994
|
+
voiceOff(sessionRef.current === null ? null : lineNote("tapNext"), false);
|
|
2601
2995
|
micCancelRef.current();
|
|
2602
2996
|
const current = phaseRef.current;
|
|
2603
2997
|
const reason = event === "user" || event === "chat" ? "user" : "page_hide";
|
|
@@ -2638,7 +3032,7 @@ function AvatarLiveView({
|
|
|
2638
3032
|
return;
|
|
2639
3033
|
}
|
|
2640
3034
|
},
|
|
2641
|
-
[cancelStart, cancelWaiting, finish, go, goLive, reoffer]
|
|
3035
|
+
[cancelStart, cancelWaiting, finish, go, goLive, reoffer, voiceOff]
|
|
2642
3036
|
);
|
|
2643
3037
|
useEffect(() => {
|
|
2644
3038
|
if (!showing) {
|
|
@@ -2752,6 +3146,7 @@ function AvatarLiveView({
|
|
|
2752
3146
|
const wasLive = phaseRef.current.name === "live";
|
|
2753
3147
|
switch (event.type) {
|
|
2754
3148
|
case "speaker-track":
|
|
3149
|
+
holdMute();
|
|
2755
3150
|
if (event.kind !== "video") {
|
|
2756
3151
|
handle.voiceArrived = true;
|
|
2757
3152
|
attend();
|
|
@@ -2761,6 +3156,7 @@ function AvatarLiveView({
|
|
|
2761
3156
|
if (!inSight()) goLive();
|
|
2762
3157
|
return;
|
|
2763
3158
|
case "playback-blocked":
|
|
3159
|
+
holdMute();
|
|
2764
3160
|
setBlocked(event.blocked);
|
|
2765
3161
|
handle.soundHeld = event.sound ?? event.blocked;
|
|
2766
3162
|
attend();
|
|
@@ -2791,7 +3187,7 @@ function AvatarLiveView({
|
|
|
2791
3187
|
return;
|
|
2792
3188
|
}
|
|
2793
3189
|
},
|
|
2794
|
-
[attend, finish, goLive, heardVoice, inSight]
|
|
3190
|
+
[attend, finish, goLive, heardVoice, holdMute, inSight]
|
|
2795
3191
|
);
|
|
2796
3192
|
useEffect(() => {
|
|
2797
3193
|
if (phase.name !== "pending") return;
|
|
@@ -2908,6 +3304,7 @@ function AvatarLiveView({
|
|
|
2908
3304
|
wait?.check?.abort();
|
|
2909
3305
|
const handle = sessionRef.current;
|
|
2910
3306
|
sessionRef.current = null;
|
|
3307
|
+
if (audioRef.current !== null) audioRef.current.muted = false;
|
|
2911
3308
|
if (handle && !handle.done) {
|
|
2912
3309
|
handle.done = true;
|
|
2913
3310
|
stopAttending(handle);
|
|
@@ -2924,7 +3321,8 @@ function AvatarLiveView({
|
|
|
2924
3321
|
[]
|
|
2925
3322
|
);
|
|
2926
3323
|
const any = questions === "any";
|
|
2927
|
-
const
|
|
3324
|
+
const usedHere = usedToday && !voiceOffered;
|
|
3325
|
+
const textMode = any && (usedHere && (phase.name === "needs-answer" || phase.name === "consent" || phase.name === "declined" || phase.name === "ended") || phase.name === "refused" && phase.reason === "limits");
|
|
2928
3326
|
textBoxRef.current = textMode;
|
|
2929
3327
|
const askState = !any ? void 0 : textMode ? "text" : phase.name === "needs-answer" ? kind === "empty" ? "ready" : kind : phase.name;
|
|
2930
3328
|
const followUp = phase.name === "live" ? phase.session.followUp : null;
|
|
@@ -2967,14 +3365,146 @@ function AvatarLiveView({
|
|
|
2967
3365
|
return;
|
|
2968
3366
|
}
|
|
2969
3367
|
const run = handle.followUps;
|
|
3368
|
+
const voiced = micControlRef.current?.mode === "conversation";
|
|
2970
3369
|
tell(handle, "interrupt");
|
|
3370
|
+
if (voiced) {
|
|
3371
|
+
run.muted = true;
|
|
3372
|
+
run.mutedUnheard = run.owed || run.owedWait;
|
|
3373
|
+
if (audioRef.current !== null) audioRef.current.muted = true;
|
|
3374
|
+
}
|
|
2971
3375
|
if (run.quiet !== null) clearTimeout(run.quiet);
|
|
2972
3376
|
run.quiet = null;
|
|
2973
3377
|
run.owed = false;
|
|
2974
3378
|
handle.playedOut = true;
|
|
2975
3379
|
showFollowUp(handle, { speaking: false });
|
|
2976
|
-
|
|
2977
|
-
|
|
3380
|
+
if (voiced && !run.voice) whenQuiet(handle, QUIET_PLAYED_OUT_MS);
|
|
3381
|
+
}, [showFollowUp, tell, whenQuiet]);
|
|
3382
|
+
const firstArmable = useCallback(() => {
|
|
3383
|
+
const current = phaseRef.current;
|
|
3384
|
+
return sessionRef.current === null && !textBoxRef.current && current.name === "needs-answer" && current.offer.copy.notice === "none" && exchangeNow().kind === "empty";
|
|
3385
|
+
}, [exchangeNow]);
|
|
3386
|
+
const armFirst = useCallback(
|
|
3387
|
+
(stage, askSeq) => {
|
|
3388
|
+
const current = phaseRef.current;
|
|
3389
|
+
if (current.name !== "needs-answer") return null;
|
|
3390
|
+
const { live: live2, locale: locale2 } = latest.current;
|
|
3391
|
+
unlockSound(audioRef.current);
|
|
3392
|
+
const module = loadModule(live2.load);
|
|
3393
|
+
module.catch(() => void 0);
|
|
3394
|
+
return {
|
|
3395
|
+
stage,
|
|
3396
|
+
consentVersion: current.offer.consentVersion,
|
|
3397
|
+
botCheck: current.offer.botCheck,
|
|
3398
|
+
site: live2.site,
|
|
3399
|
+
locale: locale2,
|
|
3400
|
+
path: pathNow(),
|
|
3401
|
+
module,
|
|
3402
|
+
askSeq
|
|
3403
|
+
};
|
|
3404
|
+
},
|
|
3405
|
+
[pathNow]
|
|
3406
|
+
);
|
|
3407
|
+
const onMicPress = useCallback(
|
|
3408
|
+
(gesture) => {
|
|
3409
|
+
const run = voiceRef.current;
|
|
3410
|
+
if (run.on && run.note === null && !gesture) return;
|
|
3411
|
+
const arm = gesture && firstArmable() ? armFirst("pressing", null) : null;
|
|
3412
|
+
voiceSet({
|
|
3413
|
+
on: true,
|
|
3414
|
+
note: null,
|
|
3415
|
+
...gesture ? { echoes: 0, arm } : {}
|
|
3416
|
+
});
|
|
3417
|
+
},
|
|
3418
|
+
[armFirst, firstArmable, voiceSet]
|
|
3419
|
+
);
|
|
3420
|
+
const echoOf = useCallback(() => {
|
|
3421
|
+
const current = phaseRef.current;
|
|
3422
|
+
return current.name === "live" && current.session.followUp !== null && sessionRef.current?.followUps ? current.session.captions.text : null;
|
|
3423
|
+
}, []);
|
|
3424
|
+
const askRef = useRef(() => null);
|
|
3425
|
+
const askReasonRef = useRef(null);
|
|
3426
|
+
const draftRef = useRef("");
|
|
3427
|
+
draftRef.current = draft;
|
|
3428
|
+
askMaxLengthRef.current = askMaxLength;
|
|
3429
|
+
const autoSend = useCallback(
|
|
3430
|
+
(text) => {
|
|
3431
|
+
const question = text.trim();
|
|
3432
|
+
const refuse = () => {
|
|
3433
|
+
onMicText(text);
|
|
3434
|
+
voiceOff(lineNote("inBox"), false);
|
|
3435
|
+
};
|
|
3436
|
+
if (question === "" || askReasonRef.current !== null || sendingRef.current || question.length > askMaxLengthRef.current || draftRef.current.trim() !== "") {
|
|
3437
|
+
refuse();
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
const sending = askRef.current(question);
|
|
3441
|
+
if (sending === null) {
|
|
3442
|
+
refuse();
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
const arm = voiceRef.current.arm;
|
|
3446
|
+
voiceSet({
|
|
3447
|
+
echoes: 0,
|
|
3448
|
+
note: lineNote("sending"),
|
|
3449
|
+
...arm === null ? {} : { arm: { ...arm, stage: "asked", askSeq: askSeqRef.current } }
|
|
3450
|
+
});
|
|
3451
|
+
void sending.then(
|
|
3452
|
+
(asked) => {
|
|
3453
|
+
if (!asked) {
|
|
3454
|
+
voiceOff(lineNote("off"), false);
|
|
3455
|
+
return;
|
|
3456
|
+
}
|
|
3457
|
+
const run = voiceRef.current;
|
|
3458
|
+
if (run.on && sameNote(run.note, lineNote("sending"))) {
|
|
3459
|
+
voiceSet({ note: lineNote("again") });
|
|
3460
|
+
}
|
|
3461
|
+
},
|
|
3462
|
+
() => voiceOff(lineNote("off"), false)
|
|
3463
|
+
);
|
|
3464
|
+
},
|
|
3465
|
+
[onMicText, voiceOff, voiceSet]
|
|
3466
|
+
);
|
|
3467
|
+
const onMicTurn = useCallback(
|
|
3468
|
+
(turn) => {
|
|
3469
|
+
const run = voiceRef.current;
|
|
3470
|
+
if (!run.on) return;
|
|
3471
|
+
if (turn.by === "pause" && turn.echo) {
|
|
3472
|
+
if (run.echoes >= MAX_ECHO_DISCARDS || run.arm !== null) {
|
|
3473
|
+
voiceOff(lineNote("off"), false);
|
|
3474
|
+
} else {
|
|
3475
|
+
voiceSet({ echoes: run.echoes + 1, note: null });
|
|
3476
|
+
}
|
|
3477
|
+
return;
|
|
3478
|
+
}
|
|
3479
|
+
if (turn.by === "pause") {
|
|
3480
|
+
autoSend(turn.text);
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3483
|
+
if (turn.by === "silence") {
|
|
3484
|
+
voiceOff(lineNote("noQuestion"), false);
|
|
3485
|
+
return;
|
|
3486
|
+
}
|
|
3487
|
+
if (turn.text.trim() !== "") {
|
|
3488
|
+
onMicText(turn.text);
|
|
3489
|
+
voiceOff(lineNote(turn.by === "limit" ? "atLimit" : "inBox"), false);
|
|
3490
|
+
return;
|
|
3491
|
+
}
|
|
3492
|
+
if (turn.by === "failed") {
|
|
3493
|
+
voiceOff(
|
|
3494
|
+
turn.gesture ? { kind: "failed", reason: turn.failure ?? "failed" } : lineNote("tapNext"),
|
|
3495
|
+
false
|
|
3496
|
+
);
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
voiceOff(lineNote("off"), false);
|
|
3500
|
+
},
|
|
3501
|
+
[autoSend, onMicText, voiceOff, voiceSet]
|
|
3502
|
+
);
|
|
3503
|
+
const micSource = micChoice.kind === "browser" ? {
|
|
3504
|
+
kind: "browser",
|
|
3505
|
+
notice: micChoice.notice,
|
|
3506
|
+
...micChoice.pauseMs === void 0 ? {} : { pauseMs: micChoice.pauseMs }
|
|
3507
|
+
} : micChoice.kind === "stream" && base !== null ? {
|
|
2978
3508
|
kind: "stream",
|
|
2979
3509
|
base,
|
|
2980
3510
|
offer: micChoice.offer,
|
|
@@ -2992,10 +3522,17 @@ function AvatarLiveView({
|
|
|
2992
3522
|
locale,
|
|
2993
3523
|
source: micSource,
|
|
2994
3524
|
onText: onMicText,
|
|
2995
|
-
onStart: onMicStart
|
|
3525
|
+
onStart: onMicStart,
|
|
3526
|
+
onTurn: onMicTurn,
|
|
3527
|
+
onPress: onMicPress,
|
|
3528
|
+
echoOf,
|
|
3529
|
+
interruptible: followUp?.speaking === true,
|
|
3530
|
+
note: voiceRef.current.note
|
|
2996
3531
|
});
|
|
2997
3532
|
micCancelRef.current = mic.cancel;
|
|
2998
|
-
|
|
3533
|
+
micControlRef.current = mic;
|
|
3534
|
+
askReasonRef.current = askReason;
|
|
3535
|
+
const micRecording = mic.mode === "conversation" ? mic.speaking : mic.recording;
|
|
2999
3536
|
useEffect(() => {
|
|
3000
3537
|
const handle = sessionRef.current;
|
|
3001
3538
|
if (!micRecording || handle === null || handle.followUps === null || handle.done) {
|
|
@@ -3033,7 +3570,7 @@ function AvatarLiveView({
|
|
|
3033
3570
|
const run = handle.followUps;
|
|
3034
3571
|
const current = phaseRef.current;
|
|
3035
3572
|
if (run === null || handle.done || current.name !== "live" || current.session.followUp?.pending !== null || !followUpBox(current, run, conversation2.getConversationToken())) {
|
|
3036
|
-
return;
|
|
3573
|
+
return null;
|
|
3037
3574
|
}
|
|
3038
3575
|
sendingRef.current = true;
|
|
3039
3576
|
run.seq += 1;
|
|
@@ -3041,17 +3578,17 @@ function AvatarLiveView({
|
|
|
3041
3578
|
setDraft("");
|
|
3042
3579
|
tell(handle, "asking");
|
|
3043
3580
|
showFollowUp(handle, { pending: "asking", unread: null });
|
|
3044
|
-
|
|
3581
|
+
return conversation2.send(question, [], { surface: "AVATAR" }).then((added) => {
|
|
3045
3582
|
if (added === null) {
|
|
3046
3583
|
setDraft((current2) => current2 === "" ? question : current2);
|
|
3047
3584
|
}
|
|
3048
3585
|
if (sessionRef.current !== handle || handle.done || run.seq !== seq) {
|
|
3049
|
-
return;
|
|
3586
|
+
return false;
|
|
3050
3587
|
}
|
|
3051
3588
|
if (added === null || added.conversationToken !== run.token) {
|
|
3052
3589
|
showFollowUp(handle, { pending: null });
|
|
3053
3590
|
attend();
|
|
3054
|
-
return;
|
|
3591
|
+
return false;
|
|
3055
3592
|
}
|
|
3056
3593
|
run.asked = {
|
|
3057
3594
|
messageId: added.userMessageId,
|
|
@@ -3059,6 +3596,7 @@ function AvatarLiveView({
|
|
|
3059
3596
|
answerId: null
|
|
3060
3597
|
};
|
|
3061
3598
|
showFollowUp(handle, { pending: "asking" });
|
|
3599
|
+
return true;
|
|
3062
3600
|
}).finally(() => {
|
|
3063
3601
|
sendingRef.current = false;
|
|
3064
3602
|
});
|
|
@@ -3070,37 +3608,135 @@ function AvatarLiveView({
|
|
|
3070
3608
|
const conversation2 = conversationRef.current;
|
|
3071
3609
|
const question = text.trim();
|
|
3072
3610
|
if (conversation2 === null || question === "" || sendingRef.current) {
|
|
3073
|
-
return;
|
|
3611
|
+
return null;
|
|
3074
3612
|
}
|
|
3075
3613
|
const spoken = !textBoxRef.current;
|
|
3076
3614
|
const handle = sessionRef.current;
|
|
3077
3615
|
if (spoken && handle !== null && handle.followUps !== null) {
|
|
3078
|
-
askFollowUp(handle, conversation2, question);
|
|
3079
|
-
return;
|
|
3616
|
+
return askFollowUp(handle, conversation2, question);
|
|
3080
3617
|
}
|
|
3081
|
-
if (spoken && conversation2.messages.length > 0) return;
|
|
3618
|
+
if (spoken && conversation2.messages.length > 0) return null;
|
|
3082
3619
|
sendingRef.current = true;
|
|
3083
3620
|
setAskedHere(null);
|
|
3084
3621
|
const seq = askSeqRef.current;
|
|
3085
3622
|
setNote(null);
|
|
3086
3623
|
setDraft("");
|
|
3087
|
-
|
|
3624
|
+
return conversation2.send(question, [], spoken ? { surface: "AVATAR" } : {}).then((added) => {
|
|
3088
3625
|
if (added === null) {
|
|
3089
3626
|
setDraft((current) => current === "" ? question : current);
|
|
3090
3627
|
}
|
|
3091
|
-
if (
|
|
3092
|
-
return;
|
|
3628
|
+
if (added === null || spoken && (askSeqRef.current !== seq || added.conversationToken !== (conversationRef.current?.getConversationToken() ?? null))) {
|
|
3629
|
+
return false;
|
|
3093
3630
|
}
|
|
3631
|
+
if (!spoken) return true;
|
|
3094
3632
|
setAskedHere({
|
|
3095
3633
|
messageId: added.userMessageId,
|
|
3096
3634
|
token: added.conversationToken
|
|
3097
3635
|
});
|
|
3636
|
+
return true;
|
|
3098
3637
|
}).finally(() => {
|
|
3099
3638
|
sendingRef.current = false;
|
|
3100
3639
|
});
|
|
3101
3640
|
},
|
|
3102
3641
|
[askFollowUp, setAskedHere]
|
|
3103
3642
|
);
|
|
3643
|
+
askRef.current = ask;
|
|
3644
|
+
const askTyped = useCallback(
|
|
3645
|
+
(text) => {
|
|
3646
|
+
voiceOff(null, false);
|
|
3647
|
+
const armable = firstArmable();
|
|
3648
|
+
const sending = ask(text);
|
|
3649
|
+
if (sending !== null && armable) {
|
|
3650
|
+
voiceSet({ arm: armFirst("asked", askSeqRef.current) });
|
|
3651
|
+
}
|
|
3652
|
+
return sending;
|
|
3653
|
+
},
|
|
3654
|
+
[armFirst, ask, firstArmable, voiceOff, voiceSet]
|
|
3655
|
+
);
|
|
3656
|
+
const onDraftChange = useCallback(
|
|
3657
|
+
(value) => {
|
|
3658
|
+
setDraft(value);
|
|
3659
|
+
const run = voiceRef.current;
|
|
3660
|
+
if (run.on || run.arm !== null) voiceOff(lineNote("off"), true);
|
|
3661
|
+
},
|
|
3662
|
+
[voiceOff]
|
|
3663
|
+
);
|
|
3664
|
+
const armVoiceStart = useCallback(() => {
|
|
3665
|
+
const arm = voiceRef.current.arm;
|
|
3666
|
+
if (arm === null || arm.stage !== "asked") return;
|
|
3667
|
+
const current = phaseRef.current;
|
|
3668
|
+
const exchange2 = exchangeNow();
|
|
3669
|
+
const drop = () => voiceSet({ arm: null });
|
|
3670
|
+
if (arm.askSeq !== askSeqRef.current || current.name !== "needs-answer" && current.name !== "consent") {
|
|
3671
|
+
drop();
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
if (sendingRef.current || exchange2.kind === "asking" && (!exchange2.askedHere || exchange2.pendingAnswerId === null)) {
|
|
3675
|
+
return;
|
|
3676
|
+
}
|
|
3677
|
+
const answerId2 = exchange2.kind === "asking" ? exchange2.pendingAnswerId : exchange2.kind === "answer" ? exchange2.answerId : null;
|
|
3678
|
+
const token2 = conversationRef.current?.getConversationToken() ?? null;
|
|
3679
|
+
const { live: live2, locale: locale2 } = latest.current;
|
|
3680
|
+
const path = pathNow();
|
|
3681
|
+
if (answerId2 === null || !exchange2.askedHere || exchange2.question === null || token2 === null || !isAvatarConversationToken(token2) || arm.consentVersion !== current.offer.consentVersion || arm.botCheck !== current.offer.botCheck || arm.site !== live2.site || arm.locale !== locale2 || arm.path !== path) {
|
|
3682
|
+
drop();
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
voiceSet({ arm: null });
|
|
3686
|
+
waitRef.current = {
|
|
3687
|
+
bound: {
|
|
3688
|
+
token: token2,
|
|
3689
|
+
questionId: exchange2.question.id,
|
|
3690
|
+
answerId: answerId2,
|
|
3691
|
+
site: live2.site,
|
|
3692
|
+
locale: locale2,
|
|
3693
|
+
path,
|
|
3694
|
+
consentVersion: arm.consentVersion,
|
|
3695
|
+
botCheck: arm.botCheck
|
|
3696
|
+
},
|
|
3697
|
+
module: arm.module,
|
|
3698
|
+
check: null
|
|
3699
|
+
};
|
|
3700
|
+
setNote(null);
|
|
3701
|
+
go({ name: "waiting", offer: current.offer });
|
|
3702
|
+
}, [exchangeNow, go, pathNow, voiceSet]);
|
|
3703
|
+
useEffect(() => {
|
|
3704
|
+
armVoiceStart();
|
|
3705
|
+
});
|
|
3706
|
+
useEffect(() => {
|
|
3707
|
+
const run = voiceRef.current;
|
|
3708
|
+
if (!run.on && run.arm === null) return;
|
|
3709
|
+
const current = phaseRef.current;
|
|
3710
|
+
const live2 = current.name === "live" ? current.session.followUp : null;
|
|
3711
|
+
const keeps = (mic.mode === "conversation" || !run.on) && !textMode && (current.name === "needs-answer" && (sendingRef.current || kind === "empty" || kind === "asking" || kind === "answer") || current.name === "consent" || current.name === "waiting" || current.name === "bot-check" || current.name === "creating" || current.name === "pending" || current.name === "live" && followUpAsks && live2 !== null && !live2.closed && (live2.unread === null || !SESSION_REFUSALS.has(live2.unread)));
|
|
3712
|
+
if (!keeps) voiceOff(lineNote("off"), true);
|
|
3713
|
+
});
|
|
3714
|
+
useEffect(() => {
|
|
3715
|
+
const run = voiceRef.current;
|
|
3716
|
+
const controls = micControlRef.current;
|
|
3717
|
+
const handle = sessionRef.current;
|
|
3718
|
+
const followUps = handle?.followUps ?? null;
|
|
3719
|
+
const current = phaseRef.current;
|
|
3720
|
+
if (!run.on || controls === null || controls.mode !== "conversation" || handle === null || followUps === null || handle.done || current.name !== "live") {
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
const shownFollowUp = current.session.followUp;
|
|
3724
|
+
if (shownFollowUp === null || shownFollowUp.speaking || shownFollowUp.pending !== null || shownFollowUp.closed || shownFollowUp.unread !== null && SESSION_REFUSALS.has(shownFollowUp.unread) || !followUpBox(
|
|
3725
|
+
current,
|
|
3726
|
+
followUps,
|
|
3727
|
+
conversationRef.current?.getConversationToken() ?? null
|
|
3728
|
+
)) {
|
|
3729
|
+
return;
|
|
3730
|
+
}
|
|
3731
|
+
if (sendingRef.current || followUps.asked !== null || followUps.due !== null || !playedOutNow(handle) || blockedRef.current || !inSight() || controls.pressing()) {
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
if (handle.endsAt - Date.now() < LISTEN_AGAIN_MIN_LEFT_MS) {
|
|
3735
|
+
voiceOff(lineNote("off"), false);
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
controls.listen();
|
|
3739
|
+
});
|
|
3104
3740
|
useEffect(() => {
|
|
3105
3741
|
const handle = sessionRef.current;
|
|
3106
3742
|
const run = handle?.followUps ?? null;
|
|
@@ -3175,6 +3811,8 @@ function AvatarLiveView({
|
|
|
3175
3811
|
check();
|
|
3176
3812
|
}
|
|
3177
3813
|
}, [check, exchangeNow, go, setAskedHere]);
|
|
3814
|
+
const voiceTalk = mic.mode === "conversation" && mic.button !== null && voiceWords !== null;
|
|
3815
|
+
const voiceArming = voiceRef.current.arm?.stage === "asked";
|
|
3178
3816
|
useEffect(() => {
|
|
3179
3817
|
if (!active) return;
|
|
3180
3818
|
const focused = document.activeElement;
|
|
@@ -3190,8 +3828,12 @@ function AvatarLiveView({
|
|
|
3190
3828
|
]);
|
|
3191
3829
|
const onPlaying = goLive;
|
|
3192
3830
|
const enablePlayback = useCallback(() => {
|
|
3193
|
-
|
|
3194
|
-
|
|
3831
|
+
const room = sessionRef.current?.joined ?? null;
|
|
3832
|
+
if (room === null) return;
|
|
3833
|
+
const resumed = room.enablePlayback();
|
|
3834
|
+
holdMute();
|
|
3835
|
+
void resumed.catch(() => void 0).then(holdMute);
|
|
3836
|
+
}, [holdMute]);
|
|
3195
3837
|
if (off || phase.name === "inert") {
|
|
3196
3838
|
return (
|
|
3197
3839
|
// `display: contents`: the earlier Avatar view's picture, line and button lay out in the pane exactly as before.
|
|
@@ -3310,7 +3952,7 @@ function AvatarLiveView({
|
|
|
3310
3952
|
]
|
|
3311
3953
|
}
|
|
3312
3954
|
);
|
|
3313
|
-
const consentActions = (whenReady) => /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
3955
|
+
const consentActions = (whenReady, label = whenReady ? S.startWhenReady : S.start) => /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
3314
3956
|
/* @__PURE__ */ jsx(
|
|
3315
3957
|
"button",
|
|
3316
3958
|
{
|
|
@@ -3319,7 +3961,7 @@ function AvatarLiveView({
|
|
|
3319
3961
|
"data-boff-explore": whenReady ? "avatar-live-start-when-ready" : "avatar-live-start",
|
|
3320
3962
|
className: primaryButton,
|
|
3321
3963
|
onClick: whenReady ? startWhenReady : start,
|
|
3322
|
-
children:
|
|
3964
|
+
children: label
|
|
3323
3965
|
}
|
|
3324
3966
|
),
|
|
3325
3967
|
/* @__PURE__ */ jsx(
|
|
@@ -3363,7 +4005,14 @@ function AvatarLiveView({
|
|
|
3363
4005
|
className: "absolute inset-0 h-full w-full object-cover"
|
|
3364
4006
|
}
|
|
3365
4007
|
),
|
|
3366
|
-
any ? null : /* @__PURE__ */ jsx(
|
|
4008
|
+
any ? null : /* @__PURE__ */ jsx(
|
|
4009
|
+
"audio",
|
|
4010
|
+
{
|
|
4011
|
+
ref: audioRef,
|
|
4012
|
+
"data-boff-explore": "avatar-live-audio",
|
|
4013
|
+
onVolumeChange: holdMute
|
|
4014
|
+
}
|
|
4015
|
+
)
|
|
3367
4016
|
]
|
|
3368
4017
|
}
|
|
3369
4018
|
);
|
|
@@ -3496,20 +4145,24 @@ function AvatarLiveView({
|
|
|
3496
4145
|
switch (shown.kind) {
|
|
3497
4146
|
case "empty":
|
|
3498
4147
|
status = note === "new-chat" ? S.newChatStarted : "";
|
|
3499
|
-
lead = /* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: S.askHeadline });
|
|
4148
|
+
lead = /* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: voiceTalk && voiceWords !== null ? voiceWords.headline : S.askHeadline });
|
|
3500
4149
|
box = "spoken";
|
|
3501
4150
|
break;
|
|
3502
4151
|
case "asking":
|
|
3503
4152
|
case "answer": {
|
|
3504
|
-
const card = shown.askedHere && shown.pendingAnswerId !== null;
|
|
3505
|
-
|
|
3506
|
-
|
|
4153
|
+
const card = shown.askedHere && shown.pendingAnswerId !== null && !voiceArming;
|
|
4154
|
+
const words = phase.offer.copy.notice === "none" ? null : phase.offer.copy;
|
|
4155
|
+
status = note === "cancelled" ? words === null && voiceWords !== null ? voiceWords.cancelled : S.armedCancelled : S.gettingAnswer;
|
|
4156
|
+
if (card && words !== null) media2 = stillPicture(true);
|
|
3507
4157
|
body2 = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3508
4158
|
questionLine,
|
|
3509
4159
|
activityLine,
|
|
3510
|
-
card ? consentCard(
|
|
4160
|
+
card && words !== null ? consentCard(words) : null
|
|
3511
4161
|
] });
|
|
3512
|
-
actions2 = card ? consentActions(
|
|
4162
|
+
actions2 = card ? consentActions(
|
|
4163
|
+
true,
|
|
4164
|
+
words === null ? voiceWords?.start ?? S.start : S.startWhenReady
|
|
4165
|
+
) : null;
|
|
3513
4166
|
break;
|
|
3514
4167
|
}
|
|
3515
4168
|
case "text-only":
|
|
@@ -3541,6 +4194,16 @@ function AvatarLiveView({
|
|
|
3541
4194
|
}
|
|
3542
4195
|
break;
|
|
3543
4196
|
case "consent":
|
|
4197
|
+
if (voiceArming) {
|
|
4198
|
+
status = S.armed;
|
|
4199
|
+
body2 = questionLine;
|
|
4200
|
+
break;
|
|
4201
|
+
}
|
|
4202
|
+
if (phase.offer.copy.notice === "none") {
|
|
4203
|
+
body2 = questionLine;
|
|
4204
|
+
actions2 = consentActions(false, voiceWords?.start ?? S.start);
|
|
4205
|
+
break;
|
|
4206
|
+
}
|
|
3544
4207
|
media2 = stillPicture(true);
|
|
3545
4208
|
body2 = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3546
4209
|
questionLine,
|
|
@@ -3632,7 +4295,7 @@ function AvatarLiveView({
|
|
|
3632
4295
|
"aria-disabled": reason !== null ? "true" : void 0,
|
|
3633
4296
|
className: "min-h-11 rounded-full border border-border bg-background px-3 py-2 text-start text-sm text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-disabled:cursor-not-allowed aria-disabled:opacity-50 motion-reduce:transition-none",
|
|
3634
4297
|
onClick: () => {
|
|
3635
|
-
if (reason === null)
|
|
4298
|
+
if (reason === null) void askTyped(prompt);
|
|
3636
4299
|
},
|
|
3637
4300
|
children: prompt
|
|
3638
4301
|
},
|
|
@@ -3660,7 +4323,7 @@ function AvatarLiveView({
|
|
|
3660
4323
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: S.newChatNote })
|
|
3661
4324
|
] }) : null,
|
|
3662
4325
|
chips,
|
|
3663
|
-
box !== null &&
|
|
4326
|
+
box !== null && usedHere && phase.name !== "live" ? /* @__PURE__ */ jsx(
|
|
3664
4327
|
"p",
|
|
3665
4328
|
{
|
|
3666
4329
|
"data-boff-explore": "avatar-live-used-today",
|
|
@@ -3674,13 +4337,16 @@ function AvatarLiveView({
|
|
|
3674
4337
|
{
|
|
3675
4338
|
value: draft,
|
|
3676
4339
|
maxLength: askMaxLength,
|
|
3677
|
-
onChange:
|
|
3678
|
-
onAsk:
|
|
4340
|
+
onChange: onDraftChange,
|
|
4341
|
+
onAsk: askTyped,
|
|
3679
4342
|
reason,
|
|
3680
4343
|
label: S.askLabel,
|
|
3681
|
-
placeholder:
|
|
4344
|
+
placeholder: box === "spoken" && voiceTalk && voiceWords !== null ? (
|
|
4345
|
+
// None while the mic listens: its line says so, and the box is at its narrowest then.
|
|
4346
|
+
mic.busy ? "" : voiceWords.placeholder
|
|
4347
|
+
) : S.askPlaceholder,
|
|
3682
4348
|
send,
|
|
3683
|
-
footer: S.askFooter,
|
|
4349
|
+
footer: noNotice ? null : S.askFooter,
|
|
3684
4350
|
noFiles: S.askNoFiles,
|
|
3685
4351
|
hint: box === "spoken" ? S.needsAnswerAny : null,
|
|
3686
4352
|
touchFirst,
|
|
@@ -3718,7 +4384,14 @@ function AvatarLiveView({
|
|
|
3718
4384
|
actions2
|
|
3719
4385
|
] }),
|
|
3720
4386
|
foot,
|
|
3721
|
-
/* @__PURE__ */ jsx(
|
|
4387
|
+
/* @__PURE__ */ jsx(
|
|
4388
|
+
"audio",
|
|
4389
|
+
{
|
|
4390
|
+
ref: audioRef,
|
|
4391
|
+
"data-boff-explore": "avatar-live-audio",
|
|
4392
|
+
onVolumeChange: holdMute
|
|
4393
|
+
}
|
|
4394
|
+
)
|
|
3722
4395
|
]
|
|
3723
4396
|
}
|
|
3724
4397
|
);
|
|
@@ -3750,6 +4423,7 @@ function AvatarLiveView({
|
|
|
3750
4423
|
actions = startOver ? startOverActions : continueButton;
|
|
3751
4424
|
break;
|
|
3752
4425
|
case "consent":
|
|
4426
|
+
if (phase.offer.copy.notice === "none") break;
|
|
3753
4427
|
media = stillPicture(true);
|
|
3754
4428
|
body = consentCard(phase.offer.copy);
|
|
3755
4429
|
actions = consentActions(false);
|
|
@@ -3842,6 +4516,7 @@ function AskBox({
|
|
|
3842
4516
|
const footerId = `${uid}-footer`;
|
|
3843
4517
|
const [refusedFiles, setRefusedFiles] = useState(false);
|
|
3844
4518
|
const empty = value.trim() === "";
|
|
4519
|
+
const describedBy = [hint === null ? null : hintId, footer === null ? null : footerId].filter((id) => id !== null).join(" ") || void 0;
|
|
3845
4520
|
const submit = () => {
|
|
3846
4521
|
if (reason !== null || empty) return;
|
|
3847
4522
|
onAsk(value);
|
|
@@ -3884,7 +4559,7 @@ function AskBox({
|
|
|
3884
4559
|
rows: 1,
|
|
3885
4560
|
maxLength,
|
|
3886
4561
|
"aria-label": label,
|
|
3887
|
-
"aria-describedby":
|
|
4562
|
+
"aria-describedby": describedBy,
|
|
3888
4563
|
placeholder,
|
|
3889
4564
|
dir: "auto",
|
|
3890
4565
|
"data-avatar-primary": touchFirst ? void 0 : "",
|
|
@@ -3937,7 +4612,7 @@ function AskBox({
|
|
|
3937
4612
|
}
|
|
3938
4613
|
) : null,
|
|
3939
4614
|
hint === null ? null : /* @__PURE__ */ jsx("p", { id: hintId, className: "sr-only", children: hint }),
|
|
3940
|
-
/* @__PURE__ */ jsx("p", { id: footerId, className: "text-xs text-muted-foreground", children: footer })
|
|
4615
|
+
footer === null ? null : /* @__PURE__ */ jsx("p", { id: footerId, className: "text-xs text-muted-foreground", children: footer })
|
|
3941
4616
|
]
|
|
3942
4617
|
}
|
|
3943
4618
|
);
|