@burdenoff/website-sdk 2026.1104.3 → 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 +51 -6
- 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 +482 -64
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +476 -58
- 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-5JQGE3XI.mjs → chunk-7HE33NBS.mjs} +4 -111
- package/dist/chunk-7HE33NBS.mjs.map +1 -0
- package/dist/chunk-JZS6FQT6.mjs +127 -0
- package/dist/chunk-JZS6FQT6.mjs.map +1 -0
- package/dist/{chunk-22OT6E26.js → chunk-MBVXDFVC.js} +18 -127
- package/dist/chunk-MBVXDFVC.js.map +1 -0
- package/dist/{chunk-7MSB3EUI.mjs → chunk-MVNN6LLO.mjs} +4 -3
- package/dist/chunk-MVNN6LLO.mjs.map +1 -0
- package/dist/chunk-N7E6Z2GF.js +132 -0
- package/dist/chunk-N7E6Z2GF.js.map +1 -0
- package/dist/{chunk-WSEQ2I6I.js → chunk-XIZE37IB.js} +4 -3
- package/dist/chunk-XIZE37IB.js.map +1 -0
- package/dist/components/explore.d.mts +1 -1
- package/dist/components/explore.d.ts +1 -1
- package/dist/components/explore.js +20 -20
- package/dist/components/explore.mjs +2 -2
- package/dist/{explore-BV4dRwoC.d.ts → explore-Dfru4j4m.d.ts} +45 -31
- 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/{explore-BgEeGPvY.d.mts → explore-ei8uoko9.d.mts} +45 -31
- package/dist/index.d.mts +11 -6
- package/dist/index.d.ts +11 -6
- package/dist/index.js +70 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-22OT6E26.js.map +0 -1
- package/dist/chunk-5JQGE3XI.mjs.map +0 -1
- package/dist/chunk-7MSB3EUI.mjs.map +0 -1
- package/dist/chunk-O4GS3XPZ.js +0 -10
- package/dist/chunk-O4GS3XPZ.js.map +0 -1
- package/dist/chunk-TT3MHN42.mjs +0 -8
- package/dist/chunk-TT3MHN42.mjs.map +0 -1
- package/dist/chunk-WSEQ2I6I.js.map +0 -1
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { turnstileSiteKeyOf, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion } from '../chunk-
|
|
2
|
-
import { fill } from '../chunk-
|
|
1
|
+
import { turnstileSiteKeyOf, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion } from '../chunk-MVNN6LLO.mjs';
|
|
2
|
+
import { answerDisplayText, fill } from '../chunk-JZS6FQT6.mjs';
|
|
3
3
|
import { cn } from '../chunk-R7G6TWIS.mjs';
|
|
4
4
|
import '../chunk-VNS5WDWO.mjs';
|
|
5
5
|
import { useId, useState, useRef, useCallback, useEffect } from 'react';
|
|
6
|
+
import { Square, Mic } from 'lucide-react';
|
|
6
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
8
|
|
|
8
9
|
// src/components/explore-avatar-api.ts
|
|
9
10
|
var AVAILABILITY_TIMEOUT_MS = 8e3;
|
|
10
11
|
var CREATE_TIMEOUT_MS = 3e4;
|
|
11
12
|
var MESSAGE_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
13
|
+
var STT_TOKEN = /^[A-Za-z0-9._~-]{1,4096}$/;
|
|
12
14
|
var CONVERSATION_TOKEN = /^[A-Za-z0-9._~+/=-]{16,512}$/;
|
|
13
15
|
var LOCALE = /^[A-Za-z0-9-]{1,16}$/;
|
|
14
16
|
var SESSION_ID = /^avs_[0-9a-f]{32}$/;
|
|
@@ -56,6 +58,15 @@ function readQuestions(value) {
|
|
|
56
58
|
if (typeof value !== "string" || !QUESTION_MODES.has(value)) return null;
|
|
57
59
|
return value === "any" ? "any" : "suggested";
|
|
58
60
|
}
|
|
61
|
+
function readSttOffer(value) {
|
|
62
|
+
if (!isRecord(value)) return void 0;
|
|
63
|
+
const keys = Object.keys(value).sort();
|
|
64
|
+
const { token, expiresAt } = value;
|
|
65
|
+
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) {
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
return { token, expiresAt };
|
|
69
|
+
}
|
|
59
70
|
function readAvailability(status, body) {
|
|
60
71
|
if (status !== 200 || !isRecord(body)) return INERT;
|
|
61
72
|
const { available, reason } = body;
|
|
@@ -69,9 +80,16 @@ function readAvailability(status, body) {
|
|
|
69
80
|
if (available !== true || typeof consentVersion !== "string" || !isAvatarVersion(consentVersion) || typeof maxSessionSeconds !== "number" || !Number.isInteger(maxSessionSeconds) || maxSessionSeconds < 1 || botCheck === null || questions === null) {
|
|
70
81
|
return INERT;
|
|
71
82
|
}
|
|
83
|
+
const stt = readSttOffer(body.stt);
|
|
72
84
|
return {
|
|
73
85
|
kind: "available",
|
|
74
|
-
offer: {
|
|
86
|
+
offer: {
|
|
87
|
+
consentVersion,
|
|
88
|
+
maxSessionSeconds,
|
|
89
|
+
botCheck,
|
|
90
|
+
questions,
|
|
91
|
+
...stt === void 0 ? {} : { stt }
|
|
92
|
+
}
|
|
75
93
|
};
|
|
76
94
|
}
|
|
77
95
|
const meaning = available ? void 0 : AVAILABILITY_REASONS.get(reason);
|
|
@@ -159,6 +177,13 @@ function deadline(timeoutMs, outer) {
|
|
|
159
177
|
};
|
|
160
178
|
}
|
|
161
179
|
async function requestAvailability(target) {
|
|
180
|
+
try {
|
|
181
|
+
return await requestAvailabilityAnswer(target);
|
|
182
|
+
} catch {
|
|
183
|
+
return INERT;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function requestAvailabilityAnswer(target) {
|
|
162
187
|
if (!isAvatarSite(target.site) || !LOCALE.test(target.locale)) return INERT;
|
|
163
188
|
const query = new URLSearchParams({
|
|
164
189
|
site: target.site,
|
|
@@ -174,13 +199,11 @@ async function requestAvailability(target) {
|
|
|
174
199
|
signal: timer.signal
|
|
175
200
|
});
|
|
176
201
|
return readAvailability(response.status, await readJson(response));
|
|
177
|
-
} catch {
|
|
178
|
-
return INERT;
|
|
179
202
|
} finally {
|
|
180
203
|
timer.done();
|
|
181
204
|
}
|
|
182
205
|
}
|
|
183
|
-
|
|
206
|
+
function requestCreate(target) {
|
|
184
207
|
const { body } = target;
|
|
185
208
|
const payload = JSON.stringify({
|
|
186
209
|
site: body.site,
|
|
@@ -190,27 +213,45 @@ async function requestCreate(target) {
|
|
|
190
213
|
consentVersion: body.consentVersion,
|
|
191
214
|
manifestVersion: body.manifestVersion
|
|
192
215
|
});
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
216
|
+
const controller = new AbortController();
|
|
217
|
+
let answered = false;
|
|
218
|
+
const answer = (async () => {
|
|
219
|
+
try {
|
|
220
|
+
const response = await fetch(`${target.base}/sessions`, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
credentials: "omit",
|
|
223
|
+
cache: "no-store",
|
|
224
|
+
headers: {
|
|
225
|
+
...target.headers,
|
|
226
|
+
"Content-Type": "application/json",
|
|
227
|
+
"Idempotency-Key": target.idempotencyKey,
|
|
228
|
+
...target.botToken === void 0 ? {} : { "x-web-avatar-bot-token": target.botToken }
|
|
229
|
+
},
|
|
230
|
+
body: payload,
|
|
231
|
+
signal: controller.signal
|
|
232
|
+
});
|
|
233
|
+
answered = true;
|
|
234
|
+
return readCreate(response.status, await readJson(response));
|
|
235
|
+
} catch {
|
|
236
|
+
return FAILED;
|
|
237
|
+
}
|
|
238
|
+
})();
|
|
239
|
+
return new Promise((resolve) => {
|
|
240
|
+
let late = false;
|
|
241
|
+
const timer = setTimeout(() => {
|
|
242
|
+
late = true;
|
|
243
|
+
if (!answered) controller.abort();
|
|
244
|
+
resolve(FAILED);
|
|
245
|
+
}, CREATE_TIMEOUT_MS);
|
|
246
|
+
void answer.then((result) => {
|
|
247
|
+
if (late) {
|
|
248
|
+
target.onLate?.(result);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
resolve(result);
|
|
207
253
|
});
|
|
208
|
-
|
|
209
|
-
} catch {
|
|
210
|
-
return INERT;
|
|
211
|
-
} finally {
|
|
212
|
-
timer.done();
|
|
213
|
-
}
|
|
254
|
+
});
|
|
214
255
|
}
|
|
215
256
|
function sendEnd(target) {
|
|
216
257
|
try {
|
|
@@ -786,6 +827,318 @@ function avatarConsentCopy(version, locale) {
|
|
|
786
827
|
if (!isAvatarLiveLocaleReviewed(locale)) return null;
|
|
787
828
|
return CONSENT.get(version)?.[locale] ?? null;
|
|
788
829
|
}
|
|
830
|
+
var MIC_EN = {
|
|
831
|
+
micStart: "Ask by voice",
|
|
832
|
+
micStop: "Stop recording",
|
|
833
|
+
micAllow: "Use the microphone",
|
|
834
|
+
micNotNow: "Not now",
|
|
835
|
+
micStarting: "Starting the microphone\u2026",
|
|
836
|
+
micListening: "Listening\u2026",
|
|
837
|
+
micElapsed: "{elapsed} of {limit}",
|
|
838
|
+
micFinishing: "Getting the text\u2026",
|
|
839
|
+
micDone: "Check the text, then press Send.",
|
|
840
|
+
micDoneLimit: "The recording stopped at thirty seconds. Check the text, then press Send.",
|
|
841
|
+
micNoSpeech: "No words were heard. Try again, or type your question.",
|
|
842
|
+
micDenied: "The microphone is blocked in this browser. You can type your question instead.",
|
|
843
|
+
micUnsupported: "The microphone can't be used here. You can type your question instead.",
|
|
844
|
+
micUnavailable: "Voice questions aren't available right now. You can type your question instead.",
|
|
845
|
+
micLimit: "Voice questions have reached their limit for now. You can type your question instead.",
|
|
846
|
+
micBusy: "Voice questions are busy right now. Try again in a moment, or type your question.",
|
|
847
|
+
micFailed: "The microphone stopped before the text came. Try again, or type your question."
|
|
848
|
+
};
|
|
849
|
+
var MIC_STRINGS = { en: MIC_EN };
|
|
850
|
+
function avatarMicStrings(locale) {
|
|
851
|
+
if (!isAvatarLiveLocaleReviewed(locale)) return null;
|
|
852
|
+
return MIC_STRINGS[locale] ?? null;
|
|
853
|
+
}
|
|
854
|
+
var AVATAR_MIC_NOTICE_VERSION = "web-mic-2026-11-05";
|
|
855
|
+
var MIC_NOTICE = /* @__PURE__ */ new Map([
|
|
856
|
+
[
|
|
857
|
+
AVATAR_MIC_NOTICE_VERSION,
|
|
858
|
+
{
|
|
859
|
+
en: {
|
|
860
|
+
title: "Ask with your voice",
|
|
861
|
+
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.",
|
|
862
|
+
storage: "We don't store or log your audio, or the text made from it.",
|
|
863
|
+
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.",
|
|
864
|
+
limits: "Each recording stops after thirty seconds, and voice questions have daily limits.",
|
|
865
|
+
privacyLink: "How we handle this"
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
]
|
|
869
|
+
]);
|
|
870
|
+
function avatarMicNotice(version, locale) {
|
|
871
|
+
if (!isAvatarLiveLocaleReviewed(locale)) return null;
|
|
872
|
+
return MIC_NOTICE.get(version)?.[locale] ?? null;
|
|
873
|
+
}
|
|
874
|
+
var PRIVACY_HREF = "/privacy#live-avatar";
|
|
875
|
+
var LIMIT_SECONDS = 30;
|
|
876
|
+
var TICK_MS = 250;
|
|
877
|
+
var IDLE = { name: "idle" };
|
|
878
|
+
var FOR_GOOD = /* @__PURE__ */ new Set([
|
|
879
|
+
"unavailable",
|
|
880
|
+
"limit"
|
|
881
|
+
]);
|
|
882
|
+
var FAILURE_TEXT = {
|
|
883
|
+
denied: "micDenied",
|
|
884
|
+
unsupported: "micUnsupported",
|
|
885
|
+
unavailable: "micUnavailable",
|
|
886
|
+
limit: "micLimit",
|
|
887
|
+
busy: "micBusy",
|
|
888
|
+
failed: "micFailed"
|
|
889
|
+
};
|
|
890
|
+
function loadAvatarMic() {
|
|
891
|
+
return import('../avatar-mic/index.mjs').then((loaded) => ({
|
|
892
|
+
createAvatarMic: loaded.createAvatarMic
|
|
893
|
+
}));
|
|
894
|
+
}
|
|
895
|
+
function canUseAvatarMic() {
|
|
896
|
+
return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getUserMedia === "function" && typeof AudioContext !== "undefined" && typeof AudioWorkletNode !== "undefined" && typeof WebSocket !== "undefined";
|
|
897
|
+
}
|
|
898
|
+
function clock(seconds) {
|
|
899
|
+
const whole = Math.max(0, Math.floor(seconds));
|
|
900
|
+
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
|
|
901
|
+
}
|
|
902
|
+
function withHeardText(current, heard, maxLength) {
|
|
903
|
+
const text = heard.trim();
|
|
904
|
+
if (text === "") return current;
|
|
905
|
+
const joined = current.trim() === "" ? text : `${current.replace(/\s+$/, "")} ${text}`;
|
|
906
|
+
return joined.slice(0, Math.max(0, maxLength));
|
|
907
|
+
}
|
|
908
|
+
function useAvatarMic({
|
|
909
|
+
enabled,
|
|
910
|
+
locale,
|
|
911
|
+
base,
|
|
912
|
+
offer,
|
|
913
|
+
refreshOffer,
|
|
914
|
+
onText
|
|
915
|
+
}) {
|
|
916
|
+
const S = avatarMicStrings(locale);
|
|
917
|
+
const copy = avatarMicNotice(AVATAR_MIC_NOTICE_VERSION, locale);
|
|
918
|
+
const uid = useId();
|
|
919
|
+
const noticeTitleId = `${uid}-mic-notice`;
|
|
920
|
+
const [withdrawn, setWithdrawn] = useState(null);
|
|
921
|
+
const [supported, setSupported] = useState(false);
|
|
922
|
+
useEffect(() => setSupported(canUseAvatarMic()), []);
|
|
923
|
+
const possible = enabled && supported && base !== null && S !== null && copy !== null;
|
|
924
|
+
const offered = possible && withdrawn === null;
|
|
925
|
+
const [state, setState] = useState(IDLE);
|
|
926
|
+
const [noticeOpen, setNoticeOpen] = useState(false);
|
|
927
|
+
const agreedRef = useRef(false);
|
|
928
|
+
const [loading2, setLoading] = useState(false);
|
|
929
|
+
const [loadFailed, setLoadFailed] = useState(false);
|
|
930
|
+
const [now, setNow] = useState(() => Date.now());
|
|
931
|
+
const micRef = useRef(null);
|
|
932
|
+
const seqRef = useRef(0);
|
|
933
|
+
const buttonRef = useRef(null);
|
|
934
|
+
const allowRef = useRef(null);
|
|
935
|
+
const usedEpochRef = useRef(0);
|
|
936
|
+
const latest = useRef({ offer, refreshOffer, onText, base });
|
|
937
|
+
latest.current = { offer, refreshOffer, onText, base };
|
|
938
|
+
const cancel = useCallback(() => {
|
|
939
|
+
seqRef.current += 1;
|
|
940
|
+
setLoading(false);
|
|
941
|
+
setLoadFailed(false);
|
|
942
|
+
setNoticeOpen(false);
|
|
943
|
+
micRef.current?.mic.cancel();
|
|
944
|
+
}, []);
|
|
945
|
+
useEffect(() => {
|
|
946
|
+
if (!offered) cancel();
|
|
947
|
+
}, [offered, cancel]);
|
|
948
|
+
useEffect(
|
|
949
|
+
() => () => {
|
|
950
|
+
seqRef.current += 1;
|
|
951
|
+
micRef.current?.mic.cancel();
|
|
952
|
+
},
|
|
953
|
+
[]
|
|
954
|
+
);
|
|
955
|
+
const micFor = useCallback(async (target) => {
|
|
956
|
+
const held = micRef.current;
|
|
957
|
+
if (held !== null && held.base === target) return held.mic;
|
|
958
|
+
held?.mic.cancel();
|
|
959
|
+
const loaded = await loadAvatarMic();
|
|
960
|
+
const mic = loaded.createAvatarMic({
|
|
961
|
+
base: target,
|
|
962
|
+
notice: AVATAR_MIC_NOTICE_VERSION,
|
|
963
|
+
offer: () => latest.current.offer,
|
|
964
|
+
refreshOffer: () => latest.current.refreshOffer(),
|
|
965
|
+
onState: setState
|
|
966
|
+
});
|
|
967
|
+
micRef.current = { base: target, mic };
|
|
968
|
+
return mic;
|
|
969
|
+
}, []);
|
|
970
|
+
const press = useCallback(() => {
|
|
971
|
+
const target = latest.current.base;
|
|
972
|
+
if (target === null) return;
|
|
973
|
+
seqRef.current += 1;
|
|
974
|
+
const seq = seqRef.current;
|
|
975
|
+
setLoadFailed(false);
|
|
976
|
+
setLoading(true);
|
|
977
|
+
void micFor(target).then(
|
|
978
|
+
(mic) => {
|
|
979
|
+
if (seqRef.current !== seq) return;
|
|
980
|
+
setLoading(false);
|
|
981
|
+
mic.start();
|
|
982
|
+
},
|
|
983
|
+
() => {
|
|
984
|
+
if (seqRef.current !== seq) return;
|
|
985
|
+
setLoading(false);
|
|
986
|
+
setLoadFailed(true);
|
|
987
|
+
}
|
|
988
|
+
);
|
|
989
|
+
}, [micFor]);
|
|
990
|
+
const recording = loading2 || state.name === "starting" || state.name === "listening";
|
|
991
|
+
const tap = useCallback(() => {
|
|
992
|
+
if (state.name === "listening" || state.name === "starting") {
|
|
993
|
+
micRef.current?.mic.stop();
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
if (loading2) {
|
|
997
|
+
cancel();
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (!agreedRef.current) {
|
|
1001
|
+
setNoticeOpen(true);
|
|
1002
|
+
void loadAvatarMic().catch(() => void 0);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
press();
|
|
1006
|
+
}, [cancel, loading2, press, state.name]);
|
|
1007
|
+
const allow = useCallback(() => {
|
|
1008
|
+
agreedRef.current = true;
|
|
1009
|
+
setNoticeOpen(false);
|
|
1010
|
+
buttonRef.current?.focus();
|
|
1011
|
+
press();
|
|
1012
|
+
}, [press]);
|
|
1013
|
+
const notNow = useCallback(() => {
|
|
1014
|
+
setNoticeOpen(false);
|
|
1015
|
+
buttonRef.current?.focus();
|
|
1016
|
+
}, []);
|
|
1017
|
+
useEffect(() => {
|
|
1018
|
+
if (noticeOpen) allowRef.current?.focus();
|
|
1019
|
+
}, [noticeOpen]);
|
|
1020
|
+
const listeningSince = state.name === "listening" ? state.startedAt : null;
|
|
1021
|
+
useEffect(() => {
|
|
1022
|
+
if (listeningSince === null) return;
|
|
1023
|
+
setNow(Date.now());
|
|
1024
|
+
const timer = setInterval(() => setNow(Date.now()), TICK_MS);
|
|
1025
|
+
return () => clearInterval(timer);
|
|
1026
|
+
}, [listeningSince]);
|
|
1027
|
+
useEffect(() => {
|
|
1028
|
+
if (state.name === "done" && state.epoch !== usedEpochRef.current) {
|
|
1029
|
+
usedEpochRef.current = state.epoch;
|
|
1030
|
+
if (state.text !== "") latest.current.onText(state.text);
|
|
1031
|
+
} else if (state.name === "failed" && FOR_GOOD.has(state.reason)) {
|
|
1032
|
+
setWithdrawn(state.reason);
|
|
1033
|
+
}
|
|
1034
|
+
}, [state]);
|
|
1035
|
+
if (!possible || S === null || copy === null) {
|
|
1036
|
+
return { button: null, notice: null, lines: null, cancel };
|
|
1037
|
+
}
|
|
1038
|
+
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]] : "";
|
|
1039
|
+
const heard = offered && (state.name === "listening" || state.name === "finishing") ? state.text : "";
|
|
1040
|
+
const button = !offered ? null : /* @__PURE__ */ jsx(
|
|
1041
|
+
"button",
|
|
1042
|
+
{
|
|
1043
|
+
ref: buttonRef,
|
|
1044
|
+
type: "button",
|
|
1045
|
+
"aria-label": recording ? S.micStop : S.micStart,
|
|
1046
|
+
"aria-pressed": recording,
|
|
1047
|
+
title: recording ? S.micStop : S.micStart,
|
|
1048
|
+
"data-boff-explore": "avatar-live-mic",
|
|
1049
|
+
"data-mic": loading2 ? "starting" : state.name,
|
|
1050
|
+
className: cn(
|
|
1051
|
+
"inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-md border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background motion-reduce:transition-none",
|
|
1052
|
+
recording ? "border-primary bg-primary text-primary-foreground" : "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1053
|
+
),
|
|
1054
|
+
onClick: tap,
|
|
1055
|
+
children: recording ? /* @__PURE__ */ jsx(Square, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Mic, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
1056
|
+
}
|
|
1057
|
+
);
|
|
1058
|
+
const notice = offered && noticeOpen ? /* @__PURE__ */ jsxs(
|
|
1059
|
+
"section",
|
|
1060
|
+
{
|
|
1061
|
+
"aria-labelledby": noticeTitleId,
|
|
1062
|
+
"data-boff-explore": "avatar-live-mic-notice",
|
|
1063
|
+
className: "w-full rounded-lg border border-border p-3 text-start",
|
|
1064
|
+
children: [
|
|
1065
|
+
/* @__PURE__ */ jsx("h3", { id: noticeTitleId, className: "text-sm font-semibold", children: copy.title }),
|
|
1066
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.processing }),
|
|
1067
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.storage }),
|
|
1068
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.text }),
|
|
1069
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: copy.limits }),
|
|
1070
|
+
/* @__PURE__ */ jsx(
|
|
1071
|
+
"a",
|
|
1072
|
+
{
|
|
1073
|
+
href: PRIVACY_HREF,
|
|
1074
|
+
target: "_blank",
|
|
1075
|
+
rel: "noopener noreferrer",
|
|
1076
|
+
"data-boff-explore": "avatar-live-mic-privacy",
|
|
1077
|
+
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",
|
|
1078
|
+
children: copy.privacyLink
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
|
|
1082
|
+
/* @__PURE__ */ jsx(
|
|
1083
|
+
"button",
|
|
1084
|
+
{
|
|
1085
|
+
ref: allowRef,
|
|
1086
|
+
type: "button",
|
|
1087
|
+
"data-boff-explore": "avatar-live-mic-allow",
|
|
1088
|
+
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",
|
|
1089
|
+
onClick: allow,
|
|
1090
|
+
children: S.micAllow
|
|
1091
|
+
}
|
|
1092
|
+
),
|
|
1093
|
+
/* @__PURE__ */ jsx(
|
|
1094
|
+
"button",
|
|
1095
|
+
{
|
|
1096
|
+
type: "button",
|
|
1097
|
+
"data-boff-explore": "avatar-live-mic-not-now",
|
|
1098
|
+
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",
|
|
1099
|
+
onClick: notNow,
|
|
1100
|
+
children: S.micNotNow
|
|
1101
|
+
}
|
|
1102
|
+
)
|
|
1103
|
+
] })
|
|
1104
|
+
]
|
|
1105
|
+
}
|
|
1106
|
+
) : null;
|
|
1107
|
+
const listening = offered && state.name === "listening";
|
|
1108
|
+
const lines = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1109
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
1110
|
+
listening ? /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "relative flex h-2 w-2 shrink-0", children: [
|
|
1111
|
+
/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75 motion-reduce:animate-none" }),
|
|
1112
|
+
/* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
|
|
1113
|
+
] }) : null,
|
|
1114
|
+
/* @__PURE__ */ jsx("p", { role: "status", "data-boff-explore": "avatar-live-mic-status", children: status }),
|
|
1115
|
+
listening ? /* @__PURE__ */ jsx(
|
|
1116
|
+
"span",
|
|
1117
|
+
{
|
|
1118
|
+
role: "timer",
|
|
1119
|
+
"data-boff-explore": "avatar-live-mic-elapsed",
|
|
1120
|
+
className: "tabular-nums",
|
|
1121
|
+
children: fill(S.micElapsed, {
|
|
1122
|
+
elapsed: clock(
|
|
1123
|
+
Math.min(LIMIT_SECONDS, (now - (listeningSince ?? now)) / 1e3)
|
|
1124
|
+
),
|
|
1125
|
+
limit: clock(LIMIT_SECONDS)
|
|
1126
|
+
})
|
|
1127
|
+
}
|
|
1128
|
+
) : null
|
|
1129
|
+
] }),
|
|
1130
|
+
heard === "" ? null : /* @__PURE__ */ jsx(
|
|
1131
|
+
"p",
|
|
1132
|
+
{
|
|
1133
|
+
"data-boff-explore": "avatar-live-mic-heard",
|
|
1134
|
+
dir: "auto",
|
|
1135
|
+
className: "line-clamp-3 break-words text-sm italic text-muted-foreground",
|
|
1136
|
+
children: heard
|
|
1137
|
+
}
|
|
1138
|
+
)
|
|
1139
|
+
] });
|
|
1140
|
+
return { button, notice, lines, cancel };
|
|
1141
|
+
}
|
|
789
1142
|
|
|
790
1143
|
// src/components/explore-avatar-turnstile.ts
|
|
791
1144
|
var SCRIPT_SRC = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
@@ -901,7 +1254,7 @@ function runTurnstile(container, siteKey, action, cData) {
|
|
|
901
1254
|
}
|
|
902
1255
|
};
|
|
903
1256
|
}
|
|
904
|
-
var
|
|
1257
|
+
var PRIVACY_HREF2 = "/privacy#live-avatar";
|
|
905
1258
|
var SPEAKER_WAIT_MS = 3e4;
|
|
906
1259
|
var END_GRACE_MS = 1e4;
|
|
907
1260
|
var CLOCK_TOLERANCE_MS = 5e3;
|
|
@@ -914,7 +1267,7 @@ var ASK_MAX_LENGTH = 300;
|
|
|
914
1267
|
var MAX_REFERENCES = 64;
|
|
915
1268
|
var USED_ON_KEY = "boff.explore.avatarUsedOn";
|
|
916
1269
|
var PRIMARY = '[data-avatar-primary], [data-boff-explore="panel-continue-chat"]';
|
|
917
|
-
var
|
|
1270
|
+
var IDLE2 = { name: "idle" };
|
|
918
1271
|
var INERT2 = { name: "inert" };
|
|
919
1272
|
function stopAttending(handle) {
|
|
920
1273
|
if (handle.unheard !== null) clearTimeout(handle.unheard);
|
|
@@ -1004,6 +1357,12 @@ function firstExchange(messages, rules = SUGGESTED_RULES) {
|
|
|
1004
1357
|
function inFlight(conversation) {
|
|
1005
1358
|
return conversation?.phase === "sending" || conversation?.phase === "streaming";
|
|
1006
1359
|
}
|
|
1360
|
+
function acceptsQuestion(slot) {
|
|
1361
|
+
return slot.any && !slot.textMode && slot.phase === "needs-answer" && slot.kind === "empty" && slot.reason === null;
|
|
1362
|
+
}
|
|
1363
|
+
function micOfferOf(result) {
|
|
1364
|
+
return result.kind === "available" && result.offer.stt !== void 0 ? { ...result.offer.stt, receivedAt: Date.now() } : null;
|
|
1365
|
+
}
|
|
1007
1366
|
function utcToday() {
|
|
1008
1367
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1009
1368
|
}
|
|
@@ -1073,7 +1432,7 @@ async function loadModule(load) {
|
|
|
1073
1432
|
}
|
|
1074
1433
|
return loaded;
|
|
1075
1434
|
}
|
|
1076
|
-
function
|
|
1435
|
+
function clock2(seconds) {
|
|
1077
1436
|
const whole = Math.max(0, Math.ceil(seconds));
|
|
1078
1437
|
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
|
|
1079
1438
|
}
|
|
@@ -1126,7 +1485,7 @@ function AvatarLiveView({
|
|
|
1126
1485
|
const shown = offeredAt(exchange, Date.now());
|
|
1127
1486
|
const { answerId, startOver } = shown;
|
|
1128
1487
|
const offerUntil = answerId === null ? null : exchange.offerUntil;
|
|
1129
|
-
const [phase, setPhase] = useState(
|
|
1488
|
+
const [phase, setPhase] = useState(IDLE2);
|
|
1130
1489
|
const [blocked, setBlocked] = useState(false);
|
|
1131
1490
|
const [now, setNow] = useState(() => Date.now());
|
|
1132
1491
|
const [draft, setDraft] = useState("");
|
|
@@ -1136,7 +1495,7 @@ function AvatarLiveView({
|
|
|
1136
1495
|
const [usedToday, setUsedToday] = useState(false);
|
|
1137
1496
|
const showing = active && !off;
|
|
1138
1497
|
const behindChat = open && !active && !off;
|
|
1139
|
-
const phaseRef = useRef(
|
|
1498
|
+
const phaseRef = useRef(IDLE2);
|
|
1140
1499
|
const latest = useRef({
|
|
1141
1500
|
live,
|
|
1142
1501
|
locale,
|
|
@@ -1152,6 +1511,8 @@ function AvatarLiveView({
|
|
|
1152
1511
|
const waitRef = useRef(null);
|
|
1153
1512
|
const sendingRef = useRef(false);
|
|
1154
1513
|
const textBoxRef = useRef(false);
|
|
1514
|
+
const [micOffer, setMicOffer] = useState(null);
|
|
1515
|
+
const micCancelRef = useRef(() => void 0);
|
|
1155
1516
|
const checkRef = useRef(null);
|
|
1156
1517
|
const askedLocale = useRef(locale);
|
|
1157
1518
|
const startRef = useRef(null);
|
|
@@ -1272,6 +1633,7 @@ function AvatarLiveView({
|
|
|
1272
1633
|
const applyAvailability = useCallback(
|
|
1273
1634
|
(result, locale2) => {
|
|
1274
1635
|
const { live: live2 } = latest.current;
|
|
1636
|
+
setMicOffer(micOfferOf(result));
|
|
1275
1637
|
if (result.kind === "inert") {
|
|
1276
1638
|
setMode("suggested");
|
|
1277
1639
|
go(INERT2);
|
|
@@ -1359,21 +1721,21 @@ function AvatarLiveView({
|
|
|
1359
1721
|
[go]
|
|
1360
1722
|
);
|
|
1361
1723
|
const settle = useCallback(
|
|
1362
|
-
(result,
|
|
1724
|
+
(result, givenUp, context) => {
|
|
1363
1725
|
const created = result.kind === "created" ? {
|
|
1364
1726
|
sessionId: result.session.sessionId,
|
|
1365
1727
|
secret: result.session.endSecret
|
|
1366
1728
|
} : result.kind === "unusable" ? { sessionId: result.sessionId, secret: result.endSecret } : null;
|
|
1367
1729
|
const claimed = created !== null || result.kind === "refused" && result.reason === "answer_claimed";
|
|
1368
1730
|
if (claimed) claimedRef.current.add(context.messageId);
|
|
1369
|
-
if (
|
|
1731
|
+
if (givenUp !== null) {
|
|
1370
1732
|
if (created !== null) {
|
|
1371
1733
|
sendEnd({
|
|
1372
1734
|
base: context.base,
|
|
1373
1735
|
headers: context.headers,
|
|
1374
1736
|
sessionId: created.sessionId,
|
|
1375
1737
|
secret: created.secret,
|
|
1376
|
-
reason:
|
|
1738
|
+
reason: givenUp
|
|
1377
1739
|
});
|
|
1378
1740
|
}
|
|
1379
1741
|
const shown2 = phaseRef.current;
|
|
@@ -1482,21 +1844,21 @@ function AvatarLiveView({
|
|
|
1482
1844
|
return;
|
|
1483
1845
|
}
|
|
1484
1846
|
if (turnstile !== null) go({ name: "creating" });
|
|
1847
|
+
const context = { module: loaded, offer, messageId, base: base2, headers: headers2 };
|
|
1485
1848
|
const result = await requestCreate({
|
|
1486
1849
|
base: base2,
|
|
1487
1850
|
headers: headers2,
|
|
1488
1851
|
idempotencyKey,
|
|
1489
1852
|
botToken,
|
|
1490
|
-
body: body2
|
|
1853
|
+
body: body2,
|
|
1854
|
+
// An answer past the deadline, after the view has said the live avatar couldn't start: a session it made is
|
|
1855
|
+
// ended at once with its own secret, and never joined. The reason is the visitor's own where they gave the
|
|
1856
|
+
// start up first, else `client_error`. It reads `run` and never writes it, so the deadline's `failed` below is
|
|
1857
|
+
// shown whichever of the two is handled first.
|
|
1858
|
+
onLate: (late) => settle(late, run.cancelled ?? "client_error", context)
|
|
1491
1859
|
});
|
|
1492
1860
|
if (startRef.current === run) startRef.current = null;
|
|
1493
|
-
settle(result, run,
|
|
1494
|
-
module: loaded,
|
|
1495
|
-
offer,
|
|
1496
|
-
messageId,
|
|
1497
|
-
base: base2,
|
|
1498
|
-
headers: headers2
|
|
1499
|
-
});
|
|
1861
|
+
settle(result, run.cancelled, context);
|
|
1500
1862
|
})();
|
|
1501
1863
|
},
|
|
1502
1864
|
[exchangeNow, go, reoffer, settle]
|
|
@@ -1587,6 +1949,7 @@ function AvatarLiveView({
|
|
|
1587
1949
|
[applyAvailability, bindingHolds, cancelWaiting, exchangeNow, launch]
|
|
1588
1950
|
);
|
|
1589
1951
|
const stop = useCallback(() => {
|
|
1952
|
+
micCancelRef.current();
|
|
1590
1953
|
const current = phaseRef.current;
|
|
1591
1954
|
if (current.name === "waiting") {
|
|
1592
1955
|
cancelWaiting(false);
|
|
@@ -1608,6 +1971,7 @@ function AvatarLiveView({
|
|
|
1608
1971
|
}, [cancelStart, cancelWaiting, finish, go, reoffer]);
|
|
1609
1972
|
const leave = useCallback(
|
|
1610
1973
|
(event) => {
|
|
1974
|
+
micCancelRef.current();
|
|
1611
1975
|
const current = phaseRef.current;
|
|
1612
1976
|
const reason = event === "user" || event === "chat" ? "user" : "page_hide";
|
|
1613
1977
|
const how = reason === "user" ? "stopped" : "interrupted";
|
|
@@ -1618,7 +1982,7 @@ function AvatarLiveView({
|
|
|
1618
1982
|
const controller = checkRef.current;
|
|
1619
1983
|
checkRef.current = null;
|
|
1620
1984
|
controller?.abort();
|
|
1621
|
-
go(
|
|
1985
|
+
go(IDLE2);
|
|
1622
1986
|
return;
|
|
1623
1987
|
}
|
|
1624
1988
|
case "waiting":
|
|
@@ -1626,7 +1990,7 @@ function AvatarLiveView({
|
|
|
1626
1990
|
return;
|
|
1627
1991
|
case "bot-check":
|
|
1628
1992
|
cancelStart(reason);
|
|
1629
|
-
go(reason === "user" ?
|
|
1993
|
+
go(reason === "user" ? IDLE2 : reoffer(current.offer));
|
|
1630
1994
|
return;
|
|
1631
1995
|
case "creating":
|
|
1632
1996
|
if (keepsSession) return;
|
|
@@ -1688,7 +2052,7 @@ function AvatarLiveView({
|
|
|
1688
2052
|
} else {
|
|
1689
2053
|
checkRef.current?.abort();
|
|
1690
2054
|
checkRef.current = null;
|
|
1691
|
-
go(
|
|
2055
|
+
go(IDLE2);
|
|
1692
2056
|
}
|
|
1693
2057
|
}, [active, cancelWaiting, check, go, locale]);
|
|
1694
2058
|
const kind = shown.kind;
|
|
@@ -1933,6 +2297,46 @@ function AvatarLiveView({
|
|
|
1933
2297
|
const textMode = any && (usedToday && (phase.name === "needs-answer" || phase.name === "consent" || phase.name === "declined" || phase.name === "ended") || phase.name === "refused" && phase.reason === "limits");
|
|
1934
2298
|
textBoxRef.current = textMode;
|
|
1935
2299
|
const askState = !any ? void 0 : textMode ? "text" : phase.name === "needs-answer" ? kind === "empty" ? "ready" : kind : phase.name;
|
|
2300
|
+
const askReason = inFlight(conversation) ? S.askBusy : conversation?.canSend ? null : S.askUnavailable;
|
|
2301
|
+
const refreshMicOffer = useCallback(async () => {
|
|
2302
|
+
const { live: live2, locale: locale2, base: base2, headers: headers2 } = latest.current;
|
|
2303
|
+
if (base2 === null) return null;
|
|
2304
|
+
const next = micOfferOf(
|
|
2305
|
+
await requestAvailabilityAnswer({
|
|
2306
|
+
base: base2,
|
|
2307
|
+
headers: headers2,
|
|
2308
|
+
site: live2.site,
|
|
2309
|
+
locale: locale2
|
|
2310
|
+
})
|
|
2311
|
+
);
|
|
2312
|
+
if (next !== null) setMicOffer(next);
|
|
2313
|
+
return next;
|
|
2314
|
+
}, []);
|
|
2315
|
+
const onMicText = useCallback(
|
|
2316
|
+
(text) => {
|
|
2317
|
+
setDraft((current) => withHeardText(current, text, askMaxLength));
|
|
2318
|
+
if (touchFirst) return;
|
|
2319
|
+
rootRef.current?.querySelector(
|
|
2320
|
+
'[data-boff-explore="avatar-live-ask-input"]'
|
|
2321
|
+
)?.focus();
|
|
2322
|
+
},
|
|
2323
|
+
[askMaxLength, touchFirst]
|
|
2324
|
+
);
|
|
2325
|
+
const mic = useAvatarMic({
|
|
2326
|
+
enabled: live.mic === true && micOffer !== null && !off && acceptsQuestion({
|
|
2327
|
+
any,
|
|
2328
|
+
phase: phase.name,
|
|
2329
|
+
kind,
|
|
2330
|
+
textMode,
|
|
2331
|
+
reason: askReason
|
|
2332
|
+
}),
|
|
2333
|
+
locale,
|
|
2334
|
+
base,
|
|
2335
|
+
offer: micOffer,
|
|
2336
|
+
refreshOffer: refreshMicOffer,
|
|
2337
|
+
onText: onMicText
|
|
2338
|
+
});
|
|
2339
|
+
micCancelRef.current = mic.cancel;
|
|
1936
2340
|
const notNow = useCallback(() => {
|
|
1937
2341
|
const current = phaseRef.current;
|
|
1938
2342
|
if ((current.name === "consent" || current.name === "needs-answer") && current.offer.questions === "any") {
|
|
@@ -2101,7 +2505,7 @@ function AvatarLiveView({
|
|
|
2101
2505
|
/* @__PURE__ */ jsxs(
|
|
2102
2506
|
"a",
|
|
2103
2507
|
{
|
|
2104
|
-
href:
|
|
2508
|
+
href: PRIVACY_HREF2,
|
|
2105
2509
|
target: "_blank",
|
|
2106
2510
|
rel: "noopener noreferrer",
|
|
2107
2511
|
"data-boff-explore": "avatar-live-privacy",
|
|
@@ -2193,7 +2597,7 @@ function AvatarLiveView({
|
|
|
2193
2597
|
/* @__PURE__ */ jsxs("p", { className: "text-sm tabular-nums text-muted-foreground", children: [
|
|
2194
2598
|
S.timeLeft,
|
|
2195
2599
|
" ",
|
|
2196
|
-
/* @__PURE__ */ jsx("span", { role: "timer", "data-boff-explore": "avatar-live-countdown", children:
|
|
2600
|
+
/* @__PURE__ */ jsx("span", { role: "timer", "data-boff-explore": "avatar-live-countdown", children: clock2(Math.max(0, (session.endsAt - now) / 1e3)) })
|
|
2197
2601
|
] })
|
|
2198
2602
|
] });
|
|
2199
2603
|
const liveActions = (back) => /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
@@ -2275,14 +2679,21 @@ function AvatarLiveView({
|
|
|
2275
2679
|
children: lastQuestion.content
|
|
2276
2680
|
}
|
|
2277
2681
|
),
|
|
2278
|
-
lastAnswer === void 0 ? unanswered === "waiting" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: activity ?? S.gettingAnswer }) : unanswered === "failed" ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: S.answerFailed }) : null : lastAnswer.status === "PENDING" || lastAnswer.status === "RUNNING" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: activity ?? S.gettingAnswer }) : lastAnswer.status === "FAILED" ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: S.answerFailed }) :
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2682
|
+
lastAnswer === void 0 ? unanswered === "waiting" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: activity ?? S.gettingAnswer }) : unanswered === "failed" ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: S.answerFailed }) : null : lastAnswer.status === "PENDING" || lastAnswer.status === "RUNNING" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: activity ?? S.gettingAnswer }) : lastAnswer.status === "FAILED" ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: S.answerFailed }) : (
|
|
2683
|
+
// The answer's words, not its markdown: `content` is written for the chat's renderer, and shown as it is
|
|
2684
|
+
// here a link reads `[label](https://…)`. In its spoken form, as the chat's read-aloud says it: a link is its
|
|
2685
|
+
// label (the address is in Chat, one tap away), a table its rows, and the markers of emphasis, headings,
|
|
2686
|
+
// quotes and list items go (`answerDisplayText`). The words are the ones the chat shows, citation markers
|
|
2687
|
+
// included, which the sources in Chat answer to.
|
|
2688
|
+
/* @__PURE__ */ jsx(
|
|
2689
|
+
"p",
|
|
2690
|
+
{
|
|
2691
|
+
"data-boff-explore": "avatar-live-text-answer",
|
|
2692
|
+
dir: "auto",
|
|
2693
|
+
className: "line-clamp-6 w-full max-w-sm whitespace-pre-wrap break-words text-start text-sm",
|
|
2694
|
+
children: answerDisplayText(lastAnswer.content)
|
|
2695
|
+
}
|
|
2696
|
+
)
|
|
2286
2697
|
)
|
|
2287
2698
|
] });
|
|
2288
2699
|
const ended = phase.name === "ended";
|
|
@@ -2403,7 +2814,7 @@ function AvatarLiveView({
|
|
|
2403
2814
|
break;
|
|
2404
2815
|
}
|
|
2405
2816
|
}
|
|
2406
|
-
const reason =
|
|
2817
|
+
const reason = askReason;
|
|
2407
2818
|
const chips = box !== null && shown.kind === "empty" && prompts.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
2408
2819
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: tryAsking }),
|
|
2409
2820
|
/* @__PURE__ */ jsx("div", { className: "flex max-h-28 flex-wrap gap-2 overflow-y-auto", children: prompts.map((prompt) => /* @__PURE__ */ jsx(
|
|
@@ -2450,6 +2861,7 @@ function AvatarLiveView({
|
|
|
2450
2861
|
children: S.usedToday
|
|
2451
2862
|
}
|
|
2452
2863
|
) : null,
|
|
2864
|
+
box === "spoken" ? mic.notice : null,
|
|
2453
2865
|
box === null ? null : /* @__PURE__ */ jsx(
|
|
2454
2866
|
AskBox,
|
|
2455
2867
|
{
|
|
@@ -2464,7 +2876,9 @@ function AvatarLiveView({
|
|
|
2464
2876
|
footer: S.askFooter,
|
|
2465
2877
|
noFiles: S.askNoFiles,
|
|
2466
2878
|
hint: box === "spoken" ? S.needsAnswerAny : null,
|
|
2467
|
-
touchFirst
|
|
2879
|
+
touchFirst,
|
|
2880
|
+
mic: box === "spoken" ? mic.button : null,
|
|
2881
|
+
micLines: box === "spoken" ? mic.lines : null
|
|
2468
2882
|
}
|
|
2469
2883
|
)
|
|
2470
2884
|
]
|
|
@@ -2611,7 +3025,9 @@ function AskBox({
|
|
|
2611
3025
|
footer,
|
|
2612
3026
|
noFiles,
|
|
2613
3027
|
hint,
|
|
2614
|
-
touchFirst
|
|
3028
|
+
touchFirst,
|
|
3029
|
+
mic,
|
|
3030
|
+
micLines
|
|
2615
3031
|
}) {
|
|
2616
3032
|
const uid = useId();
|
|
2617
3033
|
const reasonId = `${uid}-reason`;
|
|
@@ -2676,6 +3092,7 @@ function AskBox({
|
|
|
2676
3092
|
onDrop
|
|
2677
3093
|
}
|
|
2678
3094
|
),
|
|
3095
|
+
mic,
|
|
2679
3096
|
/* @__PURE__ */ jsx(
|
|
2680
3097
|
"button",
|
|
2681
3098
|
{
|
|
@@ -2689,6 +3106,7 @@ function AskBox({
|
|
|
2689
3106
|
}
|
|
2690
3107
|
)
|
|
2691
3108
|
] }),
|
|
3109
|
+
micLines,
|
|
2692
3110
|
reason === null ? null : /* @__PURE__ */ jsx(
|
|
2693
3111
|
"p",
|
|
2694
3112
|
{
|