@burdenoff/website-sdk 2026.829.5 → 2026.829.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -73,6 +73,12 @@ var EN = {
73
73
  disclaimerShort: "Grounded in our docs \u2014 check anything important.",
74
74
  dictate: "Dictate your question",
75
75
  stopDictating: "Stop dictating",
76
+ attach: "Attach files",
77
+ attachHint: "Up to {{max}} files, {{size}} MB each",
78
+ removeAttachment: "Remove {{name}}",
79
+ attachmentNameOnly: "name and type only",
80
+ attachmentTooLarge: "{{name}} is larger than {{size}} MB and was not attached.",
81
+ attachmentTooMany: "Only {{max}} files can be attached, so {{name}} was skipped.",
76
82
  noAnswer: "I couldn't find this in our documentation, so the answer below is not grounded in a source. Try rephrasing, or",
77
83
  askHuman: "ask a human",
78
84
  prevSuggestion: "Previous suggestion",
@@ -115,6 +121,12 @@ var ZH = {
115
121
  disclaimerShort: "\u5185\u5BB9\u57FA\u4E8E\u6211\u4EEC\u7684\u6587\u6863\uFF0C\u91CD\u8981\u4FE1\u606F\u8BF7\u81EA\u884C\u6838\u5BF9\u3002",
116
122
  dictate: "\u8BED\u97F3\u8F93\u5165",
117
123
  stopDictating: "\u505C\u6B62\u8BED\u97F3\u8F93\u5165",
124
+ attach: "\u6DFB\u52A0\u6587\u4EF6",
125
+ attachHint: "\u6700\u591A {{max}} \u4E2A\u6587\u4EF6\uFF0C\u6BCF\u4E2A\u4E0D\u8D85\u8FC7 {{size}} MB",
126
+ removeAttachment: "\u79FB\u9664 {{name}}",
127
+ attachmentNameOnly: "\u4EC5\u6587\u4EF6\u540D\u548C\u7C7B\u578B",
128
+ attachmentTooLarge: "{{name}} \u8D85\u8FC7 {{size}} MB\uFF0C\u672A\u80FD\u6DFB\u52A0\u3002",
129
+ attachmentTooMany: "\u6700\u591A\u53EA\u80FD\u6DFB\u52A0 {{max}} \u4E2A\u6587\u4EF6\uFF0C\u5DF2\u8DF3\u8FC7 {{name}}\u3002",
118
130
  noAnswer: "\u6211\u5728\u6587\u6863\u4E2D\u6CA1\u6709\u627E\u5230\u76F8\u5173\u5185\u5BB9\uFF0C\u4E0B\u9762\u7684\u56DE\u7B54\u6CA1\u6709\u6765\u6E90\u652F\u6301\u3002\u8BF7\u6362\u4E2A\u8BF4\u6CD5\uFF0C\u6216",
119
131
  askHuman: "\u8054\u7CFB\u6211\u4EEC",
120
132
  prevSuggestion: "\u4E0A\u4E00\u4E2A\u5EFA\u8BAE",
@@ -153,6 +165,12 @@ var HI = {
153
165
  disclaimerShort: "\u092F\u0939 \u0939\u092E\u093E\u0930\u0947 \u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C\u094B\u0902 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0939\u0948 \u2014 \u092E\u0939\u0924\u094D\u0935\u092A\u0942\u0930\u094D\u0923 \u092C\u093E\u0924\u0947\u0902 \u091C\u093C\u0930\u0942\u0930 \u091C\u093E\u0901\u091A \u0932\u0947\u0902\u0964",
154
166
  dictate: "\u092C\u094B\u0932\u0915\u0930 \u092A\u0942\u091B\u0947\u0902",
155
167
  stopDictating: "\u092C\u094B\u0932\u0928\u093E \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
168
+ attach: "\u092B\u093C\u093E\u0907\u0932\u0947\u0902 \u091C\u094B\u0921\u093C\u0947\u0902",
169
+ attachHint: "\u0905\u0927\u093F\u0915\u0924\u092E {{max}} \u092B\u093C\u093E\u0907\u0932\u0947\u0902, \u092A\u094D\u0930\u0924\u094D\u092F\u0947\u0915 {{size}} MB \u0924\u0915",
170
+ removeAttachment: "{{name}} \u0939\u091F\u093E\u090F\u0901",
171
+ attachmentNameOnly: "\u0915\u0947\u0935\u0932 \u0928\u093E\u092E \u0914\u0930 \u092A\u094D\u0930\u0915\u093E\u0930",
172
+ attachmentTooLarge: "{{name}} {{size}} MB \u0938\u0947 \u092C\u0921\u093C\u0940 \u0939\u0948, \u0907\u0938\u0932\u093F\u090F \u091C\u094B\u0921\u093C\u0940 \u0928\u0939\u0940\u0902 \u0917\u0908\u0964",
173
+ attachmentTooMany: "\u0915\u0947\u0935\u0932 {{max}} \u092B\u093C\u093E\u0907\u0932\u0947\u0902 \u091C\u094B\u0921\u093C\u0940 \u091C\u093E \u0938\u0915\u0924\u0940 \u0939\u0948\u0902, \u0907\u0938\u0932\u093F\u090F {{name}} \u091B\u094B\u0921\u093C \u0926\u0940 \u0917\u0908\u0964",
156
174
  noAnswer: "\u092F\u0939 \u092E\u0941\u091D\u0947 \u0939\u092E\u093E\u0930\u0947 \u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C\u094B\u0902 \u092E\u0947\u0902 \u0928\u0939\u0940\u0902 \u092E\u093F\u0932\u093E, \u0907\u0938\u0932\u093F\u090F \u0928\u0940\u091A\u0947 \u0926\u093F\u092F\u093E \u0909\u0924\u094D\u0924\u0930 \u0915\u093F\u0938\u0940 \u0938\u094D\u0930\u094B\u0924 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0928\u0939\u0940\u0902 \u0939\u0948\u0964 \u0926\u0942\u0938\u0930\u0947 \u0936\u092C\u094D\u0926\u094B\u0902 \u092E\u0947\u0902 \u092A\u0942\u091B\u0947\u0902, \u092F\u093E",
157
175
  askHuman: "\u0939\u092E\u0938\u0947 \u0938\u0902\u092A\u0930\u094D\u0915 \u0915\u0930\u0947\u0902",
158
176
  prevSuggestion: "\u092A\u093F\u091B\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
@@ -195,6 +213,12 @@ var ES = {
195
213
  disclaimerShort: "Basado en nuestra documentaci\xF3n: comprueba cualquier dato importante.",
196
214
  dictate: "Dictar tu pregunta",
197
215
  stopDictating: "Dejar de dictar",
216
+ attach: "Adjuntar archivos",
217
+ attachHint: "Hasta {{max}} archivos, {{size}} MB cada uno",
218
+ removeAttachment: "Quitar {{name}}",
219
+ attachmentNameOnly: "solo nombre y tipo",
220
+ attachmentTooLarge: "{{name}} supera los {{size}} MB y no se adjunt\xF3.",
221
+ attachmentTooMany: "Solo se pueden adjuntar {{max}} archivos, as\xED que se omiti\xF3 {{name}}.",
198
222
  noAnswer: "No he encontrado esto en nuestra documentaci\xF3n, as\xED que la respuesta de abajo no se apoya en ninguna fuente. Prueba a reformularla, o",
199
223
  askHuman: "habla con una persona",
200
224
  prevSuggestion: "Sugerencia anterior",
@@ -237,6 +261,12 @@ var AR = {
237
261
  disclaimerShort: "\u0645\u0628\u0646\u064A \u0639\u0644\u0649 \u0648\u062B\u0627\u0626\u0642\u0646\u0627 \u2014 \u062A\u062D\u0642\u0651\u0642 \u0645\u0646 \u0623\u064A \u0645\u0639\u0644\u0648\u0645\u0629 \u0645\u0647\u0645\u0629.",
238
262
  dictate: "\u0623\u0645\u0644\u0650 \u0633\u0624\u0627\u0644\u0643",
239
263
  stopDictating: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0625\u0645\u0644\u0627\u0621",
264
+ attach: "\u0625\u0631\u0641\u0627\u0642 \u0645\u0644\u0641\u0627\u062A",
265
+ attachHint: "\u062D\u062A\u0649 {{max}} \u0645\u0644\u0641\u0627\u062A\u060C \u0628\u062D\u062F \u0623\u0642\u0635\u0649 {{size}} \u0645\u064A\u063A\u0627\u0628\u0627\u064A\u062A \u0644\u0643\u0644 \u0645\u0644\u0641",
266
+ removeAttachment: "\u0625\u0632\u0627\u0644\u0629 {{name}}",
267
+ attachmentNameOnly: "\u0627\u0644\u0627\u0633\u0645 \u0648\u0627\u0644\u0646\u0648\u0639 \u0641\u0642\u0637",
268
+ attachmentTooLarge: "{{name}} \u0623\u0643\u0628\u0631 \u0645\u0646 {{size}} \u0645\u064A\u063A\u0627\u0628\u0627\u064A\u062A \u0648\u0644\u0645 \u064A\u062A\u0645 \u0625\u0631\u0641\u0627\u0642\u0647.",
269
+ attachmentTooMany: "\u064A\u0645\u0643\u0646 \u0625\u0631\u0641\u0627\u0642 {{max}} \u0645\u0644\u0641\u0627\u062A \u0641\u0642\u0637\u060C \u0644\u0630\u0627 \u062A\u0645 \u062A\u062E\u0637\u064A {{name}}.",
240
270
  noAnswer: "\u0644\u0645 \u0623\u062C\u062F \u0647\u0630\u0627 \u0641\u064A \u0648\u062B\u0627\u0626\u0642\u0646\u0627\u060C \u0644\u0630\u0627 \u0641\u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0623\u062F\u0646\u0627\u0647 \u063A\u064A\u0631 \u0645\u0633\u062A\u0646\u062F\u0629 \u0625\u0644\u0649 \u0645\u0635\u062F\u0631. \u062D\u0627\u0648\u0644 \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0635\u064A\u0627\u063A\u0629\u060C \u0623\u0648",
241
271
  askHuman: "\u062A\u062D\u062F\u0651\u062B \u0625\u0644\u0649 \u0634\u062E\u0635",
242
272
  prevSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u0633\u0627\u0628\u0642",
@@ -279,6 +309,12 @@ var TA = {
279
309
  disclaimerShort: "\u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B86\u0BB5\u0BA3\u0B99\u0BCD\u0B95\u0BB3\u0BC8 \u0B85\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0B9F\u0BC8\u0BAF\u0BBE\u0B95\u0B95\u0BCD \u0B95\u0BCA\u0BA3\u0BCD\u0B9F\u0BA4\u0BC1 \u2014 \u0BAE\u0BC1\u0B95\u0BCD\u0B95\u0BBF\u0BAF\u0BAE\u0BBE\u0BA9\u0BA4\u0BC8\u0B9A\u0BCD \u0B9A\u0BB0\u0BBF\u0BAA\u0BBE\u0BB0\u0BCD\u0B95\u0BCD\u0B95\u0BB5\u0BC1\u0BAE\u0BCD.",
280
310
  dictate: "\u0B89\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B95\u0BC7\u0BB3\u0BCD\u0BB5\u0BBF\u0BAF\u0BC8\u0BAA\u0BCD \u0BAA\u0BC7\u0B9A\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
281
311
  stopDictating: "\u0BAA\u0BC7\u0B9A\u0BC1\u0BB5\u0BA4\u0BC8 \u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
312
+ attach: "\u0B95\u0BCB\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BC8 \u0B87\u0BA3\u0BC8\u0B95\u0BCD\u0B95",
313
+ attachHint: "\u0B85\u0BA4\u0BBF\u0B95\u0BAA\u0B9F\u0BCD\u0B9A\u0BAE\u0BCD {{max}} \u0B95\u0BCB\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BCD, \u0B92\u0BB5\u0BCD\u0BB5\u0BCA\u0BA9\u0BCD\u0BB1\u0BC1\u0BAE\u0BCD {{size}} MB \u0BB5\u0BB0\u0BC8",
314
+ removeAttachment: "{{name}} \u0B85\u0B95\u0BB1\u0BCD\u0BB1\u0BC1",
315
+ attachmentNameOnly: "\u0BAA\u0BC6\u0BAF\u0BB0\u0BC1\u0BAE\u0BCD \u0BB5\u0B95\u0BC8\u0BAF\u0BC1\u0BAE\u0BCD \u0BAE\u0B9F\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD",
316
+ attachmentTooLarge: "{{name}} {{size}} MB-\u0B90 \u0BB5\u0BBF\u0B9F \u0BAA\u0BC6\u0BB0\u0BBF\u0BAF\u0BA4\u0BC1, \u0B8E\u0BA9\u0BB5\u0BC7 \u0B87\u0BA3\u0BC8\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8.",
317
+ attachmentTooMany: "{{max}} \u0B95\u0BCB\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BCD \u0BAE\u0B9F\u0BCD\u0B9F\u0BC1\u0BAE\u0BC7 \u0B87\u0BA3\u0BC8\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BC1\u0BAE\u0BCD, \u0B8E\u0BA9\u0BB5\u0BC7 {{name}} \u0BA4\u0BB5\u0BBF\u0BB0\u0BCD\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BCD\u0B9F\u0BA4\u0BC1.",
282
318
  noAnswer: "\u0B87\u0BA4\u0BC1 \u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B86\u0BB5\u0BA3\u0B99\u0BCD\u0B95\u0BB3\u0BBF\u0BB2\u0BCD \u0B95\u0BBF\u0B9F\u0BC8\u0B95\u0BCD\u0B95\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8, \u0B8E\u0BA9\u0BB5\u0BC7 \u0B95\u0BC0\u0BB4\u0BC7 \u0B89\u0BB3\u0BCD\u0BB3 \u0BAA\u0BA4\u0BBF\u0BB2\u0BC1\u0B95\u0BCD\u0B95\u0BC1 \u0BAE\u0BC2\u0BB2\u0BAE\u0BCD \u0B87\u0BB2\u0BCD\u0BB2\u0BC8. \u0BB5\u0BC7\u0BB1\u0BC1 \u0BB5\u0B95\u0BC8\u0BAF\u0BBF\u0BB2\u0BCD \u0B95\u0BC7\u0BB3\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD, \u0B85\u0BB2\u0BCD\u0BB2\u0BA4\u0BC1",
283
319
  askHuman: "\u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BC8\u0BA4\u0BCD \u0BA4\u0BCA\u0B9F\u0BB0\u0BCD\u0BAA\u0BC1 \u0B95\u0BCA\u0BB3\u0BCD\u0BB3\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
284
320
  prevSuggestion: "\u0BAE\u0BC1\u0BA8\u0BCD\u0BA4\u0BC8\u0BAF \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
@@ -782,6 +818,12 @@ var EXPLORE_MESSAGE_FIELDS = (
782
818
  }
783
819
  followUps
784
820
  answered
821
+ attachments {
822
+ name
823
+ mimeType
824
+ size
825
+ textExtracted
826
+ }
785
827
  errorCode
786
828
  createdAt
787
829
  completedAt
@@ -867,6 +909,16 @@ var CREATE_EXPLORE_SHARE_LINK_MUTATION = (
867
909
  }
868
910
  `
869
911
  );
912
+ var EXPLORE_SHARED_CONVERSATION_QUERY = (
913
+ /* GraphQL */
914
+ `
915
+ query ExploreSharedConversation($shareToken: String!) {
916
+ exploreSharedConversation(shareToken: $shareToken) {
917
+ ${EXPLORE_MESSAGE_FIELDS}
918
+ }
919
+ }
920
+ `
921
+ );
870
922
 
871
923
  // src/data/products.ts
872
924
  var ECOSYSTEM_PRODUCTS = [
@@ -1317,6 +1369,14 @@ function readPersisted(key) {
1317
1369
  return null;
1318
1370
  }
1319
1371
  }
1372
+ function readShareToken() {
1373
+ if (typeof window === "undefined") return null;
1374
+ try {
1375
+ return new URL(window.location.href).searchParams.get("c");
1376
+ } catch {
1377
+ return null;
1378
+ }
1379
+ }
1320
1380
  function visibleText(message) {
1321
1381
  return message.content || message.partialContent || "";
1322
1382
  }
@@ -1355,10 +1415,18 @@ function useExploreChat(options = {}) {
1355
1415
  const pollGenerationRef = React.useRef(0);
1356
1416
  const pollTimerRef = React.useRef(null);
1357
1417
  const conversationTokenRef = React.useRef(null);
1418
+ const shareTokenRef = React.useRef(void 0);
1419
+ if (shareTokenRef.current === void 0) {
1420
+ shareTokenRef.current = readShareToken();
1421
+ }
1422
+ const isSharedView = shareTokenRef.current !== null;
1358
1423
  const catalogRef = React.useRef(fallbackCatalog);
1359
1424
  const turnCountRef = React.useRef(0);
1360
1425
  const focusProductRef = React.useRef(null);
1361
1426
  const lastSentTextRef = React.useRef(null);
1427
+ const lastSentAttachmentsRef = React.useRef(
1428
+ []
1429
+ );
1362
1430
  const inFlightRef = React.useRef(false);
1363
1431
  catalogRef.current = catalog;
1364
1432
  const clearPollTimer = React.useCallback(() => {
@@ -1383,6 +1451,43 @@ function useExploreChat(options = {}) {
1383
1451
  };
1384
1452
  }, []);
1385
1453
  React.useEffect(() => {
1454
+ const shareToken = shareTokenRef.current;
1455
+ if (!shareToken) return;
1456
+ let cancelled = false;
1457
+ setHydrated(true);
1458
+ void client.query(
1459
+ EXPLORE_SHARED_CONVERSATION_QUERY,
1460
+ { shareToken },
1461
+ { cache: false }
1462
+ ).then((res) => {
1463
+ if (cancelled || !mountedRef.current) return;
1464
+ const shared = res?.data?.exploreSharedConversation;
1465
+ if (shared && shared.length > 0) {
1466
+ setMessages(shared);
1467
+ return;
1468
+ }
1469
+ setError({
1470
+ kind: "unknown",
1471
+ code: "SHARE_NOT_FOUND",
1472
+ message: "That shared conversation could not be found. The link may have expired.",
1473
+ // Nothing to retry: a bad token will stay bad.
1474
+ retryable: false
1475
+ });
1476
+ }).catch(() => {
1477
+ if (cancelled || !mountedRef.current) return;
1478
+ setError({
1479
+ kind: "unknown",
1480
+ code: "SHARE_NOT_FOUND",
1481
+ message: "That shared conversation could not be loaded.",
1482
+ retryable: true
1483
+ });
1484
+ });
1485
+ return () => {
1486
+ cancelled = true;
1487
+ };
1488
+ }, [client]);
1489
+ React.useEffect(() => {
1490
+ if (shareTokenRef.current !== null) return;
1386
1491
  if (!persist) {
1387
1492
  setHydrated(true);
1388
1493
  return;
@@ -1550,7 +1655,7 @@ function useExploreChat(options = {}) {
1550
1655
  [client, pollIntervalMs, pollTimeoutMs]
1551
1656
  );
1552
1657
  const submit = React.useCallback(
1553
- async (text, optimisticId, captchaAttempt) => {
1658
+ async (text, optimisticId, captchaAttempt, attachments) => {
1554
1659
  const activeCatalog = catalogRef.current;
1555
1660
  let recaptchaToken;
1556
1661
  if (activeCatalog.captchaRequired && activeCatalog.recaptchaSiteKey) {
@@ -1568,7 +1673,8 @@ function useExploreChat(options = {}) {
1568
1673
  ...focusProductRef.current || productSlug ? { productSlug: focusProductRef.current ?? productSlug } : {},
1569
1674
  ...recaptchaToken ? { recaptchaToken } : {},
1570
1675
  ...locale ? { locale } : {},
1571
- ...pageUrl ? { pageUrl } : {}
1676
+ ...pageUrl ? { pageUrl } : {},
1677
+ ...attachments.length > 0 ? { attachments: [...attachments] } : {}
1572
1678
  };
1573
1679
  const res = await client.mutate(
1574
1680
  SEND_EXPLORE_MESSAGE_MUTATION,
@@ -1582,7 +1688,7 @@ function useExploreChat(options = {}) {
1582
1688
  const code = typeof first?.extensions?.code === "string" ? first.extensions.code : void 0;
1583
1689
  const classified = classifyExploreError(code, first?.message);
1584
1690
  if (classified.kind === "captcha" && captchaAttempt === 0) {
1585
- await submit(text, optimisticId, 1);
1691
+ await submit(text, optimisticId, 1, attachments);
1586
1692
  return;
1587
1693
  }
1588
1694
  inFlightRef.current = false;
@@ -1619,7 +1725,7 @@ function useExploreChat(options = {}) {
1619
1725
  [client, locale, pageUrl, productSlug, startPolling]
1620
1726
  );
1621
1727
  const send = React.useCallback(
1622
- async (message) => {
1728
+ async (message, attachments = []) => {
1623
1729
  const text = message.trim();
1624
1730
  if (!text) return;
1625
1731
  if (inFlightRef.current) return;
@@ -1649,6 +1755,7 @@ function useExploreChat(options = {}) {
1649
1755
  }
1650
1756
  inFlightRef.current = true;
1651
1757
  lastSentTextRef.current = text;
1758
+ lastSentAttachmentsRef.current = attachments;
1652
1759
  setError(null);
1653
1760
  setPhase("sending");
1654
1761
  cancelPolling();
@@ -1671,7 +1778,7 @@ function useExploreChat(options = {}) {
1671
1778
  setTurnCount(turnCountRef.current);
1672
1779
  onSend?.(text);
1673
1780
  try {
1674
- await submit(text, optimistic.id, 0);
1781
+ await submit(text, optimistic.id, 0, attachments);
1675
1782
  } catch (err) {
1676
1783
  if (!mountedRef.current) return;
1677
1784
  console.error("[WebSDK] Explore send failed", err);
@@ -1720,7 +1827,7 @@ function useExploreChat(options = {}) {
1720
1827
  });
1721
1828
  turnCountRef.current = Math.max(0, turnCountRef.current - 1);
1722
1829
  setTurnCount(turnCountRef.current);
1723
- void send(text);
1830
+ void send(text, lastSentAttachmentsRef.current);
1724
1831
  }, [cancelPolling, send]);
1725
1832
  const reset = React.useCallback(() => {
1726
1833
  setArchive(
@@ -1734,6 +1841,7 @@ function useExploreChat(options = {}) {
1734
1841
  inFlightRef.current = false;
1735
1842
  conversationTokenRef.current = null;
1736
1843
  lastSentTextRef.current = null;
1844
+ lastSentAttachmentsRef.current = [];
1737
1845
  turnCountRef.current = 0;
1738
1846
  setTurnCount(0);
1739
1847
  setMessages([]);
@@ -1842,7 +1950,8 @@ function useExploreChat(options = {}) {
1842
1950
  deleteConversation,
1843
1951
  clearHistory,
1844
1952
  recordClick,
1845
- createShareLink
1953
+ createShareLink,
1954
+ isSharedView
1846
1955
  };
1847
1956
  }
1848
1957
  var optimisticCounter = 0;
@@ -2226,6 +2335,145 @@ var Textarea = React__namespace.forwardRef(({ className, ...props }, ref) => {
2226
2335
  );
2227
2336
  });
2228
2337
  Textarea.displayName = "Textarea";
2338
+
2339
+ // src/utils/explore-attachments.ts
2340
+ var MAX_ATTACHMENTS = 5;
2341
+ var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
2342
+ var MAX_TEXT_PER_ATTACHMENT = 2e4;
2343
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
2344
+ "txt",
2345
+ "md",
2346
+ "markdown",
2347
+ "csv",
2348
+ "tsv",
2349
+ "json",
2350
+ "jsonl",
2351
+ "yaml",
2352
+ "yml",
2353
+ "toml",
2354
+ "ini",
2355
+ "cfg",
2356
+ "conf",
2357
+ "env",
2358
+ "log",
2359
+ "sql",
2360
+ "graphql",
2361
+ "gql",
2362
+ "html",
2363
+ "htm",
2364
+ "xml",
2365
+ "svg",
2366
+ "css",
2367
+ "scss",
2368
+ "js",
2369
+ "jsx",
2370
+ "mjs",
2371
+ "cjs",
2372
+ "ts",
2373
+ "tsx",
2374
+ "py",
2375
+ "rb",
2376
+ "go",
2377
+ "rs",
2378
+ "java",
2379
+ "kt",
2380
+ "c",
2381
+ "h",
2382
+ "cpp",
2383
+ "cs",
2384
+ "php",
2385
+ "sh",
2386
+ "bash",
2387
+ "zsh",
2388
+ "ps1",
2389
+ "dockerfile",
2390
+ "tf",
2391
+ "tfvars"
2392
+ ]);
2393
+ function isTextualMimeType(mimeType) {
2394
+ if (mimeType.startsWith("text/")) return true;
2395
+ return mimeType === "application/json" || mimeType === "application/xml" || mimeType === "application/yaml" || mimeType === "application/x-yaml" || mimeType === "application/javascript" || mimeType === "application/graphql" || mimeType.endsWith("+json") || mimeType.endsWith("+xml");
2396
+ }
2397
+ function extensionOf(name) {
2398
+ const dot = name.lastIndexOf(".");
2399
+ if (dot <= 0 || dot === name.length - 1) return "";
2400
+ return name.slice(dot + 1).toLowerCase();
2401
+ }
2402
+ function looksTextual(name, mimeType) {
2403
+ if (isTextualMimeType(mimeType)) return true;
2404
+ return TEXT_EXTENSIONS.has(extensionOf(name));
2405
+ }
2406
+ function isDecodedTextUsable(decoded) {
2407
+ if (decoded.length === 0) return false;
2408
+ if (decoded.includes("\0")) return false;
2409
+ let replacements = 0;
2410
+ for (const char of decoded) if (char === "\uFFFD") replacements += 1;
2411
+ return replacements / decoded.length < 0.1;
2412
+ }
2413
+ function formatBytes(bytes) {
2414
+ if (bytes < 1024) return `${bytes} B`;
2415
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
2416
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2417
+ }
2418
+ var counter = 0;
2419
+ function nextId() {
2420
+ counter += 1;
2421
+ return `att-${counter}`;
2422
+ }
2423
+ async function readArrayBuffer(blob) {
2424
+ if (typeof blob.arrayBuffer === "function") return blob.arrayBuffer();
2425
+ return new Promise((resolve, reject) => {
2426
+ const reader = new FileReader();
2427
+ reader.onload = () => resolve(reader.result);
2428
+ reader.onerror = () => reject(reader.error ?? new Error("file read failed"));
2429
+ reader.readAsArrayBuffer(blob);
2430
+ });
2431
+ }
2432
+ async function readTextPrefix(file) {
2433
+ try {
2434
+ const slice = file.slice(0, MAX_TEXT_PER_ATTACHMENT);
2435
+ const buffer = await readArrayBuffer(slice);
2436
+ const decoded = new TextDecoder("utf-8").decode(buffer);
2437
+ return isDecodedTextUsable(decoded) ? decoded : null;
2438
+ } catch {
2439
+ return null;
2440
+ }
2441
+ }
2442
+ async function stageFiles(files, alreadyStaged) {
2443
+ const accepted = [];
2444
+ const rejected = [];
2445
+ let slots = MAX_ATTACHMENTS - alreadyStaged.length;
2446
+ for (const file of files) {
2447
+ if (slots <= 0) {
2448
+ rejected.push({ name: file.name, reason: "too-many" });
2449
+ continue;
2450
+ }
2451
+ if (file.size > MAX_ATTACHMENT_BYTES) {
2452
+ rejected.push({ name: file.name, reason: "too-large" });
2453
+ continue;
2454
+ }
2455
+ const mimeType = file.type || "application/octet-stream";
2456
+ const textContent = looksTextual(file.name, mimeType) ? await readTextPrefix(file) : null;
2457
+ accepted.push({
2458
+ id: nextId(),
2459
+ name: file.name,
2460
+ mimeType,
2461
+ size: file.size,
2462
+ textContent,
2463
+ textExtracted: textContent !== null
2464
+ });
2465
+ slots -= 1;
2466
+ }
2467
+ return { accepted, rejected };
2468
+ }
2469
+ function toPayload(staged) {
2470
+ return staged.map(({ name, mimeType, size, textContent }) => ({
2471
+ name,
2472
+ mimeType,
2473
+ size,
2474
+ textContent
2475
+ }));
2476
+ }
2229
2477
  var EXPLORE_CSS = `
2230
2478
  @keyframes boff-explore-pulse {
2231
2479
  0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
@@ -2574,15 +2822,45 @@ function ThinkingIndicator({ activity }) {
2574
2822
  }
2575
2823
  );
2576
2824
  }
2577
- function UserBubble({ message }) {
2578
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
2579
- "div",
2580
- {
2581
- "data-boff-explore": "user",
2582
- className: "max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-primary px-4 py-2.5 text-sm leading-relaxed text-primary-foreground shadow-sm",
2583
- children: message.content
2584
- }
2585
- ) });
2825
+ function UserBubble({
2826
+ strings: T,
2827
+ message
2828
+ }) {
2829
+ const attachments = message.attachments ?? [];
2830
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-end gap-1.5", children: [
2831
+ /* @__PURE__ */ jsxRuntime.jsx(
2832
+ "div",
2833
+ {
2834
+ "data-boff-explore": "user",
2835
+ className: "max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-primary px-4 py-2.5 text-sm leading-relaxed text-primary-foreground shadow-sm",
2836
+ children: message.content
2837
+ }
2838
+ ),
2839
+ attachments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2840
+ "ul",
2841
+ {
2842
+ "data-boff-explore": "user-attachments",
2843
+ className: "flex max-w-[85%] flex-wrap justify-end gap-1.5",
2844
+ children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsxs(
2845
+ "li",
2846
+ {
2847
+ "data-boff-explore": "user-attachment",
2848
+ "data-text-extracted": attachment.textExtracted ? "true" : "false",
2849
+ className: "flex max-w-full items-center gap-1.5 rounded-full border border-border bg-muted/60 px-2.5 py-1 text-xs text-muted-foreground",
2850
+ children: [
2851
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-3 w-3 shrink-0" }),
2852
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", title: attachment.name, children: attachment.name }),
2853
+ attachment.textExtracted ? null : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0", children: [
2854
+ "\xB7 ",
2855
+ T.attachmentNameOnly
2856
+ ] })
2857
+ ]
2858
+ },
2859
+ `${attachment.name}-${attachment.size}`
2860
+ ))
2861
+ }
2862
+ ) : null
2863
+ ] });
2586
2864
  }
2587
2865
  function AssistantBubble({
2588
2866
  strings: T,
@@ -3081,7 +3359,8 @@ function ExplorePage({
3081
3359
  deleteConversation,
3082
3360
  clearHistory,
3083
3361
  recordClick,
3084
- createShareLink
3362
+ createShareLink,
3363
+ isSharedView
3085
3364
  } = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
3086
3365
  const [draft, setDraft] = React.useState("");
3087
3366
  const textareaRef = React.useRef(null);
@@ -3107,6 +3386,11 @@ function ExplorePage({
3107
3386
  const T = React.useMemo(() => exploreStrings(activeLocale), [activeLocale]);
3108
3387
  const rtl = isRtlLocale(activeLocale);
3109
3388
  const [disclaimerOpen, setDisclaimerOpen] = React.useState(false);
3389
+ const [staged, setStaged] = React.useState([]);
3390
+ const [attachmentNotice, setAttachmentNotice] = React.useState(null);
3391
+ const fileInputRef = React.useRef(null);
3392
+ const stagedRef = React.useRef([]);
3393
+ stagedRef.current = staged;
3110
3394
  const [shareLabel, setShareLabel] = React.useState("Share");
3111
3395
  const [shareUrl, setShareUrl] = React.useState(null);
3112
3396
  const scrollRef = React.useRef(null);
@@ -3183,13 +3467,40 @@ function ExplorePage({
3183
3467
  setShareLabel("Share");
3184
3468
  }
3185
3469
  }, [createShareLink, pageUrl]);
3470
+ const handleFilesPicked = React.useCallback(
3471
+ async (picked) => {
3472
+ if (!picked || picked.length === 0) return;
3473
+ const { accepted, rejected } = await stageFiles(
3474
+ Array.from(picked),
3475
+ stagedRef.current
3476
+ );
3477
+ setStaged((prev) => [...prev, ...accepted]);
3478
+ const first = rejected[0];
3479
+ setAttachmentNotice(
3480
+ first ? first.reason === "too-large" ? fill(T.attachmentTooLarge, {
3481
+ name: first.name,
3482
+ size: String(MAX_ATTACHMENT_BYTES / (1024 * 1024))
3483
+ }) : fill(T.attachmentTooMany, {
3484
+ name: first.name,
3485
+ max: String(MAX_ATTACHMENTS)
3486
+ }) : null
3487
+ );
3488
+ },
3489
+ [T]
3490
+ );
3491
+ const removeAttachment = React.useCallback((id) => {
3492
+ setStaged((prev) => prev.filter((entry) => entry.id !== id));
3493
+ setAttachmentNotice(null);
3494
+ }, []);
3186
3495
  const submitDraft = React.useCallback(() => {
3187
3496
  const text = draft.trim();
3188
3497
  if (!text || overLimit || busy || !canSend) return;
3189
3498
  stopDictation();
3190
3499
  setDraft("");
3191
3500
  stickToBottomRef.current = true;
3192
- void send(text);
3501
+ void send(text, toPayload(stagedRef.current));
3502
+ setStaged([]);
3503
+ setAttachmentNotice(null);
3193
3504
  textareaRef.current?.focus();
3194
3505
  }, [busy, canSend, draft, overLimit, send, stopDictation]);
3195
3506
  const sendPrompt = React.useCallback(
@@ -3197,7 +3508,9 @@ function ExplorePage({
3197
3508
  if (!canSend || busy) return;
3198
3509
  setDraft("");
3199
3510
  stickToBottomRef.current = true;
3200
- void send(prompt);
3511
+ void send(prompt, toPayload(stagedRef.current));
3512
+ setStaged([]);
3513
+ setAttachmentNotice(null);
3201
3514
  },
3202
3515
  [busy, canSend, send]
3203
3516
  );
@@ -3210,7 +3523,9 @@ function ExplorePage({
3210
3523
  },
3211
3524
  [submitDraft]
3212
3525
  );
3213
- const composerDisabled = phase === "limited" || phase === "disabled" || !catalog.enabled;
3526
+ const composerDisabled = phase === "limited" || phase === "disabled" || !catalog.enabled || // A share token grants read access only — the server would reject a send anyway, so
3527
+ // disable it here rather than letting the visitor type into a dead composer.
3528
+ isSharedView;
3214
3529
  const captchaOn = Boolean(
3215
3530
  catalog.captchaRequired && catalog.recaptchaSiteKey
3216
3531
  );
@@ -3404,7 +3719,7 @@ function ExplorePage({
3404
3719
  disabled: !canSend || busy
3405
3720
  }
3406
3721
  ) : /* @__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(
3407
- (message) => message.role === "USER" ? /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
3722
+ (message) => message.role === "USER" ? /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { strings: T, message }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
3408
3723
  AssistantBubble,
3409
3724
  {
3410
3725
  strings: T,
@@ -3428,6 +3743,33 @@ function ExplorePage({
3428
3743
  captchaOn
3429
3744
  }
3430
3745
  ),
3746
+ isSharedView ? /* @__PURE__ */ jsxRuntime.jsxs(
3747
+ "div",
3748
+ {
3749
+ "data-boff-explore": "shared-banner",
3750
+ className: "mx-auto flex w-full max-w-3xl items-center gap-2 px-4 pb-2 text-xs text-muted-foreground",
3751
+ children: [
3752
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, { className: "h-3.5 w-3.5 shrink-0" }),
3753
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1", children: "You are viewing a shared conversation. Start your own to ask a question." }),
3754
+ /* @__PURE__ */ jsxRuntime.jsx(
3755
+ "button",
3756
+ {
3757
+ type: "button",
3758
+ "data-boff-explore": "shared-start-own",
3759
+ onClick: () => {
3760
+ if (typeof window !== "undefined") {
3761
+ const url = new URL(window.location.href);
3762
+ url.searchParams.delete("c");
3763
+ window.location.href = url.toString();
3764
+ }
3765
+ },
3766
+ className: "shrink-0 underline underline-offset-2 hover:text-foreground",
3767
+ children: "New chat"
3768
+ }
3769
+ )
3770
+ ]
3771
+ }
3772
+ ) : null,
3431
3773
  /* @__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(
3432
3774
  "form",
3433
3775
  {
@@ -3439,6 +3781,51 @@ function ExplorePage({
3439
3781
  },
3440
3782
  children: [
3441
3783
  error ? /* @__PURE__ */ jsxRuntime.jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
3784
+ staged.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
3785
+ "ul",
3786
+ {
3787
+ "data-boff-explore": "attachments",
3788
+ className: "mb-2 flex flex-wrap gap-1.5",
3789
+ "aria-label": T.attach,
3790
+ children: staged.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs(
3791
+ "li",
3792
+ {
3793
+ "data-boff-explore": "attachment",
3794
+ "data-text-extracted": entry.textExtracted ? "true" : "false",
3795
+ className: "flex max-w-full items-center gap-1.5 rounded-full border border-border bg-muted/60 py-1 pl-2.5 pr-1 text-xs",
3796
+ children: [
3797
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
3798
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", title: entry.name, children: entry.name }),
3799
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
3800
+ formatBytes(entry.size),
3801
+ entry.textExtracted ? "" : ` \xB7 ${T.attachmentNameOnly}`
3802
+ ] }),
3803
+ /* @__PURE__ */ jsxRuntime.jsx(
3804
+ "button",
3805
+ {
3806
+ type: "button",
3807
+ "data-boff-explore": "attachment-remove",
3808
+ onClick: () => removeAttachment(entry.id),
3809
+ "aria-label": fill(T.removeAttachment, { name: entry.name }),
3810
+ className: "ml-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-background hover:text-foreground",
3811
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" })
3812
+ }
3813
+ )
3814
+ ]
3815
+ },
3816
+ entry.id
3817
+ ))
3818
+ }
3819
+ ) : null,
3820
+ attachmentNotice !== null ? /* @__PURE__ */ jsxRuntime.jsx(
3821
+ "p",
3822
+ {
3823
+ "data-boff-explore": "attachment-notice",
3824
+ role: "status",
3825
+ className: "mb-2 text-xs text-destructive",
3826
+ children: attachmentNotice
3827
+ }
3828
+ ) : null,
3442
3829
  /* @__PURE__ */ jsxRuntime.jsxs(
3443
3830
  "div",
3444
3831
  {
@@ -3467,7 +3854,37 @@ function ExplorePage({
3467
3854
  placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? fill(T.placeholderProduct, {
3468
3855
  product: focusedProductName
3469
3856
  }) : T.placeholder,
3470
- 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"
3857
+ 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-base shadow-none focus-visible:ring-0 sm:text-sm"
3858
+ }
3859
+ ),
3860
+ /* @__PURE__ */ jsxRuntime.jsx(
3861
+ "input",
3862
+ {
3863
+ ref: fileInputRef,
3864
+ type: "file",
3865
+ multiple: true,
3866
+ className: "hidden",
3867
+ onChange: (event) => {
3868
+ void handleFilesPicked(event.target.files);
3869
+ event.target.value = "";
3870
+ }
3871
+ }
3872
+ ),
3873
+ /* @__PURE__ */ jsxRuntime.jsx(
3874
+ Button,
3875
+ {
3876
+ "data-boff-explore": "attach",
3877
+ type: "button",
3878
+ size: "icon",
3879
+ variant: "ghost",
3880
+ disabled: composerDisabled || staged.length >= MAX_ATTACHMENTS,
3881
+ "aria-label": T.attach,
3882
+ title: `${T.attach} \u2014 ${fill(T.attachHint, {
3883
+ max: String(MAX_ATTACHMENTS),
3884
+ size: String(MAX_ATTACHMENT_BYTES / (1024 * 1024))
3885
+ })}`,
3886
+ onClick: () => fileInputRef.current?.click(),
3887
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-4 w-4" })
3471
3888
  }
3472
3889
  ),
3473
3890
  speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(