@burdenoff/website-sdk 2026.922.4 → 2026.922.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/explore.d.mts +23 -1
- package/dist/components/explore.d.ts +23 -1
- package/dist/components/explore.js +709 -39
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +711 -42
- package/dist/components/explore.mjs.map +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +356 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +354 -1
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +720 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +719 -50
- package/dist/index.mjs.map +1 -1
- package/dist/{use-explore-chat-CNEdKBc1.d.ts → use-progressive-reveal-BfU_IHip.d.ts} +35 -1
- package/dist/{use-explore-chat-Dgm-jf96.d.mts → use-progressive-reveal-Ha3gYPfs.d.mts} +35 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2438,16 +2438,16 @@ function useRateLimit(maxAttempts, windowMinutes) {
|
|
|
2438
2438
|
}
|
|
2439
2439
|
}, [maxAttempts]);
|
|
2440
2440
|
function recordAttempt() {
|
|
2441
|
-
const
|
|
2441
|
+
const now2 = Date.now();
|
|
2442
2442
|
const state = rateLimitRef.current;
|
|
2443
|
-
if (state &&
|
|
2443
|
+
if (state && now2 > state.resetTime) {
|
|
2444
2444
|
rateLimitRef.current = null;
|
|
2445
2445
|
}
|
|
2446
2446
|
const current = rateLimitRef.current;
|
|
2447
2447
|
if (!current) {
|
|
2448
2448
|
rateLimitRef.current = {
|
|
2449
2449
|
attempts: 1,
|
|
2450
|
-
resetTime:
|
|
2450
|
+
resetTime: now2 + windowMinutes * 60 * 1e3
|
|
2451
2451
|
};
|
|
2452
2452
|
return false;
|
|
2453
2453
|
}
|
|
@@ -3542,16 +3542,16 @@ function useRateLimit2(maxAttempts, windowMinutes) {
|
|
|
3542
3542
|
const [isLimited, setIsLimited] = React.useState(false);
|
|
3543
3543
|
const [resetTime, setResetTime] = React.useState(null);
|
|
3544
3544
|
function recordAttempt() {
|
|
3545
|
-
const
|
|
3545
|
+
const now2 = Date.now();
|
|
3546
3546
|
const state = rateLimitRef.current;
|
|
3547
|
-
if (state &&
|
|
3547
|
+
if (state && now2 > state.resetTime) {
|
|
3548
3548
|
rateLimitRef.current = null;
|
|
3549
3549
|
}
|
|
3550
3550
|
const current = rateLimitRef.current;
|
|
3551
3551
|
if (!current) {
|
|
3552
3552
|
rateLimitRef.current = {
|
|
3553
3553
|
attempts: 1,
|
|
3554
|
-
resetTime:
|
|
3554
|
+
resetTime: now2 + windowMinutes * 60 * 1e3
|
|
3555
3555
|
};
|
|
3556
3556
|
return false;
|
|
3557
3557
|
}
|
|
@@ -4585,8 +4585,8 @@ function LaunchCountdown({
|
|
|
4585
4585
|
launchedMessage = "We have launched!",
|
|
4586
4586
|
className
|
|
4587
4587
|
}) {
|
|
4588
|
-
const [
|
|
4589
|
-
const remainingMs = launchDate -
|
|
4588
|
+
const [now2, setNow] = React.useState(() => Date.now());
|
|
4589
|
+
const remainingMs = launchDate - now2;
|
|
4590
4590
|
const hasLaunched = remainingMs <= 0;
|
|
4591
4591
|
React.useEffect(() => {
|
|
4592
4592
|
if (hasLaunched) return;
|
|
@@ -6010,6 +6010,9 @@ var EN = {
|
|
|
6010
6010
|
conceptAskQuestion: "How does {title} work in {product}?",
|
|
6011
6011
|
copyAnswer: "Copy answer",
|
|
6012
6012
|
copyFailed: "Couldn't copy",
|
|
6013
|
+
readAloud: "Read aloud",
|
|
6014
|
+
stopReading: "Stop reading",
|
|
6015
|
+
answerTable: "Table",
|
|
6013
6016
|
groundedIn: "What this answer is based on",
|
|
6014
6017
|
sourceCountOne: "1 source",
|
|
6015
6018
|
sourceCountMany: "{count} sources",
|
|
@@ -6092,6 +6095,9 @@ var ZH = {
|
|
|
6092
6095
|
conceptAskQuestion: "{product} \u7684 {title} \u662F\u600E\u4E48\u8FD0\u4F5C\u7684\uFF1F",
|
|
6093
6096
|
copyAnswer: "\u590D\u5236\u56DE\u7B54",
|
|
6094
6097
|
copyFailed: "\u590D\u5236\u5931\u8D25",
|
|
6098
|
+
readAloud: "\u6717\u8BFB",
|
|
6099
|
+
stopReading: "\u505C\u6B62\u6717\u8BFB",
|
|
6100
|
+
answerTable: "\u8868\u683C",
|
|
6095
6101
|
groundedIn: "\u8FD9\u4E2A\u56DE\u7B54\u7684\u4F9D\u636E",
|
|
6096
6102
|
sourceCountOne: "1 \u4E2A\u6765\u6E90",
|
|
6097
6103
|
sourceCountMany: "{count} \u4E2A\u6765\u6E90",
|
|
@@ -6170,6 +6176,9 @@ var HI = {
|
|
|
6170
6176
|
conceptAskQuestion: "{product} \u092E\u0947\u0902 {title} \u0915\u0948\u0938\u0947 \u0915\u093E\u092E \u0915\u0930\u0924\u093E \u0939\u0948?",
|
|
6171
6177
|
copyAnswer: "\u0909\u0924\u094D\u0924\u0930 \u0915\u0949\u092A\u0940 \u0915\u0930\u0947\u0902",
|
|
6172
6178
|
copyFailed: "\u0915\u0949\u092A\u0940 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u093E",
|
|
6179
|
+
readAloud: "\u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0947\u0902",
|
|
6180
|
+
stopReading: "\u092A\u0922\u093C\u0928\u093E \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
|
|
6181
|
+
answerTable: "\u0924\u093E\u0932\u093F\u0915\u093E",
|
|
6173
6182
|
groundedIn: "\u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u0915\u093F\u0938 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0939\u0948",
|
|
6174
6183
|
sourceCountOne: "1 \u0938\u094D\u0930\u094B\u0924",
|
|
6175
6184
|
sourceCountMany: "{count} \u0938\u094D\u0930\u094B\u0924",
|
|
@@ -6252,6 +6261,9 @@ var ES = {
|
|
|
6252
6261
|
conceptAskQuestion: "\xBFC\xF3mo funciona {title} en {product}?",
|
|
6253
6262
|
copyAnswer: "Copiar respuesta",
|
|
6254
6263
|
copyFailed: "No se pudo copiar",
|
|
6264
|
+
readAloud: "Leer en voz alta",
|
|
6265
|
+
stopReading: "Dejar de leer",
|
|
6266
|
+
answerTable: "Tabla",
|
|
6255
6267
|
groundedIn: "En qu\xE9 se basa esta respuesta",
|
|
6256
6268
|
sourceCountOne: "1 fuente",
|
|
6257
6269
|
sourceCountMany: "{count} fuentes",
|
|
@@ -6334,6 +6346,9 @@ var AR = {
|
|
|
6334
6346
|
conceptAskQuestion: "\u0643\u064A\u0641 \u064A\u0639\u0645\u0644 {title} \u0641\u064A {product}\u061F",
|
|
6335
6347
|
copyAnswer: "\u0646\u0633\u062E \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
|
|
6336
6348
|
copyFailed: "\u062A\u0639\u0630\u0651\u0631 \u0627\u0644\u0646\u0633\u062E",
|
|
6349
|
+
readAloud: "\u0627\u0642\u0631\u0623 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D",
|
|
6350
|
+
stopReading: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0642\u0631\u0627\u0621\u0629",
|
|
6351
|
+
answerTable: "\u062C\u062F\u0648\u0644",
|
|
6337
6352
|
groundedIn: "\u0639\u0644\u0649 \u0645\u0627\u0630\u0627 \u062A\u0633\u062A\u0646\u062F \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
|
|
6338
6353
|
sourceCountOne: "\u0645\u0635\u062F\u0631 \u0648\u0627\u062D\u062F",
|
|
6339
6354
|
sourceCountMany: "{count} \u0645\u0635\u0627\u062F\u0631",
|
|
@@ -6416,6 +6431,9 @@ var TA = {
|
|
|
6416
6431
|
conceptAskQuestion: "{product} \u0B87\u0BB2\u0BCD {title} \u0B8E\u0BAA\u0BCD\u0BAA\u0B9F\u0BBF \u0BB5\u0BC7\u0BB2\u0BC8 \u0B9A\u0BC6\u0BAF\u0BCD\u0B95\u0BBF\u0BB1\u0BA4\u0BC1?",
|
|
6417
6432
|
copyAnswer: "\u0BAA\u0BA4\u0BBF\u0BB2\u0BC8 \u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1",
|
|
6418
6433
|
copyFailed: "\u0BA8\u0B95\u0BB2\u0BC6\u0B9F\u0BC1\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8",
|
|
6434
|
+
readAloud: "\u0B9A\u0BA4\u0BCD\u0BA4\u0BAE\u0BBE\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF",
|
|
6435
|
+
stopReading: "\u0BAA\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0BA4\u0BC8 \u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
|
|
6436
|
+
answerTable: "\u0B85\u0B9F\u0BCD\u0B9F\u0BB5\u0BA3\u0BC8",
|
|
6419
6437
|
groundedIn: "\u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B8E\u0BA4\u0BC8 \u0B85\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0B9F\u0BC8\u0BAF\u0BBE\u0B95\u0B95\u0BCD \u0B95\u0BCA\u0BA3\u0BCD\u0B9F\u0BA4\u0BC1",
|
|
6420
6438
|
sourceCountOne: "1 \u0BAE\u0BC2\u0BB2\u0BAE\u0BCD",
|
|
6421
6439
|
sourceCountMany: "{count} \u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
@@ -6447,6 +6465,17 @@ var STRINGS = {
|
|
|
6447
6465
|
ar: AR,
|
|
6448
6466
|
ta: TA
|
|
6449
6467
|
};
|
|
6468
|
+
var SPEECH_TAGS = {
|
|
6469
|
+
en: "en-US",
|
|
6470
|
+
zh: "zh-CN",
|
|
6471
|
+
hi: "hi-IN",
|
|
6472
|
+
es: "es-ES",
|
|
6473
|
+
ar: "ar-SA",
|
|
6474
|
+
ta: "ta-IN"
|
|
6475
|
+
};
|
|
6476
|
+
function speechLocaleOf(locale) {
|
|
6477
|
+
return SPEECH_TAGS[locale];
|
|
6478
|
+
}
|
|
6450
6479
|
function resolveExploreLocale(explicit) {
|
|
6451
6480
|
const candidates = [
|
|
6452
6481
|
explicit,
|
|
@@ -7954,6 +7983,224 @@ function makeOptimisticId() {
|
|
|
7954
7983
|
optimisticCounter += 1;
|
|
7955
7984
|
return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
|
|
7956
7985
|
}
|
|
7986
|
+
var DRAIN_TARGET_MS = 1200;
|
|
7987
|
+
var MIN_CHARS_PER_SECOND = 90;
|
|
7988
|
+
var MAX_CHARS_PER_SECOND = 2200;
|
|
7989
|
+
var MAX_STEP_MS = 250;
|
|
7990
|
+
var COMMIT_INTERVAL_MS = 45;
|
|
7991
|
+
var FALLBACK_STEP_MS = 50;
|
|
7992
|
+
var FENCE = /^ {0,3}(?:```|~~~)/;
|
|
7993
|
+
var TABLE_ROW = /^ {0,3}\|/;
|
|
7994
|
+
var EMPTY_BLOCK_MARKER = /^ {0,3}(?:[-+>]|#{1,6}|\d{1,9}[.)])\s*$/;
|
|
7995
|
+
function isWordCharacter(char) {
|
|
7996
|
+
return char !== void 0 && /[\p{L}\p{N}]/u.test(char);
|
|
7997
|
+
}
|
|
7998
|
+
function clamp(value, min, max) {
|
|
7999
|
+
return Math.min(Math.max(value, min), max);
|
|
8000
|
+
}
|
|
8001
|
+
function now() {
|
|
8002
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
8003
|
+
}
|
|
8004
|
+
function isHighSurrogate(code) {
|
|
8005
|
+
return code >= 55296 && code <= 56319;
|
|
8006
|
+
}
|
|
8007
|
+
function safeInlineEnd(line) {
|
|
8008
|
+
let codeSpan = null;
|
|
8009
|
+
let link = null;
|
|
8010
|
+
const emphasis = [];
|
|
8011
|
+
let at = 0;
|
|
8012
|
+
while (at < line.length) {
|
|
8013
|
+
const char = line[at] ?? "";
|
|
8014
|
+
if (char === "\\") {
|
|
8015
|
+
at += 2;
|
|
8016
|
+
continue;
|
|
8017
|
+
}
|
|
8018
|
+
if (char === "`") {
|
|
8019
|
+
let run = 1;
|
|
8020
|
+
while (line[at + run] === "`") run += 1;
|
|
8021
|
+
codeSpan = codeSpan === null ? at : null;
|
|
8022
|
+
at += run;
|
|
8023
|
+
continue;
|
|
8024
|
+
}
|
|
8025
|
+
if (codeSpan !== null) {
|
|
8026
|
+
at += 1;
|
|
8027
|
+
continue;
|
|
8028
|
+
}
|
|
8029
|
+
if (char === "[") {
|
|
8030
|
+
if (link === null) link = line[at - 1] === "!" ? at - 1 : at;
|
|
8031
|
+
at += 1;
|
|
8032
|
+
continue;
|
|
8033
|
+
}
|
|
8034
|
+
if (char === "]") {
|
|
8035
|
+
if (link !== null && line[at + 1] !== "(") link = null;
|
|
8036
|
+
at += 1;
|
|
8037
|
+
continue;
|
|
8038
|
+
}
|
|
8039
|
+
if (char === ")") {
|
|
8040
|
+
link = null;
|
|
8041
|
+
at += 1;
|
|
8042
|
+
continue;
|
|
8043
|
+
}
|
|
8044
|
+
if (char === "*" || char === "_" || char === "~") {
|
|
8045
|
+
let run = 1;
|
|
8046
|
+
while (line[at + run] === char) run += 1;
|
|
8047
|
+
const start = at;
|
|
8048
|
+
at += run;
|
|
8049
|
+
const before = line[start - 1];
|
|
8050
|
+
const after = line[start + run];
|
|
8051
|
+
if (run < 2) continue;
|
|
8052
|
+
if (char === "_" && isWordCharacter(before) && isWordCharacter(after)) {
|
|
8053
|
+
continue;
|
|
8054
|
+
}
|
|
8055
|
+
const marker = char.repeat(run);
|
|
8056
|
+
const open2 = emphasis.findIndex((entry) => entry.marker === marker);
|
|
8057
|
+
if (open2 !== -1) {
|
|
8058
|
+
emphasis.splice(open2, 1);
|
|
8059
|
+
continue;
|
|
8060
|
+
}
|
|
8061
|
+
if (after !== void 0 && /\s/.test(after)) continue;
|
|
8062
|
+
emphasis.push({ marker, start });
|
|
8063
|
+
continue;
|
|
8064
|
+
}
|
|
8065
|
+
at += 1;
|
|
8066
|
+
}
|
|
8067
|
+
const open = [codeSpan, link, emphasis[0]?.start ?? null].filter(
|
|
8068
|
+
(value) => value !== null
|
|
8069
|
+
);
|
|
8070
|
+
return open.length === 0 ? line.length : Math.min(...open);
|
|
8071
|
+
}
|
|
8072
|
+
function revealBoundary(text, limit) {
|
|
8073
|
+
if (limit <= 0) return 0;
|
|
8074
|
+
const caughtUp = limit >= text.length;
|
|
8075
|
+
const head = caughtUp ? text : text.slice(0, limit);
|
|
8076
|
+
const lineStart = head.lastIndexOf("\n") + 1;
|
|
8077
|
+
const line = head.slice(lineStart);
|
|
8078
|
+
let fenceStart = null;
|
|
8079
|
+
let tableStart = null;
|
|
8080
|
+
let tableRows = 0;
|
|
8081
|
+
let at = 0;
|
|
8082
|
+
while (at < lineStart) {
|
|
8083
|
+
const breakAt = head.indexOf("\n", at);
|
|
8084
|
+
const end = breakAt === -1 ? lineStart : breakAt;
|
|
8085
|
+
const row = head.slice(at, end);
|
|
8086
|
+
if (fenceStart !== null) {
|
|
8087
|
+
if (FENCE.test(row)) fenceStart = null;
|
|
8088
|
+
} else if (FENCE.test(row)) {
|
|
8089
|
+
fenceStart = at;
|
|
8090
|
+
} else if (TABLE_ROW.test(row)) {
|
|
8091
|
+
if (tableStart === null) {
|
|
8092
|
+
tableStart = at;
|
|
8093
|
+
tableRows = 0;
|
|
8094
|
+
}
|
|
8095
|
+
tableRows += 1;
|
|
8096
|
+
} else if (tableStart !== null) {
|
|
8097
|
+
tableStart = null;
|
|
8098
|
+
tableRows = 0;
|
|
8099
|
+
}
|
|
8100
|
+
at = end + 1;
|
|
8101
|
+
}
|
|
8102
|
+
if (fenceStart === null) {
|
|
8103
|
+
const lastIsRow = TABLE_ROW.test(line);
|
|
8104
|
+
if (lastIsRow && tableStart === null) {
|
|
8105
|
+
tableStart = lineStart;
|
|
8106
|
+
tableRows = 0;
|
|
8107
|
+
} else if (!lastIsRow && line !== "") {
|
|
8108
|
+
tableStart = null;
|
|
8109
|
+
tableRows = 0;
|
|
8110
|
+
}
|
|
8111
|
+
if (tableStart !== null && tableRows < 2) return tableStart;
|
|
8112
|
+
}
|
|
8113
|
+
if (caughtUp) return text.length;
|
|
8114
|
+
if (fenceStart !== null) return fenceStart;
|
|
8115
|
+
if (TABLE_ROW.test(line)) return lineStart;
|
|
8116
|
+
if (EMPTY_BLOCK_MARKER.test(line)) return lineStart;
|
|
8117
|
+
const cut = lineStart + safeInlineEnd(line);
|
|
8118
|
+
return isHighSurrogate(text.charCodeAt(cut - 1)) ? cut - 1 : cut;
|
|
8119
|
+
}
|
|
8120
|
+
function prefersReducedMotion() {
|
|
8121
|
+
if (typeof window === "undefined") return true;
|
|
8122
|
+
try {
|
|
8123
|
+
return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
8124
|
+
} catch {
|
|
8125
|
+
return false;
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
function useProgressiveReveal(text, settled) {
|
|
8129
|
+
const [, setFrame] = React.useState(0);
|
|
8130
|
+
const [reduced] = React.useState(prefersReducedMotion);
|
|
8131
|
+
const immediate = settled || reduced;
|
|
8132
|
+
const textRef = React.useRef(text);
|
|
8133
|
+
const cursorRef = React.useRef(immediate ? text.length : 0);
|
|
8134
|
+
const cutRef = React.useRef(immediate ? text.length : 0);
|
|
8135
|
+
const committedRef = React.useRef(0);
|
|
8136
|
+
if (text !== textRef.current) {
|
|
8137
|
+
const revealed = textRef.current.slice(0, Math.floor(cursorRef.current));
|
|
8138
|
+
if (revealed !== "" && !text.startsWith(revealed)) {
|
|
8139
|
+
cursorRef.current = text.length;
|
|
8140
|
+
}
|
|
8141
|
+
cursorRef.current = Math.min(cursorRef.current, text.length);
|
|
8142
|
+
cutRef.current = Math.min(cutRef.current, text.length);
|
|
8143
|
+
textRef.current = text;
|
|
8144
|
+
}
|
|
8145
|
+
if (immediate) cursorRef.current = text.length;
|
|
8146
|
+
const cut = immediate ? text.length : Math.max(
|
|
8147
|
+
cutRef.current,
|
|
8148
|
+
revealBoundary(text, Math.floor(cursorRef.current))
|
|
8149
|
+
);
|
|
8150
|
+
cutRef.current = cut;
|
|
8151
|
+
React.useEffect(() => {
|
|
8152
|
+
if (immediate) return;
|
|
8153
|
+
if (cursorRef.current >= text.length) return;
|
|
8154
|
+
let cancelled = false;
|
|
8155
|
+
let cancelStep = null;
|
|
8156
|
+
let previous = now();
|
|
8157
|
+
const commit = (at, force) => {
|
|
8158
|
+
if (!force && at - committedRef.current < COMMIT_INTERVAL_MS) return;
|
|
8159
|
+
committedRef.current = at;
|
|
8160
|
+
setFrame((value) => value + 1);
|
|
8161
|
+
};
|
|
8162
|
+
const step = () => {
|
|
8163
|
+
cancelStep = null;
|
|
8164
|
+
if (cancelled) return;
|
|
8165
|
+
const at = now();
|
|
8166
|
+
const elapsed = clamp(at - previous, 0, MAX_STEP_MS);
|
|
8167
|
+
previous = at;
|
|
8168
|
+
const length = textRef.current.length;
|
|
8169
|
+
const pending = length - cursorRef.current;
|
|
8170
|
+
if (pending <= 0) {
|
|
8171
|
+
commit(at, true);
|
|
8172
|
+
return;
|
|
8173
|
+
}
|
|
8174
|
+
const perSecond = clamp(
|
|
8175
|
+
pending / (DRAIN_TARGET_MS / 1e3),
|
|
8176
|
+
MIN_CHARS_PER_SECOND,
|
|
8177
|
+
MAX_CHARS_PER_SECOND
|
|
8178
|
+
);
|
|
8179
|
+
cursorRef.current = Math.min(
|
|
8180
|
+
length,
|
|
8181
|
+
cursorRef.current + perSecond * elapsed / 1e3
|
|
8182
|
+
);
|
|
8183
|
+
const done = cursorRef.current >= length;
|
|
8184
|
+
commit(at, done);
|
|
8185
|
+
if (!done) schedule();
|
|
8186
|
+
};
|
|
8187
|
+
const schedule = () => {
|
|
8188
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
8189
|
+
const id2 = window.requestAnimationFrame(step);
|
|
8190
|
+
cancelStep = () => window.cancelAnimationFrame(id2);
|
|
8191
|
+
return;
|
|
8192
|
+
}
|
|
8193
|
+
const id = setTimeout(step, FALLBACK_STEP_MS);
|
|
8194
|
+
cancelStep = () => clearTimeout(id);
|
|
8195
|
+
};
|
|
8196
|
+
schedule();
|
|
8197
|
+
return () => {
|
|
8198
|
+
cancelled = true;
|
|
8199
|
+
cancelStep?.();
|
|
8200
|
+
};
|
|
8201
|
+
}, [text, immediate]);
|
|
8202
|
+
return text.slice(0, cut);
|
|
8203
|
+
}
|
|
7957
8204
|
function getRecognitionCtor() {
|
|
7958
8205
|
if (typeof window === "undefined") return null;
|
|
7959
8206
|
const w = window;
|
|
@@ -8172,6 +8419,141 @@ function useSpeechInput({
|
|
|
8172
8419
|
);
|
|
8173
8420
|
return { supported, listening, interim, error, start, stop, toggle };
|
|
8174
8421
|
}
|
|
8422
|
+
var SILENT_FAILURE_MS = 1500;
|
|
8423
|
+
function speechPlatform() {
|
|
8424
|
+
if (typeof window === "undefined") return null;
|
|
8425
|
+
const scope = window;
|
|
8426
|
+
const synth = scope.speechSynthesis;
|
|
8427
|
+
const Utterance = scope.SpeechSynthesisUtterance;
|
|
8428
|
+
if (!synth || typeof synth.speak !== "function") return null;
|
|
8429
|
+
if (typeof Utterance !== "function") return null;
|
|
8430
|
+
return { synth, Utterance };
|
|
8431
|
+
}
|
|
8432
|
+
function normaliseTag(tag) {
|
|
8433
|
+
return tag.trim().toLowerCase().replace(/_/g, "-");
|
|
8434
|
+
}
|
|
8435
|
+
function primaryOf(tag) {
|
|
8436
|
+
return normaliseTag(tag).split("-")[0] ?? "";
|
|
8437
|
+
}
|
|
8438
|
+
function pickVoice(voices, lang) {
|
|
8439
|
+
const wanted = normaliseTag(lang);
|
|
8440
|
+
if (wanted === "") return null;
|
|
8441
|
+
const primary = primaryOf(wanted);
|
|
8442
|
+
const exact = voices.filter((voice) => normaliseTag(voice.lang) === wanted);
|
|
8443
|
+
const related = voices.filter((voice) => primaryOf(voice.lang) === primary);
|
|
8444
|
+
return exact.find((voice) => voice.default) ?? exact[0] ?? related.find((voice) => voice.default) ?? related[0] ?? null;
|
|
8445
|
+
}
|
|
8446
|
+
var activeOwner = null;
|
|
8447
|
+
var activeStop = null;
|
|
8448
|
+
function stopSpeaking() {
|
|
8449
|
+
activeStop?.();
|
|
8450
|
+
}
|
|
8451
|
+
function useSpeechOutput({
|
|
8452
|
+
lang
|
|
8453
|
+
} = {}) {
|
|
8454
|
+
const [available, setAvailable] = React.useState(() => speechPlatform() !== null);
|
|
8455
|
+
const [speaking, setSpeaking] = React.useState(false);
|
|
8456
|
+
const utteranceRef = React.useRef(null);
|
|
8457
|
+
const watchdogRef = React.useRef(null);
|
|
8458
|
+
const ownerRef = React.useRef({});
|
|
8459
|
+
const stopRef = React.useRef(() => void 0);
|
|
8460
|
+
const langRef = React.useRef(lang);
|
|
8461
|
+
langRef.current = lang;
|
|
8462
|
+
const clearWatchdog = React.useCallback(() => {
|
|
8463
|
+
if (watchdogRef.current === null) return;
|
|
8464
|
+
clearTimeout(watchdogRef.current);
|
|
8465
|
+
watchdogRef.current = null;
|
|
8466
|
+
}, []);
|
|
8467
|
+
const settle = React.useCallback(() => {
|
|
8468
|
+
clearWatchdog();
|
|
8469
|
+
utteranceRef.current = null;
|
|
8470
|
+
if (activeOwner === ownerRef.current) {
|
|
8471
|
+
activeOwner = null;
|
|
8472
|
+
activeStop = null;
|
|
8473
|
+
}
|
|
8474
|
+
setSpeaking(false);
|
|
8475
|
+
}, [clearWatchdog]);
|
|
8476
|
+
const stop = React.useCallback(() => {
|
|
8477
|
+
const owned = utteranceRef.current !== null;
|
|
8478
|
+
settle();
|
|
8479
|
+
if (owned) speechPlatform()?.synth.cancel();
|
|
8480
|
+
}, [settle]);
|
|
8481
|
+
stopRef.current = stop;
|
|
8482
|
+
const speak = React.useCallback(
|
|
8483
|
+
(text) => {
|
|
8484
|
+
const platform = speechPlatform();
|
|
8485
|
+
if (!platform) {
|
|
8486
|
+
setAvailable(false);
|
|
8487
|
+
return;
|
|
8488
|
+
}
|
|
8489
|
+
const spoken = text.trim();
|
|
8490
|
+
if (spoken === "") return;
|
|
8491
|
+
if (activeStop && activeOwner !== ownerRef.current) activeStop();
|
|
8492
|
+
platform.synth.cancel();
|
|
8493
|
+
const utterance = new platform.Utterance(spoken);
|
|
8494
|
+
const wanted = langRef.current;
|
|
8495
|
+
if (wanted !== void 0 && wanted !== "") {
|
|
8496
|
+
const voice = pickVoice(platform.synth.getVoices(), wanted);
|
|
8497
|
+
utterance.lang = voice?.lang ?? wanted;
|
|
8498
|
+
if (voice) utterance.voice = voice;
|
|
8499
|
+
}
|
|
8500
|
+
const mine = () => utteranceRef.current === utterance;
|
|
8501
|
+
utterance.onstart = () => {
|
|
8502
|
+
if (!mine()) return;
|
|
8503
|
+
clearWatchdog();
|
|
8504
|
+
setSpeaking(true);
|
|
8505
|
+
};
|
|
8506
|
+
utterance.onend = () => {
|
|
8507
|
+
if (!mine()) return;
|
|
8508
|
+
settle();
|
|
8509
|
+
};
|
|
8510
|
+
utterance.onerror = (event) => {
|
|
8511
|
+
if (!mine()) return;
|
|
8512
|
+
const code = typeof event?.error === "string" ? event.error : "";
|
|
8513
|
+
settle();
|
|
8514
|
+
if (code !== "interrupted" && code !== "canceled") setAvailable(false);
|
|
8515
|
+
};
|
|
8516
|
+
utteranceRef.current = utterance;
|
|
8517
|
+
activeOwner = ownerRef.current;
|
|
8518
|
+
activeStop = () => {
|
|
8519
|
+
stopRef.current();
|
|
8520
|
+
};
|
|
8521
|
+
setSpeaking(true);
|
|
8522
|
+
platform.synth.speak(utterance);
|
|
8523
|
+
clearWatchdog();
|
|
8524
|
+
watchdogRef.current = setTimeout(() => {
|
|
8525
|
+
watchdogRef.current = null;
|
|
8526
|
+
if (!mine()) return;
|
|
8527
|
+
if (platform.synth.speaking) return;
|
|
8528
|
+
settle();
|
|
8529
|
+
setAvailable(false);
|
|
8530
|
+
}, SILENT_FAILURE_MS);
|
|
8531
|
+
},
|
|
8532
|
+
[clearWatchdog, settle]
|
|
8533
|
+
);
|
|
8534
|
+
const toggle = React.useCallback(
|
|
8535
|
+
(text) => {
|
|
8536
|
+
if (speaking) stop();
|
|
8537
|
+
else speak(text);
|
|
8538
|
+
},
|
|
8539
|
+
[speak, speaking, stop]
|
|
8540
|
+
);
|
|
8541
|
+
React.useEffect(() => {
|
|
8542
|
+
if (!speaking || typeof window === "undefined") return;
|
|
8543
|
+
const onHide = () => {
|
|
8544
|
+
stopRef.current();
|
|
8545
|
+
};
|
|
8546
|
+
window.addEventListener("pagehide", onHide);
|
|
8547
|
+
return () => window.removeEventListener("pagehide", onHide);
|
|
8548
|
+
}, [speaking]);
|
|
8549
|
+
React.useEffect(
|
|
8550
|
+
() => () => {
|
|
8551
|
+
stopRef.current();
|
|
8552
|
+
},
|
|
8553
|
+
[]
|
|
8554
|
+
);
|
|
8555
|
+
return { supported: available, speaking, speak, stop, toggle };
|
|
8556
|
+
}
|
|
8175
8557
|
var SWIPE_THRESHOLD = 40;
|
|
8176
8558
|
function useSwipe({
|
|
8177
8559
|
onSwipeLeft,
|
|
@@ -8439,6 +8821,115 @@ var EXPLORE_CSS = `
|
|
|
8439
8821
|
height: auto;
|
|
8440
8822
|
}
|
|
8441
8823
|
|
|
8824
|
+
/* ---- Comparison and pricing tables ----------------------------------------------------
|
|
8825
|
+
The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
|
|
8826
|
+
the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
|
|
8827
|
+
the page must not scroll sideways because of it, and it has to be readable on a 320px
|
|
8828
|
+
phone, which a four-column grid of 1px borders is not.
|
|
8829
|
+
|
|
8830
|
+
Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
|
|
8831
|
+
\`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
|
|
8832
|
+
cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
|
|
8833
|
+
of table from chaining to the page \u2014 the same rule the transcript needed. */
|
|
8834
|
+
.boff-explore-body .boff-explore-table {
|
|
8835
|
+
max-width: 100%;
|
|
8836
|
+
overflow-x: auto;
|
|
8837
|
+
overscroll-behavior-x: contain;
|
|
8838
|
+
margin: 0.75rem 0;
|
|
8839
|
+
border: 1px solid hsl(var(--border));
|
|
8840
|
+
border-radius: 0.75rem;
|
|
8841
|
+
background-color: hsl(var(--card));
|
|
8842
|
+
}
|
|
8843
|
+
.boff-explore-body .boff-explore-table:focus-visible {
|
|
8844
|
+
outline: 2px solid hsl(var(--ring));
|
|
8845
|
+
outline-offset: 2px;
|
|
8846
|
+
}
|
|
8847
|
+
.boff-explore-body .boff-explore-table table {
|
|
8848
|
+
/* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
|
|
8849
|
+
display: table;
|
|
8850
|
+
width: 100%;
|
|
8851
|
+
max-width: none;
|
|
8852
|
+
margin: 0;
|
|
8853
|
+
border-collapse: collapse;
|
|
8854
|
+
font-size: 0.8125rem;
|
|
8855
|
+
}
|
|
8856
|
+
.boff-explore-body .boff-explore-table th,
|
|
8857
|
+
.boff-explore-body .boff-explore-table td {
|
|
8858
|
+
border: 0;
|
|
8859
|
+
border-top: 1px solid hsl(var(--border));
|
|
8860
|
+
padding: 0.5rem 0.75rem;
|
|
8861
|
+
/* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
|
|
8862
|
+
table there reads against the grain of every other line on the page. */
|
|
8863
|
+
text-align: start;
|
|
8864
|
+
vertical-align: top;
|
|
8865
|
+
overflow-wrap: anywhere;
|
|
8866
|
+
/* Prices in a column line up under each other instead of drifting by digit width. */
|
|
8867
|
+
font-variant-numeric: tabular-nums;
|
|
8868
|
+
min-width: 6rem;
|
|
8869
|
+
}
|
|
8870
|
+
.boff-explore-body .boff-explore-table thead th {
|
|
8871
|
+
border-top: 0;
|
|
8872
|
+
background-color: hsl(var(--muted));
|
|
8873
|
+
color: hsl(var(--foreground));
|
|
8874
|
+
font-weight: 600;
|
|
8875
|
+
white-space: nowrap;
|
|
8876
|
+
}
|
|
8877
|
+
/* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
|
|
8878
|
+
the row's heading is what makes a price list scannable rather than a grid of strings. */
|
|
8879
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
8880
|
+
font-weight: 600;
|
|
8881
|
+
color: hsl(var(--card-foreground));
|
|
8882
|
+
}
|
|
8883
|
+
|
|
8884
|
+
/* Narrow: one card per row. Each cell prints its own column heading, so a value is never
|
|
8885
|
+
stranded without the question it answers, and the subject cell becomes the card's title.
|
|
8886
|
+
A media query rather than a measured width \u2014 these pages are serialised post-hydration,
|
|
8887
|
+
so anything measured here would ship to every phone as static HTML. */
|
|
8888
|
+
@media (max-width: 30rem) {
|
|
8889
|
+
.boff-explore-body .boff-explore-table {
|
|
8890
|
+
overflow-x: visible;
|
|
8891
|
+
}
|
|
8892
|
+
.boff-explore-body .boff-explore-table table,
|
|
8893
|
+
.boff-explore-body .boff-explore-table tbody,
|
|
8894
|
+
.boff-explore-body .boff-explore-table tr,
|
|
8895
|
+
.boff-explore-body .boff-explore-table td {
|
|
8896
|
+
display: block;
|
|
8897
|
+
width: 100%;
|
|
8898
|
+
min-width: 0;
|
|
8899
|
+
}
|
|
8900
|
+
.boff-explore-body .boff-explore-table thead {
|
|
8901
|
+
display: none;
|
|
8902
|
+
}
|
|
8903
|
+
.boff-explore-body .boff-explore-table tr {
|
|
8904
|
+
border-top: 1px solid hsl(var(--border));
|
|
8905
|
+
padding: 0.5rem 0;
|
|
8906
|
+
}
|
|
8907
|
+
.boff-explore-body .boff-explore-table tr:first-child {
|
|
8908
|
+
border-top: 0;
|
|
8909
|
+
}
|
|
8910
|
+
.boff-explore-body .boff-explore-table td {
|
|
8911
|
+
border: 0;
|
|
8912
|
+
padding: 0.125rem 0.75rem;
|
|
8913
|
+
}
|
|
8914
|
+
.boff-explore-body .boff-explore-table td[data-label]::before {
|
|
8915
|
+
content: attr(data-label);
|
|
8916
|
+
display: block;
|
|
8917
|
+
font-size: 0.6875rem;
|
|
8918
|
+
font-weight: 600;
|
|
8919
|
+
text-transform: uppercase;
|
|
8920
|
+
letter-spacing: 0.04em;
|
|
8921
|
+
color: hsl(var(--muted-foreground));
|
|
8922
|
+
}
|
|
8923
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
8924
|
+
font-size: 0.9375rem;
|
|
8925
|
+
padding-bottom: 0.25rem;
|
|
8926
|
+
}
|
|
8927
|
+
.boff-explore-body .boff-explore-table tbody td:first-child::before {
|
|
8928
|
+
/* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
|
|
8929
|
+
display: none;
|
|
8930
|
+
}
|
|
8931
|
+
}
|
|
8932
|
+
|
|
8442
8933
|
/* ---- Ambient backdrop -----------------------------------------------------------------
|
|
8443
8934
|
Two very low-opacity radial washes behind the welcome state, drifting slowly. Painted on
|
|
8444
8935
|
a pseudo-element with pointer-events:none so it can never intercept a click \u2014 a
|
|
@@ -8610,7 +9101,7 @@ function isOwnElement(el, root) {
|
|
|
8610
9101
|
function visualViewportHeight() {
|
|
8611
9102
|
return window.visualViewport?.height ?? window.innerHeight;
|
|
8612
9103
|
}
|
|
8613
|
-
function
|
|
9104
|
+
function clamp2(value, min, max) {
|
|
8614
9105
|
return Math.min(Math.max(value, min), max);
|
|
8615
9106
|
}
|
|
8616
9107
|
function isRestoredNavigation() {
|
|
@@ -8760,11 +9251,80 @@ function summariseReferences(references, origins, productNames) {
|
|
|
8760
9251
|
};
|
|
8761
9252
|
}
|
|
8762
9253
|
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
8763
|
-
|
|
9254
|
+
var TABLE_ROW_LINE = /^\s{0,3}\|/;
|
|
9255
|
+
var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
|
|
9256
|
+
function readableLine(raw, mode) {
|
|
9257
|
+
let line = raw;
|
|
9258
|
+
line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
|
|
9259
|
+
line = mode === "speech" ? (
|
|
9260
|
+
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
9261
|
+
// prompt asks for two to five links in every answer. The label IS the words.
|
|
9262
|
+
line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
|
|
9263
|
+
) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
|
|
9264
|
+
line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
9265
|
+
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
9266
|
+
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
9267
|
+
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
9268
|
+
line = line.replace(/`([^`]+)`/g, "$1");
|
|
9269
|
+
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
9270
|
+
line = line.replace(/__([^_]+)__/g, "$1");
|
|
9271
|
+
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
9272
|
+
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
9273
|
+
return line.replace(/[ \t]+$/, "");
|
|
9274
|
+
}
|
|
9275
|
+
function tableCells(row) {
|
|
9276
|
+
const trimmed = row.trim();
|
|
9277
|
+
const cells = [];
|
|
9278
|
+
let cell = "";
|
|
9279
|
+
for (let at = 0; at < trimmed.length; at += 1) {
|
|
9280
|
+
const char = trimmed[at] ?? "";
|
|
9281
|
+
if (char === "\\") {
|
|
9282
|
+
cell += char + (trimmed[at + 1] ?? "");
|
|
9283
|
+
at += 1;
|
|
9284
|
+
continue;
|
|
9285
|
+
}
|
|
9286
|
+
if (char === "|") {
|
|
9287
|
+
cells.push(cell.trim());
|
|
9288
|
+
cell = "";
|
|
9289
|
+
continue;
|
|
9290
|
+
}
|
|
9291
|
+
cell += char;
|
|
9292
|
+
}
|
|
9293
|
+
cells.push(cell.trim());
|
|
9294
|
+
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
9295
|
+
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
9296
|
+
return cells;
|
|
9297
|
+
}
|
|
9298
|
+
function asSentence(text) {
|
|
9299
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
9300
|
+
}
|
|
9301
|
+
function spokenTable(rows) {
|
|
9302
|
+
if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
|
|
9303
|
+
const labels = tableCells(rows[0] ?? "");
|
|
9304
|
+
const spoken = [];
|
|
9305
|
+
for (const row of rows.slice(2)) {
|
|
9306
|
+
const parts = [];
|
|
9307
|
+
tableCells(row).forEach((value, column) => {
|
|
9308
|
+
if (value === "") return;
|
|
9309
|
+
const label = column === 0 ? "" : labels[column] ?? "";
|
|
9310
|
+
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
9311
|
+
});
|
|
9312
|
+
if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
|
|
9313
|
+
}
|
|
9314
|
+
return spoken;
|
|
9315
|
+
}
|
|
9316
|
+
function readableText(markdown, mode) {
|
|
8764
9317
|
const lines = [];
|
|
8765
9318
|
let inFence = false;
|
|
9319
|
+
let table = [];
|
|
9320
|
+
const flushTable = () => {
|
|
9321
|
+
if (table.length === 0) return;
|
|
9322
|
+
for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
|
|
9323
|
+
table = [];
|
|
9324
|
+
};
|
|
8766
9325
|
for (const raw of markdown.split("\n")) {
|
|
8767
9326
|
if (CODE_FENCE.test(raw)) {
|
|
9327
|
+
flushTable();
|
|
8768
9328
|
inFence = !inFence;
|
|
8769
9329
|
continue;
|
|
8770
9330
|
}
|
|
@@ -8772,33 +9332,30 @@ function answerPlainText(markdown) {
|
|
|
8772
9332
|
lines.push(raw);
|
|
8773
9333
|
continue;
|
|
8774
9334
|
}
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
line = line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
|
|
8778
|
-
line = line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
8779
|
-
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
8780
|
-
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
8781
|
-
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) {
|
|
8782
|
-
lines.push("");
|
|
9335
|
+
if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
|
|
9336
|
+
table.push(raw);
|
|
8783
9337
|
continue;
|
|
8784
9338
|
}
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
line = line.replace(/__([^_]+)__/g, "$1");
|
|
8788
|
-
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
8789
|
-
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
8790
|
-
lines.push(line.replace(/[ \t]+$/, ""));
|
|
9339
|
+
flushTable();
|
|
9340
|
+
lines.push(readableLine(raw, mode));
|
|
8791
9341
|
}
|
|
9342
|
+
flushTable();
|
|
8792
9343
|
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
8793
9344
|
}
|
|
8794
|
-
|
|
9345
|
+
function answerPlainText(markdown) {
|
|
9346
|
+
return readableText(markdown, "clipboard");
|
|
9347
|
+
}
|
|
9348
|
+
function answerSpeechText(markdown) {
|
|
9349
|
+
return readableText(markdown, "speech");
|
|
9350
|
+
}
|
|
9351
|
+
var AnswerScopeContext = React.createContext(null);
|
|
8795
9352
|
var InsideLinkContext = React.createContext(false);
|
|
8796
9353
|
function AnswerLink(props) {
|
|
8797
9354
|
return /* @__PURE__ */ jsxRuntime.jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownLink, { ...props }) });
|
|
8798
9355
|
}
|
|
8799
9356
|
var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
|
|
8800
9357
|
function InlineConceptImage({ src, alt }) {
|
|
8801
|
-
const scope = React.useContext(
|
|
9358
|
+
const scope = React.useContext(AnswerScopeContext);
|
|
8802
9359
|
const insideLink = React.useContext(InsideLinkContext);
|
|
8803
9360
|
if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
|
|
8804
9361
|
return null;
|
|
@@ -8846,9 +9403,70 @@ function InlineConceptImage({ src, alt }) {
|
|
|
8846
9403
|
}
|
|
8847
9404
|
) });
|
|
8848
9405
|
}
|
|
9406
|
+
function markdownText(node) {
|
|
9407
|
+
if (!node) return "";
|
|
9408
|
+
if (typeof node.value === "string") return node.value;
|
|
9409
|
+
return (node.children ?? []).map(markdownText).join("");
|
|
9410
|
+
}
|
|
9411
|
+
function hasHeaderCells(node) {
|
|
9412
|
+
return (node?.children ?? []).some((cell) => cell.tagName === "th");
|
|
9413
|
+
}
|
|
9414
|
+
function findHeaderRow(node) {
|
|
9415
|
+
for (const child of node?.children ?? []) {
|
|
9416
|
+
if (child.tagName === "tr" && hasHeaderCells(child)) return child;
|
|
9417
|
+
const nested = findHeaderRow(child);
|
|
9418
|
+
if (nested !== null) return nested;
|
|
9419
|
+
}
|
|
9420
|
+
return null;
|
|
9421
|
+
}
|
|
9422
|
+
function tableColumnLabels(node) {
|
|
9423
|
+
const row = findHeaderRow(node);
|
|
9424
|
+
if (row === null) return [];
|
|
9425
|
+
return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
|
|
9426
|
+
}
|
|
9427
|
+
var TableColumnsContext = React.createContext([]);
|
|
9428
|
+
function AnswerTable({ node, children }) {
|
|
9429
|
+
const scope = React.useContext(AnswerScopeContext);
|
|
9430
|
+
const table = /* @__PURE__ */ jsxRuntime.jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsxRuntime.jsx("table", { children }) });
|
|
9431
|
+
if (!scope) return table;
|
|
9432
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9433
|
+
"div",
|
|
9434
|
+
{
|
|
9435
|
+
"data-boff-explore": "table",
|
|
9436
|
+
role: "region",
|
|
9437
|
+
"aria-label": scope.strings.answerTable,
|
|
9438
|
+
tabIndex: 0,
|
|
9439
|
+
className: "boff-explore-table",
|
|
9440
|
+
children: table
|
|
9441
|
+
}
|
|
9442
|
+
);
|
|
9443
|
+
}
|
|
9444
|
+
function AnswerTableRow({
|
|
9445
|
+
node,
|
|
9446
|
+
children,
|
|
9447
|
+
...props
|
|
9448
|
+
}) {
|
|
9449
|
+
const labels = React.useContext(TableColumnsContext);
|
|
9450
|
+
if (labels.length === 0 || hasHeaderCells(node)) {
|
|
9451
|
+
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children });
|
|
9452
|
+
}
|
|
9453
|
+
let column = 0;
|
|
9454
|
+
const cells = React.Children.map(children, (cell) => {
|
|
9455
|
+
if (!React.isValidElement(cell)) return cell;
|
|
9456
|
+
const label = labels[column] ?? "";
|
|
9457
|
+
column += 1;
|
|
9458
|
+
if (label === "") return cell;
|
|
9459
|
+
return React.cloneElement(cell, {
|
|
9460
|
+
"data-label": label
|
|
9461
|
+
});
|
|
9462
|
+
});
|
|
9463
|
+
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children: cells });
|
|
9464
|
+
}
|
|
8849
9465
|
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
8850
9466
|
a: AnswerLink,
|
|
8851
|
-
img: InlineConceptImage
|
|
9467
|
+
img: InlineConceptImage,
|
|
9468
|
+
table: AnswerTable,
|
|
9469
|
+
tr: AnswerTableRow
|
|
8852
9470
|
};
|
|
8853
9471
|
function ReferenceCard({
|
|
8854
9472
|
reference,
|
|
@@ -9237,9 +9855,42 @@ function ProvenanceChips({
|
|
|
9237
9855
|
);
|
|
9238
9856
|
}
|
|
9239
9857
|
var COPY_FEEDBACK_MS = 2500;
|
|
9858
|
+
var ANSWER_ACTION_PILL = "inline-flex max-w-full items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring";
|
|
9859
|
+
var ANSWER_ACTION_IDLE = "border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent hover:text-foreground";
|
|
9860
|
+
var ANSWER_ACTION_ACTIVE = "border-primary/40 bg-accent text-primary";
|
|
9861
|
+
function ReadAloudButton({
|
|
9862
|
+
strings: T,
|
|
9863
|
+
text,
|
|
9864
|
+
lang
|
|
9865
|
+
}) {
|
|
9866
|
+
const speech = useSpeechOutput({ lang });
|
|
9867
|
+
if (!speech.supported) return null;
|
|
9868
|
+
const label = speech.speaking ? T.stopReading : T.readAloud;
|
|
9869
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9870
|
+
"button",
|
|
9871
|
+
{
|
|
9872
|
+
"data-boff-explore": "read-aloud",
|
|
9873
|
+
"data-speaking": speech.speaking ? "true" : "false",
|
|
9874
|
+
type: "button",
|
|
9875
|
+
title: label,
|
|
9876
|
+
"aria-pressed": speech.speaking,
|
|
9877
|
+
onClick: () => speech.toggle(text),
|
|
9878
|
+
className: cn(
|
|
9879
|
+
ANSWER_ACTION_PILL,
|
|
9880
|
+
speech.speaking ? ANSWER_ACTION_ACTIVE : ANSWER_ACTION_IDLE
|
|
9881
|
+
),
|
|
9882
|
+
children: [
|
|
9883
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Volume2, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
|
|
9884
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label })
|
|
9885
|
+
]
|
|
9886
|
+
}
|
|
9887
|
+
);
|
|
9888
|
+
}
|
|
9240
9889
|
function AnswerActions({
|
|
9241
9890
|
strings: T,
|
|
9242
|
-
text
|
|
9891
|
+
text,
|
|
9892
|
+
speechText,
|
|
9893
|
+
speechLang
|
|
9243
9894
|
}) {
|
|
9244
9895
|
const [outcome, setOutcome] = React.useState("idle");
|
|
9245
9896
|
const resetRef = React.useRef(null);
|
|
@@ -9282,9 +9933,8 @@ function AnswerActions({
|
|
|
9282
9933
|
void copy();
|
|
9283
9934
|
},
|
|
9284
9935
|
className: cn(
|
|
9285
|
-
|
|
9286
|
-
"
|
|
9287
|
-
outcome === "failed" ? "border-destructive/40 bg-card text-destructive" : "border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent hover:text-foreground"
|
|
9936
|
+
ANSWER_ACTION_PILL,
|
|
9937
|
+
outcome === "failed" ? "border-destructive/40 bg-card text-destructive" : ANSWER_ACTION_IDLE
|
|
9288
9938
|
),
|
|
9289
9939
|
children: [
|
|
9290
9940
|
outcome === "copied" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" }),
|
|
@@ -9292,6 +9942,7 @@ function AnswerActions({
|
|
|
9292
9942
|
]
|
|
9293
9943
|
}
|
|
9294
9944
|
),
|
|
9945
|
+
/* @__PURE__ */ jsxRuntime.jsx(ReadAloudButton, { strings: T, text: speechText, lang: speechLang }),
|
|
9295
9946
|
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-boff-explore": "copy-status", role: "status", className: "sr-only", children: outcome === "idle" ? "" : label })
|
|
9296
9947
|
]
|
|
9297
9948
|
}
|
|
@@ -9343,6 +9994,7 @@ function AssistantBubble({
|
|
|
9343
9994
|
activity,
|
|
9344
9995
|
imageOrigins,
|
|
9345
9996
|
productNames,
|
|
9997
|
+
speechLang,
|
|
9346
9998
|
onNavigate,
|
|
9347
9999
|
anchorRef,
|
|
9348
10000
|
onFollow,
|
|
@@ -9353,6 +10005,7 @@ function AssistantBubble({
|
|
|
9353
10005
|
const body = message.content || message.partialContent || "";
|
|
9354
10006
|
const failed = message.status === "FAILED";
|
|
9355
10007
|
const completed = message.status === "COMPLETED";
|
|
10008
|
+
const visible = useProgressiveReveal(body, !streaming);
|
|
9356
10009
|
const [failedImages, setFailedImages] = React.useState(
|
|
9357
10010
|
() => /* @__PURE__ */ new Set()
|
|
9358
10011
|
);
|
|
@@ -9361,7 +10014,7 @@ function AssistantBubble({
|
|
|
9361
10014
|
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
9362
10015
|
);
|
|
9363
10016
|
}, []);
|
|
9364
|
-
const
|
|
10017
|
+
const answerScope = React.useMemo(
|
|
9365
10018
|
() => ({
|
|
9366
10019
|
strings: T,
|
|
9367
10020
|
enabled: completed,
|
|
@@ -9379,6 +10032,10 @@ function AssistantBubble({
|
|
|
9379
10032
|
() => answerPlainText(message.content),
|
|
9380
10033
|
[message.content]
|
|
9381
10034
|
);
|
|
10035
|
+
const speechText = React.useMemo(
|
|
10036
|
+
() => answerSpeechText(message.content),
|
|
10037
|
+
[message.content]
|
|
10038
|
+
);
|
|
9382
10039
|
const grounded = completed && message.answered !== false;
|
|
9383
10040
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9384
10041
|
"div",
|
|
@@ -9425,8 +10082,8 @@ function AssistantBubble({
|
|
|
9425
10082
|
]
|
|
9426
10083
|
}
|
|
9427
10084
|
) : null,
|
|
9428
|
-
|
|
9429
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
10085
|
+
visible ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
|
|
10086
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(AnswerScopeContext.Provider, { value: answerScope, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { components: ANSWER_MARKDOWN_COMPONENTS, children: visible }) }) }),
|
|
9430
10087
|
streaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
|
|
9431
10088
|
] }) : null,
|
|
9432
10089
|
grounded && provenance.total > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ProvenanceChips, { strings: T, provenance }) : null,
|
|
@@ -9469,7 +10126,15 @@ function AssistantBubble({
|
|
|
9469
10126
|
question
|
|
9470
10127
|
)) })
|
|
9471
10128
|
] }) : null,
|
|
9472
|
-
completed && copyText !== "" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
10129
|
+
completed && copyText !== "" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
10130
|
+
AnswerActions,
|
|
10131
|
+
{
|
|
10132
|
+
strings: T,
|
|
10133
|
+
text: copyText,
|
|
10134
|
+
speechText,
|
|
10135
|
+
speechLang
|
|
10136
|
+
}
|
|
10137
|
+
) : null,
|
|
9473
10138
|
completed && onFeedback ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
9474
10139
|
AnswerFeedback,
|
|
9475
10140
|
{
|
|
@@ -9703,7 +10368,7 @@ function ConceptStrip({
|
|
|
9703
10368
|
"div",
|
|
9704
10369
|
{
|
|
9705
10370
|
"data-boff-explore": "concept-strip-block",
|
|
9706
|
-
className: "boff-explore-rise boff-explore-rise-
|
|
10371
|
+
className: "boff-explore-rise boff-explore-rise-1 mt-6",
|
|
9707
10372
|
children: [
|
|
9708
10373
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: T.conceptStripTitle }),
|
|
9709
10374
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -9893,6 +10558,15 @@ function WelcomeState({
|
|
|
9893
10558
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
|
|
9894
10559
|
/* @__PURE__ */ jsxRuntime.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 })
|
|
9895
10560
|
] }),
|
|
10561
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10562
|
+
ConceptStrip,
|
|
10563
|
+
{
|
|
10564
|
+
strings: T,
|
|
10565
|
+
highlights: conceptHighlights,
|
|
10566
|
+
onAsk: onPrompt,
|
|
10567
|
+
disabled
|
|
10568
|
+
}
|
|
10569
|
+
),
|
|
9896
10570
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(
|
|
9897
10571
|
"li",
|
|
9898
10572
|
{
|
|
@@ -9917,15 +10591,6 @@ function WelcomeState({
|
|
|
9917
10591
|
}
|
|
9918
10592
|
)
|
|
9919
10593
|
] }) : null,
|
|
9920
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9921
|
-
ConceptStrip,
|
|
9922
|
-
{
|
|
9923
|
-
strings: T,
|
|
9924
|
-
highlights: conceptHighlights,
|
|
9925
|
-
onAsk: onPrompt,
|
|
9926
|
-
disabled
|
|
9927
|
-
}
|
|
9928
|
-
),
|
|
9929
10594
|
products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "boff-explore-rise boff-explore-rise-3 mt-6", children: [
|
|
9930
10595
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: T.focusProduct }),
|
|
9931
10596
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -9963,6 +10628,7 @@ function ExplorePage({
|
|
|
9963
10628
|
);
|
|
9964
10629
|
const T = React.useMemo(() => exploreStrings(activeLocale), [activeLocale]);
|
|
9965
10630
|
const rtl = isRtlLocale(activeLocale);
|
|
10631
|
+
const speechLang = speechLocaleOf(activeLocale);
|
|
9966
10632
|
const {
|
|
9967
10633
|
phase,
|
|
9968
10634
|
catalog,
|
|
@@ -10067,11 +10733,11 @@ function ExplorePage({
|
|
|
10067
10733
|
const layoutHeight = window.innerHeight;
|
|
10068
10734
|
if (layoutHeight === 0) return;
|
|
10069
10735
|
const top = Math.round(
|
|
10070
|
-
|
|
10736
|
+
clamp2(el.getBoundingClientRect().top, 0, layoutHeight)
|
|
10071
10737
|
);
|
|
10072
10738
|
const viewport = window.visualViewport;
|
|
10073
10739
|
const inset = viewport ? Math.round(
|
|
10074
|
-
|
|
10740
|
+
clamp2(
|
|
10075
10741
|
layoutHeight - viewport.height - viewport.offsetTop,
|
|
10076
10742
|
0,
|
|
10077
10743
|
layoutHeight
|
|
@@ -10352,6 +11018,7 @@ function ExplorePage({
|
|
|
10352
11018
|
const text = draft.trim();
|
|
10353
11019
|
if (!text || overLimit || busy || !canSend) return;
|
|
10354
11020
|
stopDictation();
|
|
11021
|
+
stopSpeaking();
|
|
10355
11022
|
setDraft("");
|
|
10356
11023
|
stickToBottomRef.current = true;
|
|
10357
11024
|
void send(text, toPayload(stagedRef.current));
|
|
@@ -10362,6 +11029,7 @@ function ExplorePage({
|
|
|
10362
11029
|
const sendPrompt = React.useCallback(
|
|
10363
11030
|
(prompt) => {
|
|
10364
11031
|
if (!canSend || busy) return;
|
|
11032
|
+
stopSpeaking();
|
|
10365
11033
|
setDraft("");
|
|
10366
11034
|
stickToBottomRef.current = true;
|
|
10367
11035
|
void send(prompt, toPayload(stagedRef.current));
|
|
@@ -10597,6 +11265,7 @@ function ExplorePage({
|
|
|
10597
11265
|
activity,
|
|
10598
11266
|
imageOrigins,
|
|
10599
11267
|
productNames,
|
|
11268
|
+
speechLang,
|
|
10600
11269
|
onNavigate,
|
|
10601
11270
|
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
|
|
10602
11271
|
onFollow: (kind, url) => recordClick(message.id, kind, url),
|
|
@@ -15631,14 +16300,18 @@ exports.getConceptBySlug = getConceptBySlug;
|
|
|
15631
16300
|
exports.getRecaptchaV3Token = getRecaptchaV3Token;
|
|
15632
16301
|
exports.isValidUrl = isValidUrl;
|
|
15633
16302
|
exports.loadRecaptchaV3 = loadRecaptchaV3;
|
|
16303
|
+
exports.speechLocaleOf = speechLocaleOf;
|
|
16304
|
+
exports.stopSpeaking = stopSpeaking;
|
|
15634
16305
|
exports.useContentPage = useContentPage;
|
|
15635
16306
|
exports.useContentPages = useContentPages;
|
|
15636
16307
|
exports.useExploreChat = useExploreChat;
|
|
15637
16308
|
exports.useProduct = useProduct;
|
|
15638
16309
|
exports.useProductConfig = useProductConfig;
|
|
16310
|
+
exports.useProgressiveReveal = useProgressiveReveal;
|
|
15639
16311
|
exports.usePublicJobPosting = usePublicJobPosting;
|
|
15640
16312
|
exports.usePublicJobPostings = usePublicJobPostings;
|
|
15641
16313
|
exports.usePublicJobPostingsForSubmission = usePublicJobPostingsForSubmission;
|
|
16314
|
+
exports.useSpeechOutput = useSpeechOutput;
|
|
15642
16315
|
exports.useWebSDK = useWebSDK;
|
|
15643
16316
|
exports.useWebSDKConfig = useWebSDKConfig;
|
|
15644
16317
|
//# sourceMappingURL=index.js.map
|