@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
|
@@ -806,6 +806,8 @@ async function getRecaptchaV3Token(siteKey, action = EXPLORE_RECAPTCHA_ACTION) {
|
|
|
806
806
|
|
|
807
807
|
// src/hooks/use-explore-chat.ts
|
|
808
808
|
var DEFAULT_STORAGE_KEY = "boff.explore.v1";
|
|
809
|
+
var HISTORY_SUFFIX = ".history";
|
|
810
|
+
var MAX_ARCHIVED_CONVERSATIONS = 15;
|
|
809
811
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
810
812
|
var DEFAULT_POLL_TIMEOUT_MS = 9e4;
|
|
811
813
|
var POLL_REQUEST_TIMEOUT_MS = 1e4;
|
|
@@ -944,10 +946,58 @@ function classifyExploreError(code, serverMessage) {
|
|
|
944
946
|
retryable: true
|
|
945
947
|
};
|
|
946
948
|
}
|
|
949
|
+
function historyKey(key) {
|
|
950
|
+
return `${key}${HISTORY_SUFFIX}`;
|
|
951
|
+
}
|
|
952
|
+
function readArchive(key) {
|
|
953
|
+
if (typeof window === "undefined") return [];
|
|
954
|
+
try {
|
|
955
|
+
const raw = window.localStorage.getItem(historyKey(key));
|
|
956
|
+
if (!raw) return [];
|
|
957
|
+
const parsed = JSON.parse(raw);
|
|
958
|
+
if (!Array.isArray(parsed)) return [];
|
|
959
|
+
return parsed.filter(
|
|
960
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry.token === "string" && Array.isArray(entry.messages)
|
|
961
|
+
);
|
|
962
|
+
} catch {
|
|
963
|
+
return [];
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function writeArchive(key, entries) {
|
|
967
|
+
if (typeof window === "undefined") return;
|
|
968
|
+
try {
|
|
969
|
+
if (entries.length === 0) {
|
|
970
|
+
window.localStorage.removeItem(historyKey(key));
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
window.localStorage.setItem(historyKey(key), JSON.stringify(entries));
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function upsertArchive(key, thread) {
|
|
978
|
+
if (!thread.token) return readArchive(key);
|
|
979
|
+
const real = thread.messages.filter(
|
|
980
|
+
(m) => m.content.trim() !== "" || m.role === "USER"
|
|
981
|
+
);
|
|
982
|
+
if (real.length === 0) return readArchive(key);
|
|
983
|
+
const firstUser = real.find((m) => m.role === "USER");
|
|
984
|
+
const entry = {
|
|
985
|
+
token: thread.token,
|
|
986
|
+
title: (firstUser?.content ?? "Conversation").trim().slice(0, 80),
|
|
987
|
+
updatedAt: real[real.length - 1]?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
988
|
+
messageCount: real.length,
|
|
989
|
+
focusProduct: thread.focusProduct,
|
|
990
|
+
messages: real.slice(-20)
|
|
991
|
+
};
|
|
992
|
+
const rest = readArchive(key).filter((e) => e.token !== entry.token);
|
|
993
|
+
const next = [entry, ...rest].slice(0, MAX_ARCHIVED_CONVERSATIONS);
|
|
994
|
+
writeArchive(key, next);
|
|
995
|
+
return next;
|
|
996
|
+
}
|
|
947
997
|
function readPersisted(key) {
|
|
948
998
|
if (typeof window === "undefined") return null;
|
|
949
999
|
try {
|
|
950
|
-
const raw = window.
|
|
1000
|
+
const raw = window.localStorage.getItem(key);
|
|
951
1001
|
if (!raw) return null;
|
|
952
1002
|
const parsed = JSON.parse(raw);
|
|
953
1003
|
if (!parsed || typeof parsed !== "object") return null;
|
|
@@ -992,6 +1042,8 @@ function useExploreChat(options = {}) {
|
|
|
992
1042
|
const [remainingToday, setRemainingToday] = React.useState(null);
|
|
993
1043
|
const [focusProduct, setFocusProductState] = React.useState(null);
|
|
994
1044
|
const [turnCount, setTurnCount] = React.useState(0);
|
|
1045
|
+
const messagesRef = React.useRef([]);
|
|
1046
|
+
const [archive, setArchive] = React.useState([]);
|
|
995
1047
|
const [hydrated, setHydrated] = React.useState(false);
|
|
996
1048
|
const mountedRef = React.useRef(true);
|
|
997
1049
|
const pollGenerationRef = React.useRef(0);
|
|
@@ -1029,6 +1081,7 @@ function useExploreChat(options = {}) {
|
|
|
1029
1081
|
setHydrated(true);
|
|
1030
1082
|
return;
|
|
1031
1083
|
}
|
|
1084
|
+
setArchive(readArchive(storageKey));
|
|
1032
1085
|
const stored = readPersisted(storageKey);
|
|
1033
1086
|
if (stored) {
|
|
1034
1087
|
conversationTokenRef.current = stored.conversationToken;
|
|
@@ -1057,13 +1110,28 @@ function useExploreChat(options = {}) {
|
|
|
1057
1110
|
focusProduct
|
|
1058
1111
|
};
|
|
1059
1112
|
if (!payload.conversationToken && payload.messages.length === 0 && !payload.focusProduct) {
|
|
1060
|
-
window.
|
|
1113
|
+
window.localStorage.removeItem(storageKey);
|
|
1061
1114
|
return;
|
|
1062
1115
|
}
|
|
1063
|
-
window.
|
|
1116
|
+
window.localStorage.setItem(storageKey, JSON.stringify(payload));
|
|
1064
1117
|
} catch {
|
|
1065
1118
|
}
|
|
1066
1119
|
}, [persist, hydrated, storageKey, messages, focusProduct]);
|
|
1120
|
+
React.useEffect(() => {
|
|
1121
|
+
messagesRef.current = messages;
|
|
1122
|
+
if (!persist || !hydrated) return;
|
|
1123
|
+
const settled = messages.some(
|
|
1124
|
+
(m) => m.role === "ASSISTANT" && (m.status === "COMPLETED" || m.status === "FAILED")
|
|
1125
|
+
);
|
|
1126
|
+
if (!settled) return;
|
|
1127
|
+
setArchive(
|
|
1128
|
+
upsertArchive(storageKey, {
|
|
1129
|
+
token: conversationTokenRef.current,
|
|
1130
|
+
messages,
|
|
1131
|
+
focusProduct: focusProductRef.current
|
|
1132
|
+
})
|
|
1133
|
+
);
|
|
1134
|
+
}, [messages, persist, hydrated, storageKey]);
|
|
1067
1135
|
React.useEffect(() => {
|
|
1068
1136
|
let cancelled = false;
|
|
1069
1137
|
const load = async () => {
|
|
@@ -1349,6 +1417,13 @@ function useExploreChat(options = {}) {
|
|
|
1349
1417
|
void send(text);
|
|
1350
1418
|
}, [cancelPolling, send]);
|
|
1351
1419
|
const reset = React.useCallback(() => {
|
|
1420
|
+
setArchive(
|
|
1421
|
+
upsertArchive(storageKey, {
|
|
1422
|
+
token: conversationTokenRef.current,
|
|
1423
|
+
messages: messagesRef.current,
|
|
1424
|
+
focusProduct: focusProductRef.current
|
|
1425
|
+
})
|
|
1426
|
+
);
|
|
1352
1427
|
cancelPolling();
|
|
1353
1428
|
inFlightRef.current = false;
|
|
1354
1429
|
conversationTokenRef.current = null;
|
|
@@ -1361,11 +1436,48 @@ function useExploreChat(options = {}) {
|
|
|
1361
1436
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
1362
1437
|
if (typeof window !== "undefined") {
|
|
1363
1438
|
try {
|
|
1364
|
-
window.
|
|
1439
|
+
window.localStorage.removeItem(storageKey);
|
|
1365
1440
|
} catch {
|
|
1366
1441
|
}
|
|
1367
1442
|
}
|
|
1368
1443
|
}, [cancelPolling, storageKey]);
|
|
1444
|
+
const openConversation = React.useCallback(
|
|
1445
|
+
(token) => {
|
|
1446
|
+
const entry = readArchive(storageKey).find((e) => e.token === token);
|
|
1447
|
+
if (!entry) return;
|
|
1448
|
+
upsertArchive(storageKey, {
|
|
1449
|
+
token: conversationTokenRef.current,
|
|
1450
|
+
messages: messagesRef.current,
|
|
1451
|
+
focusProduct: focusProductRef.current
|
|
1452
|
+
});
|
|
1453
|
+
cancelPolling();
|
|
1454
|
+
inFlightRef.current = false;
|
|
1455
|
+
conversationTokenRef.current = entry.token;
|
|
1456
|
+
turnCountRef.current = entry.messages.filter(
|
|
1457
|
+
(m) => m.role === "USER"
|
|
1458
|
+
).length;
|
|
1459
|
+
setTurnCount(turnCountRef.current);
|
|
1460
|
+
setMessages(entry.messages);
|
|
1461
|
+
focusProductRef.current = entry.focusProduct;
|
|
1462
|
+
setFocusProductState(entry.focusProduct);
|
|
1463
|
+
setError(null);
|
|
1464
|
+
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
1465
|
+
setArchive(readArchive(storageKey));
|
|
1466
|
+
},
|
|
1467
|
+
[cancelPolling, storageKey]
|
|
1468
|
+
);
|
|
1469
|
+
const deleteConversation = React.useCallback(
|
|
1470
|
+
(token) => {
|
|
1471
|
+
const next = readArchive(storageKey).filter((e) => e.token !== token);
|
|
1472
|
+
writeArchive(storageKey, next);
|
|
1473
|
+
setArchive(next);
|
|
1474
|
+
},
|
|
1475
|
+
[storageKey]
|
|
1476
|
+
);
|
|
1477
|
+
const clearHistory = React.useCallback(() => {
|
|
1478
|
+
writeArchive(storageKey, []);
|
|
1479
|
+
setArchive([]);
|
|
1480
|
+
}, [storageKey]);
|
|
1369
1481
|
const setFocusProduct = React.useCallback((slug) => {
|
|
1370
1482
|
focusProductRef.current = slug;
|
|
1371
1483
|
setFocusProductState(slug);
|
|
@@ -1394,7 +1506,11 @@ function useExploreChat(options = {}) {
|
|
|
1394
1506
|
stop,
|
|
1395
1507
|
retry,
|
|
1396
1508
|
reset,
|
|
1397
|
-
canSend
|
|
1509
|
+
canSend,
|
|
1510
|
+
history: archive,
|
|
1511
|
+
openConversation,
|
|
1512
|
+
deleteConversation,
|
|
1513
|
+
clearHistory
|
|
1398
1514
|
};
|
|
1399
1515
|
}
|
|
1400
1516
|
var optimisticCounter = 0;
|
|
@@ -1402,6 +1518,182 @@ function makeOptimisticId() {
|
|
|
1402
1518
|
optimisticCounter += 1;
|
|
1403
1519
|
return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
|
|
1404
1520
|
}
|
|
1521
|
+
function getRecognitionCtor() {
|
|
1522
|
+
if (typeof window === "undefined") return null;
|
|
1523
|
+
const w = window;
|
|
1524
|
+
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1525
|
+
}
|
|
1526
|
+
var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
1527
|
+
async function ensureMicrophoneAccess() {
|
|
1528
|
+
const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
1529
|
+
if (!media?.getUserMedia) return { ok: true };
|
|
1530
|
+
try {
|
|
1531
|
+
const status = await navigator.permissions?.query({
|
|
1532
|
+
name: "microphone"
|
|
1533
|
+
});
|
|
1534
|
+
if (status?.state === "granted") return { ok: true };
|
|
1535
|
+
if (status?.state === "denied")
|
|
1536
|
+
return { ok: false, message: MIC_DENIED_MESSAGE };
|
|
1537
|
+
} catch {
|
|
1538
|
+
}
|
|
1539
|
+
try {
|
|
1540
|
+
const stream = await media.getUserMedia({ audio: true });
|
|
1541
|
+
for (const track of stream.getTracks()) track.stop();
|
|
1542
|
+
return { ok: true };
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
1545
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
1546
|
+
return { ok: false, message: MIC_DENIED_MESSAGE };
|
|
1547
|
+
}
|
|
1548
|
+
if (name === "NotFoundError" || name === "DevicesNotFoundError") {
|
|
1549
|
+
return { ok: false, message: "No microphone was found." };
|
|
1550
|
+
}
|
|
1551
|
+
return {
|
|
1552
|
+
ok: false,
|
|
1553
|
+
message: "Dictation could not start. You can type instead."
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
function describeError(code) {
|
|
1558
|
+
switch (code) {
|
|
1559
|
+
case "not-allowed":
|
|
1560
|
+
case "service-not-allowed":
|
|
1561
|
+
return MIC_DENIED_MESSAGE;
|
|
1562
|
+
case "no-speech":
|
|
1563
|
+
return "I didn't catch anything \u2014 try again a little closer to the mic.";
|
|
1564
|
+
case "audio-capture":
|
|
1565
|
+
return "No microphone was found.";
|
|
1566
|
+
case "network":
|
|
1567
|
+
return "Speech recognition needs a network connection.";
|
|
1568
|
+
case "aborted":
|
|
1569
|
+
return "";
|
|
1570
|
+
default:
|
|
1571
|
+
return "Dictation stopped unexpectedly. You can type instead.";
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
function useSpeechInput({
|
|
1575
|
+
onFinalTranscript,
|
|
1576
|
+
lang,
|
|
1577
|
+
onError
|
|
1578
|
+
}) {
|
|
1579
|
+
const [supported] = React.useState(() => getRecognitionCtor() !== null);
|
|
1580
|
+
const [listening, setListening] = React.useState(false);
|
|
1581
|
+
const [interim, setInterim] = React.useState("");
|
|
1582
|
+
const [error, setError] = React.useState(null);
|
|
1583
|
+
const recognitionRef = React.useRef(null);
|
|
1584
|
+
const startTokenRef = React.useRef(0);
|
|
1585
|
+
const beginRecognitionRef = React.useRef(null);
|
|
1586
|
+
const beginRecognition = React.useCallback(
|
|
1587
|
+
(Ctor, token) => {
|
|
1588
|
+
beginRecognitionRef.current?.(Ctor, token);
|
|
1589
|
+
},
|
|
1590
|
+
[]
|
|
1591
|
+
);
|
|
1592
|
+
const finalRef = React.useRef(onFinalTranscript);
|
|
1593
|
+
const errorRef = React.useRef(onError);
|
|
1594
|
+
finalRef.current = onFinalTranscript;
|
|
1595
|
+
errorRef.current = onError;
|
|
1596
|
+
const stop = React.useCallback(() => {
|
|
1597
|
+
startTokenRef.current += 1;
|
|
1598
|
+
setListening(false);
|
|
1599
|
+
setInterim("");
|
|
1600
|
+
const recognition = recognitionRef.current;
|
|
1601
|
+
if (!recognition) return;
|
|
1602
|
+
try {
|
|
1603
|
+
recognition.stop();
|
|
1604
|
+
} catch {
|
|
1605
|
+
}
|
|
1606
|
+
setListening(false);
|
|
1607
|
+
setInterim("");
|
|
1608
|
+
}, []);
|
|
1609
|
+
const start = React.useCallback(() => {
|
|
1610
|
+
const Ctor = getRecognitionCtor();
|
|
1611
|
+
if (!Ctor) return;
|
|
1612
|
+
if (recognitionRef.current) stop();
|
|
1613
|
+
setError(null);
|
|
1614
|
+
setListening(true);
|
|
1615
|
+
startTokenRef.current += 1;
|
|
1616
|
+
const token = startTokenRef.current;
|
|
1617
|
+
void ensureMicrophoneAccess().then((access) => {
|
|
1618
|
+
if (token !== startTokenRef.current) return;
|
|
1619
|
+
if (!access.ok) {
|
|
1620
|
+
setListening(false);
|
|
1621
|
+
setError(access.message);
|
|
1622
|
+
errorRef.current?.(access.message);
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
beginRecognition(Ctor, token);
|
|
1626
|
+
});
|
|
1627
|
+
}, [beginRecognition, stop]);
|
|
1628
|
+
const beginRecognitionImpl = React.useCallback(
|
|
1629
|
+
(Ctor, token) => {
|
|
1630
|
+
const recognition = new Ctor();
|
|
1631
|
+
recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
1632
|
+
recognition.continuous = true;
|
|
1633
|
+
recognition.interimResults = true;
|
|
1634
|
+
recognition.maxAlternatives = 1;
|
|
1635
|
+
recognition.onstart = () => {
|
|
1636
|
+
if (token !== startTokenRef.current) return;
|
|
1637
|
+
setError(null);
|
|
1638
|
+
setListening(true);
|
|
1639
|
+
};
|
|
1640
|
+
recognition.onresult = (event) => {
|
|
1641
|
+
let settled = "";
|
|
1642
|
+
let pending = "";
|
|
1643
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
1644
|
+
const result = event.results[i];
|
|
1645
|
+
if (!result) continue;
|
|
1646
|
+
const text = result[0]?.transcript ?? "";
|
|
1647
|
+
if (result.isFinal) settled += text;
|
|
1648
|
+
else pending += text;
|
|
1649
|
+
}
|
|
1650
|
+
setInterim(pending);
|
|
1651
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
1652
|
+
};
|
|
1653
|
+
recognition.onerror = (event) => {
|
|
1654
|
+
const message = describeError(event.error);
|
|
1655
|
+
setListening(false);
|
|
1656
|
+
setInterim("");
|
|
1657
|
+
if (message !== "") {
|
|
1658
|
+
setError(message);
|
|
1659
|
+
errorRef.current?.(message);
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
recognition.onend = () => {
|
|
1663
|
+
setListening(false);
|
|
1664
|
+
setInterim("");
|
|
1665
|
+
};
|
|
1666
|
+
recognitionRef.current = recognition;
|
|
1667
|
+
try {
|
|
1668
|
+
recognition.start();
|
|
1669
|
+
} catch {
|
|
1670
|
+
setListening(false);
|
|
1671
|
+
}
|
|
1672
|
+
},
|
|
1673
|
+
[lang]
|
|
1674
|
+
);
|
|
1675
|
+
beginRecognitionRef.current = beginRecognitionImpl;
|
|
1676
|
+
const toggle = React.useCallback(() => {
|
|
1677
|
+
if (listening) stop();
|
|
1678
|
+
else start();
|
|
1679
|
+
}, [listening, start, stop]);
|
|
1680
|
+
React.useEffect(
|
|
1681
|
+
() => () => {
|
|
1682
|
+
const recognition = recognitionRef.current;
|
|
1683
|
+
if (!recognition) return;
|
|
1684
|
+
recognition.onresult = null;
|
|
1685
|
+
recognition.onerror = null;
|
|
1686
|
+
recognition.onend = null;
|
|
1687
|
+
recognition.onstart = null;
|
|
1688
|
+
try {
|
|
1689
|
+
recognition.abort();
|
|
1690
|
+
} catch {
|
|
1691
|
+
}
|
|
1692
|
+
},
|
|
1693
|
+
[]
|
|
1694
|
+
);
|
|
1695
|
+
return { supported, listening, interim, error, start, stop, toggle };
|
|
1696
|
+
}
|
|
1405
1697
|
function canonicalFromLocation() {
|
|
1406
1698
|
if (typeof window === "undefined" || !window.location) return void 0;
|
|
1407
1699
|
const { origin, pathname } = window.location;
|
|
@@ -1621,10 +1913,33 @@ function initialOf(value, fallback) {
|
|
|
1621
1913
|
const source = (value || fallback).trim();
|
|
1622
1914
|
return source ? source.slice(0, 1).toUpperCase() : "?";
|
|
1623
1915
|
}
|
|
1916
|
+
function logoUrlOf(url) {
|
|
1917
|
+
try {
|
|
1918
|
+
const { origin, protocol } = new URL(url);
|
|
1919
|
+
if (protocol !== "https:" && protocol !== "http:") return null;
|
|
1920
|
+
return `${origin}/favicon.svg`;
|
|
1921
|
+
} catch {
|
|
1922
|
+
return null;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1624
1925
|
function ReferenceCard({ reference }) {
|
|
1625
1926
|
const [imageFailed, setImageFailed] = React.useState(false);
|
|
1927
|
+
const [logoFailed, setLogoFailed] = React.useState(false);
|
|
1626
1928
|
const host = hostnameOf(reference.url);
|
|
1929
|
+
const logoUrl = logoUrlOf(reference.url);
|
|
1627
1930
|
const showImage = Boolean(reference.imageUrl) && !imageFailed;
|
|
1931
|
+
const showLogo = Boolean(logoUrl) && !logoFailed;
|
|
1932
|
+
const letterPlate = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/25 via-primary/5 to-secondary", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-primary/70 sm:text-2xl", children: initialOf(reference.product, reference.title || host) }) });
|
|
1933
|
+
const logoImg = showLogo ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1934
|
+
"img",
|
|
1935
|
+
{
|
|
1936
|
+
src: logoUrl ?? "",
|
|
1937
|
+
alt: "",
|
|
1938
|
+
loading: "lazy",
|
|
1939
|
+
onError: () => setLogoFailed(true),
|
|
1940
|
+
className: "h-full w-full object-contain p-1.5 sm:p-0"
|
|
1941
|
+
}
|
|
1942
|
+
) : letterPlate;
|
|
1628
1943
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1629
1944
|
"a",
|
|
1630
1945
|
{
|
|
@@ -1632,9 +1947,10 @@ function ReferenceCard({ reference }) {
|
|
|
1632
1947
|
href: reference.url,
|
|
1633
1948
|
target: "_blank",
|
|
1634
1949
|
rel: "noopener noreferrer",
|
|
1635
|
-
className: "group flex flex-
|
|
1950
|
+
className: "group flex flex-row items-center gap-3 overflow-hidden rounded-xl border border-border bg-card p-2 transition-colors hover:border-primary/40 hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:flex-col sm:items-stretch sm:gap-0 sm:p-0",
|
|
1636
1951
|
children: [
|
|
1637
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative
|
|
1952
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-11 w-11 shrink-0 overflow-hidden rounded-lg bg-muted sm:hidden", children: logoImg }),
|
|
1953
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative hidden aspect-[16/9] w-full overflow-hidden bg-muted sm:block", children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1638
1954
|
"img",
|
|
1639
1955
|
{
|
|
1640
1956
|
src: reference.imageUrl ?? "",
|
|
@@ -1643,17 +1959,22 @@ function ReferenceCard({ reference }) {
|
|
|
1643
1959
|
onError: () => setImageFailed(true),
|
|
1644
1960
|
className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
1645
1961
|
}
|
|
1646
|
-
) : (
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1962
|
+
) : showLogo ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/15 via-transparent to-secondary/60 p-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1963
|
+
"img",
|
|
1964
|
+
{
|
|
1965
|
+
src: logoUrl ?? "",
|
|
1966
|
+
alt: "",
|
|
1967
|
+
loading: "lazy",
|
|
1968
|
+
onError: () => setLogoFailed(true),
|
|
1969
|
+
className: "max-h-full max-w-full object-contain"
|
|
1970
|
+
}
|
|
1971
|
+
) }) : letterPlate }),
|
|
1972
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 sm:gap-1.5 sm:p-3", children: [
|
|
1652
1973
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
|
|
1653
|
-
reference.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: reference.description }) : null,
|
|
1654
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
1655
|
-
reference.product ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground", children: reference.product }) : null,
|
|
1656
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "
|
|
1974
|
+
reference.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground max-sm:hidden", children: reference.description }) : null,
|
|
1975
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:mt-auto sm:pt-2", children: [
|
|
1976
|
+
reference.product ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground max-sm:hidden", children: reference.product }) : null,
|
|
1977
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground sm:ml-auto", children: [
|
|
1657
1978
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3 shrink-0" }),
|
|
1658
1979
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: host })
|
|
1659
1980
|
] })
|
|
@@ -1739,7 +2060,8 @@ function UserBubble({ message }) {
|
|
|
1739
2060
|
function AssistantBubble({
|
|
1740
2061
|
message,
|
|
1741
2062
|
activity,
|
|
1742
|
-
onNavigate
|
|
2063
|
+
onNavigate,
|
|
2064
|
+
anchorRef
|
|
1743
2065
|
}) {
|
|
1744
2066
|
const streaming = message.status === "PENDING" || message.status === "RUNNING";
|
|
1745
2067
|
const body = message.content || message.partialContent || "";
|
|
@@ -1747,9 +2069,10 @@ function AssistantBubble({
|
|
|
1747
2069
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1748
2070
|
"div",
|
|
1749
2071
|
{
|
|
2072
|
+
ref: anchorRef,
|
|
1750
2073
|
"data-boff-explore": "assistant",
|
|
1751
2074
|
"data-status": message.status,
|
|
1752
|
-
className: "flex gap-3",
|
|
2075
|
+
className: "flex scroll-mt-4 gap-3",
|
|
1753
2076
|
children: [
|
|
1754
2077
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1755
2078
|
"span",
|
|
@@ -1954,11 +2277,32 @@ function ExplorePage({
|
|
|
1954
2277
|
stop,
|
|
1955
2278
|
retry,
|
|
1956
2279
|
reset,
|
|
1957
|
-
canSend
|
|
2280
|
+
canSend,
|
|
2281
|
+
history,
|
|
2282
|
+
openConversation,
|
|
2283
|
+
deleteConversation,
|
|
2284
|
+
clearHistory
|
|
1958
2285
|
} = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
|
|
1959
2286
|
const [draft, setDraft] = React.useState("");
|
|
1960
2287
|
const textareaRef = React.useRef(null);
|
|
2288
|
+
const [historyOpen, setHistoryOpen] = React.useState(false);
|
|
2289
|
+
const speechStopRef = React.useRef(() => void 0);
|
|
2290
|
+
const stopDictation = React.useCallback(() => {
|
|
2291
|
+
speechStopRef.current();
|
|
2292
|
+
}, []);
|
|
2293
|
+
const speech = useSpeechInput({
|
|
2294
|
+
onFinalTranscript: (text) => {
|
|
2295
|
+
setDraft((current) => {
|
|
2296
|
+
const joined = current.trim() === "" ? text.trimStart() : `${current.trimEnd()} ${text.trim()}`;
|
|
2297
|
+
return joined;
|
|
2298
|
+
});
|
|
2299
|
+
textareaRef.current?.focus();
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
speechStopRef.current = speech.stop;
|
|
1961
2303
|
const scrollRef = React.useRef(null);
|
|
2304
|
+
const latestAssistantRef = React.useRef(null);
|
|
2305
|
+
const alignedForRef = React.useRef(null);
|
|
1962
2306
|
const stickToBottomRef = React.useRef(true);
|
|
1963
2307
|
const composingRef = React.useRef(false);
|
|
1964
2308
|
const busy = phase === "sending" || phase === "streaming";
|
|
@@ -1984,20 +2328,41 @@ function ExplorePage({
|
|
|
1984
2328
|
if (!el) return;
|
|
1985
2329
|
stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
1986
2330
|
}, []);
|
|
2331
|
+
const latestAssistantId = React.useMemo(() => {
|
|
2332
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
2333
|
+
const message = messages[i];
|
|
2334
|
+
if (message && message.role === "ASSISTANT") return message.id;
|
|
2335
|
+
}
|
|
2336
|
+
return null;
|
|
2337
|
+
}, [messages]);
|
|
2338
|
+
React.useEffect(() => {
|
|
2339
|
+
const el = scrollRef.current;
|
|
2340
|
+
const anchor = latestAssistantRef.current;
|
|
2341
|
+
if (!el || !anchor || !latestAssistantId) return;
|
|
2342
|
+
if (alignedForRef.current === latestAssistantId) return;
|
|
2343
|
+
if (!stickToBottomRef.current) return;
|
|
2344
|
+
if (anchor.offsetHeight === 0) return;
|
|
2345
|
+
alignedForRef.current = latestAssistantId;
|
|
2346
|
+
const delta = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
|
|
2347
|
+
el.scrollTop = Math.max(0, el.scrollTop + delta - 12);
|
|
2348
|
+
}, [latestAssistantId, messages]);
|
|
1987
2349
|
React.useEffect(() => {
|
|
1988
2350
|
if (!stickToBottomRef.current) return;
|
|
2351
|
+
if (latestAssistantId && alignedForRef.current === latestAssistantId)
|
|
2352
|
+
return;
|
|
1989
2353
|
const el = scrollRef.current;
|
|
1990
2354
|
if (!el) return;
|
|
1991
2355
|
el.scrollTop = el.scrollHeight;
|
|
1992
|
-
}, [
|
|
2356
|
+
}, [activity, latestAssistantId, messages]);
|
|
1993
2357
|
const submitDraft = React.useCallback(() => {
|
|
1994
2358
|
const text = draft.trim();
|
|
1995
2359
|
if (!text || overLimit || busy || !canSend) return;
|
|
2360
|
+
stopDictation();
|
|
1996
2361
|
setDraft("");
|
|
1997
2362
|
stickToBottomRef.current = true;
|
|
1998
2363
|
void send(text);
|
|
1999
2364
|
textareaRef.current?.focus();
|
|
2000
|
-
}, [busy, canSend, draft, overLimit, send]);
|
|
2365
|
+
}, [busy, canSend, draft, overLimit, send, stopDictation]);
|
|
2001
2366
|
const sendPrompt = React.useCallback(
|
|
2002
2367
|
(prompt) => {
|
|
2003
2368
|
if (!canSend || busy) return;
|
|
@@ -2070,8 +2435,96 @@ function ExplorePage({
|
|
|
2070
2435
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "New chat" })
|
|
2071
2436
|
]
|
|
2072
2437
|
}
|
|
2073
|
-
)
|
|
2438
|
+
),
|
|
2439
|
+
history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2440
|
+
Button,
|
|
2441
|
+
{
|
|
2442
|
+
"data-boff-explore": "history-toggle",
|
|
2443
|
+
type: "button",
|
|
2444
|
+
size: "sm",
|
|
2445
|
+
variant: "ghost",
|
|
2446
|
+
className: "shrink-0 text-muted-foreground",
|
|
2447
|
+
"aria-expanded": historyOpen,
|
|
2448
|
+
onClick: () => {
|
|
2449
|
+
setHistoryOpen((open) => !open);
|
|
2450
|
+
},
|
|
2451
|
+
children: [
|
|
2452
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.History, { className: "h-3.5 w-3.5" }),
|
|
2453
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden sm:inline", children: [
|
|
2454
|
+
"History (",
|
|
2455
|
+
history.length,
|
|
2456
|
+
")"
|
|
2457
|
+
] })
|
|
2458
|
+
]
|
|
2459
|
+
}
|
|
2460
|
+
) : null
|
|
2074
2461
|
] }) : null,
|
|
2462
|
+
historyOpen && history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2463
|
+
"div",
|
|
2464
|
+
{
|
|
2465
|
+
"data-boff-explore": "history-panel",
|
|
2466
|
+
className: "border-b border-border bg-muted/30 px-4 py-3",
|
|
2467
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl", children: [
|
|
2468
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
|
|
2469
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Your recent conversations" }),
|
|
2470
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2471
|
+
Button,
|
|
2472
|
+
{
|
|
2473
|
+
"data-boff-explore": "history-clear",
|
|
2474
|
+
type: "button",
|
|
2475
|
+
size: "sm",
|
|
2476
|
+
variant: "ghost",
|
|
2477
|
+
className: "h-7 shrink-0 text-xs text-muted-foreground hover:text-destructive",
|
|
2478
|
+
onClick: () => {
|
|
2479
|
+
clearHistory();
|
|
2480
|
+
setHistoryOpen(false);
|
|
2481
|
+
},
|
|
2482
|
+
children: [
|
|
2483
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }),
|
|
2484
|
+
"Clear history"
|
|
2485
|
+
]
|
|
2486
|
+
}
|
|
2487
|
+
)
|
|
2488
|
+
] }),
|
|
2489
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1", children: history.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [
|
|
2490
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2491
|
+
"button",
|
|
2492
|
+
{
|
|
2493
|
+
"data-boff-explore": "history-item",
|
|
2494
|
+
type: "button",
|
|
2495
|
+
className: "flex-1 truncate rounded-md px-2 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
2496
|
+
onClick: () => {
|
|
2497
|
+
openConversation(entry.token);
|
|
2498
|
+
setHistoryOpen(false);
|
|
2499
|
+
},
|
|
2500
|
+
children: [
|
|
2501
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: entry.title }),
|
|
2502
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
|
|
2503
|
+
entry.messageCount,
|
|
2504
|
+
" message",
|
|
2505
|
+
entry.messageCount === 1 ? "" : "s"
|
|
2506
|
+
] })
|
|
2507
|
+
]
|
|
2508
|
+
}
|
|
2509
|
+
),
|
|
2510
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2511
|
+
Button,
|
|
2512
|
+
{
|
|
2513
|
+
type: "button",
|
|
2514
|
+
size: "icon",
|
|
2515
|
+
variant: "ghost",
|
|
2516
|
+
className: "h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive",
|
|
2517
|
+
"aria-label": `Delete conversation: ${entry.title}`,
|
|
2518
|
+
onClick: () => {
|
|
2519
|
+
deleteConversation(entry.token);
|
|
2520
|
+
},
|
|
2521
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
|
|
2522
|
+
}
|
|
2523
|
+
)
|
|
2524
|
+
] }, entry.token)) })
|
|
2525
|
+
] })
|
|
2526
|
+
}
|
|
2527
|
+
) : null,
|
|
2075
2528
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2076
2529
|
"div",
|
|
2077
2530
|
{
|
|
@@ -2100,7 +2553,8 @@ function ExplorePage({
|
|
|
2100
2553
|
{
|
|
2101
2554
|
message,
|
|
2102
2555
|
activity,
|
|
2103
|
-
onNavigate
|
|
2556
|
+
onNavigate,
|
|
2557
|
+
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0
|
|
2104
2558
|
},
|
|
2105
2559
|
message.id
|
|
2106
2560
|
)
|
|
@@ -2146,6 +2600,23 @@ function ExplorePage({
|
|
|
2146
2600
|
className: "max-h-[200px] min-h-[44px] flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
|
|
2147
2601
|
}
|
|
2148
2602
|
),
|
|
2603
|
+
speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2604
|
+
Button,
|
|
2605
|
+
{
|
|
2606
|
+
"data-boff-explore": "mic",
|
|
2607
|
+
"data-listening": speech.listening ? "true" : "false",
|
|
2608
|
+
type: "button",
|
|
2609
|
+
size: "icon",
|
|
2610
|
+
variant: speech.listening ? "default" : "ghost",
|
|
2611
|
+
disabled: composerDisabled,
|
|
2612
|
+
"aria-label": speech.listening ? "Stop dictating" : "Dictate your question",
|
|
2613
|
+
"aria-pressed": speech.listening,
|
|
2614
|
+
title: speech.listening ? "Stop dictating" : "Dictate your question",
|
|
2615
|
+
onClick: speech.toggle,
|
|
2616
|
+
className: cn(speech.listening && "animate-pulse"),
|
|
2617
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-4 w-4" })
|
|
2618
|
+
}
|
|
2619
|
+
) : null,
|
|
2149
2620
|
busy ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2150
2621
|
Button,
|
|
2151
2622
|
{
|
|
@@ -2171,6 +2642,29 @@ function ExplorePage({
|
|
|
2171
2642
|
]
|
|
2172
2643
|
}
|
|
2173
2644
|
),
|
|
2645
|
+
speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2646
|
+
"p",
|
|
2647
|
+
{
|
|
2648
|
+
"data-boff-explore": "dictation",
|
|
2649
|
+
className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
|
|
2650
|
+
"aria-live": "polite",
|
|
2651
|
+
children: [
|
|
2652
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
|
|
2653
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
|
|
2654
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
|
|
2655
|
+
] }),
|
|
2656
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
|
|
2657
|
+
]
|
|
2658
|
+
}
|
|
2659
|
+
) : null,
|
|
2660
|
+
speech.error !== null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2661
|
+
"p",
|
|
2662
|
+
{
|
|
2663
|
+
"data-boff-explore": "dictation-error",
|
|
2664
|
+
className: "mt-2 text-xs text-destructive",
|
|
2665
|
+
children: speech.error
|
|
2666
|
+
}
|
|
2667
|
+
) : null,
|
|
2174
2668
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
2175
2669
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
|
|
2176
2670
|
remainingToday !== null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-boff-explore": "remaining", children: [
|
|
@@ -2194,39 +2688,48 @@ function ExplorePage({
|
|
|
2194
2688
|
}
|
|
2195
2689
|
)
|
|
2196
2690
|
] }),
|
|
2197
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2198
|
-
"
|
|
2199
|
-
|
|
2200
|
-
" ",
|
|
2201
|
-
"
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
"
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2691
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2692
|
+
"p",
|
|
2693
|
+
{
|
|
2694
|
+
"data-boff-explore": "disclaimer",
|
|
2695
|
+
className: "mt-2 text-xs leading-relaxed text-muted-foreground",
|
|
2696
|
+
children: [
|
|
2697
|
+
"Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
|
|
2698
|
+
" ",
|
|
2699
|
+
"Conversations are saved in this browser so you can come back to them, and are also stored on our servers to help us improve these answers. Please don't share personal or confidential information.",
|
|
2700
|
+
captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2701
|
+
" ",
|
|
2702
|
+
"This site is protected by reCAPTCHA; the Google",
|
|
2703
|
+
" ",
|
|
2704
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2705
|
+
"a",
|
|
2706
|
+
{
|
|
2707
|
+
href: "https://policies.google.com/privacy",
|
|
2708
|
+
target: "_blank",
|
|
2709
|
+
rel: "noopener noreferrer",
|
|
2710
|
+
className: "underline underline-offset-2 hover:text-foreground",
|
|
2711
|
+
children: "Privacy Policy"
|
|
2712
|
+
}
|
|
2713
|
+
),
|
|
2714
|
+
" ",
|
|
2715
|
+
"and",
|
|
2716
|
+
" ",
|
|
2717
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2718
|
+
"a",
|
|
2719
|
+
{
|
|
2720
|
+
href: "https://policies.google.com/terms",
|
|
2721
|
+
target: "_blank",
|
|
2722
|
+
rel: "noopener noreferrer",
|
|
2723
|
+
className: "underline underline-offset-2 hover:text-foreground",
|
|
2724
|
+
children: "Terms of Service"
|
|
2725
|
+
}
|
|
2726
|
+
),
|
|
2727
|
+
" ",
|
|
2728
|
+
"apply."
|
|
2729
|
+
] }) : null
|
|
2730
|
+
]
|
|
2731
|
+
}
|
|
2732
|
+
)
|
|
2230
2733
|
]
|
|
2231
2734
|
}
|
|
2232
2735
|
) })
|
|
@@ -2236,5 +2739,6 @@ function ExplorePage({
|
|
|
2236
2739
|
}
|
|
2237
2740
|
|
|
2238
2741
|
exports.ExplorePage = ExplorePage;
|
|
2742
|
+
exports.logoUrlOf = logoUrlOf;
|
|
2239
2743
|
//# sourceMappingURL=explore.js.map
|
|
2240
2744
|
//# sourceMappingURL=explore.js.map
|