@burdenoff/website-sdk 2026.1104.4 → 2026.1105.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 +27 -2
- 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 +415 -22
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +410 -17
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/avatar-mic/index.d.mts +193 -0
- package/dist/avatar-mic/index.d.ts +193 -0
- package/dist/avatar-mic/index.js +738 -0
- package/dist/avatar-mic/index.js.map +1 -0
- package/dist/avatar-mic/index.mjs +736 -0
- package/dist/avatar-mic/index.mjs.map +1 -0
- package/dist/{chunk-7MSB3EUI.mjs → chunk-MVNN6LLO.mjs} +4 -3
- package/dist/chunk-MVNN6LLO.mjs.map +1 -0
- package/dist/{chunk-WSEQ2I6I.js → chunk-XIZE37IB.js} +4 -3
- package/dist/chunk-XIZE37IB.js.map +1 -0
- package/dist/{explore-avatar-live-config-DRpS-mqQ.d.ts → explore-avatar-live-config-C-OwXNje.d.ts} +8 -0
- package/dist/{explore-avatar-live-config-B6_p0q1c.d.mts → explore-avatar-live-config-OO0hvQCK.d.mts} +8 -0
- package/dist/index.d.mts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-7MSB3EUI.mjs.map +0 -1
- package/dist/chunk-WSEQ2I6I.js.map +0 -1
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkXIZE37IB_js = require('../chunk-XIZE37IB.js');
|
|
4
4
|
var chunkN7E6Z2GF_js = require('../chunk-N7E6Z2GF.js');
|
|
5
5
|
var chunkC6OJIHNO_js = require('../chunk-C6OJIHNO.js');
|
|
6
6
|
require('../chunk-ZSTFX7FV.js');
|
|
7
7
|
var react = require('react');
|
|
8
|
+
var lucideReact = require('lucide-react');
|
|
8
9
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
10
|
|
|
10
11
|
// src/components/explore-avatar-api.ts
|
|
11
12
|
var AVAILABILITY_TIMEOUT_MS = 8e3;
|
|
12
13
|
var CREATE_TIMEOUT_MS = 3e4;
|
|
13
14
|
var MESSAGE_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
15
|
+
var STT_TOKEN = /^[A-Za-z0-9._~-]{1,4096}$/;
|
|
14
16
|
var CONVERSATION_TOKEN = /^[A-Za-z0-9._~+/=-]{16,512}$/;
|
|
15
17
|
var LOCALE = /^[A-Za-z0-9-]{1,16}$/;
|
|
16
18
|
var SESSION_ID = /^avs_[0-9a-f]{32}$/;
|
|
@@ -58,6 +60,15 @@ function readQuestions(value) {
|
|
|
58
60
|
if (typeof value !== "string" || !QUESTION_MODES.has(value)) return null;
|
|
59
61
|
return value === "any" ? "any" : "suggested";
|
|
60
62
|
}
|
|
63
|
+
function readSttOffer(value) {
|
|
64
|
+
if (!isRecord(value)) return void 0;
|
|
65
|
+
const keys = Object.keys(value).sort();
|
|
66
|
+
const { token, expiresAt } = value;
|
|
67
|
+
if (keys.length !== 2 || keys[0] !== "expiresAt" || keys[1] !== "token" || typeof token !== "string" || !STT_TOKEN.test(token) || typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt) || expiresAt <= 0) {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
return { token, expiresAt };
|
|
71
|
+
}
|
|
61
72
|
function readAvailability(status, body) {
|
|
62
73
|
if (status !== 200 || !isRecord(body)) return INERT;
|
|
63
74
|
const { available, reason } = body;
|
|
@@ -68,12 +79,19 @@ function readAvailability(status, body) {
|
|
|
68
79
|
const { consentVersion, maxSessionSeconds } = body;
|
|
69
80
|
const botCheck = readBotCheck(body.botCheck);
|
|
70
81
|
const questions = readQuestions(body.questions);
|
|
71
|
-
if (available !== true || typeof consentVersion !== "string" || !
|
|
82
|
+
if (available !== true || typeof consentVersion !== "string" || !chunkXIZE37IB_js.isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
|
|
72
83
|
return INERT;
|
|
73
84
|
}
|
|
85
|
+
const stt = readSttOffer(body.stt);
|
|
74
86
|
return {
|
|
75
87
|
kind: "available",
|
|
76
|
-
offer: {
|
|
88
|
+
offer: {
|
|
89
|
+
consentVersion,
|
|
90
|
+
maxSessionSeconds,
|
|
91
|
+
botCheck,
|
|
92
|
+
questions,
|
|
93
|
+
...stt === void 0 ? {} : { stt }
|
|
94
|
+
}
|
|
77
95
|
};
|
|
78
96
|
}
|
|
79
97
|
const meaning = available ? void 0 : AVAILABILITY_REASONS.get(reason);
|
|
@@ -161,7 +179,14 @@ function deadline(timeoutMs, outer) {
|
|
|
161
179
|
};
|
|
162
180
|
}
|
|
163
181
|
async function requestAvailability(target) {
|
|
164
|
-
|
|
182
|
+
try {
|
|
183
|
+
return await requestAvailabilityAnswer(target);
|
|
184
|
+
} catch {
|
|
185
|
+
return INERT;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function requestAvailabilityAnswer(target) {
|
|
189
|
+
if (!chunkXIZE37IB_js.isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
|
|
165
190
|
const query = new URLSearchParams({
|
|
166
191
|
site: target.site,
|
|
167
192
|
locale: target.locale
|
|
@@ -176,8 +201,6 @@ async function requestAvailability(target) {
|
|
|
176
201
|
signal: timer.signal
|
|
177
202
|
});
|
|
178
203
|
return readAvailability(response.status, await readJson(response));
|
|
179
|
-
} catch {
|
|
180
|
-
return INERT;
|
|
181
204
|
} finally {
|
|
182
205
|
timer.done();
|
|
183
206
|
}
|
|
@@ -803,9 +826,321 @@ var CONSENT = /* @__PURE__ */ new Map([
|
|
|
803
826
|
]
|
|
804
827
|
]);
|
|
805
828
|
function avatarConsentCopy(version, locale) {
|
|
806
|
-
if (!
|
|
829
|
+
if (!chunkXIZE37IB_js.isAvatarLiveLocaleReviewed(locale)) return null;
|
|
807
830
|
return CONSENT.get(version)?.[locale] ?? null;
|
|
808
831
|
}
|
|
832
|
+
var MIC_EN = {
|
|
833
|
+
micStart: "Ask by voice",
|
|
834
|
+
micStop: "Stop recording",
|
|
835
|
+
micAllow: "Use the microphone",
|
|
836
|
+
micNotNow: "Not now",
|
|
837
|
+
micStarting: "Starting the microphone\u2026",
|
|
838
|
+
micListening: "Listening\u2026",
|
|
839
|
+
micElapsed: "{elapsed} of {limit}",
|
|
840
|
+
micFinishing: "Getting the text\u2026",
|
|
841
|
+
micDone: "Check the text, then press Send.",
|
|
842
|
+
micDoneLimit: "The recording stopped at thirty seconds. Check the text, then press Send.",
|
|
843
|
+
micNoSpeech: "No words were heard. Try again, or type your question.",
|
|
844
|
+
micDenied: "The microphone is blocked in this browser. You can type your question instead.",
|
|
845
|
+
micUnsupported: "The microphone can't be used here. You can type your question instead.",
|
|
846
|
+
micUnavailable: "Voice questions aren't available right now. You can type your question instead.",
|
|
847
|
+
micLimit: "Voice questions have reached their limit for now. You can type your question instead.",
|
|
848
|
+
micBusy: "Voice questions are busy right now. Try again in a moment, or type your question.",
|
|
849
|
+
micFailed: "The microphone stopped before the text came. Try again, or type your question."
|
|
850
|
+
};
|
|
851
|
+
var MIC_STRINGS = { en: MIC_EN };
|
|
852
|
+
function avatarMicStrings(locale) {
|
|
853
|
+
if (!chunkXIZE37IB_js.isAvatarLiveLocaleReviewed(locale)) return null;
|
|
854
|
+
return MIC_STRINGS[locale] ?? null;
|
|
855
|
+
}
|
|
856
|
+
var AVATAR_MIC_NOTICE_VERSION = "web-mic-2026-11-05";
|
|
857
|
+
var MIC_NOTICE = /* @__PURE__ */ new Map([
|
|
858
|
+
[
|
|
859
|
+
AVATAR_MIC_NOTICE_VERSION,
|
|
860
|
+
{
|
|
861
|
+
en: {
|
|
862
|
+
title: "Ask with your voice",
|
|
863
|
+
processing: "While you speak, your voice is streamed to our speech service, and Google Cloud Speech-to-Text turns it into text in Google's EU region.",
|
|
864
|
+
storage: "We don't store or log your audio, or the text made from it.",
|
|
865
|
+
text: "The text appears in the box and stays in this browser until you press Send. Nothing is sent by itself. Once you send it, it is a chat message, kept like any other.",
|
|
866
|
+
limits: "Each recording stops after thirty seconds, and voice questions have daily limits.",
|
|
867
|
+
privacyLink: "How we handle this"
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
]
|
|
871
|
+
]);
|
|
872
|
+
function avatarMicNotice(version, locale) {
|
|
873
|
+
if (!chunkXIZE37IB_js.isAvatarLiveLocaleReviewed(locale)) return null;
|
|
874
|
+
return MIC_NOTICE.get(version)?.[locale] ?? null;
|
|
875
|
+
}
|
|
876
|
+
var PRIVACY_HREF = "/privacy#live-avatar";
|
|
877
|
+
var LIMIT_SECONDS = 30;
|
|
878
|
+
var TICK_MS = 250;
|
|
879
|
+
var IDLE = { name: "idle" };
|
|
880
|
+
var FOR_GOOD = /* @__PURE__ */ new Set([
|
|
881
|
+
"unavailable",
|
|
882
|
+
"limit"
|
|
883
|
+
]);
|
|
884
|
+
var FAILURE_TEXT = {
|
|
885
|
+
denied: "micDenied",
|
|
886
|
+
unsupported: "micUnsupported",
|
|
887
|
+
unavailable: "micUnavailable",
|
|
888
|
+
limit: "micLimit",
|
|
889
|
+
busy: "micBusy",
|
|
890
|
+
failed: "micFailed"
|
|
891
|
+
};
|
|
892
|
+
function loadAvatarMic() {
|
|
893
|
+
return import('../avatar-mic/index.js').then((loaded) => ({
|
|
894
|
+
createAvatarMic: loaded.createAvatarMic
|
|
895
|
+
}));
|
|
896
|
+
}
|
|
897
|
+
function canUseAvatarMic() {
|
|
898
|
+
return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function" && typeof AudioContext !== "undefined" && typeof AudioWorkletNode !== "undefined" && typeof WebSocket !== "undefined";
|
|
899
|
+
}
|
|
900
|
+
function clock(seconds) {
|
|
901
|
+
const whole = Math.max(0, Math.floor(seconds));
|
|
902
|
+
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
|
|
903
|
+
}
|
|
904
|
+
function withHeardText(current, heard, maxLength) {
|
|
905
|
+
const text = heard.trim();
|
|
906
|
+
if (text === "") return current;
|
|
907
|
+
const joined = current.trim() === "" ? text : `${current.replace(/\s+$/, "")} ${text}`;
|
|
908
|
+
return joined.slice(0, Math.max(0, maxLength));
|
|
909
|
+
}
|
|
910
|
+
function useAvatarMic({
|
|
911
|
+
enabled,
|
|
912
|
+
locale,
|
|
913
|
+
base,
|
|
914
|
+
offer,
|
|
915
|
+
refreshOffer,
|
|
916
|
+
onText
|
|
917
|
+
}) {
|
|
918
|
+
const S = avatarMicStrings(locale);
|
|
919
|
+
const copy = avatarMicNotice(AVATAR_MIC_NOTICE_VERSION, locale);
|
|
920
|
+
const uid = react.useId();
|
|
921
|
+
const noticeTitleId = `${uid}-mic-notice`;
|
|
922
|
+
const [withdrawn, setWithdrawn] = react.useState(null);
|
|
923
|
+
const [supported, setSupported] = react.useState(false);
|
|
924
|
+
react.useEffect(() => setSupported(canUseAvatarMic()), []);
|
|
925
|
+
const possible = enabled && supported && base !== null && S !== null && copy !== null;
|
|
926
|
+
const offered = possible && withdrawn === null;
|
|
927
|
+
const [state, setState] = react.useState(IDLE);
|
|
928
|
+
const [noticeOpen, setNoticeOpen] = react.useState(false);
|
|
929
|
+
const agreedRef = react.useRef(false);
|
|
930
|
+
const [loading2, setLoading] = react.useState(false);
|
|
931
|
+
const [loadFailed, setLoadFailed] = react.useState(false);
|
|
932
|
+
const [now, setNow] = react.useState(() => Date.now());
|
|
933
|
+
const micRef = react.useRef(null);
|
|
934
|
+
const seqRef = react.useRef(0);
|
|
935
|
+
const buttonRef = react.useRef(null);
|
|
936
|
+
const allowRef = react.useRef(null);
|
|
937
|
+
const usedEpochRef = react.useRef(0);
|
|
938
|
+
const latest = react.useRef({ offer, refreshOffer, onText, base });
|
|
939
|
+
latest.current = { offer, refreshOffer, onText, base };
|
|
940
|
+
const cancel = react.useCallback(() => {
|
|
941
|
+
seqRef.current += 1;
|
|
942
|
+
setLoading(false);
|
|
943
|
+
setLoadFailed(false);
|
|
944
|
+
setNoticeOpen(false);
|
|
945
|
+
micRef.current?.mic.cancel();
|
|
946
|
+
}, []);
|
|
947
|
+
react.useEffect(() => {
|
|
948
|
+
if (!offered) cancel();
|
|
949
|
+
}, [offered, cancel]);
|
|
950
|
+
react.useEffect(
|
|
951
|
+
() => () => {
|
|
952
|
+
seqRef.current += 1;
|
|
953
|
+
micRef.current?.mic.cancel();
|
|
954
|
+
},
|
|
955
|
+
[]
|
|
956
|
+
);
|
|
957
|
+
const micFor = react.useCallback(async (target) => {
|
|
958
|
+
const held = micRef.current;
|
|
959
|
+
if (held !== null && held.base === target) return held.mic;
|
|
960
|
+
held?.mic.cancel();
|
|
961
|
+
const loaded = await loadAvatarMic();
|
|
962
|
+
const mic = loaded.createAvatarMic({
|
|
963
|
+
base: target,
|
|
964
|
+
notice: AVATAR_MIC_NOTICE_VERSION,
|
|
965
|
+
offer: () => latest.current.offer,
|
|
966
|
+
refreshOffer: () => latest.current.refreshOffer(),
|
|
967
|
+
onState: setState
|
|
968
|
+
});
|
|
969
|
+
micRef.current = { base: target, mic };
|
|
970
|
+
return mic;
|
|
971
|
+
}, []);
|
|
972
|
+
const press = react.useCallback(() => {
|
|
973
|
+
const target = latest.current.base;
|
|
974
|
+
if (target === null) return;
|
|
975
|
+
seqRef.current += 1;
|
|
976
|
+
const seq = seqRef.current;
|
|
977
|
+
setLoadFailed(false);
|
|
978
|
+
setLoading(true);
|
|
979
|
+
void micFor(target).then(
|
|
980
|
+
(mic) => {
|
|
981
|
+
if (seqRef.current !== seq) return;
|
|
982
|
+
setLoading(false);
|
|
983
|
+
mic.start();
|
|
984
|
+
},
|
|
985
|
+
() => {
|
|
986
|
+
if (seqRef.current !== seq) return;
|
|
987
|
+
setLoading(false);
|
|
988
|
+
setLoadFailed(true);
|
|
989
|
+
}
|
|
990
|
+
);
|
|
991
|
+
}, [micFor]);
|
|
992
|
+
const recording = loading2 || state.name === "starting" || state.name === "listening";
|
|
993
|
+
const tap = react.useCallback(() => {
|
|
994
|
+
if (state.name === "listening" || state.name === "starting") {
|
|
995
|
+
micRef.current?.mic.stop();
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (loading2) {
|
|
999
|
+
cancel();
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (!agreedRef.current) {
|
|
1003
|
+
setNoticeOpen(true);
|
|
1004
|
+
void loadAvatarMic().catch(() => void 0);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
press();
|
|
1008
|
+
}, [cancel, loading2, press, state.name]);
|
|
1009
|
+
const allow = react.useCallback(() => {
|
|
1010
|
+
agreedRef.current = true;
|
|
1011
|
+
setNoticeOpen(false);
|
|
1012
|
+
buttonRef.current?.focus();
|
|
1013
|
+
press();
|
|
1014
|
+
}, [press]);
|
|
1015
|
+
const notNow = react.useCallback(() => {
|
|
1016
|
+
setNoticeOpen(false);
|
|
1017
|
+
buttonRef.current?.focus();
|
|
1018
|
+
}, []);
|
|
1019
|
+
react.useEffect(() => {
|
|
1020
|
+
if (noticeOpen) allowRef.current?.focus();
|
|
1021
|
+
}, [noticeOpen]);
|
|
1022
|
+
const listeningSince = state.name === "listening" ? state.startedAt : null;
|
|
1023
|
+
react.useEffect(() => {
|
|
1024
|
+
if (listeningSince === null) return;
|
|
1025
|
+
setNow(Date.now());
|
|
1026
|
+
const timer = setInterval(() => setNow(Date.now()), TICK_MS);
|
|
1027
|
+
return () => clearInterval(timer);
|
|
1028
|
+
}, [listeningSince]);
|
|
1029
|
+
react.useEffect(() => {
|
|
1030
|
+
if (state.name === "done" && state.epoch !== usedEpochRef.current) {
|
|
1031
|
+
usedEpochRef.current = state.epoch;
|
|
1032
|
+
if (state.text !== "") latest.current.onText(state.text);
|
|
1033
|
+
} else if (state.name === "failed" && FOR_GOOD.has(state.reason)) {
|
|
1034
|
+
setWithdrawn(state.reason);
|
|
1035
|
+
}
|
|
1036
|
+
}, [state]);
|
|
1037
|
+
if (!possible || S === null || copy === null) {
|
|
1038
|
+
return { button: null, notice: null, lines: null, cancel };
|
|
1039
|
+
}
|
|
1040
|
+
const status = withdrawn !== null ? S[FAILURE_TEXT[withdrawn]] : loading2 ? S.micStarting : loadFailed ? S.micFailed : state.name === "starting" ? S.micStarting : state.name === "listening" ? S.micListening : state.name === "finishing" ? S.micFinishing : state.name === "done" ? state.text === "" ? S.micNoSpeech : state.limit ? S.micDoneLimit : S.micDone : state.name === "failed" ? S[FAILURE_TEXT[state.reason]] : "";
|
|
1041
|
+
const heard = offered && (state.name === "listening" || state.name === "finishing") ? state.text : "";
|
|
1042
|
+
const button = !offered ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1043
|
+
"button",
|
|
1044
|
+
{
|
|
1045
|
+
ref: buttonRef,
|
|
1046
|
+
type: "button",
|
|
1047
|
+
"aria-label": recording ? S.micStop : S.micStart,
|
|
1048
|
+
"aria-pressed": recording,
|
|
1049
|
+
title: recording ? S.micStop : S.micStart,
|
|
1050
|
+
"data-boff-explore": "avatar-live-mic",
|
|
1051
|
+
"data-mic": loading2 ? "starting" : state.name,
|
|
1052
|
+
className: chunkC6OJIHNO_js.cn(
|
|
1053
|
+
"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",
|
|
1054
|
+
recording ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1055
|
+
),
|
|
1056
|
+
onClick: tap,
|
|
1057
|
+
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" })
|
|
1058
|
+
}
|
|
1059
|
+
);
|
|
1060
|
+
const notice = offered && noticeOpen ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1061
|
+
"section",
|
|
1062
|
+
{
|
|
1063
|
+
"aria-labelledby": noticeTitleId,
|
|
1064
|
+
"data-boff-explore": "avatar-live-mic-notice",
|
|
1065
|
+
className: "w-full rounded-lg border border-border p-3 text-start",
|
|
1066
|
+
children: [
|
|
1067
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { id: noticeTitleId, className: "text-sm font-semibold", children: copy.title }),
|
|
1068
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.processing }),
|
|
1069
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.storage }),
|
|
1070
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.text }),
|
|
1071
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.limits }),
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1073
|
+
"a",
|
|
1074
|
+
{
|
|
1075
|
+
href: PRIVACY_HREF,
|
|
1076
|
+
target: "_blank",
|
|
1077
|
+
rel: "noopener noreferrer",
|
|
1078
|
+
"data-boff-explore": "avatar-live-mic-privacy",
|
|
1079
|
+
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",
|
|
1080
|
+
children: copy.privacyLink
|
|
1081
|
+
}
|
|
1082
|
+
),
|
|
1083
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
|
|
1084
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1085
|
+
"button",
|
|
1086
|
+
{
|
|
1087
|
+
ref: allowRef,
|
|
1088
|
+
type: "button",
|
|
1089
|
+
"data-boff-explore": "avatar-live-mic-allow",
|
|
1090
|
+
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",
|
|
1091
|
+
onClick: allow,
|
|
1092
|
+
children: S.micAllow
|
|
1093
|
+
}
|
|
1094
|
+
),
|
|
1095
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1096
|
+
"button",
|
|
1097
|
+
{
|
|
1098
|
+
type: "button",
|
|
1099
|
+
"data-boff-explore": "avatar-live-mic-not-now",
|
|
1100
|
+
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",
|
|
1101
|
+
onClick: notNow,
|
|
1102
|
+
children: S.micNotNow
|
|
1103
|
+
}
|
|
1104
|
+
)
|
|
1105
|
+
] })
|
|
1106
|
+
]
|
|
1107
|
+
}
|
|
1108
|
+
) : null;
|
|
1109
|
+
const listening = offered && state.name === "listening";
|
|
1110
|
+
const lines = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1111
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
1112
|
+
listening ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-hidden": "true", className: "relative flex h-2 w-2 shrink-0", children: [
|
|
1113
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75 motion-reduce:animate-none" }),
|
|
1114
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
|
|
1115
|
+
] }) : null,
|
|
1116
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", "data-boff-explore": "avatar-live-mic-status", children: status }),
|
|
1117
|
+
listening ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1118
|
+
"span",
|
|
1119
|
+
{
|
|
1120
|
+
role: "timer",
|
|
1121
|
+
"data-boff-explore": "avatar-live-mic-elapsed",
|
|
1122
|
+
className: "tabular-nums",
|
|
1123
|
+
children: chunkN7E6Z2GF_js.fill(S.micElapsed, {
|
|
1124
|
+
elapsed: clock(
|
|
1125
|
+
Math.min(LIMIT_SECONDS, (now - (listeningSince ?? now)) / 1e3)
|
|
1126
|
+
),
|
|
1127
|
+
limit: clock(LIMIT_SECONDS)
|
|
1128
|
+
})
|
|
1129
|
+
}
|
|
1130
|
+
) : null
|
|
1131
|
+
] }),
|
|
1132
|
+
heard === "" ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1133
|
+
"p",
|
|
1134
|
+
{
|
|
1135
|
+
"data-boff-explore": "avatar-live-mic-heard",
|
|
1136
|
+
dir: "auto",
|
|
1137
|
+
className: "line-clamp-3 break-words text-sm italic text-muted-foreground",
|
|
1138
|
+
children: heard
|
|
1139
|
+
}
|
|
1140
|
+
)
|
|
1141
|
+
] });
|
|
1142
|
+
return { button, notice, lines, cancel };
|
|
1143
|
+
}
|
|
809
1144
|
|
|
810
1145
|
// src/components/explore-avatar-turnstile.ts
|
|
811
1146
|
var SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
@@ -921,7 +1256,7 @@ function runTurnstile(container, siteKey, action, cData) {
|
|
|
921
1256
|
}
|
|
922
1257
|
};
|
|
923
1258
|
}
|
|
924
|
-
var
|
|
1259
|
+
var PRIVACY_HREF2 = "/privacy#live-avatar";
|
|
925
1260
|
var SPEAKER_WAIT_MS = 3e4;
|
|
926
1261
|
var END_GRACE_MS = 1e4;
|
|
927
1262
|
var CLOCK_TOLERANCE_MS = 5e3;
|
|
@@ -934,7 +1269,7 @@ var ASK_MAX_LENGTH = 300;
|
|
|
934
1269
|
var MAX_REFERENCES = 64;
|
|
935
1270
|
var USED_ON_KEY = "boff.explore.avatarUsedOn";
|
|
936
1271
|
var PRIMARY = '[data-avatar-primary], [data-boff-explore="panel-continue-chat"]';
|
|
937
|
-
var
|
|
1272
|
+
var IDLE2 = { name: "idle" };
|
|
938
1273
|
var INERT2 = { name: "inert" };
|
|
939
1274
|
function stopAttending(handle) {
|
|
940
1275
|
if (handle.unheard !== null) clearTimeout(handle.unheard);
|
|
@@ -1024,6 +1359,12 @@ function firstExchange(messages, rules = SUGGESTED_RULES) {
|
|
|
1024
1359
|
function inFlight(conversation) {
|
|
1025
1360
|
return conversation?.phase === "sending" || conversation?.phase === "streaming";
|
|
1026
1361
|
}
|
|
1362
|
+
function acceptsQuestion(slot) {
|
|
1363
|
+
return slot.any && !slot.textMode && slot.phase === "needs-answer" && slot.kind === "empty" && slot.reason === null;
|
|
1364
|
+
}
|
|
1365
|
+
function micOfferOf(result) {
|
|
1366
|
+
return result.kind === "available" && result.offer.stt !== void 0 ? { ...result.offer.stt, receivedAt: Date.now() } : null;
|
|
1367
|
+
}
|
|
1027
1368
|
function utcToday() {
|
|
1028
1369
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1029
1370
|
}
|
|
@@ -1093,7 +1434,7 @@ async function loadModule(load) {
|
|
|
1093
1434
|
}
|
|
1094
1435
|
return loaded;
|
|
1095
1436
|
}
|
|
1096
|
-
function
|
|
1437
|
+
function clock2(seconds) {
|
|
1097
1438
|
const whole = Math.max(0, Math.ceil(seconds));
|
|
1098
1439
|
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
|
|
1099
1440
|
}
|
|
@@ -1146,7 +1487,7 @@ function AvatarLiveView({
|
|
|
1146
1487
|
const shown = offeredAt(exchange, Date.now());
|
|
1147
1488
|
const { answerId, startOver } = shown;
|
|
1148
1489
|
const offerUntil = answerId === null ? null : exchange.offerUntil;
|
|
1149
|
-
const [phase, setPhase] = react.useState(
|
|
1490
|
+
const [phase, setPhase] = react.useState(IDLE2);
|
|
1150
1491
|
const [blocked, setBlocked] = react.useState(false);
|
|
1151
1492
|
const [now, setNow] = react.useState(() => Date.now());
|
|
1152
1493
|
const [draft, setDraft] = react.useState("");
|
|
@@ -1156,7 +1497,7 @@ function AvatarLiveView({
|
|
|
1156
1497
|
const [usedToday, setUsedToday] = react.useState(false);
|
|
1157
1498
|
const showing = active && !off;
|
|
1158
1499
|
const behindChat = open && !active && !off;
|
|
1159
|
-
const phaseRef = react.useRef(
|
|
1500
|
+
const phaseRef = react.useRef(IDLE2);
|
|
1160
1501
|
const latest = react.useRef({
|
|
1161
1502
|
live,
|
|
1162
1503
|
locale,
|
|
@@ -1172,6 +1513,8 @@ function AvatarLiveView({
|
|
|
1172
1513
|
const waitRef = react.useRef(null);
|
|
1173
1514
|
const sendingRef = react.useRef(false);
|
|
1174
1515
|
const textBoxRef = react.useRef(false);
|
|
1516
|
+
const [micOffer, setMicOffer] = react.useState(null);
|
|
1517
|
+
const micCancelRef = react.useRef(() => void 0);
|
|
1175
1518
|
const checkRef = react.useRef(null);
|
|
1176
1519
|
const askedLocale = react.useRef(locale);
|
|
1177
1520
|
const startRef = react.useRef(null);
|
|
@@ -1292,6 +1635,7 @@ function AvatarLiveView({
|
|
|
1292
1635
|
const applyAvailability = react.useCallback(
|
|
1293
1636
|
(result, locale2) => {
|
|
1294
1637
|
const { live: live2 } = latest.current;
|
|
1638
|
+
setMicOffer(micOfferOf(result));
|
|
1295
1639
|
if (result.kind === "inert") {
|
|
1296
1640
|
setMode("suggested");
|
|
1297
1641
|
go(INERT2);
|
|
@@ -1309,7 +1653,7 @@ function AvatarLiveView({
|
|
|
1309
1653
|
go(INERT2);
|
|
1310
1654
|
return;
|
|
1311
1655
|
}
|
|
1312
|
-
if (result.offer.botCheck === "turnstile" &&
|
|
1656
|
+
if (result.offer.botCheck === "turnstile" && chunkXIZE37IB_js.turnstileSiteKeyOf(live2) === null) {
|
|
1313
1657
|
setMode("suggested");
|
|
1314
1658
|
go(INERT2);
|
|
1315
1659
|
return;
|
|
@@ -1446,7 +1790,7 @@ function AvatarLiveView({
|
|
|
1446
1790
|
return;
|
|
1447
1791
|
}
|
|
1448
1792
|
const refusedFrom = exchangeAtStart.offerUntil - ANSWER_OFFER_MS + SERVICE_MAX_AGE_MS;
|
|
1449
|
-
const siteKey = offer.botCheck === "turnstile" ?
|
|
1793
|
+
const siteKey = offer.botCheck === "turnstile" ? chunkXIZE37IB_js.turnstileSiteKeyOf(live2) : null;
|
|
1450
1794
|
if (offer.botCheck === "turnstile" && (siteKey === null || !canBindBotCheck())) {
|
|
1451
1795
|
go(INERT2);
|
|
1452
1796
|
return;
|
|
@@ -1607,6 +1951,7 @@ function AvatarLiveView({
|
|
|
1607
1951
|
[applyAvailability, bindingHolds, cancelWaiting, exchangeNow, launch]
|
|
1608
1952
|
);
|
|
1609
1953
|
const stop = react.useCallback(() => {
|
|
1954
|
+
micCancelRef.current();
|
|
1610
1955
|
const current = phaseRef.current;
|
|
1611
1956
|
if (current.name === "waiting") {
|
|
1612
1957
|
cancelWaiting(false);
|
|
@@ -1628,6 +1973,7 @@ function AvatarLiveView({
|
|
|
1628
1973
|
}, [cancelStart, cancelWaiting, finish, go, reoffer]);
|
|
1629
1974
|
const leave = react.useCallback(
|
|
1630
1975
|
(event) => {
|
|
1976
|
+
micCancelRef.current();
|
|
1631
1977
|
const current = phaseRef.current;
|
|
1632
1978
|
const reason = event === "user" || event === "chat" ? "user" : "page_hide";
|
|
1633
1979
|
const how = reason === "user" ? "stopped" : "interrupted";
|
|
@@ -1638,7 +1984,7 @@ function AvatarLiveView({
|
|
|
1638
1984
|
const controller = checkRef.current;
|
|
1639
1985
|
checkRef.current = null;
|
|
1640
1986
|
controller?.abort();
|
|
1641
|
-
go(
|
|
1987
|
+
go(IDLE2);
|
|
1642
1988
|
return;
|
|
1643
1989
|
}
|
|
1644
1990
|
case "waiting":
|
|
@@ -1646,7 +1992,7 @@ function AvatarLiveView({
|
|
|
1646
1992
|
return;
|
|
1647
1993
|
case "bot-check":
|
|
1648
1994
|
cancelStart(reason);
|
|
1649
|
-
go(reason === "user" ?
|
|
1995
|
+
go(reason === "user" ? IDLE2 : reoffer(current.offer));
|
|
1650
1996
|
return;
|
|
1651
1997
|
case "creating":
|
|
1652
1998
|
if (keepsSession) return;
|
|
@@ -1708,7 +2054,7 @@ function AvatarLiveView({
|
|
|
1708
2054
|
} else {
|
|
1709
2055
|
checkRef.current?.abort();
|
|
1710
2056
|
checkRef.current = null;
|
|
1711
|
-
go(
|
|
2057
|
+
go(IDLE2);
|
|
1712
2058
|
}
|
|
1713
2059
|
}, [active, cancelWaiting, check, go, locale]);
|
|
1714
2060
|
const kind = shown.kind;
|
|
@@ -1953,6 +2299,46 @@ function AvatarLiveView({
|
|
|
1953
2299
|
const textMode = any && (usedToday && (phase.name === "needs-answer" || phase.name === "consent" || phase.name === "declined" || phase.name === "ended") || phase.name === "refused" && phase.reason === "limits");
|
|
1954
2300
|
textBoxRef.current = textMode;
|
|
1955
2301
|
const askState = !any ? void 0 : textMode ? "text" : phase.name === "needs-answer" ? kind === "empty" ? "ready" : kind : phase.name;
|
|
2302
|
+
const askReason = inFlight(conversation) ? S.askBusy : conversation?.canSend ? null : S.askUnavailable;
|
|
2303
|
+
const refreshMicOffer = react.useCallback(async () => {
|
|
2304
|
+
const { live: live2, locale: locale2, base: base2, headers: headers2 } = latest.current;
|
|
2305
|
+
if (base2 === null) return null;
|
|
2306
|
+
const next = micOfferOf(
|
|
2307
|
+
await requestAvailabilityAnswer({
|
|
2308
|
+
base: base2,
|
|
2309
|
+
headers: headers2,
|
|
2310
|
+
site: live2.site,
|
|
2311
|
+
locale: locale2
|
|
2312
|
+
})
|
|
2313
|
+
);
|
|
2314
|
+
if (next !== null) setMicOffer(next);
|
|
2315
|
+
return next;
|
|
2316
|
+
}, []);
|
|
2317
|
+
const onMicText = react.useCallback(
|
|
2318
|
+
(text) => {
|
|
2319
|
+
setDraft((current) => withHeardText(current, text, askMaxLength));
|
|
2320
|
+
if (touchFirst) return;
|
|
2321
|
+
rootRef.current?.querySelector(
|
|
2322
|
+
'[data-boff-explore="avatar-live-ask-input"]'
|
|
2323
|
+
)?.focus();
|
|
2324
|
+
},
|
|
2325
|
+
[askMaxLength, touchFirst]
|
|
2326
|
+
);
|
|
2327
|
+
const mic = useAvatarMic({
|
|
2328
|
+
enabled: live.mic === true && micOffer !== null && !off && acceptsQuestion({
|
|
2329
|
+
any,
|
|
2330
|
+
phase: phase.name,
|
|
2331
|
+
kind,
|
|
2332
|
+
textMode,
|
|
2333
|
+
reason: askReason
|
|
2334
|
+
}),
|
|
2335
|
+
locale,
|
|
2336
|
+
base,
|
|
2337
|
+
offer: micOffer,
|
|
2338
|
+
refreshOffer: refreshMicOffer,
|
|
2339
|
+
onText: onMicText
|
|
2340
|
+
});
|
|
2341
|
+
micCancelRef.current = mic.cancel;
|
|
1956
2342
|
const notNow = react.useCallback(() => {
|
|
1957
2343
|
const current = phaseRef.current;
|
|
1958
2344
|
if ((current.name === "consent" || current.name === "needs-answer") && current.offer.questions === "any") {
|
|
@@ -2121,7 +2507,7 @@ function AvatarLiveView({
|
|
|
2121
2507
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2122
2508
|
"a",
|
|
2123
2509
|
{
|
|
2124
|
-
href:
|
|
2510
|
+
href: PRIVACY_HREF2,
|
|
2125
2511
|
target: "_blank",
|
|
2126
2512
|
rel: "noopener noreferrer",
|
|
2127
2513
|
"data-boff-explore": "avatar-live-privacy",
|
|
@@ -2213,7 +2599,7 @@ function AvatarLiveView({
|
|
|
2213
2599
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm tabular-nums text-muted-foreground", children: [
|
|
2214
2600
|
S.timeLeft,
|
|
2215
2601
|
" ",
|
|
2216
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { role: "timer", "data-boff-explore": "avatar-live-countdown", children:
|
|
2602
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { role: "timer", "data-boff-explore": "avatar-live-countdown", children: clock2(Math.max(0, (session.endsAt - now) / 1e3)) })
|
|
2217
2603
|
] })
|
|
2218
2604
|
] });
|
|
2219
2605
|
const liveActions = (back) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
@@ -2430,7 +2816,7 @@ function AvatarLiveView({
|
|
|
2430
2816
|
break;
|
|
2431
2817
|
}
|
|
2432
2818
|
}
|
|
2433
|
-
const reason =
|
|
2819
|
+
const reason = askReason;
|
|
2434
2820
|
const chips = box !== null && shown.kind === "empty" && prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
2435
2821
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-muted-foreground", children: tryAsking }),
|
|
2436
2822
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-h-28 flex-wrap gap-2 overflow-y-auto", children: prompts.map((prompt) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2477,6 +2863,7 @@ function AvatarLiveView({
|
|
|
2477
2863
|
children: S.usedToday
|
|
2478
2864
|
}
|
|
2479
2865
|
) : null,
|
|
2866
|
+
box === "spoken" ? mic.notice : null,
|
|
2480
2867
|
box === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2481
2868
|
AskBox,
|
|
2482
2869
|
{
|
|
@@ -2491,7 +2878,9 @@ function AvatarLiveView({
|
|
|
2491
2878
|
footer: S.askFooter,
|
|
2492
2879
|
noFiles: S.askNoFiles,
|
|
2493
2880
|
hint: box === "spoken" ? S.needsAnswerAny : null,
|
|
2494
|
-
touchFirst
|
|
2881
|
+
touchFirst,
|
|
2882
|
+
mic: box === "spoken" ? mic.button : null,
|
|
2883
|
+
micLines: box === "spoken" ? mic.lines : null
|
|
2495
2884
|
}
|
|
2496
2885
|
)
|
|
2497
2886
|
]
|
|
@@ -2638,7 +3027,9 @@ function AskBox({
|
|
|
2638
3027
|
footer,
|
|
2639
3028
|
noFiles,
|
|
2640
3029
|
hint,
|
|
2641
|
-
touchFirst
|
|
3030
|
+
touchFirst,
|
|
3031
|
+
mic,
|
|
3032
|
+
micLines
|
|
2642
3033
|
}) {
|
|
2643
3034
|
const uid = react.useId();
|
|
2644
3035
|
const reasonId = `${uid}-reason`;
|
|
@@ -2703,6 +3094,7 @@ function AskBox({
|
|
|
2703
3094
|
onDrop
|
|
2704
3095
|
}
|
|
2705
3096
|
),
|
|
3097
|
+
mic,
|
|
2706
3098
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2707
3099
|
"button",
|
|
2708
3100
|
{
|
|
@@ -2716,6 +3108,7 @@ function AskBox({
|
|
|
2716
3108
|
}
|
|
2717
3109
|
)
|
|
2718
3110
|
] }),
|
|
3111
|
+
micLines,
|
|
2719
3112
|
reason === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2720
3113
|
"p",
|
|
2721
3114
|
{
|