@burdenoff/website-sdk 2026.1105.2 → 2026.1105.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -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 +269 -78
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +262 -71
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/avatar-mic/index.d.mts +187 -62
- package/dist/avatar-mic/index.d.ts +187 -62
- package/dist/avatar-mic/index.js +457 -5
- package/dist/avatar-mic/index.js.map +1 -1
- package/dist/avatar-mic/index.mjs +457 -6
- package/dist/avatar-mic/index.mjs.map +1 -1
- package/dist/{chunk-XIZE37IB.js → chunk-3AIAJ2L4.js} +2 -2
- package/dist/chunk-3AIAJ2L4.js.map +1 -0
- package/dist/{chunk-MVNN6LLO.mjs → chunk-JGU6JW4N.mjs} +2 -2
- package/dist/chunk-JGU6JW4N.mjs.map +1 -0
- package/dist/{explore-avatar-live-config-auLKxSzx.d.ts → explore-avatar-live-config-1zeXdMer.d.ts} +6 -3
- package/dist/{explore-avatar-live-config-BL8FTm0I.d.mts → explore-avatar-live-config-BxiE66Im.d.mts} +6 -3
- package/dist/index.d.mts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-MVNN6LLO.mjs.map +0 -1
- package/dist/chunk-XIZE37IB.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk3AIAJ2L4_js = require('../chunk-3AIAJ2L4.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');
|
|
@@ -71,6 +71,17 @@ function readSttOffer(value) {
|
|
|
71
71
|
}
|
|
72
72
|
return { token, expiresAt };
|
|
73
73
|
}
|
|
74
|
+
var MIC_MAX_PRESS_SECONDS = 30;
|
|
75
|
+
var UNREADABLE_MIC = { mode: "unreadable" };
|
|
76
|
+
function readMicOffer(value) {
|
|
77
|
+
if (!isRecord(value)) return UNREADABLE_MIC;
|
|
78
|
+
const keys = Object.keys(value).sort();
|
|
79
|
+
const { mode, maxPressSeconds } = value;
|
|
80
|
+
if (keys.length !== 2 || keys[0] !== "maxPressSeconds" || keys[1] !== "mode" || mode !== "browser" || maxPressSeconds !== MIC_MAX_PRESS_SECONDS) {
|
|
81
|
+
return UNREADABLE_MIC;
|
|
82
|
+
}
|
|
83
|
+
return { mode: "browser", maxPressSeconds: MIC_MAX_PRESS_SECONDS };
|
|
84
|
+
}
|
|
74
85
|
function readAvailability(status, body) {
|
|
75
86
|
if (status !== 200 || !isRecord(body)) return INERT;
|
|
76
87
|
const { available, reason } = body;
|
|
@@ -81,10 +92,11 @@ function readAvailability(status, body) {
|
|
|
81
92
|
const { consentVersion, maxSessionSeconds } = body;
|
|
82
93
|
const botCheck = readBotCheck(body.botCheck);
|
|
83
94
|
const questions = readQuestions(body.questions);
|
|
84
|
-
if (available !== true || typeof consentVersion !== "string" || !
|
|
95
|
+
if (available !== true || typeof consentVersion !== "string" || !chunk3AIAJ2L4_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
|
|
85
96
|
return INERT;
|
|
86
97
|
}
|
|
87
98
|
const stt = readSttOffer(body.stt);
|
|
99
|
+
const mic = Object.prototype.hasOwnProperty.call(body, "mic") ? readMicOffer(body.mic) : void 0;
|
|
88
100
|
return {
|
|
89
101
|
kind: "available",
|
|
90
102
|
offer: {
|
|
@@ -92,7 +104,8 @@ function readAvailability(status, body) {
|
|
|
92
104
|
maxSessionSeconds,
|
|
93
105
|
botCheck,
|
|
94
106
|
questions,
|
|
95
|
-
...stt === void 0 ? {} : { stt }
|
|
107
|
+
...stt === void 0 ? {} : { stt },
|
|
108
|
+
...mic === void 0 ? {} : { mic }
|
|
96
109
|
}
|
|
97
110
|
};
|
|
98
111
|
}
|
|
@@ -205,7 +218,7 @@ async function requestAvailability(target) {
|
|
|
205
218
|
}
|
|
206
219
|
}
|
|
207
220
|
async function requestAvailabilityAnswer(target) {
|
|
208
|
-
if (!
|
|
221
|
+
if (!chunk3AIAJ2L4_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
|
|
209
222
|
const query = new URLSearchParams({
|
|
210
223
|
site: target.site,
|
|
211
224
|
locale: target.locale
|
|
@@ -847,6 +860,7 @@ var STRINGS = {
|
|
|
847
860
|
function avatarLiveStrings(locale) {
|
|
848
861
|
return STRINGS[locale] ?? EN;
|
|
849
862
|
}
|
|
863
|
+
var BROWSER_MIC_NOTICE_VERSION = "web-mic-2026-11-21-c";
|
|
850
864
|
var CONSENT = /* @__PURE__ */ new Map([
|
|
851
865
|
[
|
|
852
866
|
"web-consent-2026-11",
|
|
@@ -1024,10 +1038,55 @@ var CONSENT = /* @__PURE__ */ new Map([
|
|
|
1024
1038
|
followUps: true
|
|
1025
1039
|
}
|
|
1026
1040
|
}
|
|
1041
|
+
],
|
|
1042
|
+
[
|
|
1043
|
+
// Stage C (BOFF-9797, the operator's decision of 2026-11-05): the mic through the browser's own speech recognition.
|
|
1044
|
+
// A DRAFT for the operator's privacy review that no environment uses yet (global-speech-svc reserves the same two
|
|
1045
|
+
// versions, and pairs them with its AVATAR_WEB_MIC=browser): web-consent-2026-11-19-d with three changes only. The
|
|
1046
|
+
// intro warns against personal details said into the microphone and says a tap on it stops the answer being read;
|
|
1047
|
+
// the processing says which speech service turns the voice into text (the browser's own, under its own terms, with
|
|
1048
|
+
// the vendors as examples) and that a live session is told of a tap and of a recording, never of the words; the
|
|
1049
|
+
// storage says the camera is not used and that the voice never reaches us, nor the text until Send, in place of
|
|
1050
|
+
// "Your microphone and camera are not used.", which is true only with no mic. The notice files of record and their
|
|
1051
|
+
// digests are a gitops change of their own, made with the review.
|
|
1052
|
+
"web-consent-2026-11-21-c",
|
|
1053
|
+
{
|
|
1054
|
+
en: {
|
|
1055
|
+
title: "Live AI avatar (beta)",
|
|
1056
|
+
introProduct: null,
|
|
1057
|
+
intro: "This is an AI avatar, not a real person. It reads aloud the answers to the questions you ask in the Avatar tab, provided each passes the speech checks. After an answer, it waits about thirty seconds for your next question, then ends. A session lasts at most three minutes; if an answer doesn't fit in the time left, it reads only the first part and says so, or doesn't read it, and the full answer stays in Chat. The answers are AI-generated and can be wrong. Don't put personal details in a question you type. Don't say them into the microphone either. Tapping the microphone while the avatar speaks stops that answer.",
|
|
1058
|
+
processing: "When you start, Cloudflare checks for bots. The answer text goes to Google to create the voice. Our server sends that voice through LiveKit to Protoface, which makes the avatar video streamed through LiveKit. Your browser connects to LiveKit, which sees your IP address. If you use the microphone, the speech service your browser uses turns your voice into text and may receive the audio: for example Google for Chrome on a computer or Android phone, Apple for Safari and on iPhone and iPad, and Microsoft for Edge on a computer, each under its own privacy terms. During a live session, our server is told when you tap the microphone and while you record (never what you say), so the avatar stops and waits.",
|
|
1059
|
+
storage: "Your camera is not used. We never receive your voice: the text appears in the question box for you to check, and none of what you said reaches us until you press Send. We do not record the live session or store the generated voice. We keep a record of this consent. The privacy notice explains our safeguards, storage periods and the providers' processing. For people aged eighteen and over.",
|
|
1060
|
+
privacyLink: "How we handle this",
|
|
1061
|
+
botCheck: "turnstile",
|
|
1062
|
+
questions: "any",
|
|
1063
|
+
followUps: true,
|
|
1064
|
+
micNotice: BROWSER_MIC_NOTICE_VERSION
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
],
|
|
1068
|
+
[
|
|
1069
|
+
// The words of web-consent-2026-11-21-c without "When you start, Cloudflare checks for bots.", for a server that runs
|
|
1070
|
+
// no bot check. A DRAFT, like it.
|
|
1071
|
+
"web-consent-2026-11-22-c",
|
|
1072
|
+
{
|
|
1073
|
+
en: {
|
|
1074
|
+
title: "Live AI avatar (beta)",
|
|
1075
|
+
introProduct: null,
|
|
1076
|
+
intro: "This is an AI avatar, not a real person. It reads aloud the answers to the questions you ask in the Avatar tab, provided each passes the speech checks. After an answer, it waits about thirty seconds for your next question, then ends. A session lasts at most three minutes; if an answer doesn't fit in the time left, it reads only the first part and says so, or doesn't read it, and the full answer stays in Chat. The answers are AI-generated and can be wrong. Don't put personal details in a question you type. Don't say them into the microphone either. Tapping the microphone while the avatar speaks stops that answer.",
|
|
1077
|
+
processing: "The answer text goes to Google to create the voice. Our server sends that voice through LiveKit to Protoface, which makes the avatar video streamed through LiveKit. Your browser connects to LiveKit, which sees your IP address. If you use the microphone, the speech service your browser uses turns your voice into text and may receive the audio: for example Google for Chrome on a computer or Android phone, Apple for Safari and on iPhone and iPad, and Microsoft for Edge on a computer, each under its own privacy terms. During a live session, our server is told when you tap the microphone and while you record (never what you say), so the avatar stops and waits.",
|
|
1078
|
+
storage: "Your camera is not used. We never receive your voice: the text appears in the question box for you to check, and none of what you said reaches us until you press Send. We do not record the live session or store the generated voice. We keep a record of this consent. The privacy notice explains our safeguards, storage periods and the providers' processing. For people aged eighteen and over.",
|
|
1079
|
+
privacyLink: "How we handle this",
|
|
1080
|
+
botCheck: "off",
|
|
1081
|
+
questions: "any",
|
|
1082
|
+
followUps: true,
|
|
1083
|
+
micNotice: BROWSER_MIC_NOTICE_VERSION
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1027
1086
|
]
|
|
1028
1087
|
]);
|
|
1029
1088
|
function avatarConsentCopy(version, locale) {
|
|
1030
|
-
if (!
|
|
1089
|
+
if (!chunk3AIAJ2L4_js.isAvatarLiveLocaleReviewed(locale)) return null;
|
|
1031
1090
|
return CONSENT.get(version)?.[locale] ?? null;
|
|
1032
1091
|
}
|
|
1033
1092
|
var MIC_EN = {
|
|
@@ -1051,7 +1110,7 @@ var MIC_EN = {
|
|
|
1051
1110
|
};
|
|
1052
1111
|
var MIC_STRINGS = { en: MIC_EN };
|
|
1053
1112
|
function avatarMicStrings(locale) {
|
|
1054
|
-
if (!
|
|
1113
|
+
if (!chunk3AIAJ2L4_js.isAvatarLiveLocaleReviewed(locale)) return null;
|
|
1055
1114
|
return MIC_STRINGS[locale] ?? null;
|
|
1056
1115
|
}
|
|
1057
1116
|
var AVATAR_MIC_NOTICE_VERSION = "web-mic-2026-11-05";
|
|
@@ -1068,20 +1127,38 @@ var MIC_NOTICE = /* @__PURE__ */ new Map([
|
|
|
1068
1127
|
privacyLink: "How we handle this"
|
|
1069
1128
|
}
|
|
1070
1129
|
}
|
|
1130
|
+
],
|
|
1131
|
+
[
|
|
1132
|
+
// The browser's own speech recognition (Stage C): the browser's speech service, named by example where each is
|
|
1133
|
+
// true, under its own terms; nothing of the voice reaches us, nor the text before Send; a live session told only of
|
|
1134
|
+
// a tap and of a recording. No daily limits: nothing is counted on the server.
|
|
1135
|
+
BROWSER_MIC_NOTICE_VERSION,
|
|
1136
|
+
{
|
|
1137
|
+
en: {
|
|
1138
|
+
title: "Ask with your voice",
|
|
1139
|
+
processing: "The speech service your browser uses turns your voice into text and may receive the audio: for example Google for Chrome on a computer or Android phone, Apple for Safari and on iPhone and iPad, and Microsoft for Edge on a computer. Its own privacy terms apply.",
|
|
1140
|
+
storage: "We never receive your voice, and your camera is not used.",
|
|
1141
|
+
text: "The text appears in the box for you to check. None of what you said reaches us until you press Send; once you send it, it is a chat message, kept like any other. During a live session, we are told when you tap the microphone and while you record (never what you say), so the avatar stops and waits.",
|
|
1142
|
+
limits: "Each recording stops after thirty seconds at the latest.",
|
|
1143
|
+
privacyLink: "How we handle this"
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1071
1146
|
]
|
|
1072
1147
|
]);
|
|
1073
1148
|
function avatarMicNotice(version, locale) {
|
|
1074
|
-
if (!
|
|
1149
|
+
if (!chunk3AIAJ2L4_js.isAvatarLiveLocaleReviewed(locale)) return null;
|
|
1075
1150
|
return MIC_NOTICE.get(version)?.[locale] ?? null;
|
|
1076
1151
|
}
|
|
1077
1152
|
var PRIVACY_HREF = "/privacy#live-avatar";
|
|
1078
1153
|
var LIMIT_SECONDS = 30;
|
|
1079
1154
|
var TICK_MS = 250;
|
|
1080
1155
|
var IDLE = { name: "idle" };
|
|
1081
|
-
var
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
])
|
|
1156
|
+
var EN_IN = { lang: "en-IN", fallbackLang: "en-US" };
|
|
1157
|
+
var BROWSER_MIC_LANGUAGES = { en: EN_IN };
|
|
1158
|
+
var FOR_GOOD = {
|
|
1159
|
+
stream: /* @__PURE__ */ new Set(["unavailable", "limit"]),
|
|
1160
|
+
browser: /* @__PURE__ */ new Set(["unsupported"])
|
|
1161
|
+
};
|
|
1085
1162
|
var FAILURE_TEXT = {
|
|
1086
1163
|
denied: "micDenied",
|
|
1087
1164
|
unsupported: "micUnsupported",
|
|
@@ -1090,14 +1167,32 @@ var FAILURE_TEXT = {
|
|
|
1090
1167
|
busy: "micBusy",
|
|
1091
1168
|
failed: "micFailed"
|
|
1092
1169
|
};
|
|
1170
|
+
var loadedMic = null;
|
|
1093
1171
|
function loadAvatarMic() {
|
|
1094
|
-
return import('../avatar-mic/index.js').then((loaded) =>
|
|
1095
|
-
|
|
1096
|
-
|
|
1172
|
+
return import('../avatar-mic/index.js').then((loaded) => {
|
|
1173
|
+
const module = {
|
|
1174
|
+
createAvatarMic: loaded.createAvatarMic,
|
|
1175
|
+
createBrowserAvatarMic: loaded.createBrowserAvatarMic
|
|
1176
|
+
};
|
|
1177
|
+
loadedMic = module;
|
|
1178
|
+
return module;
|
|
1179
|
+
});
|
|
1097
1180
|
}
|
|
1098
1181
|
function canUseAvatarMic() {
|
|
1099
1182
|
return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function" && typeof AudioContext !== "undefined" && typeof AudioWorkletNode !== "undefined" && typeof WebSocket !== "undefined";
|
|
1100
1183
|
}
|
|
1184
|
+
function canUseBrowserMic() {
|
|
1185
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
if (window.isSecureContext !== true) return false;
|
|
1189
|
+
const recognizer = Reflect.get(window, "SpeechRecognition") ?? Reflect.get(window, "webkitSpeechRecognition");
|
|
1190
|
+
if (typeof recognizer !== "function") return false;
|
|
1191
|
+
const agent = navigator.userAgent ?? "";
|
|
1192
|
+
if (/\bFirefox\//.test(agent) || /\bFxiOS\//.test(agent)) return false;
|
|
1193
|
+
return !("brave" in navigator);
|
|
1194
|
+
}
|
|
1195
|
+
var REPLAY_PRIVATE = "rr-block rr-mask";
|
|
1101
1196
|
function clock(seconds) {
|
|
1102
1197
|
const whole = Math.max(0, Math.floor(seconds));
|
|
1103
1198
|
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
|
|
@@ -1108,37 +1203,51 @@ function withHeardText(current, heard, maxLength) {
|
|
|
1108
1203
|
const joined = current.trim() === "" ? text : `${current.replace(/\s+$/, "")} ${text}`;
|
|
1109
1204
|
return joined.slice(0, Math.max(0, maxLength));
|
|
1110
1205
|
}
|
|
1206
|
+
function noticeOf(source) {
|
|
1207
|
+
return source.kind === "stream" ? AVATAR_MIC_NOTICE_VERSION : source.notice;
|
|
1208
|
+
}
|
|
1209
|
+
function micKeyOf(source, locale) {
|
|
1210
|
+
return source.kind === "stream" ? `stream ${source.base}` : `browser ${BROWSER_MIC_LANGUAGES[locale]?.lang ?? ""}`;
|
|
1211
|
+
}
|
|
1111
1212
|
function useAvatarMic({
|
|
1112
1213
|
enabled,
|
|
1113
1214
|
locale,
|
|
1114
|
-
|
|
1115
|
-
offer,
|
|
1116
|
-
refreshOffer,
|
|
1215
|
+
source,
|
|
1117
1216
|
onText,
|
|
1118
1217
|
onStart
|
|
1119
1218
|
}) {
|
|
1120
1219
|
const S = avatarMicStrings(locale);
|
|
1121
|
-
const
|
|
1220
|
+
const noticeVersion = source === null ? null : noticeOf(source);
|
|
1221
|
+
const copy = noticeVersion === null ? null : avatarMicNotice(noticeVersion, locale);
|
|
1222
|
+
const kind = source?.kind ?? null;
|
|
1223
|
+
const micKey = source === null ? null : micKeyOf(source, locale);
|
|
1122
1224
|
const uid = react.useId();
|
|
1123
1225
|
const noticeTitleId = `${uid}-mic-notice`;
|
|
1124
|
-
const [withdrawn, setWithdrawn] = react.useState(
|
|
1125
|
-
const [supported, setSupported] = react.useState(false);
|
|
1126
|
-
react.useEffect(
|
|
1127
|
-
|
|
1128
|
-
|
|
1226
|
+
const [withdrawn, setWithdrawn] = react.useState({});
|
|
1227
|
+
const [supported, setSupported] = react.useState({ stream: false, browser: false });
|
|
1228
|
+
react.useEffect(
|
|
1229
|
+
() => setSupported({ stream: canUseAvatarMic(), browser: canUseBrowserMic() }),
|
|
1230
|
+
[]
|
|
1231
|
+
);
|
|
1232
|
+
const usable = source === null ? false : source.kind === "stream" ? supported.stream : supported.browser && BROWSER_MIC_LANGUAGES[locale] !== void 0;
|
|
1233
|
+
const possible = enabled && kind !== null && usable && S !== null && copy !== null;
|
|
1234
|
+
const withdrawnReason = kind === null ? null : withdrawn[kind] ?? null;
|
|
1235
|
+
const offered = possible && withdrawnReason === null;
|
|
1129
1236
|
const [state, setState] = react.useState(IDLE);
|
|
1130
1237
|
const [noticeOpen, setNoticeOpen] = react.useState(false);
|
|
1131
|
-
const agreedRef = react.useRef(
|
|
1238
|
+
const agreedRef = react.useRef(null);
|
|
1132
1239
|
const [loading2, setLoading] = react.useState(false);
|
|
1133
1240
|
const [loadFailed, setLoadFailed] = react.useState(false);
|
|
1134
1241
|
const [now, setNow] = react.useState(() => Date.now());
|
|
1135
|
-
const micRef = react.useRef(
|
|
1242
|
+
const micRef = react.useRef(
|
|
1243
|
+
null
|
|
1244
|
+
);
|
|
1136
1245
|
const seqRef = react.useRef(0);
|
|
1137
1246
|
const buttonRef = react.useRef(null);
|
|
1138
1247
|
const allowRef = react.useRef(null);
|
|
1139
|
-
const
|
|
1140
|
-
const latest = react.useRef({
|
|
1141
|
-
latest.current = {
|
|
1248
|
+
const usedRef = react.useRef(null);
|
|
1249
|
+
const latest = react.useRef({ source, locale, noticeVersion, onText, onStart });
|
|
1250
|
+
latest.current = { source, locale, noticeVersion, onText, onStart };
|
|
1142
1251
|
const cancel = react.useCallback(() => {
|
|
1143
1252
|
seqRef.current += 1;
|
|
1144
1253
|
setLoading(false);
|
|
@@ -1149,6 +1258,12 @@ function useAvatarMic({
|
|
|
1149
1258
|
react.useEffect(() => {
|
|
1150
1259
|
if (!offered) cancel();
|
|
1151
1260
|
}, [offered, cancel]);
|
|
1261
|
+
const keyRef = react.useRef(micKey);
|
|
1262
|
+
react.useEffect(() => {
|
|
1263
|
+
if (keyRef.current === micKey) return;
|
|
1264
|
+
keyRef.current = micKey;
|
|
1265
|
+
cancel();
|
|
1266
|
+
}, [micKey, cancel]);
|
|
1152
1267
|
react.useEffect(
|
|
1153
1268
|
() => () => {
|
|
1154
1269
|
seqRef.current += 1;
|
|
@@ -1156,29 +1271,55 @@ function useAvatarMic({
|
|
|
1156
1271
|
},
|
|
1157
1272
|
[]
|
|
1158
1273
|
);
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1274
|
+
const micOf = react.useCallback(
|
|
1275
|
+
(loaded, wanted) => {
|
|
1276
|
+
const key = micKeyOf(wanted, latest.current.locale);
|
|
1277
|
+
const held = micRef.current;
|
|
1278
|
+
if (held !== null && held.key === key) return held.mic;
|
|
1279
|
+
held?.mic.cancel();
|
|
1280
|
+
let mic;
|
|
1281
|
+
if (wanted.kind === "stream") {
|
|
1282
|
+
const stream = () => {
|
|
1283
|
+
const current = latest.current.source;
|
|
1284
|
+
return current?.kind === "stream" ? current : null;
|
|
1285
|
+
};
|
|
1286
|
+
mic = loaded.createAvatarMic({
|
|
1287
|
+
base: wanted.base,
|
|
1288
|
+
notice: AVATAR_MIC_NOTICE_VERSION,
|
|
1289
|
+
offer: () => stream()?.offer ?? null,
|
|
1290
|
+
refreshOffer: () => stream()?.refreshOffer() ?? Promise.resolve(null),
|
|
1291
|
+
onState: setState
|
|
1292
|
+
});
|
|
1293
|
+
} else {
|
|
1294
|
+
const language = BROWSER_MIC_LANGUAGES[latest.current.locale] ?? EN_IN;
|
|
1295
|
+
mic = loaded.createBrowserAvatarMic({
|
|
1296
|
+
lang: language.lang,
|
|
1297
|
+
fallbackLang: language.fallbackLang,
|
|
1298
|
+
maxPressMs: LIMIT_SECONDS * 1e3,
|
|
1299
|
+
onState: setState
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
micRef.current = { key, kind: wanted.kind, mic };
|
|
1303
|
+
return mic;
|
|
1304
|
+
},
|
|
1305
|
+
[]
|
|
1306
|
+
);
|
|
1174
1307
|
const press = react.useCallback(() => {
|
|
1175
|
-
const
|
|
1176
|
-
if (
|
|
1308
|
+
const wanted = latest.current.source;
|
|
1309
|
+
if (wanted === null) return;
|
|
1177
1310
|
seqRef.current += 1;
|
|
1178
1311
|
const seq = seqRef.current;
|
|
1179
1312
|
setLoadFailed(false);
|
|
1313
|
+
const held = micRef.current;
|
|
1314
|
+
const ready = wanted.kind !== "browser" ? null : held !== null && held.key === micKeyOf(wanted, latest.current.locale) ? held.mic : loadedMic !== null ? micOf(loadedMic, wanted) : null;
|
|
1315
|
+
if (ready !== null) {
|
|
1316
|
+
setLoading(false);
|
|
1317
|
+
ready.start();
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1180
1320
|
setLoading(true);
|
|
1181
|
-
|
|
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(
|
|
1182
1323
|
(mic) => {
|
|
1183
1324
|
if (seqRef.current !== seq) return;
|
|
1184
1325
|
setLoading(false);
|
|
@@ -1190,8 +1331,9 @@ function useAvatarMic({
|
|
|
1190
1331
|
setLoadFailed(true);
|
|
1191
1332
|
}
|
|
1192
1333
|
);
|
|
1193
|
-
}, [
|
|
1334
|
+
}, [micOf]);
|
|
1194
1335
|
const recording = loading2 || state.name === "starting" || state.name === "listening";
|
|
1336
|
+
const finishing = kind === "browser" && state.name === "finishing";
|
|
1195
1337
|
const tap = react.useCallback(() => {
|
|
1196
1338
|
if (state.name === "listening" || state.name === "starting") {
|
|
1197
1339
|
micRef.current?.mic.stop();
|
|
@@ -1201,16 +1343,26 @@ function useAvatarMic({
|
|
|
1201
1343
|
cancel();
|
|
1202
1344
|
return;
|
|
1203
1345
|
}
|
|
1346
|
+
if (finishing) return;
|
|
1204
1347
|
latest.current.onStart?.();
|
|
1205
|
-
if (
|
|
1348
|
+
if (agreedRef.current !== latest.current.noticeVersion) {
|
|
1206
1349
|
setNoticeOpen(true);
|
|
1207
|
-
|
|
1350
|
+
const seq = seqRef.current;
|
|
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
|
+
);
|
|
1208
1360
|
return;
|
|
1209
1361
|
}
|
|
1210
1362
|
press();
|
|
1211
|
-
}, [cancel, loading2, press, state.name]);
|
|
1363
|
+
}, [cancel, finishing, loading2, micOf, press, state.name]);
|
|
1212
1364
|
const allow = react.useCallback(() => {
|
|
1213
|
-
agreedRef.current =
|
|
1365
|
+
agreedRef.current = latest.current.noticeVersion;
|
|
1214
1366
|
setNoticeOpen(false);
|
|
1215
1367
|
buttonRef.current?.focus();
|
|
1216
1368
|
press();
|
|
@@ -1230,11 +1382,15 @@ function useAvatarMic({
|
|
|
1230
1382
|
return () => clearInterval(timer);
|
|
1231
1383
|
}, [listeningSince]);
|
|
1232
1384
|
react.useEffect(() => {
|
|
1233
|
-
if (state.name === "done" && state
|
|
1234
|
-
|
|
1385
|
+
if (state.name === "done" && state !== usedRef.current) {
|
|
1386
|
+
usedRef.current = state;
|
|
1235
1387
|
if (state.text !== "") latest.current.onText(state.text);
|
|
1236
|
-
|
|
1237
|
-
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const pressed = micRef.current?.kind;
|
|
1391
|
+
if (state.name === "failed" && pressed !== void 0 && FOR_GOOD[pressed].has(state.reason)) {
|
|
1392
|
+
const reason = state.reason;
|
|
1393
|
+
setWithdrawn((held) => ({ ...held, [pressed]: reason }));
|
|
1238
1394
|
}
|
|
1239
1395
|
}, [state]);
|
|
1240
1396
|
if (!possible || S === null || copy === null) {
|
|
@@ -1246,7 +1402,7 @@ function useAvatarMic({
|
|
|
1246
1402
|
recording: false
|
|
1247
1403
|
};
|
|
1248
1404
|
}
|
|
1249
|
-
const status =
|
|
1405
|
+
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]] : "";
|
|
1250
1406
|
const heard = offered && (state.name === "listening" || state.name === "finishing") ? state.text : "";
|
|
1251
1407
|
const button = !offered ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1252
1408
|
"button",
|
|
@@ -1255,12 +1411,15 @@ function useAvatarMic({
|
|
|
1255
1411
|
type: "button",
|
|
1256
1412
|
"aria-label": recording ? S.micStop : S.micStart,
|
|
1257
1413
|
"aria-pressed": recording,
|
|
1414
|
+
"aria-disabled": finishing ? true : void 0,
|
|
1258
1415
|
title: recording ? S.micStop : S.micStart,
|
|
1259
1416
|
"data-boff-explore": "avatar-live-mic",
|
|
1260
1417
|
"data-mic": loading2 ? "starting" : state.name,
|
|
1418
|
+
"data-mic-source": kind ?? void 0,
|
|
1261
1419
|
className: chunkC6OJIHNO_js.cn(
|
|
1262
1420
|
"inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-md border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-reduce:transition-none",
|
|
1263
|
-
recording ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1421
|
+
recording ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
1422
|
+
finishing && "cursor-wait opacity-50"
|
|
1264
1423
|
),
|
|
1265
1424
|
onClick: tap,
|
|
1266
1425
|
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" })
|
|
@@ -1343,7 +1502,11 @@ function useAvatarMic({
|
|
|
1343
1502
|
{
|
|
1344
1503
|
"data-boff-explore": "avatar-live-mic-heard",
|
|
1345
1504
|
dir: "auto",
|
|
1346
|
-
className:
|
|
1505
|
+
className: chunkC6OJIHNO_js.cn(
|
|
1506
|
+
// rrweb's default block and mask classes: a site's session replay records none of what is heard.
|
|
1507
|
+
REPLAY_PRIVATE,
|
|
1508
|
+
"line-clamp-3 break-words text-sm italic text-muted-foreground"
|
|
1509
|
+
),
|
|
1347
1510
|
children: heard
|
|
1348
1511
|
}
|
|
1349
1512
|
)
|
|
@@ -1480,6 +1643,7 @@ var PLAYOUT_TAIL_MS = 5e3;
|
|
|
1480
1643
|
var QUIET_PLAYED_OUT_MS = 2500;
|
|
1481
1644
|
var OWED_VOICE_WAIT_MS = 15e3;
|
|
1482
1645
|
var LISTENING_MARK_MS = 1e4;
|
|
1646
|
+
var LISTENING_MARK_SPACING_MS = 5e3;
|
|
1483
1647
|
var BUSY_RETRY_MS = 2e3;
|
|
1484
1648
|
var BUSY_RETRY_MAX_MS = 8e3;
|
|
1485
1649
|
var MAX_BUSY_RETRIES = 24;
|
|
@@ -1605,8 +1769,15 @@ function acceptsQuestion(slot) {
|
|
|
1605
1769
|
function followUpBox(phase, run, token) {
|
|
1606
1770
|
return phase.name === "live" && run !== null && phase.session.followUp !== null && !phase.session.followUp.closed && token === run.token;
|
|
1607
1771
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1772
|
+
var NO_MIC = { kind: "none" };
|
|
1773
|
+
function micChoiceOf(result, locale) {
|
|
1774
|
+
if (result.kind !== "available") return NO_MIC;
|
|
1775
|
+
const { offer } = result;
|
|
1776
|
+
const micNotice = avatarConsentCopy(offer.consentVersion, locale)?.micNotice;
|
|
1777
|
+
if (offer.mic !== void 0) {
|
|
1778
|
+
return offer.mic.mode === "browser" && micNotice !== void 0 ? { kind: "browser", notice: micNotice } : NO_MIC;
|
|
1779
|
+
}
|
|
1780
|
+
return offer.stt !== void 0 && micNotice === void 0 ? { kind: "stream", offer: { ...offer.stt, receivedAt: Date.now() } } : NO_MIC;
|
|
1610
1781
|
}
|
|
1611
1782
|
function utcToday() {
|
|
1612
1783
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -1756,7 +1927,7 @@ function AvatarLiveView({
|
|
|
1756
1927
|
const waitRef = react.useRef(null);
|
|
1757
1928
|
const sendingRef = react.useRef(false);
|
|
1758
1929
|
const textBoxRef = react.useRef(false);
|
|
1759
|
-
const [
|
|
1930
|
+
const [micChoice, setMicChoice] = react.useState(NO_MIC);
|
|
1760
1931
|
const micCancelRef = react.useRef(() => void 0);
|
|
1761
1932
|
const sendDueRef = react.useRef(() => void 0);
|
|
1762
1933
|
const checkRef = react.useRef(null);
|
|
@@ -2060,7 +2231,7 @@ function AvatarLiveView({
|
|
|
2060
2231
|
const applyAvailability = react.useCallback(
|
|
2061
2232
|
(result, locale2) => {
|
|
2062
2233
|
const { live: live2 } = latest.current;
|
|
2063
|
-
|
|
2234
|
+
setMicChoice(micChoiceOf(result, locale2));
|
|
2064
2235
|
if (result.kind === "inert") {
|
|
2065
2236
|
setMode("suggested");
|
|
2066
2237
|
go(INERT2);
|
|
@@ -2078,7 +2249,7 @@ function AvatarLiveView({
|
|
|
2078
2249
|
go(INERT2);
|
|
2079
2250
|
return;
|
|
2080
2251
|
}
|
|
2081
|
-
if (result.offer.botCheck === "turnstile" &&
|
|
2252
|
+
if (result.offer.botCheck === "turnstile" && chunk3AIAJ2L4_js.turnstileSiteKeyOf(live2) === null) {
|
|
2082
2253
|
setMode("suggested");
|
|
2083
2254
|
go(INERT2);
|
|
2084
2255
|
return;
|
|
@@ -2132,7 +2303,8 @@ function AvatarLiveView({
|
|
|
2132
2303
|
due: null,
|
|
2133
2304
|
retry: null,
|
|
2134
2305
|
sent: /* @__PURE__ */ new Set([context.messageId]),
|
|
2135
|
-
seq: 0
|
|
2306
|
+
seq: 0,
|
|
2307
|
+
listenedAt: null
|
|
2136
2308
|
} : null;
|
|
2137
2309
|
sessionRef.current = {
|
|
2138
2310
|
session,
|
|
@@ -2238,7 +2410,7 @@ function AvatarLiveView({
|
|
|
2238
2410
|
return;
|
|
2239
2411
|
}
|
|
2240
2412
|
const refusedFrom = exchangeAtStart.offerUntil - ANSWER_OFFER_MS + SERVICE_MAX_AGE_MS;
|
|
2241
|
-
const siteKey = offer.botCheck === "turnstile" ?
|
|
2413
|
+
const siteKey = offer.botCheck === "turnstile" ? chunk3AIAJ2L4_js.turnstileSiteKeyOf(live2) : null;
|
|
2242
2414
|
if (offer.botCheck === "turnstile" && (siteKey === null || !canBindBotCheck())) {
|
|
2243
2415
|
go(INERT2);
|
|
2244
2416
|
return;
|
|
@@ -2767,16 +2939,18 @@ function AvatarLiveView({
|
|
|
2767
2939
|
const refreshMicOffer = react.useCallback(async () => {
|
|
2768
2940
|
const { live: live2, locale: locale2, base: base2, headers: headers2 } = latest.current;
|
|
2769
2941
|
if (base2 === null) return null;
|
|
2770
|
-
const next =
|
|
2942
|
+
const next = micChoiceOf(
|
|
2771
2943
|
await requestAvailabilityAnswer({
|
|
2772
2944
|
base: base2,
|
|
2773
2945
|
headers: headers2,
|
|
2774
2946
|
site: live2.site,
|
|
2775
2947
|
locale: locale2
|
|
2776
|
-
})
|
|
2948
|
+
}),
|
|
2949
|
+
locale2
|
|
2777
2950
|
);
|
|
2778
|
-
if (next !==
|
|
2779
|
-
|
|
2951
|
+
if (next.kind !== "stream") return null;
|
|
2952
|
+
setMicChoice(next);
|
|
2953
|
+
return next.offer;
|
|
2780
2954
|
}, []);
|
|
2781
2955
|
const onMicText = react.useCallback(
|
|
2782
2956
|
(text) => {
|
|
@@ -2802,8 +2976,14 @@ function AvatarLiveView({
|
|
|
2802
2976
|
handle.playedOut = true;
|
|
2803
2977
|
showFollowUp(handle, { speaking: false });
|
|
2804
2978
|
}, [showFollowUp, tell]);
|
|
2979
|
+
const micSource = micChoice.kind === "browser" ? { kind: "browser", notice: micChoice.notice } : micChoice.kind === "stream" && base !== null ? {
|
|
2980
|
+
kind: "stream",
|
|
2981
|
+
base,
|
|
2982
|
+
offer: micChoice.offer,
|
|
2983
|
+
refreshOffer: refreshMicOffer
|
|
2984
|
+
} : null;
|
|
2805
2985
|
const mic = useAvatarMic({
|
|
2806
|
-
enabled: live.mic === true &&
|
|
2986
|
+
enabled: live.mic === true && micSource !== null && !off && acceptsQuestion({
|
|
2807
2987
|
any,
|
|
2808
2988
|
phase: phase.name,
|
|
2809
2989
|
kind,
|
|
@@ -2812,9 +2992,7 @@ function AvatarLiveView({
|
|
|
2812
2992
|
followUp: followUpAsks
|
|
2813
2993
|
}),
|
|
2814
2994
|
locale,
|
|
2815
|
-
|
|
2816
|
-
offer: micOffer,
|
|
2817
|
-
refreshOffer: refreshMicOffer,
|
|
2995
|
+
source: micSource,
|
|
2818
2996
|
onText: onMicText,
|
|
2819
2997
|
onStart: onMicStart
|
|
2820
2998
|
});
|
|
@@ -2825,15 +3003,23 @@ function AvatarLiveView({
|
|
|
2825
3003
|
if (!micRecording || handle === null || handle.followUps === null || handle.done) {
|
|
2826
3004
|
return;
|
|
2827
3005
|
}
|
|
2828
|
-
|
|
3006
|
+
const run = handle.followUps;
|
|
2829
3007
|
const every = Math.min(
|
|
2830
3008
|
LISTENING_MARK_MS,
|
|
2831
|
-
Math.max(1e3,
|
|
3009
|
+
Math.max(1e3, run.limits.idleSeconds * 1e3 / 3)
|
|
2832
3010
|
);
|
|
2833
|
-
const
|
|
3011
|
+
const spacing = Math.min(LISTENING_MARK_SPACING_MS, every / 2);
|
|
3012
|
+
const mark = () => {
|
|
3013
|
+
const at = Date.now();
|
|
3014
|
+
if (run.listenedAt !== null && at - run.listenedAt < spacing) return;
|
|
3015
|
+
run.listenedAt = at;
|
|
3016
|
+
tell(handle, "listening");
|
|
3017
|
+
};
|
|
3018
|
+
mark();
|
|
3019
|
+
const timer = setInterval(mark, every);
|
|
2834
3020
|
return () => {
|
|
2835
3021
|
clearInterval(timer);
|
|
2836
|
-
if (sessionRef.current === handle)
|
|
3022
|
+
if (sessionRef.current === handle) mark();
|
|
2837
3023
|
};
|
|
2838
3024
|
}, [micRecording, tell]);
|
|
2839
3025
|
const notNow = react.useCallback(() => {
|
|
@@ -3705,7 +3891,12 @@ function AskBox({
|
|
|
3705
3891
|
dir: "auto",
|
|
3706
3892
|
"data-avatar-primary": touchFirst ? void 0 : "",
|
|
3707
3893
|
"data-boff-explore": "avatar-live-ask-input",
|
|
3708
|
-
className:
|
|
3894
|
+
className: chunkC6OJIHNO_js.cn(
|
|
3895
|
+
// Kept out of a site's session replay before Send (rrweb's default classes; `rr-ignore` for its input
|
|
3896
|
+
// events): the text may be what the mic heard, or what the visitor typed.
|
|
3897
|
+
REPLAY_PRIVATE,
|
|
3898
|
+
"rr-ignore max-h-32 min-h-11 flex-1 resize-none rounded-md border border-border bg-background px-3 py-2.5 text-base text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:text-sm"
|
|
3899
|
+
),
|
|
3709
3900
|
onChange: (event) => {
|
|
3710
3901
|
setRefusedFiles(false);
|
|
3711
3902
|
onChange(event.target.value);
|