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