@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.
@@ -1,10 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext } from 'react';
3
- import { Sparkles, RotateCcw, History, Trash2, Mic, Square, ArrowUp, BookOpenText, Layers, Link2, TriangleAlert, ExternalLink } from 'lucide-react';
3
+ import { Sparkles, RotateCcw, History, Trash2, Mic, Square, ArrowUp, BookOpenText, Layers, Link2, TriangleAlert, ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeSanitize from 'rehype-sanitize';
6
6
  import remarkGfm from 'remark-gfm';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
8
  import { Helmet } from 'react-helmet-async';
9
9
  import { Slot } from '@radix-ui/react-slot';
10
10
  import { cva } from 'class-variance-authority';
@@ -463,6 +463,7 @@ var EXPLORE_MESSAGE_FIELDS = (
463
463
  description
464
464
  product
465
465
  imageUrl
466
+ index
466
467
  }
467
468
  ctas {
468
469
  kind
@@ -470,6 +471,8 @@ var EXPLORE_MESSAGE_FIELDS = (
470
471
  url
471
472
  product
472
473
  }
474
+ followUps
475
+ answered
473
476
  errorCode
474
477
  createdAt
475
478
  completedAt
@@ -535,6 +538,16 @@ var SEND_EXPLORE_MESSAGE_MUTATION = (
535
538
  }
536
539
  `
537
540
  );
541
+ var RECORD_EXPLORE_CLICK_MUTATION = (
542
+ /* GraphQL */
543
+ `
544
+ mutation RecordExploreClick($input: RecordExploreClickInput!) {
545
+ recordExploreClick(input: $input) {
546
+ recorded
547
+ }
548
+ }
549
+ `
550
+ );
538
551
 
539
552
  // src/data/products.ts
540
553
  var ECOSYSTEM_PRODUCTS = [
@@ -1467,6 +1480,20 @@ function useExploreChat(options = {}) {
1467
1480
  return null;
1468
1481
  }, [messages]);
1469
1482
  const canSend = (phase === "ready" || phase === "error") && catalog.enabled && turnCount < catalog.limits.maxTurnsPerConversation;
1483
+ const recordClick = useCallback(
1484
+ (messageId, kind, url) => {
1485
+ const token = conversationTokenRef.current;
1486
+ if (!token) return;
1487
+ try {
1488
+ void client.mutate(RECORD_EXPLORE_CLICK_MUTATION, {
1489
+ input: { conversationToken: token, messageId, kind, url }
1490
+ }).catch(() => {
1491
+ });
1492
+ } catch {
1493
+ }
1494
+ },
1495
+ [client]
1496
+ );
1470
1497
  return {
1471
1498
  phase,
1472
1499
  catalog,
@@ -1484,7 +1511,8 @@ function useExploreChat(options = {}) {
1484
1511
  history: archive,
1485
1512
  openConversation,
1486
1513
  deleteConversation,
1487
- clearHistory
1514
+ clearHistory,
1515
+ recordClick
1488
1516
  };
1489
1517
  }
1490
1518
  var optimisticCounter = 0;
@@ -1497,21 +1525,37 @@ function getRecognitionCtor() {
1497
1525
  const w = window;
1498
1526
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
1499
1527
  }
1528
+ function isTouchDevice() {
1529
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1530
+ try {
1531
+ return window.matchMedia("(pointer: coarse)").matches;
1532
+ } catch {
1533
+ return false;
1534
+ }
1535
+ }
1500
1536
  var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1537
+ var MIC_UNAVAILABLE_MESSAGE = "Dictation isn't available in this browser. You can type instead.";
1538
+ var RELEASE_SETTLE_MS = 250;
1539
+ var RETRY_DELAY_MS = 350;
1540
+ var sleep = (ms) => new Promise((resolve) => {
1541
+ setTimeout(resolve, ms);
1542
+ });
1501
1543
  async function ensureMicrophoneAccess() {
1502
1544
  const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
1503
- if (!media?.getUserMedia) return { ok: true, confirmed: false };
1545
+ if (!media?.getUserMedia)
1546
+ return { ok: true, confirmed: false, primed: false };
1504
1547
  try {
1505
1548
  const status = await navigator.permissions?.query({
1506
1549
  name: "microphone"
1507
1550
  });
1508
- if (status?.state === "granted") return { ok: true, confirmed: true };
1551
+ if (status?.state === "granted")
1552
+ return { ok: true, confirmed: true, primed: false };
1509
1553
  } catch {
1510
1554
  }
1511
1555
  try {
1512
1556
  const stream = await media.getUserMedia({ audio: true });
1513
1557
  for (const track of stream.getTracks()) track.stop();
1514
- return { ok: true, confirmed: true };
1558
+ return { ok: true, confirmed: true, primed: true };
1515
1559
  } catch (error) {
1516
1560
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
1517
1561
  if (name === "NotAllowedError" || name === "SecurityError") {
@@ -1555,19 +1599,17 @@ function useSpeechInput({
1555
1599
  const recognitionRef = useRef(null);
1556
1600
  const startTokenRef = useRef(0);
1557
1601
  const micConfirmedRef = useRef(false);
1558
- const beginRecognitionRef = useRef(null);
1559
- const beginRecognition = useCallback(
1560
- (Ctor, token) => {
1561
- beginRecognitionRef.current?.(Ctor, token);
1562
- },
1563
- []
1564
- );
1602
+ const wantsListeningRef = useRef(false);
1603
+ const retriedRef = useRef(false);
1565
1604
  const finalRef = useRef(onFinalTranscript);
1566
1605
  const errorRef = useRef(onError);
1567
1606
  finalRef.current = onFinalTranscript;
1568
1607
  errorRef.current = onError;
1608
+ const langRef = useRef(lang);
1609
+ langRef.current = lang;
1569
1610
  const stop = useCallback(() => {
1570
1611
  startTokenRef.current += 1;
1612
+ wantsListeningRef.current = false;
1571
1613
  setListening(false);
1572
1614
  setInterim("");
1573
1615
  const recognition = recognitionRef.current;
@@ -1576,83 +1618,111 @@ function useSpeechInput({
1576
1618
  recognition.stop();
1577
1619
  } catch {
1578
1620
  }
1579
- setListening(false);
1580
- setInterim("");
1581
1621
  }, []);
1582
- const start = useCallback(() => {
1622
+ const openSession = useCallback((token) => {
1583
1623
  const Ctor = getRecognitionCtor();
1584
- if (!Ctor) return;
1585
- if (recognitionRef.current) stop();
1586
- setError(null);
1587
- setListening(true);
1624
+ if (!Ctor || token !== startTokenRef.current) return;
1625
+ const recognition = new Ctor();
1626
+ recognition.lang = langRef.current ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1627
+ recognition.continuous = !isTouchDevice();
1628
+ recognition.interimResults = true;
1629
+ recognition.maxAlternatives = 1;
1630
+ recognition.onstart = () => {
1631
+ if (token !== startTokenRef.current) return;
1632
+ setError(null);
1633
+ setListening(true);
1634
+ };
1635
+ recognition.onresult = (event) => {
1636
+ if (token !== startTokenRef.current) return;
1637
+ let settled = "";
1638
+ let pending = "";
1639
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
1640
+ const result = event.results[i];
1641
+ if (!result) continue;
1642
+ const text = result[0]?.transcript ?? "";
1643
+ if (result.isFinal) settled += text;
1644
+ else pending += text;
1645
+ }
1646
+ setInterim(pending);
1647
+ if (settled.trim() !== "") finalRef.current(settled);
1648
+ };
1649
+ recognition.onerror = (event) => {
1650
+ if (token !== startTokenRef.current) return;
1651
+ if ((event.error === "not-allowed" || event.error === "service-not-allowed") && micConfirmedRef.current && !retriedRef.current) {
1652
+ retriedRef.current = true;
1653
+ void sleep(RETRY_DELAY_MS).then(() => {
1654
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1655
+ return;
1656
+ openSessionRef.current?.(token);
1657
+ });
1658
+ return;
1659
+ }
1660
+ const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? MIC_UNAVAILABLE_MESSAGE : describeError(event.error);
1661
+ wantsListeningRef.current = false;
1662
+ setListening(false);
1663
+ setInterim("");
1664
+ if (message !== "") {
1665
+ setError(message);
1666
+ errorRef.current?.(message);
1667
+ }
1668
+ };
1669
+ recognition.onend = () => {
1670
+ if (token !== startTokenRef.current) return;
1671
+ setInterim("");
1672
+ if (wantsListeningRef.current && isTouchDevice()) {
1673
+ void sleep(120).then(() => {
1674
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1675
+ return;
1676
+ openSessionRef.current?.(token);
1677
+ });
1678
+ return;
1679
+ }
1680
+ setListening(false);
1681
+ };
1682
+ recognitionRef.current = recognition;
1683
+ try {
1684
+ recognition.start();
1685
+ } catch {
1686
+ wantsListeningRef.current = false;
1687
+ setListening(false);
1688
+ }
1689
+ }, []);
1690
+ const openSessionRef = useRef(null);
1691
+ openSessionRef.current = openSession;
1692
+ const start = useCallback(() => {
1693
+ if (!getRecognitionCtor()) return;
1588
1694
  startTokenRef.current += 1;
1589
1695
  const token = startTokenRef.current;
1590
- void ensureMicrophoneAccess().then((access) => {
1696
+ wantsListeningRef.current = true;
1697
+ retriedRef.current = false;
1698
+ micConfirmedRef.current = false;
1699
+ setError(null);
1700
+ setListening(true);
1701
+ void ensureMicrophoneAccess().then(async (access) => {
1591
1702
  if (token !== startTokenRef.current) return;
1592
- micConfirmedRef.current = access.ok && access.confirmed;
1593
1703
  if (!access.ok) {
1704
+ wantsListeningRef.current = false;
1594
1705
  setListening(false);
1595
1706
  setError(access.message);
1596
1707
  errorRef.current?.(access.message);
1597
1708
  return;
1598
1709
  }
1599
- beginRecognition(Ctor, token);
1600
- });
1601
- }, [beginRecognition, stop]);
1602
- const beginRecognitionImpl = useCallback(
1603
- (Ctor, token) => {
1604
- const recognition = new Ctor();
1605
- recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1606
- recognition.continuous = true;
1607
- recognition.interimResults = true;
1608
- recognition.maxAlternatives = 1;
1609
- recognition.onstart = () => {
1710
+ micConfirmedRef.current = access.confirmed;
1711
+ if (access.primed) {
1712
+ await sleep(RELEASE_SETTLE_MS);
1610
1713
  if (token !== startTokenRef.current) return;
1611
- setError(null);
1612
- setListening(true);
1613
- };
1614
- recognition.onresult = (event) => {
1615
- let settled = "";
1616
- let pending = "";
1617
- for (let i = event.resultIndex; i < event.results.length; i += 1) {
1618
- const result = event.results[i];
1619
- if (!result) continue;
1620
- const text = result[0]?.transcript ?? "";
1621
- if (result.isFinal) settled += text;
1622
- else pending += text;
1623
- }
1624
- setInterim(pending);
1625
- if (settled.trim() !== "") finalRef.current(settled);
1626
- };
1627
- recognition.onerror = (event) => {
1628
- 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);
1629
- setListening(false);
1630
- setInterim("");
1631
- if (message !== "") {
1632
- setError(message);
1633
- errorRef.current?.(message);
1634
- }
1635
- };
1636
- recognition.onend = () => {
1637
- setListening(false);
1638
- setInterim("");
1639
- };
1640
- recognitionRef.current = recognition;
1641
- try {
1642
- recognition.start();
1643
- } catch {
1644
- setListening(false);
1645
1714
  }
1646
- },
1647
- [lang]
1648
- );
1649
- beginRecognitionRef.current = beginRecognitionImpl;
1715
+ openSessionRef.current?.(token);
1716
+ });
1717
+ }, []);
1650
1718
  const toggle = useCallback(() => {
1651
1719
  if (listening) stop();
1652
1720
  else start();
1653
1721
  }, [listening, start, stop]);
1654
1722
  useEffect(
1655
1723
  () => () => {
1724
+ startTokenRef.current += 1;
1725
+ wantsListeningRef.current = false;
1656
1726
  const recognition = recognitionRef.current;
1657
1727
  if (!recognition) return;
1658
1728
  recognition.onresult = null;
@@ -1836,6 +1906,50 @@ var EXPLORE_CSS = `
1836
1906
  border-radius: 9999px;
1837
1907
  background-clip: content-box;
1838
1908
  }
1909
+
1910
+ /* The product strip scrolls inside itself; a visible scrollbar would read as a broken
1911
+ layout rather than an affordance, and reserving its gutter shifts the chips. */
1912
+ .boff-explore-strip {
1913
+ scrollbar-width: none;
1914
+ -ms-overflow-style: none;
1915
+ }
1916
+ .boff-explore-strip::-webkit-scrollbar { display: none; }
1917
+
1918
+ @keyframes boff-explore-fade-in {
1919
+ from { opacity: 0; transform: translateY(2px); }
1920
+ to { opacity: 1; transform: none; }
1921
+ }
1922
+ .boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
1923
+ @media (prefers-reduced-motion: reduce) {
1924
+ .boff-explore-fade { animation: none; }
1925
+ }
1926
+
1927
+ /* Nothing inside an answer may widen the page. Model output carries long URLs, code and
1928
+ tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
1929
+ WHOLE document horizontally scrollable, not just the bubble. */
1930
+ .boff-explore-body,
1931
+ .boff-explore-body p,
1932
+ .boff-explore-body li,
1933
+ .boff-explore-body a,
1934
+ .boff-explore-body h1,
1935
+ .boff-explore-body h2,
1936
+ .boff-explore-body h3 {
1937
+ overflow-wrap: anywhere;
1938
+ word-break: break-word;
1939
+ }
1940
+ .boff-explore-body pre {
1941
+ overflow-x: auto;
1942
+ max-width: 100%;
1943
+ }
1944
+ .boff-explore-body table {
1945
+ display: block;
1946
+ overflow-x: auto;
1947
+ max-width: 100%;
1948
+ }
1949
+ .boff-explore-body img {
1950
+ max-width: 100%;
1951
+ height: auto;
1952
+ }
1839
1953
  `;
1840
1954
  function ExploreStyles() {
1841
1955
  return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
@@ -1857,7 +1971,6 @@ var CAPABILITIES = [
1857
1971
  body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
1858
1972
  }
1859
1973
  ];
1860
- var PRODUCT_CHIP_PREVIEW = 12;
1861
1974
  var CTA_VARIANTS = {
1862
1975
  CONTACT: "default",
1863
1976
  WAITLIST: "default",
@@ -1896,7 +2009,10 @@ function logoUrlOf(url) {
1896
2009
  return null;
1897
2010
  }
1898
2011
  }
1899
- function ReferenceCard({ reference }) {
2012
+ function ReferenceCard({
2013
+ reference,
2014
+ onFollow
2015
+ }) {
1900
2016
  const [imageFailed, setImageFailed] = useState(false);
1901
2017
  const [logoFailed, setLogoFailed] = useState(false);
1902
2018
  const host = hostnameOf(reference.url);
@@ -1918,9 +2034,11 @@ function ReferenceCard({ reference }) {
1918
2034
  "a",
1919
2035
  {
1920
2036
  "data-boff-explore": "reference",
2037
+ "data-index": reference.index ?? void 0,
1921
2038
  href: reference.url,
1922
2039
  target: "_blank",
1923
2040
  rel: "noopener noreferrer",
2041
+ onClick: () => onFollow?.(reference.url),
1924
2042
  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",
1925
2043
  children: [
1926
2044
  /* @__PURE__ */ jsx("div", { className: "relative h-11 w-11 shrink-0 overflow-hidden rounded-lg bg-muted sm:hidden", children: logoImg }),
@@ -1944,7 +2062,10 @@ function ReferenceCard({ reference }) {
1944
2062
  }
1945
2063
  ) }) : letterPlate }),
1946
2064
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 sm:gap-1.5 sm:p-3", children: [
1947
- /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
2065
+ /* @__PURE__ */ jsxs("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: [
2066
+ reference.index ? /* @__PURE__ */ 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,
2067
+ reference.title || host || reference.url
2068
+ ] }),
1948
2069
  reference.description ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground max-sm:hidden", children: reference.description }) : null,
1949
2070
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:mt-auto sm:pt-2", children: [
1950
2071
  reference.product ? /* @__PURE__ */ 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,
@@ -1960,7 +2081,8 @@ function ReferenceCard({ reference }) {
1960
2081
  }
1961
2082
  function CtaButton({
1962
2083
  cta,
1963
- onNavigate
2084
+ onNavigate,
2085
+ onFollow
1964
2086
  }) {
1965
2087
  const variant = CTA_VARIANTS[cta.kind] ?? "outline";
1966
2088
  const internal = isInternalPath(cta.url);
@@ -1973,7 +2095,10 @@ function CtaButton({
1973
2095
  type: "button",
1974
2096
  size: "sm",
1975
2097
  variant,
1976
- onClick: () => onNavigate(cta.url),
2098
+ onClick: () => {
2099
+ onFollow?.(cta.url);
2100
+ onNavigate(cta.url);
2101
+ },
1977
2102
  children: cta.label
1978
2103
  }
1979
2104
  );
@@ -1984,6 +2109,7 @@ function CtaButton({
1984
2109
  "data-boff-explore": "cta",
1985
2110
  "data-kind": cta.kind,
1986
2111
  href: cta.url,
2112
+ onClick: () => onFollow?.(cta.url),
1987
2113
  ...internal ? {} : { target: "_blank", rel: "noopener noreferrer" },
1988
2114
  children: [
1989
2115
  cta.label,
@@ -2035,7 +2161,9 @@ function AssistantBubble({
2035
2161
  message,
2036
2162
  activity,
2037
2163
  onNavigate,
2038
- anchorRef
2164
+ anchorRef,
2165
+ onFollow,
2166
+ onFollowUp
2039
2167
  }) {
2040
2168
  const streaming = message.status === "PENDING" || message.status === "RUNNING";
2041
2169
  const body = message.content || message.partialContent || "";
@@ -2057,24 +2185,77 @@ function AssistantBubble({
2057
2185
  }
2058
2186
  ),
2059
2187
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
2188
+ message.status === "COMPLETED" && message.answered === false ? /* @__PURE__ */ jsxs(
2189
+ "div",
2190
+ {
2191
+ "data-boff-explore": "no-answer",
2192
+ 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",
2193
+ children: [
2194
+ /* @__PURE__ */ jsx(TriangleAlert, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-600 dark:text-amber-400" }),
2195
+ /* @__PURE__ */ jsxs("span", { children: [
2196
+ "I couldn't find this in our documentation, so the answer below is not grounded in a source. Try rephrasing, or",
2197
+ " ",
2198
+ /* @__PURE__ */ jsx(
2199
+ "a",
2200
+ {
2201
+ href: "/contact",
2202
+ className: "underline underline-offset-2 hover:text-primary",
2203
+ onClick: (event) => {
2204
+ if (!onNavigate) return;
2205
+ event.preventDefault();
2206
+ onNavigate("/contact");
2207
+ },
2208
+ children: "ask a human"
2209
+ }
2210
+ ),
2211
+ "."
2212
+ ] })
2213
+ ]
2214
+ }
2215
+ ) : null,
2060
2216
  body ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
2061
- /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm text-card-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: body }) }),
2217
+ /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: body }) }),
2062
2218
  streaming ? /* @__PURE__ */ jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
2063
2219
  ] }) : null,
2064
2220
  streaming ? /* @__PURE__ */ jsx(ThinkingIndicator, { activity }) : null,
2065
2221
  failed && !body ? /* @__PURE__ */ 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,
2066
2222
  message.references.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
2067
2223
  /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Sources" }),
2068
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: message.references.map((reference) => /* @__PURE__ */ jsx(ReferenceCard, { reference }, reference.url)) })
2224
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: message.references.map((reference) => /* @__PURE__ */ jsx(
2225
+ ReferenceCard,
2226
+ {
2227
+ reference,
2228
+ onFollow: (url) => onFollow?.("REFERENCE", url)
2229
+ },
2230
+ reference.url
2231
+ )) })
2069
2232
  ] }) : null,
2070
2233
  message.ctas.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 pt-0.5", children: message.ctas.map((cta) => /* @__PURE__ */ jsx(
2071
2234
  CtaButton,
2072
2235
  {
2073
2236
  cta,
2074
- onNavigate
2237
+ onNavigate,
2238
+ onFollow: (url) => onFollow?.("CTA", url)
2075
2239
  },
2076
2240
  `${cta.kind}-${cta.url}`
2077
- )) }) : null
2241
+ )) }) : null,
2242
+ message.status === "COMPLETED" && (message.followUps?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 pt-0.5", children: [
2243
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Next you could ask" }),
2244
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: (message.followUps ?? []).map((question) => /* @__PURE__ */ jsxs(
2245
+ "button",
2246
+ {
2247
+ "data-boff-explore": "follow-up",
2248
+ type: "button",
2249
+ onClick: () => onFollowUp?.(question),
2250
+ 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",
2251
+ children: [
2252
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: question }),
2253
+ /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2254
+ ]
2255
+ },
2256
+ question
2257
+ )) })
2258
+ ] }) : null
2078
2259
  ] })
2079
2260
  ]
2080
2261
  }
@@ -2120,6 +2301,256 @@ function ErrorBanner({
2120
2301
  }
2121
2302
  );
2122
2303
  }
2304
+ function PromptCarousel({
2305
+ prompts,
2306
+ onPrompt,
2307
+ disabled
2308
+ }) {
2309
+ const [index, setIndex] = useState(0);
2310
+ const [paused, setPaused] = useState(false);
2311
+ const count = prompts.length;
2312
+ const current = prompts[index % count] ?? prompts[0] ?? "";
2313
+ const go = useCallback(
2314
+ (delta) => {
2315
+ setIndex((i) => (i + delta + count) % count);
2316
+ },
2317
+ [count]
2318
+ );
2319
+ useEffect(() => {
2320
+ if (paused || disabled || count < 2) return;
2321
+ if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
2322
+ return;
2323
+ }
2324
+ const timer = setInterval(() => setIndex((i) => (i + 1) % count), 4500);
2325
+ return () => clearInterval(timer);
2326
+ }, [paused, disabled, count]);
2327
+ if (count === 0) return null;
2328
+ return /* @__PURE__ */ jsxs(
2329
+ "div",
2330
+ {
2331
+ className: "flex min-w-0 items-center gap-1.5",
2332
+ role: "group",
2333
+ "aria-roledescription": "carousel",
2334
+ "aria-label": "Example questions",
2335
+ onMouseEnter: () => setPaused(true),
2336
+ onMouseLeave: () => setPaused(false),
2337
+ onFocusCapture: () => setPaused(true),
2338
+ onBlurCapture: () => setPaused(false),
2339
+ children: [
2340
+ count > 1 ? /* @__PURE__ */ jsx(
2341
+ "button",
2342
+ {
2343
+ type: "button",
2344
+ "aria-label": "Previous suggestion",
2345
+ onClick: () => go(-1),
2346
+ 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",
2347
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
2348
+ }
2349
+ ) : null,
2350
+ /* @__PURE__ */ jsxs(
2351
+ "button",
2352
+ {
2353
+ "data-boff-explore": "chip",
2354
+ "data-chip-kind": "prompt",
2355
+ type: "button",
2356
+ disabled,
2357
+ "aria-label": `Ask: ${current}`,
2358
+ "aria-live": paused ? "polite" : "off",
2359
+ onClick: () => onPrompt(current),
2360
+ 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",
2361
+ children: [
2362
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
2363
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: current }),
2364
+ /* @__PURE__ */ jsx(ArrowUp, { className: "ml-auto h-3.5 w-3.5 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2365
+ ]
2366
+ },
2367
+ current
2368
+ ),
2369
+ count > 1 ? /* @__PURE__ */ jsx(
2370
+ "button",
2371
+ {
2372
+ type: "button",
2373
+ "aria-label": "Next suggestion",
2374
+ onClick: () => go(1),
2375
+ 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",
2376
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
2377
+ }
2378
+ ) : null
2379
+ ]
2380
+ }
2381
+ );
2382
+ }
2383
+ function ProductStrip({
2384
+ products,
2385
+ focusProduct,
2386
+ onFocus
2387
+ }) {
2388
+ const [expanded, setExpanded] = useState(false);
2389
+ const chipClass = (active) => cn(
2390
+ "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",
2391
+ active ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2392
+ );
2393
+ return /* @__PURE__ */ jsxs(
2394
+ "div",
2395
+ {
2396
+ className: "flex min-w-0 items-center gap-2",
2397
+ role: "group",
2398
+ "aria-label": "Focus the assistant on one product",
2399
+ children: [
2400
+ /* @__PURE__ */ jsxs(
2401
+ "div",
2402
+ {
2403
+ className: cn(
2404
+ "boff-explore-strip flex min-w-0 flex-1 gap-2",
2405
+ expanded ? "flex-wrap" : "flex-nowrap overflow-x-auto"
2406
+ ),
2407
+ children: [
2408
+ /* @__PURE__ */ jsx(
2409
+ "button",
2410
+ {
2411
+ "data-boff-explore": "chip",
2412
+ "data-chip-kind": "product",
2413
+ "data-product": "all",
2414
+ type: "button",
2415
+ "aria-pressed": focusProduct === null,
2416
+ onClick: () => onFocus(null),
2417
+ className: chipClass(focusProduct === null),
2418
+ children: "All products"
2419
+ }
2420
+ ),
2421
+ products.map((product) => /* @__PURE__ */ jsx(
2422
+ "button",
2423
+ {
2424
+ "data-boff-explore": "chip",
2425
+ "data-chip-kind": "product",
2426
+ "data-product": product.slug,
2427
+ type: "button",
2428
+ "aria-pressed": focusProduct === product.slug,
2429
+ title: product.tagline ?? product.name,
2430
+ onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2431
+ className: chipClass(focusProduct === product.slug),
2432
+ children: product.name
2433
+ },
2434
+ product.slug
2435
+ ))
2436
+ ]
2437
+ }
2438
+ ),
2439
+ products.length > 0 ? /* @__PURE__ */ jsx(
2440
+ "button",
2441
+ {
2442
+ type: "button",
2443
+ "data-boff-explore": "products-toggle",
2444
+ "aria-expanded": expanded,
2445
+ onClick: () => setExpanded((v) => !v),
2446
+ 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",
2447
+ children: expanded ? "Show less" : `All ${products.length}`
2448
+ }
2449
+ ) : null
2450
+ ]
2451
+ }
2452
+ );
2453
+ }
2454
+ function DisclaimerDialog({
2455
+ open,
2456
+ onClose,
2457
+ captchaOn
2458
+ }) {
2459
+ const panelRef = useRef(null);
2460
+ const closeRef = useRef(null);
2461
+ const titleId = "boff-explore-disclaimer-title";
2462
+ useEffect(() => {
2463
+ if (!open) return;
2464
+ const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2465
+ const focusable = () => Array.from(
2466
+ panelRef.current?.querySelectorAll(
2467
+ 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
2468
+ ) ?? []
2469
+ );
2470
+ const onKey = (event) => {
2471
+ if (event.key === "Escape") {
2472
+ onClose();
2473
+ return;
2474
+ }
2475
+ if (event.key !== "Tab") return;
2476
+ const items = focusable();
2477
+ if (items.length === 0) return;
2478
+ const first = items[0];
2479
+ const last = items[items.length - 1];
2480
+ const active = document.activeElement;
2481
+ if (event.shiftKey && (active === first || !panelRef.current?.contains(active))) {
2482
+ event.preventDefault();
2483
+ last?.focus();
2484
+ } else if (!event.shiftKey && active === last) {
2485
+ event.preventDefault();
2486
+ first?.focus();
2487
+ }
2488
+ };
2489
+ document.addEventListener("keydown", onKey);
2490
+ closeRef.current?.focus();
2491
+ return () => {
2492
+ document.removeEventListener("keydown", onKey);
2493
+ opener?.focus();
2494
+ };
2495
+ }, [open, onClose]);
2496
+ if (!open) return null;
2497
+ return /* @__PURE__ */ jsx(
2498
+ "div",
2499
+ {
2500
+ "data-boff-explore": "disclaimer-dialog",
2501
+ role: "dialog",
2502
+ "aria-modal": "true",
2503
+ "aria-labelledby": titleId,
2504
+ className: "fixed inset-0 z-50 flex items-end justify-center bg-foreground/40 p-4 backdrop-blur-sm sm:items-center",
2505
+ onClick: onClose,
2506
+ children: /* @__PURE__ */ jsxs(
2507
+ "div",
2508
+ {
2509
+ ref: panelRef,
2510
+ className: "w-full max-w-md rounded-2xl border border-border bg-card p-5 shadow-xl",
2511
+ onClick: (event) => event.stopPropagation(),
2512
+ children: [
2513
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2514
+ /* @__PURE__ */ jsx(
2515
+ "span",
2516
+ {
2517
+ "aria-hidden": "true",
2518
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
2519
+ children: /* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4" })
2520
+ }
2521
+ ),
2522
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2523
+ /* @__PURE__ */ jsx(
2524
+ "p",
2525
+ {
2526
+ id: titleId,
2527
+ className: "text-sm font-semibold text-card-foreground",
2528
+ children: "How this assistant works"
2529
+ }
2530
+ ),
2531
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2 text-xs leading-relaxed text-muted-foreground", children: [
2532
+ /* @__PURE__ */ jsx("p", { children: "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources." }),
2533
+ /* @__PURE__ */ 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." }),
2534
+ captchaOn ? /* @__PURE__ */ jsx("p", { children: "Protected by reCAPTCHA \u2014 the Google Privacy Policy and Terms of Service apply." }) : null
2535
+ ] })
2536
+ ] })
2537
+ ] }),
2538
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsx(
2539
+ Button,
2540
+ {
2541
+ ref: closeRef,
2542
+ size: "sm",
2543
+ variant: "secondary",
2544
+ onClick: onClose,
2545
+ children: "Got it"
2546
+ }
2547
+ ) })
2548
+ ]
2549
+ }
2550
+ )
2551
+ }
2552
+ );
2553
+ }
2123
2554
  function WelcomeState({
2124
2555
  title,
2125
2556
  body,
@@ -2130,21 +2561,19 @@ function WelcomeState({
2130
2561
  onFocus,
2131
2562
  disabled
2132
2563
  }) {
2133
- const [showAllProducts, setShowAllProducts] = useState(false);
2134
- const visibleProducts = showAllProducts ? products : products.slice(0, PRODUCT_CHIP_PREVIEW);
2135
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-10 sm:py-14", children: [
2564
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-8 sm:py-12", children: [
2136
2565
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2137
2566
  /* @__PURE__ */ 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: [
2138
2567
  /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" }),
2139
2568
  "AI answers, grounded in our documentation"
2140
2569
  ] }),
2141
- /* @__PURE__ */ jsx("h1", { className: "mt-5 text-balance text-3xl font-bold tracking-tight sm:text-4xl", children: title }),
2142
- /* @__PURE__ */ jsx("p", { className: "mx-auto mt-3 max-w-2xl text-pretty text-base leading-relaxed text-muted-foreground sm:text-lg", children: body })
2570
+ /* @__PURE__ */ jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
2571
+ /* @__PURE__ */ 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 })
2143
2572
  ] }),
2144
- /* @__PURE__ */ jsx("ul", { className: "mt-9 grid gap-3 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxs(
2573
+ /* @__PURE__ */ jsx("ul", { className: "mt-7 grid gap-2.5 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxs(
2145
2574
  "li",
2146
2575
  {
2147
- className: "rounded-xl border border-border bg-card p-4 text-left",
2576
+ className: "rounded-xl border border-border/70 bg-card/60 p-3.5 text-left transition-colors hover:border-border",
2148
2577
  children: [
2149
2578
  /* @__PURE__ */ jsx("span", { className: "mb-2.5 flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }) }),
2150
2579
  /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-card-foreground", children: capTitle }),
@@ -2153,72 +2582,27 @@ function WelcomeState({
2153
2582
  },
2154
2583
  capTitle
2155
2584
  )) }),
2156
- prompts.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-9", children: [
2157
- /* @__PURE__ */ jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
2158
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsxs(
2159
- "button",
2585
+ prompts.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-8", children: [
2586
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
2587
+ /* @__PURE__ */ jsx(
2588
+ PromptCarousel,
2160
2589
  {
2161
- "data-boff-explore": "chip",
2162
- "data-chip-kind": "prompt",
2163
- type: "button",
2164
- disabled,
2165
- onClick: () => onPrompt(prompt),
2166
- 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",
2167
- children: [
2168
- /* @__PURE__ */ jsx("span", { className: "truncate", children: prompt }),
2169
- /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2170
- ]
2171
- },
2172
- prompt
2173
- )) })
2590
+ prompts,
2591
+ onPrompt,
2592
+ disabled
2593
+ }
2594
+ )
2174
2595
  ] }) : null,
2175
- products.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-8", children: [
2176
- /* @__PURE__ */ jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
2177
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2178
- /* @__PURE__ */ jsx(
2179
- "button",
2180
- {
2181
- "data-boff-explore": "chip",
2182
- "data-chip-kind": "product",
2183
- "data-product": "all",
2184
- type: "button",
2185
- "aria-pressed": focusProduct === null,
2186
- onClick: () => onFocus(null),
2187
- className: cn(
2188
- "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",
2189
- focusProduct === null ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2190
- ),
2191
- children: "All products"
2192
- }
2193
- ),
2194
- visibleProducts.map((product) => /* @__PURE__ */ jsx(
2195
- "button",
2196
- {
2197
- "data-boff-explore": "chip",
2198
- "data-chip-kind": "product",
2199
- "data-product": product.slug,
2200
- type: "button",
2201
- "aria-pressed": focusProduct === product.slug,
2202
- title: product.tagline ?? product.name,
2203
- onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2204
- className: cn(
2205
- "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",
2206
- focusProduct === product.slug ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2207
- ),
2208
- children: product.name
2209
- },
2210
- product.slug
2211
- )),
2212
- products.length > PRODUCT_CHIP_PREVIEW ? /* @__PURE__ */ jsx(
2213
- "button",
2214
- {
2215
- type: "button",
2216
- onClick: () => setShowAllProducts((v) => !v),
2217
- 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",
2218
- children: showAllProducts ? "Show fewer" : `Show all ${products.length}`
2219
- }
2220
- ) : null
2221
- ] })
2596
+ products.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
2597
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
2598
+ /* @__PURE__ */ jsx(
2599
+ ProductStrip,
2600
+ {
2601
+ products,
2602
+ focusProduct,
2603
+ onFocus
2604
+ }
2605
+ )
2222
2606
  ] }) : null
2223
2607
  ] });
2224
2608
  }
@@ -2255,7 +2639,8 @@ function ExplorePage({
2255
2639
  history,
2256
2640
  openConversation,
2257
2641
  deleteConversation,
2258
- clearHistory
2642
+ clearHistory,
2643
+ recordClick
2259
2644
  } = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
2260
2645
  const [draft, setDraft] = useState("");
2261
2646
  const textareaRef = useRef(null);
@@ -2274,6 +2659,7 @@ function ExplorePage({
2274
2659
  }
2275
2660
  });
2276
2661
  speechStopRef.current = speech.stop;
2662
+ const [disclaimerOpen, setDisclaimerOpen] = useState(false);
2277
2663
  const scrollRef = useRef(null);
2278
2664
  const latestAssistantRef = useRef(null);
2279
2665
  const alignedForRef = useRef(null);
@@ -2366,7 +2752,7 @@ function ExplorePage({
2366
2752
  "data-boff-explore": "page",
2367
2753
  "data-phase": phase,
2368
2754
  className: cn(
2369
- "flex w-full flex-col bg-background text-foreground",
2755
+ "flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
2370
2756
  heightMode === "auto" && "min-h-[70vh]",
2371
2757
  className
2372
2758
  ),
@@ -2508,7 +2894,7 @@ function ExplorePage({
2508
2894
  role: "log",
2509
2895
  "aria-live": "polite",
2510
2896
  "aria-label": "Explore conversation",
2511
- className: "boff-explore-scroll min-h-0 flex-1 overflow-y-auto",
2897
+ className: "boff-explore-scroll min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden",
2512
2898
  children: showWelcome ? /* @__PURE__ */ jsx(
2513
2899
  WelcomeState,
2514
2900
  {
@@ -2521,21 +2907,31 @@ function ExplorePage({
2521
2907
  onFocus: setFocusProduct,
2522
2908
  disabled: !canSend || busy
2523
2909
  }
2524
- ) : /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-3xl space-y-6 px-4 py-6", children: messages.map(
2910
+ ) : /* @__PURE__ */ jsx("div", { className: "mx-auto w-full min-w-0 max-w-3xl space-y-6 px-4 py-6", children: messages.map(
2525
2911
  (message) => message.role === "USER" ? /* @__PURE__ */ jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsx(
2526
2912
  AssistantBubble,
2527
2913
  {
2528
2914
  message,
2529
2915
  activity,
2530
2916
  onNavigate,
2531
- anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0
2917
+ anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
2918
+ onFollow: (kind, url) => recordClick(message.id, kind, url),
2919
+ onFollowUp: sendPrompt
2532
2920
  },
2533
2921
  message.id
2534
2922
  )
2535
2923
  ) })
2536
2924
  }
2537
2925
  ),
2538
- /* @__PURE__ */ jsx("div", { className: "border-t border-border bg-background px-4 pb-4 pt-3", children: /* @__PURE__ */ jsxs(
2926
+ /* @__PURE__ */ jsx(
2927
+ DisclaimerDialog,
2928
+ {
2929
+ open: disclaimerOpen,
2930
+ onClose: () => setDisclaimerOpen(false),
2931
+ captchaOn
2932
+ }
2933
+ ),
2934
+ /* @__PURE__ */ 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__ */ jsxs(
2539
2935
  "form",
2540
2936
  {
2541
2937
  "data-boff-explore": "composer",
@@ -2550,7 +2946,7 @@ function ExplorePage({
2550
2946
  "div",
2551
2947
  {
2552
2948
  className: cn(
2553
- "flex items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-colors",
2949
+ "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
2554
2950
  overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
2555
2951
  ),
2556
2952
  children: [
@@ -2571,7 +2967,7 @@ function ExplorePage({
2571
2967
  },
2572
2968
  "aria-label": "Ask a question",
2573
2969
  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",
2574
- 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"
2970
+ 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"
2575
2971
  }
2576
2972
  ),
2577
2973
  speech.supported ? /* @__PURE__ */ jsx(
@@ -2662,48 +3058,19 @@ function ExplorePage({
2662
3058
  }
2663
3059
  )
2664
3060
  ] }),
2665
- /* @__PURE__ */ jsxs(
2666
- "p",
2667
- {
2668
- "data-boff-explore": "disclaimer",
2669
- className: "mt-2 text-xs leading-relaxed text-muted-foreground",
2670
- children: [
2671
- "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
2672
- " ",
2673
- "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.",
2674
- captchaOn ? /* @__PURE__ */ jsxs(Fragment, { children: [
2675
- " ",
2676
- "This site is protected by reCAPTCHA; the Google",
2677
- " ",
2678
- /* @__PURE__ */ jsx(
2679
- "a",
2680
- {
2681
- href: "https://policies.google.com/privacy",
2682
- target: "_blank",
2683
- rel: "noopener noreferrer",
2684
- className: "underline underline-offset-2 hover:text-foreground",
2685
- children: "Privacy Policy"
2686
- }
2687
- ),
2688
- " ",
2689
- "and",
2690
- " ",
2691
- /* @__PURE__ */ jsx(
2692
- "a",
2693
- {
2694
- href: "https://policies.google.com/terms",
2695
- target: "_blank",
2696
- rel: "noopener noreferrer",
2697
- className: "underline underline-offset-2 hover:text-foreground",
2698
- children: "Terms of Service"
2699
- }
2700
- ),
2701
- " ",
2702
- "apply."
2703
- ] }) : null
2704
- ]
2705
- }
2706
- )
3061
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
3062
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: "Grounded in our docs \u2014 check anything important." }),
3063
+ /* @__PURE__ */ jsx(
3064
+ "button",
3065
+ {
3066
+ type: "button",
3067
+ "data-boff-explore": "disclaimer",
3068
+ onClick: () => setDisclaimerOpen(true),
3069
+ className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3070
+ children: "Disclaimer"
3071
+ }
3072
+ )
3073
+ ] })
2707
3074
  ]
2708
3075
  }
2709
3076
  ) })