@burdenoff/website-sdk 2026.922.4 → 2026.923.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/explore.d.mts +4 -158
- package/dist/components/explore.d.ts +4 -158
- package/dist/components/explore.js +1464 -263
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +1466 -266
- package/dist/components/explore.mjs.map +1 -1
- package/dist/explore-D25zoXy9.d.mts +240 -0
- package/dist/explore-I4QnY4J9.d.ts +240 -0
- package/dist/{explore-types-DvQPiL1t.d.mts → explore-types-B5sdOC5M.d.mts} +75 -2
- package/dist/{explore-types-DvQPiL1t.d.ts → explore-types-B5sdOC5M.d.ts} +75 -2
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +481 -8
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +479 -9
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +433 -25
- package/dist/index.d.ts +433 -25
- package/dist/index.js +2839 -409
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2817 -412
- package/dist/index.mjs.map +1 -1
- package/dist/{use-explore-chat-Dgm-jf96.d.mts → use-progressive-reveal-Cmy8bA7L.d.mts} +88 -7
- package/dist/{use-explore-chat-CNEdKBc1.d.ts → use-progressive-reveal-DHPGGxZu.d.ts} +88 -7
- package/package.json +3 -2
|
@@ -6,11 +6,11 @@ var ReactMarkdown = require('react-markdown');
|
|
|
6
6
|
var rehypeSanitize = require('rehype-sanitize');
|
|
7
7
|
var remarkGfm = require('remark-gfm');
|
|
8
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
var clsx = require('clsx');
|
|
10
|
+
var tailwindMerge = require('tailwind-merge');
|
|
9
11
|
var reactHelmetAsync = require('react-helmet-async');
|
|
10
12
|
var reactSlot = require('@radix-ui/react-slot');
|
|
11
13
|
var classVarianceAuthority = require('class-variance-authority');
|
|
12
|
-
var clsx = require('clsx');
|
|
13
|
-
var tailwindMerge = require('tailwind-merge');
|
|
14
14
|
|
|
15
15
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
16
|
|
|
@@ -40,6 +40,472 @@ var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
|
|
|
40
40
|
var __defProp = Object.defineProperty;
|
|
41
41
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
42
42
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
43
|
+
function MarkdownLink({
|
|
44
|
+
href,
|
|
45
|
+
children,
|
|
46
|
+
// react-markdown passes the hast node to every override; it is not a DOM attribute.
|
|
47
|
+
node: _node,
|
|
48
|
+
...props
|
|
49
|
+
}) {
|
|
50
|
+
const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
|
|
51
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
52
|
+
"a",
|
|
53
|
+
{
|
|
54
|
+
href,
|
|
55
|
+
...props,
|
|
56
|
+
...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
|
|
57
|
+
children
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
var defaultComponents = {
|
|
62
|
+
a: MarkdownLink
|
|
63
|
+
};
|
|
64
|
+
function Markdown({
|
|
65
|
+
children,
|
|
66
|
+
className,
|
|
67
|
+
inline,
|
|
68
|
+
components
|
|
69
|
+
}) {
|
|
70
|
+
const content = /* @__PURE__ */ jsxRuntime.jsx(
|
|
71
|
+
ReactMarkdown__default.default,
|
|
72
|
+
{
|
|
73
|
+
remarkPlugins: [remarkGfm__default.default],
|
|
74
|
+
rehypePlugins: [rehypeSanitize__default.default],
|
|
75
|
+
components: { ...defaultComponents, ...components },
|
|
76
|
+
...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
|
|
77
|
+
children
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
if (inline) {
|
|
81
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: content });
|
|
82
|
+
}
|
|
83
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: content });
|
|
84
|
+
}
|
|
85
|
+
function cn(...inputs) {
|
|
86
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
87
|
+
}
|
|
88
|
+
var EXPLORE_ANSWER_CSS = `
|
|
89
|
+
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
90
|
+
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
91
|
+
WHOLE document horizontally scrollable, not just the bubble. */
|
|
92
|
+
.boff-explore-body,
|
|
93
|
+
.boff-explore-body p,
|
|
94
|
+
.boff-explore-body li,
|
|
95
|
+
.boff-explore-body a,
|
|
96
|
+
.boff-explore-body h1,
|
|
97
|
+
.boff-explore-body h2,
|
|
98
|
+
.boff-explore-body h3,
|
|
99
|
+
/* h4-h6 as well: a published answer page pushes the model's own headings down a level or
|
|
100
|
+
two so they nest under the page's, and a long unbroken word is no narrower for it. */
|
|
101
|
+
.boff-explore-body h4,
|
|
102
|
+
.boff-explore-body h5,
|
|
103
|
+
.boff-explore-body h6 {
|
|
104
|
+
overflow-wrap: anywhere;
|
|
105
|
+
word-break: break-word;
|
|
106
|
+
}
|
|
107
|
+
.boff-explore-body pre {
|
|
108
|
+
overflow-x: auto;
|
|
109
|
+
max-width: 100%;
|
|
110
|
+
}
|
|
111
|
+
.boff-explore-body table {
|
|
112
|
+
display: block;
|
|
113
|
+
overflow-x: auto;
|
|
114
|
+
max-width: 100%;
|
|
115
|
+
}
|
|
116
|
+
.boff-explore-body img {
|
|
117
|
+
max-width: 100%;
|
|
118
|
+
height: auto;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* ---- Comparison and pricing tables ----------------------------------------------------
|
|
122
|
+
The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
|
|
123
|
+
the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
|
|
124
|
+
the page must not scroll sideways because of it, and it has to be readable on a 320px
|
|
125
|
+
phone, which a four-column grid of 1px borders is not.
|
|
126
|
+
|
|
127
|
+
Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
|
|
128
|
+
\`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
|
|
129
|
+
cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
|
|
130
|
+
of table from chaining to the page \u2014 the same rule the transcript needed. */
|
|
131
|
+
.boff-explore-body .boff-explore-table {
|
|
132
|
+
max-width: 100%;
|
|
133
|
+
overflow-x: auto;
|
|
134
|
+
overscroll-behavior-x: contain;
|
|
135
|
+
margin: 0.75rem 0;
|
|
136
|
+
border: 1px solid hsl(var(--border));
|
|
137
|
+
border-radius: 0.75rem;
|
|
138
|
+
background-color: hsl(var(--card));
|
|
139
|
+
}
|
|
140
|
+
.boff-explore-body .boff-explore-table:focus-visible {
|
|
141
|
+
outline: 2px solid hsl(var(--ring));
|
|
142
|
+
outline-offset: 2px;
|
|
143
|
+
}
|
|
144
|
+
.boff-explore-body .boff-explore-table table {
|
|
145
|
+
/* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
|
|
146
|
+
display: table;
|
|
147
|
+
width: 100%;
|
|
148
|
+
max-width: none;
|
|
149
|
+
margin: 0;
|
|
150
|
+
border-collapse: collapse;
|
|
151
|
+
font-size: 0.8125rem;
|
|
152
|
+
}
|
|
153
|
+
.boff-explore-body .boff-explore-table th,
|
|
154
|
+
.boff-explore-body .boff-explore-table td {
|
|
155
|
+
border: 0;
|
|
156
|
+
border-top: 1px solid hsl(var(--border));
|
|
157
|
+
padding: 0.5rem 0.75rem;
|
|
158
|
+
/* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
|
|
159
|
+
table there reads against the grain of every other line on the page. */
|
|
160
|
+
text-align: start;
|
|
161
|
+
vertical-align: top;
|
|
162
|
+
overflow-wrap: anywhere;
|
|
163
|
+
/* Prices in a column line up under each other instead of drifting by digit width. */
|
|
164
|
+
font-variant-numeric: tabular-nums;
|
|
165
|
+
min-width: 6rem;
|
|
166
|
+
}
|
|
167
|
+
.boff-explore-body .boff-explore-table thead th {
|
|
168
|
+
border-top: 0;
|
|
169
|
+
background-color: hsl(var(--muted));
|
|
170
|
+
color: hsl(var(--foreground));
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
white-space: nowrap;
|
|
173
|
+
}
|
|
174
|
+
/* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
|
|
175
|
+
the row's heading is what makes a price list scannable rather than a grid of strings. */
|
|
176
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
177
|
+
font-weight: 600;
|
|
178
|
+
color: hsl(var(--card-foreground));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Narrow: one card per row. Each cell prints its own column heading, so a value is never
|
|
182
|
+
stranded without the question it answers, and the subject cell becomes the card's title.
|
|
183
|
+
A media query rather than a measured width \u2014 these pages are serialised post-hydration,
|
|
184
|
+
so anything measured here would ship to every phone as static HTML. */
|
|
185
|
+
@media (max-width: 30rem) {
|
|
186
|
+
.boff-explore-body .boff-explore-table {
|
|
187
|
+
overflow-x: visible;
|
|
188
|
+
}
|
|
189
|
+
.boff-explore-body .boff-explore-table table,
|
|
190
|
+
.boff-explore-body .boff-explore-table tbody,
|
|
191
|
+
.boff-explore-body .boff-explore-table tr,
|
|
192
|
+
.boff-explore-body .boff-explore-table td {
|
|
193
|
+
display: block;
|
|
194
|
+
width: 100%;
|
|
195
|
+
min-width: 0;
|
|
196
|
+
}
|
|
197
|
+
.boff-explore-body .boff-explore-table thead {
|
|
198
|
+
display: none;
|
|
199
|
+
}
|
|
200
|
+
.boff-explore-body .boff-explore-table tr {
|
|
201
|
+
border-top: 1px solid hsl(var(--border));
|
|
202
|
+
padding: 0.5rem 0;
|
|
203
|
+
}
|
|
204
|
+
.boff-explore-body .boff-explore-table tr:first-child {
|
|
205
|
+
border-top: 0;
|
|
206
|
+
}
|
|
207
|
+
.boff-explore-body .boff-explore-table td {
|
|
208
|
+
border: 0;
|
|
209
|
+
padding: 0.125rem 0.75rem;
|
|
210
|
+
}
|
|
211
|
+
.boff-explore-body .boff-explore-table td[data-label]::before {
|
|
212
|
+
content: attr(data-label);
|
|
213
|
+
display: block;
|
|
214
|
+
font-size: 0.6875rem;
|
|
215
|
+
font-weight: 600;
|
|
216
|
+
text-transform: uppercase;
|
|
217
|
+
letter-spacing: 0.04em;
|
|
218
|
+
color: hsl(var(--muted-foreground));
|
|
219
|
+
}
|
|
220
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
221
|
+
font-size: 0.9375rem;
|
|
222
|
+
padding-bottom: 0.25rem;
|
|
223
|
+
}
|
|
224
|
+
.boff-explore-body .boff-explore-table tbody td:first-child::before {
|
|
225
|
+
/* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
|
|
226
|
+
display: none;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
function ExploreAnswerStyles() {
|
|
231
|
+
return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_ANSWER_CSS } });
|
|
232
|
+
}
|
|
233
|
+
function inlineImageOrigins(products) {
|
|
234
|
+
const origins = /* @__PURE__ */ new Set();
|
|
235
|
+
for (const product of products) {
|
|
236
|
+
let url;
|
|
237
|
+
try {
|
|
238
|
+
url = new URL(product.website);
|
|
239
|
+
} catch {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (url.protocol !== "https:") continue;
|
|
243
|
+
const apex = url.hostname.replace(/^www\./, "");
|
|
244
|
+
const port = url.port ? `:${url.port}` : "";
|
|
245
|
+
origins.add(`https://${apex}${port}`);
|
|
246
|
+
origins.add(`https://www.${apex}${port}`);
|
|
247
|
+
}
|
|
248
|
+
return origins;
|
|
249
|
+
}
|
|
250
|
+
function isInlineImageAllowed(src, origins) {
|
|
251
|
+
if (typeof src !== "string" || src === "") return false;
|
|
252
|
+
let url;
|
|
253
|
+
try {
|
|
254
|
+
url = new URL(src);
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
if (url.protocol !== "https:" || url.username || url.password) return false;
|
|
259
|
+
return origins.has(url.origin);
|
|
260
|
+
}
|
|
261
|
+
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
262
|
+
var TABLE_ROW_LINE = /^\s{0,3}\|/;
|
|
263
|
+
var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
|
|
264
|
+
function readableLine(raw, mode) {
|
|
265
|
+
let line = raw;
|
|
266
|
+
line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
|
|
267
|
+
line = mode === "speech" ? (
|
|
268
|
+
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
269
|
+
// prompt asks for two to five links in every answer. The label IS the words.
|
|
270
|
+
line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
|
|
271
|
+
) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
|
|
272
|
+
line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
273
|
+
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
274
|
+
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
275
|
+
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
276
|
+
line = line.replace(/`([^`]+)`/g, "$1");
|
|
277
|
+
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
278
|
+
line = line.replace(/__([^_]+)__/g, "$1");
|
|
279
|
+
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
280
|
+
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
281
|
+
return line.replace(/[ \t]+$/, "");
|
|
282
|
+
}
|
|
283
|
+
function tableCells(row) {
|
|
284
|
+
const trimmed = row.trim();
|
|
285
|
+
const cells = [];
|
|
286
|
+
let cell = "";
|
|
287
|
+
for (let at = 0; at < trimmed.length; at += 1) {
|
|
288
|
+
const char = trimmed[at] ?? "";
|
|
289
|
+
if (char === "\\") {
|
|
290
|
+
cell += char + (trimmed[at + 1] ?? "");
|
|
291
|
+
at += 1;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (char === "|") {
|
|
295
|
+
cells.push(cell.trim());
|
|
296
|
+
cell = "";
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
cell += char;
|
|
300
|
+
}
|
|
301
|
+
cells.push(cell.trim());
|
|
302
|
+
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
303
|
+
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
304
|
+
return cells;
|
|
305
|
+
}
|
|
306
|
+
function asSentence(text) {
|
|
307
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
308
|
+
}
|
|
309
|
+
function spokenTable(rows) {
|
|
310
|
+
if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
|
|
311
|
+
const labels = tableCells(rows[0] ?? "");
|
|
312
|
+
const spoken = [];
|
|
313
|
+
for (const row of rows.slice(2)) {
|
|
314
|
+
const parts = [];
|
|
315
|
+
tableCells(row).forEach((value, column) => {
|
|
316
|
+
if (value === "") return;
|
|
317
|
+
const label = column === 0 ? "" : labels[column] ?? "";
|
|
318
|
+
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
319
|
+
});
|
|
320
|
+
if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
|
|
321
|
+
}
|
|
322
|
+
return spoken;
|
|
323
|
+
}
|
|
324
|
+
function readableText(markdown, mode) {
|
|
325
|
+
const lines = [];
|
|
326
|
+
let inFence = false;
|
|
327
|
+
let table = [];
|
|
328
|
+
const flushTable = () => {
|
|
329
|
+
if (table.length === 0) return;
|
|
330
|
+
for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
|
|
331
|
+
table = [];
|
|
332
|
+
};
|
|
333
|
+
for (const raw of markdown.split("\n")) {
|
|
334
|
+
if (CODE_FENCE.test(raw)) {
|
|
335
|
+
flushTable();
|
|
336
|
+
inFence = !inFence;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (inFence) {
|
|
340
|
+
lines.push(raw);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
|
|
344
|
+
table.push(raw);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
flushTable();
|
|
348
|
+
lines.push(readableLine(raw, mode));
|
|
349
|
+
}
|
|
350
|
+
flushTable();
|
|
351
|
+
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
352
|
+
}
|
|
353
|
+
function answerPlainText(markdown) {
|
|
354
|
+
return readableText(markdown, "clipboard");
|
|
355
|
+
}
|
|
356
|
+
function answerSpeechText(markdown) {
|
|
357
|
+
return readableText(markdown, "speech");
|
|
358
|
+
}
|
|
359
|
+
var AnswerScopeContext = React.createContext(null);
|
|
360
|
+
var InsideLinkContext = React.createContext(false);
|
|
361
|
+
function AnswerLink(props) {
|
|
362
|
+
return /* @__PURE__ */ jsxRuntime.jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownLink, { ...props }) });
|
|
363
|
+
}
|
|
364
|
+
var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
|
|
365
|
+
function InlineConceptImage({ src, alt }) {
|
|
366
|
+
const scope = React.useContext(AnswerScopeContext);
|
|
367
|
+
const insideLink = React.useContext(InsideLinkContext);
|
|
368
|
+
if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const { strings: T, onFailed } = scope;
|
|
372
|
+
const picture = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
373
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
374
|
+
"img",
|
|
375
|
+
{
|
|
376
|
+
src,
|
|
377
|
+
alt: alt ?? "",
|
|
378
|
+
loading: "lazy",
|
|
379
|
+
decoding: "async",
|
|
380
|
+
onError: () => onFailed(src),
|
|
381
|
+
className: "block h-auto max-h-80 w-auto max-w-full object-contain"
|
|
382
|
+
}
|
|
383
|
+
),
|
|
384
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
385
|
+
"span",
|
|
386
|
+
{
|
|
387
|
+
"aria-hidden": "true",
|
|
388
|
+
className: "pointer-events-none absolute left-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
|
|
389
|
+
children: T.conceptIllustration
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
] });
|
|
393
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
394
|
+
"a",
|
|
395
|
+
{
|
|
396
|
+
href: src,
|
|
397
|
+
target: "_blank",
|
|
398
|
+
rel: "noopener noreferrer",
|
|
399
|
+
className: cn(
|
|
400
|
+
INLINE_IMAGE_FRAME,
|
|
401
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
402
|
+
),
|
|
403
|
+
children: [
|
|
404
|
+
picture,
|
|
405
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sr-only", children: [
|
|
406
|
+
"(",
|
|
407
|
+
T.opensInNewTab,
|
|
408
|
+
")"
|
|
409
|
+
] })
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
) });
|
|
413
|
+
}
|
|
414
|
+
function markdownText(node) {
|
|
415
|
+
if (!node) return "";
|
|
416
|
+
if (typeof node.value === "string") return node.value;
|
|
417
|
+
return (node.children ?? []).map(markdownText).join("");
|
|
418
|
+
}
|
|
419
|
+
function hasHeaderCells(node) {
|
|
420
|
+
return (node?.children ?? []).some((cell) => cell.tagName === "th");
|
|
421
|
+
}
|
|
422
|
+
function findHeaderRow(node) {
|
|
423
|
+
for (const child of node?.children ?? []) {
|
|
424
|
+
if (child.tagName === "tr" && hasHeaderCells(child)) return child;
|
|
425
|
+
const nested = findHeaderRow(child);
|
|
426
|
+
if (nested !== null) return nested;
|
|
427
|
+
}
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
function tableColumnLabels(node) {
|
|
431
|
+
const row = findHeaderRow(node);
|
|
432
|
+
if (row === null) return [];
|
|
433
|
+
return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
|
|
434
|
+
}
|
|
435
|
+
var TableColumnsContext = React.createContext([]);
|
|
436
|
+
function AnswerTable({ node, children }) {
|
|
437
|
+
const scope = React.useContext(AnswerScopeContext);
|
|
438
|
+
const table = /* @__PURE__ */ jsxRuntime.jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsxRuntime.jsx("table", { children }) });
|
|
439
|
+
if (!scope) return table;
|
|
440
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
441
|
+
"div",
|
|
442
|
+
{
|
|
443
|
+
"data-boff-explore": "table",
|
|
444
|
+
role: "region",
|
|
445
|
+
"aria-label": scope.strings.answerTable,
|
|
446
|
+
tabIndex: 0,
|
|
447
|
+
className: "boff-explore-table",
|
|
448
|
+
children: table
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
function AnswerTableRow({
|
|
453
|
+
node,
|
|
454
|
+
children,
|
|
455
|
+
...props
|
|
456
|
+
}) {
|
|
457
|
+
const labels = React.useContext(TableColumnsContext);
|
|
458
|
+
if (labels.length === 0 || hasHeaderCells(node)) {
|
|
459
|
+
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children });
|
|
460
|
+
}
|
|
461
|
+
let column = 0;
|
|
462
|
+
const cells = React.Children.map(children, (cell) => {
|
|
463
|
+
if (!React.isValidElement(cell)) return cell;
|
|
464
|
+
const label = labels[column] ?? "";
|
|
465
|
+
column += 1;
|
|
466
|
+
if (label === "") return cell;
|
|
467
|
+
return React.cloneElement(cell, {
|
|
468
|
+
"data-label": label
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children: cells });
|
|
472
|
+
}
|
|
473
|
+
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
474
|
+
a: AnswerLink,
|
|
475
|
+
img: InlineConceptImage,
|
|
476
|
+
table: AnswerTable,
|
|
477
|
+
tr: AnswerTableRow
|
|
478
|
+
};
|
|
479
|
+
function ExploreAnswerBody({
|
|
480
|
+
children,
|
|
481
|
+
strings,
|
|
482
|
+
imageOrigins,
|
|
483
|
+
enabled = true,
|
|
484
|
+
components,
|
|
485
|
+
className
|
|
486
|
+
}) {
|
|
487
|
+
const [failed, setFailed] = React.useState(() => /* @__PURE__ */ new Set());
|
|
488
|
+
const onFailed = React.useCallback((src) => {
|
|
489
|
+
setFailed(
|
|
490
|
+
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
491
|
+
);
|
|
492
|
+
}, []);
|
|
493
|
+
const scope = React.useMemo(
|
|
494
|
+
() => ({
|
|
495
|
+
strings,
|
|
496
|
+
enabled,
|
|
497
|
+
origins: imageOrigins,
|
|
498
|
+
failed,
|
|
499
|
+
onFailed
|
|
500
|
+
}),
|
|
501
|
+
[strings, enabled, imageOrigins, failed, onFailed]
|
|
502
|
+
);
|
|
503
|
+
const overrides = React.useMemo(
|
|
504
|
+
() => components ? { ...ANSWER_MARKDOWN_COMPONENTS, ...components } : ANSWER_MARKDOWN_COMPONENTS,
|
|
505
|
+
[components]
|
|
506
|
+
);
|
|
507
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("boff-prose boff-explore-body min-w-0", className), children: /* @__PURE__ */ jsxRuntime.jsx(AnswerScopeContext.Provider, { value: scope, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { components: overrides, children }) }) });
|
|
508
|
+
}
|
|
43
509
|
|
|
44
510
|
// src/components/explore-i18n.ts
|
|
45
511
|
var EXPLORE_LOCALES = ["en", "zh", "hi", "es", "ar", "ta"];
|
|
@@ -58,7 +524,7 @@ var EN = {
|
|
|
58
524
|
showLess: "Show less",
|
|
59
525
|
sources: "Sources",
|
|
60
526
|
nextYouCouldAsk: "Next you could ask",
|
|
61
|
-
placeholder: "Describe the problem
|
|
527
|
+
placeholder: "Describe the problem\u2026",
|
|
62
528
|
placeholderProduct: "Describe a problem, or ask about {product}\u2026",
|
|
63
529
|
send: "Send",
|
|
64
530
|
stop: "Stop",
|
|
@@ -99,6 +565,20 @@ var EN = {
|
|
|
99
565
|
feedbackDismiss: "Dismiss",
|
|
100
566
|
feedbackThanks: "Thanks \u2014 that helps.",
|
|
101
567
|
feedbackNotSent: "That didn't send.",
|
|
568
|
+
escalateOpen: "Talk to a person",
|
|
569
|
+
escalateTitle: "Send this conversation to a person",
|
|
570
|
+
escalateBody: "We'll pass the whole conversation on, so you don't have to type any of it again.",
|
|
571
|
+
escalateEmailLabel: "Your email address",
|
|
572
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
573
|
+
escalateNoteLabel: "Anything else a person should know? (optional)",
|
|
574
|
+
escalateSubmit: "Send to a person",
|
|
575
|
+
escalateSending: "Sending\u2026",
|
|
576
|
+
escalateCancel: "Cancel",
|
|
577
|
+
escalateInvalidEmail: "That doesn't look like an address we can reply to. Please check it.",
|
|
578
|
+
escalateSent: "A person now has this conversation. We'll reply to {email}.",
|
|
579
|
+
escalateRateLimited: "That's a lot of requests in a short time. Please try again in a few minutes.",
|
|
580
|
+
escalateCaptcha: "We couldn't verify that you're human. Please try that again.",
|
|
581
|
+
escalateFailed: "We couldn't pass that on just now. Please try again.",
|
|
102
582
|
prevSuggestion: "Previous suggestion",
|
|
103
583
|
nextSuggestion: "Next suggestion",
|
|
104
584
|
conceptIllustration: "Concept illustration",
|
|
@@ -109,6 +589,9 @@ var EN = {
|
|
|
109
589
|
conceptAskQuestion: "How does {title} work in {product}?",
|
|
110
590
|
copyAnswer: "Copy answer",
|
|
111
591
|
copyFailed: "Couldn't copy",
|
|
592
|
+
readAloud: "Read aloud",
|
|
593
|
+
stopReading: "Stop reading",
|
|
594
|
+
answerTable: "Table",
|
|
112
595
|
groundedIn: "What this answer is based on",
|
|
113
596
|
sourceCountOne: "1 source",
|
|
114
597
|
sourceCountMany: "{count} sources",
|
|
@@ -143,7 +626,7 @@ var ZH = {
|
|
|
143
626
|
showLess: "\u6536\u8D77",
|
|
144
627
|
sources: "\u6765\u6E90",
|
|
145
628
|
nextYouCouldAsk: "\u4F60\u8FD8\u53EF\u4EE5\u95EE",
|
|
146
|
-
placeholder: "\u63CF\u8FF0\u4F60\
|
|
629
|
+
placeholder: "\u63CF\u8FF0\u4F60\u7684\u95EE\u9898\u2026",
|
|
147
630
|
placeholderProduct: "\u63CF\u8FF0\u95EE\u9898\uFF0C\u6216\u8BE2\u95EE {product}\u2026",
|
|
148
631
|
send: "\u53D1\u9001",
|
|
149
632
|
stop: "\u505C\u6B62",
|
|
@@ -181,6 +664,20 @@ var ZH = {
|
|
|
181
664
|
feedbackDismiss: "\u5173\u95ED",
|
|
182
665
|
feedbackThanks: "\u8C22\u8C22\uFF0C\u8FD9\u5BF9\u6211\u4EEC\u5F88\u6709\u5E2E\u52A9\u3002",
|
|
183
666
|
feedbackNotSent: "\u6CA1\u6709\u63D0\u4EA4\u6210\u529F\u3002",
|
|
667
|
+
escalateOpen: "\u8054\u7CFB\u771F\u4EBA",
|
|
668
|
+
escalateTitle: "\u628A\u8FD9\u6BB5\u5BF9\u8BDD\u8F6C\u7ED9\u771F\u4EBA",
|
|
669
|
+
escalateBody: "\u6211\u4EEC\u4F1A\u628A\u6574\u6BB5\u5BF9\u8BDD\u4E00\u8D77\u8F6C\u8FC7\u53BB\uFF0C\u60A8\u4E0D\u7528\u518D\u91CD\u65B0\u6253\u4E00\u904D\u3002",
|
|
670
|
+
escalateEmailLabel: "\u60A8\u7684\u90AE\u7BB1\u5730\u5740",
|
|
671
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
672
|
+
escalateNoteLabel: "\u8FD8\u6709\u4EC0\u4E48\u9700\u8981\u544A\u8BC9\u6211\u4EEC\u7684\u5417\uFF1F\uFF08\u9009\u586B\uFF09",
|
|
673
|
+
escalateSubmit: "\u8F6C\u7ED9\u771F\u4EBA",
|
|
674
|
+
escalateSending: "\u6B63\u5728\u53D1\u9001\u2026",
|
|
675
|
+
escalateCancel: "\u53D6\u6D88",
|
|
676
|
+
escalateInvalidEmail: "\u8FD9\u4F3C\u4E4E\u4E0D\u662F\u4E00\u4E2A\u6211\u4EEC\u53EF\u4EE5\u56DE\u590D\u7684\u90AE\u7BB1\u5730\u5740\uFF0C\u8BF7\u68C0\u67E5\u4E00\u4E0B\u3002",
|
|
677
|
+
escalateSent: "\u5DF2\u6709\u540C\u4E8B\u63A5\u624B\u8FD9\u6BB5\u5BF9\u8BDD\uFF0C\u6211\u4EEC\u4F1A\u56DE\u590D\u5230 {email}\u3002",
|
|
678
|
+
escalateRateLimited: "\u77ED\u65F6\u95F4\u5185\u8BF7\u6C42\u8FC7\u591A\uFF0C\u8BF7\u51E0\u5206\u949F\u540E\u518D\u8BD5\u3002",
|
|
679
|
+
escalateCaptcha: "\u6211\u4EEC\u65E0\u6CD5\u9A8C\u8BC1\u60A8\u662F\u771F\u4EBA\uFF0C\u8BF7\u518D\u8BD5\u4E00\u6B21\u3002",
|
|
680
|
+
escalateFailed: "\u6682\u65F6\u65E0\u6CD5\u8F6C\u4EA4\uFF0C\u8BF7\u518D\u8BD5\u4E00\u6B21\u3002",
|
|
184
681
|
prevSuggestion: "\u4E0A\u4E00\u4E2A\u5EFA\u8BAE",
|
|
185
682
|
nextSuggestion: "\u4E0B\u4E00\u4E2A\u5EFA\u8BAE",
|
|
186
683
|
conceptIllustration: "\u6982\u5FF5\u793A\u610F\u56FE",
|
|
@@ -191,6 +688,9 @@ var ZH = {
|
|
|
191
688
|
conceptAskQuestion: "{product} \u7684 {title} \u662F\u600E\u4E48\u8FD0\u4F5C\u7684\uFF1F",
|
|
192
689
|
copyAnswer: "\u590D\u5236\u56DE\u7B54",
|
|
193
690
|
copyFailed: "\u590D\u5236\u5931\u8D25",
|
|
691
|
+
readAloud: "\u6717\u8BFB",
|
|
692
|
+
stopReading: "\u505C\u6B62\u6717\u8BFB",
|
|
693
|
+
answerTable: "\u8868\u683C",
|
|
194
694
|
groundedIn: "\u8FD9\u4E2A\u56DE\u7B54\u7684\u4F9D\u636E",
|
|
195
695
|
sourceCountOne: "1 \u4E2A\u6765\u6E90",
|
|
196
696
|
sourceCountMany: "{count} \u4E2A\u6765\u6E90",
|
|
@@ -221,7 +721,7 @@ var HI = {
|
|
|
221
721
|
showLess: "\u0915\u092E \u0926\u093F\u0916\u093E\u090F\u0901",
|
|
222
722
|
sources: "\u0938\u094D\u0930\u094B\u0924",
|
|
223
723
|
nextYouCouldAsk: "\u0906\u0917\u0947 \u0906\u092A \u092A\u0942\u091B \u0938\u0915\u0924\u0947 \u0939\u0948\u0902",
|
|
224
|
-
placeholder: "\u0905\u092A\u0928\u0940 \u0938\u092E\u0938\u094D\u092F\u093E \
|
|
724
|
+
placeholder: "\u0905\u092A\u0928\u0940 \u0938\u092E\u0938\u094D\u092F\u093E \u092C\u0924\u093E\u090F\u0902\u2026",
|
|
225
725
|
placeholderProduct: "\u0938\u092E\u0938\u094D\u092F\u093E \u092C\u0924\u093E\u090F\u0902, \u092F\u093E {product} \u0915\u0947 \u092C\u093E\u0930\u0947 \u092E\u0947\u0902 \u092A\u0942\u091B\u0947\u0902\u2026",
|
|
226
726
|
send: "\u092D\u0947\u091C\u0947\u0902",
|
|
227
727
|
stop: "\u0930\u094B\u0915\u0947\u0902",
|
|
@@ -259,6 +759,20 @@ var HI = {
|
|
|
259
759
|
feedbackDismiss: "\u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
|
|
260
760
|
feedbackThanks: "\u0927\u0928\u094D\u092F\u0935\u093E\u0926, \u0907\u0938\u0938\u0947 \u092E\u0926\u0926 \u092E\u093F\u0932\u0924\u0940 \u0939\u0948\u0964",
|
|
261
761
|
feedbackNotSent: "\u092F\u0939 \u092D\u0947\u091C\u093E \u0928\u0939\u0940\u0902 \u091C\u093E \u0938\u0915\u093E\u0964",
|
|
762
|
+
escalateOpen: "\u0915\u093F\u0938\u0940 \u0935\u094D\u092F\u0915\u094D\u0924\u093F \u0938\u0947 \u092C\u093E\u0924 \u0915\u0930\u0947\u0902",
|
|
763
|
+
escalateTitle: "\u092F\u0939 \u092C\u093E\u0924\u091A\u0940\u0924 \u0915\u093F\u0938\u0940 \u0935\u094D\u092F\u0915\u094D\u0924\u093F \u0915\u094B \u092D\u0947\u091C\u0947\u0902",
|
|
764
|
+
escalateBody: "\u0939\u092E \u092A\u0942\u0930\u0940 \u092C\u093E\u0924\u091A\u0940\u0924 \u0906\u0917\u0947 \u092D\u0947\u091C \u0926\u0947\u0902\u0917\u0947, \u0924\u093E\u0915\u093F \u0906\u092A\u0915\u094B \u0926\u094B\u092C\u093E\u0930\u093E \u0915\u0941\u091B \u0932\u093F\u0916\u0928\u093E \u0928 \u092A\u0921\u093C\u0947\u0964",
|
|
765
|
+
escalateEmailLabel: "\u0906\u092A\u0915\u093E \u0908\u092E\u0947\u0932 \u092A\u0924\u093E",
|
|
766
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
767
|
+
escalateNoteLabel: "\u0915\u094D\u092F\u093E \u0915\u0941\u091B \u0914\u0930 \u092C\u0924\u093E\u0928\u093E \u091A\u093E\u0939\u0947\u0902\u0917\u0947? (\u0935\u0948\u0915\u0932\u094D\u092A\u093F\u0915)",
|
|
768
|
+
escalateSubmit: "\u0915\u093F\u0938\u0940 \u0935\u094D\u092F\u0915\u094D\u0924\u093F \u0915\u094B \u092D\u0947\u091C\u0947\u0902",
|
|
769
|
+
escalateSending: "\u092D\u0947\u091C\u093E \u091C\u093E \u0930\u0939\u093E \u0939\u0948\u2026",
|
|
770
|
+
escalateCancel: "\u0930\u0926\u094D\u0926 \u0915\u0930\u0947\u0902",
|
|
771
|
+
escalateInvalidEmail: "\u092F\u0939 \u0910\u0938\u093E \u0908\u092E\u0947\u0932 \u092A\u0924\u093E \u0928\u0939\u0940\u0902 \u0932\u0917\u0924\u093E \u091C\u093F\u0938 \u092A\u0930 \u0939\u092E \u091C\u0935\u093E\u092C \u092D\u0947\u091C \u0938\u0915\u0947\u0902\u0964 \u0915\u0943\u092A\u092F\u093E \u091C\u093E\u0901\u091A \u0932\u0947\u0902\u0964",
|
|
772
|
+
escalateSent: "\u0905\u092C \u092F\u0939 \u092C\u093E\u0924\u091A\u0940\u0924 \u090F\u0915 \u0935\u094D\u092F\u0915\u094D\u0924\u093F \u0915\u0947 \u092A\u093E\u0938 \u0939\u0948\u0964 \u0939\u092E {email} \u092A\u0930 \u091C\u0935\u093E\u092C \u0926\u0947\u0902\u0917\u0947\u0964",
|
|
773
|
+
escalateRateLimited: "\u0925\u094B\u0921\u093C\u0947 \u0938\u092E\u092F \u092E\u0947\u0902 \u092C\u0939\u0941\u0924 \u0938\u093E\u0930\u0947 \u0905\u0928\u0941\u0930\u094B\u0927 \u0939\u094B \u0917\u090F\u0964 \u0915\u0943\u092A\u092F\u093E \u0915\u0941\u091B \u092E\u093F\u0928\u091F \u092C\u093E\u0926 \u0915\u094B\u0936\u093F\u0936 \u0915\u0930\u0947\u0902\u0964",
|
|
774
|
+
escalateCaptcha: "\u0939\u092E \u092F\u0939 \u0938\u0924\u094D\u092F\u093E\u092A\u093F\u0924 \u0928\u0939\u0940\u0902 \u0915\u0930 \u0938\u0915\u0947 \u0915\u093F \u0906\u092A \u090F\u0915 \u0935\u094D\u092F\u0915\u094D\u0924\u093F \u0939\u0948\u0902\u0964 \u0915\u0943\u092A\u092F\u093E \u092B\u093F\u0930 \u0915\u094B\u0936\u093F\u0936 \u0915\u0930\u0947\u0902\u0964",
|
|
775
|
+
escalateFailed: "\u0905\u092D\u0940 \u0907\u0938\u0947 \u0906\u0917\u0947 \u0928\u0939\u0940\u0902 \u092D\u0947\u091C\u093E \u091C\u093E \u0938\u0915\u093E\u0964 \u0915\u0943\u092A\u092F\u093E \u092B\u093F\u0930 \u0915\u094B\u0936\u093F\u0936 \u0915\u0930\u0947\u0902\u0964",
|
|
262
776
|
prevSuggestion: "\u092A\u093F\u091B\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
|
|
263
777
|
nextSuggestion: "\u0905\u0917\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
|
|
264
778
|
conceptIllustration: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E \u091A\u093F\u0924\u094D\u0930\u0923",
|
|
@@ -269,6 +783,9 @@ var HI = {
|
|
|
269
783
|
conceptAskQuestion: "{product} \u092E\u0947\u0902 {title} \u0915\u0948\u0938\u0947 \u0915\u093E\u092E \u0915\u0930\u0924\u093E \u0939\u0948?",
|
|
270
784
|
copyAnswer: "\u0909\u0924\u094D\u0924\u0930 \u0915\u0949\u092A\u0940 \u0915\u0930\u0947\u0902",
|
|
271
785
|
copyFailed: "\u0915\u0949\u092A\u0940 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u093E",
|
|
786
|
+
readAloud: "\u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0947\u0902",
|
|
787
|
+
stopReading: "\u092A\u0922\u093C\u0928\u093E \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
|
|
788
|
+
answerTable: "\u0924\u093E\u0932\u093F\u0915\u093E",
|
|
272
789
|
groundedIn: "\u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u0915\u093F\u0938 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0939\u0948",
|
|
273
790
|
sourceCountOne: "1 \u0938\u094D\u0930\u094B\u0924",
|
|
274
791
|
sourceCountMany: "{count} \u0938\u094D\u0930\u094B\u0924",
|
|
@@ -303,7 +820,7 @@ var ES = {
|
|
|
303
820
|
showLess: "Ver menos",
|
|
304
821
|
sources: "Fuentes",
|
|
305
822
|
nextYouCouldAsk: "Tambi\xE9n puedes preguntar",
|
|
306
|
-
placeholder: "Describe el problema
|
|
823
|
+
placeholder: "Describe el problema\u2026",
|
|
307
824
|
placeholderProduct: "Describe un problema o pregunta por {product}\u2026",
|
|
308
825
|
send: "Enviar",
|
|
309
826
|
stop: "Detener",
|
|
@@ -341,6 +858,20 @@ var ES = {
|
|
|
341
858
|
feedbackDismiss: "Cerrar",
|
|
342
859
|
feedbackThanks: "Gracias, nos sirve.",
|
|
343
860
|
feedbackNotSent: "No se ha podido enviar.",
|
|
861
|
+
escalateOpen: "Hablar con una persona",
|
|
862
|
+
escalateTitle: "Env\xEDa esta conversaci\xF3n a una persona",
|
|
863
|
+
escalateBody: "Le pasaremos la conversaci\xF3n entera, as\xED no tienes que volver a escribirla.",
|
|
864
|
+
escalateEmailLabel: "Tu direcci\xF3n de correo",
|
|
865
|
+
escalateEmailPlaceholder: "tu@ejemplo.com",
|
|
866
|
+
escalateNoteLabel: "\xBFAlgo m\xE1s que deber\xEDamos saber? (opcional)",
|
|
867
|
+
escalateSubmit: "Enviar a una persona",
|
|
868
|
+
escalateSending: "Enviando\u2026",
|
|
869
|
+
escalateCancel: "Cancelar",
|
|
870
|
+
escalateInvalidEmail: "No parece una direcci\xF3n a la que podamos responder. Rev\xEDsala, por favor.",
|
|
871
|
+
escalateSent: "Una persona ya tiene esta conversaci\xF3n. Te responderemos a {email}.",
|
|
872
|
+
escalateRateLimited: "Han sido muchas peticiones en poco tiempo. Int\xE9ntalo de nuevo en unos minutos.",
|
|
873
|
+
escalateCaptcha: "No hemos podido verificar que eres una persona. Int\xE9ntalo de nuevo.",
|
|
874
|
+
escalateFailed: "No hemos podido pasarlo ahora mismo. Int\xE9ntalo de nuevo.",
|
|
344
875
|
prevSuggestion: "Sugerencia anterior",
|
|
345
876
|
nextSuggestion: "Sugerencia siguiente",
|
|
346
877
|
conceptIllustration: "Ilustraci\xF3n del concepto",
|
|
@@ -351,6 +882,9 @@ var ES = {
|
|
|
351
882
|
conceptAskQuestion: "\xBFC\xF3mo funciona {title} en {product}?",
|
|
352
883
|
copyAnswer: "Copiar respuesta",
|
|
353
884
|
copyFailed: "No se pudo copiar",
|
|
885
|
+
readAloud: "Leer en voz alta",
|
|
886
|
+
stopReading: "Dejar de leer",
|
|
887
|
+
answerTable: "Tabla",
|
|
354
888
|
groundedIn: "En qu\xE9 se basa esta respuesta",
|
|
355
889
|
sourceCountOne: "1 fuente",
|
|
356
890
|
sourceCountMany: "{count} fuentes",
|
|
@@ -385,7 +919,7 @@ var AR = {
|
|
|
385
919
|
showLess: "\u0639\u0631\u0636 \u0623\u0642\u0644",
|
|
386
920
|
sources: "\u0627\u0644\u0645\u0635\u0627\u062F\u0631",
|
|
387
921
|
nextYouCouldAsk: "\u064A\u0645\u0643\u0646\u0643 \u0623\u0646 \u062A\u0633\u0623\u0644 \u0628\u0639\u062F \u0630\u0644\u0643",
|
|
388
|
-
placeholder: "\u0635\u0641 \u0627\u0644\u0645\u0634\u0643\u0644\u0629
|
|
922
|
+
placeholder: "\u0635\u0641 \u0627\u0644\u0645\u0634\u0643\u0644\u0629\u2026",
|
|
389
923
|
placeholderProduct: "\u0635\u0641 \u0645\u0634\u0643\u0644\u062A\u0643 \u0623\u0648 \u0627\u0633\u0623\u0644 \u0639\u0646 {product}\u2026",
|
|
390
924
|
send: "\u0625\u0631\u0633\u0627\u0644",
|
|
391
925
|
stop: "\u0625\u064A\u0642\u0627\u0641",
|
|
@@ -423,6 +957,20 @@ var AR = {
|
|
|
423
957
|
feedbackDismiss: "\u0625\u063A\u0644\u0627\u0642",
|
|
424
958
|
feedbackThanks: "\u0634\u0643\u0631\u064B\u0627\u060C \u0647\u0630\u0627 \u064A\u0633\u0627\u0639\u062F\u0646\u0627.",
|
|
425
959
|
feedbackNotSent: "\u0644\u0645 \u064A\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644.",
|
|
960
|
+
escalateOpen: "\u062A\u0648\u0627\u0635\u0644 \u0645\u0639 \u0634\u062E\u0635",
|
|
961
|
+
escalateTitle: "\u0623\u0631\u0633\u0644 \u0647\u0630\u0647 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629 \u0625\u0644\u0649 \u0634\u062E\u0635",
|
|
962
|
+
escalateBody: "\u0633\u0646\u0645\u0631\u0651\u0631 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629 \u0643\u0627\u0645\u0644\u0629\u060C \u062D\u062A\u0649 \u0644\u0627 \u062A\u0636\u0637\u0631 \u0625\u0644\u0649 \u0643\u062A\u0627\u0628\u062A\u0647\u0627 \u0645\u0646 \u062C\u062F\u064A\u062F.",
|
|
963
|
+
escalateEmailLabel: "\u0628\u0631\u064A\u062F\u0643 \u0627\u0644\u0625\u0644\u0643\u062A\u0631\u0648\u0646\u064A",
|
|
964
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
965
|
+
escalateNoteLabel: "\u0647\u0644 \u0645\u0646 \u0634\u064A\u0621 \u0622\u062E\u0631 \u064A\u0646\u0628\u063A\u064A \u0623\u0646 \u0646\u0639\u0631\u0641\u0647\u061F (\u0627\u062E\u062A\u064A\u0627\u0631\u064A)",
|
|
966
|
+
escalateSubmit: "\u0625\u0631\u0633\u0627\u0644 \u0625\u0644\u0649 \u0634\u062E\u0635",
|
|
967
|
+
escalateSending: "\u062C\u0627\u0631\u064D \u0627\u0644\u0625\u0631\u0633\u0627\u0644\u2026",
|
|
968
|
+
escalateCancel: "\u0625\u0644\u063A\u0627\u0621",
|
|
969
|
+
escalateInvalidEmail: "\u0644\u0627 \u064A\u0628\u062F\u0648 \u0647\u0630\u0627 \u0639\u0646\u0648\u0627\u0646\u064B\u0627 \u064A\u0645\u0643\u0646\u0646\u0627 \u0627\u0644\u0631\u062F \u0639\u0644\u064A\u0647. \u064A\u0631\u062C\u0649 \u0627\u0644\u062A\u062D\u0642\u0642 \u0645\u0646\u0647.",
|
|
970
|
+
escalateSent: "\u0623\u0635\u0628\u062D\u062A \u0647\u0630\u0647 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629 \u0644\u062F\u0649 \u0623\u062D\u062F \u0627\u0644\u0632\u0645\u0644\u0627\u0621. \u0633\u0646\u0631\u062F \u0639\u0644\u0649 {email}.",
|
|
971
|
+
escalateRateLimited: "\u0637\u0644\u0628\u0627\u062A \u0643\u062B\u064A\u0631\u0629 \u062E\u0644\u0627\u0644 \u0648\u0642\u062A \u0642\u0635\u064A\u0631. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0628\u0639\u062F \u0628\u0636\u0639 \u062F\u0642\u0627\u0626\u0642.",
|
|
972
|
+
escalateCaptcha: "\u062A\u0639\u0630\u0651\u0631 \u0627\u0644\u062A\u062D\u0642\u0642 \u0645\u0646 \u0623\u0646\u0643 \u0644\u0633\u062A \u0631\u0648\u0628\u0648\u062A\u064B\u0627. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.",
|
|
973
|
+
escalateFailed: "\u062A\u0639\u0630\u0651\u0631 \u062A\u0645\u0631\u064A\u0631 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629 \u0627\u0644\u0622\u0646. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.",
|
|
426
974
|
prevSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u0633\u0627\u0628\u0642",
|
|
427
975
|
nextSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u062A\u0627\u0644\u064A",
|
|
428
976
|
conceptIllustration: "\u0631\u0633\u0645 \u062A\u0648\u0636\u064A\u062D\u064A \u0644\u0644\u0645\u0641\u0647\u0648\u0645",
|
|
@@ -433,6 +981,9 @@ var AR = {
|
|
|
433
981
|
conceptAskQuestion: "\u0643\u064A\u0641 \u064A\u0639\u0645\u0644 {title} \u0641\u064A {product}\u061F",
|
|
434
982
|
copyAnswer: "\u0646\u0633\u062E \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
|
|
435
983
|
copyFailed: "\u062A\u0639\u0630\u0651\u0631 \u0627\u0644\u0646\u0633\u062E",
|
|
984
|
+
readAloud: "\u0627\u0642\u0631\u0623 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D",
|
|
985
|
+
stopReading: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0642\u0631\u0627\u0621\u0629",
|
|
986
|
+
answerTable: "\u062C\u062F\u0648\u0644",
|
|
436
987
|
groundedIn: "\u0639\u0644\u0649 \u0645\u0627\u0630\u0627 \u062A\u0633\u062A\u0646\u062F \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
|
|
437
988
|
sourceCountOne: "\u0645\u0635\u062F\u0631 \u0648\u0627\u062D\u062F",
|
|
438
989
|
sourceCountMany: "{count} \u0645\u0635\u0627\u062F\u0631",
|
|
@@ -467,7 +1018,7 @@ var TA = {
|
|
|
467
1018
|
showLess: "\u0B95\u0BC1\u0BB1\u0BC8\u0BB5\u0BBE\u0B95\u0B95\u0BCD \u0B95\u0BBE\u0B9F\u0BCD\u0B9F\u0BC1",
|
|
468
1019
|
sources: "\u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
469
1020
|
nextYouCouldAsk: "\u0B85\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1 \u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B95\u0BC7\u0B9F\u0BCD\u0B95\u0BB2\u0BBE\u0BAE\u0BCD",
|
|
470
|
-
placeholder: "\
|
|
1021
|
+
placeholder: "\u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD\u2026",
|
|
471
1022
|
placeholderProduct: "\u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD, \u0B85\u0BB2\u0BCD\u0BB2\u0BA4\u0BC1 {product} \u0BAA\u0BB1\u0BCD\u0BB1\u0BBF\u0B95\u0BCD \u0B95\u0BC7\u0BB3\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD\u2026",
|
|
472
1023
|
send: "\u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA\u0BC1",
|
|
473
1024
|
stop: "\u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
|
|
@@ -505,6 +1056,20 @@ var TA = {
|
|
|
505
1056
|
feedbackDismiss: "\u0BAE\u0BC2\u0B9F\u0BC1",
|
|
506
1057
|
feedbackThanks: "\u0BA8\u0BA9\u0BCD\u0BB1\u0BBF, \u0B87\u0BA4\u0BC1 \u0B8E\u0B99\u0BCD\u0B95\u0BB3\u0BC1\u0B95\u0BCD\u0B95\u0BC1 \u0B89\u0BA4\u0BB5\u0BC1\u0B95\u0BBF\u0BB1\u0BA4\u0BC1.",
|
|
507
1058
|
feedbackNotSent: "\u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8.",
|
|
1059
|
+
escalateOpen: "\u0B92\u0BB0\u0BC1\u0BB5\u0BB0\u0BBF\u0B9F\u0BAE\u0BCD \u0BAA\u0BC7\u0B9A\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
1060
|
+
escalateTitle: "\u0B87\u0BA8\u0BCD\u0BA4 \u0B89\u0BB0\u0BC8\u0BAF\u0BBE\u0B9F\u0BB2\u0BC8 \u0B92\u0BB0\u0BC1\u0BB5\u0BB0\u0BC1\u0B95\u0BCD\u0B95\u0BC1 \u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
1061
|
+
escalateBody: "\u0BAE\u0BC1\u0BB4\u0BC1 \u0B89\u0BB0\u0BC8\u0BAF\u0BBE\u0B9F\u0BB2\u0BC8\u0BAF\u0BC1\u0BAE\u0BCD \u0BA8\u0BBE\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA\u0BBF\u0BB5\u0BBF\u0B9F\u0BC1\u0BB5\u0BCB\u0BAE\u0BCD; \u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BAE\u0BC0\u0BA3\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD \u0BA4\u0B9F\u0BCD\u0B9F\u0B9A\u0BCD\u0B9A\u0BC1 \u0B9A\u0BC6\u0BAF\u0BCD\u0BAF \u0BB5\u0BC7\u0BA3\u0BCD\u0B9F\u0BBF\u0BAF\u0BA4\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8.",
|
|
1062
|
+
escalateEmailLabel: "\u0B89\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BAE\u0BBF\u0BA9\u0BCD\u0BA9\u0B9E\u0BCD\u0B9A\u0BB2\u0BCD \u0BAE\u0BC1\u0B95\u0BB5\u0BB0\u0BBF",
|
|
1063
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
1064
|
+
escalateNoteLabel: "\u0BB5\u0BC7\u0BB1\u0BC1 \u0B8F\u0BA4\u0BC7\u0BA9\u0BC1\u0BAE\u0BCD \u0B9A\u0BCA\u0BB2\u0BCD\u0BB2 \u0BB5\u0BBF\u0BB0\u0BC1\u0BAE\u0BCD\u0BAA\u0BC1\u0B95\u0BBF\u0BB1\u0BC0\u0BB0\u0BCD\u0B95\u0BB3\u0BBE? (\u0BB5\u0BBF\u0BB0\u0BC1\u0BAA\u0BCD\u0BAA\u0BAE\u0BCD)",
|
|
1065
|
+
escalateSubmit: "\u0B92\u0BB0\u0BC1\u0BB5\u0BB0\u0BC1\u0B95\u0BCD\u0B95\u0BC1 \u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA\u0BC1",
|
|
1066
|
+
escalateSending: "\u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA\u0BAA\u0BCD\u0BAA\u0B9F\u0BC1\u0B95\u0BBF\u0BB1\u0BA4\u0BC1\u2026",
|
|
1067
|
+
escalateCancel: "\u0BB0\u0BA4\u0BCD\u0BA4\u0BC1 \u0B9A\u0BC6\u0BAF\u0BCD",
|
|
1068
|
+
escalateInvalidEmail: "\u0B87\u0BA4\u0BC1 \u0BA8\u0BBE\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BB3\u0BBF\u0B95\u0BCD\u0B95\u0B95\u0BCD\u0B95\u0BC2\u0B9F\u0BBF\u0BAF \u0BAE\u0BBF\u0BA9\u0BCD\u0BA9\u0B9E\u0BCD\u0B9A\u0BB2\u0BCD \u0BAE\u0BC1\u0B95\u0BB5\u0BB0\u0BBF \u0BAA\u0BCB\u0BB2\u0BCD \u0BA4\u0BC6\u0BB0\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8. \u0B9A\u0BB0\u0BBF\u0BAA\u0BBE\u0BB0\u0BCD\u0B95\u0BCD\u0B95\u0BB5\u0BC1\u0BAE\u0BCD.",
|
|
1069
|
+
escalateSent: "\u0B87\u0BA8\u0BCD\u0BA4 \u0B89\u0BB0\u0BC8\u0BAF\u0BBE\u0B9F\u0BB2\u0BCD \u0B87\u0BAA\u0BCD\u0BAA\u0BCB\u0BA4\u0BC1 \u0B92\u0BB0\u0BC1\u0BB5\u0BB0\u0BBF\u0B9F\u0BAE\u0BCD \u0B89\u0BB3\u0BCD\u0BB3\u0BA4\u0BC1. {email} \u0BAE\u0BC1\u0B95\u0BB5\u0BB0\u0BBF\u0B95\u0BCD\u0B95\u0BC1\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BB3\u0BBF\u0BAA\u0BCD\u0BAA\u0BCB\u0BAE\u0BCD.",
|
|
1070
|
+
escalateRateLimited: "\u0B95\u0BC1\u0BB1\u0BC1\u0B95\u0BBF\u0BAF \u0BA8\u0BC7\u0BB0\u0BA4\u0BCD\u0BA4\u0BBF\u0BB2\u0BCD \u0B85\u0BA4\u0BBF\u0B95 \u0B95\u0BCB\u0BB0\u0BBF\u0B95\u0BCD\u0B95\u0BC8\u0B95\u0BB3\u0BCD. \u0B9A\u0BBF\u0BB2 \u0BA8\u0BBF\u0BAE\u0BBF\u0B9F\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B95\u0BB4\u0BBF\u0BA4\u0BCD\u0BA4\u0BC1 \u0BAE\u0BC1\u0BAF\u0BB1\u0BCD\u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB5\u0BC1\u0BAE\u0BCD.",
|
|
1071
|
+
escalateCaptcha: "\u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BAE\u0BA9\u0BBF\u0BA4\u0BB0\u0BCD \u0B8E\u0BA9\u0BCD\u0BAA\u0BA4\u0BC8 \u0B89\u0BB1\u0BC1\u0BA4\u0BBF\u0B9A\u0BC6\u0BAF\u0BCD\u0BAF \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8. \u0BAE\u0BC0\u0BA3\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD \u0BAE\u0BC1\u0BAF\u0BB1\u0BCD\u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB5\u0BC1\u0BAE\u0BCD.",
|
|
1072
|
+
escalateFailed: "\u0B87\u0BAA\u0BCD\u0BAA\u0BCB\u0BA4\u0BC1 \u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8. \u0BAE\u0BC0\u0BA3\u0BCD\u0B9F\u0BC1\u0BAE\u0BCD \u0BAE\u0BC1\u0BAF\u0BB1\u0BCD\u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB5\u0BC1\u0BAE\u0BCD.",
|
|
508
1073
|
prevSuggestion: "\u0BAE\u0BC1\u0BA8\u0BCD\u0BA4\u0BC8\u0BAF \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
|
|
509
1074
|
nextSuggestion: "\u0B85\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4 \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
|
|
510
1075
|
conceptIllustration: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1 \u0BB5\u0BBF\u0BB3\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BAE\u0BCD",
|
|
@@ -515,6 +1080,9 @@ var TA = {
|
|
|
515
1080
|
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?",
|
|
516
1081
|
copyAnswer: "\u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1",
|
|
517
1082
|
copyFailed: "\u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8",
|
|
1083
|
+
readAloud: "\u0B9A\u0BA4\u0BCD\u0BA4\u0BAE\u0BBE\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF",
|
|
1084
|
+
stopReading: "\u0BAA\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0BA4\u0BC8 \u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
|
|
1085
|
+
answerTable: "\u0B85\u0B9F\u0BCD\u0B9F\u0BB5\u0BA3\u0BC8",
|
|
518
1086
|
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",
|
|
519
1087
|
sourceCountOne: "1 \u0BAE\u0BC2\u0BB2\u0BAE\u0BCD",
|
|
520
1088
|
sourceCountMany: "{count} \u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
@@ -546,6 +1114,17 @@ var STRINGS = {
|
|
|
546
1114
|
ar: AR,
|
|
547
1115
|
ta: TA
|
|
548
1116
|
};
|
|
1117
|
+
var SPEECH_TAGS = {
|
|
1118
|
+
en: "en-US",
|
|
1119
|
+
zh: "zh-CN",
|
|
1120
|
+
hi: "hi-IN",
|
|
1121
|
+
es: "es-ES",
|
|
1122
|
+
ar: "ar-SA",
|
|
1123
|
+
ta: "ta-IN"
|
|
1124
|
+
};
|
|
1125
|
+
function speechLocaleOf(locale) {
|
|
1126
|
+
return SPEECH_TAGS[locale];
|
|
1127
|
+
}
|
|
549
1128
|
function resolveExploreLocale(explicit) {
|
|
550
1129
|
const candidates = [
|
|
551
1130
|
explicit,
|
|
@@ -733,6 +1312,29 @@ var SUBMIT_EXPLORE_FEEDBACK_MUTATION = (
|
|
|
733
1312
|
`
|
|
734
1313
|
);
|
|
735
1314
|
var EXPLORE_FEEDBACK_COMMENT_SOFT_MAX = 500;
|
|
1315
|
+
var ESCALATE_EXPLORE_CONVERSATION_MUTATION = (
|
|
1316
|
+
/* GraphQL */
|
|
1317
|
+
`
|
|
1318
|
+
mutation EscalateExploreConversation(
|
|
1319
|
+
$input: EscalateExploreConversationInput!
|
|
1320
|
+
) {
|
|
1321
|
+
escalateExploreConversation(input: $input) {
|
|
1322
|
+
created
|
|
1323
|
+
escalation {
|
|
1324
|
+
email
|
|
1325
|
+
createdAt
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
`
|
|
1330
|
+
);
|
|
1331
|
+
var EXPLORE_ESCALATION_NOTE_SOFT_MAX = 2e3;
|
|
1332
|
+
var DELIVERABLE_EMAIL = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,}$/;
|
|
1333
|
+
function looksLikeDeliverableEmail(value) {
|
|
1334
|
+
const trimmed = value.trim();
|
|
1335
|
+
if (trimmed.length === 0 || trimmed.length > 254) return false;
|
|
1336
|
+
return DELIVERABLE_EMAIL.test(trimmed);
|
|
1337
|
+
}
|
|
736
1338
|
var CREATE_EXPLORE_SHARE_LINK_MUTATION = (
|
|
737
1339
|
/* GraphQL */
|
|
738
1340
|
`
|
|
@@ -753,48 +1355,6 @@ var EXPLORE_SHARED_CONVERSATION_QUERY = (
|
|
|
753
1355
|
}
|
|
754
1356
|
`
|
|
755
1357
|
);
|
|
756
|
-
function MarkdownLink({
|
|
757
|
-
href,
|
|
758
|
-
children,
|
|
759
|
-
// react-markdown passes the hast node to every override; it is not a DOM attribute.
|
|
760
|
-
node: _node,
|
|
761
|
-
...props
|
|
762
|
-
}) {
|
|
763
|
-
const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
|
|
764
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
765
|
-
"a",
|
|
766
|
-
{
|
|
767
|
-
href,
|
|
768
|
-
...props,
|
|
769
|
-
...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
|
|
770
|
-
children
|
|
771
|
-
}
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
var defaultComponents = {
|
|
775
|
-
a: MarkdownLink
|
|
776
|
-
};
|
|
777
|
-
function Markdown({
|
|
778
|
-
children,
|
|
779
|
-
className,
|
|
780
|
-
inline,
|
|
781
|
-
components
|
|
782
|
-
}) {
|
|
783
|
-
const content = /* @__PURE__ */ jsxRuntime.jsx(
|
|
784
|
-
ReactMarkdown__default.default,
|
|
785
|
-
{
|
|
786
|
-
remarkPlugins: [remarkGfm__default.default],
|
|
787
|
-
rehypePlugins: [rehypeSanitize__default.default],
|
|
788
|
-
components: { ...defaultComponents, ...components },
|
|
789
|
-
...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
|
|
790
|
-
children
|
|
791
|
-
}
|
|
792
|
-
);
|
|
793
|
-
if (inline) {
|
|
794
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: content });
|
|
795
|
-
}
|
|
796
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: content });
|
|
797
|
-
}
|
|
798
1358
|
var PROSE_CSS = `
|
|
799
1359
|
.boff-prose {
|
|
800
1360
|
max-width: none;
|
|
@@ -1191,6 +1751,10 @@ function useWebSDK() {
|
|
|
1191
1751
|
}
|
|
1192
1752
|
return client;
|
|
1193
1753
|
}
|
|
1754
|
+
function useWebSDKConfig() {
|
|
1755
|
+
const client = useWebSDK();
|
|
1756
|
+
return client.getConfig();
|
|
1757
|
+
}
|
|
1194
1758
|
|
|
1195
1759
|
// src/data/products.ts
|
|
1196
1760
|
var ECOSYSTEM_PRODUCTS = [
|
|
@@ -1537,6 +2101,25 @@ var VALIDATION_CODES = /* @__PURE__ */ new Set([
|
|
|
1537
2101
|
"CONVERSATION_LIMIT",
|
|
1538
2102
|
"INVALID_INPUT"
|
|
1539
2103
|
]);
|
|
2104
|
+
function escalateOutcomeForCode(code) {
|
|
2105
|
+
switch (code) {
|
|
2106
|
+
case "INVALID_EMAIL":
|
|
2107
|
+
return "invalid-email";
|
|
2108
|
+
case "RATE_LIMITED":
|
|
2109
|
+
case "HTTP_429":
|
|
2110
|
+
return "rate-limited";
|
|
2111
|
+
case "CAPTCHA_REQUIRED":
|
|
2112
|
+
case "CAPTCHA_FAILED":
|
|
2113
|
+
return "captcha";
|
|
2114
|
+
// The token owns nothing, or owns a different conversation. The server answers NOT_FOUND
|
|
2115
|
+
// rather than FORBIDDEN so the endpoint is not an oracle for whether a thread exists;
|
|
2116
|
+
// either way there is nothing this visitor can do about it, so it reads as a failure.
|
|
2117
|
+
case "NOT_FOUND":
|
|
2118
|
+
return "failed";
|
|
2119
|
+
default:
|
|
2120
|
+
return "failed";
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
1540
2123
|
function classifyExploreError(code, serverMessage) {
|
|
1541
2124
|
const c = code ?? "UNKNOWN";
|
|
1542
2125
|
if (RATE_LIMIT_CODES.has(c)) {
|
|
@@ -1703,6 +2286,7 @@ function useExploreChat(options = {}) {
|
|
|
1703
2286
|
onSend
|
|
1704
2287
|
} = options;
|
|
1705
2288
|
const client = useWebSDK();
|
|
2289
|
+
const { productId } = useWebSDKConfig();
|
|
1706
2290
|
const promptKey = JSON.stringify(examplePrompts ?? []);
|
|
1707
2291
|
const welcomeKey = JSON.stringify(fallbackWelcome ?? null);
|
|
1708
2292
|
const fallbackCatalog = React.useMemo(
|
|
@@ -1726,6 +2310,7 @@ function useExploreChat(options = {}) {
|
|
|
1726
2310
|
const pollGenerationRef = React.useRef(0);
|
|
1727
2311
|
const pollTimerRef = React.useRef(null);
|
|
1728
2312
|
const conversationTokenRef = React.useRef(null);
|
|
2313
|
+
const [hasConversation, setHasConversation] = React.useState(false);
|
|
1729
2314
|
const shareTokenRef = React.useRef(void 0);
|
|
1730
2315
|
if (shareTokenRef.current === void 0) {
|
|
1731
2316
|
shareTokenRef.current = readShareToken();
|
|
@@ -1740,6 +2325,10 @@ function useExploreChat(options = {}) {
|
|
|
1740
2325
|
);
|
|
1741
2326
|
const inFlightRef = React.useRef(false);
|
|
1742
2327
|
catalogRef.current = catalog;
|
|
2328
|
+
const rememberConversationToken = React.useCallback((token) => {
|
|
2329
|
+
conversationTokenRef.current = token;
|
|
2330
|
+
setHasConversation(token !== null);
|
|
2331
|
+
}, []);
|
|
1743
2332
|
const clearPollTimer = React.useCallback(() => {
|
|
1744
2333
|
if (pollTimerRef.current !== null) {
|
|
1745
2334
|
clearTimeout(pollTimerRef.current);
|
|
@@ -1806,7 +2395,7 @@ function useExploreChat(options = {}) {
|
|
|
1806
2395
|
setArchive(readArchive(storageKey));
|
|
1807
2396
|
const stored = readPersisted(storageKey);
|
|
1808
2397
|
if (stored) {
|
|
1809
|
-
|
|
2398
|
+
rememberConversationToken(stored.conversationToken);
|
|
1810
2399
|
focusProductRef.current = stored.focusProduct;
|
|
1811
2400
|
const settled = stored.messages.flatMap((m) => {
|
|
1812
2401
|
if (m.role !== "ASSISTANT") return [m];
|
|
@@ -1822,7 +2411,7 @@ function useExploreChat(options = {}) {
|
|
|
1822
2411
|
setTurnCount(turns);
|
|
1823
2412
|
}
|
|
1824
2413
|
setHydrated(true);
|
|
1825
|
-
}, [persist, storageKey]);
|
|
2414
|
+
}, [persist, rememberConversationToken, storageKey]);
|
|
1826
2415
|
React.useEffect(() => {
|
|
1827
2416
|
if (!persist || !hydrated || typeof window === "undefined") return;
|
|
1828
2417
|
try {
|
|
@@ -2015,7 +2604,7 @@ function useExploreChat(options = {}) {
|
|
|
2015
2604
|
setPhase(classified.kind === "rate-limit" ? "limited" : "error");
|
|
2016
2605
|
return;
|
|
2017
2606
|
}
|
|
2018
|
-
|
|
2607
|
+
rememberConversationToken(result.conversation.token);
|
|
2019
2608
|
turnCountRef.current = result.conversation.turnCount;
|
|
2020
2609
|
setTurnCount(result.conversation.turnCount);
|
|
2021
2610
|
setRemainingToday(result.remainingToday ?? null);
|
|
@@ -2041,7 +2630,14 @@ function useExploreChat(options = {}) {
|
|
|
2041
2630
|
setPhase("streaming");
|
|
2042
2631
|
startPolling(result.conversation.token, assistantMessage.id);
|
|
2043
2632
|
},
|
|
2044
|
-
[
|
|
2633
|
+
[
|
|
2634
|
+
client,
|
|
2635
|
+
locale,
|
|
2636
|
+
pageUrl,
|
|
2637
|
+
productSlug,
|
|
2638
|
+
rememberConversationToken,
|
|
2639
|
+
startPolling
|
|
2640
|
+
]
|
|
2045
2641
|
);
|
|
2046
2642
|
const send = React.useCallback(
|
|
2047
2643
|
async (message, attachments = []) => {
|
|
@@ -2158,7 +2754,7 @@ function useExploreChat(options = {}) {
|
|
|
2158
2754
|
);
|
|
2159
2755
|
cancelPolling();
|
|
2160
2756
|
inFlightRef.current = false;
|
|
2161
|
-
|
|
2757
|
+
rememberConversationToken(null);
|
|
2162
2758
|
lastSentTextRef.current = null;
|
|
2163
2759
|
lastSentAttachmentsRef.current = [];
|
|
2164
2760
|
turnCountRef.current = 0;
|
|
@@ -2173,7 +2769,7 @@ function useExploreChat(options = {}) {
|
|
|
2173
2769
|
} catch {
|
|
2174
2770
|
}
|
|
2175
2771
|
}
|
|
2176
|
-
}, [cancelPolling, storageKey]);
|
|
2772
|
+
}, [cancelPolling, rememberConversationToken, storageKey]);
|
|
2177
2773
|
const openConversation = React.useCallback(
|
|
2178
2774
|
(token) => {
|
|
2179
2775
|
const entry = readArchive(storageKey).find((e) => e.token === token);
|
|
@@ -2185,7 +2781,7 @@ function useExploreChat(options = {}) {
|
|
|
2185
2781
|
});
|
|
2186
2782
|
cancelPolling();
|
|
2187
2783
|
inFlightRef.current = false;
|
|
2188
|
-
|
|
2784
|
+
rememberConversationToken(entry.token);
|
|
2189
2785
|
turnCountRef.current = entry.messages.filter(
|
|
2190
2786
|
(m) => m.role === "USER"
|
|
2191
2787
|
).length;
|
|
@@ -2197,7 +2793,7 @@ function useExploreChat(options = {}) {
|
|
|
2197
2793
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
2198
2794
|
setArchive(readArchive(storageKey));
|
|
2199
2795
|
},
|
|
2200
|
-
[cancelPolling, storageKey]
|
|
2796
|
+
[cancelPolling, rememberConversationToken, storageKey]
|
|
2201
2797
|
);
|
|
2202
2798
|
const deleteConversation = React.useCallback(
|
|
2203
2799
|
(token) => {
|
|
@@ -2286,6 +2882,69 @@ function useExploreChat(options = {}) {
|
|
|
2286
2882
|
},
|
|
2287
2883
|
[client]
|
|
2288
2884
|
);
|
|
2885
|
+
const escalate = React.useCallback(
|
|
2886
|
+
async (email, note) => {
|
|
2887
|
+
const token = conversationTokenRef.current;
|
|
2888
|
+
if (!token) {
|
|
2889
|
+
return { outcome: "no-conversation", email: null, createdAt: null };
|
|
2890
|
+
}
|
|
2891
|
+
const attempt = async (captchaAttempt) => {
|
|
2892
|
+
const activeCatalog = catalogRef.current;
|
|
2893
|
+
let recaptchaToken;
|
|
2894
|
+
if (activeCatalog.captchaRequired && activeCatalog.recaptchaSiteKey) {
|
|
2895
|
+
try {
|
|
2896
|
+
recaptchaToken = await getRecaptchaV3Token(
|
|
2897
|
+
activeCatalog.recaptchaSiteKey
|
|
2898
|
+
);
|
|
2899
|
+
} catch (err) {
|
|
2900
|
+
console.warn(
|
|
2901
|
+
"[WebSDK] reCAPTCHA v3 token could not be minted",
|
|
2902
|
+
err
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
const input = {
|
|
2907
|
+
conversationToken: token,
|
|
2908
|
+
email: email.trim(),
|
|
2909
|
+
note: note?.trim() ? note.trim() : null,
|
|
2910
|
+
...recaptchaToken ? { recaptchaToken } : {},
|
|
2911
|
+
...productId ? { productId } : {}
|
|
2912
|
+
};
|
|
2913
|
+
try {
|
|
2914
|
+
const res = await client.mutate(
|
|
2915
|
+
ESCALATE_EXPLORE_CONVERSATION_MUTATION,
|
|
2916
|
+
{ input }
|
|
2917
|
+
);
|
|
2918
|
+
const first = res?.errors?.[0];
|
|
2919
|
+
const result = res?.data?.escalateExploreConversation;
|
|
2920
|
+
if (first || !result) {
|
|
2921
|
+
const code = typeof first?.extensions?.code === "string" ? first.extensions.code : "ESCALATION_FAILED";
|
|
2922
|
+
if (code === "CAPTCHA_FAILED" && captchaAttempt === 0) {
|
|
2923
|
+
return attempt(1);
|
|
2924
|
+
}
|
|
2925
|
+
return {
|
|
2926
|
+
outcome: escalateOutcomeForCode(code),
|
|
2927
|
+
email: null,
|
|
2928
|
+
createdAt: null
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
return {
|
|
2932
|
+
// `created: false` is the double-click, the retry, the reload. The handover
|
|
2933
|
+
// exists, so this is a success — and the address comes from the SERVER, because
|
|
2934
|
+
// on a repeat submit the reply goes to whatever the FIRST handover used.
|
|
2935
|
+
outcome: result.created ? "sent" : "already-handed-over",
|
|
2936
|
+
email: result.escalation?.email ?? null,
|
|
2937
|
+
createdAt: result.escalation?.createdAt ?? null
|
|
2938
|
+
};
|
|
2939
|
+
} catch {
|
|
2940
|
+
return { outcome: "failed", email: null, createdAt: null };
|
|
2941
|
+
}
|
|
2942
|
+
};
|
|
2943
|
+
return attempt(0);
|
|
2944
|
+
},
|
|
2945
|
+
[client, productId]
|
|
2946
|
+
);
|
|
2947
|
+
const canEscalate = hasConversation && !isSharedView;
|
|
2289
2948
|
return {
|
|
2290
2949
|
phase,
|
|
2291
2950
|
catalog,
|
|
@@ -2306,6 +2965,8 @@ function useExploreChat(options = {}) {
|
|
|
2306
2965
|
clearHistory,
|
|
2307
2966
|
recordClick,
|
|
2308
2967
|
submitFeedback,
|
|
2968
|
+
escalate,
|
|
2969
|
+
canEscalate,
|
|
2309
2970
|
createShareLink,
|
|
2310
2971
|
isSharedView
|
|
2311
2972
|
};
|
|
@@ -2315,6 +2976,224 @@ function makeOptimisticId() {
|
|
|
2315
2976
|
optimisticCounter += 1;
|
|
2316
2977
|
return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
|
|
2317
2978
|
}
|
|
2979
|
+
var DRAIN_TARGET_MS = 1200;
|
|
2980
|
+
var MIN_CHARS_PER_SECOND = 90;
|
|
2981
|
+
var MAX_CHARS_PER_SECOND = 2200;
|
|
2982
|
+
var MAX_STEP_MS = 250;
|
|
2983
|
+
var COMMIT_INTERVAL_MS = 45;
|
|
2984
|
+
var FALLBACK_STEP_MS = 50;
|
|
2985
|
+
var FENCE = /^ {0,3}(?:```|~~~)/;
|
|
2986
|
+
var TABLE_ROW = /^ {0,3}\|/;
|
|
2987
|
+
var EMPTY_BLOCK_MARKER = /^ {0,3}(?:[-+>]|#{1,6}|\d{1,9}[.)])\s*$/;
|
|
2988
|
+
function isWordCharacter(char) {
|
|
2989
|
+
return char !== void 0 && /[\p{L}\p{N}]/u.test(char);
|
|
2990
|
+
}
|
|
2991
|
+
function clamp(value, min, max) {
|
|
2992
|
+
return Math.min(Math.max(value, min), max);
|
|
2993
|
+
}
|
|
2994
|
+
function now() {
|
|
2995
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
2996
|
+
}
|
|
2997
|
+
function isHighSurrogate(code) {
|
|
2998
|
+
return code >= 55296 && code <= 56319;
|
|
2999
|
+
}
|
|
3000
|
+
function safeInlineEnd(line) {
|
|
3001
|
+
let codeSpan = null;
|
|
3002
|
+
let link = null;
|
|
3003
|
+
const emphasis = [];
|
|
3004
|
+
let at = 0;
|
|
3005
|
+
while (at < line.length) {
|
|
3006
|
+
const char = line[at] ?? "";
|
|
3007
|
+
if (char === "\\") {
|
|
3008
|
+
at += 2;
|
|
3009
|
+
continue;
|
|
3010
|
+
}
|
|
3011
|
+
if (char === "`") {
|
|
3012
|
+
let run = 1;
|
|
3013
|
+
while (line[at + run] === "`") run += 1;
|
|
3014
|
+
codeSpan = codeSpan === null ? at : null;
|
|
3015
|
+
at += run;
|
|
3016
|
+
continue;
|
|
3017
|
+
}
|
|
3018
|
+
if (codeSpan !== null) {
|
|
3019
|
+
at += 1;
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
3022
|
+
if (char === "[") {
|
|
3023
|
+
if (link === null) link = line[at - 1] === "!" ? at - 1 : at;
|
|
3024
|
+
at += 1;
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
if (char === "]") {
|
|
3028
|
+
if (link !== null && line[at + 1] !== "(") link = null;
|
|
3029
|
+
at += 1;
|
|
3030
|
+
continue;
|
|
3031
|
+
}
|
|
3032
|
+
if (char === ")") {
|
|
3033
|
+
link = null;
|
|
3034
|
+
at += 1;
|
|
3035
|
+
continue;
|
|
3036
|
+
}
|
|
3037
|
+
if (char === "*" || char === "_" || char === "~") {
|
|
3038
|
+
let run = 1;
|
|
3039
|
+
while (line[at + run] === char) run += 1;
|
|
3040
|
+
const start = at;
|
|
3041
|
+
at += run;
|
|
3042
|
+
const before = line[start - 1];
|
|
3043
|
+
const after = line[start + run];
|
|
3044
|
+
if (run < 2) continue;
|
|
3045
|
+
if (char === "_" && isWordCharacter(before) && isWordCharacter(after)) {
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
const marker = char.repeat(run);
|
|
3049
|
+
const open2 = emphasis.findIndex((entry) => entry.marker === marker);
|
|
3050
|
+
if (open2 !== -1) {
|
|
3051
|
+
emphasis.splice(open2, 1);
|
|
3052
|
+
continue;
|
|
3053
|
+
}
|
|
3054
|
+
if (after !== void 0 && /\s/.test(after)) continue;
|
|
3055
|
+
emphasis.push({ marker, start });
|
|
3056
|
+
continue;
|
|
3057
|
+
}
|
|
3058
|
+
at += 1;
|
|
3059
|
+
}
|
|
3060
|
+
const open = [codeSpan, link, emphasis[0]?.start ?? null].filter(
|
|
3061
|
+
(value) => value !== null
|
|
3062
|
+
);
|
|
3063
|
+
return open.length === 0 ? line.length : Math.min(...open);
|
|
3064
|
+
}
|
|
3065
|
+
function revealBoundary(text, limit) {
|
|
3066
|
+
if (limit <= 0) return 0;
|
|
3067
|
+
const caughtUp = limit >= text.length;
|
|
3068
|
+
const head = caughtUp ? text : text.slice(0, limit);
|
|
3069
|
+
const lineStart = head.lastIndexOf("\n") + 1;
|
|
3070
|
+
const line = head.slice(lineStart);
|
|
3071
|
+
let fenceStart = null;
|
|
3072
|
+
let tableStart = null;
|
|
3073
|
+
let tableRows = 0;
|
|
3074
|
+
let at = 0;
|
|
3075
|
+
while (at < lineStart) {
|
|
3076
|
+
const breakAt = head.indexOf("\n", at);
|
|
3077
|
+
const end = breakAt === -1 ? lineStart : breakAt;
|
|
3078
|
+
const row = head.slice(at, end);
|
|
3079
|
+
if (fenceStart !== null) {
|
|
3080
|
+
if (FENCE.test(row)) fenceStart = null;
|
|
3081
|
+
} else if (FENCE.test(row)) {
|
|
3082
|
+
fenceStart = at;
|
|
3083
|
+
} else if (TABLE_ROW.test(row)) {
|
|
3084
|
+
if (tableStart === null) {
|
|
3085
|
+
tableStart = at;
|
|
3086
|
+
tableRows = 0;
|
|
3087
|
+
}
|
|
3088
|
+
tableRows += 1;
|
|
3089
|
+
} else if (tableStart !== null) {
|
|
3090
|
+
tableStart = null;
|
|
3091
|
+
tableRows = 0;
|
|
3092
|
+
}
|
|
3093
|
+
at = end + 1;
|
|
3094
|
+
}
|
|
3095
|
+
if (fenceStart === null) {
|
|
3096
|
+
const lastIsRow = TABLE_ROW.test(line);
|
|
3097
|
+
if (lastIsRow && tableStart === null) {
|
|
3098
|
+
tableStart = lineStart;
|
|
3099
|
+
tableRows = 0;
|
|
3100
|
+
} else if (!lastIsRow && line !== "") {
|
|
3101
|
+
tableStart = null;
|
|
3102
|
+
tableRows = 0;
|
|
3103
|
+
}
|
|
3104
|
+
if (tableStart !== null && tableRows < 2) return tableStart;
|
|
3105
|
+
}
|
|
3106
|
+
if (caughtUp) return text.length;
|
|
3107
|
+
if (fenceStart !== null) return fenceStart;
|
|
3108
|
+
if (TABLE_ROW.test(line)) return lineStart;
|
|
3109
|
+
if (EMPTY_BLOCK_MARKER.test(line)) return lineStart;
|
|
3110
|
+
const cut = lineStart + safeInlineEnd(line);
|
|
3111
|
+
return isHighSurrogate(text.charCodeAt(cut - 1)) ? cut - 1 : cut;
|
|
3112
|
+
}
|
|
3113
|
+
function prefersReducedMotion() {
|
|
3114
|
+
if (typeof window === "undefined") return true;
|
|
3115
|
+
try {
|
|
3116
|
+
return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
3117
|
+
} catch {
|
|
3118
|
+
return false;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
function useProgressiveReveal(text, settled) {
|
|
3122
|
+
const [, setFrame] = React.useState(0);
|
|
3123
|
+
const [reduced] = React.useState(prefersReducedMotion);
|
|
3124
|
+
const immediate = settled || reduced;
|
|
3125
|
+
const textRef = React.useRef(text);
|
|
3126
|
+
const cursorRef = React.useRef(immediate ? text.length : 0);
|
|
3127
|
+
const cutRef = React.useRef(immediate ? text.length : 0);
|
|
3128
|
+
const committedRef = React.useRef(0);
|
|
3129
|
+
if (text !== textRef.current) {
|
|
3130
|
+
const revealed = textRef.current.slice(0, Math.floor(cursorRef.current));
|
|
3131
|
+
if (revealed !== "" && !text.startsWith(revealed)) {
|
|
3132
|
+
cursorRef.current = text.length;
|
|
3133
|
+
}
|
|
3134
|
+
cursorRef.current = Math.min(cursorRef.current, text.length);
|
|
3135
|
+
cutRef.current = Math.min(cutRef.current, text.length);
|
|
3136
|
+
textRef.current = text;
|
|
3137
|
+
}
|
|
3138
|
+
if (immediate) cursorRef.current = text.length;
|
|
3139
|
+
const cut = immediate ? text.length : Math.max(
|
|
3140
|
+
cutRef.current,
|
|
3141
|
+
revealBoundary(text, Math.floor(cursorRef.current))
|
|
3142
|
+
);
|
|
3143
|
+
cutRef.current = cut;
|
|
3144
|
+
React.useEffect(() => {
|
|
3145
|
+
if (immediate) return;
|
|
3146
|
+
if (cursorRef.current >= text.length) return;
|
|
3147
|
+
let cancelled = false;
|
|
3148
|
+
let cancelStep = null;
|
|
3149
|
+
let previous = now();
|
|
3150
|
+
const commit = (at, force) => {
|
|
3151
|
+
if (!force && at - committedRef.current < COMMIT_INTERVAL_MS) return;
|
|
3152
|
+
committedRef.current = at;
|
|
3153
|
+
setFrame((value) => value + 1);
|
|
3154
|
+
};
|
|
3155
|
+
const step = () => {
|
|
3156
|
+
cancelStep = null;
|
|
3157
|
+
if (cancelled) return;
|
|
3158
|
+
const at = now();
|
|
3159
|
+
const elapsed = clamp(at - previous, 0, MAX_STEP_MS);
|
|
3160
|
+
previous = at;
|
|
3161
|
+
const length = textRef.current.length;
|
|
3162
|
+
const pending = length - cursorRef.current;
|
|
3163
|
+
if (pending <= 0) {
|
|
3164
|
+
commit(at, true);
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
const perSecond = clamp(
|
|
3168
|
+
pending / (DRAIN_TARGET_MS / 1e3),
|
|
3169
|
+
MIN_CHARS_PER_SECOND,
|
|
3170
|
+
MAX_CHARS_PER_SECOND
|
|
3171
|
+
);
|
|
3172
|
+
cursorRef.current = Math.min(
|
|
3173
|
+
length,
|
|
3174
|
+
cursorRef.current + perSecond * elapsed / 1e3
|
|
3175
|
+
);
|
|
3176
|
+
const done = cursorRef.current >= length;
|
|
3177
|
+
commit(at, done);
|
|
3178
|
+
if (!done) schedule();
|
|
3179
|
+
};
|
|
3180
|
+
const schedule = () => {
|
|
3181
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
3182
|
+
const id2 = window.requestAnimationFrame(step);
|
|
3183
|
+
cancelStep = () => window.cancelAnimationFrame(id2);
|
|
3184
|
+
return;
|
|
3185
|
+
}
|
|
3186
|
+
const id = setTimeout(step, FALLBACK_STEP_MS);
|
|
3187
|
+
cancelStep = () => clearTimeout(id);
|
|
3188
|
+
};
|
|
3189
|
+
schedule();
|
|
3190
|
+
return () => {
|
|
3191
|
+
cancelled = true;
|
|
3192
|
+
cancelStep?.();
|
|
3193
|
+
};
|
|
3194
|
+
}, [text, immediate]);
|
|
3195
|
+
return text.slice(0, cut);
|
|
3196
|
+
}
|
|
2318
3197
|
function getRecognitionCtor() {
|
|
2319
3198
|
if (typeof window === "undefined") return null;
|
|
2320
3199
|
const w = window;
|
|
@@ -2533,6 +3412,141 @@ function useSpeechInput({
|
|
|
2533
3412
|
);
|
|
2534
3413
|
return { supported, listening, interim, error, start, stop, toggle };
|
|
2535
3414
|
}
|
|
3415
|
+
var SILENT_FAILURE_MS = 1500;
|
|
3416
|
+
function speechPlatform() {
|
|
3417
|
+
if (typeof window === "undefined") return null;
|
|
3418
|
+
const scope = window;
|
|
3419
|
+
const synth = scope.speechSynthesis;
|
|
3420
|
+
const Utterance = scope.SpeechSynthesisUtterance;
|
|
3421
|
+
if (!synth || typeof synth.speak !== "function") return null;
|
|
3422
|
+
if (typeof Utterance !== "function") return null;
|
|
3423
|
+
return { synth, Utterance };
|
|
3424
|
+
}
|
|
3425
|
+
function normaliseTag(tag) {
|
|
3426
|
+
return tag.trim().toLowerCase().replace(/_/g, "-");
|
|
3427
|
+
}
|
|
3428
|
+
function primaryOf(tag) {
|
|
3429
|
+
return normaliseTag(tag).split("-")[0] ?? "";
|
|
3430
|
+
}
|
|
3431
|
+
function pickVoice(voices, lang) {
|
|
3432
|
+
const wanted = normaliseTag(lang);
|
|
3433
|
+
if (wanted === "") return null;
|
|
3434
|
+
const primary = primaryOf(wanted);
|
|
3435
|
+
const exact = voices.filter((voice) => normaliseTag(voice.lang) === wanted);
|
|
3436
|
+
const related = voices.filter((voice) => primaryOf(voice.lang) === primary);
|
|
3437
|
+
return exact.find((voice) => voice.default) ?? exact[0] ?? related.find((voice) => voice.default) ?? related[0] ?? null;
|
|
3438
|
+
}
|
|
3439
|
+
var activeOwner = null;
|
|
3440
|
+
var activeStop = null;
|
|
3441
|
+
function stopSpeaking() {
|
|
3442
|
+
activeStop?.();
|
|
3443
|
+
}
|
|
3444
|
+
function useSpeechOutput({
|
|
3445
|
+
lang
|
|
3446
|
+
} = {}) {
|
|
3447
|
+
const [available, setAvailable] = React.useState(() => speechPlatform() !== null);
|
|
3448
|
+
const [speaking, setSpeaking] = React.useState(false);
|
|
3449
|
+
const utteranceRef = React.useRef(null);
|
|
3450
|
+
const watchdogRef = React.useRef(null);
|
|
3451
|
+
const ownerRef = React.useRef({});
|
|
3452
|
+
const stopRef = React.useRef(() => void 0);
|
|
3453
|
+
const langRef = React.useRef(lang);
|
|
3454
|
+
langRef.current = lang;
|
|
3455
|
+
const clearWatchdog = React.useCallback(() => {
|
|
3456
|
+
if (watchdogRef.current === null) return;
|
|
3457
|
+
clearTimeout(watchdogRef.current);
|
|
3458
|
+
watchdogRef.current = null;
|
|
3459
|
+
}, []);
|
|
3460
|
+
const settle = React.useCallback(() => {
|
|
3461
|
+
clearWatchdog();
|
|
3462
|
+
utteranceRef.current = null;
|
|
3463
|
+
if (activeOwner === ownerRef.current) {
|
|
3464
|
+
activeOwner = null;
|
|
3465
|
+
activeStop = null;
|
|
3466
|
+
}
|
|
3467
|
+
setSpeaking(false);
|
|
3468
|
+
}, [clearWatchdog]);
|
|
3469
|
+
const stop = React.useCallback(() => {
|
|
3470
|
+
const owned = utteranceRef.current !== null;
|
|
3471
|
+
settle();
|
|
3472
|
+
if (owned) speechPlatform()?.synth.cancel();
|
|
3473
|
+
}, [settle]);
|
|
3474
|
+
stopRef.current = stop;
|
|
3475
|
+
const speak = React.useCallback(
|
|
3476
|
+
(text) => {
|
|
3477
|
+
const platform = speechPlatform();
|
|
3478
|
+
if (!platform) {
|
|
3479
|
+
setAvailable(false);
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
const spoken = text.trim();
|
|
3483
|
+
if (spoken === "") return;
|
|
3484
|
+
if (activeStop && activeOwner !== ownerRef.current) activeStop();
|
|
3485
|
+
platform.synth.cancel();
|
|
3486
|
+
const utterance = new platform.Utterance(spoken);
|
|
3487
|
+
const wanted = langRef.current;
|
|
3488
|
+
if (wanted !== void 0 && wanted !== "") {
|
|
3489
|
+
const voice = pickVoice(platform.synth.getVoices(), wanted);
|
|
3490
|
+
utterance.lang = voice?.lang ?? wanted;
|
|
3491
|
+
if (voice) utterance.voice = voice;
|
|
3492
|
+
}
|
|
3493
|
+
const mine = () => utteranceRef.current === utterance;
|
|
3494
|
+
utterance.onstart = () => {
|
|
3495
|
+
if (!mine()) return;
|
|
3496
|
+
clearWatchdog();
|
|
3497
|
+
setSpeaking(true);
|
|
3498
|
+
};
|
|
3499
|
+
utterance.onend = () => {
|
|
3500
|
+
if (!mine()) return;
|
|
3501
|
+
settle();
|
|
3502
|
+
};
|
|
3503
|
+
utterance.onerror = (event) => {
|
|
3504
|
+
if (!mine()) return;
|
|
3505
|
+
const code = typeof event?.error === "string" ? event.error : "";
|
|
3506
|
+
settle();
|
|
3507
|
+
if (code !== "interrupted" && code !== "canceled") setAvailable(false);
|
|
3508
|
+
};
|
|
3509
|
+
utteranceRef.current = utterance;
|
|
3510
|
+
activeOwner = ownerRef.current;
|
|
3511
|
+
activeStop = () => {
|
|
3512
|
+
stopRef.current();
|
|
3513
|
+
};
|
|
3514
|
+
setSpeaking(true);
|
|
3515
|
+
platform.synth.speak(utterance);
|
|
3516
|
+
clearWatchdog();
|
|
3517
|
+
watchdogRef.current = setTimeout(() => {
|
|
3518
|
+
watchdogRef.current = null;
|
|
3519
|
+
if (!mine()) return;
|
|
3520
|
+
if (platform.synth.speaking) return;
|
|
3521
|
+
settle();
|
|
3522
|
+
setAvailable(false);
|
|
3523
|
+
}, SILENT_FAILURE_MS);
|
|
3524
|
+
},
|
|
3525
|
+
[clearWatchdog, settle]
|
|
3526
|
+
);
|
|
3527
|
+
const toggle = React.useCallback(
|
|
3528
|
+
(text) => {
|
|
3529
|
+
if (speaking) stop();
|
|
3530
|
+
else speak(text);
|
|
3531
|
+
},
|
|
3532
|
+
[speak, speaking, stop]
|
|
3533
|
+
);
|
|
3534
|
+
React.useEffect(() => {
|
|
3535
|
+
if (!speaking || typeof window === "undefined") return;
|
|
3536
|
+
const onHide = () => {
|
|
3537
|
+
stopRef.current();
|
|
3538
|
+
};
|
|
3539
|
+
window.addEventListener("pagehide", onHide);
|
|
3540
|
+
return () => window.removeEventListener("pagehide", onHide);
|
|
3541
|
+
}, [speaking]);
|
|
3542
|
+
React.useEffect(
|
|
3543
|
+
() => () => {
|
|
3544
|
+
stopRef.current();
|
|
3545
|
+
},
|
|
3546
|
+
[]
|
|
3547
|
+
);
|
|
3548
|
+
return { supported: available, speaking, speak, stop, toggle };
|
|
3549
|
+
}
|
|
2536
3550
|
var SWIPE_THRESHOLD = 40;
|
|
2537
3551
|
function useSwipe({
|
|
2538
3552
|
onSwipeLeft,
|
|
@@ -2638,9 +3652,6 @@ function PageHead({
|
|
|
2638
3652
|
))
|
|
2639
3653
|
] });
|
|
2640
3654
|
}
|
|
2641
|
-
function cn(...inputs) {
|
|
2642
|
-
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
2643
|
-
}
|
|
2644
3655
|
var buttonVariants = classVarianceAuthority.cva(
|
|
2645
3656
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
2646
3657
|
{
|
|
@@ -2898,34 +3909,7 @@ var EXPLORE_CSS = `
|
|
|
2898
3909
|
}
|
|
2899
3910
|
.boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
|
|
2900
3911
|
@media (prefers-reduced-motion: reduce) {
|
|
2901
|
-
.boff-explore-fade { animation: none; }
|
|
2902
|
-
}
|
|
2903
|
-
|
|
2904
|
-
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
2905
|
-
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
2906
|
-
WHOLE document horizontally scrollable, not just the bubble. */
|
|
2907
|
-
.boff-explore-body,
|
|
2908
|
-
.boff-explore-body p,
|
|
2909
|
-
.boff-explore-body li,
|
|
2910
|
-
.boff-explore-body a,
|
|
2911
|
-
.boff-explore-body h1,
|
|
2912
|
-
.boff-explore-body h2,
|
|
2913
|
-
.boff-explore-body h3 {
|
|
2914
|
-
overflow-wrap: anywhere;
|
|
2915
|
-
word-break: break-word;
|
|
2916
|
-
}
|
|
2917
|
-
.boff-explore-body pre {
|
|
2918
|
-
overflow-x: auto;
|
|
2919
|
-
max-width: 100%;
|
|
2920
|
-
}
|
|
2921
|
-
.boff-explore-body table {
|
|
2922
|
-
display: block;
|
|
2923
|
-
overflow-x: auto;
|
|
2924
|
-
max-width: 100%;
|
|
2925
|
-
}
|
|
2926
|
-
.boff-explore-body img {
|
|
2927
|
-
max-width: 100%;
|
|
2928
|
-
height: auto;
|
|
3912
|
+
.boff-explore-fade { animation: none; }
|
|
2929
3913
|
}
|
|
2930
3914
|
|
|
2931
3915
|
/* ---- Ambient backdrop -----------------------------------------------------------------
|
|
@@ -2984,11 +3968,40 @@ var EXPLORE_CSS = `
|
|
|
2984
3968
|
animation: boff-explore-sheen 1.5s ease-in-out infinite;
|
|
2985
3969
|
}
|
|
2986
3970
|
|
|
3971
|
+
/* ---- The composer, before anyone has typed -----------------------------------------
|
|
3972
|
+
On the welcome screen the box IS the page: everything above it is a way of explaining
|
|
3973
|
+
what to put in it. Unhighlighted it reads as a search bar at the bottom of a landing
|
|
3974
|
+
page, which people scroll past \u2014 so while the conversation is empty it gets a soft halo
|
|
3975
|
+
in the site's own --primary and one slow breath of it, and the moment there is a
|
|
3976
|
+
transcript, or the visitor focuses or types, it drops back to the ordinary card it was.
|
|
3977
|
+
An input that keeps pulsing while you are using it is nagging, not inviting.
|
|
3978
|
+
|
|
3979
|
+
Colour comes from the product's --primary token, so it is whatever that site's palette
|
|
3980
|
+
already is, never a colour chosen here. */
|
|
3981
|
+
.boff-explore-invite {
|
|
3982
|
+
border-color: hsl(var(--primary) / 0.45);
|
|
3983
|
+
box-shadow:
|
|
3984
|
+
0 0 0 4px hsl(var(--primary) / 0.08),
|
|
3985
|
+
0 8px 24px -12px hsl(var(--primary) / 0.45);
|
|
3986
|
+
animation: boff-explore-breathe 3.2s ease-in-out infinite;
|
|
3987
|
+
}
|
|
3988
|
+
/* Focus and typing both mean "I have found it", and the hint has done its job. */
|
|
3989
|
+
.boff-explore-invite:focus-within,
|
|
3990
|
+
.boff-explore-invite.boff-explore-invite-quiet {
|
|
3991
|
+
animation: none;
|
|
3992
|
+
box-shadow: 0 0 0 4px hsl(var(--primary) / 0.10);
|
|
3993
|
+
}
|
|
3994
|
+
@keyframes boff-explore-breathe {
|
|
3995
|
+
0%, 100% { box-shadow: 0 0 0 4px hsl(var(--primary) / 0.07), 0 8px 24px -12px hsl(var(--primary) / 0.40); }
|
|
3996
|
+
50% { box-shadow: 0 0 0 7px hsl(var(--primary) / 0.13), 0 8px 28px -12px hsl(var(--primary) / 0.55); }
|
|
3997
|
+
}
|
|
3998
|
+
|
|
2987
3999
|
/* Every one of the above is decoration. Reduced motion removes the movement and keeps the
|
|
2988
4000
|
final state, rather than leaving content mid-animation and invisible. */
|
|
2989
4001
|
@media (prefers-reduced-motion: reduce) {
|
|
2990
4002
|
.boff-explore-aura::before,
|
|
2991
|
-
.boff-explore-busy::after
|
|
4003
|
+
.boff-explore-busy::after,
|
|
4004
|
+
.boff-explore-invite { animation: none; }
|
|
2992
4005
|
.boff-explore-rise { animation: none; opacity: 1; transform: none; }
|
|
2993
4006
|
}
|
|
2994
4007
|
|
|
@@ -3099,7 +4112,7 @@ function isOwnElement(el, root) {
|
|
|
3099
4112
|
function visualViewportHeight() {
|
|
3100
4113
|
return window.visualViewport?.height ?? window.innerHeight;
|
|
3101
4114
|
}
|
|
3102
|
-
function
|
|
4115
|
+
function clamp2(value, min, max) {
|
|
3103
4116
|
return Math.min(Math.max(value, min), max);
|
|
3104
4117
|
}
|
|
3105
4118
|
function isRestoredNavigation() {
|
|
@@ -3136,34 +4149,6 @@ function logoUrlOf(url) {
|
|
|
3136
4149
|
return null;
|
|
3137
4150
|
}
|
|
3138
4151
|
}
|
|
3139
|
-
function inlineImageOrigins(products) {
|
|
3140
|
-
const origins = /* @__PURE__ */ new Set();
|
|
3141
|
-
for (const product of products) {
|
|
3142
|
-
let url;
|
|
3143
|
-
try {
|
|
3144
|
-
url = new URL(product.website);
|
|
3145
|
-
} catch {
|
|
3146
|
-
continue;
|
|
3147
|
-
}
|
|
3148
|
-
if (url.protocol !== "https:") continue;
|
|
3149
|
-
const apex = url.hostname.replace(/^www\./, "");
|
|
3150
|
-
const port = url.port ? `:${url.port}` : "";
|
|
3151
|
-
origins.add(`https://${apex}${port}`);
|
|
3152
|
-
origins.add(`https://www.${apex}${port}`);
|
|
3153
|
-
}
|
|
3154
|
-
return origins;
|
|
3155
|
-
}
|
|
3156
|
-
function isInlineImageAllowed(src, origins) {
|
|
3157
|
-
if (typeof src !== "string" || src === "") return false;
|
|
3158
|
-
let url;
|
|
3159
|
-
try {
|
|
3160
|
-
url = new URL(src);
|
|
3161
|
-
} catch {
|
|
3162
|
-
return false;
|
|
3163
|
-
}
|
|
3164
|
-
if (url.protocol !== "https:" || url.username || url.password) return false;
|
|
3165
|
-
return origins.has(url.origin);
|
|
3166
|
-
}
|
|
3167
4152
|
var REFERENCE_KIND_ORDER = [
|
|
3168
4153
|
"CONCEPT",
|
|
3169
4154
|
"DOCS",
|
|
@@ -3248,97 +4233,6 @@ function summariseReferences(references, origins, productNames) {
|
|
|
3248
4233
|
products
|
|
3249
4234
|
};
|
|
3250
4235
|
}
|
|
3251
|
-
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
3252
|
-
function answerPlainText(markdown) {
|
|
3253
|
-
const lines = [];
|
|
3254
|
-
let inFence = false;
|
|
3255
|
-
for (const raw of markdown.split("\n")) {
|
|
3256
|
-
if (CODE_FENCE.test(raw)) {
|
|
3257
|
-
inFence = !inFence;
|
|
3258
|
-
continue;
|
|
3259
|
-
}
|
|
3260
|
-
if (inFence) {
|
|
3261
|
-
lines.push(raw);
|
|
3262
|
-
continue;
|
|
3263
|
-
}
|
|
3264
|
-
let line = raw;
|
|
3265
|
-
line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
|
|
3266
|
-
line = line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
|
|
3267
|
-
line = line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
3268
|
-
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
3269
|
-
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
3270
|
-
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) {
|
|
3271
|
-
lines.push("");
|
|
3272
|
-
continue;
|
|
3273
|
-
}
|
|
3274
|
-
line = line.replace(/`([^`]+)`/g, "$1");
|
|
3275
|
-
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
3276
|
-
line = line.replace(/__([^_]+)__/g, "$1");
|
|
3277
|
-
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
3278
|
-
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
3279
|
-
lines.push(line.replace(/[ \t]+$/, ""));
|
|
3280
|
-
}
|
|
3281
|
-
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
3282
|
-
}
|
|
3283
|
-
var InlineImageScopeContext = React.createContext(null);
|
|
3284
|
-
var InsideLinkContext = React.createContext(false);
|
|
3285
|
-
function AnswerLink(props) {
|
|
3286
|
-
return /* @__PURE__ */ jsxRuntime.jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownLink, { ...props }) });
|
|
3287
|
-
}
|
|
3288
|
-
var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
|
|
3289
|
-
function InlineConceptImage({ src, alt }) {
|
|
3290
|
-
const scope = React.useContext(InlineImageScopeContext);
|
|
3291
|
-
const insideLink = React.useContext(InsideLinkContext);
|
|
3292
|
-
if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
|
|
3293
|
-
return null;
|
|
3294
|
-
}
|
|
3295
|
-
const { strings: T, onFailed } = scope;
|
|
3296
|
-
const picture = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3297
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3298
|
-
"img",
|
|
3299
|
-
{
|
|
3300
|
-
src,
|
|
3301
|
-
alt: alt ?? "",
|
|
3302
|
-
loading: "lazy",
|
|
3303
|
-
decoding: "async",
|
|
3304
|
-
onError: () => onFailed(src),
|
|
3305
|
-
className: "block h-auto max-h-80 w-auto max-w-full object-contain"
|
|
3306
|
-
}
|
|
3307
|
-
),
|
|
3308
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3309
|
-
"span",
|
|
3310
|
-
{
|
|
3311
|
-
"aria-hidden": "true",
|
|
3312
|
-
className: "pointer-events-none absolute left-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
|
|
3313
|
-
children: T.conceptIllustration
|
|
3314
|
-
}
|
|
3315
|
-
)
|
|
3316
|
-
] });
|
|
3317
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3318
|
-
"a",
|
|
3319
|
-
{
|
|
3320
|
-
href: src,
|
|
3321
|
-
target: "_blank",
|
|
3322
|
-
rel: "noopener noreferrer",
|
|
3323
|
-
className: cn(
|
|
3324
|
-
INLINE_IMAGE_FRAME,
|
|
3325
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
3326
|
-
),
|
|
3327
|
-
children: [
|
|
3328
|
-
picture,
|
|
3329
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sr-only", children: [
|
|
3330
|
-
"(",
|
|
3331
|
-
T.opensInNewTab,
|
|
3332
|
-
")"
|
|
3333
|
-
] })
|
|
3334
|
-
]
|
|
3335
|
-
}
|
|
3336
|
-
) });
|
|
3337
|
-
}
|
|
3338
|
-
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
3339
|
-
a: AnswerLink,
|
|
3340
|
-
img: InlineConceptImage
|
|
3341
|
-
};
|
|
3342
4236
|
function ReferenceCard({
|
|
3343
4237
|
reference,
|
|
3344
4238
|
onFollow
|
|
@@ -3677,6 +4571,195 @@ function AnswerFeedback({
|
|
|
3677
4571
|
}
|
|
3678
4572
|
);
|
|
3679
4573
|
}
|
|
4574
|
+
function EscalateForm({
|
|
4575
|
+
strings: T,
|
|
4576
|
+
formId,
|
|
4577
|
+
onEscalate,
|
|
4578
|
+
onClose,
|
|
4579
|
+
onBusyChange
|
|
4580
|
+
}) {
|
|
4581
|
+
const [email, setEmail] = React.useState("");
|
|
4582
|
+
const [note, setNote] = React.useState("");
|
|
4583
|
+
const [error, setError] = React.useState(null);
|
|
4584
|
+
const [sending, setSending] = React.useState(false);
|
|
4585
|
+
const [sentTo, setSentTo] = React.useState(null);
|
|
4586
|
+
const emailRef = React.useRef(null);
|
|
4587
|
+
const inFlightRef = React.useRef(false);
|
|
4588
|
+
const titleId = `${formId}-title`;
|
|
4589
|
+
const emailId = `${formId}-email`;
|
|
4590
|
+
const noteId = `${formId}-note`;
|
|
4591
|
+
const errorId = `${formId}-error`;
|
|
4592
|
+
React.useEffect(() => {
|
|
4593
|
+
emailRef.current?.focus();
|
|
4594
|
+
}, []);
|
|
4595
|
+
React.useEffect(() => {
|
|
4596
|
+
onBusyChange?.(sending);
|
|
4597
|
+
return () => onBusyChange?.(false);
|
|
4598
|
+
}, [sending, onBusyChange]);
|
|
4599
|
+
const handleSubmit = React.useCallback(
|
|
4600
|
+
async (event) => {
|
|
4601
|
+
event.preventDefault();
|
|
4602
|
+
if (inFlightRef.current) return;
|
|
4603
|
+
const address = email.trim();
|
|
4604
|
+
if (!looksLikeDeliverableEmail(address)) {
|
|
4605
|
+
setError(T.escalateInvalidEmail);
|
|
4606
|
+
emailRef.current?.focus();
|
|
4607
|
+
return;
|
|
4608
|
+
}
|
|
4609
|
+
inFlightRef.current = true;
|
|
4610
|
+
setSending(true);
|
|
4611
|
+
setError(null);
|
|
4612
|
+
const result = await onEscalate(address, note);
|
|
4613
|
+
inFlightRef.current = false;
|
|
4614
|
+
setSending(false);
|
|
4615
|
+
switch (result.outcome) {
|
|
4616
|
+
case "sent":
|
|
4617
|
+
case "already-handed-over":
|
|
4618
|
+
setSentTo(result.email ?? address);
|
|
4619
|
+
return;
|
|
4620
|
+
case "invalid-email":
|
|
4621
|
+
setError(T.escalateInvalidEmail);
|
|
4622
|
+
emailRef.current?.focus();
|
|
4623
|
+
return;
|
|
4624
|
+
case "rate-limited":
|
|
4625
|
+
setError(T.escalateRateLimited);
|
|
4626
|
+
return;
|
|
4627
|
+
case "captcha":
|
|
4628
|
+
setError(T.escalateCaptcha);
|
|
4629
|
+
return;
|
|
4630
|
+
// `no-conversation` included: the form should not have been offered at all, and the
|
|
4631
|
+
// honest thing to show is that nothing was passed on.
|
|
4632
|
+
default:
|
|
4633
|
+
setError(T.escalateFailed);
|
|
4634
|
+
return;
|
|
4635
|
+
}
|
|
4636
|
+
},
|
|
4637
|
+
[T, email, note, onEscalate]
|
|
4638
|
+
);
|
|
4639
|
+
if (sentTo !== null) {
|
|
4640
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4641
|
+
"p",
|
|
4642
|
+
{
|
|
4643
|
+
id: formId,
|
|
4644
|
+
"data-boff-explore": "escalate-sent",
|
|
4645
|
+
role: "status",
|
|
4646
|
+
className: "boff-explore-fade mt-2 rounded-xl border border-border bg-card px-3 py-2 text-xs leading-relaxed text-card-foreground",
|
|
4647
|
+
children: fill(T.escalateSent, { email: sentTo })
|
|
4648
|
+
}
|
|
4649
|
+
);
|
|
4650
|
+
}
|
|
4651
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4652
|
+
"form",
|
|
4653
|
+
{
|
|
4654
|
+
id: formId,
|
|
4655
|
+
"data-boff-explore": "escalate-form",
|
|
4656
|
+
"aria-labelledby": titleId,
|
|
4657
|
+
noValidate: true,
|
|
4658
|
+
onSubmit: handleSubmit,
|
|
4659
|
+
onKeyDown: (event) => {
|
|
4660
|
+
if (event.key !== "Escape") return;
|
|
4661
|
+
event.stopPropagation();
|
|
4662
|
+
if (sending) return;
|
|
4663
|
+
onClose();
|
|
4664
|
+
},
|
|
4665
|
+
className: "boff-explore-fade mt-2 space-y-2 rounded-xl border border-border bg-card p-3",
|
|
4666
|
+
children: [
|
|
4667
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { id: titleId, className: "text-xs font-medium text-card-foreground", children: T.escalateTitle }),
|
|
4668
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: T.escalateBody }),
|
|
4669
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
4670
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4671
|
+
"label",
|
|
4672
|
+
{
|
|
4673
|
+
htmlFor: emailId,
|
|
4674
|
+
className: "block text-xs font-medium text-card-foreground",
|
|
4675
|
+
children: T.escalateEmailLabel
|
|
4676
|
+
}
|
|
4677
|
+
),
|
|
4678
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4679
|
+
"input",
|
|
4680
|
+
{
|
|
4681
|
+
ref: emailRef,
|
|
4682
|
+
id: emailId,
|
|
4683
|
+
name: "email",
|
|
4684
|
+
"data-boff-explore": "escalate-email",
|
|
4685
|
+
type: "email",
|
|
4686
|
+
inputMode: "email",
|
|
4687
|
+
autoComplete: "email",
|
|
4688
|
+
required: true,
|
|
4689
|
+
value: email,
|
|
4690
|
+
onChange: (event) => {
|
|
4691
|
+
setEmail(event.target.value);
|
|
4692
|
+
if (error) setError(null);
|
|
4693
|
+
},
|
|
4694
|
+
"aria-invalid": error ? "true" : void 0,
|
|
4695
|
+
"aria-describedby": error ? errorId : void 0,
|
|
4696
|
+
placeholder: T.escalateEmailPlaceholder,
|
|
4697
|
+
className: "w-full rounded-md border border-border bg-background px-2 py-1 text-base text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:text-sm"
|
|
4698
|
+
}
|
|
4699
|
+
)
|
|
4700
|
+
] }),
|
|
4701
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
4702
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4703
|
+
"label",
|
|
4704
|
+
{
|
|
4705
|
+
htmlFor: noteId,
|
|
4706
|
+
className: "block text-xs font-medium text-card-foreground",
|
|
4707
|
+
children: T.escalateNoteLabel
|
|
4708
|
+
}
|
|
4709
|
+
),
|
|
4710
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4711
|
+
Textarea,
|
|
4712
|
+
{
|
|
4713
|
+
id: noteId,
|
|
4714
|
+
name: "note",
|
|
4715
|
+
"data-boff-explore": "escalate-note",
|
|
4716
|
+
rows: 2,
|
|
4717
|
+
value: note,
|
|
4718
|
+
onChange: (event) => setNote(event.target.value),
|
|
4719
|
+
maxLength: EXPLORE_ESCALATION_NOTE_SOFT_MAX,
|
|
4720
|
+
className: "min-h-0 border-border bg-background"
|
|
4721
|
+
}
|
|
4722
|
+
)
|
|
4723
|
+
] }),
|
|
4724
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4725
|
+
"p",
|
|
4726
|
+
{
|
|
4727
|
+
id: errorId,
|
|
4728
|
+
"data-boff-explore": "escalate-error",
|
|
4729
|
+
role: "alert",
|
|
4730
|
+
className: "text-xs leading-relaxed text-destructive",
|
|
4731
|
+
children: error
|
|
4732
|
+
}
|
|
4733
|
+
) : null,
|
|
4734
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
4735
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4736
|
+
Button,
|
|
4737
|
+
{
|
|
4738
|
+
"data-boff-explore": "escalate-send",
|
|
4739
|
+
type: "submit",
|
|
4740
|
+
size: "sm",
|
|
4741
|
+
disabled: sending,
|
|
4742
|
+
children: sending ? T.escalateSending : T.escalateSubmit
|
|
4743
|
+
}
|
|
4744
|
+
),
|
|
4745
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4746
|
+
Button,
|
|
4747
|
+
{
|
|
4748
|
+
"data-boff-explore": "escalate-cancel",
|
|
4749
|
+
type: "button",
|
|
4750
|
+
size: "sm",
|
|
4751
|
+
variant: "ghost",
|
|
4752
|
+
className: "text-muted-foreground",
|
|
4753
|
+
disabled: sending,
|
|
4754
|
+
onClick: onClose,
|
|
4755
|
+
children: T.escalateCancel
|
|
4756
|
+
}
|
|
4757
|
+
)
|
|
4758
|
+
] })
|
|
4759
|
+
]
|
|
4760
|
+
}
|
|
4761
|
+
);
|
|
4762
|
+
}
|
|
3680
4763
|
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";
|
|
3681
4764
|
function ProvenanceChips({
|
|
3682
4765
|
strings: T,
|
|
@@ -3726,9 +4809,42 @@ function ProvenanceChips({
|
|
|
3726
4809
|
);
|
|
3727
4810
|
}
|
|
3728
4811
|
var COPY_FEEDBACK_MS = 2500;
|
|
4812
|
+
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";
|
|
4813
|
+
var ANSWER_ACTION_IDLE = "border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent hover:text-foreground";
|
|
4814
|
+
var ANSWER_ACTION_ACTIVE = "border-primary/40 bg-accent text-primary";
|
|
4815
|
+
function ReadAloudButton({
|
|
4816
|
+
strings: T,
|
|
4817
|
+
text,
|
|
4818
|
+
lang
|
|
4819
|
+
}) {
|
|
4820
|
+
const speech = useSpeechOutput({ lang });
|
|
4821
|
+
if (!speech.supported) return null;
|
|
4822
|
+
const label = speech.speaking ? T.stopReading : T.readAloud;
|
|
4823
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4824
|
+
"button",
|
|
4825
|
+
{
|
|
4826
|
+
"data-boff-explore": "read-aloud",
|
|
4827
|
+
"data-speaking": speech.speaking ? "true" : "false",
|
|
4828
|
+
type: "button",
|
|
4829
|
+
title: label,
|
|
4830
|
+
"aria-pressed": speech.speaking,
|
|
4831
|
+
onClick: () => speech.toggle(text),
|
|
4832
|
+
className: cn(
|
|
4833
|
+
ANSWER_ACTION_PILL,
|
|
4834
|
+
speech.speaking ? ANSWER_ACTION_ACTIVE : ANSWER_ACTION_IDLE
|
|
4835
|
+
),
|
|
4836
|
+
children: [
|
|
4837
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Volume2, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
|
|
4838
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label })
|
|
4839
|
+
]
|
|
4840
|
+
}
|
|
4841
|
+
);
|
|
4842
|
+
}
|
|
3729
4843
|
function AnswerActions({
|
|
3730
4844
|
strings: T,
|
|
3731
|
-
text
|
|
4845
|
+
text,
|
|
4846
|
+
speechText,
|
|
4847
|
+
speechLang
|
|
3732
4848
|
}) {
|
|
3733
4849
|
const [outcome, setOutcome] = React.useState("idle");
|
|
3734
4850
|
const resetRef = React.useRef(null);
|
|
@@ -3771,9 +4887,8 @@ function AnswerActions({
|
|
|
3771
4887
|
void copy();
|
|
3772
4888
|
},
|
|
3773
4889
|
className: cn(
|
|
3774
|
-
|
|
3775
|
-
"
|
|
3776
|
-
outcome === "failed" ? "border-destructive/40 bg-card text-destructive" : "border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent hover:text-foreground"
|
|
4890
|
+
ANSWER_ACTION_PILL,
|
|
4891
|
+
outcome === "failed" ? "border-destructive/40 bg-card text-destructive" : ANSWER_ACTION_IDLE
|
|
3777
4892
|
),
|
|
3778
4893
|
children: [
|
|
3779
4894
|
outcome === "copied" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
|
|
@@ -3781,6 +4896,7 @@ function AnswerActions({
|
|
|
3781
4896
|
]
|
|
3782
4897
|
}
|
|
3783
4898
|
),
|
|
4899
|
+
/* @__PURE__ */ jsxRuntime.jsx(ReadAloudButton, { strings: T, text: speechText, lang: speechLang }),
|
|
3784
4900
|
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-boff-explore": "copy-status", role: "status", className: "sr-only", children: outcome === "idle" ? "" : label })
|
|
3785
4901
|
]
|
|
3786
4902
|
}
|
|
@@ -3832,34 +4948,20 @@ function AssistantBubble({
|
|
|
3832
4948
|
activity,
|
|
3833
4949
|
imageOrigins,
|
|
3834
4950
|
productNames,
|
|
4951
|
+
speechLang,
|
|
3835
4952
|
onNavigate,
|
|
3836
4953
|
anchorRef,
|
|
3837
4954
|
onFollow,
|
|
3838
4955
|
onFollowUp,
|
|
3839
|
-
onFeedback
|
|
4956
|
+
onFeedback,
|
|
4957
|
+
onEscalate,
|
|
4958
|
+
isLatestAnswer = false
|
|
3840
4959
|
}) {
|
|
3841
4960
|
const streaming = message.status === "PENDING" || message.status === "RUNNING";
|
|
3842
4961
|
const body = message.content || message.partialContent || "";
|
|
3843
4962
|
const failed = message.status === "FAILED";
|
|
3844
4963
|
const completed = message.status === "COMPLETED";
|
|
3845
|
-
const
|
|
3846
|
-
() => /* @__PURE__ */ new Set()
|
|
3847
|
-
);
|
|
3848
|
-
const markImageFailed = React.useCallback((src) => {
|
|
3849
|
-
setFailedImages(
|
|
3850
|
-
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
3851
|
-
);
|
|
3852
|
-
}, []);
|
|
3853
|
-
const imageScope = React.useMemo(
|
|
3854
|
-
() => ({
|
|
3855
|
-
strings: T,
|
|
3856
|
-
enabled: completed,
|
|
3857
|
-
origins: imageOrigins,
|
|
3858
|
-
failed: failedImages,
|
|
3859
|
-
onFailed: markImageFailed
|
|
3860
|
-
}),
|
|
3861
|
-
[T, completed, imageOrigins, failedImages, markImageFailed]
|
|
3862
|
-
);
|
|
4964
|
+
const visible = useProgressiveReveal(body, !streaming);
|
|
3863
4965
|
const provenance = React.useMemo(
|
|
3864
4966
|
() => summariseReferences(message.references, imageOrigins, productNames),
|
|
3865
4967
|
[message.references, imageOrigins, productNames]
|
|
@@ -3868,7 +4970,25 @@ function AssistantBubble({
|
|
|
3868
4970
|
() => answerPlainText(message.content),
|
|
3869
4971
|
[message.content]
|
|
3870
4972
|
);
|
|
4973
|
+
const speechText = React.useMemo(
|
|
4974
|
+
() => answerSpeechText(message.content),
|
|
4975
|
+
[message.content]
|
|
4976
|
+
);
|
|
3871
4977
|
const grounded = completed && message.answered !== false;
|
|
4978
|
+
const [escalateOpen, setEscalateOpen] = React.useState(false);
|
|
4979
|
+
const [escalateBusy, setEscalateBusy] = React.useState(false);
|
|
4980
|
+
const escalateTriggerRef = React.useRef(null);
|
|
4981
|
+
const escalateFormId = `boff-explore-escalate-${message.id}`;
|
|
4982
|
+
const unanswered = completed && message.answered === false;
|
|
4983
|
+
const offerEscalate = Boolean(onEscalate) && completed;
|
|
4984
|
+
const showQuietEscalate = offerEscalate && !unanswered && isLatestAnswer;
|
|
4985
|
+
const closeEscalate = React.useCallback(() => {
|
|
4986
|
+
setEscalateOpen(false);
|
|
4987
|
+
escalateTriggerRef.current?.focus();
|
|
4988
|
+
}, []);
|
|
4989
|
+
const handleEscalateBusy = React.useCallback((busy) => {
|
|
4990
|
+
setEscalateBusy(busy);
|
|
4991
|
+
}, []);
|
|
3872
4992
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3873
4993
|
"div",
|
|
3874
4994
|
{
|
|
@@ -3886,7 +5006,7 @@ function AssistantBubble({
|
|
|
3886
5006
|
}
|
|
3887
5007
|
),
|
|
3888
5008
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
|
|
3889
|
-
|
|
5009
|
+
unanswered ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3890
5010
|
"div",
|
|
3891
5011
|
{
|
|
3892
5012
|
"data-boff-explore": "no-answer",
|
|
@@ -3896,26 +5016,56 @@ function AssistantBubble({
|
|
|
3896
5016
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
3897
5017
|
T.noAnswer,
|
|
3898
5018
|
" ",
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
5019
|
+
offerEscalate ? (
|
|
5020
|
+
/* The point of the wave: the same words, but they now open a form that
|
|
5021
|
+
sends the conversation we are both looking at, instead of a link to a
|
|
5022
|
+
blank contact page that asks the visitor to type it all again. */
|
|
5023
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5024
|
+
"button",
|
|
5025
|
+
{
|
|
5026
|
+
ref: escalateTriggerRef,
|
|
5027
|
+
"data-boff-explore": "escalate-open",
|
|
5028
|
+
type: "button",
|
|
5029
|
+
"aria-expanded": escalateOpen,
|
|
5030
|
+
"aria-controls": escalateOpen ? escalateFormId : void 0,
|
|
5031
|
+
onClick: () => setEscalateOpen(true),
|
|
5032
|
+
className: "rounded-sm underline underline-offset-2 hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
5033
|
+
children: T.askHuman
|
|
5034
|
+
}
|
|
5035
|
+
)
|
|
5036
|
+
) : (
|
|
5037
|
+
/* No conversation to hand over (a shared, read-only view). The old dead end
|
|
5038
|
+
is still better than a control whose submit could only fail. */
|
|
5039
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5040
|
+
"a",
|
|
5041
|
+
{
|
|
5042
|
+
href: "/contact",
|
|
5043
|
+
className: "underline underline-offset-2 hover:text-primary",
|
|
5044
|
+
onClick: (event) => {
|
|
5045
|
+
if (!onNavigate) return;
|
|
5046
|
+
event.preventDefault();
|
|
5047
|
+
onNavigate("/contact");
|
|
5048
|
+
},
|
|
5049
|
+
children: T.askHuman
|
|
5050
|
+
}
|
|
5051
|
+
)
|
|
3911
5052
|
),
|
|
3912
5053
|
"."
|
|
3913
5054
|
] })
|
|
3914
5055
|
]
|
|
3915
5056
|
}
|
|
3916
5057
|
) : null,
|
|
3917
|
-
|
|
3918
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5058
|
+
visible ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
|
|
5059
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5060
|
+
ExploreAnswerBody,
|
|
5061
|
+
{
|
|
5062
|
+
strings: T,
|
|
5063
|
+
imageOrigins,
|
|
5064
|
+
enabled: completed,
|
|
5065
|
+
className: "boff-prose-sm text-card-foreground",
|
|
5066
|
+
children: visible
|
|
5067
|
+
}
|
|
5068
|
+
),
|
|
3919
5069
|
streaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
|
|
3920
5070
|
] }) : null,
|
|
3921
5071
|
grounded && provenance.total > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ProvenanceChips, { strings: T, provenance }) : null,
|
|
@@ -3958,7 +5108,15 @@ function AssistantBubble({
|
|
|
3958
5108
|
question
|
|
3959
5109
|
)) })
|
|
3960
5110
|
] }) : null,
|
|
3961
|
-
completed && copyText !== "" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5111
|
+
completed && copyText !== "" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5112
|
+
AnswerActions,
|
|
5113
|
+
{
|
|
5114
|
+
strings: T,
|
|
5115
|
+
text: copyText,
|
|
5116
|
+
speechText,
|
|
5117
|
+
speechLang
|
|
5118
|
+
}
|
|
5119
|
+
) : null,
|
|
3962
5120
|
completed && onFeedback ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3963
5121
|
AnswerFeedback,
|
|
3964
5122
|
{
|
|
@@ -3967,6 +5125,30 @@ function AssistantBubble({
|
|
|
3967
5125
|
feedback: message.feedback,
|
|
3968
5126
|
onSubmit: onFeedback
|
|
3969
5127
|
}
|
|
5128
|
+
) : null,
|
|
5129
|
+
showQuietEscalate ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5130
|
+
"button",
|
|
5131
|
+
{
|
|
5132
|
+
ref: escalateTriggerRef,
|
|
5133
|
+
"data-boff-explore": "escalate-open",
|
|
5134
|
+
type: "button",
|
|
5135
|
+
"aria-expanded": escalateOpen,
|
|
5136
|
+
"aria-controls": escalateOpen ? escalateFormId : void 0,
|
|
5137
|
+
disabled: escalateBusy,
|
|
5138
|
+
onClick: () => setEscalateOpen((open) => !open),
|
|
5139
|
+
className: "rounded-sm text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
5140
|
+
children: T.escalateOpen
|
|
5141
|
+
}
|
|
5142
|
+
) }) : null,
|
|
5143
|
+
escalateOpen && onEscalate ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5144
|
+
EscalateForm,
|
|
5145
|
+
{
|
|
5146
|
+
strings: T,
|
|
5147
|
+
formId: escalateFormId,
|
|
5148
|
+
onEscalate,
|
|
5149
|
+
onClose: closeEscalate,
|
|
5150
|
+
onBusyChange: handleEscalateBusy
|
|
5151
|
+
}
|
|
3970
5152
|
) : null
|
|
3971
5153
|
] })
|
|
3972
5154
|
]
|
|
@@ -4406,15 +5588,6 @@ function WelcomeState({
|
|
|
4406
5588
|
}
|
|
4407
5589
|
)
|
|
4408
5590
|
] }) : null,
|
|
4409
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4410
|
-
ConceptStrip,
|
|
4411
|
-
{
|
|
4412
|
-
strings: T,
|
|
4413
|
-
highlights: conceptHighlights,
|
|
4414
|
-
onAsk: onPrompt,
|
|
4415
|
-
disabled
|
|
4416
|
-
}
|
|
4417
|
-
),
|
|
4418
5591
|
products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "boff-explore-rise boff-explore-rise-3 mt-6", children: [
|
|
4419
5592
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: T.focusProduct }),
|
|
4420
5593
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4426,7 +5599,16 @@ function WelcomeState({
|
|
|
4426
5599
|
onFocus
|
|
4427
5600
|
}
|
|
4428
5601
|
)
|
|
4429
|
-
] }) : null
|
|
5602
|
+
] }) : null,
|
|
5603
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5604
|
+
ConceptStrip,
|
|
5605
|
+
{
|
|
5606
|
+
strings: T,
|
|
5607
|
+
highlights: conceptHighlights,
|
|
5608
|
+
onAsk: onPrompt,
|
|
5609
|
+
disabled
|
|
5610
|
+
}
|
|
5611
|
+
)
|
|
4430
5612
|
] });
|
|
4431
5613
|
}
|
|
4432
5614
|
function ExplorePage({
|
|
@@ -4452,6 +5634,7 @@ function ExplorePage({
|
|
|
4452
5634
|
);
|
|
4453
5635
|
const T = React.useMemo(() => exploreStrings(activeLocale), [activeLocale]);
|
|
4454
5636
|
const rtl = isRtlLocale(activeLocale);
|
|
5637
|
+
const speechLang = speechLocaleOf(activeLocale);
|
|
4455
5638
|
const {
|
|
4456
5639
|
phase,
|
|
4457
5640
|
catalog,
|
|
@@ -4472,6 +5655,8 @@ function ExplorePage({
|
|
|
4472
5655
|
clearHistory,
|
|
4473
5656
|
recordClick,
|
|
4474
5657
|
submitFeedback,
|
|
5658
|
+
escalate,
|
|
5659
|
+
canEscalate,
|
|
4475
5660
|
createShareLink,
|
|
4476
5661
|
isSharedView
|
|
4477
5662
|
} = useExploreChat({
|
|
@@ -4556,11 +5741,11 @@ function ExplorePage({
|
|
|
4556
5741
|
const layoutHeight = window.innerHeight;
|
|
4557
5742
|
if (layoutHeight === 0) return;
|
|
4558
5743
|
const top = Math.round(
|
|
4559
|
-
|
|
5744
|
+
clamp2(el.getBoundingClientRect().top, 0, layoutHeight)
|
|
4560
5745
|
);
|
|
4561
5746
|
const viewport = window.visualViewport;
|
|
4562
5747
|
const inset = viewport ? Math.round(
|
|
4563
|
-
|
|
5748
|
+
clamp2(
|
|
4564
5749
|
layoutHeight - viewport.height - viewport.offsetTop,
|
|
4565
5750
|
0,
|
|
4566
5751
|
layoutHeight
|
|
@@ -4841,6 +6026,7 @@ function ExplorePage({
|
|
|
4841
6026
|
const text = draft.trim();
|
|
4842
6027
|
if (!text || overLimit || busy || !canSend) return;
|
|
4843
6028
|
stopDictation();
|
|
6029
|
+
stopSpeaking();
|
|
4844
6030
|
setDraft("");
|
|
4845
6031
|
stickToBottomRef.current = true;
|
|
4846
6032
|
void send(text, toPayload(stagedRef.current));
|
|
@@ -4851,6 +6037,7 @@ function ExplorePage({
|
|
|
4851
6037
|
const sendPrompt = React.useCallback(
|
|
4852
6038
|
(prompt) => {
|
|
4853
6039
|
if (!canSend || busy) return;
|
|
6040
|
+
stopSpeaking();
|
|
4854
6041
|
setDraft("");
|
|
4855
6042
|
stickToBottomRef.current = true;
|
|
4856
6043
|
void send(prompt, toPayload(stagedRef.current));
|
|
@@ -4913,6 +6100,7 @@ function ExplorePage({
|
|
|
4913
6100
|
),
|
|
4914
6101
|
/* @__PURE__ */ jsxRuntime.jsx(ProseStyles, {}),
|
|
4915
6102
|
/* @__PURE__ */ jsxRuntime.jsx(ExploreStyles, {}),
|
|
6103
|
+
/* @__PURE__ */ jsxRuntime.jsx(ExploreAnswerStyles, {}),
|
|
4916
6104
|
!showWelcome ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 border-b border-border px-4 py-2.5", children: [
|
|
4917
6105
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4918
6106
|
"span",
|
|
@@ -5086,11 +6274,14 @@ function ExplorePage({
|
|
|
5086
6274
|
activity,
|
|
5087
6275
|
imageOrigins,
|
|
5088
6276
|
productNames,
|
|
6277
|
+
speechLang,
|
|
5089
6278
|
onNavigate,
|
|
5090
6279
|
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
|
|
5091
6280
|
onFollow: (kind, url) => recordClick(message.id, kind, url),
|
|
5092
6281
|
onFollowUp: sendPrompt,
|
|
5093
|
-
onFeedback: isSharedView ? void 0 : (rating, detail) => submitFeedback(message.id, rating, detail)
|
|
6282
|
+
onFeedback: isSharedView ? void 0 : (rating, detail) => submitFeedback(message.id, rating, detail),
|
|
6283
|
+
onEscalate: canEscalate ? escalate : void 0,
|
|
6284
|
+
isLatestAnswer: message.id === latestAssistantId
|
|
5094
6285
|
},
|
|
5095
6286
|
message.id
|
|
5096
6287
|
)
|
|
@@ -5197,10 +6388,19 @@ function ExplorePage({
|
|
|
5197
6388
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5198
6389
|
"div",
|
|
5199
6390
|
{
|
|
6391
|
+
"data-boff-explore": "composer-field",
|
|
5200
6392
|
className: cn(
|
|
5201
6393
|
"flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
|
|
5202
6394
|
busy && "boff-explore-busy",
|
|
5203
|
-
overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
|
|
6395
|
+
overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring",
|
|
6396
|
+
// The halo is an invitation, and an invitation is only useful before it has
|
|
6397
|
+
// been accepted: while the conversation is empty, the composer is usable, and
|
|
6398
|
+
// nothing has been typed. Once any of those stops being true the class goes
|
|
6399
|
+
// and the box is an ordinary card again. `-quiet` keeps the ring but stops the
|
|
6400
|
+
// movement the moment there are characters in the field, so it does not pulse
|
|
6401
|
+
// at someone who is mid-sentence.
|
|
6402
|
+
showWelcome && !composerDisabled && !overLimit && "boff-explore-invite",
|
|
6403
|
+
showWelcome && draft.length > 0 && "boff-explore-invite-quiet"
|
|
5204
6404
|
),
|
|
5205
6405
|
children: [
|
|
5206
6406
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -5390,6 +6590,7 @@ function ExplorePage({
|
|
|
5390
6590
|
|
|
5391
6591
|
exports.ExplorePage = ExplorePage;
|
|
5392
6592
|
exports.answerPlainText = answerPlainText;
|
|
6593
|
+
exports.answerSpeechText = answerSpeechText;
|
|
5393
6594
|
exports.inlineImageOrigins = inlineImageOrigins;
|
|
5394
6595
|
exports.isInlineImageAllowed = isInlineImageAllowed;
|
|
5395
6596
|
exports.logoUrlOf = logoUrlOf;
|