@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.
- package/dist/components/explore.js +567 -200
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +569 -202
- package/dist/components/explore.mjs.map +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +139 -69
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +139 -69
- 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 +567 -200
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +567 -200
- package/dist/index.mjs.map +1 -1
- package/dist/{use-explore-chat-DhvZhH8f.d.mts → use-explore-chat-8eL7WdLc.d.mts} +16 -2
- package/dist/{use-explore-chat-DhvZhH8f.d.ts → use-explore-chat-8eL7WdLc.d.ts} +16 -2
- package/package.json +1 -1
|
@@ -489,6 +489,7 @@ var EXPLORE_MESSAGE_FIELDS = (
|
|
|
489
489
|
description
|
|
490
490
|
product
|
|
491
491
|
imageUrl
|
|
492
|
+
index
|
|
492
493
|
}
|
|
493
494
|
ctas {
|
|
494
495
|
kind
|
|
@@ -496,6 +497,8 @@ var EXPLORE_MESSAGE_FIELDS = (
|
|
|
496
497
|
url
|
|
497
498
|
product
|
|
498
499
|
}
|
|
500
|
+
followUps
|
|
501
|
+
answered
|
|
499
502
|
errorCode
|
|
500
503
|
createdAt
|
|
501
504
|
completedAt
|
|
@@ -561,6 +564,16 @@ var SEND_EXPLORE_MESSAGE_MUTATION = (
|
|
|
561
564
|
}
|
|
562
565
|
`
|
|
563
566
|
);
|
|
567
|
+
var RECORD_EXPLORE_CLICK_MUTATION = (
|
|
568
|
+
/* GraphQL */
|
|
569
|
+
`
|
|
570
|
+
mutation RecordExploreClick($input: RecordExploreClickInput!) {
|
|
571
|
+
recordExploreClick(input: $input) {
|
|
572
|
+
recorded
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
`
|
|
576
|
+
);
|
|
564
577
|
|
|
565
578
|
// src/data/products.ts
|
|
566
579
|
var ECOSYSTEM_PRODUCTS = [
|
|
@@ -1493,6 +1506,20 @@ function useExploreChat(options = {}) {
|
|
|
1493
1506
|
return null;
|
|
1494
1507
|
}, [messages]);
|
|
1495
1508
|
const canSend = (phase === "ready" || phase === "error") && catalog.enabled && turnCount < catalog.limits.maxTurnsPerConversation;
|
|
1509
|
+
const recordClick = React.useCallback(
|
|
1510
|
+
(messageId, kind, url) => {
|
|
1511
|
+
const token = conversationTokenRef.current;
|
|
1512
|
+
if (!token) return;
|
|
1513
|
+
try {
|
|
1514
|
+
void client.mutate(RECORD_EXPLORE_CLICK_MUTATION, {
|
|
1515
|
+
input: { conversationToken: token, messageId, kind, url }
|
|
1516
|
+
}).catch(() => {
|
|
1517
|
+
});
|
|
1518
|
+
} catch {
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
[client]
|
|
1522
|
+
);
|
|
1496
1523
|
return {
|
|
1497
1524
|
phase,
|
|
1498
1525
|
catalog,
|
|
@@ -1510,7 +1537,8 @@ function useExploreChat(options = {}) {
|
|
|
1510
1537
|
history: archive,
|
|
1511
1538
|
openConversation,
|
|
1512
1539
|
deleteConversation,
|
|
1513
|
-
clearHistory
|
|
1540
|
+
clearHistory,
|
|
1541
|
+
recordClick
|
|
1514
1542
|
};
|
|
1515
1543
|
}
|
|
1516
1544
|
var optimisticCounter = 0;
|
|
@@ -1523,21 +1551,37 @@ function getRecognitionCtor() {
|
|
|
1523
1551
|
const w = window;
|
|
1524
1552
|
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1525
1553
|
}
|
|
1554
|
+
function isTouchDevice() {
|
|
1555
|
+
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
1556
|
+
try {
|
|
1557
|
+
return window.matchMedia("(pointer: coarse)").matches;
|
|
1558
|
+
} catch {
|
|
1559
|
+
return false;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1526
1562
|
var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
1563
|
+
var MIC_UNAVAILABLE_MESSAGE = "Dictation isn't available in this browser. You can type instead.";
|
|
1564
|
+
var RELEASE_SETTLE_MS = 250;
|
|
1565
|
+
var RETRY_DELAY_MS = 350;
|
|
1566
|
+
var sleep = (ms) => new Promise((resolve) => {
|
|
1567
|
+
setTimeout(resolve, ms);
|
|
1568
|
+
});
|
|
1527
1569
|
async function ensureMicrophoneAccess() {
|
|
1528
1570
|
const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
1529
|
-
if (!media?.getUserMedia)
|
|
1571
|
+
if (!media?.getUserMedia)
|
|
1572
|
+
return { ok: true, confirmed: false, primed: false };
|
|
1530
1573
|
try {
|
|
1531
1574
|
const status = await navigator.permissions?.query({
|
|
1532
1575
|
name: "microphone"
|
|
1533
1576
|
});
|
|
1534
|
-
if (status?.state === "granted")
|
|
1577
|
+
if (status?.state === "granted")
|
|
1578
|
+
return { ok: true, confirmed: true, primed: false };
|
|
1535
1579
|
} catch {
|
|
1536
1580
|
}
|
|
1537
1581
|
try {
|
|
1538
1582
|
const stream = await media.getUserMedia({ audio: true });
|
|
1539
1583
|
for (const track of stream.getTracks()) track.stop();
|
|
1540
|
-
return { ok: true, confirmed: true };
|
|
1584
|
+
return { ok: true, confirmed: true, primed: true };
|
|
1541
1585
|
} catch (error) {
|
|
1542
1586
|
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
1543
1587
|
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
@@ -1581,19 +1625,17 @@ function useSpeechInput({
|
|
|
1581
1625
|
const recognitionRef = React.useRef(null);
|
|
1582
1626
|
const startTokenRef = React.useRef(0);
|
|
1583
1627
|
const micConfirmedRef = React.useRef(false);
|
|
1584
|
-
const
|
|
1585
|
-
const
|
|
1586
|
-
(Ctor, token) => {
|
|
1587
|
-
beginRecognitionRef.current?.(Ctor, token);
|
|
1588
|
-
},
|
|
1589
|
-
[]
|
|
1590
|
-
);
|
|
1628
|
+
const wantsListeningRef = React.useRef(false);
|
|
1629
|
+
const retriedRef = React.useRef(false);
|
|
1591
1630
|
const finalRef = React.useRef(onFinalTranscript);
|
|
1592
1631
|
const errorRef = React.useRef(onError);
|
|
1593
1632
|
finalRef.current = onFinalTranscript;
|
|
1594
1633
|
errorRef.current = onError;
|
|
1634
|
+
const langRef = React.useRef(lang);
|
|
1635
|
+
langRef.current = lang;
|
|
1595
1636
|
const stop = React.useCallback(() => {
|
|
1596
1637
|
startTokenRef.current += 1;
|
|
1638
|
+
wantsListeningRef.current = false;
|
|
1597
1639
|
setListening(false);
|
|
1598
1640
|
setInterim("");
|
|
1599
1641
|
const recognition = recognitionRef.current;
|
|
@@ -1602,83 +1644,111 @@ function useSpeechInput({
|
|
|
1602
1644
|
recognition.stop();
|
|
1603
1645
|
} catch {
|
|
1604
1646
|
}
|
|
1605
|
-
setListening(false);
|
|
1606
|
-
setInterim("");
|
|
1607
1647
|
}, []);
|
|
1608
|
-
const
|
|
1648
|
+
const openSession = React.useCallback((token) => {
|
|
1609
1649
|
const Ctor = getRecognitionCtor();
|
|
1610
|
-
if (!Ctor) return;
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1650
|
+
if (!Ctor || token !== startTokenRef.current) return;
|
|
1651
|
+
const recognition = new Ctor();
|
|
1652
|
+
recognition.lang = langRef.current ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
1653
|
+
recognition.continuous = !isTouchDevice();
|
|
1654
|
+
recognition.interimResults = true;
|
|
1655
|
+
recognition.maxAlternatives = 1;
|
|
1656
|
+
recognition.onstart = () => {
|
|
1657
|
+
if (token !== startTokenRef.current) return;
|
|
1658
|
+
setError(null);
|
|
1659
|
+
setListening(true);
|
|
1660
|
+
};
|
|
1661
|
+
recognition.onresult = (event) => {
|
|
1662
|
+
if (token !== startTokenRef.current) return;
|
|
1663
|
+
let settled = "";
|
|
1664
|
+
let pending = "";
|
|
1665
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
1666
|
+
const result = event.results[i];
|
|
1667
|
+
if (!result) continue;
|
|
1668
|
+
const text = result[0]?.transcript ?? "";
|
|
1669
|
+
if (result.isFinal) settled += text;
|
|
1670
|
+
else pending += text;
|
|
1671
|
+
}
|
|
1672
|
+
setInterim(pending);
|
|
1673
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
1674
|
+
};
|
|
1675
|
+
recognition.onerror = (event) => {
|
|
1676
|
+
if (token !== startTokenRef.current) return;
|
|
1677
|
+
if ((event.error === "not-allowed" || event.error === "service-not-allowed") && micConfirmedRef.current && !retriedRef.current) {
|
|
1678
|
+
retriedRef.current = true;
|
|
1679
|
+
void sleep(RETRY_DELAY_MS).then(() => {
|
|
1680
|
+
if (token !== startTokenRef.current || !wantsListeningRef.current)
|
|
1681
|
+
return;
|
|
1682
|
+
openSessionRef.current?.(token);
|
|
1683
|
+
});
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? MIC_UNAVAILABLE_MESSAGE : describeError(event.error);
|
|
1687
|
+
wantsListeningRef.current = false;
|
|
1688
|
+
setListening(false);
|
|
1689
|
+
setInterim("");
|
|
1690
|
+
if (message !== "") {
|
|
1691
|
+
setError(message);
|
|
1692
|
+
errorRef.current?.(message);
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
recognition.onend = () => {
|
|
1696
|
+
if (token !== startTokenRef.current) return;
|
|
1697
|
+
setInterim("");
|
|
1698
|
+
if (wantsListeningRef.current && isTouchDevice()) {
|
|
1699
|
+
void sleep(120).then(() => {
|
|
1700
|
+
if (token !== startTokenRef.current || !wantsListeningRef.current)
|
|
1701
|
+
return;
|
|
1702
|
+
openSessionRef.current?.(token);
|
|
1703
|
+
});
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
setListening(false);
|
|
1707
|
+
};
|
|
1708
|
+
recognitionRef.current = recognition;
|
|
1709
|
+
try {
|
|
1710
|
+
recognition.start();
|
|
1711
|
+
} catch {
|
|
1712
|
+
wantsListeningRef.current = false;
|
|
1713
|
+
setListening(false);
|
|
1714
|
+
}
|
|
1715
|
+
}, []);
|
|
1716
|
+
const openSessionRef = React.useRef(null);
|
|
1717
|
+
openSessionRef.current = openSession;
|
|
1718
|
+
const start = React.useCallback(() => {
|
|
1719
|
+
if (!getRecognitionCtor()) return;
|
|
1614
1720
|
startTokenRef.current += 1;
|
|
1615
1721
|
const token = startTokenRef.current;
|
|
1616
|
-
|
|
1722
|
+
wantsListeningRef.current = true;
|
|
1723
|
+
retriedRef.current = false;
|
|
1724
|
+
micConfirmedRef.current = false;
|
|
1725
|
+
setError(null);
|
|
1726
|
+
setListening(true);
|
|
1727
|
+
void ensureMicrophoneAccess().then(async (access) => {
|
|
1617
1728
|
if (token !== startTokenRef.current) return;
|
|
1618
|
-
micConfirmedRef.current = access.ok && access.confirmed;
|
|
1619
1729
|
if (!access.ok) {
|
|
1730
|
+
wantsListeningRef.current = false;
|
|
1620
1731
|
setListening(false);
|
|
1621
1732
|
setError(access.message);
|
|
1622
1733
|
errorRef.current?.(access.message);
|
|
1623
1734
|
return;
|
|
1624
1735
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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 = () => {
|
|
1736
|
+
micConfirmedRef.current = access.confirmed;
|
|
1737
|
+
if (access.primed) {
|
|
1738
|
+
await sleep(RELEASE_SETTLE_MS);
|
|
1636
1739
|
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 = 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);
|
|
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
1740
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
);
|
|
1675
|
-
beginRecognitionRef.current = beginRecognitionImpl;
|
|
1741
|
+
openSessionRef.current?.(token);
|
|
1742
|
+
});
|
|
1743
|
+
}, []);
|
|
1676
1744
|
const toggle = React.useCallback(() => {
|
|
1677
1745
|
if (listening) stop();
|
|
1678
1746
|
else start();
|
|
1679
1747
|
}, [listening, start, stop]);
|
|
1680
1748
|
React.useEffect(
|
|
1681
1749
|
() => () => {
|
|
1750
|
+
startTokenRef.current += 1;
|
|
1751
|
+
wantsListeningRef.current = false;
|
|
1682
1752
|
const recognition = recognitionRef.current;
|
|
1683
1753
|
if (!recognition) return;
|
|
1684
1754
|
recognition.onresult = null;
|
|
@@ -1862,6 +1932,50 @@ var EXPLORE_CSS = `
|
|
|
1862
1932
|
border-radius: 9999px;
|
|
1863
1933
|
background-clip: content-box;
|
|
1864
1934
|
}
|
|
1935
|
+
|
|
1936
|
+
/* The product strip scrolls inside itself; a visible scrollbar would read as a broken
|
|
1937
|
+
layout rather than an affordance, and reserving its gutter shifts the chips. */
|
|
1938
|
+
.boff-explore-strip {
|
|
1939
|
+
scrollbar-width: none;
|
|
1940
|
+
-ms-overflow-style: none;
|
|
1941
|
+
}
|
|
1942
|
+
.boff-explore-strip::-webkit-scrollbar { display: none; }
|
|
1943
|
+
|
|
1944
|
+
@keyframes boff-explore-fade-in {
|
|
1945
|
+
from { opacity: 0; transform: translateY(2px); }
|
|
1946
|
+
to { opacity: 1; transform: none; }
|
|
1947
|
+
}
|
|
1948
|
+
.boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
|
|
1949
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1950
|
+
.boff-explore-fade { animation: none; }
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
1954
|
+
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
1955
|
+
WHOLE document horizontally scrollable, not just the bubble. */
|
|
1956
|
+
.boff-explore-body,
|
|
1957
|
+
.boff-explore-body p,
|
|
1958
|
+
.boff-explore-body li,
|
|
1959
|
+
.boff-explore-body a,
|
|
1960
|
+
.boff-explore-body h1,
|
|
1961
|
+
.boff-explore-body h2,
|
|
1962
|
+
.boff-explore-body h3 {
|
|
1963
|
+
overflow-wrap: anywhere;
|
|
1964
|
+
word-break: break-word;
|
|
1965
|
+
}
|
|
1966
|
+
.boff-explore-body pre {
|
|
1967
|
+
overflow-x: auto;
|
|
1968
|
+
max-width: 100%;
|
|
1969
|
+
}
|
|
1970
|
+
.boff-explore-body table {
|
|
1971
|
+
display: block;
|
|
1972
|
+
overflow-x: auto;
|
|
1973
|
+
max-width: 100%;
|
|
1974
|
+
}
|
|
1975
|
+
.boff-explore-body img {
|
|
1976
|
+
max-width: 100%;
|
|
1977
|
+
height: auto;
|
|
1978
|
+
}
|
|
1865
1979
|
`;
|
|
1866
1980
|
function ExploreStyles() {
|
|
1867
1981
|
return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
|
|
@@ -1883,7 +1997,6 @@ var CAPABILITIES = [
|
|
|
1883
1997
|
body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
|
|
1884
1998
|
}
|
|
1885
1999
|
];
|
|
1886
|
-
var PRODUCT_CHIP_PREVIEW = 12;
|
|
1887
2000
|
var CTA_VARIANTS = {
|
|
1888
2001
|
CONTACT: "default",
|
|
1889
2002
|
WAITLIST: "default",
|
|
@@ -1922,7 +2035,10 @@ function logoUrlOf(url) {
|
|
|
1922
2035
|
return null;
|
|
1923
2036
|
}
|
|
1924
2037
|
}
|
|
1925
|
-
function ReferenceCard({
|
|
2038
|
+
function ReferenceCard({
|
|
2039
|
+
reference,
|
|
2040
|
+
onFollow
|
|
2041
|
+
}) {
|
|
1926
2042
|
const [imageFailed, setImageFailed] = React.useState(false);
|
|
1927
2043
|
const [logoFailed, setLogoFailed] = React.useState(false);
|
|
1928
2044
|
const host = hostnameOf(reference.url);
|
|
@@ -1944,9 +2060,11 @@ function ReferenceCard({ reference }) {
|
|
|
1944
2060
|
"a",
|
|
1945
2061
|
{
|
|
1946
2062
|
"data-boff-explore": "reference",
|
|
2063
|
+
"data-index": reference.index ?? void 0,
|
|
1947
2064
|
href: reference.url,
|
|
1948
2065
|
target: "_blank",
|
|
1949
2066
|
rel: "noopener noreferrer",
|
|
2067
|
+
onClick: () => onFollow?.(reference.url),
|
|
1950
2068
|
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",
|
|
1951
2069
|
children: [
|
|
1952
2070
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-11 w-11 shrink-0 overflow-hidden rounded-lg bg-muted sm:hidden", children: logoImg }),
|
|
@@ -1970,7 +2088,10 @@ function ReferenceCard({ reference }) {
|
|
|
1970
2088
|
}
|
|
1971
2089
|
) }) : letterPlate }),
|
|
1972
2090
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 sm:gap-1.5 sm:p-3", children: [
|
|
1973
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2091
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: [
|
|
2092
|
+
reference.index ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1.5 inline-flex h-4 min-w-4 items-center justify-center rounded bg-primary/10 px-1 text-[10px] font-bold text-primary", children: reference.index }) : null,
|
|
2093
|
+
reference.title || host || reference.url
|
|
2094
|
+
] }),
|
|
1974
2095
|
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
2096
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:mt-auto sm:pt-2", children: [
|
|
1976
2097
|
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,
|
|
@@ -1986,7 +2107,8 @@ function ReferenceCard({ reference }) {
|
|
|
1986
2107
|
}
|
|
1987
2108
|
function CtaButton({
|
|
1988
2109
|
cta,
|
|
1989
|
-
onNavigate
|
|
2110
|
+
onNavigate,
|
|
2111
|
+
onFollow
|
|
1990
2112
|
}) {
|
|
1991
2113
|
const variant = CTA_VARIANTS[cta.kind] ?? "outline";
|
|
1992
2114
|
const internal = isInternalPath(cta.url);
|
|
@@ -1999,7 +2121,10 @@ function CtaButton({
|
|
|
1999
2121
|
type: "button",
|
|
2000
2122
|
size: "sm",
|
|
2001
2123
|
variant,
|
|
2002
|
-
onClick: () =>
|
|
2124
|
+
onClick: () => {
|
|
2125
|
+
onFollow?.(cta.url);
|
|
2126
|
+
onNavigate(cta.url);
|
|
2127
|
+
},
|
|
2003
2128
|
children: cta.label
|
|
2004
2129
|
}
|
|
2005
2130
|
);
|
|
@@ -2010,6 +2135,7 @@ function CtaButton({
|
|
|
2010
2135
|
"data-boff-explore": "cta",
|
|
2011
2136
|
"data-kind": cta.kind,
|
|
2012
2137
|
href: cta.url,
|
|
2138
|
+
onClick: () => onFollow?.(cta.url),
|
|
2013
2139
|
...internal ? {} : { target: "_blank", rel: "noopener noreferrer" },
|
|
2014
2140
|
children: [
|
|
2015
2141
|
cta.label,
|
|
@@ -2061,7 +2187,9 @@ function AssistantBubble({
|
|
|
2061
2187
|
message,
|
|
2062
2188
|
activity,
|
|
2063
2189
|
onNavigate,
|
|
2064
|
-
anchorRef
|
|
2190
|
+
anchorRef,
|
|
2191
|
+
onFollow,
|
|
2192
|
+
onFollowUp
|
|
2065
2193
|
}) {
|
|
2066
2194
|
const streaming = message.status === "PENDING" || message.status === "RUNNING";
|
|
2067
2195
|
const body = message.content || message.partialContent || "";
|
|
@@ -2083,24 +2211,77 @@ function AssistantBubble({
|
|
|
2083
2211
|
}
|
|
2084
2212
|
),
|
|
2085
2213
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
|
|
2214
|
+
message.status === "COMPLETED" && message.answered === false ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2215
|
+
"div",
|
|
2216
|
+
{
|
|
2217
|
+
"data-boff-explore": "no-answer",
|
|
2218
|
+
className: "flex items-start gap-2 rounded-xl border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs leading-relaxed text-foreground",
|
|
2219
|
+
children: [
|
|
2220
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.TriangleAlert, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-600 dark:text-amber-400" }),
|
|
2221
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
2222
|
+
"I couldn't find this in our documentation, so the answer below is not grounded in a source. Try rephrasing, or",
|
|
2223
|
+
" ",
|
|
2224
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2225
|
+
"a",
|
|
2226
|
+
{
|
|
2227
|
+
href: "/contact",
|
|
2228
|
+
className: "underline underline-offset-2 hover:text-primary",
|
|
2229
|
+
onClick: (event) => {
|
|
2230
|
+
if (!onNavigate) return;
|
|
2231
|
+
event.preventDefault();
|
|
2232
|
+
onNavigate("/contact");
|
|
2233
|
+
},
|
|
2234
|
+
children: "ask a human"
|
|
2235
|
+
}
|
|
2236
|
+
),
|
|
2237
|
+
"."
|
|
2238
|
+
] })
|
|
2239
|
+
]
|
|
2240
|
+
}
|
|
2241
|
+
) : null,
|
|
2086
2242
|
body ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
|
|
2087
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: body }) }),
|
|
2243
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: body }) }),
|
|
2088
2244
|
streaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
|
|
2089
2245
|
] }) : null,
|
|
2090
2246
|
streaming ? /* @__PURE__ */ jsxRuntime.jsx(ThinkingIndicator, { activity }) : null,
|
|
2091
2247
|
failed && !body ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-2xl rounded-tl-md border border-border bg-muted px-4 py-3 text-sm text-muted-foreground", children: "That answer didn't come through. Try asking again." }) : null,
|
|
2092
2248
|
message.references.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2093
2249
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Sources" }),
|
|
2094
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: message.references.map((reference) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2250
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: message.references.map((reference) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2251
|
+
ReferenceCard,
|
|
2252
|
+
{
|
|
2253
|
+
reference,
|
|
2254
|
+
onFollow: (url) => onFollow?.("REFERENCE", url)
|
|
2255
|
+
},
|
|
2256
|
+
reference.url
|
|
2257
|
+
)) })
|
|
2095
2258
|
] }) : null,
|
|
2096
2259
|
message.ctas.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 pt-0.5", children: message.ctas.map((cta) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2097
2260
|
CtaButton,
|
|
2098
2261
|
{
|
|
2099
2262
|
cta,
|
|
2100
|
-
onNavigate
|
|
2263
|
+
onNavigate,
|
|
2264
|
+
onFollow: (url) => onFollow?.("CTA", url)
|
|
2101
2265
|
},
|
|
2102
2266
|
`${cta.kind}-${cta.url}`
|
|
2103
|
-
)) }) : null
|
|
2267
|
+
)) }) : null,
|
|
2268
|
+
message.status === "COMPLETED" && (message.followUps?.length ?? 0) > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5 pt-0.5", children: [
|
|
2269
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Next you could ask" }),
|
|
2270
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: (message.followUps ?? []).map((question) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2271
|
+
"button",
|
|
2272
|
+
{
|
|
2273
|
+
"data-boff-explore": "follow-up",
|
|
2274
|
+
type: "button",
|
|
2275
|
+
onClick: () => onFollowUp?.(question),
|
|
2276
|
+
className: "group inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1 text-left text-xs text-card-foreground transition-colors hover:border-primary/40 hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2277
|
+
children: [
|
|
2278
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: question }),
|
|
2279
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
|
|
2280
|
+
]
|
|
2281
|
+
},
|
|
2282
|
+
question
|
|
2283
|
+
)) })
|
|
2284
|
+
] }) : null
|
|
2104
2285
|
] })
|
|
2105
2286
|
]
|
|
2106
2287
|
}
|
|
@@ -2146,6 +2327,256 @@ function ErrorBanner({
|
|
|
2146
2327
|
}
|
|
2147
2328
|
);
|
|
2148
2329
|
}
|
|
2330
|
+
function PromptCarousel({
|
|
2331
|
+
prompts,
|
|
2332
|
+
onPrompt,
|
|
2333
|
+
disabled
|
|
2334
|
+
}) {
|
|
2335
|
+
const [index, setIndex] = React.useState(0);
|
|
2336
|
+
const [paused, setPaused] = React.useState(false);
|
|
2337
|
+
const count = prompts.length;
|
|
2338
|
+
const current = prompts[index % count] ?? prompts[0] ?? "";
|
|
2339
|
+
const go = React.useCallback(
|
|
2340
|
+
(delta) => {
|
|
2341
|
+
setIndex((i) => (i + delta + count) % count);
|
|
2342
|
+
},
|
|
2343
|
+
[count]
|
|
2344
|
+
);
|
|
2345
|
+
React.useEffect(() => {
|
|
2346
|
+
if (paused || disabled || count < 2) return;
|
|
2347
|
+
if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
const timer = setInterval(() => setIndex((i) => (i + 1) % count), 4500);
|
|
2351
|
+
return () => clearInterval(timer);
|
|
2352
|
+
}, [paused, disabled, count]);
|
|
2353
|
+
if (count === 0) return null;
|
|
2354
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2355
|
+
"div",
|
|
2356
|
+
{
|
|
2357
|
+
className: "flex min-w-0 items-center gap-1.5",
|
|
2358
|
+
role: "group",
|
|
2359
|
+
"aria-roledescription": "carousel",
|
|
2360
|
+
"aria-label": "Example questions",
|
|
2361
|
+
onMouseEnter: () => setPaused(true),
|
|
2362
|
+
onMouseLeave: () => setPaused(false),
|
|
2363
|
+
onFocusCapture: () => setPaused(true),
|
|
2364
|
+
onBlurCapture: () => setPaused(false),
|
|
2365
|
+
children: [
|
|
2366
|
+
count > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2367
|
+
"button",
|
|
2368
|
+
{
|
|
2369
|
+
type: "button",
|
|
2370
|
+
"aria-label": "Previous suggestion",
|
|
2371
|
+
onClick: () => go(-1),
|
|
2372
|
+
className: "hidden h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:inline-flex",
|
|
2373
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
|
|
2374
|
+
}
|
|
2375
|
+
) : null,
|
|
2376
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2377
|
+
"button",
|
|
2378
|
+
{
|
|
2379
|
+
"data-boff-explore": "chip",
|
|
2380
|
+
"data-chip-kind": "prompt",
|
|
2381
|
+
type: "button",
|
|
2382
|
+
disabled,
|
|
2383
|
+
"aria-label": `Ask: ${current}`,
|
|
2384
|
+
"aria-live": paused ? "polite" : "off",
|
|
2385
|
+
onClick: () => onPrompt(current),
|
|
2386
|
+
className: "boff-explore-fade group flex min-w-0 flex-1 items-center gap-2 rounded-full border border-border bg-card px-4 py-2 text-left text-sm text-card-foreground shadow-sm transition-colors hover:border-primary/50 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2387
|
+
children: [
|
|
2388
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
|
|
2389
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: current }),
|
|
2390
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "ml-auto h-3.5 w-3.5 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
|
|
2391
|
+
]
|
|
2392
|
+
},
|
|
2393
|
+
current
|
|
2394
|
+
),
|
|
2395
|
+
count > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2396
|
+
"button",
|
|
2397
|
+
{
|
|
2398
|
+
type: "button",
|
|
2399
|
+
"aria-label": "Next suggestion",
|
|
2400
|
+
onClick: () => go(1),
|
|
2401
|
+
className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2402
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
|
|
2403
|
+
}
|
|
2404
|
+
) : null
|
|
2405
|
+
]
|
|
2406
|
+
}
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
function ProductStrip({
|
|
2410
|
+
products,
|
|
2411
|
+
focusProduct,
|
|
2412
|
+
onFocus
|
|
2413
|
+
}) {
|
|
2414
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
2415
|
+
const chipClass = (active) => cn(
|
|
2416
|
+
"shrink-0 rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2417
|
+
active ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
2418
|
+
);
|
|
2419
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2420
|
+
"div",
|
|
2421
|
+
{
|
|
2422
|
+
className: "flex min-w-0 items-center gap-2",
|
|
2423
|
+
role: "group",
|
|
2424
|
+
"aria-label": "Focus the assistant on one product",
|
|
2425
|
+
children: [
|
|
2426
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2427
|
+
"div",
|
|
2428
|
+
{
|
|
2429
|
+
className: cn(
|
|
2430
|
+
"boff-explore-strip flex min-w-0 flex-1 gap-2",
|
|
2431
|
+
expanded ? "flex-wrap" : "flex-nowrap overflow-x-auto"
|
|
2432
|
+
),
|
|
2433
|
+
children: [
|
|
2434
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2435
|
+
"button",
|
|
2436
|
+
{
|
|
2437
|
+
"data-boff-explore": "chip",
|
|
2438
|
+
"data-chip-kind": "product",
|
|
2439
|
+
"data-product": "all",
|
|
2440
|
+
type: "button",
|
|
2441
|
+
"aria-pressed": focusProduct === null,
|
|
2442
|
+
onClick: () => onFocus(null),
|
|
2443
|
+
className: chipClass(focusProduct === null),
|
|
2444
|
+
children: "All products"
|
|
2445
|
+
}
|
|
2446
|
+
),
|
|
2447
|
+
products.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2448
|
+
"button",
|
|
2449
|
+
{
|
|
2450
|
+
"data-boff-explore": "chip",
|
|
2451
|
+
"data-chip-kind": "product",
|
|
2452
|
+
"data-product": product.slug,
|
|
2453
|
+
type: "button",
|
|
2454
|
+
"aria-pressed": focusProduct === product.slug,
|
|
2455
|
+
title: product.tagline ?? product.name,
|
|
2456
|
+
onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
|
|
2457
|
+
className: chipClass(focusProduct === product.slug),
|
|
2458
|
+
children: product.name
|
|
2459
|
+
},
|
|
2460
|
+
product.slug
|
|
2461
|
+
))
|
|
2462
|
+
]
|
|
2463
|
+
}
|
|
2464
|
+
),
|
|
2465
|
+
products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2466
|
+
"button",
|
|
2467
|
+
{
|
|
2468
|
+
type: "button",
|
|
2469
|
+
"data-boff-explore": "products-toggle",
|
|
2470
|
+
"aria-expanded": expanded,
|
|
2471
|
+
onClick: () => setExpanded((v) => !v),
|
|
2472
|
+
className: "shrink-0 whitespace-nowrap rounded-full px-2 py-1 text-xs font-medium text-primary underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2473
|
+
children: expanded ? "Show less" : `All ${products.length}`
|
|
2474
|
+
}
|
|
2475
|
+
) : null
|
|
2476
|
+
]
|
|
2477
|
+
}
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
function DisclaimerDialog({
|
|
2481
|
+
open,
|
|
2482
|
+
onClose,
|
|
2483
|
+
captchaOn
|
|
2484
|
+
}) {
|
|
2485
|
+
const panelRef = React.useRef(null);
|
|
2486
|
+
const closeRef = React.useRef(null);
|
|
2487
|
+
const titleId = "boff-explore-disclaimer-title";
|
|
2488
|
+
React.useEffect(() => {
|
|
2489
|
+
if (!open) return;
|
|
2490
|
+
const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
2491
|
+
const focusable = () => Array.from(
|
|
2492
|
+
panelRef.current?.querySelectorAll(
|
|
2493
|
+
'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
|
|
2494
|
+
) ?? []
|
|
2495
|
+
);
|
|
2496
|
+
const onKey = (event) => {
|
|
2497
|
+
if (event.key === "Escape") {
|
|
2498
|
+
onClose();
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
if (event.key !== "Tab") return;
|
|
2502
|
+
const items = focusable();
|
|
2503
|
+
if (items.length === 0) return;
|
|
2504
|
+
const first = items[0];
|
|
2505
|
+
const last = items[items.length - 1];
|
|
2506
|
+
const active = document.activeElement;
|
|
2507
|
+
if (event.shiftKey && (active === first || !panelRef.current?.contains(active))) {
|
|
2508
|
+
event.preventDefault();
|
|
2509
|
+
last?.focus();
|
|
2510
|
+
} else if (!event.shiftKey && active === last) {
|
|
2511
|
+
event.preventDefault();
|
|
2512
|
+
first?.focus();
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2515
|
+
document.addEventListener("keydown", onKey);
|
|
2516
|
+
closeRef.current?.focus();
|
|
2517
|
+
return () => {
|
|
2518
|
+
document.removeEventListener("keydown", onKey);
|
|
2519
|
+
opener?.focus();
|
|
2520
|
+
};
|
|
2521
|
+
}, [open, onClose]);
|
|
2522
|
+
if (!open) return null;
|
|
2523
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2524
|
+
"div",
|
|
2525
|
+
{
|
|
2526
|
+
"data-boff-explore": "disclaimer-dialog",
|
|
2527
|
+
role: "dialog",
|
|
2528
|
+
"aria-modal": "true",
|
|
2529
|
+
"aria-labelledby": titleId,
|
|
2530
|
+
className: "fixed inset-0 z-50 flex items-end justify-center bg-foreground/40 p-4 backdrop-blur-sm sm:items-center",
|
|
2531
|
+
onClick: onClose,
|
|
2532
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2533
|
+
"div",
|
|
2534
|
+
{
|
|
2535
|
+
ref: panelRef,
|
|
2536
|
+
className: "w-full max-w-md rounded-2xl border border-border bg-card p-5 shadow-xl",
|
|
2537
|
+
onClick: (event) => event.stopPropagation(),
|
|
2538
|
+
children: [
|
|
2539
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
|
|
2540
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2541
|
+
"span",
|
|
2542
|
+
{
|
|
2543
|
+
"aria-hidden": "true",
|
|
2544
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
|
|
2545
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TriangleAlert, { className: "h-4 w-4" })
|
|
2546
|
+
}
|
|
2547
|
+
),
|
|
2548
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
2549
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2550
|
+
"p",
|
|
2551
|
+
{
|
|
2552
|
+
id: titleId,
|
|
2553
|
+
className: "text-sm font-semibold text-card-foreground",
|
|
2554
|
+
children: "How this assistant works"
|
|
2555
|
+
}
|
|
2556
|
+
),
|
|
2557
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 space-y-2 text-xs leading-relaxed text-muted-foreground", children: [
|
|
2558
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources." }),
|
|
2559
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "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." }),
|
|
2560
|
+
captchaOn ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Protected by reCAPTCHA \u2014 the Google Privacy Policy and Terms of Service apply." }) : null
|
|
2561
|
+
] })
|
|
2562
|
+
] })
|
|
2563
|
+
] }),
|
|
2564
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2565
|
+
Button,
|
|
2566
|
+
{
|
|
2567
|
+
ref: closeRef,
|
|
2568
|
+
size: "sm",
|
|
2569
|
+
variant: "secondary",
|
|
2570
|
+
onClick: onClose,
|
|
2571
|
+
children: "Got it"
|
|
2572
|
+
}
|
|
2573
|
+
) })
|
|
2574
|
+
]
|
|
2575
|
+
}
|
|
2576
|
+
)
|
|
2577
|
+
}
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2149
2580
|
function WelcomeState({
|
|
2150
2581
|
title,
|
|
2151
2582
|
body,
|
|
@@ -2156,21 +2587,19 @@ function WelcomeState({
|
|
|
2156
2587
|
onFocus,
|
|
2157
2588
|
disabled
|
|
2158
2589
|
}) {
|
|
2159
|
-
|
|
2160
|
-
const visibleProducts = showAllProducts ? products : products.slice(0, PRODUCT_CHIP_PREVIEW);
|
|
2161
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-10 sm:py-14", children: [
|
|
2590
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-8 sm:py-12", children: [
|
|
2162
2591
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
2163
2592
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-3 py-1 text-xs font-medium text-muted-foreground", children: [
|
|
2164
2593
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5" }),
|
|
2165
2594
|
"AI answers, grounded in our documentation"
|
|
2166
2595
|
] }),
|
|
2167
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-
|
|
2168
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-
|
|
2596
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
|
|
2597
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-2.5 max-w-2xl text-pretty text-sm leading-relaxed text-muted-foreground sm:text-lg", children: body })
|
|
2169
2598
|
] }),
|
|
2170
|
-
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-
|
|
2599
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-7 grid gap-2.5 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2171
2600
|
"li",
|
|
2172
2601
|
{
|
|
2173
|
-
className: "rounded-xl border border-border bg-card p-
|
|
2602
|
+
className: "rounded-xl border border-border/70 bg-card/60 p-3.5 text-left transition-colors hover:border-border",
|
|
2174
2603
|
children: [
|
|
2175
2604
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-2.5 flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" }) }),
|
|
2176
2605
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-card-foreground", children: capTitle }),
|
|
@@ -2179,72 +2608,27 @@ function WelcomeState({
|
|
|
2179
2608
|
},
|
|
2180
2609
|
capTitle
|
|
2181
2610
|
)) }),
|
|
2182
|
-
prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-
|
|
2183
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2
|
|
2184
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2185
|
-
|
|
2611
|
+
prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
|
|
2612
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
|
|
2613
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2614
|
+
PromptCarousel,
|
|
2186
2615
|
{
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
className: "group inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-card px-3.5 py-1.5 text-left text-sm text-card-foreground transition-colors hover:border-primary/40 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2193
|
-
children: [
|
|
2194
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: prompt }),
|
|
2195
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
|
|
2196
|
-
]
|
|
2197
|
-
},
|
|
2198
|
-
prompt
|
|
2199
|
-
)) })
|
|
2616
|
+
prompts,
|
|
2617
|
+
onPrompt,
|
|
2618
|
+
disabled
|
|
2619
|
+
}
|
|
2620
|
+
)
|
|
2200
2621
|
] }) : null,
|
|
2201
|
-
products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-
|
|
2202
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2
|
|
2203
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
"aria-pressed": focusProduct === null,
|
|
2212
|
-
onClick: () => onFocus(null),
|
|
2213
|
-
className: cn(
|
|
2214
|
-
"rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2215
|
-
focusProduct === null ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
2216
|
-
),
|
|
2217
|
-
children: "All products"
|
|
2218
|
-
}
|
|
2219
|
-
),
|
|
2220
|
-
visibleProducts.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2221
|
-
"button",
|
|
2222
|
-
{
|
|
2223
|
-
"data-boff-explore": "chip",
|
|
2224
|
-
"data-chip-kind": "product",
|
|
2225
|
-
"data-product": product.slug,
|
|
2226
|
-
type: "button",
|
|
2227
|
-
"aria-pressed": focusProduct === product.slug,
|
|
2228
|
-
title: product.tagline ?? product.name,
|
|
2229
|
-
onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
|
|
2230
|
-
className: cn(
|
|
2231
|
-
"rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2232
|
-
focusProduct === product.slug ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
2233
|
-
),
|
|
2234
|
-
children: product.name
|
|
2235
|
-
},
|
|
2236
|
-
product.slug
|
|
2237
|
-
)),
|
|
2238
|
-
products.length > PRODUCT_CHIP_PREVIEW ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2239
|
-
"button",
|
|
2240
|
-
{
|
|
2241
|
-
type: "button",
|
|
2242
|
-
onClick: () => setShowAllProducts((v) => !v),
|
|
2243
|
-
className: "rounded-full px-3 py-1 text-xs font-medium text-primary underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2244
|
-
children: showAllProducts ? "Show fewer" : `Show all ${products.length}`
|
|
2245
|
-
}
|
|
2246
|
-
) : null
|
|
2247
|
-
] })
|
|
2622
|
+
products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6", children: [
|
|
2623
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
|
|
2624
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2625
|
+
ProductStrip,
|
|
2626
|
+
{
|
|
2627
|
+
products,
|
|
2628
|
+
focusProduct,
|
|
2629
|
+
onFocus
|
|
2630
|
+
}
|
|
2631
|
+
)
|
|
2248
2632
|
] }) : null
|
|
2249
2633
|
] });
|
|
2250
2634
|
}
|
|
@@ -2281,7 +2665,8 @@ function ExplorePage({
|
|
|
2281
2665
|
history,
|
|
2282
2666
|
openConversation,
|
|
2283
2667
|
deleteConversation,
|
|
2284
|
-
clearHistory
|
|
2668
|
+
clearHistory,
|
|
2669
|
+
recordClick
|
|
2285
2670
|
} = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
|
|
2286
2671
|
const [draft, setDraft] = React.useState("");
|
|
2287
2672
|
const textareaRef = React.useRef(null);
|
|
@@ -2300,6 +2685,7 @@ function ExplorePage({
|
|
|
2300
2685
|
}
|
|
2301
2686
|
});
|
|
2302
2687
|
speechStopRef.current = speech.stop;
|
|
2688
|
+
const [disclaimerOpen, setDisclaimerOpen] = React.useState(false);
|
|
2303
2689
|
const scrollRef = React.useRef(null);
|
|
2304
2690
|
const latestAssistantRef = React.useRef(null);
|
|
2305
2691
|
const alignedForRef = React.useRef(null);
|
|
@@ -2392,7 +2778,7 @@ function ExplorePage({
|
|
|
2392
2778
|
"data-boff-explore": "page",
|
|
2393
2779
|
"data-phase": phase,
|
|
2394
2780
|
className: cn(
|
|
2395
|
-
"flex w-full flex-col bg-background text-foreground",
|
|
2781
|
+
"flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
|
|
2396
2782
|
heightMode === "auto" && "min-h-[70vh]",
|
|
2397
2783
|
className
|
|
2398
2784
|
),
|
|
@@ -2534,7 +2920,7 @@ function ExplorePage({
|
|
|
2534
2920
|
role: "log",
|
|
2535
2921
|
"aria-live": "polite",
|
|
2536
2922
|
"aria-label": "Explore conversation",
|
|
2537
|
-
className: "boff-explore-scroll min-h-0 flex-1 overflow-y-auto",
|
|
2923
|
+
className: "boff-explore-scroll min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden",
|
|
2538
2924
|
children: showWelcome ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2539
2925
|
WelcomeState,
|
|
2540
2926
|
{
|
|
@@ -2547,21 +2933,31 @@ function ExplorePage({
|
|
|
2547
2933
|
onFocus: setFocusProduct,
|
|
2548
2934
|
disabled: !canSend || busy
|
|
2549
2935
|
}
|
|
2550
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-3xl space-y-6 px-4 py-6", children: messages.map(
|
|
2936
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full min-w-0 max-w-3xl space-y-6 px-4 py-6", children: messages.map(
|
|
2551
2937
|
(message) => message.role === "USER" ? /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2552
2938
|
AssistantBubble,
|
|
2553
2939
|
{
|
|
2554
2940
|
message,
|
|
2555
2941
|
activity,
|
|
2556
2942
|
onNavigate,
|
|
2557
|
-
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0
|
|
2943
|
+
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
|
|
2944
|
+
onFollow: (kind, url) => recordClick(message.id, kind, url),
|
|
2945
|
+
onFollowUp: sendPrompt
|
|
2558
2946
|
},
|
|
2559
2947
|
message.id
|
|
2560
2948
|
)
|
|
2561
2949
|
) })
|
|
2562
2950
|
}
|
|
2563
2951
|
),
|
|
2564
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2952
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2953
|
+
DisclaimerDialog,
|
|
2954
|
+
{
|
|
2955
|
+
open: disclaimerOpen,
|
|
2956
|
+
onClose: () => setDisclaimerOpen(false),
|
|
2957
|
+
captchaOn
|
|
2958
|
+
}
|
|
2959
|
+
),
|
|
2960
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border bg-background/85 px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3 backdrop-blur-md", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2565
2961
|
"form",
|
|
2566
2962
|
{
|
|
2567
2963
|
"data-boff-explore": "composer",
|
|
@@ -2576,7 +2972,7 @@ function ExplorePage({
|
|
|
2576
2972
|
"div",
|
|
2577
2973
|
{
|
|
2578
2974
|
className: cn(
|
|
2579
|
-
"flex items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-
|
|
2975
|
+
"flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
|
|
2580
2976
|
overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
|
|
2581
2977
|
),
|
|
2582
2978
|
children: [
|
|
@@ -2597,7 +2993,7 @@ function ExplorePage({
|
|
|
2597
2993
|
},
|
|
2598
2994
|
"aria-label": "Ask a question",
|
|
2599
2995
|
placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? `Ask about ${focusedProductName}\u2026` : "Ask anything about our products\u2026",
|
|
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"
|
|
2996
|
+
className: "max-h-[200px] min-h-[44px] w-full min-w-0 flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
|
|
2601
2997
|
}
|
|
2602
2998
|
),
|
|
2603
2999
|
speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2688,48 +3084,19 @@ function ExplorePage({
|
|
|
2688
3084
|
}
|
|
2689
3085
|
)
|
|
2690
3086
|
] }),
|
|
2691
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2692
|
-
"
|
|
2693
|
-
|
|
2694
|
-
"
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
"
|
|
2698
|
-
|
|
2699
|
-
"
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
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
|
-
)
|
|
3087
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
3088
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: "Grounded in our docs \u2014 check anything important." }),
|
|
3089
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3090
|
+
"button",
|
|
3091
|
+
{
|
|
3092
|
+
type: "button",
|
|
3093
|
+
"data-boff-explore": "disclaimer",
|
|
3094
|
+
onClick: () => setDisclaimerOpen(true),
|
|
3095
|
+
className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
3096
|
+
children: "Disclaimer"
|
|
3097
|
+
}
|
|
3098
|
+
)
|
|
3099
|
+
] })
|
|
2733
3100
|
]
|
|
2734
3101
|
}
|
|
2735
3102
|
) })
|