@burdenoff/website-sdk 2026.828.7 → 2026.829.2

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.
@@ -242,6 +242,7 @@ var EXPLORE_MESSAGE_FIELDS = (
242
242
  description
243
243
  product
244
244
  imageUrl
245
+ index
245
246
  }
246
247
  ctas {
247
248
  kind
@@ -249,6 +250,8 @@ var EXPLORE_MESSAGE_FIELDS = (
249
250
  url
250
251
  product
251
252
  }
253
+ followUps
254
+ answered
252
255
  errorCode
253
256
  createdAt
254
257
  completedAt
@@ -314,6 +317,16 @@ var SEND_EXPLORE_MESSAGE_MUTATION = (
314
317
  }
315
318
  `
316
319
  );
320
+ var RECORD_EXPLORE_CLICK_MUTATION = (
321
+ /* GraphQL */
322
+ `
323
+ mutation RecordExploreClick($input: RecordExploreClickInput!) {
324
+ recordExploreClick(input: $input) {
325
+ recorded
326
+ }
327
+ }
328
+ `
329
+ );
317
330
 
318
331
  // src/data/products.ts
319
332
  var ECOSYSTEM_PRODUCTS = [
@@ -1246,6 +1259,20 @@ function useExploreChat(options = {}) {
1246
1259
  return null;
1247
1260
  }, [messages]);
1248
1261
  const canSend = (phase === "ready" || phase === "error") && catalog.enabled && turnCount < catalog.limits.maxTurnsPerConversation;
1262
+ const recordClick = useCallback(
1263
+ (messageId, kind, url) => {
1264
+ const token = conversationTokenRef.current;
1265
+ if (!token) return;
1266
+ try {
1267
+ void client.mutate(RECORD_EXPLORE_CLICK_MUTATION, {
1268
+ input: { conversationToken: token, messageId, kind, url }
1269
+ }).catch(() => {
1270
+ });
1271
+ } catch {
1272
+ }
1273
+ },
1274
+ [client]
1275
+ );
1249
1276
  return {
1250
1277
  phase,
1251
1278
  catalog,
@@ -1263,7 +1290,8 @@ function useExploreChat(options = {}) {
1263
1290
  history: archive,
1264
1291
  openConversation,
1265
1292
  deleteConversation,
1266
- clearHistory
1293
+ clearHistory,
1294
+ recordClick
1267
1295
  };
1268
1296
  }
1269
1297
  var optimisticCounter = 0;
@@ -1276,21 +1304,37 @@ function getRecognitionCtor() {
1276
1304
  const w = window;
1277
1305
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
1278
1306
  }
1307
+ function isTouchDevice() {
1308
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1309
+ try {
1310
+ return window.matchMedia("(pointer: coarse)").matches;
1311
+ } catch {
1312
+ return false;
1313
+ }
1314
+ }
1279
1315
  var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1316
+ var MIC_UNAVAILABLE_MESSAGE = "Dictation isn't available in this browser. You can type instead.";
1317
+ var RELEASE_SETTLE_MS = 250;
1318
+ var RETRY_DELAY_MS = 350;
1319
+ var sleep = (ms) => new Promise((resolve) => {
1320
+ setTimeout(resolve, ms);
1321
+ });
1280
1322
  async function ensureMicrophoneAccess() {
1281
1323
  const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
1282
- if (!media?.getUserMedia) return { ok: true, confirmed: false };
1324
+ if (!media?.getUserMedia)
1325
+ return { ok: true, confirmed: false, primed: false };
1283
1326
  try {
1284
1327
  const status = await navigator.permissions?.query({
1285
1328
  name: "microphone"
1286
1329
  });
1287
- if (status?.state === "granted") return { ok: true, confirmed: true };
1330
+ if (status?.state === "granted")
1331
+ return { ok: true, confirmed: true, primed: false };
1288
1332
  } catch {
1289
1333
  }
1290
1334
  try {
1291
1335
  const stream = await media.getUserMedia({ audio: true });
1292
1336
  for (const track of stream.getTracks()) track.stop();
1293
- return { ok: true, confirmed: true };
1337
+ return { ok: true, confirmed: true, primed: true };
1294
1338
  } catch (error) {
1295
1339
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
1296
1340
  if (name === "NotAllowedError" || name === "SecurityError") {
@@ -1334,19 +1378,17 @@ function useSpeechInput({
1334
1378
  const recognitionRef = useRef(null);
1335
1379
  const startTokenRef = useRef(0);
1336
1380
  const micConfirmedRef = useRef(false);
1337
- const beginRecognitionRef = useRef(null);
1338
- const beginRecognition = useCallback(
1339
- (Ctor, token) => {
1340
- beginRecognitionRef.current?.(Ctor, token);
1341
- },
1342
- []
1343
- );
1381
+ const wantsListeningRef = useRef(false);
1382
+ const retriedRef = useRef(false);
1344
1383
  const finalRef = useRef(onFinalTranscript);
1345
1384
  const errorRef = useRef(onError);
1346
1385
  finalRef.current = onFinalTranscript;
1347
1386
  errorRef.current = onError;
1387
+ const langRef = useRef(lang);
1388
+ langRef.current = lang;
1348
1389
  const stop = useCallback(() => {
1349
1390
  startTokenRef.current += 1;
1391
+ wantsListeningRef.current = false;
1350
1392
  setListening(false);
1351
1393
  setInterim("");
1352
1394
  const recognition = recognitionRef.current;
@@ -1355,83 +1397,111 @@ function useSpeechInput({
1355
1397
  recognition.stop();
1356
1398
  } catch {
1357
1399
  }
1358
- setListening(false);
1359
- setInterim("");
1360
1400
  }, []);
1361
- const start = useCallback(() => {
1401
+ const openSession = useCallback((token) => {
1362
1402
  const Ctor = getRecognitionCtor();
1363
- if (!Ctor) return;
1364
- if (recognitionRef.current) stop();
1365
- setError(null);
1366
- setListening(true);
1403
+ if (!Ctor || token !== startTokenRef.current) return;
1404
+ const recognition = new Ctor();
1405
+ recognition.lang = langRef.current ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1406
+ recognition.continuous = !isTouchDevice();
1407
+ recognition.interimResults = true;
1408
+ recognition.maxAlternatives = 1;
1409
+ recognition.onstart = () => {
1410
+ if (token !== startTokenRef.current) return;
1411
+ setError(null);
1412
+ setListening(true);
1413
+ };
1414
+ recognition.onresult = (event) => {
1415
+ if (token !== startTokenRef.current) return;
1416
+ let settled = "";
1417
+ let pending = "";
1418
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
1419
+ const result = event.results[i];
1420
+ if (!result) continue;
1421
+ const text = result[0]?.transcript ?? "";
1422
+ if (result.isFinal) settled += text;
1423
+ else pending += text;
1424
+ }
1425
+ setInterim(pending);
1426
+ if (settled.trim() !== "") finalRef.current(settled);
1427
+ };
1428
+ recognition.onerror = (event) => {
1429
+ if (token !== startTokenRef.current) return;
1430
+ if ((event.error === "not-allowed" || event.error === "service-not-allowed") && micConfirmedRef.current && !retriedRef.current) {
1431
+ retriedRef.current = true;
1432
+ void sleep(RETRY_DELAY_MS).then(() => {
1433
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1434
+ return;
1435
+ openSessionRef.current?.(token);
1436
+ });
1437
+ return;
1438
+ }
1439
+ const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? MIC_UNAVAILABLE_MESSAGE : describeError(event.error);
1440
+ wantsListeningRef.current = false;
1441
+ setListening(false);
1442
+ setInterim("");
1443
+ if (message !== "") {
1444
+ setError(message);
1445
+ errorRef.current?.(message);
1446
+ }
1447
+ };
1448
+ recognition.onend = () => {
1449
+ if (token !== startTokenRef.current) return;
1450
+ setInterim("");
1451
+ if (wantsListeningRef.current && isTouchDevice()) {
1452
+ void sleep(120).then(() => {
1453
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1454
+ return;
1455
+ openSessionRef.current?.(token);
1456
+ });
1457
+ return;
1458
+ }
1459
+ setListening(false);
1460
+ };
1461
+ recognitionRef.current = recognition;
1462
+ try {
1463
+ recognition.start();
1464
+ } catch {
1465
+ wantsListeningRef.current = false;
1466
+ setListening(false);
1467
+ }
1468
+ }, []);
1469
+ const openSessionRef = useRef(null);
1470
+ openSessionRef.current = openSession;
1471
+ const start = useCallback(() => {
1472
+ if (!getRecognitionCtor()) return;
1367
1473
  startTokenRef.current += 1;
1368
1474
  const token = startTokenRef.current;
1369
- void ensureMicrophoneAccess().then((access) => {
1475
+ wantsListeningRef.current = true;
1476
+ retriedRef.current = false;
1477
+ micConfirmedRef.current = false;
1478
+ setError(null);
1479
+ setListening(true);
1480
+ void ensureMicrophoneAccess().then(async (access) => {
1370
1481
  if (token !== startTokenRef.current) return;
1371
- micConfirmedRef.current = access.ok && access.confirmed;
1372
1482
  if (!access.ok) {
1483
+ wantsListeningRef.current = false;
1373
1484
  setListening(false);
1374
1485
  setError(access.message);
1375
1486
  errorRef.current?.(access.message);
1376
1487
  return;
1377
1488
  }
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 = () => {
1489
+ micConfirmedRef.current = access.confirmed;
1490
+ if (access.primed) {
1491
+ await sleep(RELEASE_SETTLE_MS);
1389
1492
  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 = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? "Dictation isn't available in this browser. You can type instead." : 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
1493
  }
1425
- },
1426
- [lang]
1427
- );
1428
- beginRecognitionRef.current = beginRecognitionImpl;
1494
+ openSessionRef.current?.(token);
1495
+ });
1496
+ }, []);
1429
1497
  const toggle = useCallback(() => {
1430
1498
  if (listening) stop();
1431
1499
  else start();
1432
1500
  }, [listening, start, stop]);
1433
1501
  useEffect(
1434
1502
  () => () => {
1503
+ startTokenRef.current += 1;
1504
+ wantsListeningRef.current = false;
1435
1505
  const recognition = recognitionRef.current;
1436
1506
  if (!recognition) return;
1437
1507
  recognition.onresult = null;