@burdenoff/website-sdk 2026.922.3 → 2026.922.5

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
- import { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext } from 'react';
3
- import { Sparkles, RotateCcw, Link2, History, Trash2, Paperclip, X, Mic, Square, ArrowUp, BookOpenText, Layers, TriangleAlert, ChevronLeft, ChevronRight, ExternalLink, ThumbsUp, ThumbsDown } from 'lucide-react';
2
+ import { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext, Children, isValidElement, cloneElement } from 'react';
3
+ import { Sparkles, RotateCcw, Link2, History, Trash2, Paperclip, X, Mic, Square, ArrowUp, BookOpenText, Layers, TriangleAlert, ChevronLeft, ChevronRight, ExternalLink, Check, Copy, ThumbsUp, ThumbsDown, Volume2 } from 'lucide-react';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeSanitize from 'rehype-sanitize';
6
6
  import remarkGfm from 'remark-gfm';
@@ -77,6 +77,23 @@ var EN = {
77
77
  nextSuggestion: "Next suggestion",
78
78
  conceptIllustration: "Concept illustration",
79
79
  opensInNewTab: "opens in a new tab",
80
+ conceptStripTitle: "Or start from a concept",
81
+ conceptStripLabel: "Concepts you can ask about",
82
+ conceptAsk: "Ask about {title} in {product}",
83
+ conceptAskQuestion: "How does {title} work in {product}?",
84
+ copyAnswer: "Copy answer",
85
+ copyFailed: "Couldn't copy",
86
+ readAloud: "Read aloud",
87
+ stopReading: "Stop reading",
88
+ answerTable: "Table",
89
+ groundedIn: "What this answer is based on",
90
+ sourceCountOne: "1 source",
91
+ sourceCountMany: "{count} sources",
92
+ sourceKindConcept: "Concept",
93
+ sourceKindDocs: "Documentation",
94
+ sourceKindPricing: "Pricing",
95
+ sourceKindUseCase: "Use case",
96
+ sourceKindPage: "Product page",
80
97
  seoTitle: "Describe a problem, get an answer",
81
98
  seoDescription: "Describe the problem you're facing in your own words and get an answer drawn from our product documentation, with links to the exact pages it came from.",
82
99
  seoDescriptionProduct: "Describe the problem you're facing in your own words and get an answer drawn from {product}'s documentation \u2014 and every other product we build \u2014 with links to the exact pages it came from.",
@@ -145,6 +162,23 @@ var ZH = {
145
162
  nextSuggestion: "\u4E0B\u4E00\u4E2A\u5EFA\u8BAE",
146
163
  conceptIllustration: "\u6982\u5FF5\u793A\u610F\u56FE",
147
164
  opensInNewTab: "\u5728\u65B0\u6807\u7B7E\u9875\u4E2D\u6253\u5F00",
165
+ conceptStripTitle: "\u6216\u8005\u4ECE\u4E00\u4E2A\u6982\u5FF5\u5F00\u59CB",
166
+ conceptStripLabel: "\u4F60\u53EF\u4EE5\u8BE2\u95EE\u7684\u6982\u5FF5",
167
+ conceptAsk: "\u8BE2\u95EE {product} \u4E2D\u7684 {title}",
168
+ conceptAskQuestion: "{product} \u7684 {title} \u662F\u600E\u4E48\u8FD0\u4F5C\u7684\uFF1F",
169
+ copyAnswer: "\u590D\u5236\u56DE\u7B54",
170
+ copyFailed: "\u590D\u5236\u5931\u8D25",
171
+ readAloud: "\u6717\u8BFB",
172
+ stopReading: "\u505C\u6B62\u6717\u8BFB",
173
+ answerTable: "\u8868\u683C",
174
+ groundedIn: "\u8FD9\u4E2A\u56DE\u7B54\u7684\u4F9D\u636E",
175
+ sourceCountOne: "1 \u4E2A\u6765\u6E90",
176
+ sourceCountMany: "{count} \u4E2A\u6765\u6E90",
177
+ sourceKindConcept: "\u6982\u5FF5",
178
+ sourceKindDocs: "\u6587\u6863",
179
+ sourceKindPricing: "\u4EF7\u683C",
180
+ sourceKindUseCase: "\u5E94\u7528\u573A\u666F",
181
+ sourceKindPage: "\u4EA7\u54C1\u9875\u9762",
148
182
  seoTitle: "\u63CF\u8FF0\u95EE\u9898\uFF0C\u83B7\u5F97\u7B54\u6848",
149
183
  seoDescription: "\u7528\u4F60\u81EA\u5DF1\u7684\u8BDD\u63CF\u8FF0\u9047\u5230\u7684\u95EE\u9898\uFF0C\u5373\u53EF\u83B7\u5F97\u6765\u81EA\u6211\u4EEC\u4EA7\u54C1\u6587\u6863\u7684\u56DE\u7B54\uFF0C\u5E76\u9644\u4E0A\u7B54\u6848\u51FA\u5904\u7684\u9875\u9762\u94FE\u63A5\u3002",
150
184
  seoDescriptionProduct: "\u7528\u4F60\u81EA\u5DF1\u7684\u8BDD\u63CF\u8FF0\u9047\u5230\u7684\u95EE\u9898\uFF0C\u5373\u53EF\u83B7\u5F97\u6765\u81EA {product} \u53CA\u6211\u4EEC\u5176\u4ED6\u4EA7\u54C1\u6587\u6863\u7684\u56DE\u7B54\uFF0C\u5E76\u9644\u4E0A\u7B54\u6848\u51FA\u5904\u7684\u9875\u9762\u94FE\u63A5\u3002",
@@ -209,6 +243,23 @@ var HI = {
209
243
  nextSuggestion: "\u0905\u0917\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
210
244
  conceptIllustration: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E \u091A\u093F\u0924\u094D\u0930\u0923",
211
245
  opensInNewTab: "\u0928\u090F \u091F\u0948\u092C \u092E\u0947\u0902 \u0916\u0941\u0932\u0924\u093E \u0939\u0948",
246
+ conceptStripTitle: "\u092F\u093E \u0915\u093F\u0938\u0940 \u0905\u0935\u0927\u093E\u0930\u0923\u093E \u0938\u0947 \u0936\u0941\u0930\u0942 \u0915\u0930\u0947\u0902",
247
+ conceptStripLabel: "\u091C\u093F\u0928 \u0905\u0935\u0927\u093E\u0930\u0923\u093E\u0913\u0902 \u0915\u0947 \u092C\u093E\u0930\u0947 \u092E\u0947\u0902 \u0906\u092A \u092A\u0942\u091B \u0938\u0915\u0924\u0947 \u0939\u0948\u0902",
248
+ conceptAsk: "{product} \u092E\u0947\u0902 {title} \u0915\u0947 \u092C\u093E\u0930\u0947 \u092E\u0947\u0902 \u092A\u0942\u091B\u0947\u0902",
249
+ conceptAskQuestion: "{product} \u092E\u0947\u0902 {title} \u0915\u0948\u0938\u0947 \u0915\u093E\u092E \u0915\u0930\u0924\u093E \u0939\u0948?",
250
+ copyAnswer: "\u0909\u0924\u094D\u0924\u0930 \u0915\u0949\u092A\u0940 \u0915\u0930\u0947\u0902",
251
+ copyFailed: "\u0915\u0949\u092A\u0940 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u093E",
252
+ readAloud: "\u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0947\u0902",
253
+ stopReading: "\u092A\u0922\u093C\u0928\u093E \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
254
+ answerTable: "\u0924\u093E\u0932\u093F\u0915\u093E",
255
+ groundedIn: "\u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u0915\u093F\u0938 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0939\u0948",
256
+ sourceCountOne: "1 \u0938\u094D\u0930\u094B\u0924",
257
+ sourceCountMany: "{count} \u0938\u094D\u0930\u094B\u0924",
258
+ sourceKindConcept: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E",
259
+ sourceKindDocs: "\u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C",
260
+ sourceKindPricing: "\u092E\u0942\u0932\u094D\u092F",
261
+ sourceKindUseCase: "\u0909\u092A\u092F\u094B\u0917 \u0915\u093E \u0909\u0926\u093E\u0939\u0930\u0923",
262
+ sourceKindPage: "\u0909\u0924\u094D\u092A\u093E\u0926 \u092A\u0947\u091C",
212
263
  seoTitle: "\u0938\u092E\u0938\u094D\u092F\u093E \u092C\u0924\u093E\u090F\u0902, \u091C\u0935\u093E\u092C \u092A\u093E\u090F\u0902",
213
264
  seoDescription: "\u0905\u092A\u0928\u0940 \u0938\u092E\u0938\u094D\u092F\u093E \u0905\u092A\u0928\u0947 \u0936\u092C\u094D\u0926\u094B\u0902 \u092E\u0947\u0902 \u092C\u0924\u093E\u090F\u0902 \u0914\u0930 \u0939\u092E\u093E\u0930\u0947 \u0909\u0924\u094D\u092A\u093E\u0926 \u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C\u094B\u0902 \u0938\u0947 \u0932\u093F\u092F\u093E \u0917\u092F\u093E \u091C\u0935\u093E\u092C \u092A\u093E\u090F\u0902, \u0938\u093E\u0925 \u092E\u0947\u0902 \u0909\u0928\u094D\u0939\u0940\u0902 \u092A\u0928\u094D\u0928\u094B\u0902 \u0915\u0947 \u0932\u093F\u0902\u0915 \u091C\u0939\u093E\u0901 \u0938\u0947 \u0935\u0939 \u0906\u092F\u093E \u0939\u0948\u0964",
214
265
  seoDescriptionProduct: "\u0905\u092A\u0928\u0940 \u0938\u092E\u0938\u094D\u092F\u093E \u0905\u092A\u0928\u0947 \u0936\u092C\u094D\u0926\u094B\u0902 \u092E\u0947\u0902 \u092C\u0924\u093E\u090F\u0902 \u0914\u0930 {product} \u0924\u0925\u093E \u0939\u092E\u093E\u0930\u0947 \u092C\u093E\u0915\u0940 \u0909\u0924\u094D\u092A\u093E\u0926\u094B\u0902 \u0915\u0947 \u0926\u0938\u094D\u0924\u093E\u0935\u0947\u091C\u093C\u094B\u0902 \u0938\u0947 \u0932\u093F\u092F\u093E \u0917\u092F\u093E \u091C\u0935\u093E\u092C \u092A\u093E\u090F\u0902, \u0938\u093E\u0925 \u092E\u0947\u0902 \u0909\u0928\u094D\u0939\u0940\u0902 \u092A\u0928\u094D\u0928\u094B\u0902 \u0915\u0947 \u0932\u093F\u0902\u0915 \u091C\u0939\u093E\u0901 \u0938\u0947 \u0935\u0939 \u0906\u092F\u093E \u0939\u0948\u0964",
@@ -277,6 +328,23 @@ var ES = {
277
328
  nextSuggestion: "Sugerencia siguiente",
278
329
  conceptIllustration: "Ilustraci\xF3n del concepto",
279
330
  opensInNewTab: "se abre en una pesta\xF1a nueva",
331
+ conceptStripTitle: "O empieza por un concepto",
332
+ conceptStripLabel: "Conceptos sobre los que puedes preguntar",
333
+ conceptAsk: "Preguntar por {title} en {product}",
334
+ conceptAskQuestion: "\xBFC\xF3mo funciona {title} en {product}?",
335
+ copyAnswer: "Copiar respuesta",
336
+ copyFailed: "No se pudo copiar",
337
+ readAloud: "Leer en voz alta",
338
+ stopReading: "Dejar de leer",
339
+ answerTable: "Tabla",
340
+ groundedIn: "En qu\xE9 se basa esta respuesta",
341
+ sourceCountOne: "1 fuente",
342
+ sourceCountMany: "{count} fuentes",
343
+ sourceKindConcept: "Concepto",
344
+ sourceKindDocs: "Documentaci\xF3n",
345
+ sourceKindPricing: "Precios",
346
+ sourceKindUseCase: "Caso de uso",
347
+ sourceKindPage: "P\xE1gina de producto",
280
348
  seoTitle: "Describe un problema y obt\xE9n una respuesta",
281
349
  seoDescription: "Describe con tus palabras el problema que tienes y recibe una respuesta extra\xEDda de la documentaci\xF3n de nuestros productos, con enlaces a las p\xE1ginas exactas de las que procede.",
282
350
  seoDescriptionProduct: "Describe con tus palabras el problema que tienes y recibe una respuesta extra\xEDda de la documentaci\xF3n de {product} y del resto de nuestros productos, con enlaces a las p\xE1ginas exactas de las que procede.",
@@ -345,6 +413,23 @@ var AR = {
345
413
  nextSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u062A\u0627\u0644\u064A",
346
414
  conceptIllustration: "\u0631\u0633\u0645 \u062A\u0648\u0636\u064A\u062D\u064A \u0644\u0644\u0645\u0641\u0647\u0648\u0645",
347
415
  opensInNewTab: "\u064A\u064F\u0641\u062A\u062D \u0641\u064A \u0639\u0644\u0627\u0645\u0629 \u062A\u0628\u0648\u064A\u0628 \u062C\u062F\u064A\u062F\u0629",
416
+ conceptStripTitle: "\u0623\u0648 \u0627\u0628\u062F\u0623 \u0645\u0646 \u0645\u0641\u0647\u0648\u0645",
417
+ conceptStripLabel: "\u0645\u0641\u0627\u0647\u064A\u0645 \u064A\u0645\u0643\u0646\u0643 \u0627\u0644\u0633\u0624\u0627\u0644 \u0639\u0646\u0647\u0627",
418
+ conceptAsk: "\u0627\u0633\u0623\u0644 \u0639\u0646 {title} \u0641\u064A {product}",
419
+ conceptAskQuestion: "\u0643\u064A\u0641 \u064A\u0639\u0645\u0644 {title} \u0641\u064A {product}\u061F",
420
+ copyAnswer: "\u0646\u0633\u062E \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
421
+ copyFailed: "\u062A\u0639\u0630\u0651\u0631 \u0627\u0644\u0646\u0633\u062E",
422
+ readAloud: "\u0627\u0642\u0631\u0623 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D",
423
+ stopReading: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0642\u0631\u0627\u0621\u0629",
424
+ answerTable: "\u062C\u062F\u0648\u0644",
425
+ groundedIn: "\u0639\u0644\u0649 \u0645\u0627\u0630\u0627 \u062A\u0633\u062A\u0646\u062F \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
426
+ sourceCountOne: "\u0645\u0635\u062F\u0631 \u0648\u0627\u062D\u062F",
427
+ sourceCountMany: "{count} \u0645\u0635\u0627\u062F\u0631",
428
+ sourceKindConcept: "\u0645\u0641\u0647\u0648\u0645",
429
+ sourceKindDocs: "\u0627\u0644\u062A\u0648\u062B\u064A\u0642",
430
+ sourceKindPricing: "\u0627\u0644\u0623\u0633\u0639\u0627\u0631",
431
+ sourceKindUseCase: "\u062D\u0627\u0644\u0629 \u0627\u0633\u062A\u062E\u062F\u0627\u0645",
432
+ sourceKindPage: "\u0635\u0641\u062D\u0629 \u0627\u0644\u0645\u0646\u062A\u062C",
348
433
  seoTitle: "\u0635\u0641 \u0645\u0634\u0643\u0644\u062A\u0643 \u0648\u0627\u062D\u0635\u0644 \u0639\u0644\u0649 \u0625\u062C\u0627\u0628\u0629",
349
434
  seoDescription: "\u0635\u0641 \u0627\u0644\u0645\u0634\u0643\u0644\u0629 \u0627\u0644\u062A\u064A \u062A\u0648\u0627\u062C\u0647\u0647\u0627 \u0628\u0643\u0644\u0645\u0627\u062A\u0643 \u0648\u0627\u062D\u0635\u0644 \u0639\u0644\u0649 \u0625\u062C\u0627\u0628\u0629 \u0645\u0633\u062A\u0645\u062F\u0629 \u0645\u0646 \u0648\u062B\u0627\u0626\u0642 \u0645\u0646\u062A\u062C\u0627\u062A\u0646\u0627\u060C \u0645\u0639 \u0631\u0648\u0627\u0628\u0637 \u0625\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0627\u062A \u0627\u0644\u062A\u064A \u062C\u0627\u0621\u062A \u0645\u0646\u0647\u0627.",
350
435
  seoDescriptionProduct: "\u0635\u0641 \u0627\u0644\u0645\u0634\u0643\u0644\u0629 \u0627\u0644\u062A\u064A \u062A\u0648\u0627\u062C\u0647\u0647\u0627 \u0628\u0643\u0644\u0645\u0627\u062A\u0643 \u0648\u0627\u062D\u0635\u0644 \u0639\u0644\u0649 \u0625\u062C\u0627\u0628\u0629 \u0645\u0633\u062A\u0645\u062F\u0629 \u0645\u0646 \u0648\u062B\u0627\u0626\u0642 {product} \u0648\u0628\u0642\u064A\u0629 \u0645\u0646\u062A\u062C\u0627\u062A\u0646\u0627\u060C \u0645\u0639 \u0631\u0648\u0627\u0628\u0637 \u0625\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0627\u062A \u0627\u0644\u062A\u064A \u062C\u0627\u0621\u062A \u0645\u0646\u0647\u0627.",
@@ -413,6 +498,23 @@ var TA = {
413
498
  nextSuggestion: "\u0B85\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4 \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
414
499
  conceptIllustration: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1 \u0BB5\u0BBF\u0BB3\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BAE\u0BCD",
415
500
  opensInNewTab: "\u0BAA\u0BC1\u0BA4\u0BBF\u0BAF \u0BA4\u0BBE\u0BB5\u0BB2\u0BBF\u0BB2\u0BCD \u0BA4\u0BBF\u0BB1\u0B95\u0BCD\u0B95\u0BC1\u0BAE\u0BCD",
501
+ conceptStripTitle: "\u0B85\u0BB2\u0BCD\u0BB2\u0BA4\u0BC1 \u0B92\u0BB0\u0BC1 \u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BBF\u0BB2\u0BBF\u0BB0\u0BC1\u0BA8\u0BCD\u0BA4\u0BC1 \u0BA4\u0BCA\u0B9F\u0B99\u0BCD\u0B95\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
502
+ conceptStripLabel: "\u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B95\u0BC7\u0B9F\u0BCD\u0B95\u0B95\u0BCD\u0B95\u0BC2\u0B9F\u0BBF\u0BAF \u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1\u0B95\u0BB3\u0BCD",
503
+ conceptAsk: "{product} \u0B87\u0BB2\u0BCD {title} \u0BAA\u0BB1\u0BCD\u0BB1\u0BBF\u0B95\u0BCD \u0B95\u0BC7\u0BB3\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
504
+ conceptAskQuestion: "{product} \u0B87\u0BB2\u0BCD {title} \u0B8E\u0BAA\u0BCD\u0BAA\u0B9F\u0BBF \u0BB5\u0BC7\u0BB2\u0BC8 \u0B9A\u0BC6\u0BAF\u0BCD\u0B95\u0BBF\u0BB1\u0BA4\u0BC1?",
505
+ copyAnswer: "\u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1",
506
+ copyFailed: "\u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8",
507
+ readAloud: "\u0B9A\u0BA4\u0BCD\u0BA4\u0BAE\u0BBE\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF",
508
+ stopReading: "\u0BAA\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0BA4\u0BC8 \u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
509
+ answerTable: "\u0B85\u0B9F\u0BCD\u0B9F\u0BB5\u0BA3\u0BC8",
510
+ groundedIn: "\u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B8E\u0BA4\u0BC8 \u0B85\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0B9F\u0BC8\u0BAF\u0BBE\u0B95\u0B95\u0BCD \u0B95\u0BCA\u0BA3\u0BCD\u0B9F\u0BA4\u0BC1",
511
+ sourceCountOne: "1 \u0BAE\u0BC2\u0BB2\u0BAE\u0BCD",
512
+ sourceCountMany: "{count} \u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
513
+ sourceKindConcept: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
514
+ sourceKindDocs: "\u0B86\u0BB5\u0BA3\u0BAE\u0BCD",
515
+ sourceKindPricing: "\u0BB5\u0BBF\u0BB2\u0BC8",
516
+ sourceKindUseCase: "\u0BAA\u0BAF\u0BA9\u0BCD\u0BAA\u0BBE\u0B9F\u0BCD\u0B9F\u0BC1 \u0BA8\u0BBF\u0BB2\u0BC8",
517
+ sourceKindPage: "\u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1\u0BAA\u0BCD \u0BAA\u0B95\u0BCD\u0B95\u0BAE\u0BCD",
416
518
  seoTitle: "\u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD, \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0BAA\u0BC6\u0BB1\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
417
519
  seoDescription: "\u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B8E\u0BA4\u0BBF\u0BB0\u0BCD\u0B95\u0BCA\u0BB3\u0BCD\u0BB3\u0BC1\u0BAE\u0BCD \u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0B89\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B9A\u0BCA\u0BA8\u0BCD\u0BA4 \u0BB5\u0BBE\u0BB0\u0BCD\u0BA4\u0BCD\u0BA4\u0BC8\u0B95\u0BB3\u0BBF\u0BB2\u0BCD \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD; \u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1 \u0B86\u0BB5\u0BA3\u0B99\u0BCD\u0B95\u0BB3\u0BBF\u0BB2\u0BBF\u0BB0\u0BC1\u0BA8\u0BCD\u0BA4\u0BC1 \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B95\u0BBF\u0B9F\u0BC8\u0B95\u0BCD\u0B95\u0BC1\u0BAE\u0BCD, \u0B85\u0BA4\u0BC1 \u0BB5\u0BA8\u0BCD\u0BA4 \u0BAA\u0B95\u0BCD\u0B95\u0B99\u0BCD\u0B95\u0BB3\u0BC1\u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0B87\u0BA3\u0BC8\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BC1\u0B9F\u0BA9\u0BCD.",
418
520
  seoDescriptionProduct: "{product} \u0BAE\u0BB1\u0BCD\u0BB1\u0BC1\u0BAE\u0BCD \u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BAA\u0BBF\u0BB1 \u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BBF\u0BA9\u0BCD \u0B86\u0BB5\u0BA3\u0B99\u0BCD\u0B95\u0BB3\u0BBF\u0BB2\u0BBF\u0BB0\u0BC1\u0BA8\u0BCD\u0BA4\u0BC1 \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0BAA\u0BC6\u0BB1, \u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B8E\u0BA4\u0BBF\u0BB0\u0BCD\u0B95\u0BCA\u0BB3\u0BCD\u0BB3\u0BC1\u0BAE\u0BCD \u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0B89\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B9A\u0BCA\u0BA8\u0BCD\u0BA4 \u0BB5\u0BBE\u0BB0\u0BCD\u0BA4\u0BCD\u0BA4\u0BC8\u0B95\u0BB3\u0BBF\u0BB2\u0BCD \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u2014 \u0B85\u0BA4\u0BC1 \u0BB5\u0BA8\u0BCD\u0BA4 \u0BAA\u0B95\u0BCD\u0B95\u0B99\u0BCD\u0B95\u0BB3\u0BC1\u0B95\u0BCD\u0B95\u0BBE\u0BA9 \u0B87\u0BA3\u0BC8\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BC1\u0B9F\u0BA9\u0BCD.",
@@ -436,6 +538,17 @@ var STRINGS = {
436
538
  ar: AR,
437
539
  ta: TA
438
540
  };
541
+ var SPEECH_TAGS = {
542
+ en: "en-US",
543
+ zh: "zh-CN",
544
+ hi: "hi-IN",
545
+ es: "es-ES",
546
+ ar: "ar-SA",
547
+ ta: "ta-IN"
548
+ };
549
+ function speechLocaleOf(locale) {
550
+ return SPEECH_TAGS[locale];
551
+ }
439
552
  function resolveExploreLocale(explicit) {
440
553
  const candidates = [
441
554
  explicit,
@@ -514,11 +627,9 @@ var EXPLORE_OWN_MESSAGE_FIELDS = (
514
627
  }
515
628
  `
516
629
  );
517
- var EXPLORE_CATALOG_QUERY = (
630
+ var EXPLORE_CATALOG_ESTABLISHED_FIELDS = (
518
631
  /* GraphQL */
519
632
  `
520
- query ExploreCatalog($productSlug: String) {
521
- exploreCatalog(productSlug: $productSlug) {
522
633
  enabled
523
634
  products {
524
635
  slug
@@ -536,11 +647,35 @@ var EXPLORE_CATALOG_QUERY = (
536
647
  historyTurns
537
648
  }
538
649
  captchaRequired
539
- recaptchaSiteKey
650
+ recaptchaSiteKey`
651
+ );
652
+ var EXPLORE_CATALOG_HIGHLIGHT_FIELD = (
653
+ /* GraphQL */
654
+ `
655
+ conceptHighlights {
656
+ product
657
+ productName
658
+ title
659
+ url
660
+ imageUrl
661
+ alt
662
+ }`
663
+ );
664
+ function exploreCatalogDocument(extraFields) {
665
+ return (
666
+ /* GraphQL */
667
+ `
668
+ query ExploreCatalog($productSlug: String) {
669
+ exploreCatalog(productSlug: $productSlug) {${EXPLORE_CATALOG_ESTABLISHED_FIELDS}${extraFields}
540
670
  }
541
671
  }
542
672
  `
673
+ );
674
+ }
675
+ var EXPLORE_CATALOG_QUERY = exploreCatalogDocument(
676
+ EXPLORE_CATALOG_HIGHLIGHT_FIELD
543
677
  );
678
+ var EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS = exploreCatalogDocument("");
544
679
  var EXPLORE_MESSAGE_QUERY = (
545
680
  /* GraphQL */
546
681
  `
@@ -1343,7 +1478,12 @@ function buildFallbackCatalog(examplePrompts, welcome) {
1343
1478
  welcomeBody: welcome?.body ?? FALLBACK_WELCOME_BODY,
1344
1479
  limits: FALLBACK_EXPLORE_LIMITS,
1345
1480
  captchaRequired: false,
1346
- recaptchaSiteKey: null
1481
+ recaptchaSiteKey: null,
1482
+ // Empty, and deliberately not synthesised from `ECOSYSTEM_PRODUCTS`: a concept highlight
1483
+ // needs a real published illustration URL, and this SDK has no registry of them. An
1484
+ // invented path would render 38 broken frames on the one screen every prerender captures.
1485
+ // The strip renders nothing at all when the list is empty, which is the honest fallback.
1486
+ conceptHighlights: []
1347
1487
  };
1348
1488
  }
1349
1489
  function normalizeCatalog(raw, fallback) {
@@ -1360,9 +1500,27 @@ function normalizeCatalog(raw, fallback) {
1360
1500
  historyTurns: raw.limits?.historyTurns || fallback.limits.historyTurns
1361
1501
  },
1362
1502
  captchaRequired: raw.captchaRequired === true,
1363
- recaptchaSiteKey: raw.recaptchaSiteKey ?? null
1503
+ recaptchaSiteKey: raw.recaptchaSiteKey ?? null,
1504
+ // `[ExploreConceptHighlight!]!` on the wire, so a gateway that KNOWS the field always
1505
+ // sends an array. The one response that arrives without it is the retry against an older
1506
+ // supergraph (`EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS`) — that gateway does not answer
1507
+ // the full document at all, it refuses it. Either way
1508
+ // `catalog.conceptHighlights.filter(...)` on `undefined` would throw inside render,
1509
+ // blanking the welcome screen over a decorative strip.
1510
+ conceptHighlights: raw.conceptHighlights?.length ? raw.conceptHighlights : fallback.conceptHighlights
1364
1511
  };
1365
1512
  }
1513
+ var CATALOG_DOCUMENT_REJECTED_CODES = /* @__PURE__ */ new Set([
1514
+ "GRAPHQL_VALIDATION_FAILED",
1515
+ "HTTP_400"
1516
+ ]);
1517
+ function catalogRejectedTheHighlightField(res) {
1518
+ if (res.data?.exploreCatalog) return false;
1519
+ return (res.errors ?? []).some((error) => {
1520
+ const code = error.extensions?.code;
1521
+ return typeof code === "string" && CATALOG_DOCUMENT_REJECTED_CODES.has(code) || error.message.includes("conceptHighlights");
1522
+ });
1523
+ }
1366
1524
  var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
1367
1525
  "RATE_LIMITED",
1368
1526
  "HTTP_429",
@@ -1702,11 +1860,19 @@ function useExploreChat(options = {}) {
1702
1860
  useEffect(() => {
1703
1861
  let cancelled = false;
1704
1862
  const load = async () => {
1705
- const res = await client.query(
1863
+ const variables = { productSlug: productSlug ?? null };
1864
+ let res = await client.query(
1706
1865
  EXPLORE_CATALOG_QUERY,
1707
- { productSlug: productSlug ?? null }
1866
+ variables
1708
1867
  );
1709
1868
  if (cancelled || !mountedRef.current) return;
1869
+ if (catalogRejectedTheHighlightField(res)) {
1870
+ res = await client.query(
1871
+ EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS,
1872
+ variables
1873
+ );
1874
+ if (cancelled || !mountedRef.current) return;
1875
+ }
1710
1876
  const raw = res.data?.exploreCatalog;
1711
1877
  if (res.errors?.length || !raw) {
1712
1878
  console.warn(
@@ -2152,6 +2318,224 @@ function makeOptimisticId() {
2152
2318
  optimisticCounter += 1;
2153
2319
  return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
2154
2320
  }
2321
+ var DRAIN_TARGET_MS = 1200;
2322
+ var MIN_CHARS_PER_SECOND = 90;
2323
+ var MAX_CHARS_PER_SECOND = 2200;
2324
+ var MAX_STEP_MS = 250;
2325
+ var COMMIT_INTERVAL_MS = 45;
2326
+ var FALLBACK_STEP_MS = 50;
2327
+ var FENCE = /^ {0,3}(?:```|~~~)/;
2328
+ var TABLE_ROW = /^ {0,3}\|/;
2329
+ var EMPTY_BLOCK_MARKER = /^ {0,3}(?:[-+>]|#{1,6}|\d{1,9}[.)])\s*$/;
2330
+ function isWordCharacter(char) {
2331
+ return char !== void 0 && /[\p{L}\p{N}]/u.test(char);
2332
+ }
2333
+ function clamp(value, min, max) {
2334
+ return Math.min(Math.max(value, min), max);
2335
+ }
2336
+ function now() {
2337
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
2338
+ }
2339
+ function isHighSurrogate(code) {
2340
+ return code >= 55296 && code <= 56319;
2341
+ }
2342
+ function safeInlineEnd(line) {
2343
+ let codeSpan = null;
2344
+ let link = null;
2345
+ const emphasis = [];
2346
+ let at = 0;
2347
+ while (at < line.length) {
2348
+ const char = line[at] ?? "";
2349
+ if (char === "\\") {
2350
+ at += 2;
2351
+ continue;
2352
+ }
2353
+ if (char === "`") {
2354
+ let run = 1;
2355
+ while (line[at + run] === "`") run += 1;
2356
+ codeSpan = codeSpan === null ? at : null;
2357
+ at += run;
2358
+ continue;
2359
+ }
2360
+ if (codeSpan !== null) {
2361
+ at += 1;
2362
+ continue;
2363
+ }
2364
+ if (char === "[") {
2365
+ if (link === null) link = line[at - 1] === "!" ? at - 1 : at;
2366
+ at += 1;
2367
+ continue;
2368
+ }
2369
+ if (char === "]") {
2370
+ if (link !== null && line[at + 1] !== "(") link = null;
2371
+ at += 1;
2372
+ continue;
2373
+ }
2374
+ if (char === ")") {
2375
+ link = null;
2376
+ at += 1;
2377
+ continue;
2378
+ }
2379
+ if (char === "*" || char === "_" || char === "~") {
2380
+ let run = 1;
2381
+ while (line[at + run] === char) run += 1;
2382
+ const start = at;
2383
+ at += run;
2384
+ const before = line[start - 1];
2385
+ const after = line[start + run];
2386
+ if (run < 2) continue;
2387
+ if (char === "_" && isWordCharacter(before) && isWordCharacter(after)) {
2388
+ continue;
2389
+ }
2390
+ const marker = char.repeat(run);
2391
+ const open2 = emphasis.findIndex((entry) => entry.marker === marker);
2392
+ if (open2 !== -1) {
2393
+ emphasis.splice(open2, 1);
2394
+ continue;
2395
+ }
2396
+ if (after !== void 0 && /\s/.test(after)) continue;
2397
+ emphasis.push({ marker, start });
2398
+ continue;
2399
+ }
2400
+ at += 1;
2401
+ }
2402
+ const open = [codeSpan, link, emphasis[0]?.start ?? null].filter(
2403
+ (value) => value !== null
2404
+ );
2405
+ return open.length === 0 ? line.length : Math.min(...open);
2406
+ }
2407
+ function revealBoundary(text, limit) {
2408
+ if (limit <= 0) return 0;
2409
+ const caughtUp = limit >= text.length;
2410
+ const head = caughtUp ? text : text.slice(0, limit);
2411
+ const lineStart = head.lastIndexOf("\n") + 1;
2412
+ const line = head.slice(lineStart);
2413
+ let fenceStart = null;
2414
+ let tableStart = null;
2415
+ let tableRows = 0;
2416
+ let at = 0;
2417
+ while (at < lineStart) {
2418
+ const breakAt = head.indexOf("\n", at);
2419
+ const end = breakAt === -1 ? lineStart : breakAt;
2420
+ const row = head.slice(at, end);
2421
+ if (fenceStart !== null) {
2422
+ if (FENCE.test(row)) fenceStart = null;
2423
+ } else if (FENCE.test(row)) {
2424
+ fenceStart = at;
2425
+ } else if (TABLE_ROW.test(row)) {
2426
+ if (tableStart === null) {
2427
+ tableStart = at;
2428
+ tableRows = 0;
2429
+ }
2430
+ tableRows += 1;
2431
+ } else if (tableStart !== null) {
2432
+ tableStart = null;
2433
+ tableRows = 0;
2434
+ }
2435
+ at = end + 1;
2436
+ }
2437
+ if (fenceStart === null) {
2438
+ const lastIsRow = TABLE_ROW.test(line);
2439
+ if (lastIsRow && tableStart === null) {
2440
+ tableStart = lineStart;
2441
+ tableRows = 0;
2442
+ } else if (!lastIsRow && line !== "") {
2443
+ tableStart = null;
2444
+ tableRows = 0;
2445
+ }
2446
+ if (tableStart !== null && tableRows < 2) return tableStart;
2447
+ }
2448
+ if (caughtUp) return text.length;
2449
+ if (fenceStart !== null) return fenceStart;
2450
+ if (TABLE_ROW.test(line)) return lineStart;
2451
+ if (EMPTY_BLOCK_MARKER.test(line)) return lineStart;
2452
+ const cut = lineStart + safeInlineEnd(line);
2453
+ return isHighSurrogate(text.charCodeAt(cut - 1)) ? cut - 1 : cut;
2454
+ }
2455
+ function prefersReducedMotion() {
2456
+ if (typeof window === "undefined") return true;
2457
+ try {
2458
+ return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
2459
+ } catch {
2460
+ return false;
2461
+ }
2462
+ }
2463
+ function useProgressiveReveal(text, settled) {
2464
+ const [, setFrame] = useState(0);
2465
+ const [reduced] = useState(prefersReducedMotion);
2466
+ const immediate = settled || reduced;
2467
+ const textRef = useRef(text);
2468
+ const cursorRef = useRef(immediate ? text.length : 0);
2469
+ const cutRef = useRef(immediate ? text.length : 0);
2470
+ const committedRef = useRef(0);
2471
+ if (text !== textRef.current) {
2472
+ const revealed = textRef.current.slice(0, Math.floor(cursorRef.current));
2473
+ if (revealed !== "" && !text.startsWith(revealed)) {
2474
+ cursorRef.current = text.length;
2475
+ }
2476
+ cursorRef.current = Math.min(cursorRef.current, text.length);
2477
+ cutRef.current = Math.min(cutRef.current, text.length);
2478
+ textRef.current = text;
2479
+ }
2480
+ if (immediate) cursorRef.current = text.length;
2481
+ const cut = immediate ? text.length : Math.max(
2482
+ cutRef.current,
2483
+ revealBoundary(text, Math.floor(cursorRef.current))
2484
+ );
2485
+ cutRef.current = cut;
2486
+ useEffect(() => {
2487
+ if (immediate) return;
2488
+ if (cursorRef.current >= text.length) return;
2489
+ let cancelled = false;
2490
+ let cancelStep = null;
2491
+ let previous = now();
2492
+ const commit = (at, force) => {
2493
+ if (!force && at - committedRef.current < COMMIT_INTERVAL_MS) return;
2494
+ committedRef.current = at;
2495
+ setFrame((value) => value + 1);
2496
+ };
2497
+ const step = () => {
2498
+ cancelStep = null;
2499
+ if (cancelled) return;
2500
+ const at = now();
2501
+ const elapsed = clamp(at - previous, 0, MAX_STEP_MS);
2502
+ previous = at;
2503
+ const length = textRef.current.length;
2504
+ const pending = length - cursorRef.current;
2505
+ if (pending <= 0) {
2506
+ commit(at, true);
2507
+ return;
2508
+ }
2509
+ const perSecond = clamp(
2510
+ pending / (DRAIN_TARGET_MS / 1e3),
2511
+ MIN_CHARS_PER_SECOND,
2512
+ MAX_CHARS_PER_SECOND
2513
+ );
2514
+ cursorRef.current = Math.min(
2515
+ length,
2516
+ cursorRef.current + perSecond * elapsed / 1e3
2517
+ );
2518
+ const done = cursorRef.current >= length;
2519
+ commit(at, done);
2520
+ if (!done) schedule();
2521
+ };
2522
+ const schedule = () => {
2523
+ if (typeof window.requestAnimationFrame === "function") {
2524
+ const id2 = window.requestAnimationFrame(step);
2525
+ cancelStep = () => window.cancelAnimationFrame(id2);
2526
+ return;
2527
+ }
2528
+ const id = setTimeout(step, FALLBACK_STEP_MS);
2529
+ cancelStep = () => clearTimeout(id);
2530
+ };
2531
+ schedule();
2532
+ return () => {
2533
+ cancelled = true;
2534
+ cancelStep?.();
2535
+ };
2536
+ }, [text, immediate]);
2537
+ return text.slice(0, cut);
2538
+ }
2155
2539
  function getRecognitionCtor() {
2156
2540
  if (typeof window === "undefined") return null;
2157
2541
  const w = window;
@@ -2370,6 +2754,141 @@ function useSpeechInput({
2370
2754
  );
2371
2755
  return { supported, listening, interim, error, start, stop, toggle };
2372
2756
  }
2757
+ var SILENT_FAILURE_MS = 1500;
2758
+ function speechPlatform() {
2759
+ if (typeof window === "undefined") return null;
2760
+ const scope = window;
2761
+ const synth = scope.speechSynthesis;
2762
+ const Utterance = scope.SpeechSynthesisUtterance;
2763
+ if (!synth || typeof synth.speak !== "function") return null;
2764
+ if (typeof Utterance !== "function") return null;
2765
+ return { synth, Utterance };
2766
+ }
2767
+ function normaliseTag(tag) {
2768
+ return tag.trim().toLowerCase().replace(/_/g, "-");
2769
+ }
2770
+ function primaryOf(tag) {
2771
+ return normaliseTag(tag).split("-")[0] ?? "";
2772
+ }
2773
+ function pickVoice(voices, lang) {
2774
+ const wanted = normaliseTag(lang);
2775
+ if (wanted === "") return null;
2776
+ const primary = primaryOf(wanted);
2777
+ const exact = voices.filter((voice) => normaliseTag(voice.lang) === wanted);
2778
+ const related = voices.filter((voice) => primaryOf(voice.lang) === primary);
2779
+ return exact.find((voice) => voice.default) ?? exact[0] ?? related.find((voice) => voice.default) ?? related[0] ?? null;
2780
+ }
2781
+ var activeOwner = null;
2782
+ var activeStop = null;
2783
+ function stopSpeaking() {
2784
+ activeStop?.();
2785
+ }
2786
+ function useSpeechOutput({
2787
+ lang
2788
+ } = {}) {
2789
+ const [available, setAvailable] = useState(() => speechPlatform() !== null);
2790
+ const [speaking, setSpeaking] = useState(false);
2791
+ const utteranceRef = useRef(null);
2792
+ const watchdogRef = useRef(null);
2793
+ const ownerRef = useRef({});
2794
+ const stopRef = useRef(() => void 0);
2795
+ const langRef = useRef(lang);
2796
+ langRef.current = lang;
2797
+ const clearWatchdog = useCallback(() => {
2798
+ if (watchdogRef.current === null) return;
2799
+ clearTimeout(watchdogRef.current);
2800
+ watchdogRef.current = null;
2801
+ }, []);
2802
+ const settle = useCallback(() => {
2803
+ clearWatchdog();
2804
+ utteranceRef.current = null;
2805
+ if (activeOwner === ownerRef.current) {
2806
+ activeOwner = null;
2807
+ activeStop = null;
2808
+ }
2809
+ setSpeaking(false);
2810
+ }, [clearWatchdog]);
2811
+ const stop = useCallback(() => {
2812
+ const owned = utteranceRef.current !== null;
2813
+ settle();
2814
+ if (owned) speechPlatform()?.synth.cancel();
2815
+ }, [settle]);
2816
+ stopRef.current = stop;
2817
+ const speak = useCallback(
2818
+ (text) => {
2819
+ const platform = speechPlatform();
2820
+ if (!platform) {
2821
+ setAvailable(false);
2822
+ return;
2823
+ }
2824
+ const spoken = text.trim();
2825
+ if (spoken === "") return;
2826
+ if (activeStop && activeOwner !== ownerRef.current) activeStop();
2827
+ platform.synth.cancel();
2828
+ const utterance = new platform.Utterance(spoken);
2829
+ const wanted = langRef.current;
2830
+ if (wanted !== void 0 && wanted !== "") {
2831
+ const voice = pickVoice(platform.synth.getVoices(), wanted);
2832
+ utterance.lang = voice?.lang ?? wanted;
2833
+ if (voice) utterance.voice = voice;
2834
+ }
2835
+ const mine = () => utteranceRef.current === utterance;
2836
+ utterance.onstart = () => {
2837
+ if (!mine()) return;
2838
+ clearWatchdog();
2839
+ setSpeaking(true);
2840
+ };
2841
+ utterance.onend = () => {
2842
+ if (!mine()) return;
2843
+ settle();
2844
+ };
2845
+ utterance.onerror = (event) => {
2846
+ if (!mine()) return;
2847
+ const code = typeof event?.error === "string" ? event.error : "";
2848
+ settle();
2849
+ if (code !== "interrupted" && code !== "canceled") setAvailable(false);
2850
+ };
2851
+ utteranceRef.current = utterance;
2852
+ activeOwner = ownerRef.current;
2853
+ activeStop = () => {
2854
+ stopRef.current();
2855
+ };
2856
+ setSpeaking(true);
2857
+ platform.synth.speak(utterance);
2858
+ clearWatchdog();
2859
+ watchdogRef.current = setTimeout(() => {
2860
+ watchdogRef.current = null;
2861
+ if (!mine()) return;
2862
+ if (platform.synth.speaking) return;
2863
+ settle();
2864
+ setAvailable(false);
2865
+ }, SILENT_FAILURE_MS);
2866
+ },
2867
+ [clearWatchdog, settle]
2868
+ );
2869
+ const toggle = useCallback(
2870
+ (text) => {
2871
+ if (speaking) stop();
2872
+ else speak(text);
2873
+ },
2874
+ [speak, speaking, stop]
2875
+ );
2876
+ useEffect(() => {
2877
+ if (!speaking || typeof window === "undefined") return;
2878
+ const onHide = () => {
2879
+ stopRef.current();
2880
+ };
2881
+ window.addEventListener("pagehide", onHide);
2882
+ return () => window.removeEventListener("pagehide", onHide);
2883
+ }, [speaking]);
2884
+ useEffect(
2885
+ () => () => {
2886
+ stopRef.current();
2887
+ },
2888
+ []
2889
+ );
2890
+ return { supported: available, speaking, speak, stop, toggle };
2891
+ }
2373
2892
  var SWIPE_THRESHOLD = 40;
2374
2893
  function useSwipe({
2375
2894
  onSwipeLeft,
@@ -2765,6 +3284,115 @@ var EXPLORE_CSS = `
2765
3284
  height: auto;
2766
3285
  }
2767
3286
 
3287
+ /* ---- Comparison and pricing tables ----------------------------------------------------
3288
+ The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
3289
+ the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
3290
+ the page must not scroll sideways because of it, and it has to be readable on a 320px
3291
+ phone, which a four-column grid of 1px borders is not.
3292
+
3293
+ Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
3294
+ \`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
3295
+ cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
3296
+ of table from chaining to the page \u2014 the same rule the transcript needed. */
3297
+ .boff-explore-body .boff-explore-table {
3298
+ max-width: 100%;
3299
+ overflow-x: auto;
3300
+ overscroll-behavior-x: contain;
3301
+ margin: 0.75rem 0;
3302
+ border: 1px solid hsl(var(--border));
3303
+ border-radius: 0.75rem;
3304
+ background-color: hsl(var(--card));
3305
+ }
3306
+ .boff-explore-body .boff-explore-table:focus-visible {
3307
+ outline: 2px solid hsl(var(--ring));
3308
+ outline-offset: 2px;
3309
+ }
3310
+ .boff-explore-body .boff-explore-table table {
3311
+ /* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
3312
+ display: table;
3313
+ width: 100%;
3314
+ max-width: none;
3315
+ margin: 0;
3316
+ border-collapse: collapse;
3317
+ font-size: 0.8125rem;
3318
+ }
3319
+ .boff-explore-body .boff-explore-table th,
3320
+ .boff-explore-body .boff-explore-table td {
3321
+ border: 0;
3322
+ border-top: 1px solid hsl(var(--border));
3323
+ padding: 0.5rem 0.75rem;
3324
+ /* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
3325
+ table there reads against the grain of every other line on the page. */
3326
+ text-align: start;
3327
+ vertical-align: top;
3328
+ overflow-wrap: anywhere;
3329
+ /* Prices in a column line up under each other instead of drifting by digit width. */
3330
+ font-variant-numeric: tabular-nums;
3331
+ min-width: 6rem;
3332
+ }
3333
+ .boff-explore-body .boff-explore-table thead th {
3334
+ border-top: 0;
3335
+ background-color: hsl(var(--muted));
3336
+ color: hsl(var(--foreground));
3337
+ font-weight: 600;
3338
+ white-space: nowrap;
3339
+ }
3340
+ /* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
3341
+ the row's heading is what makes a price list scannable rather than a grid of strings. */
3342
+ .boff-explore-body .boff-explore-table tbody td:first-child {
3343
+ font-weight: 600;
3344
+ color: hsl(var(--card-foreground));
3345
+ }
3346
+
3347
+ /* Narrow: one card per row. Each cell prints its own column heading, so a value is never
3348
+ stranded without the question it answers, and the subject cell becomes the card's title.
3349
+ A media query rather than a measured width \u2014 these pages are serialised post-hydration,
3350
+ so anything measured here would ship to every phone as static HTML. */
3351
+ @media (max-width: 30rem) {
3352
+ .boff-explore-body .boff-explore-table {
3353
+ overflow-x: visible;
3354
+ }
3355
+ .boff-explore-body .boff-explore-table table,
3356
+ .boff-explore-body .boff-explore-table tbody,
3357
+ .boff-explore-body .boff-explore-table tr,
3358
+ .boff-explore-body .boff-explore-table td {
3359
+ display: block;
3360
+ width: 100%;
3361
+ min-width: 0;
3362
+ }
3363
+ .boff-explore-body .boff-explore-table thead {
3364
+ display: none;
3365
+ }
3366
+ .boff-explore-body .boff-explore-table tr {
3367
+ border-top: 1px solid hsl(var(--border));
3368
+ padding: 0.5rem 0;
3369
+ }
3370
+ .boff-explore-body .boff-explore-table tr:first-child {
3371
+ border-top: 0;
3372
+ }
3373
+ .boff-explore-body .boff-explore-table td {
3374
+ border: 0;
3375
+ padding: 0.125rem 0.75rem;
3376
+ }
3377
+ .boff-explore-body .boff-explore-table td[data-label]::before {
3378
+ content: attr(data-label);
3379
+ display: block;
3380
+ font-size: 0.6875rem;
3381
+ font-weight: 600;
3382
+ text-transform: uppercase;
3383
+ letter-spacing: 0.04em;
3384
+ color: hsl(var(--muted-foreground));
3385
+ }
3386
+ .boff-explore-body .boff-explore-table tbody td:first-child {
3387
+ font-size: 0.9375rem;
3388
+ padding-bottom: 0.25rem;
3389
+ }
3390
+ .boff-explore-body .boff-explore-table tbody td:first-child::before {
3391
+ /* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
3392
+ display: none;
3393
+ }
3394
+ }
3395
+
2768
3396
  /* ---- Ambient backdrop -----------------------------------------------------------------
2769
3397
  Two very low-opacity radial washes behind the welcome state, drifting slowly. Painted on
2770
3398
  a pseudo-element with pointer-events:none so it can never intercept a click \u2014 a
@@ -2936,7 +3564,7 @@ function isOwnElement(el, root) {
2936
3564
  function visualViewportHeight() {
2937
3565
  return window.visualViewport?.height ?? window.innerHeight;
2938
3566
  }
2939
- function clamp(value, min, max) {
3567
+ function clamp2(value, min, max) {
2940
3568
  return Math.min(Math.max(value, min), max);
2941
3569
  }
2942
3570
  function isRestoredNavigation() {
@@ -3001,14 +3629,196 @@ function isInlineImageAllowed(src, origins) {
3001
3629
  if (url.protocol !== "https:" || url.username || url.password) return false;
3002
3630
  return origins.has(url.origin);
3003
3631
  }
3004
- var InlineImageScopeContext = createContext(null);
3632
+ var REFERENCE_KIND_ORDER = [
3633
+ "CONCEPT",
3634
+ "DOCS",
3635
+ "PRICING",
3636
+ "USE_CASE",
3637
+ "PAGE"
3638
+ ];
3639
+ var PRODUCT_SUBDOMAINS = [
3640
+ "alphadocs.",
3641
+ "alphaapp.",
3642
+ "docs.",
3643
+ "alpha.",
3644
+ "app.",
3645
+ "www."
3646
+ ];
3647
+ function readCatalogUrl(raw) {
3648
+ let parsed;
3649
+ try {
3650
+ parsed = new URL(raw);
3651
+ } catch {
3652
+ return null;
3653
+ }
3654
+ if (parsed.protocol !== "https:") return null;
3655
+ const host = parsed.hostname.toLowerCase();
3656
+ const prefix = PRODUCT_SUBDOMAINS.find(
3657
+ (candidate) => host.startsWith(candidate)
3658
+ );
3659
+ const apex = prefix === void 0 ? host : host.slice(prefix.length);
3660
+ const port = parsed.port ? `:${parsed.port}` : "";
3661
+ return {
3662
+ origin: `https://${apex}${port}`,
3663
+ prefix,
3664
+ pathname: parsed.pathname
3665
+ };
3666
+ }
3667
+ function referenceKindOf(url, origins) {
3668
+ const parsed = readCatalogUrl(url);
3669
+ if (parsed === null || !origins.has(parsed.origin)) return null;
3670
+ if (parsed.prefix === "docs." || parsed.prefix === "alphadocs.")
3671
+ return "DOCS";
3672
+ const segment = parsed.pathname.toLowerCase().split("/").filter(Boolean)[0] ?? "";
3673
+ if (segment === "concept" || segment === "concepts") return "CONCEPT";
3674
+ if (segment === "docs") return "DOCS";
3675
+ if (segment === "pricing" || segment === "plans") return "PRICING";
3676
+ if (segment === "use-case" || segment === "use-cases" || segment === "solutions") {
3677
+ return "USE_CASE";
3678
+ }
3679
+ return "PAGE";
3680
+ }
3681
+ function productNamesByOrigin(products) {
3682
+ const names = /* @__PURE__ */ new Map();
3683
+ for (const product of products) {
3684
+ let url;
3685
+ try {
3686
+ url = new URL(product.website);
3687
+ } catch {
3688
+ continue;
3689
+ }
3690
+ if (url.protocol !== "https:") continue;
3691
+ const apex = url.hostname.replace(/^www\./, "");
3692
+ const port = url.port ? `:${url.port}` : "";
3693
+ const key = `https://${apex}${port}`;
3694
+ if (!names.has(key)) names.set(key, product.name);
3695
+ }
3696
+ return names;
3697
+ }
3698
+ function summariseReferences(references, origins, productNames) {
3699
+ const counts = /* @__PURE__ */ new Map();
3700
+ const products = [];
3701
+ for (const reference of references) {
3702
+ const kind = referenceKindOf(reference.url, origins);
3703
+ if (kind !== null) counts.set(kind, (counts.get(kind) ?? 0) + 1);
3704
+ const parsed = readCatalogUrl(reference.url);
3705
+ const name = (parsed === null ? void 0 : productNames.get(parsed.origin)) ?? (reference.product || null);
3706
+ if (name != null && !products.includes(name)) products.push(name);
3707
+ }
3708
+ return {
3709
+ total: references.length,
3710
+ kinds: REFERENCE_KIND_ORDER.filter((kind) => counts.has(kind)).map(
3711
+ (kind) => ({ kind, count: counts.get(kind) ?? 0 })
3712
+ ),
3713
+ products
3714
+ };
3715
+ }
3716
+ var CODE_FENCE = /^\s*(?:```|~~~)/;
3717
+ var TABLE_ROW_LINE = /^\s{0,3}\|/;
3718
+ var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
3719
+ function readableLine(raw, mode) {
3720
+ let line = raw;
3721
+ line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
3722
+ line = mode === "speech" ? (
3723
+ // A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
3724
+ // prompt asks for two to five links in every answer. The label IS the words.
3725
+ line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
3726
+ ) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
3727
+ line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
3728
+ line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
3729
+ line = line.replace(/^\s{0,3}>\s?/, "");
3730
+ if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
3731
+ line = line.replace(/`([^`]+)`/g, "$1");
3732
+ line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
3733
+ line = line.replace(/__([^_]+)__/g, "$1");
3734
+ line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
3735
+ line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
3736
+ return line.replace(/[ \t]+$/, "");
3737
+ }
3738
+ function tableCells(row) {
3739
+ const trimmed = row.trim();
3740
+ const cells = [];
3741
+ let cell = "";
3742
+ for (let at = 0; at < trimmed.length; at += 1) {
3743
+ const char = trimmed[at] ?? "";
3744
+ if (char === "\\") {
3745
+ cell += char + (trimmed[at + 1] ?? "");
3746
+ at += 1;
3747
+ continue;
3748
+ }
3749
+ if (char === "|") {
3750
+ cells.push(cell.trim());
3751
+ cell = "";
3752
+ continue;
3753
+ }
3754
+ cell += char;
3755
+ }
3756
+ cells.push(cell.trim());
3757
+ if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
3758
+ if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
3759
+ return cells;
3760
+ }
3761
+ function asSentence(text) {
3762
+ return /[.!?]$/.test(text) ? text : `${text}.`;
3763
+ }
3764
+ function spokenTable(rows) {
3765
+ if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
3766
+ const labels = tableCells(rows[0] ?? "");
3767
+ const spoken = [];
3768
+ for (const row of rows.slice(2)) {
3769
+ const parts = [];
3770
+ tableCells(row).forEach((value, column) => {
3771
+ if (value === "") return;
3772
+ const label = column === 0 ? "" : labels[column] ?? "";
3773
+ parts.push(label === "" ? value : `${label}: ${value}`);
3774
+ });
3775
+ if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
3776
+ }
3777
+ return spoken;
3778
+ }
3779
+ function readableText(markdown, mode) {
3780
+ const lines = [];
3781
+ let inFence = false;
3782
+ let table = [];
3783
+ const flushTable = () => {
3784
+ if (table.length === 0) return;
3785
+ for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
3786
+ table = [];
3787
+ };
3788
+ for (const raw of markdown.split("\n")) {
3789
+ if (CODE_FENCE.test(raw)) {
3790
+ flushTable();
3791
+ inFence = !inFence;
3792
+ continue;
3793
+ }
3794
+ if (inFence) {
3795
+ lines.push(raw);
3796
+ continue;
3797
+ }
3798
+ if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
3799
+ table.push(raw);
3800
+ continue;
3801
+ }
3802
+ flushTable();
3803
+ lines.push(readableLine(raw, mode));
3804
+ }
3805
+ flushTable();
3806
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
3807
+ }
3808
+ function answerPlainText(markdown) {
3809
+ return readableText(markdown, "clipboard");
3810
+ }
3811
+ function answerSpeechText(markdown) {
3812
+ return readableText(markdown, "speech");
3813
+ }
3814
+ var AnswerScopeContext = createContext(null);
3005
3815
  var InsideLinkContext = createContext(false);
3006
3816
  function AnswerLink(props) {
3007
3817
  return /* @__PURE__ */ jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsx(MarkdownLink, { ...props }) });
3008
3818
  }
3009
3819
  var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
3010
3820
  function InlineConceptImage({ src, alt }) {
3011
- const scope = useContext(InlineImageScopeContext);
3821
+ const scope = useContext(AnswerScopeContext);
3012
3822
  const insideLink = useContext(InsideLinkContext);
3013
3823
  if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
3014
3824
  return null;
@@ -3056,9 +3866,70 @@ function InlineConceptImage({ src, alt }) {
3056
3866
  }
3057
3867
  ) });
3058
3868
  }
3869
+ function markdownText(node) {
3870
+ if (!node) return "";
3871
+ if (typeof node.value === "string") return node.value;
3872
+ return (node.children ?? []).map(markdownText).join("");
3873
+ }
3874
+ function hasHeaderCells(node) {
3875
+ return (node?.children ?? []).some((cell) => cell.tagName === "th");
3876
+ }
3877
+ function findHeaderRow(node) {
3878
+ for (const child of node?.children ?? []) {
3879
+ if (child.tagName === "tr" && hasHeaderCells(child)) return child;
3880
+ const nested = findHeaderRow(child);
3881
+ if (nested !== null) return nested;
3882
+ }
3883
+ return null;
3884
+ }
3885
+ function tableColumnLabels(node) {
3886
+ const row = findHeaderRow(node);
3887
+ if (row === null) return [];
3888
+ return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
3889
+ }
3890
+ var TableColumnsContext = createContext([]);
3891
+ function AnswerTable({ node, children }) {
3892
+ const scope = useContext(AnswerScopeContext);
3893
+ const table = /* @__PURE__ */ jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsx("table", { children }) });
3894
+ if (!scope) return table;
3895
+ return /* @__PURE__ */ jsx(
3896
+ "div",
3897
+ {
3898
+ "data-boff-explore": "table",
3899
+ role: "region",
3900
+ "aria-label": scope.strings.answerTable,
3901
+ tabIndex: 0,
3902
+ className: "boff-explore-table",
3903
+ children: table
3904
+ }
3905
+ );
3906
+ }
3907
+ function AnswerTableRow({
3908
+ node,
3909
+ children,
3910
+ ...props
3911
+ }) {
3912
+ const labels = useContext(TableColumnsContext);
3913
+ if (labels.length === 0 || hasHeaderCells(node)) {
3914
+ return /* @__PURE__ */ jsx("tr", { ...props, children });
3915
+ }
3916
+ let column = 0;
3917
+ const cells = Children.map(children, (cell) => {
3918
+ if (!isValidElement(cell)) return cell;
3919
+ const label = labels[column] ?? "";
3920
+ column += 1;
3921
+ if (label === "") return cell;
3922
+ return cloneElement(cell, {
3923
+ "data-label": label
3924
+ });
3925
+ });
3926
+ return /* @__PURE__ */ jsx("tr", { ...props, children: cells });
3927
+ }
3059
3928
  var ANSWER_MARKDOWN_COMPONENTS = {
3060
3929
  a: AnswerLink,
3061
- img: InlineConceptImage
3930
+ img: InlineConceptImage,
3931
+ table: AnswerTable,
3932
+ tr: AnswerTableRow
3062
3933
  };
3063
3934
  function ReferenceCard({
3064
3935
  reference,
@@ -3398,6 +4269,148 @@ function AnswerFeedback({
3398
4269
  }
3399
4270
  );
3400
4271
  }
4272
+ var PROVENANCE_CHIP = "inline-flex max-w-full items-center gap-1 rounded-full border border-border bg-muted/60 px-2 py-0.5 text-[11px] leading-5 text-muted-foreground";
4273
+ function ProvenanceChips({
4274
+ strings: T,
4275
+ provenance
4276
+ }) {
4277
+ if (provenance.total === 0) return null;
4278
+ const kindLabels = {
4279
+ CONCEPT: T.sourceKindConcept,
4280
+ DOCS: T.sourceKindDocs,
4281
+ PRICING: T.sourceKindPricing,
4282
+ USE_CASE: T.sourceKindUseCase,
4283
+ PAGE: T.sourceKindPage
4284
+ };
4285
+ return /* @__PURE__ */ jsxs(
4286
+ "ul",
4287
+ {
4288
+ "data-boff-explore": "provenance",
4289
+ "aria-label": T.groundedIn,
4290
+ className: "flex flex-wrap items-center gap-1.5",
4291
+ children: [
4292
+ /* @__PURE__ */ jsxs("li", { "data-boff-explore": "provenance-count", className: PROVENANCE_CHIP, children: [
4293
+ /* @__PURE__ */ jsx(BookOpenText, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
4294
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: provenance.total === 1 ? T.sourceCountOne : fill(T.sourceCountMany, { count: String(provenance.total) }) })
4295
+ ] }),
4296
+ provenance.kinds.map(({ kind, count }) => /* @__PURE__ */ jsx(
4297
+ "li",
4298
+ {
4299
+ "data-boff-explore": "provenance-kind",
4300
+ "data-kind": kind,
4301
+ "data-count": count,
4302
+ className: PROVENANCE_CHIP,
4303
+ children: /* @__PURE__ */ jsx("span", { className: "truncate", children: count > 1 ? `${kindLabels[kind]} \xB7 ${count}` : kindLabels[kind] })
4304
+ },
4305
+ kind
4306
+ )),
4307
+ provenance.products.map((name) => /* @__PURE__ */ jsx(
4308
+ "li",
4309
+ {
4310
+ "data-boff-explore": "provenance-product",
4311
+ className: PROVENANCE_CHIP,
4312
+ children: /* @__PURE__ */ jsx("span", { className: "truncate", children: name })
4313
+ },
4314
+ name
4315
+ ))
4316
+ ]
4317
+ }
4318
+ );
4319
+ }
4320
+ var COPY_FEEDBACK_MS = 2500;
4321
+ var ANSWER_ACTION_PILL = "inline-flex max-w-full items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring";
4322
+ var ANSWER_ACTION_IDLE = "border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent hover:text-foreground";
4323
+ var ANSWER_ACTION_ACTIVE = "border-primary/40 bg-accent text-primary";
4324
+ function ReadAloudButton({
4325
+ strings: T,
4326
+ text,
4327
+ lang
4328
+ }) {
4329
+ const speech = useSpeechOutput({ lang });
4330
+ if (!speech.supported) return null;
4331
+ const label = speech.speaking ? T.stopReading : T.readAloud;
4332
+ return /* @__PURE__ */ jsxs(
4333
+ "button",
4334
+ {
4335
+ "data-boff-explore": "read-aloud",
4336
+ "data-speaking": speech.speaking ? "true" : "false",
4337
+ type: "button",
4338
+ title: label,
4339
+ "aria-pressed": speech.speaking,
4340
+ onClick: () => speech.toggle(text),
4341
+ className: cn(
4342
+ ANSWER_ACTION_PILL,
4343
+ speech.speaking ? ANSWER_ACTION_ACTIVE : ANSWER_ACTION_IDLE
4344
+ ),
4345
+ children: [
4346
+ /* @__PURE__ */ jsx(Volume2, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
4347
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
4348
+ ]
4349
+ }
4350
+ );
4351
+ }
4352
+ function AnswerActions({
4353
+ strings: T,
4354
+ text,
4355
+ speechText,
4356
+ speechLang
4357
+ }) {
4358
+ const [outcome, setOutcome] = useState("idle");
4359
+ const resetRef = useRef(null);
4360
+ useEffect(
4361
+ () => () => {
4362
+ if (resetRef.current !== null) clearTimeout(resetRef.current);
4363
+ },
4364
+ []
4365
+ );
4366
+ const copy = useCallback(async () => {
4367
+ let copied = false;
4368
+ try {
4369
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
4370
+ if (typeof clipboard?.writeText === "function") {
4371
+ await clipboard.writeText(text);
4372
+ copied = true;
4373
+ }
4374
+ } catch {
4375
+ copied = false;
4376
+ }
4377
+ setOutcome(copied ? "copied" : "failed");
4378
+ if (resetRef.current !== null) clearTimeout(resetRef.current);
4379
+ resetRef.current = setTimeout(() => setOutcome("idle"), COPY_FEEDBACK_MS);
4380
+ }, [text]);
4381
+ const label = outcome === "copied" ? T.copied : outcome === "failed" ? T.copyFailed : T.copyAnswer;
4382
+ return /* @__PURE__ */ jsxs(
4383
+ "div",
4384
+ {
4385
+ "data-boff-explore": "answer-actions",
4386
+ className: "flex flex-wrap items-center gap-2 pt-0.5",
4387
+ children: [
4388
+ /* @__PURE__ */ jsxs(
4389
+ "button",
4390
+ {
4391
+ "data-boff-explore": "copy-answer",
4392
+ "data-outcome": outcome,
4393
+ type: "button",
4394
+ title: label,
4395
+ onClick: () => {
4396
+ void copy();
4397
+ },
4398
+ className: cn(
4399
+ ANSWER_ACTION_PILL,
4400
+ outcome === "failed" ? "border-destructive/40 bg-card text-destructive" : ANSWER_ACTION_IDLE
4401
+ ),
4402
+ children: [
4403
+ outcome === "copied" ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
4404
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
4405
+ ]
4406
+ }
4407
+ ),
4408
+ /* @__PURE__ */ jsx(ReadAloudButton, { strings: T, text: speechText, lang: speechLang }),
4409
+ /* @__PURE__ */ jsx("span", { "data-boff-explore": "copy-status", role: "status", className: "sr-only", children: outcome === "idle" ? "" : label })
4410
+ ]
4411
+ }
4412
+ );
4413
+ }
3401
4414
  function UserBubble({
3402
4415
  strings: T,
3403
4416
  message
@@ -3443,6 +4456,8 @@ function AssistantBubble({
3443
4456
  message,
3444
4457
  activity,
3445
4458
  imageOrigins,
4459
+ productNames,
4460
+ speechLang,
3446
4461
  onNavigate,
3447
4462
  anchorRef,
3448
4463
  onFollow,
@@ -3453,6 +4468,7 @@ function AssistantBubble({
3453
4468
  const body = message.content || message.partialContent || "";
3454
4469
  const failed = message.status === "FAILED";
3455
4470
  const completed = message.status === "COMPLETED";
4471
+ const visible = useProgressiveReveal(body, !streaming);
3456
4472
  const [failedImages, setFailedImages] = useState(
3457
4473
  () => /* @__PURE__ */ new Set()
3458
4474
  );
@@ -3461,7 +4477,7 @@ function AssistantBubble({
3461
4477
  (current) => current.has(src) ? current : new Set(current).add(src)
3462
4478
  );
3463
4479
  }, []);
3464
- const imageScope = useMemo(
4480
+ const answerScope = useMemo(
3465
4481
  () => ({
3466
4482
  strings: T,
3467
4483
  enabled: completed,
@@ -3471,6 +4487,19 @@ function AssistantBubble({
3471
4487
  }),
3472
4488
  [T, completed, imageOrigins, failedImages, markImageFailed]
3473
4489
  );
4490
+ const provenance = useMemo(
4491
+ () => summariseReferences(message.references, imageOrigins, productNames),
4492
+ [message.references, imageOrigins, productNames]
4493
+ );
4494
+ const copyText = useMemo(
4495
+ () => answerPlainText(message.content),
4496
+ [message.content]
4497
+ );
4498
+ const speechText = useMemo(
4499
+ () => answerSpeechText(message.content),
4500
+ [message.content]
4501
+ );
4502
+ const grounded = completed && message.answered !== false;
3474
4503
  return /* @__PURE__ */ jsxs(
3475
4504
  "div",
3476
4505
  {
@@ -3516,10 +4545,11 @@ function AssistantBubble({
3516
4545
  ]
3517
4546
  }
3518
4547
  ) : null,
3519
- body ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
3520
- /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsx(InlineImageScopeContext.Provider, { value: imageScope, children: /* @__PURE__ */ jsx(Markdown, { components: ANSWER_MARKDOWN_COMPONENTS, children: body }) }) }),
4548
+ visible ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
4549
+ /* @__PURE__ */ jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsx(AnswerScopeContext.Provider, { value: answerScope, children: /* @__PURE__ */ jsx(Markdown, { components: ANSWER_MARKDOWN_COMPONENTS, children: visible }) }) }),
3521
4550
  streaming ? /* @__PURE__ */ jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
3522
4551
  ] }) : null,
4552
+ grounded && provenance.total > 0 ? /* @__PURE__ */ jsx(ProvenanceChips, { strings: T, provenance }) : null,
3523
4553
  streaming ? /* @__PURE__ */ jsx(ThinkingIndicator, { activity }) : null,
3524
4554
  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: T.answerDidNotArrive }) : null,
3525
4555
  message.references.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
@@ -3559,6 +4589,15 @@ function AssistantBubble({
3559
4589
  question
3560
4590
  )) })
3561
4591
  ] }) : null,
4592
+ completed && copyText !== "" ? /* @__PURE__ */ jsx(
4593
+ AnswerActions,
4594
+ {
4595
+ strings: T,
4596
+ text: copyText,
4597
+ speechText,
4598
+ speechLang
4599
+ }
4600
+ ) : null,
3562
4601
  completed && onFeedback ? /* @__PURE__ */ jsx(
3563
4602
  AnswerFeedback,
3564
4603
  {
@@ -3771,6 +4810,96 @@ function ProductStrip({
3771
4810
  }
3772
4811
  );
3773
4812
  }
4813
+ var MAX_CONCEPT_CARDS = 6;
4814
+ function ConceptStrip({
4815
+ strings: T,
4816
+ highlights,
4817
+ onAsk,
4818
+ disabled
4819
+ }) {
4820
+ const [failed, setFailed] = useState(() => /* @__PURE__ */ new Set());
4821
+ const markFailed = useCallback((src) => {
4822
+ setFailed(
4823
+ (current) => current.has(src) ? current : new Set(current).add(src)
4824
+ );
4825
+ }, []);
4826
+ const visible = highlights.filter(
4827
+ (highlight) => highlight.imageUrl !== "" && !failed.has(highlight.imageUrl)
4828
+ ).slice(0, MAX_CONCEPT_CARDS);
4829
+ if (visible.length === 0) return null;
4830
+ return /* @__PURE__ */ jsxs(
4831
+ "div",
4832
+ {
4833
+ "data-boff-explore": "concept-strip-block",
4834
+ className: "boff-explore-rise boff-explore-rise-1 mt-6",
4835
+ children: [
4836
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: T.conceptStripTitle }),
4837
+ /* @__PURE__ */ jsx(
4838
+ "ul",
4839
+ {
4840
+ "data-boff-explore": "concept-strip",
4841
+ "aria-label": T.conceptStripLabel,
4842
+ className: "boff-explore-strip flex min-w-0 snap-x gap-3 overflow-x-auto pb-1",
4843
+ children: visible.map((highlight) => /* @__PURE__ */ jsx(
4844
+ "li",
4845
+ {
4846
+ className: "shrink-0 snap-start",
4847
+ children: /* @__PURE__ */ jsxs(
4848
+ "button",
4849
+ {
4850
+ "data-boff-explore": "concept-card",
4851
+ "data-product": highlight.product,
4852
+ type: "button",
4853
+ disabled,
4854
+ "aria-label": fill(T.conceptAsk, {
4855
+ title: highlight.title,
4856
+ product: highlight.productName
4857
+ }),
4858
+ onClick: () => onAsk(
4859
+ fill(T.conceptAskQuestion, {
4860
+ title: highlight.title,
4861
+ product: highlight.productName
4862
+ })
4863
+ ),
4864
+ className: "group flex w-40 flex-col overflow-hidden rounded-xl border border-border bg-card text-left 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 sm:w-48",
4865
+ children: [
4866
+ /* @__PURE__ */ jsxs("span", { className: "relative block aspect-[16/9] w-full overflow-hidden bg-muted", children: [
4867
+ /* @__PURE__ */ jsx(
4868
+ "img",
4869
+ {
4870
+ src: highlight.imageUrl,
4871
+ alt: highlight.alt,
4872
+ loading: "lazy",
4873
+ decoding: "async",
4874
+ onError: () => markFailed(highlight.imageUrl),
4875
+ className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
4876
+ }
4877
+ ),
4878
+ /* @__PURE__ */ jsx(
4879
+ "span",
4880
+ {
4881
+ "aria-hidden": "true",
4882
+ className: "pointer-events-none absolute left-1.5 top-1.5 rounded-full border border-border bg-background/90 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-foreground",
4883
+ children: T.conceptIllustration
4884
+ }
4885
+ )
4886
+ ] }),
4887
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5 p-2.5", children: [
4888
+ /* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-xs font-semibold leading-snug text-card-foreground", children: highlight.title }),
4889
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: highlight.productName })
4890
+ ] })
4891
+ ]
4892
+ }
4893
+ )
4894
+ },
4895
+ `${highlight.product}:${highlight.url}`
4896
+ ))
4897
+ }
4898
+ )
4899
+ ]
4900
+ }
4901
+ );
4902
+ }
3774
4903
  function DisclaimerDialog({
3775
4904
  open,
3776
4905
  onClose,
@@ -3877,6 +5006,7 @@ function WelcomeState({
3877
5006
  body,
3878
5007
  prompts,
3879
5008
  products,
5009
+ conceptHighlights,
3880
5010
  focusProduct,
3881
5011
  onPrompt,
3882
5012
  onFocus,
@@ -3891,6 +5021,15 @@ function WelcomeState({
3891
5021
  /* @__PURE__ */ jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
3892
5022
  /* @__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 })
3893
5023
  ] }),
5024
+ /* @__PURE__ */ jsx(
5025
+ ConceptStrip,
5026
+ {
5027
+ strings: T,
5028
+ highlights: conceptHighlights,
5029
+ onAsk: onPrompt,
5030
+ disabled
5031
+ }
5032
+ ),
3894
5033
  /* @__PURE__ */ jsx("ul", { className: "boff-explore-rise boff-explore-rise-1 mt-6 grid grid-cols-3 gap-2 sm:mt-7 sm:gap-2.5", children: CAPABILITIES.map(({ icon: Icon, body: capBody }, i) => /* @__PURE__ */ jsxs(
3895
5034
  "li",
3896
5035
  {
@@ -3952,6 +5091,7 @@ function ExplorePage({
3952
5091
  );
3953
5092
  const T = useMemo(() => exploreStrings(activeLocale), [activeLocale]);
3954
5093
  const rtl = isRtlLocale(activeLocale);
5094
+ const speechLang = speechLocaleOf(activeLocale);
3955
5095
  const {
3956
5096
  phase,
3957
5097
  catalog,
@@ -4036,6 +5176,10 @@ function ExplorePage({
4036
5176
  () => inlineImageOrigins(catalog.products),
4037
5177
  [catalog.products]
4038
5178
  );
5179
+ const productNames = useMemo(
5180
+ () => productNamesByOrigin(catalog.products),
5181
+ [catalog.products]
5182
+ );
4039
5183
  useEffect(() => {
4040
5184
  const el = textareaRef.current;
4041
5185
  if (!el) return;
@@ -4052,11 +5196,11 @@ function ExplorePage({
4052
5196
  const layoutHeight = window.innerHeight;
4053
5197
  if (layoutHeight === 0) return;
4054
5198
  const top = Math.round(
4055
- clamp(el.getBoundingClientRect().top, 0, layoutHeight)
5199
+ clamp2(el.getBoundingClientRect().top, 0, layoutHeight)
4056
5200
  );
4057
5201
  const viewport = window.visualViewport;
4058
5202
  const inset = viewport ? Math.round(
4059
- clamp(
5203
+ clamp2(
4060
5204
  layoutHeight - viewport.height - viewport.offsetTop,
4061
5205
  0,
4062
5206
  layoutHeight
@@ -4337,6 +5481,7 @@ function ExplorePage({
4337
5481
  const text = draft.trim();
4338
5482
  if (!text || overLimit || busy || !canSend) return;
4339
5483
  stopDictation();
5484
+ stopSpeaking();
4340
5485
  setDraft("");
4341
5486
  stickToBottomRef.current = true;
4342
5487
  void send(text, toPayload(stagedRef.current));
@@ -4347,6 +5492,7 @@ function ExplorePage({
4347
5492
  const sendPrompt = useCallback(
4348
5493
  (prompt) => {
4349
5494
  if (!canSend || busy) return;
5495
+ stopSpeaking();
4350
5496
  setDraft("");
4351
5497
  stickToBottomRef.current = true;
4352
5498
  void send(prompt, toPayload(stagedRef.current));
@@ -4567,6 +5713,7 @@ function ExplorePage({
4567
5713
  body: resolvedBody,
4568
5714
  prompts,
4569
5715
  products: catalog.products,
5716
+ conceptHighlights: catalog.conceptHighlights,
4570
5717
  focusProduct,
4571
5718
  onPrompt: sendPrompt,
4572
5719
  onFocus: setFocusProduct,
@@ -4580,6 +5727,8 @@ function ExplorePage({
4580
5727
  message,
4581
5728
  activity,
4582
5729
  imageOrigins,
5730
+ productNames,
5731
+ speechLang,
4583
5732
  onNavigate,
4584
5733
  anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
4585
5734
  onFollow: (kind, url) => recordClick(message.id, kind, url),
@@ -4882,6 +6031,6 @@ function ExplorePage({
4882
6031
  );
4883
6032
  }
4884
6033
 
4885
- export { ExplorePage, inlineImageOrigins, isInlineImageAllowed, logoUrlOf };
6034
+ export { ExplorePage, answerPlainText, answerSpeechText, inlineImageOrigins, isInlineImageAllowed, logoUrlOf, productNamesByOrigin, referenceKindOf, summariseReferences };
4886
6035
  //# sourceMappingURL=explore.mjs.map
4887
6036
  //# sourceMappingURL=explore.mjs.map