@burdenoff/website-sdk 2026.828.4 → 2026.828.6
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/dist/components/explore-cta.js +9 -7
- package/dist/components/explore-cta.js.map +1 -1
- package/dist/components/explore-cta.mjs +9 -7
- package/dist/components/explore-cta.mjs.map +1 -1
- package/dist/components/explore.d.mts +11 -1
- package/dist/components/explore.d.ts +11 -1
- package/dist/components/explore.js +561 -57
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +563 -60
- package/dist/components/explore.mjs.map +1 -1
- package/dist/hooks/index.d.mts +24 -1
- package/dist/hooks/index.d.ts +24 -1
- package/dist/hooks/index.js +298 -5
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +298 -6
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +569 -64
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +570 -65
- package/dist/index.mjs.map +1 -1
- package/dist/{index-CUJTTlYx.d.mts → use-explore-chat-DhvZhH8f.d.mts} +21 -2
- package/dist/{index-CUJTTlYx.d.ts → use-explore-chat-DhvZhH8f.d.ts} +21 -2
- package/package.json +1 -1
package/dist/hooks/index.mjs
CHANGED
|
@@ -559,6 +559,8 @@ async function getRecaptchaV3Token(siteKey, action = EXPLORE_RECAPTCHA_ACTION) {
|
|
|
559
559
|
|
|
560
560
|
// src/hooks/use-explore-chat.ts
|
|
561
561
|
var DEFAULT_STORAGE_KEY = "boff.explore.v1";
|
|
562
|
+
var HISTORY_SUFFIX = ".history";
|
|
563
|
+
var MAX_ARCHIVED_CONVERSATIONS = 15;
|
|
562
564
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
563
565
|
var DEFAULT_POLL_TIMEOUT_MS = 9e4;
|
|
564
566
|
var POLL_REQUEST_TIMEOUT_MS = 1e4;
|
|
@@ -697,10 +699,58 @@ function classifyExploreError(code, serverMessage) {
|
|
|
697
699
|
retryable: true
|
|
698
700
|
};
|
|
699
701
|
}
|
|
702
|
+
function historyKey(key) {
|
|
703
|
+
return `${key}${HISTORY_SUFFIX}`;
|
|
704
|
+
}
|
|
705
|
+
function readArchive(key) {
|
|
706
|
+
if (typeof window === "undefined") return [];
|
|
707
|
+
try {
|
|
708
|
+
const raw = window.localStorage.getItem(historyKey(key));
|
|
709
|
+
if (!raw) return [];
|
|
710
|
+
const parsed = JSON.parse(raw);
|
|
711
|
+
if (!Array.isArray(parsed)) return [];
|
|
712
|
+
return parsed.filter(
|
|
713
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry.token === "string" && Array.isArray(entry.messages)
|
|
714
|
+
);
|
|
715
|
+
} catch {
|
|
716
|
+
return [];
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function writeArchive(key, entries) {
|
|
720
|
+
if (typeof window === "undefined") return;
|
|
721
|
+
try {
|
|
722
|
+
if (entries.length === 0) {
|
|
723
|
+
window.localStorage.removeItem(historyKey(key));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
window.localStorage.setItem(historyKey(key), JSON.stringify(entries));
|
|
727
|
+
} catch {
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function upsertArchive(key, thread) {
|
|
731
|
+
if (!thread.token) return readArchive(key);
|
|
732
|
+
const real = thread.messages.filter(
|
|
733
|
+
(m) => m.content.trim() !== "" || m.role === "USER"
|
|
734
|
+
);
|
|
735
|
+
if (real.length === 0) return readArchive(key);
|
|
736
|
+
const firstUser = real.find((m) => m.role === "USER");
|
|
737
|
+
const entry = {
|
|
738
|
+
token: thread.token,
|
|
739
|
+
title: (firstUser?.content ?? "Conversation").trim().slice(0, 80),
|
|
740
|
+
updatedAt: real[real.length - 1]?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
741
|
+
messageCount: real.length,
|
|
742
|
+
focusProduct: thread.focusProduct,
|
|
743
|
+
messages: real.slice(-20)
|
|
744
|
+
};
|
|
745
|
+
const rest = readArchive(key).filter((e) => e.token !== entry.token);
|
|
746
|
+
const next = [entry, ...rest].slice(0, MAX_ARCHIVED_CONVERSATIONS);
|
|
747
|
+
writeArchive(key, next);
|
|
748
|
+
return next;
|
|
749
|
+
}
|
|
700
750
|
function readPersisted(key) {
|
|
701
751
|
if (typeof window === "undefined") return null;
|
|
702
752
|
try {
|
|
703
|
-
const raw = window.
|
|
753
|
+
const raw = window.localStorage.getItem(key);
|
|
704
754
|
if (!raw) return null;
|
|
705
755
|
const parsed = JSON.parse(raw);
|
|
706
756
|
if (!parsed || typeof parsed !== "object") return null;
|
|
@@ -745,6 +795,8 @@ function useExploreChat(options = {}) {
|
|
|
745
795
|
const [remainingToday, setRemainingToday] = useState(null);
|
|
746
796
|
const [focusProduct, setFocusProductState] = useState(null);
|
|
747
797
|
const [turnCount, setTurnCount] = useState(0);
|
|
798
|
+
const messagesRef = useRef([]);
|
|
799
|
+
const [archive, setArchive] = useState([]);
|
|
748
800
|
const [hydrated, setHydrated] = useState(false);
|
|
749
801
|
const mountedRef = useRef(true);
|
|
750
802
|
const pollGenerationRef = useRef(0);
|
|
@@ -782,6 +834,7 @@ function useExploreChat(options = {}) {
|
|
|
782
834
|
setHydrated(true);
|
|
783
835
|
return;
|
|
784
836
|
}
|
|
837
|
+
setArchive(readArchive(storageKey));
|
|
785
838
|
const stored = readPersisted(storageKey);
|
|
786
839
|
if (stored) {
|
|
787
840
|
conversationTokenRef.current = stored.conversationToken;
|
|
@@ -810,13 +863,28 @@ function useExploreChat(options = {}) {
|
|
|
810
863
|
focusProduct
|
|
811
864
|
};
|
|
812
865
|
if (!payload.conversationToken && payload.messages.length === 0 && !payload.focusProduct) {
|
|
813
|
-
window.
|
|
866
|
+
window.localStorage.removeItem(storageKey);
|
|
814
867
|
return;
|
|
815
868
|
}
|
|
816
|
-
window.
|
|
869
|
+
window.localStorage.setItem(storageKey, JSON.stringify(payload));
|
|
817
870
|
} catch {
|
|
818
871
|
}
|
|
819
872
|
}, [persist, hydrated, storageKey, messages, focusProduct]);
|
|
873
|
+
useEffect(() => {
|
|
874
|
+
messagesRef.current = messages;
|
|
875
|
+
if (!persist || !hydrated) return;
|
|
876
|
+
const settled = messages.some(
|
|
877
|
+
(m) => m.role === "ASSISTANT" && (m.status === "COMPLETED" || m.status === "FAILED")
|
|
878
|
+
);
|
|
879
|
+
if (!settled) return;
|
|
880
|
+
setArchive(
|
|
881
|
+
upsertArchive(storageKey, {
|
|
882
|
+
token: conversationTokenRef.current,
|
|
883
|
+
messages,
|
|
884
|
+
focusProduct: focusProductRef.current
|
|
885
|
+
})
|
|
886
|
+
);
|
|
887
|
+
}, [messages, persist, hydrated, storageKey]);
|
|
820
888
|
useEffect(() => {
|
|
821
889
|
let cancelled = false;
|
|
822
890
|
const load = async () => {
|
|
@@ -1102,6 +1170,13 @@ function useExploreChat(options = {}) {
|
|
|
1102
1170
|
void send(text);
|
|
1103
1171
|
}, [cancelPolling, send]);
|
|
1104
1172
|
const reset = useCallback(() => {
|
|
1173
|
+
setArchive(
|
|
1174
|
+
upsertArchive(storageKey, {
|
|
1175
|
+
token: conversationTokenRef.current,
|
|
1176
|
+
messages: messagesRef.current,
|
|
1177
|
+
focusProduct: focusProductRef.current
|
|
1178
|
+
})
|
|
1179
|
+
);
|
|
1105
1180
|
cancelPolling();
|
|
1106
1181
|
inFlightRef.current = false;
|
|
1107
1182
|
conversationTokenRef.current = null;
|
|
@@ -1114,11 +1189,48 @@ function useExploreChat(options = {}) {
|
|
|
1114
1189
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
1115
1190
|
if (typeof window !== "undefined") {
|
|
1116
1191
|
try {
|
|
1117
|
-
window.
|
|
1192
|
+
window.localStorage.removeItem(storageKey);
|
|
1118
1193
|
} catch {
|
|
1119
1194
|
}
|
|
1120
1195
|
}
|
|
1121
1196
|
}, [cancelPolling, storageKey]);
|
|
1197
|
+
const openConversation = useCallback(
|
|
1198
|
+
(token) => {
|
|
1199
|
+
const entry = readArchive(storageKey).find((e) => e.token === token);
|
|
1200
|
+
if (!entry) return;
|
|
1201
|
+
upsertArchive(storageKey, {
|
|
1202
|
+
token: conversationTokenRef.current,
|
|
1203
|
+
messages: messagesRef.current,
|
|
1204
|
+
focusProduct: focusProductRef.current
|
|
1205
|
+
});
|
|
1206
|
+
cancelPolling();
|
|
1207
|
+
inFlightRef.current = false;
|
|
1208
|
+
conversationTokenRef.current = entry.token;
|
|
1209
|
+
turnCountRef.current = entry.messages.filter(
|
|
1210
|
+
(m) => m.role === "USER"
|
|
1211
|
+
).length;
|
|
1212
|
+
setTurnCount(turnCountRef.current);
|
|
1213
|
+
setMessages(entry.messages);
|
|
1214
|
+
focusProductRef.current = entry.focusProduct;
|
|
1215
|
+
setFocusProductState(entry.focusProduct);
|
|
1216
|
+
setError(null);
|
|
1217
|
+
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
1218
|
+
setArchive(readArchive(storageKey));
|
|
1219
|
+
},
|
|
1220
|
+
[cancelPolling, storageKey]
|
|
1221
|
+
);
|
|
1222
|
+
const deleteConversation = useCallback(
|
|
1223
|
+
(token) => {
|
|
1224
|
+
const next = readArchive(storageKey).filter((e) => e.token !== token);
|
|
1225
|
+
writeArchive(storageKey, next);
|
|
1226
|
+
setArchive(next);
|
|
1227
|
+
},
|
|
1228
|
+
[storageKey]
|
|
1229
|
+
);
|
|
1230
|
+
const clearHistory = useCallback(() => {
|
|
1231
|
+
writeArchive(storageKey, []);
|
|
1232
|
+
setArchive([]);
|
|
1233
|
+
}, [storageKey]);
|
|
1122
1234
|
const setFocusProduct = useCallback((slug) => {
|
|
1123
1235
|
focusProductRef.current = slug;
|
|
1124
1236
|
setFocusProductState(slug);
|
|
@@ -1147,7 +1259,11 @@ function useExploreChat(options = {}) {
|
|
|
1147
1259
|
stop,
|
|
1148
1260
|
retry,
|
|
1149
1261
|
reset,
|
|
1150
|
-
canSend
|
|
1262
|
+
canSend,
|
|
1263
|
+
history: archive,
|
|
1264
|
+
openConversation,
|
|
1265
|
+
deleteConversation,
|
|
1266
|
+
clearHistory
|
|
1151
1267
|
};
|
|
1152
1268
|
}
|
|
1153
1269
|
var optimisticCounter = 0;
|
|
@@ -1155,7 +1271,183 @@ function makeOptimisticId() {
|
|
|
1155
1271
|
optimisticCounter += 1;
|
|
1156
1272
|
return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
|
|
1157
1273
|
}
|
|
1274
|
+
function getRecognitionCtor() {
|
|
1275
|
+
if (typeof window === "undefined") return null;
|
|
1276
|
+
const w = window;
|
|
1277
|
+
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1278
|
+
}
|
|
1279
|
+
var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
1280
|
+
async function ensureMicrophoneAccess() {
|
|
1281
|
+
const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
1282
|
+
if (!media?.getUserMedia) return { ok: true };
|
|
1283
|
+
try {
|
|
1284
|
+
const status = await navigator.permissions?.query({
|
|
1285
|
+
name: "microphone"
|
|
1286
|
+
});
|
|
1287
|
+
if (status?.state === "granted") return { ok: true };
|
|
1288
|
+
if (status?.state === "denied")
|
|
1289
|
+
return { ok: false, message: MIC_DENIED_MESSAGE };
|
|
1290
|
+
} catch {
|
|
1291
|
+
}
|
|
1292
|
+
try {
|
|
1293
|
+
const stream = await media.getUserMedia({ audio: true });
|
|
1294
|
+
for (const track of stream.getTracks()) track.stop();
|
|
1295
|
+
return { ok: true };
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
1298
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
1299
|
+
return { ok: false, message: MIC_DENIED_MESSAGE };
|
|
1300
|
+
}
|
|
1301
|
+
if (name === "NotFoundError" || name === "DevicesNotFoundError") {
|
|
1302
|
+
return { ok: false, message: "No microphone was found." };
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
ok: false,
|
|
1306
|
+
message: "Dictation could not start. You can type instead."
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function describeError(code) {
|
|
1311
|
+
switch (code) {
|
|
1312
|
+
case "not-allowed":
|
|
1313
|
+
case "service-not-allowed":
|
|
1314
|
+
return MIC_DENIED_MESSAGE;
|
|
1315
|
+
case "no-speech":
|
|
1316
|
+
return "I didn't catch anything \u2014 try again a little closer to the mic.";
|
|
1317
|
+
case "audio-capture":
|
|
1318
|
+
return "No microphone was found.";
|
|
1319
|
+
case "network":
|
|
1320
|
+
return "Speech recognition needs a network connection.";
|
|
1321
|
+
case "aborted":
|
|
1322
|
+
return "";
|
|
1323
|
+
default:
|
|
1324
|
+
return "Dictation stopped unexpectedly. You can type instead.";
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function useSpeechInput({
|
|
1328
|
+
onFinalTranscript,
|
|
1329
|
+
lang,
|
|
1330
|
+
onError
|
|
1331
|
+
}) {
|
|
1332
|
+
const [supported] = useState(() => getRecognitionCtor() !== null);
|
|
1333
|
+
const [listening, setListening] = useState(false);
|
|
1334
|
+
const [interim, setInterim] = useState("");
|
|
1335
|
+
const [error, setError] = useState(null);
|
|
1336
|
+
const recognitionRef = useRef(null);
|
|
1337
|
+
const startTokenRef = useRef(0);
|
|
1338
|
+
const beginRecognitionRef = useRef(null);
|
|
1339
|
+
const beginRecognition = useCallback(
|
|
1340
|
+
(Ctor, token) => {
|
|
1341
|
+
beginRecognitionRef.current?.(Ctor, token);
|
|
1342
|
+
},
|
|
1343
|
+
[]
|
|
1344
|
+
);
|
|
1345
|
+
const finalRef = useRef(onFinalTranscript);
|
|
1346
|
+
const errorRef = useRef(onError);
|
|
1347
|
+
finalRef.current = onFinalTranscript;
|
|
1348
|
+
errorRef.current = onError;
|
|
1349
|
+
const stop = useCallback(() => {
|
|
1350
|
+
startTokenRef.current += 1;
|
|
1351
|
+
setListening(false);
|
|
1352
|
+
setInterim("");
|
|
1353
|
+
const recognition = recognitionRef.current;
|
|
1354
|
+
if (!recognition) return;
|
|
1355
|
+
try {
|
|
1356
|
+
recognition.stop();
|
|
1357
|
+
} catch {
|
|
1358
|
+
}
|
|
1359
|
+
setListening(false);
|
|
1360
|
+
setInterim("");
|
|
1361
|
+
}, []);
|
|
1362
|
+
const start = useCallback(() => {
|
|
1363
|
+
const Ctor = getRecognitionCtor();
|
|
1364
|
+
if (!Ctor) return;
|
|
1365
|
+
if (recognitionRef.current) stop();
|
|
1366
|
+
setError(null);
|
|
1367
|
+
setListening(true);
|
|
1368
|
+
startTokenRef.current += 1;
|
|
1369
|
+
const token = startTokenRef.current;
|
|
1370
|
+
void ensureMicrophoneAccess().then((access) => {
|
|
1371
|
+
if (token !== startTokenRef.current) return;
|
|
1372
|
+
if (!access.ok) {
|
|
1373
|
+
setListening(false);
|
|
1374
|
+
setError(access.message);
|
|
1375
|
+
errorRef.current?.(access.message);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
beginRecognition(Ctor, token);
|
|
1379
|
+
});
|
|
1380
|
+
}, [beginRecognition, stop]);
|
|
1381
|
+
const beginRecognitionImpl = useCallback(
|
|
1382
|
+
(Ctor, token) => {
|
|
1383
|
+
const recognition = new Ctor();
|
|
1384
|
+
recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
1385
|
+
recognition.continuous = true;
|
|
1386
|
+
recognition.interimResults = true;
|
|
1387
|
+
recognition.maxAlternatives = 1;
|
|
1388
|
+
recognition.onstart = () => {
|
|
1389
|
+
if (token !== startTokenRef.current) return;
|
|
1390
|
+
setError(null);
|
|
1391
|
+
setListening(true);
|
|
1392
|
+
};
|
|
1393
|
+
recognition.onresult = (event) => {
|
|
1394
|
+
let settled = "";
|
|
1395
|
+
let pending = "";
|
|
1396
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
1397
|
+
const result = event.results[i];
|
|
1398
|
+
if (!result) continue;
|
|
1399
|
+
const text = result[0]?.transcript ?? "";
|
|
1400
|
+
if (result.isFinal) settled += text;
|
|
1401
|
+
else pending += text;
|
|
1402
|
+
}
|
|
1403
|
+
setInterim(pending);
|
|
1404
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
1405
|
+
};
|
|
1406
|
+
recognition.onerror = (event) => {
|
|
1407
|
+
const message = describeError(event.error);
|
|
1408
|
+
setListening(false);
|
|
1409
|
+
setInterim("");
|
|
1410
|
+
if (message !== "") {
|
|
1411
|
+
setError(message);
|
|
1412
|
+
errorRef.current?.(message);
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
recognition.onend = () => {
|
|
1416
|
+
setListening(false);
|
|
1417
|
+
setInterim("");
|
|
1418
|
+
};
|
|
1419
|
+
recognitionRef.current = recognition;
|
|
1420
|
+
try {
|
|
1421
|
+
recognition.start();
|
|
1422
|
+
} catch {
|
|
1423
|
+
setListening(false);
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
[lang]
|
|
1427
|
+
);
|
|
1428
|
+
beginRecognitionRef.current = beginRecognitionImpl;
|
|
1429
|
+
const toggle = useCallback(() => {
|
|
1430
|
+
if (listening) stop();
|
|
1431
|
+
else start();
|
|
1432
|
+
}, [listening, start, stop]);
|
|
1433
|
+
useEffect(
|
|
1434
|
+
() => () => {
|
|
1435
|
+
const recognition = recognitionRef.current;
|
|
1436
|
+
if (!recognition) return;
|
|
1437
|
+
recognition.onresult = null;
|
|
1438
|
+
recognition.onerror = null;
|
|
1439
|
+
recognition.onend = null;
|
|
1440
|
+
recognition.onstart = null;
|
|
1441
|
+
try {
|
|
1442
|
+
recognition.abort();
|
|
1443
|
+
} catch {
|
|
1444
|
+
}
|
|
1445
|
+
},
|
|
1446
|
+
[]
|
|
1447
|
+
);
|
|
1448
|
+
return { supported, listening, interim, error, start, stop, toggle };
|
|
1449
|
+
}
|
|
1158
1450
|
|
|
1159
|
-
export { DEFAULT_EXPLORE_EXAMPLE_PROMPTS, FALLBACK_EXPLORE_LIMITS, classifyExploreError, useExploreChat };
|
|
1451
|
+
export { DEFAULT_EXPLORE_EXAMPLE_PROMPTS, FALLBACK_EXPLORE_LIMITS, classifyExploreError, useExploreChat, useSpeechInput };
|
|
1160
1452
|
//# sourceMappingURL=index.mjs.map
|
|
1161
1453
|
//# sourceMappingURL=index.mjs.map
|