@burdenoff/website-sdk 2026.829.1 → 2026.829.3

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,6 +1,6 @@
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, ChevronLeft, ChevronRight, ExternalLink } from 'lucide-react';
3
+ import { Sparkles, RotateCcw, Link2, History, Trash2, Mic, Square, ArrowUp, BookOpenText, Layers, 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';
@@ -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,26 @@ 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
+ );
551
+ var CREATE_EXPLORE_SHARE_LINK_MUTATION = (
552
+ /* GraphQL */
553
+ `
554
+ mutation CreateExploreShareLink($conversationToken: String!) {
555
+ createExploreShareLink(conversationToken: $conversationToken) {
556
+ shareToken
557
+ }
558
+ }
559
+ `
560
+ );
538
561
 
539
562
  // src/data/products.ts
540
563
  var ECOSYSTEM_PRODUCTS = [
@@ -1467,6 +1490,30 @@ function useExploreChat(options = {}) {
1467
1490
  return null;
1468
1491
  }, [messages]);
1469
1492
  const canSend = (phase === "ready" || phase === "error") && catalog.enabled && turnCount < catalog.limits.maxTurnsPerConversation;
1493
+ const createShareLink = useCallback(async () => {
1494
+ const token = conversationTokenRef.current;
1495
+ if (!token) return null;
1496
+ try {
1497
+ const result = await client.mutate(CREATE_EXPLORE_SHARE_LINK_MUTATION, { conversationToken: token });
1498
+ return result?.data?.createExploreShareLink?.shareToken ?? null;
1499
+ } catch {
1500
+ return null;
1501
+ }
1502
+ }, [client]);
1503
+ const recordClick = useCallback(
1504
+ (messageId, kind, url) => {
1505
+ const token = conversationTokenRef.current;
1506
+ if (!token) return;
1507
+ try {
1508
+ void client.mutate(RECORD_EXPLORE_CLICK_MUTATION, {
1509
+ input: { conversationToken: token, messageId, kind, url }
1510
+ }).catch(() => {
1511
+ });
1512
+ } catch {
1513
+ }
1514
+ },
1515
+ [client]
1516
+ );
1470
1517
  return {
1471
1518
  phase,
1472
1519
  catalog,
@@ -1484,7 +1531,9 @@ function useExploreChat(options = {}) {
1484
1531
  history: archive,
1485
1532
  openConversation,
1486
1533
  deleteConversation,
1487
- clearHistory
1534
+ clearHistory,
1535
+ recordClick,
1536
+ createShareLink
1488
1537
  };
1489
1538
  }
1490
1539
  var optimisticCounter = 0;
@@ -1981,7 +2030,10 @@ function logoUrlOf(url) {
1981
2030
  return null;
1982
2031
  }
1983
2032
  }
1984
- function ReferenceCard({ reference }) {
2033
+ function ReferenceCard({
2034
+ reference,
2035
+ onFollow
2036
+ }) {
1985
2037
  const [imageFailed, setImageFailed] = useState(false);
1986
2038
  const [logoFailed, setLogoFailed] = useState(false);
1987
2039
  const host = hostnameOf(reference.url);
@@ -2003,9 +2055,11 @@ function ReferenceCard({ reference }) {
2003
2055
  "a",
2004
2056
  {
2005
2057
  "data-boff-explore": "reference",
2058
+ "data-index": reference.index ?? void 0,
2006
2059
  href: reference.url,
2007
2060
  target: "_blank",
2008
2061
  rel: "noopener noreferrer",
2062
+ onClick: () => onFollow?.(reference.url),
2009
2063
  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",
2010
2064
  children: [
2011
2065
  /* @__PURE__ */ jsx("div", { className: "relative h-11 w-11 shrink-0 overflow-hidden rounded-lg bg-muted sm:hidden", children: logoImg }),
@@ -2029,7 +2083,10 @@ function ReferenceCard({ reference }) {
2029
2083
  }
2030
2084
  ) }) : letterPlate }),
2031
2085
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 sm:gap-1.5 sm:p-3", children: [
2032
- /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
2086
+ /* @__PURE__ */ jsxs("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: [
2087
+ 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,
2088
+ reference.title || host || reference.url
2089
+ ] }),
2033
2090
  reference.description ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground max-sm:hidden", children: reference.description }) : null,
2034
2091
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:mt-auto sm:pt-2", children: [
2035
2092
  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,
@@ -2045,7 +2102,8 @@ function ReferenceCard({ reference }) {
2045
2102
  }
2046
2103
  function CtaButton({
2047
2104
  cta,
2048
- onNavigate
2105
+ onNavigate,
2106
+ onFollow
2049
2107
  }) {
2050
2108
  const variant = CTA_VARIANTS[cta.kind] ?? "outline";
2051
2109
  const internal = isInternalPath(cta.url);
@@ -2058,7 +2116,10 @@ function CtaButton({
2058
2116
  type: "button",
2059
2117
  size: "sm",
2060
2118
  variant,
2061
- onClick: () => onNavigate(cta.url),
2119
+ onClick: () => {
2120
+ onFollow?.(cta.url);
2121
+ onNavigate(cta.url);
2122
+ },
2062
2123
  children: cta.label
2063
2124
  }
2064
2125
  );
@@ -2069,6 +2130,7 @@ function CtaButton({
2069
2130
  "data-boff-explore": "cta",
2070
2131
  "data-kind": cta.kind,
2071
2132
  href: cta.url,
2133
+ onClick: () => onFollow?.(cta.url),
2072
2134
  ...internal ? {} : { target: "_blank", rel: "noopener noreferrer" },
2073
2135
  children: [
2074
2136
  cta.label,
@@ -2120,7 +2182,9 @@ function AssistantBubble({
2120
2182
  message,
2121
2183
  activity,
2122
2184
  onNavigate,
2123
- anchorRef
2185
+ anchorRef,
2186
+ onFollow,
2187
+ onFollowUp
2124
2188
  }) {
2125
2189
  const streaming = message.status === "PENDING" || message.status === "RUNNING";
2126
2190
  const body = message.content || message.partialContent || "";
@@ -2142,6 +2206,34 @@ function AssistantBubble({
2142
2206
  }
2143
2207
  ),
2144
2208
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
2209
+ message.status === "COMPLETED" && message.answered === false ? /* @__PURE__ */ jsxs(
2210
+ "div",
2211
+ {
2212
+ "data-boff-explore": "no-answer",
2213
+ 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",
2214
+ children: [
2215
+ /* @__PURE__ */ jsx(TriangleAlert, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-600 dark:text-amber-400" }),
2216
+ /* @__PURE__ */ jsxs("span", { children: [
2217
+ "I couldn't find this in our documentation, so the answer below is not grounded in a source. Try rephrasing, or",
2218
+ " ",
2219
+ /* @__PURE__ */ jsx(
2220
+ "a",
2221
+ {
2222
+ href: "/contact",
2223
+ className: "underline underline-offset-2 hover:text-primary",
2224
+ onClick: (event) => {
2225
+ if (!onNavigate) return;
2226
+ event.preventDefault();
2227
+ onNavigate("/contact");
2228
+ },
2229
+ children: "ask a human"
2230
+ }
2231
+ ),
2232
+ "."
2233
+ ] })
2234
+ ]
2235
+ }
2236
+ ) : null,
2145
2237
  body ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
2146
2238
  /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsx(Markdown, { children: body }) }),
2147
2239
  streaming ? /* @__PURE__ */ jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
@@ -2150,16 +2242,41 @@ function AssistantBubble({
2150
2242
  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,
2151
2243
  message.references.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
2152
2244
  /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Sources" }),
2153
- /* @__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)) })
2245
+ /* @__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(
2246
+ ReferenceCard,
2247
+ {
2248
+ reference,
2249
+ onFollow: (url) => onFollow?.("REFERENCE", url)
2250
+ },
2251
+ reference.url
2252
+ )) })
2154
2253
  ] }) : null,
2155
2254
  message.ctas.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 pt-0.5", children: message.ctas.map((cta) => /* @__PURE__ */ jsx(
2156
2255
  CtaButton,
2157
2256
  {
2158
2257
  cta,
2159
- onNavigate
2258
+ onNavigate,
2259
+ onFollow: (url) => onFollow?.("CTA", url)
2160
2260
  },
2161
2261
  `${cta.kind}-${cta.url}`
2162
- )) }) : null
2262
+ )) }) : null,
2263
+ message.status === "COMPLETED" && (message.followUps?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 pt-0.5", children: [
2264
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Next you could ask" }),
2265
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: (message.followUps ?? []).map((question) => /* @__PURE__ */ jsxs(
2266
+ "button",
2267
+ {
2268
+ "data-boff-explore": "follow-up",
2269
+ type: "button",
2270
+ onClick: () => onFollowUp?.(question),
2271
+ 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",
2272
+ children: [
2273
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: question }),
2274
+ /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2275
+ ]
2276
+ },
2277
+ question
2278
+ )) })
2279
+ ] }) : null
2163
2280
  ] })
2164
2281
  ]
2165
2282
  }
@@ -2233,6 +2350,9 @@ function PromptCarousel({
2233
2350
  "div",
2234
2351
  {
2235
2352
  className: "flex min-w-0 items-center gap-1.5",
2353
+ role: "group",
2354
+ "aria-roledescription": "carousel",
2355
+ "aria-label": "Example questions",
2236
2356
  onMouseEnter: () => setPaused(true),
2237
2357
  onMouseLeave: () => setPaused(false),
2238
2358
  onFocusCapture: () => setPaused(true),
@@ -2255,6 +2375,8 @@ function PromptCarousel({
2255
2375
  "data-chip-kind": "prompt",
2256
2376
  type: "button",
2257
2377
  disabled,
2378
+ "aria-label": `Ask: ${current}`,
2379
+ "aria-live": paused ? "polite" : "off",
2258
2380
  onClick: () => onPrompt(current),
2259
2381
  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",
2260
2382
  children: [
@@ -2289,72 +2411,108 @@ function ProductStrip({
2289
2411
  "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",
2290
2412
  active ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2291
2413
  );
2292
- return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
2293
- /* @__PURE__ */ jsxs(
2294
- "div",
2295
- {
2296
- className: cn(
2297
- "boff-explore-strip flex min-w-0 flex-1 gap-2",
2298
- expanded ? "flex-wrap" : "flex-nowrap overflow-x-auto"
2414
+ return /* @__PURE__ */ jsxs(
2415
+ "div",
2416
+ {
2417
+ className: "flex min-w-0 items-center gap-2",
2418
+ role: "group",
2419
+ "aria-label": "Focus the assistant on one product",
2420
+ children: [
2421
+ /* @__PURE__ */ jsxs(
2422
+ "div",
2423
+ {
2424
+ className: cn(
2425
+ "boff-explore-strip flex min-w-0 flex-1 gap-2",
2426
+ expanded ? "flex-wrap" : "flex-nowrap overflow-x-auto"
2427
+ ),
2428
+ children: [
2429
+ /* @__PURE__ */ jsx(
2430
+ "button",
2431
+ {
2432
+ "data-boff-explore": "chip",
2433
+ "data-chip-kind": "product",
2434
+ "data-product": "all",
2435
+ type: "button",
2436
+ "aria-pressed": focusProduct === null,
2437
+ onClick: () => onFocus(null),
2438
+ className: chipClass(focusProduct === null),
2439
+ children: "All products"
2440
+ }
2441
+ ),
2442
+ products.map((product) => /* @__PURE__ */ jsx(
2443
+ "button",
2444
+ {
2445
+ "data-boff-explore": "chip",
2446
+ "data-chip-kind": "product",
2447
+ "data-product": product.slug,
2448
+ type: "button",
2449
+ "aria-pressed": focusProduct === product.slug,
2450
+ title: product.tagline ?? product.name,
2451
+ onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2452
+ className: chipClass(focusProduct === product.slug),
2453
+ children: product.name
2454
+ },
2455
+ product.slug
2456
+ ))
2457
+ ]
2458
+ }
2299
2459
  ),
2300
- children: [
2301
- /* @__PURE__ */ jsx(
2302
- "button",
2303
- {
2304
- "data-boff-explore": "chip",
2305
- "data-chip-kind": "product",
2306
- "data-product": "all",
2307
- type: "button",
2308
- "aria-pressed": focusProduct === null,
2309
- onClick: () => onFocus(null),
2310
- className: chipClass(focusProduct === null),
2311
- children: "All products"
2312
- }
2313
- ),
2314
- products.map((product) => /* @__PURE__ */ jsx(
2315
- "button",
2316
- {
2317
- "data-boff-explore": "chip",
2318
- "data-chip-kind": "product",
2319
- "data-product": product.slug,
2320
- type: "button",
2321
- "aria-pressed": focusProduct === product.slug,
2322
- title: product.tagline ?? product.name,
2323
- onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2324
- className: chipClass(focusProduct === product.slug),
2325
- children: product.name
2326
- },
2327
- product.slug
2328
- ))
2329
- ]
2330
- }
2331
- ),
2332
- products.length > 0 ? /* @__PURE__ */ jsx(
2333
- "button",
2334
- {
2335
- type: "button",
2336
- "data-boff-explore": "products-toggle",
2337
- onClick: () => setExpanded((v) => !v),
2338
- 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",
2339
- children: expanded ? "Show less" : `All ${products.length}`
2340
- }
2341
- ) : null
2342
- ] });
2460
+ products.length > 0 ? /* @__PURE__ */ jsx(
2461
+ "button",
2462
+ {
2463
+ type: "button",
2464
+ "data-boff-explore": "products-toggle",
2465
+ "aria-expanded": expanded,
2466
+ onClick: () => setExpanded((v) => !v),
2467
+ 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",
2468
+ children: expanded ? "Show less" : `All ${products.length}`
2469
+ }
2470
+ ) : null
2471
+ ]
2472
+ }
2473
+ );
2343
2474
  }
2344
2475
  function DisclaimerDialog({
2345
2476
  open,
2346
2477
  onClose,
2347
2478
  captchaOn
2348
2479
  }) {
2480
+ const panelRef = useRef(null);
2349
2481
  const closeRef = useRef(null);
2482
+ const titleId = "boff-explore-disclaimer-title";
2350
2483
  useEffect(() => {
2351
2484
  if (!open) return;
2485
+ const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2486
+ const focusable = () => Array.from(
2487
+ panelRef.current?.querySelectorAll(
2488
+ 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
2489
+ ) ?? []
2490
+ );
2352
2491
  const onKey = (event) => {
2353
- if (event.key === "Escape") onClose();
2492
+ if (event.key === "Escape") {
2493
+ onClose();
2494
+ return;
2495
+ }
2496
+ if (event.key !== "Tab") return;
2497
+ const items = focusable();
2498
+ if (items.length === 0) return;
2499
+ const first = items[0];
2500
+ const last = items[items.length - 1];
2501
+ const active = document.activeElement;
2502
+ if (event.shiftKey && (active === first || !panelRef.current?.contains(active))) {
2503
+ event.preventDefault();
2504
+ last?.focus();
2505
+ } else if (!event.shiftKey && active === last) {
2506
+ event.preventDefault();
2507
+ first?.focus();
2508
+ }
2354
2509
  };
2355
2510
  document.addEventListener("keydown", onKey);
2356
2511
  closeRef.current?.focus();
2357
- return () => document.removeEventListener("keydown", onKey);
2512
+ return () => {
2513
+ document.removeEventListener("keydown", onKey);
2514
+ opener?.focus();
2515
+ };
2358
2516
  }, [open, onClose]);
2359
2517
  if (!open) return null;
2360
2518
  return /* @__PURE__ */ jsx(
@@ -2363,19 +2521,34 @@ function DisclaimerDialog({
2363
2521
  "data-boff-explore": "disclaimer-dialog",
2364
2522
  role: "dialog",
2365
2523
  "aria-modal": "true",
2366
- "aria-label": "How this assistant works",
2524
+ "aria-labelledby": titleId,
2367
2525
  className: "fixed inset-0 z-50 flex items-end justify-center bg-foreground/40 p-4 backdrop-blur-sm sm:items-center",
2368
2526
  onClick: onClose,
2369
2527
  children: /* @__PURE__ */ jsxs(
2370
2528
  "div",
2371
2529
  {
2530
+ ref: panelRef,
2372
2531
  className: "w-full max-w-md rounded-2xl border border-border bg-card p-5 shadow-xl",
2373
2532
  onClick: (event) => event.stopPropagation(),
2374
2533
  children: [
2375
2534
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2376
- /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4" }) }),
2535
+ /* @__PURE__ */ jsx(
2536
+ "span",
2537
+ {
2538
+ "aria-hidden": "true",
2539
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
2540
+ children: /* @__PURE__ */ jsx(TriangleAlert, { className: "h-4 w-4" })
2541
+ }
2542
+ ),
2377
2543
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2378
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-card-foreground", children: "How this assistant works" }),
2544
+ /* @__PURE__ */ jsx(
2545
+ "p",
2546
+ {
2547
+ id: titleId,
2548
+ className: "text-sm font-semibold text-card-foreground",
2549
+ children: "How this assistant works"
2550
+ }
2551
+ ),
2379
2552
  /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2 text-xs leading-relaxed text-muted-foreground", children: [
2380
2553
  /* @__PURE__ */ jsx("p", { children: "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources." }),
2381
2554
  /* @__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." }),
@@ -2487,7 +2660,9 @@ function ExplorePage({
2487
2660
  history,
2488
2661
  openConversation,
2489
2662
  deleteConversation,
2490
- clearHistory
2663
+ clearHistory,
2664
+ recordClick,
2665
+ createShareLink
2491
2666
  } = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
2492
2667
  const [draft, setDraft] = useState("");
2493
2668
  const textareaRef = useRef(null);
@@ -2507,6 +2682,7 @@ function ExplorePage({
2507
2682
  });
2508
2683
  speechStopRef.current = speech.stop;
2509
2684
  const [disclaimerOpen, setDisclaimerOpen] = useState(false);
2685
+ const [shareLabel, setShareLabel] = useState("Share");
2510
2686
  const scrollRef = useRef(null);
2511
2687
  const latestAssistantRef = useRef(null);
2512
2688
  const alignedForRef = useRef(null);
@@ -2561,6 +2737,25 @@ function ExplorePage({
2561
2737
  if (!el) return;
2562
2738
  el.scrollTop = el.scrollHeight;
2563
2739
  }, [activity, latestAssistantId, messages]);
2740
+ const handleShare = useCallback(async () => {
2741
+ setShareLabel("Copying\u2026");
2742
+ const token = await createShareLink();
2743
+ if (!token) {
2744
+ setShareLabel("Unavailable");
2745
+ setTimeout(() => setShareLabel("Share"), 2500);
2746
+ return;
2747
+ }
2748
+ const base = (pageUrl ?? window.location.href).split("?")[0];
2749
+ const url = `${base}?c=${encodeURIComponent(token)}`;
2750
+ try {
2751
+ await navigator.clipboard.writeText(url);
2752
+ setShareLabel("Copied");
2753
+ } catch {
2754
+ window.prompt("Copy this link", url);
2755
+ setShareLabel("Share");
2756
+ }
2757
+ setTimeout(() => setShareLabel("Share"), 2500);
2758
+ }, [createShareLink, pageUrl]);
2564
2759
  const submitDraft = useCallback(() => {
2565
2760
  const text = draft.trim();
2566
2761
  if (!text || overLimit || busy || !canSend) return;
@@ -2643,6 +2838,24 @@ function ExplorePage({
2643
2838
  ]
2644
2839
  }
2645
2840
  ),
2841
+ messages.length > 0 ? /* @__PURE__ */ jsxs(
2842
+ Button,
2843
+ {
2844
+ "data-boff-explore": "share",
2845
+ type: "button",
2846
+ size: "sm",
2847
+ variant: "ghost",
2848
+ className: "shrink-0 text-muted-foreground",
2849
+ title: "Copy a read-only link to this conversation",
2850
+ onClick: () => {
2851
+ void handleShare();
2852
+ },
2853
+ children: [
2854
+ /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5" }),
2855
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: shareLabel })
2856
+ ]
2857
+ }
2858
+ ) : null,
2646
2859
  history.length > 0 ? /* @__PURE__ */ jsxs(
2647
2860
  Button,
2648
2861
  {
@@ -2761,7 +2974,9 @@ function ExplorePage({
2761
2974
  message,
2762
2975
  activity,
2763
2976
  onNavigate,
2764
- anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0
2977
+ anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
2978
+ onFollow: (kind, url) => recordClick(message.id, kind, url),
2979
+ onFollowUp: sendPrompt
2765
2980
  },
2766
2981
  message.id
2767
2982
  )