@burdenoff/website-sdk 2026.922.5 → 2026.923.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/explore.d.mts +4 -180
- package/dist/components/explore.d.ts +4 -180
- package/dist/components/explore.js +1458 -927
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +1225 -694
- 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 +125 -8
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +125 -8
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +418 -26
- package/dist/index.d.ts +418 -26
- package/dist/index.js +2522 -748
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2499 -750
- package/dist/index.mjs.map +1 -1
- package/dist/{use-progressive-reveal-Ha3gYPfs.d.mts → use-progressive-reveal-Cmy8bA7L.d.mts} +54 -7
- package/dist/{use-progressive-reveal-BfU_IHip.d.ts → use-progressive-reveal-DHPGGxZu.d.ts} +54 -7
- package/dist/utils/index.d.mts +55 -1
- package/dist/utils/index.d.ts +55 -1
- package/dist/utils/index.js +17 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +14 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +3 -2
|
@@ -5,15 +5,481 @@ import ReactMarkdown from 'react-markdown';
|
|
|
5
5
|
import rehypeSanitize from 'rehype-sanitize';
|
|
6
6
|
import remarkGfm from 'remark-gfm';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
+
import { clsx } from 'clsx';
|
|
9
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
10
|
import { Helmet } from 'react-helmet-async';
|
|
9
11
|
import { Slot } from '@radix-ui/react-slot';
|
|
10
12
|
import { cva } from 'class-variance-authority';
|
|
11
|
-
import { clsx } from 'clsx';
|
|
12
|
-
import { twMerge } from 'tailwind-merge';
|
|
13
13
|
|
|
14
14
|
var __defProp = Object.defineProperty;
|
|
15
15
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
16
16
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
17
|
+
function MarkdownLink({
|
|
18
|
+
href,
|
|
19
|
+
children,
|
|
20
|
+
// react-markdown passes the hast node to every override; it is not a DOM attribute.
|
|
21
|
+
node: _node,
|
|
22
|
+
...props
|
|
23
|
+
}) {
|
|
24
|
+
const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
|
|
25
|
+
return /* @__PURE__ */ jsx(
|
|
26
|
+
"a",
|
|
27
|
+
{
|
|
28
|
+
href,
|
|
29
|
+
...props,
|
|
30
|
+
...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
|
|
31
|
+
children
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
var defaultComponents = {
|
|
36
|
+
a: MarkdownLink
|
|
37
|
+
};
|
|
38
|
+
function Markdown({
|
|
39
|
+
children,
|
|
40
|
+
className,
|
|
41
|
+
inline,
|
|
42
|
+
components
|
|
43
|
+
}) {
|
|
44
|
+
const content = /* @__PURE__ */ jsx(
|
|
45
|
+
ReactMarkdown,
|
|
46
|
+
{
|
|
47
|
+
remarkPlugins: [remarkGfm],
|
|
48
|
+
rehypePlugins: [rehypeSanitize],
|
|
49
|
+
components: { ...defaultComponents, ...components },
|
|
50
|
+
...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
|
|
51
|
+
children
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
if (inline) {
|
|
55
|
+
return /* @__PURE__ */ jsx("span", { className, children: content });
|
|
56
|
+
}
|
|
57
|
+
return /* @__PURE__ */ jsx("div", { className, children: content });
|
|
58
|
+
}
|
|
59
|
+
function cn(...inputs) {
|
|
60
|
+
return twMerge(clsx(inputs));
|
|
61
|
+
}
|
|
62
|
+
var EXPLORE_ANSWER_CSS = `
|
|
63
|
+
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
64
|
+
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
65
|
+
WHOLE document horizontally scrollable, not just the bubble. */
|
|
66
|
+
.boff-explore-body,
|
|
67
|
+
.boff-explore-body p,
|
|
68
|
+
.boff-explore-body li,
|
|
69
|
+
.boff-explore-body a,
|
|
70
|
+
.boff-explore-body h1,
|
|
71
|
+
.boff-explore-body h2,
|
|
72
|
+
.boff-explore-body h3,
|
|
73
|
+
/* h4-h6 as well: a published answer page pushes the model's own headings down a level or
|
|
74
|
+
two so they nest under the page's, and a long unbroken word is no narrower for it. */
|
|
75
|
+
.boff-explore-body h4,
|
|
76
|
+
.boff-explore-body h5,
|
|
77
|
+
.boff-explore-body h6 {
|
|
78
|
+
overflow-wrap: anywhere;
|
|
79
|
+
word-break: break-word;
|
|
80
|
+
}
|
|
81
|
+
.boff-explore-body pre {
|
|
82
|
+
overflow-x: auto;
|
|
83
|
+
max-width: 100%;
|
|
84
|
+
}
|
|
85
|
+
.boff-explore-body table {
|
|
86
|
+
display: block;
|
|
87
|
+
overflow-x: auto;
|
|
88
|
+
max-width: 100%;
|
|
89
|
+
}
|
|
90
|
+
.boff-explore-body img {
|
|
91
|
+
max-width: 100%;
|
|
92
|
+
height: auto;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ---- Comparison and pricing tables ----------------------------------------------------
|
|
96
|
+
The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
|
|
97
|
+
the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
|
|
98
|
+
the page must not scroll sideways because of it, and it has to be readable on a 320px
|
|
99
|
+
phone, which a four-column grid of 1px borders is not.
|
|
100
|
+
|
|
101
|
+
Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
|
|
102
|
+
\`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
|
|
103
|
+
cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
|
|
104
|
+
of table from chaining to the page \u2014 the same rule the transcript needed. */
|
|
105
|
+
.boff-explore-body .boff-explore-table {
|
|
106
|
+
max-width: 100%;
|
|
107
|
+
overflow-x: auto;
|
|
108
|
+
overscroll-behavior-x: contain;
|
|
109
|
+
margin: 0.75rem 0;
|
|
110
|
+
border: 1px solid hsl(var(--border));
|
|
111
|
+
border-radius: 0.75rem;
|
|
112
|
+
background-color: hsl(var(--card));
|
|
113
|
+
}
|
|
114
|
+
.boff-explore-body .boff-explore-table:focus-visible {
|
|
115
|
+
outline: 2px solid hsl(var(--ring));
|
|
116
|
+
outline-offset: 2px;
|
|
117
|
+
}
|
|
118
|
+
.boff-explore-body .boff-explore-table table {
|
|
119
|
+
/* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
|
|
120
|
+
display: table;
|
|
121
|
+
width: 100%;
|
|
122
|
+
max-width: none;
|
|
123
|
+
margin: 0;
|
|
124
|
+
border-collapse: collapse;
|
|
125
|
+
font-size: 0.8125rem;
|
|
126
|
+
}
|
|
127
|
+
.boff-explore-body .boff-explore-table th,
|
|
128
|
+
.boff-explore-body .boff-explore-table td {
|
|
129
|
+
border: 0;
|
|
130
|
+
border-top: 1px solid hsl(var(--border));
|
|
131
|
+
padding: 0.5rem 0.75rem;
|
|
132
|
+
/* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
|
|
133
|
+
table there reads against the grain of every other line on the page. */
|
|
134
|
+
text-align: start;
|
|
135
|
+
vertical-align: top;
|
|
136
|
+
overflow-wrap: anywhere;
|
|
137
|
+
/* Prices in a column line up under each other instead of drifting by digit width. */
|
|
138
|
+
font-variant-numeric: tabular-nums;
|
|
139
|
+
min-width: 6rem;
|
|
140
|
+
}
|
|
141
|
+
.boff-explore-body .boff-explore-table thead th {
|
|
142
|
+
border-top: 0;
|
|
143
|
+
background-color: hsl(var(--muted));
|
|
144
|
+
color: hsl(var(--foreground));
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
white-space: nowrap;
|
|
147
|
+
}
|
|
148
|
+
/* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
|
|
149
|
+
the row's heading is what makes a price list scannable rather than a grid of strings. */
|
|
150
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
151
|
+
font-weight: 600;
|
|
152
|
+
color: hsl(var(--card-foreground));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Narrow: one card per row. Each cell prints its own column heading, so a value is never
|
|
156
|
+
stranded without the question it answers, and the subject cell becomes the card's title.
|
|
157
|
+
A media query rather than a measured width \u2014 these pages are serialised post-hydration,
|
|
158
|
+
so anything measured here would ship to every phone as static HTML. */
|
|
159
|
+
@media (max-width: 30rem) {
|
|
160
|
+
.boff-explore-body .boff-explore-table {
|
|
161
|
+
overflow-x: visible;
|
|
162
|
+
}
|
|
163
|
+
.boff-explore-body .boff-explore-table table,
|
|
164
|
+
.boff-explore-body .boff-explore-table tbody,
|
|
165
|
+
.boff-explore-body .boff-explore-table tr,
|
|
166
|
+
.boff-explore-body .boff-explore-table td {
|
|
167
|
+
display: block;
|
|
168
|
+
width: 100%;
|
|
169
|
+
min-width: 0;
|
|
170
|
+
}
|
|
171
|
+
.boff-explore-body .boff-explore-table thead {
|
|
172
|
+
display: none;
|
|
173
|
+
}
|
|
174
|
+
.boff-explore-body .boff-explore-table tr {
|
|
175
|
+
border-top: 1px solid hsl(var(--border));
|
|
176
|
+
padding: 0.5rem 0;
|
|
177
|
+
}
|
|
178
|
+
.boff-explore-body .boff-explore-table tr:first-child {
|
|
179
|
+
border-top: 0;
|
|
180
|
+
}
|
|
181
|
+
.boff-explore-body .boff-explore-table td {
|
|
182
|
+
border: 0;
|
|
183
|
+
padding: 0.125rem 0.75rem;
|
|
184
|
+
}
|
|
185
|
+
.boff-explore-body .boff-explore-table td[data-label]::before {
|
|
186
|
+
content: attr(data-label);
|
|
187
|
+
display: block;
|
|
188
|
+
font-size: 0.6875rem;
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
text-transform: uppercase;
|
|
191
|
+
letter-spacing: 0.04em;
|
|
192
|
+
color: hsl(var(--muted-foreground));
|
|
193
|
+
}
|
|
194
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
195
|
+
font-size: 0.9375rem;
|
|
196
|
+
padding-bottom: 0.25rem;
|
|
197
|
+
}
|
|
198
|
+
.boff-explore-body .boff-explore-table tbody td:first-child::before {
|
|
199
|
+
/* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
|
|
200
|
+
display: none;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
`;
|
|
204
|
+
function ExploreAnswerStyles() {
|
|
205
|
+
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_ANSWER_CSS } });
|
|
206
|
+
}
|
|
207
|
+
function inlineImageOrigins(products) {
|
|
208
|
+
const origins = /* @__PURE__ */ new Set();
|
|
209
|
+
for (const product of products) {
|
|
210
|
+
let url;
|
|
211
|
+
try {
|
|
212
|
+
url = new URL(product.website);
|
|
213
|
+
} catch {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (url.protocol !== "https:") continue;
|
|
217
|
+
const apex = url.hostname.replace(/^www\./, "");
|
|
218
|
+
const port = url.port ? `:${url.port}` : "";
|
|
219
|
+
origins.add(`https://${apex}${port}`);
|
|
220
|
+
origins.add(`https://www.${apex}${port}`);
|
|
221
|
+
}
|
|
222
|
+
return origins;
|
|
223
|
+
}
|
|
224
|
+
function isInlineImageAllowed(src, origins) {
|
|
225
|
+
if (typeof src !== "string" || src === "") return false;
|
|
226
|
+
let url;
|
|
227
|
+
try {
|
|
228
|
+
url = new URL(src);
|
|
229
|
+
} catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
if (url.protocol !== "https:" || url.username || url.password) return false;
|
|
233
|
+
return origins.has(url.origin);
|
|
234
|
+
}
|
|
235
|
+
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
236
|
+
var TABLE_ROW_LINE = /^\s{0,3}\|/;
|
|
237
|
+
var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
|
|
238
|
+
function readableLine(raw, mode) {
|
|
239
|
+
let line = raw;
|
|
240
|
+
line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
|
|
241
|
+
line = mode === "speech" ? (
|
|
242
|
+
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
243
|
+
// prompt asks for two to five links in every answer. The label IS the words.
|
|
244
|
+
line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
|
|
245
|
+
) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
|
|
246
|
+
line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
247
|
+
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
248
|
+
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
249
|
+
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
250
|
+
line = line.replace(/`([^`]+)`/g, "$1");
|
|
251
|
+
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
252
|
+
line = line.replace(/__([^_]+)__/g, "$1");
|
|
253
|
+
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
254
|
+
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
255
|
+
return line.replace(/[ \t]+$/, "");
|
|
256
|
+
}
|
|
257
|
+
function tableCells(row) {
|
|
258
|
+
const trimmed = row.trim();
|
|
259
|
+
const cells = [];
|
|
260
|
+
let cell = "";
|
|
261
|
+
for (let at = 0; at < trimmed.length; at += 1) {
|
|
262
|
+
const char = trimmed[at] ?? "";
|
|
263
|
+
if (char === "\\") {
|
|
264
|
+
cell += char + (trimmed[at + 1] ?? "");
|
|
265
|
+
at += 1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (char === "|") {
|
|
269
|
+
cells.push(cell.trim());
|
|
270
|
+
cell = "";
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
cell += char;
|
|
274
|
+
}
|
|
275
|
+
cells.push(cell.trim());
|
|
276
|
+
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
277
|
+
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
278
|
+
return cells;
|
|
279
|
+
}
|
|
280
|
+
function asSentence(text) {
|
|
281
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
282
|
+
}
|
|
283
|
+
function spokenTable(rows) {
|
|
284
|
+
if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
|
|
285
|
+
const labels = tableCells(rows[0] ?? "");
|
|
286
|
+
const spoken = [];
|
|
287
|
+
for (const row of rows.slice(2)) {
|
|
288
|
+
const parts = [];
|
|
289
|
+
tableCells(row).forEach((value, column) => {
|
|
290
|
+
if (value === "") return;
|
|
291
|
+
const label = column === 0 ? "" : labels[column] ?? "";
|
|
292
|
+
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
293
|
+
});
|
|
294
|
+
if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
|
|
295
|
+
}
|
|
296
|
+
return spoken;
|
|
297
|
+
}
|
|
298
|
+
function readableText(markdown, mode) {
|
|
299
|
+
const lines = [];
|
|
300
|
+
let inFence = false;
|
|
301
|
+
let table = [];
|
|
302
|
+
const flushTable = () => {
|
|
303
|
+
if (table.length === 0) return;
|
|
304
|
+
for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
|
|
305
|
+
table = [];
|
|
306
|
+
};
|
|
307
|
+
for (const raw of markdown.split("\n")) {
|
|
308
|
+
if (CODE_FENCE.test(raw)) {
|
|
309
|
+
flushTable();
|
|
310
|
+
inFence = !inFence;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (inFence) {
|
|
314
|
+
lines.push(raw);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
|
|
318
|
+
table.push(raw);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
flushTable();
|
|
322
|
+
lines.push(readableLine(raw, mode));
|
|
323
|
+
}
|
|
324
|
+
flushTable();
|
|
325
|
+
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
326
|
+
}
|
|
327
|
+
function answerPlainText(markdown) {
|
|
328
|
+
return readableText(markdown, "clipboard");
|
|
329
|
+
}
|
|
330
|
+
function answerSpeechText(markdown) {
|
|
331
|
+
return readableText(markdown, "speech");
|
|
332
|
+
}
|
|
333
|
+
var AnswerScopeContext = createContext(null);
|
|
334
|
+
var InsideLinkContext = createContext(false);
|
|
335
|
+
function AnswerLink(props) {
|
|
336
|
+
return /* @__PURE__ */ jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsx(MarkdownLink, { ...props }) });
|
|
337
|
+
}
|
|
338
|
+
var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
|
|
339
|
+
function InlineConceptImage({ src, alt }) {
|
|
340
|
+
const scope = useContext(AnswerScopeContext);
|
|
341
|
+
const insideLink = useContext(InsideLinkContext);
|
|
342
|
+
if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const { strings: T, onFailed } = scope;
|
|
346
|
+
const picture = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
347
|
+
/* @__PURE__ */ jsx(
|
|
348
|
+
"img",
|
|
349
|
+
{
|
|
350
|
+
src,
|
|
351
|
+
alt: alt ?? "",
|
|
352
|
+
loading: "lazy",
|
|
353
|
+
decoding: "async",
|
|
354
|
+
onError: () => onFailed(src),
|
|
355
|
+
className: "block h-auto max-h-80 w-auto max-w-full object-contain"
|
|
356
|
+
}
|
|
357
|
+
),
|
|
358
|
+
/* @__PURE__ */ jsx(
|
|
359
|
+
"span",
|
|
360
|
+
{
|
|
361
|
+
"aria-hidden": "true",
|
|
362
|
+
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",
|
|
363
|
+
children: T.conceptIllustration
|
|
364
|
+
}
|
|
365
|
+
)
|
|
366
|
+
] });
|
|
367
|
+
return /* @__PURE__ */ jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxs(
|
|
368
|
+
"a",
|
|
369
|
+
{
|
|
370
|
+
href: src,
|
|
371
|
+
target: "_blank",
|
|
372
|
+
rel: "noopener noreferrer",
|
|
373
|
+
className: cn(
|
|
374
|
+
INLINE_IMAGE_FRAME,
|
|
375
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
376
|
+
),
|
|
377
|
+
children: [
|
|
378
|
+
picture,
|
|
379
|
+
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
380
|
+
"(",
|
|
381
|
+
T.opensInNewTab,
|
|
382
|
+
")"
|
|
383
|
+
] })
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
) });
|
|
387
|
+
}
|
|
388
|
+
function markdownText(node) {
|
|
389
|
+
if (!node) return "";
|
|
390
|
+
if (typeof node.value === "string") return node.value;
|
|
391
|
+
return (node.children ?? []).map(markdownText).join("");
|
|
392
|
+
}
|
|
393
|
+
function hasHeaderCells(node) {
|
|
394
|
+
return (node?.children ?? []).some((cell) => cell.tagName === "th");
|
|
395
|
+
}
|
|
396
|
+
function findHeaderRow(node) {
|
|
397
|
+
for (const child of node?.children ?? []) {
|
|
398
|
+
if (child.tagName === "tr" && hasHeaderCells(child)) return child;
|
|
399
|
+
const nested = findHeaderRow(child);
|
|
400
|
+
if (nested !== null) return nested;
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
function tableColumnLabels(node) {
|
|
405
|
+
const row = findHeaderRow(node);
|
|
406
|
+
if (row === null) return [];
|
|
407
|
+
return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
|
|
408
|
+
}
|
|
409
|
+
var TableColumnsContext = createContext([]);
|
|
410
|
+
function AnswerTable({ node, children }) {
|
|
411
|
+
const scope = useContext(AnswerScopeContext);
|
|
412
|
+
const table = /* @__PURE__ */ jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsx("table", { children }) });
|
|
413
|
+
if (!scope) return table;
|
|
414
|
+
return /* @__PURE__ */ jsx(
|
|
415
|
+
"div",
|
|
416
|
+
{
|
|
417
|
+
"data-boff-explore": "table",
|
|
418
|
+
role: "region",
|
|
419
|
+
"aria-label": scope.strings.answerTable,
|
|
420
|
+
tabIndex: 0,
|
|
421
|
+
className: "boff-explore-table",
|
|
422
|
+
children: table
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
function AnswerTableRow({
|
|
427
|
+
node,
|
|
428
|
+
children,
|
|
429
|
+
...props
|
|
430
|
+
}) {
|
|
431
|
+
const labels = useContext(TableColumnsContext);
|
|
432
|
+
if (labels.length === 0 || hasHeaderCells(node)) {
|
|
433
|
+
return /* @__PURE__ */ jsx("tr", { ...props, children });
|
|
434
|
+
}
|
|
435
|
+
let column = 0;
|
|
436
|
+
const cells = Children.map(children, (cell) => {
|
|
437
|
+
if (!isValidElement(cell)) return cell;
|
|
438
|
+
const label = labels[column] ?? "";
|
|
439
|
+
column += 1;
|
|
440
|
+
if (label === "") return cell;
|
|
441
|
+
return cloneElement(cell, {
|
|
442
|
+
"data-label": label
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
return /* @__PURE__ */ jsx("tr", { ...props, children: cells });
|
|
446
|
+
}
|
|
447
|
+
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
448
|
+
a: AnswerLink,
|
|
449
|
+
img: InlineConceptImage,
|
|
450
|
+
table: AnswerTable,
|
|
451
|
+
tr: AnswerTableRow
|
|
452
|
+
};
|
|
453
|
+
function ExploreAnswerBody({
|
|
454
|
+
children,
|
|
455
|
+
strings,
|
|
456
|
+
imageOrigins,
|
|
457
|
+
enabled = true,
|
|
458
|
+
components,
|
|
459
|
+
className
|
|
460
|
+
}) {
|
|
461
|
+
const [failed, setFailed] = useState(() => /* @__PURE__ */ new Set());
|
|
462
|
+
const onFailed = useCallback((src) => {
|
|
463
|
+
setFailed(
|
|
464
|
+
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
465
|
+
);
|
|
466
|
+
}, []);
|
|
467
|
+
const scope = useMemo(
|
|
468
|
+
() => ({
|
|
469
|
+
strings,
|
|
470
|
+
enabled,
|
|
471
|
+
origins: imageOrigins,
|
|
472
|
+
failed,
|
|
473
|
+
onFailed
|
|
474
|
+
}),
|
|
475
|
+
[strings, enabled, imageOrigins, failed, onFailed]
|
|
476
|
+
);
|
|
477
|
+
const overrides = useMemo(
|
|
478
|
+
() => components ? { ...ANSWER_MARKDOWN_COMPONENTS, ...components } : ANSWER_MARKDOWN_COMPONENTS,
|
|
479
|
+
[components]
|
|
480
|
+
);
|
|
481
|
+
return /* @__PURE__ */ jsx("div", { className: cn("boff-prose boff-explore-body min-w-0", className), children: /* @__PURE__ */ jsx(AnswerScopeContext.Provider, { value: scope, children: /* @__PURE__ */ jsx(Markdown, { components: overrides, children }) }) });
|
|
482
|
+
}
|
|
17
483
|
|
|
18
484
|
// src/components/explore-i18n.ts
|
|
19
485
|
var EXPLORE_LOCALES = ["en", "zh", "hi", "es", "ar", "ta"];
|
|
@@ -32,7 +498,7 @@ var EN = {
|
|
|
32
498
|
showLess: "Show less",
|
|
33
499
|
sources: "Sources",
|
|
34
500
|
nextYouCouldAsk: "Next you could ask",
|
|
35
|
-
placeholder: "Describe the problem
|
|
501
|
+
placeholder: "Describe the problem\u2026",
|
|
36
502
|
placeholderProduct: "Describe a problem, or ask about {product}\u2026",
|
|
37
503
|
send: "Send",
|
|
38
504
|
stop: "Stop",
|
|
@@ -73,6 +539,20 @@ var EN = {
|
|
|
73
539
|
feedbackDismiss: "Dismiss",
|
|
74
540
|
feedbackThanks: "Thanks \u2014 that helps.",
|
|
75
541
|
feedbackNotSent: "That didn't send.",
|
|
542
|
+
escalateOpen: "Talk to a person",
|
|
543
|
+
escalateTitle: "Send this conversation to a person",
|
|
544
|
+
escalateBody: "We'll pass the whole conversation on, so you don't have to type any of it again.",
|
|
545
|
+
escalateEmailLabel: "Your email address",
|
|
546
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
547
|
+
escalateNoteLabel: "Anything else a person should know? (optional)",
|
|
548
|
+
escalateSubmit: "Send to a person",
|
|
549
|
+
escalateSending: "Sending\u2026",
|
|
550
|
+
escalateCancel: "Cancel",
|
|
551
|
+
escalateInvalidEmail: "That doesn't look like an address we can reply to. Please check it.",
|
|
552
|
+
escalateSent: "A person now has this conversation. We'll reply to {email}.",
|
|
553
|
+
escalateRateLimited: "That's a lot of requests in a short time. Please try again in a few minutes.",
|
|
554
|
+
escalateCaptcha: "We couldn't verify that you're human. Please try that again.",
|
|
555
|
+
escalateFailed: "We couldn't pass that on just now. Please try again.",
|
|
76
556
|
prevSuggestion: "Previous suggestion",
|
|
77
557
|
nextSuggestion: "Next suggestion",
|
|
78
558
|
conceptIllustration: "Concept illustration",
|
|
@@ -120,7 +600,7 @@ var ZH = {
|
|
|
120
600
|
showLess: "\u6536\u8D77",
|
|
121
601
|
sources: "\u6765\u6E90",
|
|
122
602
|
nextYouCouldAsk: "\u4F60\u8FD8\u53EF\u4EE5\u95EE",
|
|
123
|
-
placeholder: "\u63CF\u8FF0\u4F60\
|
|
603
|
+
placeholder: "\u63CF\u8FF0\u4F60\u7684\u95EE\u9898\u2026",
|
|
124
604
|
placeholderProduct: "\u63CF\u8FF0\u95EE\u9898\uFF0C\u6216\u8BE2\u95EE {product}\u2026",
|
|
125
605
|
send: "\u53D1\u9001",
|
|
126
606
|
stop: "\u505C\u6B62",
|
|
@@ -158,6 +638,20 @@ var ZH = {
|
|
|
158
638
|
feedbackDismiss: "\u5173\u95ED",
|
|
159
639
|
feedbackThanks: "\u8C22\u8C22\uFF0C\u8FD9\u5BF9\u6211\u4EEC\u5F88\u6709\u5E2E\u52A9\u3002",
|
|
160
640
|
feedbackNotSent: "\u6CA1\u6709\u63D0\u4EA4\u6210\u529F\u3002",
|
|
641
|
+
escalateOpen: "\u8054\u7CFB\u771F\u4EBA",
|
|
642
|
+
escalateTitle: "\u628A\u8FD9\u6BB5\u5BF9\u8BDD\u8F6C\u7ED9\u771F\u4EBA",
|
|
643
|
+
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",
|
|
644
|
+
escalateEmailLabel: "\u60A8\u7684\u90AE\u7BB1\u5730\u5740",
|
|
645
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
646
|
+
escalateNoteLabel: "\u8FD8\u6709\u4EC0\u4E48\u9700\u8981\u544A\u8BC9\u6211\u4EEC\u7684\u5417\uFF1F\uFF08\u9009\u586B\uFF09",
|
|
647
|
+
escalateSubmit: "\u8F6C\u7ED9\u771F\u4EBA",
|
|
648
|
+
escalateSending: "\u6B63\u5728\u53D1\u9001\u2026",
|
|
649
|
+
escalateCancel: "\u53D6\u6D88",
|
|
650
|
+
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",
|
|
651
|
+
escalateSent: "\u5DF2\u6709\u540C\u4E8B\u63A5\u624B\u8FD9\u6BB5\u5BF9\u8BDD\uFF0C\u6211\u4EEC\u4F1A\u56DE\u590D\u5230 {email}\u3002",
|
|
652
|
+
escalateRateLimited: "\u77ED\u65F6\u95F4\u5185\u8BF7\u6C42\u8FC7\u591A\uFF0C\u8BF7\u51E0\u5206\u949F\u540E\u518D\u8BD5\u3002",
|
|
653
|
+
escalateCaptcha: "\u6211\u4EEC\u65E0\u6CD5\u9A8C\u8BC1\u60A8\u662F\u771F\u4EBA\uFF0C\u8BF7\u518D\u8BD5\u4E00\u6B21\u3002",
|
|
654
|
+
escalateFailed: "\u6682\u65F6\u65E0\u6CD5\u8F6C\u4EA4\uFF0C\u8BF7\u518D\u8BD5\u4E00\u6B21\u3002",
|
|
161
655
|
prevSuggestion: "\u4E0A\u4E00\u4E2A\u5EFA\u8BAE",
|
|
162
656
|
nextSuggestion: "\u4E0B\u4E00\u4E2A\u5EFA\u8BAE",
|
|
163
657
|
conceptIllustration: "\u6982\u5FF5\u793A\u610F\u56FE",
|
|
@@ -201,7 +695,7 @@ var HI = {
|
|
|
201
695
|
showLess: "\u0915\u092E \u0926\u093F\u0916\u093E\u090F\u0901",
|
|
202
696
|
sources: "\u0938\u094D\u0930\u094B\u0924",
|
|
203
697
|
nextYouCouldAsk: "\u0906\u0917\u0947 \u0906\u092A \u092A\u0942\u091B \u0938\u0915\u0924\u0947 \u0939\u0948\u0902",
|
|
204
|
-
placeholder: "\u0905\u092A\u0928\u0940 \u0938\u092E\u0938\u094D\u092F\u093E \
|
|
698
|
+
placeholder: "\u0905\u092A\u0928\u0940 \u0938\u092E\u0938\u094D\u092F\u093E \u092C\u0924\u093E\u090F\u0902\u2026",
|
|
205
699
|
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",
|
|
206
700
|
send: "\u092D\u0947\u091C\u0947\u0902",
|
|
207
701
|
stop: "\u0930\u094B\u0915\u0947\u0902",
|
|
@@ -239,6 +733,20 @@ var HI = {
|
|
|
239
733
|
feedbackDismiss: "\u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
|
|
240
734
|
feedbackThanks: "\u0927\u0928\u094D\u092F\u0935\u093E\u0926, \u0907\u0938\u0938\u0947 \u092E\u0926\u0926 \u092E\u093F\u0932\u0924\u0940 \u0939\u0948\u0964",
|
|
241
735
|
feedbackNotSent: "\u092F\u0939 \u092D\u0947\u091C\u093E \u0928\u0939\u0940\u0902 \u091C\u093E \u0938\u0915\u093E\u0964",
|
|
736
|
+
escalateOpen: "\u0915\u093F\u0938\u0940 \u0935\u094D\u092F\u0915\u094D\u0924\u093F \u0938\u0947 \u092C\u093E\u0924 \u0915\u0930\u0947\u0902",
|
|
737
|
+
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",
|
|
738
|
+
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",
|
|
739
|
+
escalateEmailLabel: "\u0906\u092A\u0915\u093E \u0908\u092E\u0947\u0932 \u092A\u0924\u093E",
|
|
740
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
741
|
+
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)",
|
|
742
|
+
escalateSubmit: "\u0915\u093F\u0938\u0940 \u0935\u094D\u092F\u0915\u094D\u0924\u093F \u0915\u094B \u092D\u0947\u091C\u0947\u0902",
|
|
743
|
+
escalateSending: "\u092D\u0947\u091C\u093E \u091C\u093E \u0930\u0939\u093E \u0939\u0948\u2026",
|
|
744
|
+
escalateCancel: "\u0930\u0926\u094D\u0926 \u0915\u0930\u0947\u0902",
|
|
745
|
+
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",
|
|
746
|
+
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",
|
|
747
|
+
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",
|
|
748
|
+
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",
|
|
749
|
+
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",
|
|
242
750
|
prevSuggestion: "\u092A\u093F\u091B\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
|
|
243
751
|
nextSuggestion: "\u0905\u0917\u0932\u093E \u0938\u0941\u091D\u093E\u0935",
|
|
244
752
|
conceptIllustration: "\u0905\u0935\u0927\u093E\u0930\u0923\u093E \u091A\u093F\u0924\u094D\u0930\u0923",
|
|
@@ -286,7 +794,7 @@ var ES = {
|
|
|
286
794
|
showLess: "Ver menos",
|
|
287
795
|
sources: "Fuentes",
|
|
288
796
|
nextYouCouldAsk: "Tambi\xE9n puedes preguntar",
|
|
289
|
-
placeholder: "Describe el problema
|
|
797
|
+
placeholder: "Describe el problema\u2026",
|
|
290
798
|
placeholderProduct: "Describe un problema o pregunta por {product}\u2026",
|
|
291
799
|
send: "Enviar",
|
|
292
800
|
stop: "Detener",
|
|
@@ -324,6 +832,20 @@ var ES = {
|
|
|
324
832
|
feedbackDismiss: "Cerrar",
|
|
325
833
|
feedbackThanks: "Gracias, nos sirve.",
|
|
326
834
|
feedbackNotSent: "No se ha podido enviar.",
|
|
835
|
+
escalateOpen: "Hablar con una persona",
|
|
836
|
+
escalateTitle: "Env\xEDa esta conversaci\xF3n a una persona",
|
|
837
|
+
escalateBody: "Le pasaremos la conversaci\xF3n entera, as\xED no tienes que volver a escribirla.",
|
|
838
|
+
escalateEmailLabel: "Tu direcci\xF3n de correo",
|
|
839
|
+
escalateEmailPlaceholder: "tu@ejemplo.com",
|
|
840
|
+
escalateNoteLabel: "\xBFAlgo m\xE1s que deber\xEDamos saber? (opcional)",
|
|
841
|
+
escalateSubmit: "Enviar a una persona",
|
|
842
|
+
escalateSending: "Enviando\u2026",
|
|
843
|
+
escalateCancel: "Cancelar",
|
|
844
|
+
escalateInvalidEmail: "No parece una direcci\xF3n a la que podamos responder. Rev\xEDsala, por favor.",
|
|
845
|
+
escalateSent: "Una persona ya tiene esta conversaci\xF3n. Te responderemos a {email}.",
|
|
846
|
+
escalateRateLimited: "Han sido muchas peticiones en poco tiempo. Int\xE9ntalo de nuevo en unos minutos.",
|
|
847
|
+
escalateCaptcha: "No hemos podido verificar que eres una persona. Int\xE9ntalo de nuevo.",
|
|
848
|
+
escalateFailed: "No hemos podido pasarlo ahora mismo. Int\xE9ntalo de nuevo.",
|
|
327
849
|
prevSuggestion: "Sugerencia anterior",
|
|
328
850
|
nextSuggestion: "Sugerencia siguiente",
|
|
329
851
|
conceptIllustration: "Ilustraci\xF3n del concepto",
|
|
@@ -371,7 +893,7 @@ var AR = {
|
|
|
371
893
|
showLess: "\u0639\u0631\u0636 \u0623\u0642\u0644",
|
|
372
894
|
sources: "\u0627\u0644\u0645\u0635\u0627\u062F\u0631",
|
|
373
895
|
nextYouCouldAsk: "\u064A\u0645\u0643\u0646\u0643 \u0623\u0646 \u062A\u0633\u0623\u0644 \u0628\u0639\u062F \u0630\u0644\u0643",
|
|
374
|
-
placeholder: "\u0635\u0641 \u0627\u0644\u0645\u0634\u0643\u0644\u0629
|
|
896
|
+
placeholder: "\u0635\u0641 \u0627\u0644\u0645\u0634\u0643\u0644\u0629\u2026",
|
|
375
897
|
placeholderProduct: "\u0635\u0641 \u0645\u0634\u0643\u0644\u062A\u0643 \u0623\u0648 \u0627\u0633\u0623\u0644 \u0639\u0646 {product}\u2026",
|
|
376
898
|
send: "\u0625\u0631\u0633\u0627\u0644",
|
|
377
899
|
stop: "\u0625\u064A\u0642\u0627\u0641",
|
|
@@ -409,6 +931,20 @@ var AR = {
|
|
|
409
931
|
feedbackDismiss: "\u0625\u063A\u0644\u0627\u0642",
|
|
410
932
|
feedbackThanks: "\u0634\u0643\u0631\u064B\u0627\u060C \u0647\u0630\u0627 \u064A\u0633\u0627\u0639\u062F\u0646\u0627.",
|
|
411
933
|
feedbackNotSent: "\u0644\u0645 \u064A\u062A\u0645 \u0627\u0644\u0625\u0631\u0633\u0627\u0644.",
|
|
934
|
+
escalateOpen: "\u062A\u0648\u0627\u0635\u0644 \u0645\u0639 \u0634\u062E\u0635",
|
|
935
|
+
escalateTitle: "\u0623\u0631\u0633\u0644 \u0647\u0630\u0647 \u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629 \u0625\u0644\u0649 \u0634\u062E\u0635",
|
|
936
|
+
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.",
|
|
937
|
+
escalateEmailLabel: "\u0628\u0631\u064A\u062F\u0643 \u0627\u0644\u0625\u0644\u0643\u062A\u0631\u0648\u0646\u064A",
|
|
938
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
939
|
+
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)",
|
|
940
|
+
escalateSubmit: "\u0625\u0631\u0633\u0627\u0644 \u0625\u0644\u0649 \u0634\u062E\u0635",
|
|
941
|
+
escalateSending: "\u062C\u0627\u0631\u064D \u0627\u0644\u0625\u0631\u0633\u0627\u0644\u2026",
|
|
942
|
+
escalateCancel: "\u0625\u0644\u063A\u0627\u0621",
|
|
943
|
+
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.",
|
|
944
|
+
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}.",
|
|
945
|
+
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.",
|
|
946
|
+
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.",
|
|
947
|
+
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.",
|
|
412
948
|
prevSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u0633\u0627\u0628\u0642",
|
|
413
949
|
nextSuggestion: "\u0627\u0644\u0627\u0642\u062A\u0631\u0627\u062D \u0627\u0644\u062A\u0627\u0644\u064A",
|
|
414
950
|
conceptIllustration: "\u0631\u0633\u0645 \u062A\u0648\u0636\u064A\u062D\u064A \u0644\u0644\u0645\u0641\u0647\u0648\u0645",
|
|
@@ -456,7 +992,7 @@ var TA = {
|
|
|
456
992
|
showLess: "\u0B95\u0BC1\u0BB1\u0BC8\u0BB5\u0BBE\u0B95\u0B95\u0BCD \u0B95\u0BBE\u0B9F\u0BCD\u0B9F\u0BC1",
|
|
457
993
|
sources: "\u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
458
994
|
nextYouCouldAsk: "\u0B85\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1 \u0BA8\u0BC0\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0B95\u0BC7\u0B9F\u0BCD\u0B95\u0BB2\u0BBE\u0BAE\u0BCD",
|
|
459
|
-
placeholder: "\
|
|
995
|
+
placeholder: "\u0B9A\u0BBF\u0B95\u0BCD\u0B95\u0BB2\u0BC8 \u0BB5\u0BBF\u0BB5\u0BB0\u0BBF\u0BAF\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD\u2026",
|
|
460
996
|
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",
|
|
461
997
|
send: "\u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA\u0BC1",
|
|
462
998
|
stop: "\u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
|
|
@@ -494,6 +1030,20 @@ var TA = {
|
|
|
494
1030
|
feedbackDismiss: "\u0BAE\u0BC2\u0B9F\u0BC1",
|
|
495
1031
|
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.",
|
|
496
1032
|
feedbackNotSent: "\u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8.",
|
|
1033
|
+
escalateOpen: "\u0B92\u0BB0\u0BC1\u0BB5\u0BB0\u0BBF\u0B9F\u0BAE\u0BCD \u0BAA\u0BC7\u0B9A\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
1034
|
+
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",
|
|
1035
|
+
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.",
|
|
1036
|
+
escalateEmailLabel: "\u0B89\u0B99\u0BCD\u0B95\u0BB3\u0BCD \u0BAE\u0BBF\u0BA9\u0BCD\u0BA9\u0B9E\u0BCD\u0B9A\u0BB2\u0BCD \u0BAE\u0BC1\u0B95\u0BB5\u0BB0\u0BBF",
|
|
1037
|
+
escalateEmailPlaceholder: "you@example.com",
|
|
1038
|
+
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)",
|
|
1039
|
+
escalateSubmit: "\u0B92\u0BB0\u0BC1\u0BB5\u0BB0\u0BC1\u0B95\u0BCD\u0B95\u0BC1 \u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA\u0BC1",
|
|
1040
|
+
escalateSending: "\u0B85\u0BA9\u0BC1\u0BAA\u0BCD\u0BAA\u0BAA\u0BCD\u0BAA\u0B9F\u0BC1\u0B95\u0BBF\u0BB1\u0BA4\u0BC1\u2026",
|
|
1041
|
+
escalateCancel: "\u0BB0\u0BA4\u0BCD\u0BA4\u0BC1 \u0B9A\u0BC6\u0BAF\u0BCD",
|
|
1042
|
+
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.",
|
|
1043
|
+
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.",
|
|
1044
|
+
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.",
|
|
1045
|
+
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.",
|
|
1046
|
+
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.",
|
|
497
1047
|
prevSuggestion: "\u0BAE\u0BC1\u0BA8\u0BCD\u0BA4\u0BC8\u0BAF \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
|
|
498
1048
|
nextSuggestion: "\u0B85\u0B9F\u0BC1\u0BA4\u0BCD\u0BA4 \u0BAA\u0BB0\u0BBF\u0BA8\u0BCD\u0BA4\u0BC1\u0BB0\u0BC8",
|
|
499
1049
|
conceptIllustration: "\u0B95\u0BB0\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1 \u0BB5\u0BBF\u0BB3\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BAE\u0BCD",
|
|
@@ -736,6 +1286,29 @@ var SUBMIT_EXPLORE_FEEDBACK_MUTATION = (
|
|
|
736
1286
|
`
|
|
737
1287
|
);
|
|
738
1288
|
var EXPLORE_FEEDBACK_COMMENT_SOFT_MAX = 500;
|
|
1289
|
+
var ESCALATE_EXPLORE_CONVERSATION_MUTATION = (
|
|
1290
|
+
/* GraphQL */
|
|
1291
|
+
`
|
|
1292
|
+
mutation EscalateExploreConversation(
|
|
1293
|
+
$input: EscalateExploreConversationInput!
|
|
1294
|
+
) {
|
|
1295
|
+
escalateExploreConversation(input: $input) {
|
|
1296
|
+
created
|
|
1297
|
+
escalation {
|
|
1298
|
+
email
|
|
1299
|
+
createdAt
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
`
|
|
1304
|
+
);
|
|
1305
|
+
var EXPLORE_ESCALATION_NOTE_SOFT_MAX = 2e3;
|
|
1306
|
+
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,}$/;
|
|
1307
|
+
function looksLikeDeliverableEmail(value) {
|
|
1308
|
+
const trimmed = value.trim();
|
|
1309
|
+
if (trimmed.length === 0 || trimmed.length > 254) return false;
|
|
1310
|
+
return DELIVERABLE_EMAIL.test(trimmed);
|
|
1311
|
+
}
|
|
739
1312
|
var CREATE_EXPLORE_SHARE_LINK_MUTATION = (
|
|
740
1313
|
/* GraphQL */
|
|
741
1314
|
`
|
|
@@ -756,48 +1329,6 @@ var EXPLORE_SHARED_CONVERSATION_QUERY = (
|
|
|
756
1329
|
}
|
|
757
1330
|
`
|
|
758
1331
|
);
|
|
759
|
-
function MarkdownLink({
|
|
760
|
-
href,
|
|
761
|
-
children,
|
|
762
|
-
// react-markdown passes the hast node to every override; it is not a DOM attribute.
|
|
763
|
-
node: _node,
|
|
764
|
-
...props
|
|
765
|
-
}) {
|
|
766
|
-
const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
|
|
767
|
-
return /* @__PURE__ */ jsx(
|
|
768
|
-
"a",
|
|
769
|
-
{
|
|
770
|
-
href,
|
|
771
|
-
...props,
|
|
772
|
-
...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
|
|
773
|
-
children
|
|
774
|
-
}
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
|
-
var defaultComponents = {
|
|
778
|
-
a: MarkdownLink
|
|
779
|
-
};
|
|
780
|
-
function Markdown({
|
|
781
|
-
children,
|
|
782
|
-
className,
|
|
783
|
-
inline,
|
|
784
|
-
components
|
|
785
|
-
}) {
|
|
786
|
-
const content = /* @__PURE__ */ jsx(
|
|
787
|
-
ReactMarkdown,
|
|
788
|
-
{
|
|
789
|
-
remarkPlugins: [remarkGfm],
|
|
790
|
-
rehypePlugins: [rehypeSanitize],
|
|
791
|
-
components: { ...defaultComponents, ...components },
|
|
792
|
-
...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
|
|
793
|
-
children
|
|
794
|
-
}
|
|
795
|
-
);
|
|
796
|
-
if (inline) {
|
|
797
|
-
return /* @__PURE__ */ jsx("span", { className, children: content });
|
|
798
|
-
}
|
|
799
|
-
return /* @__PURE__ */ jsx("div", { className, children: content });
|
|
800
|
-
}
|
|
801
1332
|
var PROSE_CSS = `
|
|
802
1333
|
.boff-prose {
|
|
803
1334
|
max-width: none;
|
|
@@ -1194,6 +1725,10 @@ function useWebSDK() {
|
|
|
1194
1725
|
}
|
|
1195
1726
|
return client;
|
|
1196
1727
|
}
|
|
1728
|
+
function useWebSDKConfig() {
|
|
1729
|
+
const client = useWebSDK();
|
|
1730
|
+
return client.getConfig();
|
|
1731
|
+
}
|
|
1197
1732
|
|
|
1198
1733
|
// src/data/products.ts
|
|
1199
1734
|
var ECOSYSTEM_PRODUCTS = [
|
|
@@ -1540,6 +2075,25 @@ var VALIDATION_CODES = /* @__PURE__ */ new Set([
|
|
|
1540
2075
|
"CONVERSATION_LIMIT",
|
|
1541
2076
|
"INVALID_INPUT"
|
|
1542
2077
|
]);
|
|
2078
|
+
function escalateOutcomeForCode(code) {
|
|
2079
|
+
switch (code) {
|
|
2080
|
+
case "INVALID_EMAIL":
|
|
2081
|
+
return "invalid-email";
|
|
2082
|
+
case "RATE_LIMITED":
|
|
2083
|
+
case "HTTP_429":
|
|
2084
|
+
return "rate-limited";
|
|
2085
|
+
case "CAPTCHA_REQUIRED":
|
|
2086
|
+
case "CAPTCHA_FAILED":
|
|
2087
|
+
return "captcha";
|
|
2088
|
+
// The token owns nothing, or owns a different conversation. The server answers NOT_FOUND
|
|
2089
|
+
// rather than FORBIDDEN so the endpoint is not an oracle for whether a thread exists;
|
|
2090
|
+
// either way there is nothing this visitor can do about it, so it reads as a failure.
|
|
2091
|
+
case "NOT_FOUND":
|
|
2092
|
+
return "failed";
|
|
2093
|
+
default:
|
|
2094
|
+
return "failed";
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
1543
2097
|
function classifyExploreError(code, serverMessage) {
|
|
1544
2098
|
const c = code ?? "UNKNOWN";
|
|
1545
2099
|
if (RATE_LIMIT_CODES.has(c)) {
|
|
@@ -1706,6 +2260,7 @@ function useExploreChat(options = {}) {
|
|
|
1706
2260
|
onSend
|
|
1707
2261
|
} = options;
|
|
1708
2262
|
const client = useWebSDK();
|
|
2263
|
+
const { productId } = useWebSDKConfig();
|
|
1709
2264
|
const promptKey = JSON.stringify(examplePrompts ?? []);
|
|
1710
2265
|
const welcomeKey = JSON.stringify(fallbackWelcome ?? null);
|
|
1711
2266
|
const fallbackCatalog = useMemo(
|
|
@@ -1729,6 +2284,7 @@ function useExploreChat(options = {}) {
|
|
|
1729
2284
|
const pollGenerationRef = useRef(0);
|
|
1730
2285
|
const pollTimerRef = useRef(null);
|
|
1731
2286
|
const conversationTokenRef = useRef(null);
|
|
2287
|
+
const [hasConversation, setHasConversation] = useState(false);
|
|
1732
2288
|
const shareTokenRef = useRef(void 0);
|
|
1733
2289
|
if (shareTokenRef.current === void 0) {
|
|
1734
2290
|
shareTokenRef.current = readShareToken();
|
|
@@ -1743,6 +2299,10 @@ function useExploreChat(options = {}) {
|
|
|
1743
2299
|
);
|
|
1744
2300
|
const inFlightRef = useRef(false);
|
|
1745
2301
|
catalogRef.current = catalog;
|
|
2302
|
+
const rememberConversationToken = useCallback((token) => {
|
|
2303
|
+
conversationTokenRef.current = token;
|
|
2304
|
+
setHasConversation(token !== null);
|
|
2305
|
+
}, []);
|
|
1746
2306
|
const clearPollTimer = useCallback(() => {
|
|
1747
2307
|
if (pollTimerRef.current !== null) {
|
|
1748
2308
|
clearTimeout(pollTimerRef.current);
|
|
@@ -1809,7 +2369,7 @@ function useExploreChat(options = {}) {
|
|
|
1809
2369
|
setArchive(readArchive(storageKey));
|
|
1810
2370
|
const stored = readPersisted(storageKey);
|
|
1811
2371
|
if (stored) {
|
|
1812
|
-
|
|
2372
|
+
rememberConversationToken(stored.conversationToken);
|
|
1813
2373
|
focusProductRef.current = stored.focusProduct;
|
|
1814
2374
|
const settled = stored.messages.flatMap((m) => {
|
|
1815
2375
|
if (m.role !== "ASSISTANT") return [m];
|
|
@@ -1825,7 +2385,7 @@ function useExploreChat(options = {}) {
|
|
|
1825
2385
|
setTurnCount(turns);
|
|
1826
2386
|
}
|
|
1827
2387
|
setHydrated(true);
|
|
1828
|
-
}, [persist, storageKey]);
|
|
2388
|
+
}, [persist, rememberConversationToken, storageKey]);
|
|
1829
2389
|
useEffect(() => {
|
|
1830
2390
|
if (!persist || !hydrated || typeof window === "undefined") return;
|
|
1831
2391
|
try {
|
|
@@ -2018,7 +2578,7 @@ function useExploreChat(options = {}) {
|
|
|
2018
2578
|
setPhase(classified.kind === "rate-limit" ? "limited" : "error");
|
|
2019
2579
|
return;
|
|
2020
2580
|
}
|
|
2021
|
-
|
|
2581
|
+
rememberConversationToken(result.conversation.token);
|
|
2022
2582
|
turnCountRef.current = result.conversation.turnCount;
|
|
2023
2583
|
setTurnCount(result.conversation.turnCount);
|
|
2024
2584
|
setRemainingToday(result.remainingToday ?? null);
|
|
@@ -2044,7 +2604,14 @@ function useExploreChat(options = {}) {
|
|
|
2044
2604
|
setPhase("streaming");
|
|
2045
2605
|
startPolling(result.conversation.token, assistantMessage.id);
|
|
2046
2606
|
},
|
|
2047
|
-
[
|
|
2607
|
+
[
|
|
2608
|
+
client,
|
|
2609
|
+
locale,
|
|
2610
|
+
pageUrl,
|
|
2611
|
+
productSlug,
|
|
2612
|
+
rememberConversationToken,
|
|
2613
|
+
startPolling
|
|
2614
|
+
]
|
|
2048
2615
|
);
|
|
2049
2616
|
const send = useCallback(
|
|
2050
2617
|
async (message, attachments = []) => {
|
|
@@ -2161,7 +2728,7 @@ function useExploreChat(options = {}) {
|
|
|
2161
2728
|
);
|
|
2162
2729
|
cancelPolling();
|
|
2163
2730
|
inFlightRef.current = false;
|
|
2164
|
-
|
|
2731
|
+
rememberConversationToken(null);
|
|
2165
2732
|
lastSentTextRef.current = null;
|
|
2166
2733
|
lastSentAttachmentsRef.current = [];
|
|
2167
2734
|
turnCountRef.current = 0;
|
|
@@ -2176,7 +2743,7 @@ function useExploreChat(options = {}) {
|
|
|
2176
2743
|
} catch {
|
|
2177
2744
|
}
|
|
2178
2745
|
}
|
|
2179
|
-
}, [cancelPolling, storageKey]);
|
|
2746
|
+
}, [cancelPolling, rememberConversationToken, storageKey]);
|
|
2180
2747
|
const openConversation = useCallback(
|
|
2181
2748
|
(token) => {
|
|
2182
2749
|
const entry = readArchive(storageKey).find((e) => e.token === token);
|
|
@@ -2188,7 +2755,7 @@ function useExploreChat(options = {}) {
|
|
|
2188
2755
|
});
|
|
2189
2756
|
cancelPolling();
|
|
2190
2757
|
inFlightRef.current = false;
|
|
2191
|
-
|
|
2758
|
+
rememberConversationToken(entry.token);
|
|
2192
2759
|
turnCountRef.current = entry.messages.filter(
|
|
2193
2760
|
(m) => m.role === "USER"
|
|
2194
2761
|
).length;
|
|
@@ -2200,7 +2767,7 @@ function useExploreChat(options = {}) {
|
|
|
2200
2767
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
2201
2768
|
setArchive(readArchive(storageKey));
|
|
2202
2769
|
},
|
|
2203
|
-
[cancelPolling, storageKey]
|
|
2770
|
+
[cancelPolling, rememberConversationToken, storageKey]
|
|
2204
2771
|
);
|
|
2205
2772
|
const deleteConversation = useCallback(
|
|
2206
2773
|
(token) => {
|
|
@@ -2289,6 +2856,69 @@ function useExploreChat(options = {}) {
|
|
|
2289
2856
|
},
|
|
2290
2857
|
[client]
|
|
2291
2858
|
);
|
|
2859
|
+
const escalate = useCallback(
|
|
2860
|
+
async (email, note) => {
|
|
2861
|
+
const token = conversationTokenRef.current;
|
|
2862
|
+
if (!token) {
|
|
2863
|
+
return { outcome: "no-conversation", email: null, createdAt: null };
|
|
2864
|
+
}
|
|
2865
|
+
const attempt = async (captchaAttempt) => {
|
|
2866
|
+
const activeCatalog = catalogRef.current;
|
|
2867
|
+
let recaptchaToken;
|
|
2868
|
+
if (activeCatalog.captchaRequired && activeCatalog.recaptchaSiteKey) {
|
|
2869
|
+
try {
|
|
2870
|
+
recaptchaToken = await getRecaptchaV3Token(
|
|
2871
|
+
activeCatalog.recaptchaSiteKey
|
|
2872
|
+
);
|
|
2873
|
+
} catch (err) {
|
|
2874
|
+
console.warn(
|
|
2875
|
+
"[WebSDK] reCAPTCHA v3 token could not be minted",
|
|
2876
|
+
err
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
const input = {
|
|
2881
|
+
conversationToken: token,
|
|
2882
|
+
email: email.trim(),
|
|
2883
|
+
note: note?.trim() ? note.trim() : null,
|
|
2884
|
+
...recaptchaToken ? { recaptchaToken } : {},
|
|
2885
|
+
...productId ? { productId } : {}
|
|
2886
|
+
};
|
|
2887
|
+
try {
|
|
2888
|
+
const res = await client.mutate(
|
|
2889
|
+
ESCALATE_EXPLORE_CONVERSATION_MUTATION,
|
|
2890
|
+
{ input }
|
|
2891
|
+
);
|
|
2892
|
+
const first = res?.errors?.[0];
|
|
2893
|
+
const result = res?.data?.escalateExploreConversation;
|
|
2894
|
+
if (first || !result) {
|
|
2895
|
+
const code = typeof first?.extensions?.code === "string" ? first.extensions.code : "ESCALATION_FAILED";
|
|
2896
|
+
if (code === "CAPTCHA_FAILED" && captchaAttempt === 0) {
|
|
2897
|
+
return attempt(1);
|
|
2898
|
+
}
|
|
2899
|
+
return {
|
|
2900
|
+
outcome: escalateOutcomeForCode(code),
|
|
2901
|
+
email: null,
|
|
2902
|
+
createdAt: null
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
return {
|
|
2906
|
+
// `created: false` is the double-click, the retry, the reload. The handover
|
|
2907
|
+
// exists, so this is a success — and the address comes from the SERVER, because
|
|
2908
|
+
// on a repeat submit the reply goes to whatever the FIRST handover used.
|
|
2909
|
+
outcome: result.created ? "sent" : "already-handed-over",
|
|
2910
|
+
email: result.escalation?.email ?? null,
|
|
2911
|
+
createdAt: result.escalation?.createdAt ?? null
|
|
2912
|
+
};
|
|
2913
|
+
} catch {
|
|
2914
|
+
return { outcome: "failed", email: null, createdAt: null };
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
return attempt(0);
|
|
2918
|
+
},
|
|
2919
|
+
[client, productId]
|
|
2920
|
+
);
|
|
2921
|
+
const canEscalate = hasConversation && !isSharedView;
|
|
2292
2922
|
return {
|
|
2293
2923
|
phase,
|
|
2294
2924
|
catalog,
|
|
@@ -2309,6 +2939,8 @@ function useExploreChat(options = {}) {
|
|
|
2309
2939
|
clearHistory,
|
|
2310
2940
|
recordClick,
|
|
2311
2941
|
submitFeedback,
|
|
2942
|
+
escalate,
|
|
2943
|
+
canEscalate,
|
|
2312
2944
|
createShareLink,
|
|
2313
2945
|
isSharedView
|
|
2314
2946
|
};
|
|
@@ -2994,9 +3626,6 @@ function PageHead({
|
|
|
2994
3626
|
))
|
|
2995
3627
|
] });
|
|
2996
3628
|
}
|
|
2997
|
-
function cn(...inputs) {
|
|
2998
|
-
return twMerge(clsx(inputs));
|
|
2999
|
-
}
|
|
3000
3629
|
var buttonVariants = cva(
|
|
3001
3630
|
"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",
|
|
3002
3631
|
{
|
|
@@ -3234,163 +3863,27 @@ var EXPLORE_CSS = `
|
|
|
3234
3863
|
.boff-explore-scroll::-webkit-scrollbar { width: 10px; }
|
|
3235
3864
|
.boff-explore-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
3236
3865
|
.boff-explore-scroll::-webkit-scrollbar-thumb {
|
|
3237
|
-
background-color: hsl(var(--border));
|
|
3238
|
-
border: 3px solid transparent;
|
|
3239
|
-
border-radius: 9999px;
|
|
3240
|
-
background-clip: content-box;
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3243
|
-
/* The product strip scrolls inside itself; a visible scrollbar would read as a broken
|
|
3244
|
-
layout rather than an affordance, and reserving its gutter shifts the chips. */
|
|
3245
|
-
.boff-explore-strip {
|
|
3246
|
-
scrollbar-width: none;
|
|
3247
|
-
-ms-overflow-style: none;
|
|
3248
|
-
}
|
|
3249
|
-
.boff-explore-strip::-webkit-scrollbar { display: none; }
|
|
3250
|
-
|
|
3251
|
-
@keyframes boff-explore-fade-in {
|
|
3252
|
-
from { opacity: 0; transform: translateY(2px); }
|
|
3253
|
-
to { opacity: 1; transform: none; }
|
|
3254
|
-
}
|
|
3255
|
-
.boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
|
|
3256
|
-
@media (prefers-reduced-motion: reduce) {
|
|
3257
|
-
.boff-explore-fade { animation: none; }
|
|
3258
|
-
}
|
|
3259
|
-
|
|
3260
|
-
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
3261
|
-
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
3262
|
-
WHOLE document horizontally scrollable, not just the bubble. */
|
|
3263
|
-
.boff-explore-body,
|
|
3264
|
-
.boff-explore-body p,
|
|
3265
|
-
.boff-explore-body li,
|
|
3266
|
-
.boff-explore-body a,
|
|
3267
|
-
.boff-explore-body h1,
|
|
3268
|
-
.boff-explore-body h2,
|
|
3269
|
-
.boff-explore-body h3 {
|
|
3270
|
-
overflow-wrap: anywhere;
|
|
3271
|
-
word-break: break-word;
|
|
3272
|
-
}
|
|
3273
|
-
.boff-explore-body pre {
|
|
3274
|
-
overflow-x: auto;
|
|
3275
|
-
max-width: 100%;
|
|
3276
|
-
}
|
|
3277
|
-
.boff-explore-body table {
|
|
3278
|
-
display: block;
|
|
3279
|
-
overflow-x: auto;
|
|
3280
|
-
max-width: 100%;
|
|
3281
|
-
}
|
|
3282
|
-
.boff-explore-body img {
|
|
3283
|
-
max-width: 100%;
|
|
3284
|
-
height: auto;
|
|
3285
|
-
}
|
|
3286
|
-
|
|
3287
|
-
/* ---- Comparison and pricing tables ----------------------------------------------------
|
|
3288
|
-
The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
|
|
3289
|
-
the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
|
|
3290
|
-
the page must not scroll sideways because of it, and it has to be readable on a 320px
|
|
3291
|
-
phone, which a four-column grid of 1px borders is not.
|
|
3292
|
-
|
|
3293
|
-
Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
|
|
3294
|
-
\`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
|
|
3295
|
-
cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
|
|
3296
|
-
of table from chaining to the page \u2014 the same rule the transcript needed. */
|
|
3297
|
-
.boff-explore-body .boff-explore-table {
|
|
3298
|
-
max-width: 100%;
|
|
3299
|
-
overflow-x: auto;
|
|
3300
|
-
overscroll-behavior-x: contain;
|
|
3301
|
-
margin: 0.75rem 0;
|
|
3302
|
-
border: 1px solid hsl(var(--border));
|
|
3303
|
-
border-radius: 0.75rem;
|
|
3304
|
-
background-color: hsl(var(--card));
|
|
3305
|
-
}
|
|
3306
|
-
.boff-explore-body .boff-explore-table:focus-visible {
|
|
3307
|
-
outline: 2px solid hsl(var(--ring));
|
|
3308
|
-
outline-offset: 2px;
|
|
3309
|
-
}
|
|
3310
|
-
.boff-explore-body .boff-explore-table table {
|
|
3311
|
-
/* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
|
|
3312
|
-
display: table;
|
|
3313
|
-
width: 100%;
|
|
3314
|
-
max-width: none;
|
|
3315
|
-
margin: 0;
|
|
3316
|
-
border-collapse: collapse;
|
|
3317
|
-
font-size: 0.8125rem;
|
|
3318
|
-
}
|
|
3319
|
-
.boff-explore-body .boff-explore-table th,
|
|
3320
|
-
.boff-explore-body .boff-explore-table td {
|
|
3321
|
-
border: 0;
|
|
3322
|
-
border-top: 1px solid hsl(var(--border));
|
|
3323
|
-
padding: 0.5rem 0.75rem;
|
|
3324
|
-
/* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
|
|
3325
|
-
table there reads against the grain of every other line on the page. */
|
|
3326
|
-
text-align: start;
|
|
3327
|
-
vertical-align: top;
|
|
3328
|
-
overflow-wrap: anywhere;
|
|
3329
|
-
/* Prices in a column line up under each other instead of drifting by digit width. */
|
|
3330
|
-
font-variant-numeric: tabular-nums;
|
|
3331
|
-
min-width: 6rem;
|
|
3332
|
-
}
|
|
3333
|
-
.boff-explore-body .boff-explore-table thead th {
|
|
3334
|
-
border-top: 0;
|
|
3335
|
-
background-color: hsl(var(--muted));
|
|
3336
|
-
color: hsl(var(--foreground));
|
|
3337
|
-
font-weight: 600;
|
|
3338
|
-
white-space: nowrap;
|
|
3339
|
-
}
|
|
3340
|
-
/* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
|
|
3341
|
-
the row's heading is what makes a price list scannable rather than a grid of strings. */
|
|
3342
|
-
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
3343
|
-
font-weight: 600;
|
|
3344
|
-
color: hsl(var(--card-foreground));
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
/* Narrow: one card per row. Each cell prints its own column heading, so a value is never
|
|
3348
|
-
stranded without the question it answers, and the subject cell becomes the card's title.
|
|
3349
|
-
A media query rather than a measured width \u2014 these pages are serialised post-hydration,
|
|
3350
|
-
so anything measured here would ship to every phone as static HTML. */
|
|
3351
|
-
@media (max-width: 30rem) {
|
|
3352
|
-
.boff-explore-body .boff-explore-table {
|
|
3353
|
-
overflow-x: visible;
|
|
3354
|
-
}
|
|
3355
|
-
.boff-explore-body .boff-explore-table table,
|
|
3356
|
-
.boff-explore-body .boff-explore-table tbody,
|
|
3357
|
-
.boff-explore-body .boff-explore-table tr,
|
|
3358
|
-
.boff-explore-body .boff-explore-table td {
|
|
3359
|
-
display: block;
|
|
3360
|
-
width: 100%;
|
|
3361
|
-
min-width: 0;
|
|
3362
|
-
}
|
|
3363
|
-
.boff-explore-body .boff-explore-table thead {
|
|
3364
|
-
display: none;
|
|
3365
|
-
}
|
|
3366
|
-
.boff-explore-body .boff-explore-table tr {
|
|
3367
|
-
border-top: 1px solid hsl(var(--border));
|
|
3368
|
-
padding: 0.5rem 0;
|
|
3369
|
-
}
|
|
3370
|
-
.boff-explore-body .boff-explore-table tr:first-child {
|
|
3371
|
-
border-top: 0;
|
|
3372
|
-
}
|
|
3373
|
-
.boff-explore-body .boff-explore-table td {
|
|
3374
|
-
border: 0;
|
|
3375
|
-
padding: 0.125rem 0.75rem;
|
|
3376
|
-
}
|
|
3377
|
-
.boff-explore-body .boff-explore-table td[data-label]::before {
|
|
3378
|
-
content: attr(data-label);
|
|
3379
|
-
display: block;
|
|
3380
|
-
font-size: 0.6875rem;
|
|
3381
|
-
font-weight: 600;
|
|
3382
|
-
text-transform: uppercase;
|
|
3383
|
-
letter-spacing: 0.04em;
|
|
3384
|
-
color: hsl(var(--muted-foreground));
|
|
3385
|
-
}
|
|
3386
|
-
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
3387
|
-
font-size: 0.9375rem;
|
|
3388
|
-
padding-bottom: 0.25rem;
|
|
3389
|
-
}
|
|
3390
|
-
.boff-explore-body .boff-explore-table tbody td:first-child::before {
|
|
3391
|
-
/* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
|
|
3392
|
-
display: none;
|
|
3393
|
-
}
|
|
3866
|
+
background-color: hsl(var(--border));
|
|
3867
|
+
border: 3px solid transparent;
|
|
3868
|
+
border-radius: 9999px;
|
|
3869
|
+
background-clip: content-box;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
/* The product strip scrolls inside itself; a visible scrollbar would read as a broken
|
|
3873
|
+
layout rather than an affordance, and reserving its gutter shifts the chips. */
|
|
3874
|
+
.boff-explore-strip {
|
|
3875
|
+
scrollbar-width: none;
|
|
3876
|
+
-ms-overflow-style: none;
|
|
3877
|
+
}
|
|
3878
|
+
.boff-explore-strip::-webkit-scrollbar { display: none; }
|
|
3879
|
+
|
|
3880
|
+
@keyframes boff-explore-fade-in {
|
|
3881
|
+
from { opacity: 0; transform: translateY(2px); }
|
|
3882
|
+
to { opacity: 1; transform: none; }
|
|
3883
|
+
}
|
|
3884
|
+
.boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
|
|
3885
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3886
|
+
.boff-explore-fade { animation: none; }
|
|
3394
3887
|
}
|
|
3395
3888
|
|
|
3396
3889
|
/* ---- Ambient backdrop -----------------------------------------------------------------
|
|
@@ -3449,11 +3942,40 @@ var EXPLORE_CSS = `
|
|
|
3449
3942
|
animation: boff-explore-sheen 1.5s ease-in-out infinite;
|
|
3450
3943
|
}
|
|
3451
3944
|
|
|
3945
|
+
/* ---- The composer, before anyone has typed -----------------------------------------
|
|
3946
|
+
On the welcome screen the box IS the page: everything above it is a way of explaining
|
|
3947
|
+
what to put in it. Unhighlighted it reads as a search bar at the bottom of a landing
|
|
3948
|
+
page, which people scroll past \u2014 so while the conversation is empty it gets a soft halo
|
|
3949
|
+
in the site's own --primary and one slow breath of it, and the moment there is a
|
|
3950
|
+
transcript, or the visitor focuses or types, it drops back to the ordinary card it was.
|
|
3951
|
+
An input that keeps pulsing while you are using it is nagging, not inviting.
|
|
3952
|
+
|
|
3953
|
+
Colour comes from the product's --primary token, so it is whatever that site's palette
|
|
3954
|
+
already is, never a colour chosen here. */
|
|
3955
|
+
.boff-explore-invite {
|
|
3956
|
+
border-color: hsl(var(--primary) / 0.45);
|
|
3957
|
+
box-shadow:
|
|
3958
|
+
0 0 0 4px hsl(var(--primary) / 0.08),
|
|
3959
|
+
0 8px 24px -12px hsl(var(--primary) / 0.45);
|
|
3960
|
+
animation: boff-explore-breathe 3.2s ease-in-out infinite;
|
|
3961
|
+
}
|
|
3962
|
+
/* Focus and typing both mean "I have found it", and the hint has done its job. */
|
|
3963
|
+
.boff-explore-invite:focus-within,
|
|
3964
|
+
.boff-explore-invite.boff-explore-invite-quiet {
|
|
3965
|
+
animation: none;
|
|
3966
|
+
box-shadow: 0 0 0 4px hsl(var(--primary) / 0.10);
|
|
3967
|
+
}
|
|
3968
|
+
@keyframes boff-explore-breathe {
|
|
3969
|
+
0%, 100% { box-shadow: 0 0 0 4px hsl(var(--primary) / 0.07), 0 8px 24px -12px hsl(var(--primary) / 0.40); }
|
|
3970
|
+
50% { box-shadow: 0 0 0 7px hsl(var(--primary) / 0.13), 0 8px 28px -12px hsl(var(--primary) / 0.55); }
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3452
3973
|
/* Every one of the above is decoration. Reduced motion removes the movement and keeps the
|
|
3453
3974
|
final state, rather than leaving content mid-animation and invisible. */
|
|
3454
3975
|
@media (prefers-reduced-motion: reduce) {
|
|
3455
3976
|
.boff-explore-aura::before,
|
|
3456
|
-
.boff-explore-busy::after
|
|
3977
|
+
.boff-explore-busy::after,
|
|
3978
|
+
.boff-explore-invite { animation: none; }
|
|
3457
3979
|
.boff-explore-rise { animation: none; opacity: 1; transform: none; }
|
|
3458
3980
|
}
|
|
3459
3981
|
|
|
@@ -3480,457 +4002,211 @@ var EXPLORE_CSS = `
|
|
|
3480
4002
|
something is actually covering the bottom, so a snapshot taken
|
|
3481
4003
|
with no keyboard open never carries it. */
|
|
3482
4004
|
.boff-explore-viewport {
|
|
3483
|
-
height: calc(100vh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
|
|
3484
|
-
}
|
|
3485
|
-
@supports (height: 100dvh) {
|
|
3486
|
-
.boff-explore-viewport {
|
|
3487
|
-
height: calc(100dvh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
|
|
3488
|
-
}
|
|
3489
|
-
}
|
|
3490
|
-
|
|
3491
|
-
/* Landscape phones and other short viewports: a one-screen pane leaves a ~124px transcript
|
|
3492
|
-
sliver under ~610px of chrome and content. At or below the threshold the pane stops
|
|
3493
|
-
pretending to be a window and lets the page's own scrolling carry the transcript. The
|
|
3494
|
-
min-height is the available height rather than a fixed rem value, so the fallback can
|
|
3495
|
-
never be taller than the screen it is rescuing. */
|
|
3496
|
-
@media (max-height: ${SHORT_VIEWPORT_PX}px) {
|
|
3497
|
-
.boff-explore-viewport {
|
|
3498
|
-
height: auto;
|
|
3499
|
-
min-height: calc(100vh - var(--boff-explore-offset, 0px));
|
|
3500
|
-
}
|
|
3501
|
-
}
|
|
3502
|
-
@supports (height: 100dvh) {
|
|
3503
|
-
@media (max-height: ${SHORT_VIEWPORT_PX}px) {
|
|
3504
|
-
.boff-explore-viewport {
|
|
3505
|
-
min-height: calc(100dvh - var(--boff-explore-offset, 0px));
|
|
3506
|
-
}
|
|
3507
|
-
}
|
|
3508
|
-
}
|
|
3509
|
-
|
|
3510
|
-
/* ---- Composer dock --------------------------------------------------------------------
|
|
3511
|
-
Three declarations, each the fallback for the one after it: a plain 1rem where \`max()\`
|
|
3512
|
-
is unknown, then the measured overlap of any FOREIGN fixed chrome sitting over the
|
|
3513
|
-
composer (the host sites' cookie banner is 229px tall and lands exactly there), then the
|
|
3514
|
-
same plus the home-indicator inset. \`--boff-explore-chrome\` is set on the root only once
|
|
3515
|
-
an overlap has actually been measured, so everywhere else this collapses to 1rem. */
|
|
3516
|
-
.boff-explore-composer {
|
|
3517
|
-
padding-bottom: 1rem;
|
|
3518
|
-
padding-bottom: max(1rem, var(--boff-explore-chrome, 0px));
|
|
3519
|
-
padding-bottom: max(1rem, env(safe-area-inset-bottom), var(--boff-explore-chrome, 0px));
|
|
3520
|
-
}
|
|
3521
|
-
`;
|
|
3522
|
-
function ExploreStyles() {
|
|
3523
|
-
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
|
|
3524
|
-
}
|
|
3525
|
-
var CAPABILITIES = [
|
|
3526
|
-
{
|
|
3527
|
-
icon: BookOpenText,
|
|
3528
|
-
title: "Grounded in our own docs",
|
|
3529
|
-
body: "Every answer comes from published product pages and documentation \u2014 never invented."
|
|
3530
|
-
},
|
|
3531
|
-
{
|
|
3532
|
-
icon: Layers,
|
|
3533
|
-
title: "Compare across the ecosystem",
|
|
3534
|
-
body: "Ask which product fits your team, or how two of them work together."
|
|
3535
|
-
},
|
|
3536
|
-
{
|
|
3537
|
-
icon: Link2,
|
|
3538
|
-
title: "Straight to the right page",
|
|
3539
|
-
body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
|
|
3540
|
-
}
|
|
3541
|
-
];
|
|
3542
|
-
var MIN_VISIBLE_FRACTION = 0.25;
|
|
3543
|
-
var SDK_OVERLAY_SELECTOR = "[data-boff-explore]";
|
|
3544
|
-
var CTA_VARIANTS = {
|
|
3545
|
-
CONTACT: "default",
|
|
3546
|
-
WAITLIST: "default",
|
|
3547
|
-
APP: "default",
|
|
3548
|
-
PRICING: "secondary",
|
|
3549
|
-
STORE: "secondary",
|
|
3550
|
-
PARTNERS: "secondary",
|
|
3551
|
-
DOCS: "outline",
|
|
3552
|
-
SERVICES: "outline",
|
|
3553
|
-
OTHER: "outline"
|
|
3554
|
-
};
|
|
3555
|
-
function normalizeOffset(offset) {
|
|
3556
|
-
if (offset === void 0) return "0px";
|
|
3557
|
-
return typeof offset === "number" ? `${offset}px` : offset;
|
|
3558
|
-
}
|
|
3559
|
-
function isOwnElement(el, root) {
|
|
3560
|
-
if (el === document.body || el === document.documentElement) return true;
|
|
3561
|
-
if (root.contains(el)) return true;
|
|
3562
|
-
return el.closest(SDK_OVERLAY_SELECTOR) !== null;
|
|
3563
|
-
}
|
|
3564
|
-
function visualViewportHeight() {
|
|
3565
|
-
return window.visualViewport?.height ?? window.innerHeight;
|
|
3566
|
-
}
|
|
3567
|
-
function clamp2(value, min, max) {
|
|
3568
|
-
return Math.min(Math.max(value, min), max);
|
|
3569
|
-
}
|
|
3570
|
-
function isRestoredNavigation() {
|
|
3571
|
-
const entries = window.performance?.getEntriesByType?.("navigation");
|
|
3572
|
-
const entry = entries?.[0];
|
|
3573
|
-
if (!entry) return true;
|
|
3574
|
-
const navigation = entry;
|
|
3575
|
-
return navigation.type === "reload" || navigation.type === "back_forward";
|
|
3576
|
-
}
|
|
3577
|
-
function pageOwnsScrolling(el) {
|
|
3578
|
-
if (window.innerHeight > SHORT_VIEWPORT_PX) return false;
|
|
3579
|
-
return el.scrollHeight - el.clientHeight <= 1;
|
|
3580
|
-
}
|
|
3581
|
-
function isInternalPath(url) {
|
|
3582
|
-
return url.startsWith("/") && !url.startsWith("//");
|
|
3583
|
-
}
|
|
3584
|
-
function hostnameOf(url) {
|
|
3585
|
-
try {
|
|
3586
|
-
return new URL(url).hostname.replace(/^www\./, "");
|
|
3587
|
-
} catch {
|
|
3588
|
-
return "";
|
|
3589
|
-
}
|
|
3590
|
-
}
|
|
3591
|
-
function initialOf(value, fallback) {
|
|
3592
|
-
const source = (value || fallback).trim();
|
|
3593
|
-
return source ? source.slice(0, 1).toUpperCase() : "?";
|
|
3594
|
-
}
|
|
3595
|
-
function logoUrlOf(url) {
|
|
3596
|
-
try {
|
|
3597
|
-
const { origin, protocol } = new URL(url);
|
|
3598
|
-
if (protocol !== "https:" && protocol !== "http:") return null;
|
|
3599
|
-
return `${origin}/favicon.svg`;
|
|
3600
|
-
} catch {
|
|
3601
|
-
return null;
|
|
3602
|
-
}
|
|
3603
|
-
}
|
|
3604
|
-
function inlineImageOrigins(products) {
|
|
3605
|
-
const origins = /* @__PURE__ */ new Set();
|
|
3606
|
-
for (const product of products) {
|
|
3607
|
-
let url;
|
|
3608
|
-
try {
|
|
3609
|
-
url = new URL(product.website);
|
|
3610
|
-
} catch {
|
|
3611
|
-
continue;
|
|
3612
|
-
}
|
|
3613
|
-
if (url.protocol !== "https:") continue;
|
|
3614
|
-
const apex = url.hostname.replace(/^www\./, "");
|
|
3615
|
-
const port = url.port ? `:${url.port}` : "";
|
|
3616
|
-
origins.add(`https://${apex}${port}`);
|
|
3617
|
-
origins.add(`https://www.${apex}${port}`);
|
|
3618
|
-
}
|
|
3619
|
-
return origins;
|
|
3620
|
-
}
|
|
3621
|
-
function isInlineImageAllowed(src, origins) {
|
|
3622
|
-
if (typeof src !== "string" || src === "") return false;
|
|
3623
|
-
let url;
|
|
3624
|
-
try {
|
|
3625
|
-
url = new URL(src);
|
|
3626
|
-
} catch {
|
|
3627
|
-
return false;
|
|
3628
|
-
}
|
|
3629
|
-
if (url.protocol !== "https:" || url.username || url.password) return false;
|
|
3630
|
-
return origins.has(url.origin);
|
|
3631
|
-
}
|
|
3632
|
-
var REFERENCE_KIND_ORDER = [
|
|
3633
|
-
"CONCEPT",
|
|
3634
|
-
"DOCS",
|
|
3635
|
-
"PRICING",
|
|
3636
|
-
"USE_CASE",
|
|
3637
|
-
"PAGE"
|
|
3638
|
-
];
|
|
3639
|
-
var PRODUCT_SUBDOMAINS = [
|
|
3640
|
-
"alphadocs.",
|
|
3641
|
-
"alphaapp.",
|
|
3642
|
-
"docs.",
|
|
3643
|
-
"alpha.",
|
|
3644
|
-
"app.",
|
|
3645
|
-
"www."
|
|
3646
|
-
];
|
|
3647
|
-
function readCatalogUrl(raw) {
|
|
3648
|
-
let parsed;
|
|
3649
|
-
try {
|
|
3650
|
-
parsed = new URL(raw);
|
|
3651
|
-
} catch {
|
|
3652
|
-
return null;
|
|
3653
|
-
}
|
|
3654
|
-
if (parsed.protocol !== "https:") return null;
|
|
3655
|
-
const host = parsed.hostname.toLowerCase();
|
|
3656
|
-
const prefix = PRODUCT_SUBDOMAINS.find(
|
|
3657
|
-
(candidate) => host.startsWith(candidate)
|
|
3658
|
-
);
|
|
3659
|
-
const apex = prefix === void 0 ? host : host.slice(prefix.length);
|
|
3660
|
-
const port = parsed.port ? `:${parsed.port}` : "";
|
|
3661
|
-
return {
|
|
3662
|
-
origin: `https://${apex}${port}`,
|
|
3663
|
-
prefix,
|
|
3664
|
-
pathname: parsed.pathname
|
|
3665
|
-
};
|
|
3666
|
-
}
|
|
3667
|
-
function referenceKindOf(url, origins) {
|
|
3668
|
-
const parsed = readCatalogUrl(url);
|
|
3669
|
-
if (parsed === null || !origins.has(parsed.origin)) return null;
|
|
3670
|
-
if (parsed.prefix === "docs." || parsed.prefix === "alphadocs.")
|
|
3671
|
-
return "DOCS";
|
|
3672
|
-
const segment = parsed.pathname.toLowerCase().split("/").filter(Boolean)[0] ?? "";
|
|
3673
|
-
if (segment === "concept" || segment === "concepts") return "CONCEPT";
|
|
3674
|
-
if (segment === "docs") return "DOCS";
|
|
3675
|
-
if (segment === "pricing" || segment === "plans") return "PRICING";
|
|
3676
|
-
if (segment === "use-case" || segment === "use-cases" || segment === "solutions") {
|
|
3677
|
-
return "USE_CASE";
|
|
3678
|
-
}
|
|
3679
|
-
return "PAGE";
|
|
3680
|
-
}
|
|
3681
|
-
function productNamesByOrigin(products) {
|
|
3682
|
-
const names = /* @__PURE__ */ new Map();
|
|
3683
|
-
for (const product of products) {
|
|
3684
|
-
let url;
|
|
3685
|
-
try {
|
|
3686
|
-
url = new URL(product.website);
|
|
3687
|
-
} catch {
|
|
3688
|
-
continue;
|
|
3689
|
-
}
|
|
3690
|
-
if (url.protocol !== "https:") continue;
|
|
3691
|
-
const apex = url.hostname.replace(/^www\./, "");
|
|
3692
|
-
const port = url.port ? `:${url.port}` : "";
|
|
3693
|
-
const key = `https://${apex}${port}`;
|
|
3694
|
-
if (!names.has(key)) names.set(key, product.name);
|
|
3695
|
-
}
|
|
3696
|
-
return names;
|
|
3697
|
-
}
|
|
3698
|
-
function summariseReferences(references, origins, productNames) {
|
|
3699
|
-
const counts = /* @__PURE__ */ new Map();
|
|
3700
|
-
const products = [];
|
|
3701
|
-
for (const reference of references) {
|
|
3702
|
-
const kind = referenceKindOf(reference.url, origins);
|
|
3703
|
-
if (kind !== null) counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
3704
|
-
const parsed = readCatalogUrl(reference.url);
|
|
3705
|
-
const name = (parsed === null ? void 0 : productNames.get(parsed.origin)) ?? (reference.product || null);
|
|
3706
|
-
if (name != null && !products.includes(name)) products.push(name);
|
|
3707
|
-
}
|
|
3708
|
-
return {
|
|
3709
|
-
total: references.length,
|
|
3710
|
-
kinds: REFERENCE_KIND_ORDER.filter((kind) => counts.has(kind)).map(
|
|
3711
|
-
(kind) => ({ kind, count: counts.get(kind) ?? 0 })
|
|
3712
|
-
),
|
|
3713
|
-
products
|
|
3714
|
-
};
|
|
4005
|
+
height: calc(100vh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
|
|
3715
4006
|
}
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
var
|
|
3719
|
-
|
|
3720
|
-
let line = raw;
|
|
3721
|
-
line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
|
|
3722
|
-
line = mode === "speech" ? (
|
|
3723
|
-
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
3724
|
-
// prompt asks for two to five links in every answer. The label IS the words.
|
|
3725
|
-
line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
|
|
3726
|
-
) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
|
|
3727
|
-
line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
3728
|
-
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
3729
|
-
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
3730
|
-
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
3731
|
-
line = line.replace(/`([^`]+)`/g, "$1");
|
|
3732
|
-
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
3733
|
-
line = line.replace(/__([^_]+)__/g, "$1");
|
|
3734
|
-
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
3735
|
-
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
3736
|
-
return line.replace(/[ \t]+$/, "");
|
|
4007
|
+
@supports (height: 100dvh) {
|
|
4008
|
+
.boff-explore-viewport {
|
|
4009
|
+
height: calc(100dvh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
|
|
4010
|
+
}
|
|
3737
4011
|
}
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
4012
|
+
|
|
4013
|
+
/* Landscape phones and other short viewports: a one-screen pane leaves a ~124px transcript
|
|
4014
|
+
sliver under ~610px of chrome and content. At or below the threshold the pane stops
|
|
4015
|
+
pretending to be a window and lets the page's own scrolling carry the transcript. The
|
|
4016
|
+
min-height is the available height rather than a fixed rem value, so the fallback can
|
|
4017
|
+
never be taller than the screen it is rescuing. */
|
|
4018
|
+
@media (max-height: ${SHORT_VIEWPORT_PX}px) {
|
|
4019
|
+
.boff-explore-viewport {
|
|
4020
|
+
height: auto;
|
|
4021
|
+
min-height: calc(100vh - var(--boff-explore-offset, 0px));
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
@supports (height: 100dvh) {
|
|
4025
|
+
@media (max-height: ${SHORT_VIEWPORT_PX}px) {
|
|
4026
|
+
.boff-explore-viewport {
|
|
4027
|
+
min-height: calc(100dvh - var(--boff-explore-offset, 0px));
|
|
3753
4028
|
}
|
|
3754
|
-
cell += char;
|
|
3755
4029
|
}
|
|
3756
|
-
cells.push(cell.trim());
|
|
3757
|
-
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
3758
|
-
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
3759
|
-
return cells;
|
|
3760
4030
|
}
|
|
3761
|
-
|
|
3762
|
-
|
|
4031
|
+
|
|
4032
|
+
/* ---- Composer dock --------------------------------------------------------------------
|
|
4033
|
+
Three declarations, each the fallback for the one after it: a plain 1rem where \`max()\`
|
|
4034
|
+
is unknown, then the measured overlap of any FOREIGN fixed chrome sitting over the
|
|
4035
|
+
composer (the host sites' cookie banner is 229px tall and lands exactly there), then the
|
|
4036
|
+
same plus the home-indicator inset. \`--boff-explore-chrome\` is set on the root only once
|
|
4037
|
+
an overlap has actually been measured, so everywhere else this collapses to 1rem. */
|
|
4038
|
+
.boff-explore-composer {
|
|
4039
|
+
padding-bottom: 1rem;
|
|
4040
|
+
padding-bottom: max(1rem, var(--boff-explore-chrome, 0px));
|
|
4041
|
+
padding-bottom: max(1rem, env(safe-area-inset-bottom), var(--boff-explore-chrome, 0px));
|
|
3763
4042
|
}
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
const spoken = [];
|
|
3768
|
-
for (const row of rows.slice(2)) {
|
|
3769
|
-
const parts = [];
|
|
3770
|
-
tableCells(row).forEach((value, column) => {
|
|
3771
|
-
if (value === "") return;
|
|
3772
|
-
const label = column === 0 ? "" : labels[column] ?? "";
|
|
3773
|
-
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
3774
|
-
});
|
|
3775
|
-
if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
|
|
3776
|
-
}
|
|
3777
|
-
return spoken;
|
|
4043
|
+
`;
|
|
4044
|
+
function ExploreStyles() {
|
|
4045
|
+
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
|
|
3778
4046
|
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
if (inFence) {
|
|
3795
|
-
lines.push(raw);
|
|
3796
|
-
continue;
|
|
3797
|
-
}
|
|
3798
|
-
if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
|
|
3799
|
-
table.push(raw);
|
|
3800
|
-
continue;
|
|
3801
|
-
}
|
|
3802
|
-
flushTable();
|
|
3803
|
-
lines.push(readableLine(raw, mode));
|
|
4047
|
+
var CAPABILITIES = [
|
|
4048
|
+
{
|
|
4049
|
+
icon: BookOpenText,
|
|
4050
|
+
title: "Grounded in our own docs",
|
|
4051
|
+
body: "Every answer comes from published product pages and documentation \u2014 never invented."
|
|
4052
|
+
},
|
|
4053
|
+
{
|
|
4054
|
+
icon: Layers,
|
|
4055
|
+
title: "Compare across the ecosystem",
|
|
4056
|
+
body: "Ask which product fits your team, or how two of them work together."
|
|
4057
|
+
},
|
|
4058
|
+
{
|
|
4059
|
+
icon: Link2,
|
|
4060
|
+
title: "Straight to the right page",
|
|
4061
|
+
body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
|
|
3804
4062
|
}
|
|
3805
|
-
|
|
3806
|
-
|
|
4063
|
+
];
|
|
4064
|
+
var MIN_VISIBLE_FRACTION = 0.25;
|
|
4065
|
+
var SDK_OVERLAY_SELECTOR = "[data-boff-explore]";
|
|
4066
|
+
var CTA_VARIANTS = {
|
|
4067
|
+
CONTACT: "default",
|
|
4068
|
+
WAITLIST: "default",
|
|
4069
|
+
APP: "default",
|
|
4070
|
+
PRICING: "secondary",
|
|
4071
|
+
STORE: "secondary",
|
|
4072
|
+
PARTNERS: "secondary",
|
|
4073
|
+
DOCS: "outline",
|
|
4074
|
+
SERVICES: "outline",
|
|
4075
|
+
OTHER: "outline"
|
|
4076
|
+
};
|
|
4077
|
+
function normalizeOffset(offset) {
|
|
4078
|
+
if (offset === void 0) return "0px";
|
|
4079
|
+
return typeof offset === "number" ? `${offset}px` : offset;
|
|
3807
4080
|
}
|
|
3808
|
-
function
|
|
3809
|
-
|
|
4081
|
+
function isOwnElement(el, root) {
|
|
4082
|
+
if (el === document.body || el === document.documentElement) return true;
|
|
4083
|
+
if (root.contains(el)) return true;
|
|
4084
|
+
return el.closest(SDK_OVERLAY_SELECTOR) !== null;
|
|
3810
4085
|
}
|
|
3811
|
-
function
|
|
3812
|
-
return
|
|
4086
|
+
function visualViewportHeight() {
|
|
4087
|
+
return window.visualViewport?.height ?? window.innerHeight;
|
|
3813
4088
|
}
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
function AnswerLink(props) {
|
|
3817
|
-
return /* @__PURE__ */ jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsx(MarkdownLink, { ...props }) });
|
|
4089
|
+
function clamp2(value, min, max) {
|
|
4090
|
+
return Math.min(Math.max(value, min), max);
|
|
3818
4091
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
const
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
}
|
|
3826
|
-
const { strings: T, onFailed } = scope;
|
|
3827
|
-
const picture = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3828
|
-
/* @__PURE__ */ jsx(
|
|
3829
|
-
"img",
|
|
3830
|
-
{
|
|
3831
|
-
src,
|
|
3832
|
-
alt: alt ?? "",
|
|
3833
|
-
loading: "lazy",
|
|
3834
|
-
decoding: "async",
|
|
3835
|
-
onError: () => onFailed(src),
|
|
3836
|
-
className: "block h-auto max-h-80 w-auto max-w-full object-contain"
|
|
3837
|
-
}
|
|
3838
|
-
),
|
|
3839
|
-
/* @__PURE__ */ jsx(
|
|
3840
|
-
"span",
|
|
3841
|
-
{
|
|
3842
|
-
"aria-hidden": "true",
|
|
3843
|
-
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",
|
|
3844
|
-
children: T.conceptIllustration
|
|
3845
|
-
}
|
|
3846
|
-
)
|
|
3847
|
-
] });
|
|
3848
|
-
return /* @__PURE__ */ jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxs(
|
|
3849
|
-
"a",
|
|
3850
|
-
{
|
|
3851
|
-
href: src,
|
|
3852
|
-
target: "_blank",
|
|
3853
|
-
rel: "noopener noreferrer",
|
|
3854
|
-
className: cn(
|
|
3855
|
-
INLINE_IMAGE_FRAME,
|
|
3856
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
3857
|
-
),
|
|
3858
|
-
children: [
|
|
3859
|
-
picture,
|
|
3860
|
-
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
3861
|
-
"(",
|
|
3862
|
-
T.opensInNewTab,
|
|
3863
|
-
")"
|
|
3864
|
-
] })
|
|
3865
|
-
]
|
|
3866
|
-
}
|
|
3867
|
-
) });
|
|
4092
|
+
function isRestoredNavigation() {
|
|
4093
|
+
const entries = window.performance?.getEntriesByType?.("navigation");
|
|
4094
|
+
const entry = entries?.[0];
|
|
4095
|
+
if (!entry) return true;
|
|
4096
|
+
const navigation = entry;
|
|
4097
|
+
return navigation.type === "reload" || navigation.type === "back_forward";
|
|
3868
4098
|
}
|
|
3869
|
-
function
|
|
3870
|
-
if (
|
|
3871
|
-
|
|
3872
|
-
return (node.children ?? []).map(markdownText).join("");
|
|
4099
|
+
function pageOwnsScrolling(el) {
|
|
4100
|
+
if (window.innerHeight > SHORT_VIEWPORT_PX) return false;
|
|
4101
|
+
return el.scrollHeight - el.clientHeight <= 1;
|
|
3873
4102
|
}
|
|
3874
|
-
function
|
|
3875
|
-
return
|
|
4103
|
+
function isInternalPath(url) {
|
|
4104
|
+
return url.startsWith("/") && !url.startsWith("//");
|
|
3876
4105
|
}
|
|
3877
|
-
function
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
4106
|
+
function hostnameOf(url) {
|
|
4107
|
+
try {
|
|
4108
|
+
return new URL(url).hostname.replace(/^www\./, "");
|
|
4109
|
+
} catch {
|
|
4110
|
+
return "";
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
function initialOf(value, fallback) {
|
|
4114
|
+
const source = (value || fallback).trim();
|
|
4115
|
+
return source ? source.slice(0, 1).toUpperCase() : "?";
|
|
4116
|
+
}
|
|
4117
|
+
function logoUrlOf(url) {
|
|
4118
|
+
try {
|
|
4119
|
+
const { origin, protocol } = new URL(url);
|
|
4120
|
+
if (protocol !== "https:" && protocol !== "http:") return null;
|
|
4121
|
+
return `${origin}/favicon.svg`;
|
|
4122
|
+
} catch {
|
|
4123
|
+
return null;
|
|
3882
4124
|
}
|
|
3883
|
-
return null;
|
|
3884
4125
|
}
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
4126
|
+
var REFERENCE_KIND_ORDER = [
|
|
4127
|
+
"CONCEPT",
|
|
4128
|
+
"DOCS",
|
|
4129
|
+
"PRICING",
|
|
4130
|
+
"USE_CASE",
|
|
4131
|
+
"PAGE"
|
|
4132
|
+
];
|
|
4133
|
+
var PRODUCT_SUBDOMAINS = [
|
|
4134
|
+
"alphadocs.",
|
|
4135
|
+
"alphaapp.",
|
|
4136
|
+
"docs.",
|
|
4137
|
+
"alpha.",
|
|
4138
|
+
"app.",
|
|
4139
|
+
"www."
|
|
4140
|
+
];
|
|
4141
|
+
function readCatalogUrl(raw) {
|
|
4142
|
+
let parsed;
|
|
4143
|
+
try {
|
|
4144
|
+
parsed = new URL(raw);
|
|
4145
|
+
} catch {
|
|
4146
|
+
return null;
|
|
4147
|
+
}
|
|
4148
|
+
if (parsed.protocol !== "https:") return null;
|
|
4149
|
+
const host = parsed.hostname.toLowerCase();
|
|
4150
|
+
const prefix = PRODUCT_SUBDOMAINS.find(
|
|
4151
|
+
(candidate) => host.startsWith(candidate)
|
|
4152
|
+
);
|
|
4153
|
+
const apex = prefix === void 0 ? host : host.slice(prefix.length);
|
|
4154
|
+
const port = parsed.port ? `:${parsed.port}` : "";
|
|
4155
|
+
return {
|
|
4156
|
+
origin: `https://${apex}${port}`,
|
|
4157
|
+
prefix,
|
|
4158
|
+
pathname: parsed.pathname
|
|
4159
|
+
};
|
|
3889
4160
|
}
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
4161
|
+
function referenceKindOf(url, origins) {
|
|
4162
|
+
const parsed = readCatalogUrl(url);
|
|
4163
|
+
if (parsed === null || !origins.has(parsed.origin)) return null;
|
|
4164
|
+
if (parsed.prefix === "docs." || parsed.prefix === "alphadocs.")
|
|
4165
|
+
return "DOCS";
|
|
4166
|
+
const segment = parsed.pathname.toLowerCase().split("/").filter(Boolean)[0] ?? "";
|
|
4167
|
+
if (segment === "concept" || segment === "concepts") return "CONCEPT";
|
|
4168
|
+
if (segment === "docs") return "DOCS";
|
|
4169
|
+
if (segment === "pricing" || segment === "plans") return "PRICING";
|
|
4170
|
+
if (segment === "use-case" || segment === "use-cases" || segment === "solutions") {
|
|
4171
|
+
return "USE_CASE";
|
|
4172
|
+
}
|
|
4173
|
+
return "PAGE";
|
|
4174
|
+
}
|
|
4175
|
+
function productNamesByOrigin(products) {
|
|
4176
|
+
const names = /* @__PURE__ */ new Map();
|
|
4177
|
+
for (const product of products) {
|
|
4178
|
+
let url;
|
|
4179
|
+
try {
|
|
4180
|
+
url = new URL(product.website);
|
|
4181
|
+
} catch {
|
|
4182
|
+
continue;
|
|
3904
4183
|
}
|
|
3905
|
-
|
|
4184
|
+
if (url.protocol !== "https:") continue;
|
|
4185
|
+
const apex = url.hostname.replace(/^www\./, "");
|
|
4186
|
+
const port = url.port ? `:${url.port}` : "";
|
|
4187
|
+
const key = `https://${apex}${port}`;
|
|
4188
|
+
if (!names.has(key)) names.set(key, product.name);
|
|
4189
|
+
}
|
|
4190
|
+
return names;
|
|
3906
4191
|
}
|
|
3907
|
-
function
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
4192
|
+
function summariseReferences(references, origins, productNames) {
|
|
4193
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4194
|
+
const products = [];
|
|
4195
|
+
for (const reference of references) {
|
|
4196
|
+
const kind = referenceKindOf(reference.url, origins);
|
|
4197
|
+
if (kind !== null) counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
4198
|
+
const parsed = readCatalogUrl(reference.url);
|
|
4199
|
+
const name = (parsed === null ? void 0 : productNames.get(parsed.origin)) ?? (reference.product || null);
|
|
4200
|
+
if (name != null && !products.includes(name)) products.push(name);
|
|
3915
4201
|
}
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
"data-label": label
|
|
3924
|
-
});
|
|
3925
|
-
});
|
|
3926
|
-
return /* @__PURE__ */ jsx("tr", { ...props, children: cells });
|
|
4202
|
+
return {
|
|
4203
|
+
total: references.length,
|
|
4204
|
+
kinds: REFERENCE_KIND_ORDER.filter((kind) => counts.has(kind)).map(
|
|
4205
|
+
(kind) => ({ kind, count: counts.get(kind) ?? 0 })
|
|
4206
|
+
),
|
|
4207
|
+
products
|
|
4208
|
+
};
|
|
3927
4209
|
}
|
|
3928
|
-
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
3929
|
-
a: AnswerLink,
|
|
3930
|
-
img: InlineConceptImage,
|
|
3931
|
-
table: AnswerTable,
|
|
3932
|
-
tr: AnswerTableRow
|
|
3933
|
-
};
|
|
3934
4210
|
function ReferenceCard({
|
|
3935
4211
|
reference,
|
|
3936
4212
|
onFollow
|
|
@@ -4269,6 +4545,195 @@ function AnswerFeedback({
|
|
|
4269
4545
|
}
|
|
4270
4546
|
);
|
|
4271
4547
|
}
|
|
4548
|
+
function EscalateForm({
|
|
4549
|
+
strings: T,
|
|
4550
|
+
formId,
|
|
4551
|
+
onEscalate,
|
|
4552
|
+
onClose,
|
|
4553
|
+
onBusyChange
|
|
4554
|
+
}) {
|
|
4555
|
+
const [email, setEmail] = useState("");
|
|
4556
|
+
const [note, setNote] = useState("");
|
|
4557
|
+
const [error, setError] = useState(null);
|
|
4558
|
+
const [sending, setSending] = useState(false);
|
|
4559
|
+
const [sentTo, setSentTo] = useState(null);
|
|
4560
|
+
const emailRef = useRef(null);
|
|
4561
|
+
const inFlightRef = useRef(false);
|
|
4562
|
+
const titleId = `${formId}-title`;
|
|
4563
|
+
const emailId = `${formId}-email`;
|
|
4564
|
+
const noteId = `${formId}-note`;
|
|
4565
|
+
const errorId = `${formId}-error`;
|
|
4566
|
+
useEffect(() => {
|
|
4567
|
+
emailRef.current?.focus();
|
|
4568
|
+
}, []);
|
|
4569
|
+
useEffect(() => {
|
|
4570
|
+
onBusyChange?.(sending);
|
|
4571
|
+
return () => onBusyChange?.(false);
|
|
4572
|
+
}, [sending, onBusyChange]);
|
|
4573
|
+
const handleSubmit = useCallback(
|
|
4574
|
+
async (event) => {
|
|
4575
|
+
event.preventDefault();
|
|
4576
|
+
if (inFlightRef.current) return;
|
|
4577
|
+
const address = email.trim();
|
|
4578
|
+
if (!looksLikeDeliverableEmail(address)) {
|
|
4579
|
+
setError(T.escalateInvalidEmail);
|
|
4580
|
+
emailRef.current?.focus();
|
|
4581
|
+
return;
|
|
4582
|
+
}
|
|
4583
|
+
inFlightRef.current = true;
|
|
4584
|
+
setSending(true);
|
|
4585
|
+
setError(null);
|
|
4586
|
+
const result = await onEscalate(address, note);
|
|
4587
|
+
inFlightRef.current = false;
|
|
4588
|
+
setSending(false);
|
|
4589
|
+
switch (result.outcome) {
|
|
4590
|
+
case "sent":
|
|
4591
|
+
case "already-handed-over":
|
|
4592
|
+
setSentTo(result.email ?? address);
|
|
4593
|
+
return;
|
|
4594
|
+
case "invalid-email":
|
|
4595
|
+
setError(T.escalateInvalidEmail);
|
|
4596
|
+
emailRef.current?.focus();
|
|
4597
|
+
return;
|
|
4598
|
+
case "rate-limited":
|
|
4599
|
+
setError(T.escalateRateLimited);
|
|
4600
|
+
return;
|
|
4601
|
+
case "captcha":
|
|
4602
|
+
setError(T.escalateCaptcha);
|
|
4603
|
+
return;
|
|
4604
|
+
// `no-conversation` included: the form should not have been offered at all, and the
|
|
4605
|
+
// honest thing to show is that nothing was passed on.
|
|
4606
|
+
default:
|
|
4607
|
+
setError(T.escalateFailed);
|
|
4608
|
+
return;
|
|
4609
|
+
}
|
|
4610
|
+
},
|
|
4611
|
+
[T, email, note, onEscalate]
|
|
4612
|
+
);
|
|
4613
|
+
if (sentTo !== null) {
|
|
4614
|
+
return /* @__PURE__ */ jsx(
|
|
4615
|
+
"p",
|
|
4616
|
+
{
|
|
4617
|
+
id: formId,
|
|
4618
|
+
"data-boff-explore": "escalate-sent",
|
|
4619
|
+
role: "status",
|
|
4620
|
+
className: "boff-explore-fade mt-2 rounded-xl border border-border bg-card px-3 py-2 text-xs leading-relaxed text-card-foreground",
|
|
4621
|
+
children: fill(T.escalateSent, { email: sentTo })
|
|
4622
|
+
}
|
|
4623
|
+
);
|
|
4624
|
+
}
|
|
4625
|
+
return /* @__PURE__ */ jsxs(
|
|
4626
|
+
"form",
|
|
4627
|
+
{
|
|
4628
|
+
id: formId,
|
|
4629
|
+
"data-boff-explore": "escalate-form",
|
|
4630
|
+
"aria-labelledby": titleId,
|
|
4631
|
+
noValidate: true,
|
|
4632
|
+
onSubmit: handleSubmit,
|
|
4633
|
+
onKeyDown: (event) => {
|
|
4634
|
+
if (event.key !== "Escape") return;
|
|
4635
|
+
event.stopPropagation();
|
|
4636
|
+
if (sending) return;
|
|
4637
|
+
onClose();
|
|
4638
|
+
},
|
|
4639
|
+
className: "boff-explore-fade mt-2 space-y-2 rounded-xl border border-border bg-card p-3",
|
|
4640
|
+
children: [
|
|
4641
|
+
/* @__PURE__ */ jsx("p", { id: titleId, className: "text-xs font-medium text-card-foreground", children: T.escalateTitle }),
|
|
4642
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: T.escalateBody }),
|
|
4643
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
4644
|
+
/* @__PURE__ */ jsx(
|
|
4645
|
+
"label",
|
|
4646
|
+
{
|
|
4647
|
+
htmlFor: emailId,
|
|
4648
|
+
className: "block text-xs font-medium text-card-foreground",
|
|
4649
|
+
children: T.escalateEmailLabel
|
|
4650
|
+
}
|
|
4651
|
+
),
|
|
4652
|
+
/* @__PURE__ */ jsx(
|
|
4653
|
+
"input",
|
|
4654
|
+
{
|
|
4655
|
+
ref: emailRef,
|
|
4656
|
+
id: emailId,
|
|
4657
|
+
name: "email",
|
|
4658
|
+
"data-boff-explore": "escalate-email",
|
|
4659
|
+
type: "email",
|
|
4660
|
+
inputMode: "email",
|
|
4661
|
+
autoComplete: "email",
|
|
4662
|
+
required: true,
|
|
4663
|
+
value: email,
|
|
4664
|
+
onChange: (event) => {
|
|
4665
|
+
setEmail(event.target.value);
|
|
4666
|
+
if (error) setError(null);
|
|
4667
|
+
},
|
|
4668
|
+
"aria-invalid": error ? "true" : void 0,
|
|
4669
|
+
"aria-describedby": error ? errorId : void 0,
|
|
4670
|
+
placeholder: T.escalateEmailPlaceholder,
|
|
4671
|
+
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"
|
|
4672
|
+
}
|
|
4673
|
+
)
|
|
4674
|
+
] }),
|
|
4675
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
4676
|
+
/* @__PURE__ */ jsx(
|
|
4677
|
+
"label",
|
|
4678
|
+
{
|
|
4679
|
+
htmlFor: noteId,
|
|
4680
|
+
className: "block text-xs font-medium text-card-foreground",
|
|
4681
|
+
children: T.escalateNoteLabel
|
|
4682
|
+
}
|
|
4683
|
+
),
|
|
4684
|
+
/* @__PURE__ */ jsx(
|
|
4685
|
+
Textarea,
|
|
4686
|
+
{
|
|
4687
|
+
id: noteId,
|
|
4688
|
+
name: "note",
|
|
4689
|
+
"data-boff-explore": "escalate-note",
|
|
4690
|
+
rows: 2,
|
|
4691
|
+
value: note,
|
|
4692
|
+
onChange: (event) => setNote(event.target.value),
|
|
4693
|
+
maxLength: EXPLORE_ESCALATION_NOTE_SOFT_MAX,
|
|
4694
|
+
className: "min-h-0 border-border bg-background"
|
|
4695
|
+
}
|
|
4696
|
+
)
|
|
4697
|
+
] }),
|
|
4698
|
+
error ? /* @__PURE__ */ jsx(
|
|
4699
|
+
"p",
|
|
4700
|
+
{
|
|
4701
|
+
id: errorId,
|
|
4702
|
+
"data-boff-explore": "escalate-error",
|
|
4703
|
+
role: "alert",
|
|
4704
|
+
className: "text-xs leading-relaxed text-destructive",
|
|
4705
|
+
children: error
|
|
4706
|
+
}
|
|
4707
|
+
) : null,
|
|
4708
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
4709
|
+
/* @__PURE__ */ jsx(
|
|
4710
|
+
Button,
|
|
4711
|
+
{
|
|
4712
|
+
"data-boff-explore": "escalate-send",
|
|
4713
|
+
type: "submit",
|
|
4714
|
+
size: "sm",
|
|
4715
|
+
disabled: sending,
|
|
4716
|
+
children: sending ? T.escalateSending : T.escalateSubmit
|
|
4717
|
+
}
|
|
4718
|
+
),
|
|
4719
|
+
/* @__PURE__ */ jsx(
|
|
4720
|
+
Button,
|
|
4721
|
+
{
|
|
4722
|
+
"data-boff-explore": "escalate-cancel",
|
|
4723
|
+
type: "button",
|
|
4724
|
+
size: "sm",
|
|
4725
|
+
variant: "ghost",
|
|
4726
|
+
className: "text-muted-foreground",
|
|
4727
|
+
disabled: sending,
|
|
4728
|
+
onClick: onClose,
|
|
4729
|
+
children: T.escalateCancel
|
|
4730
|
+
}
|
|
4731
|
+
)
|
|
4732
|
+
] })
|
|
4733
|
+
]
|
|
4734
|
+
}
|
|
4735
|
+
);
|
|
4736
|
+
}
|
|
4272
4737
|
var PROVENANCE_CHIP = "inline-flex max-w-full items-center gap-1 rounded-full border border-border bg-muted/60 px-2 py-0.5 text-[11px] leading-5 text-muted-foreground";
|
|
4273
4738
|
function ProvenanceChips({
|
|
4274
4739
|
strings: T,
|
|
@@ -4462,31 +4927,15 @@ function AssistantBubble({
|
|
|
4462
4927
|
anchorRef,
|
|
4463
4928
|
onFollow,
|
|
4464
4929
|
onFollowUp,
|
|
4465
|
-
onFeedback
|
|
4930
|
+
onFeedback,
|
|
4931
|
+
onEscalate,
|
|
4932
|
+
isLatestAnswer = false
|
|
4466
4933
|
}) {
|
|
4467
4934
|
const streaming = message.status === "PENDING" || message.status === "RUNNING";
|
|
4468
4935
|
const body = message.content || message.partialContent || "";
|
|
4469
4936
|
const failed = message.status === "FAILED";
|
|
4470
4937
|
const completed = message.status === "COMPLETED";
|
|
4471
4938
|
const visible = useProgressiveReveal(body, !streaming);
|
|
4472
|
-
const [failedImages, setFailedImages] = useState(
|
|
4473
|
-
() => /* @__PURE__ */ new Set()
|
|
4474
|
-
);
|
|
4475
|
-
const markImageFailed = useCallback((src) => {
|
|
4476
|
-
setFailedImages(
|
|
4477
|
-
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
4478
|
-
);
|
|
4479
|
-
}, []);
|
|
4480
|
-
const answerScope = useMemo(
|
|
4481
|
-
() => ({
|
|
4482
|
-
strings: T,
|
|
4483
|
-
enabled: completed,
|
|
4484
|
-
origins: imageOrigins,
|
|
4485
|
-
failed: failedImages,
|
|
4486
|
-
onFailed: markImageFailed
|
|
4487
|
-
}),
|
|
4488
|
-
[T, completed, imageOrigins, failedImages, markImageFailed]
|
|
4489
|
-
);
|
|
4490
4939
|
const provenance = useMemo(
|
|
4491
4940
|
() => summariseReferences(message.references, imageOrigins, productNames),
|
|
4492
4941
|
[message.references, imageOrigins, productNames]
|
|
@@ -4500,6 +4949,20 @@ function AssistantBubble({
|
|
|
4500
4949
|
[message.content]
|
|
4501
4950
|
);
|
|
4502
4951
|
const grounded = completed && message.answered !== false;
|
|
4952
|
+
const [escalateOpen, setEscalateOpen] = useState(false);
|
|
4953
|
+
const [escalateBusy, setEscalateBusy] = useState(false);
|
|
4954
|
+
const escalateTriggerRef = useRef(null);
|
|
4955
|
+
const escalateFormId = `boff-explore-escalate-${message.id}`;
|
|
4956
|
+
const unanswered = completed && message.answered === false;
|
|
4957
|
+
const offerEscalate = Boolean(onEscalate) && completed;
|
|
4958
|
+
const showQuietEscalate = offerEscalate && !unanswered && isLatestAnswer;
|
|
4959
|
+
const closeEscalate = useCallback(() => {
|
|
4960
|
+
setEscalateOpen(false);
|
|
4961
|
+
escalateTriggerRef.current?.focus();
|
|
4962
|
+
}, []);
|
|
4963
|
+
const handleEscalateBusy = useCallback((busy) => {
|
|
4964
|
+
setEscalateBusy(busy);
|
|
4965
|
+
}, []);
|
|
4503
4966
|
return /* @__PURE__ */ jsxs(
|
|
4504
4967
|
"div",
|
|
4505
4968
|
{
|
|
@@ -4517,7 +4980,7 @@ function AssistantBubble({
|
|
|
4517
4980
|
}
|
|
4518
4981
|
),
|
|
4519
4982
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
|
|
4520
|
-
|
|
4983
|
+
unanswered ? /* @__PURE__ */ jsxs(
|
|
4521
4984
|
"div",
|
|
4522
4985
|
{
|
|
4523
4986
|
"data-boff-explore": "no-answer",
|
|
@@ -4527,18 +4990,39 @@ function AssistantBubble({
|
|
|
4527
4990
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
4528
4991
|
T.noAnswer,
|
|
4529
4992
|
" ",
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4993
|
+
offerEscalate ? (
|
|
4994
|
+
/* The point of the wave: the same words, but they now open a form that
|
|
4995
|
+
sends the conversation we are both looking at, instead of a link to a
|
|
4996
|
+
blank contact page that asks the visitor to type it all again. */
|
|
4997
|
+
/* @__PURE__ */ jsx(
|
|
4998
|
+
"button",
|
|
4999
|
+
{
|
|
5000
|
+
ref: escalateTriggerRef,
|
|
5001
|
+
"data-boff-explore": "escalate-open",
|
|
5002
|
+
type: "button",
|
|
5003
|
+
"aria-expanded": escalateOpen,
|
|
5004
|
+
"aria-controls": escalateOpen ? escalateFormId : void 0,
|
|
5005
|
+
onClick: () => setEscalateOpen(true),
|
|
5006
|
+
className: "rounded-sm underline underline-offset-2 hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
5007
|
+
children: T.askHuman
|
|
5008
|
+
}
|
|
5009
|
+
)
|
|
5010
|
+
) : (
|
|
5011
|
+
/* No conversation to hand over (a shared, read-only view). The old dead end
|
|
5012
|
+
is still better than a control whose submit could only fail. */
|
|
5013
|
+
/* @__PURE__ */ jsx(
|
|
5014
|
+
"a",
|
|
5015
|
+
{
|
|
5016
|
+
href: "/contact",
|
|
5017
|
+
className: "underline underline-offset-2 hover:text-primary",
|
|
5018
|
+
onClick: (event) => {
|
|
5019
|
+
if (!onNavigate) return;
|
|
5020
|
+
event.preventDefault();
|
|
5021
|
+
onNavigate("/contact");
|
|
5022
|
+
},
|
|
5023
|
+
children: T.askHuman
|
|
5024
|
+
}
|
|
5025
|
+
)
|
|
4542
5026
|
),
|
|
4543
5027
|
"."
|
|
4544
5028
|
] })
|
|
@@ -4546,7 +5030,16 @@ function AssistantBubble({
|
|
|
4546
5030
|
}
|
|
4547
5031
|
) : null,
|
|
4548
5032
|
visible ? /* @__PURE__ */ jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
|
|
4549
|
-
/* @__PURE__ */ jsx(
|
|
5033
|
+
/* @__PURE__ */ jsx(
|
|
5034
|
+
ExploreAnswerBody,
|
|
5035
|
+
{
|
|
5036
|
+
strings: T,
|
|
5037
|
+
imageOrigins,
|
|
5038
|
+
enabled: completed,
|
|
5039
|
+
className: "boff-prose-sm text-card-foreground",
|
|
5040
|
+
children: visible
|
|
5041
|
+
}
|
|
5042
|
+
),
|
|
4550
5043
|
streaming ? /* @__PURE__ */ jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
|
|
4551
5044
|
] }) : null,
|
|
4552
5045
|
grounded && provenance.total > 0 ? /* @__PURE__ */ jsx(ProvenanceChips, { strings: T, provenance }) : null,
|
|
@@ -4606,6 +5099,30 @@ function AssistantBubble({
|
|
|
4606
5099
|
feedback: message.feedback,
|
|
4607
5100
|
onSubmit: onFeedback
|
|
4608
5101
|
}
|
|
5102
|
+
) : null,
|
|
5103
|
+
showQuietEscalate ? /* @__PURE__ */ jsx("div", { className: "pt-0.5", children: /* @__PURE__ */ jsx(
|
|
5104
|
+
"button",
|
|
5105
|
+
{
|
|
5106
|
+
ref: escalateTriggerRef,
|
|
5107
|
+
"data-boff-explore": "escalate-open",
|
|
5108
|
+
type: "button",
|
|
5109
|
+
"aria-expanded": escalateOpen,
|
|
5110
|
+
"aria-controls": escalateOpen ? escalateFormId : void 0,
|
|
5111
|
+
disabled: escalateBusy,
|
|
5112
|
+
onClick: () => setEscalateOpen((open) => !open),
|
|
5113
|
+
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",
|
|
5114
|
+
children: T.escalateOpen
|
|
5115
|
+
}
|
|
5116
|
+
) }) : null,
|
|
5117
|
+
escalateOpen && onEscalate ? /* @__PURE__ */ jsx(
|
|
5118
|
+
EscalateForm,
|
|
5119
|
+
{
|
|
5120
|
+
strings: T,
|
|
5121
|
+
formId: escalateFormId,
|
|
5122
|
+
onEscalate,
|
|
5123
|
+
onClose: closeEscalate,
|
|
5124
|
+
onBusyChange: handleEscalateBusy
|
|
5125
|
+
}
|
|
4609
5126
|
) : null
|
|
4610
5127
|
] })
|
|
4611
5128
|
]
|
|
@@ -4831,7 +5348,7 @@ function ConceptStrip({
|
|
|
4831
5348
|
"div",
|
|
4832
5349
|
{
|
|
4833
5350
|
"data-boff-explore": "concept-strip-block",
|
|
4834
|
-
className: "boff-explore-rise boff-explore-rise-
|
|
5351
|
+
className: "boff-explore-rise boff-explore-rise-3 mt-6",
|
|
4835
5352
|
children: [
|
|
4836
5353
|
/* @__PURE__ */ jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: T.conceptStripTitle }),
|
|
4837
5354
|
/* @__PURE__ */ jsx(
|
|
@@ -5021,15 +5538,6 @@ function WelcomeState({
|
|
|
5021
5538
|
/* @__PURE__ */ jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
|
|
5022
5539
|
/* @__PURE__ */ jsx("p", { className: "mx-auto mt-2.5 max-w-2xl text-pretty text-sm leading-relaxed text-muted-foreground sm:text-lg", children: body })
|
|
5023
5540
|
] }),
|
|
5024
|
-
/* @__PURE__ */ jsx(
|
|
5025
|
-
ConceptStrip,
|
|
5026
|
-
{
|
|
5027
|
-
strings: T,
|
|
5028
|
-
highlights: conceptHighlights,
|
|
5029
|
-
onAsk: onPrompt,
|
|
5030
|
-
disabled
|
|
5031
|
-
}
|
|
5032
|
-
),
|
|
5033
5541
|
/* @__PURE__ */ jsx("ul", { className: "boff-explore-rise boff-explore-rise-1 mt-6 grid grid-cols-3 gap-2 sm:mt-7 sm:gap-2.5", children: CAPABILITIES.map(({ icon: Icon, body: capBody }, i) => /* @__PURE__ */ jsxs(
|
|
5034
5542
|
"li",
|
|
5035
5543
|
{
|
|
@@ -5065,7 +5573,16 @@ function WelcomeState({
|
|
|
5065
5573
|
onFocus
|
|
5066
5574
|
}
|
|
5067
5575
|
)
|
|
5068
|
-
] }) : null
|
|
5576
|
+
] }) : null,
|
|
5577
|
+
/* @__PURE__ */ jsx(
|
|
5578
|
+
ConceptStrip,
|
|
5579
|
+
{
|
|
5580
|
+
strings: T,
|
|
5581
|
+
highlights: conceptHighlights,
|
|
5582
|
+
onAsk: onPrompt,
|
|
5583
|
+
disabled
|
|
5584
|
+
}
|
|
5585
|
+
)
|
|
5069
5586
|
] });
|
|
5070
5587
|
}
|
|
5071
5588
|
function ExplorePage({
|
|
@@ -5112,6 +5629,8 @@ function ExplorePage({
|
|
|
5112
5629
|
clearHistory,
|
|
5113
5630
|
recordClick,
|
|
5114
5631
|
submitFeedback,
|
|
5632
|
+
escalate,
|
|
5633
|
+
canEscalate,
|
|
5115
5634
|
createShareLink,
|
|
5116
5635
|
isSharedView
|
|
5117
5636
|
} = useExploreChat({
|
|
@@ -5555,6 +6074,7 @@ function ExplorePage({
|
|
|
5555
6074
|
),
|
|
5556
6075
|
/* @__PURE__ */ jsx(ProseStyles, {}),
|
|
5557
6076
|
/* @__PURE__ */ jsx(ExploreStyles, {}),
|
|
6077
|
+
/* @__PURE__ */ jsx(ExploreAnswerStyles, {}),
|
|
5558
6078
|
!showWelcome ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 border-b border-border px-4 py-2.5", children: [
|
|
5559
6079
|
/* @__PURE__ */ jsx(
|
|
5560
6080
|
"span",
|
|
@@ -5733,7 +6253,9 @@ function ExplorePage({
|
|
|
5733
6253
|
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
|
|
5734
6254
|
onFollow: (kind, url) => recordClick(message.id, kind, url),
|
|
5735
6255
|
onFollowUp: sendPrompt,
|
|
5736
|
-
onFeedback: isSharedView ? void 0 : (rating, detail) => submitFeedback(message.id, rating, detail)
|
|
6256
|
+
onFeedback: isSharedView ? void 0 : (rating, detail) => submitFeedback(message.id, rating, detail),
|
|
6257
|
+
onEscalate: canEscalate ? escalate : void 0,
|
|
6258
|
+
isLatestAnswer: message.id === latestAssistantId
|
|
5737
6259
|
},
|
|
5738
6260
|
message.id
|
|
5739
6261
|
)
|
|
@@ -5840,10 +6362,19 @@ function ExplorePage({
|
|
|
5840
6362
|
/* @__PURE__ */ jsxs(
|
|
5841
6363
|
"div",
|
|
5842
6364
|
{
|
|
6365
|
+
"data-boff-explore": "composer-field",
|
|
5843
6366
|
className: cn(
|
|
5844
6367
|
"flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
|
|
5845
6368
|
busy && "boff-explore-busy",
|
|
5846
|
-
overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
|
|
6369
|
+
overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring",
|
|
6370
|
+
// The halo is an invitation, and an invitation is only useful before it has
|
|
6371
|
+
// been accepted: while the conversation is empty, the composer is usable, and
|
|
6372
|
+
// nothing has been typed. Once any of those stops being true the class goes
|
|
6373
|
+
// and the box is an ordinary card again. `-quiet` keeps the ring but stops the
|
|
6374
|
+
// movement the moment there are characters in the field, so it does not pulse
|
|
6375
|
+
// at someone who is mid-sentence.
|
|
6376
|
+
showWelcome && !composerDisabled && !overLimit && "boff-explore-invite",
|
|
6377
|
+
showWelcome && draft.length > 0 && "boff-explore-invite-quiet"
|
|
5847
6378
|
),
|
|
5848
6379
|
children: [
|
|
5849
6380
|
/* @__PURE__ */ jsx(
|