@burdenoff/website-sdk 2026.922.4 → 2026.923.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/explore.d.mts +4 -158
- package/dist/components/explore.d.ts +4 -158
- package/dist/components/explore.js +1464 -263
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +1466 -266
- package/dist/components/explore.mjs.map +1 -1
- package/dist/explore-D25zoXy9.d.mts +240 -0
- package/dist/explore-I4QnY4J9.d.ts +240 -0
- package/dist/{explore-types-DvQPiL1t.d.mts → explore-types-B5sdOC5M.d.mts} +75 -2
- package/dist/{explore-types-DvQPiL1t.d.ts → explore-types-B5sdOC5M.d.ts} +75 -2
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +481 -8
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +479 -9
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +433 -25
- package/dist/index.d.ts +433 -25
- package/dist/index.js +2839 -409
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2817 -412
- package/dist/index.mjs.map +1 -1
- package/dist/{use-explore-chat-Dgm-jf96.d.mts → use-progressive-reveal-Cmy8bA7L.d.mts} +88 -7
- package/dist/{use-explore-chat-CNEdKBc1.d.ts → use-progressive-reveal-DHPGGxZu.d.ts} +88 -7
- package/package.json +3 -2
package/dist/hooks/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions,
|
|
2
|
-
import '../explore-types-
|
|
1
|
+
export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, c as ExploreEscalateOutcome, d as ExploreEscalateResult, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, e as UseExploreChatResult, f as UseSpeechOutputOptions, g as UseSpeechOutputResult, h as classifyExploreError, s as stopSpeaking, u as useExploreChat, i as useProgressiveReveal, j as useSpeechOutput } from '../use-progressive-reveal-Cmy8bA7L.mjs';
|
|
2
|
+
import '../explore-types-B5sdOC5M.mjs';
|
|
3
3
|
|
|
4
4
|
interface UseSpeechInputOptions {
|
|
5
5
|
/** Receives settled text as it is recognised. Append it to the draft. */
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions,
|
|
2
|
-
import '../explore-types-
|
|
1
|
+
export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, c as ExploreEscalateOutcome, d as ExploreEscalateResult, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, e as UseExploreChatResult, f as UseSpeechOutputOptions, g as UseSpeechOutputResult, h as classifyExploreError, s as stopSpeaking, u as useExploreChat, i as useProgressiveReveal, j as useSpeechOutput } from '../use-progressive-reveal-DHPGGxZu.js';
|
|
2
|
+
import '../explore-types-B5sdOC5M.js';
|
|
3
3
|
|
|
4
4
|
interface UseSpeechInputOptions {
|
|
5
5
|
/** Receives settled text as it is recognised. Append it to the draft. */
|
package/dist/hooks/index.js
CHANGED
|
@@ -226,6 +226,10 @@ function useWebSDK() {
|
|
|
226
226
|
}
|
|
227
227
|
return client;
|
|
228
228
|
}
|
|
229
|
+
function useWebSDKConfig() {
|
|
230
|
+
const client = useWebSDK();
|
|
231
|
+
return client.getConfig();
|
|
232
|
+
}
|
|
229
233
|
|
|
230
234
|
// src/components/explore-types.ts
|
|
231
235
|
var EXPLORE_MESSAGE_FIELDS = (
|
|
@@ -385,6 +389,22 @@ var SUBMIT_EXPLORE_FEEDBACK_MUTATION = (
|
|
|
385
389
|
}
|
|
386
390
|
`
|
|
387
391
|
);
|
|
392
|
+
var ESCALATE_EXPLORE_CONVERSATION_MUTATION = (
|
|
393
|
+
/* GraphQL */
|
|
394
|
+
`
|
|
395
|
+
mutation EscalateExploreConversation(
|
|
396
|
+
$input: EscalateExploreConversationInput!
|
|
397
|
+
) {
|
|
398
|
+
escalateExploreConversation(input: $input) {
|
|
399
|
+
created
|
|
400
|
+
escalation {
|
|
401
|
+
email
|
|
402
|
+
createdAt
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
`
|
|
407
|
+
);
|
|
388
408
|
var CREATE_EXPLORE_SHARE_LINK_MUTATION = (
|
|
389
409
|
/* GraphQL */
|
|
390
410
|
`
|
|
@@ -751,6 +771,25 @@ var VALIDATION_CODES = /* @__PURE__ */ new Set([
|
|
|
751
771
|
"CONVERSATION_LIMIT",
|
|
752
772
|
"INVALID_INPUT"
|
|
753
773
|
]);
|
|
774
|
+
function escalateOutcomeForCode(code) {
|
|
775
|
+
switch (code) {
|
|
776
|
+
case "INVALID_EMAIL":
|
|
777
|
+
return "invalid-email";
|
|
778
|
+
case "RATE_LIMITED":
|
|
779
|
+
case "HTTP_429":
|
|
780
|
+
return "rate-limited";
|
|
781
|
+
case "CAPTCHA_REQUIRED":
|
|
782
|
+
case "CAPTCHA_FAILED":
|
|
783
|
+
return "captcha";
|
|
784
|
+
// The token owns nothing, or owns a different conversation. The server answers NOT_FOUND
|
|
785
|
+
// rather than FORBIDDEN so the endpoint is not an oracle for whether a thread exists;
|
|
786
|
+
// either way there is nothing this visitor can do about it, so it reads as a failure.
|
|
787
|
+
case "NOT_FOUND":
|
|
788
|
+
return "failed";
|
|
789
|
+
default:
|
|
790
|
+
return "failed";
|
|
791
|
+
}
|
|
792
|
+
}
|
|
754
793
|
function classifyExploreError(code, serverMessage) {
|
|
755
794
|
const c = code ?? "UNKNOWN";
|
|
756
795
|
if (RATE_LIMIT_CODES.has(c)) {
|
|
@@ -917,6 +956,7 @@ function useExploreChat(options = {}) {
|
|
|
917
956
|
onSend
|
|
918
957
|
} = options;
|
|
919
958
|
const client = useWebSDK();
|
|
959
|
+
const { productId } = useWebSDKConfig();
|
|
920
960
|
const promptKey = JSON.stringify(examplePrompts ?? []);
|
|
921
961
|
const welcomeKey = JSON.stringify(fallbackWelcome ?? null);
|
|
922
962
|
const fallbackCatalog = react.useMemo(
|
|
@@ -940,6 +980,7 @@ function useExploreChat(options = {}) {
|
|
|
940
980
|
const pollGenerationRef = react.useRef(0);
|
|
941
981
|
const pollTimerRef = react.useRef(null);
|
|
942
982
|
const conversationTokenRef = react.useRef(null);
|
|
983
|
+
const [hasConversation, setHasConversation] = react.useState(false);
|
|
943
984
|
const shareTokenRef = react.useRef(void 0);
|
|
944
985
|
if (shareTokenRef.current === void 0) {
|
|
945
986
|
shareTokenRef.current = readShareToken();
|
|
@@ -954,6 +995,10 @@ function useExploreChat(options = {}) {
|
|
|
954
995
|
);
|
|
955
996
|
const inFlightRef = react.useRef(false);
|
|
956
997
|
catalogRef.current = catalog;
|
|
998
|
+
const rememberConversationToken = react.useCallback((token) => {
|
|
999
|
+
conversationTokenRef.current = token;
|
|
1000
|
+
setHasConversation(token !== null);
|
|
1001
|
+
}, []);
|
|
957
1002
|
const clearPollTimer = react.useCallback(() => {
|
|
958
1003
|
if (pollTimerRef.current !== null) {
|
|
959
1004
|
clearTimeout(pollTimerRef.current);
|
|
@@ -1020,7 +1065,7 @@ function useExploreChat(options = {}) {
|
|
|
1020
1065
|
setArchive(readArchive(storageKey));
|
|
1021
1066
|
const stored = readPersisted(storageKey);
|
|
1022
1067
|
if (stored) {
|
|
1023
|
-
|
|
1068
|
+
rememberConversationToken(stored.conversationToken);
|
|
1024
1069
|
focusProductRef.current = stored.focusProduct;
|
|
1025
1070
|
const settled = stored.messages.flatMap((m) => {
|
|
1026
1071
|
if (m.role !== "ASSISTANT") return [m];
|
|
@@ -1036,7 +1081,7 @@ function useExploreChat(options = {}) {
|
|
|
1036
1081
|
setTurnCount(turns);
|
|
1037
1082
|
}
|
|
1038
1083
|
setHydrated(true);
|
|
1039
|
-
}, [persist, storageKey]);
|
|
1084
|
+
}, [persist, rememberConversationToken, storageKey]);
|
|
1040
1085
|
react.useEffect(() => {
|
|
1041
1086
|
if (!persist || !hydrated || typeof window === "undefined") return;
|
|
1042
1087
|
try {
|
|
@@ -1229,7 +1274,7 @@ function useExploreChat(options = {}) {
|
|
|
1229
1274
|
setPhase(classified.kind === "rate-limit" ? "limited" : "error");
|
|
1230
1275
|
return;
|
|
1231
1276
|
}
|
|
1232
|
-
|
|
1277
|
+
rememberConversationToken(result.conversation.token);
|
|
1233
1278
|
turnCountRef.current = result.conversation.turnCount;
|
|
1234
1279
|
setTurnCount(result.conversation.turnCount);
|
|
1235
1280
|
setRemainingToday(result.remainingToday ?? null);
|
|
@@ -1255,7 +1300,14 @@ function useExploreChat(options = {}) {
|
|
|
1255
1300
|
setPhase("streaming");
|
|
1256
1301
|
startPolling(result.conversation.token, assistantMessage.id);
|
|
1257
1302
|
},
|
|
1258
|
-
[
|
|
1303
|
+
[
|
|
1304
|
+
client,
|
|
1305
|
+
locale,
|
|
1306
|
+
pageUrl,
|
|
1307
|
+
productSlug,
|
|
1308
|
+
rememberConversationToken,
|
|
1309
|
+
startPolling
|
|
1310
|
+
]
|
|
1259
1311
|
);
|
|
1260
1312
|
const send = react.useCallback(
|
|
1261
1313
|
async (message, attachments = []) => {
|
|
@@ -1372,7 +1424,7 @@ function useExploreChat(options = {}) {
|
|
|
1372
1424
|
);
|
|
1373
1425
|
cancelPolling();
|
|
1374
1426
|
inFlightRef.current = false;
|
|
1375
|
-
|
|
1427
|
+
rememberConversationToken(null);
|
|
1376
1428
|
lastSentTextRef.current = null;
|
|
1377
1429
|
lastSentAttachmentsRef.current = [];
|
|
1378
1430
|
turnCountRef.current = 0;
|
|
@@ -1387,7 +1439,7 @@ function useExploreChat(options = {}) {
|
|
|
1387
1439
|
} catch {
|
|
1388
1440
|
}
|
|
1389
1441
|
}
|
|
1390
|
-
}, [cancelPolling, storageKey]);
|
|
1442
|
+
}, [cancelPolling, rememberConversationToken, storageKey]);
|
|
1391
1443
|
const openConversation = react.useCallback(
|
|
1392
1444
|
(token) => {
|
|
1393
1445
|
const entry = readArchive(storageKey).find((e) => e.token === token);
|
|
@@ -1399,7 +1451,7 @@ function useExploreChat(options = {}) {
|
|
|
1399
1451
|
});
|
|
1400
1452
|
cancelPolling();
|
|
1401
1453
|
inFlightRef.current = false;
|
|
1402
|
-
|
|
1454
|
+
rememberConversationToken(entry.token);
|
|
1403
1455
|
turnCountRef.current = entry.messages.filter(
|
|
1404
1456
|
(m) => m.role === "USER"
|
|
1405
1457
|
).length;
|
|
@@ -1411,7 +1463,7 @@ function useExploreChat(options = {}) {
|
|
|
1411
1463
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
1412
1464
|
setArchive(readArchive(storageKey));
|
|
1413
1465
|
},
|
|
1414
|
-
[cancelPolling, storageKey]
|
|
1466
|
+
[cancelPolling, rememberConversationToken, storageKey]
|
|
1415
1467
|
);
|
|
1416
1468
|
const deleteConversation = react.useCallback(
|
|
1417
1469
|
(token) => {
|
|
@@ -1500,6 +1552,69 @@ function useExploreChat(options = {}) {
|
|
|
1500
1552
|
},
|
|
1501
1553
|
[client]
|
|
1502
1554
|
);
|
|
1555
|
+
const escalate = react.useCallback(
|
|
1556
|
+
async (email, note) => {
|
|
1557
|
+
const token = conversationTokenRef.current;
|
|
1558
|
+
if (!token) {
|
|
1559
|
+
return { outcome: "no-conversation", email: null, createdAt: null };
|
|
1560
|
+
}
|
|
1561
|
+
const attempt = async (captchaAttempt) => {
|
|
1562
|
+
const activeCatalog = catalogRef.current;
|
|
1563
|
+
let recaptchaToken;
|
|
1564
|
+
if (activeCatalog.captchaRequired && activeCatalog.recaptchaSiteKey) {
|
|
1565
|
+
try {
|
|
1566
|
+
recaptchaToken = await getRecaptchaV3Token(
|
|
1567
|
+
activeCatalog.recaptchaSiteKey
|
|
1568
|
+
);
|
|
1569
|
+
} catch (err) {
|
|
1570
|
+
console.warn(
|
|
1571
|
+
"[WebSDK] reCAPTCHA v3 token could not be minted",
|
|
1572
|
+
err
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
const input = {
|
|
1577
|
+
conversationToken: token,
|
|
1578
|
+
email: email.trim(),
|
|
1579
|
+
note: note?.trim() ? note.trim() : null,
|
|
1580
|
+
...recaptchaToken ? { recaptchaToken } : {},
|
|
1581
|
+
...productId ? { productId } : {}
|
|
1582
|
+
};
|
|
1583
|
+
try {
|
|
1584
|
+
const res = await client.mutate(
|
|
1585
|
+
ESCALATE_EXPLORE_CONVERSATION_MUTATION,
|
|
1586
|
+
{ input }
|
|
1587
|
+
);
|
|
1588
|
+
const first = res?.errors?.[0];
|
|
1589
|
+
const result = res?.data?.escalateExploreConversation;
|
|
1590
|
+
if (first || !result) {
|
|
1591
|
+
const code = typeof first?.extensions?.code === "string" ? first.extensions.code : "ESCALATION_FAILED";
|
|
1592
|
+
if (code === "CAPTCHA_FAILED" && captchaAttempt === 0) {
|
|
1593
|
+
return attempt(1);
|
|
1594
|
+
}
|
|
1595
|
+
return {
|
|
1596
|
+
outcome: escalateOutcomeForCode(code),
|
|
1597
|
+
email: null,
|
|
1598
|
+
createdAt: null
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
return {
|
|
1602
|
+
// `created: false` is the double-click, the retry, the reload. The handover
|
|
1603
|
+
// exists, so this is a success — and the address comes from the SERVER, because
|
|
1604
|
+
// on a repeat submit the reply goes to whatever the FIRST handover used.
|
|
1605
|
+
outcome: result.created ? "sent" : "already-handed-over",
|
|
1606
|
+
email: result.escalation?.email ?? null,
|
|
1607
|
+
createdAt: result.escalation?.createdAt ?? null
|
|
1608
|
+
};
|
|
1609
|
+
} catch {
|
|
1610
|
+
return { outcome: "failed", email: null, createdAt: null };
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
return attempt(0);
|
|
1614
|
+
},
|
|
1615
|
+
[client, productId]
|
|
1616
|
+
);
|
|
1617
|
+
const canEscalate = hasConversation && !isSharedView;
|
|
1503
1618
|
return {
|
|
1504
1619
|
phase,
|
|
1505
1620
|
catalog,
|
|
@@ -1520,6 +1635,8 @@ function useExploreChat(options = {}) {
|
|
|
1520
1635
|
clearHistory,
|
|
1521
1636
|
recordClick,
|
|
1522
1637
|
submitFeedback,
|
|
1638
|
+
escalate,
|
|
1639
|
+
canEscalate,
|
|
1523
1640
|
createShareLink,
|
|
1524
1641
|
isSharedView
|
|
1525
1642
|
};
|
|
@@ -1747,11 +1864,367 @@ function useSpeechInput({
|
|
|
1747
1864
|
);
|
|
1748
1865
|
return { supported, listening, interim, error, start, stop, toggle };
|
|
1749
1866
|
}
|
|
1867
|
+
var SILENT_FAILURE_MS = 1500;
|
|
1868
|
+
function speechPlatform() {
|
|
1869
|
+
if (typeof window === "undefined") return null;
|
|
1870
|
+
const scope = window;
|
|
1871
|
+
const synth = scope.speechSynthesis;
|
|
1872
|
+
const Utterance = scope.SpeechSynthesisUtterance;
|
|
1873
|
+
if (!synth || typeof synth.speak !== "function") return null;
|
|
1874
|
+
if (typeof Utterance !== "function") return null;
|
|
1875
|
+
return { synth, Utterance };
|
|
1876
|
+
}
|
|
1877
|
+
function normaliseTag(tag) {
|
|
1878
|
+
return tag.trim().toLowerCase().replace(/_/g, "-");
|
|
1879
|
+
}
|
|
1880
|
+
function primaryOf(tag) {
|
|
1881
|
+
return normaliseTag(tag).split("-")[0] ?? "";
|
|
1882
|
+
}
|
|
1883
|
+
function pickVoice(voices, lang) {
|
|
1884
|
+
const wanted = normaliseTag(lang);
|
|
1885
|
+
if (wanted === "") return null;
|
|
1886
|
+
const primary = primaryOf(wanted);
|
|
1887
|
+
const exact = voices.filter((voice) => normaliseTag(voice.lang) === wanted);
|
|
1888
|
+
const related = voices.filter((voice) => primaryOf(voice.lang) === primary);
|
|
1889
|
+
return exact.find((voice) => voice.default) ?? exact[0] ?? related.find((voice) => voice.default) ?? related[0] ?? null;
|
|
1890
|
+
}
|
|
1891
|
+
var activeOwner = null;
|
|
1892
|
+
var activeStop = null;
|
|
1893
|
+
function stopSpeaking() {
|
|
1894
|
+
activeStop?.();
|
|
1895
|
+
}
|
|
1896
|
+
function useSpeechOutput({
|
|
1897
|
+
lang
|
|
1898
|
+
} = {}) {
|
|
1899
|
+
const [available, setAvailable] = react.useState(() => speechPlatform() !== null);
|
|
1900
|
+
const [speaking, setSpeaking] = react.useState(false);
|
|
1901
|
+
const utteranceRef = react.useRef(null);
|
|
1902
|
+
const watchdogRef = react.useRef(null);
|
|
1903
|
+
const ownerRef = react.useRef({});
|
|
1904
|
+
const stopRef = react.useRef(() => void 0);
|
|
1905
|
+
const langRef = react.useRef(lang);
|
|
1906
|
+
langRef.current = lang;
|
|
1907
|
+
const clearWatchdog = react.useCallback(() => {
|
|
1908
|
+
if (watchdogRef.current === null) return;
|
|
1909
|
+
clearTimeout(watchdogRef.current);
|
|
1910
|
+
watchdogRef.current = null;
|
|
1911
|
+
}, []);
|
|
1912
|
+
const settle = react.useCallback(() => {
|
|
1913
|
+
clearWatchdog();
|
|
1914
|
+
utteranceRef.current = null;
|
|
1915
|
+
if (activeOwner === ownerRef.current) {
|
|
1916
|
+
activeOwner = null;
|
|
1917
|
+
activeStop = null;
|
|
1918
|
+
}
|
|
1919
|
+
setSpeaking(false);
|
|
1920
|
+
}, [clearWatchdog]);
|
|
1921
|
+
const stop = react.useCallback(() => {
|
|
1922
|
+
const owned = utteranceRef.current !== null;
|
|
1923
|
+
settle();
|
|
1924
|
+
if (owned) speechPlatform()?.synth.cancel();
|
|
1925
|
+
}, [settle]);
|
|
1926
|
+
stopRef.current = stop;
|
|
1927
|
+
const speak = react.useCallback(
|
|
1928
|
+
(text) => {
|
|
1929
|
+
const platform = speechPlatform();
|
|
1930
|
+
if (!platform) {
|
|
1931
|
+
setAvailable(false);
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
const spoken = text.trim();
|
|
1935
|
+
if (spoken === "") return;
|
|
1936
|
+
if (activeStop && activeOwner !== ownerRef.current) activeStop();
|
|
1937
|
+
platform.synth.cancel();
|
|
1938
|
+
const utterance = new platform.Utterance(spoken);
|
|
1939
|
+
const wanted = langRef.current;
|
|
1940
|
+
if (wanted !== void 0 && wanted !== "") {
|
|
1941
|
+
const voice = pickVoice(platform.synth.getVoices(), wanted);
|
|
1942
|
+
utterance.lang = voice?.lang ?? wanted;
|
|
1943
|
+
if (voice) utterance.voice = voice;
|
|
1944
|
+
}
|
|
1945
|
+
const mine = () => utteranceRef.current === utterance;
|
|
1946
|
+
utterance.onstart = () => {
|
|
1947
|
+
if (!mine()) return;
|
|
1948
|
+
clearWatchdog();
|
|
1949
|
+
setSpeaking(true);
|
|
1950
|
+
};
|
|
1951
|
+
utterance.onend = () => {
|
|
1952
|
+
if (!mine()) return;
|
|
1953
|
+
settle();
|
|
1954
|
+
};
|
|
1955
|
+
utterance.onerror = (event) => {
|
|
1956
|
+
if (!mine()) return;
|
|
1957
|
+
const code = typeof event?.error === "string" ? event.error : "";
|
|
1958
|
+
settle();
|
|
1959
|
+
if (code !== "interrupted" && code !== "canceled") setAvailable(false);
|
|
1960
|
+
};
|
|
1961
|
+
utteranceRef.current = utterance;
|
|
1962
|
+
activeOwner = ownerRef.current;
|
|
1963
|
+
activeStop = () => {
|
|
1964
|
+
stopRef.current();
|
|
1965
|
+
};
|
|
1966
|
+
setSpeaking(true);
|
|
1967
|
+
platform.synth.speak(utterance);
|
|
1968
|
+
clearWatchdog();
|
|
1969
|
+
watchdogRef.current = setTimeout(() => {
|
|
1970
|
+
watchdogRef.current = null;
|
|
1971
|
+
if (!mine()) return;
|
|
1972
|
+
if (platform.synth.speaking) return;
|
|
1973
|
+
settle();
|
|
1974
|
+
setAvailable(false);
|
|
1975
|
+
}, SILENT_FAILURE_MS);
|
|
1976
|
+
},
|
|
1977
|
+
[clearWatchdog, settle]
|
|
1978
|
+
);
|
|
1979
|
+
const toggle = react.useCallback(
|
|
1980
|
+
(text) => {
|
|
1981
|
+
if (speaking) stop();
|
|
1982
|
+
else speak(text);
|
|
1983
|
+
},
|
|
1984
|
+
[speak, speaking, stop]
|
|
1985
|
+
);
|
|
1986
|
+
react.useEffect(() => {
|
|
1987
|
+
if (!speaking || typeof window === "undefined") return;
|
|
1988
|
+
const onHide = () => {
|
|
1989
|
+
stopRef.current();
|
|
1990
|
+
};
|
|
1991
|
+
window.addEventListener("pagehide", onHide);
|
|
1992
|
+
return () => window.removeEventListener("pagehide", onHide);
|
|
1993
|
+
}, [speaking]);
|
|
1994
|
+
react.useEffect(
|
|
1995
|
+
() => () => {
|
|
1996
|
+
stopRef.current();
|
|
1997
|
+
},
|
|
1998
|
+
[]
|
|
1999
|
+
);
|
|
2000
|
+
return { supported: available, speaking, speak, stop, toggle };
|
|
2001
|
+
}
|
|
2002
|
+
var DRAIN_TARGET_MS = 1200;
|
|
2003
|
+
var MIN_CHARS_PER_SECOND = 90;
|
|
2004
|
+
var MAX_CHARS_PER_SECOND = 2200;
|
|
2005
|
+
var MAX_STEP_MS = 250;
|
|
2006
|
+
var COMMIT_INTERVAL_MS = 45;
|
|
2007
|
+
var FALLBACK_STEP_MS = 50;
|
|
2008
|
+
var FENCE = /^ {0,3}(?:```|~~~)/;
|
|
2009
|
+
var TABLE_ROW = /^ {0,3}\|/;
|
|
2010
|
+
var EMPTY_BLOCK_MARKER = /^ {0,3}(?:[-+>]|#{1,6}|\d{1,9}[.)])\s*$/;
|
|
2011
|
+
function isWordCharacter(char) {
|
|
2012
|
+
return char !== void 0 && /[\p{L}\p{N}]/u.test(char);
|
|
2013
|
+
}
|
|
2014
|
+
function clamp(value, min, max) {
|
|
2015
|
+
return Math.min(Math.max(value, min), max);
|
|
2016
|
+
}
|
|
2017
|
+
function now() {
|
|
2018
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
2019
|
+
}
|
|
2020
|
+
function isHighSurrogate(code) {
|
|
2021
|
+
return code >= 55296 && code <= 56319;
|
|
2022
|
+
}
|
|
2023
|
+
function safeInlineEnd(line) {
|
|
2024
|
+
let codeSpan = null;
|
|
2025
|
+
let link = null;
|
|
2026
|
+
const emphasis = [];
|
|
2027
|
+
let at = 0;
|
|
2028
|
+
while (at < line.length) {
|
|
2029
|
+
const char = line[at] ?? "";
|
|
2030
|
+
if (char === "\\") {
|
|
2031
|
+
at += 2;
|
|
2032
|
+
continue;
|
|
2033
|
+
}
|
|
2034
|
+
if (char === "`") {
|
|
2035
|
+
let run = 1;
|
|
2036
|
+
while (line[at + run] === "`") run += 1;
|
|
2037
|
+
codeSpan = codeSpan === null ? at : null;
|
|
2038
|
+
at += run;
|
|
2039
|
+
continue;
|
|
2040
|
+
}
|
|
2041
|
+
if (codeSpan !== null) {
|
|
2042
|
+
at += 1;
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
if (char === "[") {
|
|
2046
|
+
if (link === null) link = line[at - 1] === "!" ? at - 1 : at;
|
|
2047
|
+
at += 1;
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
if (char === "]") {
|
|
2051
|
+
if (link !== null && line[at + 1] !== "(") link = null;
|
|
2052
|
+
at += 1;
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
if (char === ")") {
|
|
2056
|
+
link = null;
|
|
2057
|
+
at += 1;
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
if (char === "*" || char === "_" || char === "~") {
|
|
2061
|
+
let run = 1;
|
|
2062
|
+
while (line[at + run] === char) run += 1;
|
|
2063
|
+
const start = at;
|
|
2064
|
+
at += run;
|
|
2065
|
+
const before = line[start - 1];
|
|
2066
|
+
const after = line[start + run];
|
|
2067
|
+
if (run < 2) continue;
|
|
2068
|
+
if (char === "_" && isWordCharacter(before) && isWordCharacter(after)) {
|
|
2069
|
+
continue;
|
|
2070
|
+
}
|
|
2071
|
+
const marker = char.repeat(run);
|
|
2072
|
+
const open2 = emphasis.findIndex((entry) => entry.marker === marker);
|
|
2073
|
+
if (open2 !== -1) {
|
|
2074
|
+
emphasis.splice(open2, 1);
|
|
2075
|
+
continue;
|
|
2076
|
+
}
|
|
2077
|
+
if (after !== void 0 && /\s/.test(after)) continue;
|
|
2078
|
+
emphasis.push({ marker, start });
|
|
2079
|
+
continue;
|
|
2080
|
+
}
|
|
2081
|
+
at += 1;
|
|
2082
|
+
}
|
|
2083
|
+
const open = [codeSpan, link, emphasis[0]?.start ?? null].filter(
|
|
2084
|
+
(value) => value !== null
|
|
2085
|
+
);
|
|
2086
|
+
return open.length === 0 ? line.length : Math.min(...open);
|
|
2087
|
+
}
|
|
2088
|
+
function revealBoundary(text, limit) {
|
|
2089
|
+
if (limit <= 0) return 0;
|
|
2090
|
+
const caughtUp = limit >= text.length;
|
|
2091
|
+
const head = caughtUp ? text : text.slice(0, limit);
|
|
2092
|
+
const lineStart = head.lastIndexOf("\n") + 1;
|
|
2093
|
+
const line = head.slice(lineStart);
|
|
2094
|
+
let fenceStart = null;
|
|
2095
|
+
let tableStart = null;
|
|
2096
|
+
let tableRows = 0;
|
|
2097
|
+
let at = 0;
|
|
2098
|
+
while (at < lineStart) {
|
|
2099
|
+
const breakAt = head.indexOf("\n", at);
|
|
2100
|
+
const end = breakAt === -1 ? lineStart : breakAt;
|
|
2101
|
+
const row = head.slice(at, end);
|
|
2102
|
+
if (fenceStart !== null) {
|
|
2103
|
+
if (FENCE.test(row)) fenceStart = null;
|
|
2104
|
+
} else if (FENCE.test(row)) {
|
|
2105
|
+
fenceStart = at;
|
|
2106
|
+
} else if (TABLE_ROW.test(row)) {
|
|
2107
|
+
if (tableStart === null) {
|
|
2108
|
+
tableStart = at;
|
|
2109
|
+
tableRows = 0;
|
|
2110
|
+
}
|
|
2111
|
+
tableRows += 1;
|
|
2112
|
+
} else if (tableStart !== null) {
|
|
2113
|
+
tableStart = null;
|
|
2114
|
+
tableRows = 0;
|
|
2115
|
+
}
|
|
2116
|
+
at = end + 1;
|
|
2117
|
+
}
|
|
2118
|
+
if (fenceStart === null) {
|
|
2119
|
+
const lastIsRow = TABLE_ROW.test(line);
|
|
2120
|
+
if (lastIsRow && tableStart === null) {
|
|
2121
|
+
tableStart = lineStart;
|
|
2122
|
+
tableRows = 0;
|
|
2123
|
+
} else if (!lastIsRow && line !== "") {
|
|
2124
|
+
tableStart = null;
|
|
2125
|
+
tableRows = 0;
|
|
2126
|
+
}
|
|
2127
|
+
if (tableStart !== null && tableRows < 2) return tableStart;
|
|
2128
|
+
}
|
|
2129
|
+
if (caughtUp) return text.length;
|
|
2130
|
+
if (fenceStart !== null) return fenceStart;
|
|
2131
|
+
if (TABLE_ROW.test(line)) return lineStart;
|
|
2132
|
+
if (EMPTY_BLOCK_MARKER.test(line)) return lineStart;
|
|
2133
|
+
const cut = lineStart + safeInlineEnd(line);
|
|
2134
|
+
return isHighSurrogate(text.charCodeAt(cut - 1)) ? cut - 1 : cut;
|
|
2135
|
+
}
|
|
2136
|
+
function prefersReducedMotion() {
|
|
2137
|
+
if (typeof window === "undefined") return true;
|
|
2138
|
+
try {
|
|
2139
|
+
return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
2140
|
+
} catch {
|
|
2141
|
+
return false;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
function useProgressiveReveal(text, settled) {
|
|
2145
|
+
const [, setFrame] = react.useState(0);
|
|
2146
|
+
const [reduced] = react.useState(prefersReducedMotion);
|
|
2147
|
+
const immediate = settled || reduced;
|
|
2148
|
+
const textRef = react.useRef(text);
|
|
2149
|
+
const cursorRef = react.useRef(immediate ? text.length : 0);
|
|
2150
|
+
const cutRef = react.useRef(immediate ? text.length : 0);
|
|
2151
|
+
const committedRef = react.useRef(0);
|
|
2152
|
+
if (text !== textRef.current) {
|
|
2153
|
+
const revealed = textRef.current.slice(0, Math.floor(cursorRef.current));
|
|
2154
|
+
if (revealed !== "" && !text.startsWith(revealed)) {
|
|
2155
|
+
cursorRef.current = text.length;
|
|
2156
|
+
}
|
|
2157
|
+
cursorRef.current = Math.min(cursorRef.current, text.length);
|
|
2158
|
+
cutRef.current = Math.min(cutRef.current, text.length);
|
|
2159
|
+
textRef.current = text;
|
|
2160
|
+
}
|
|
2161
|
+
if (immediate) cursorRef.current = text.length;
|
|
2162
|
+
const cut = immediate ? text.length : Math.max(
|
|
2163
|
+
cutRef.current,
|
|
2164
|
+
revealBoundary(text, Math.floor(cursorRef.current))
|
|
2165
|
+
);
|
|
2166
|
+
cutRef.current = cut;
|
|
2167
|
+
react.useEffect(() => {
|
|
2168
|
+
if (immediate) return;
|
|
2169
|
+
if (cursorRef.current >= text.length) return;
|
|
2170
|
+
let cancelled = false;
|
|
2171
|
+
let cancelStep = null;
|
|
2172
|
+
let previous = now();
|
|
2173
|
+
const commit = (at, force) => {
|
|
2174
|
+
if (!force && at - committedRef.current < COMMIT_INTERVAL_MS) return;
|
|
2175
|
+
committedRef.current = at;
|
|
2176
|
+
setFrame((value) => value + 1);
|
|
2177
|
+
};
|
|
2178
|
+
const step = () => {
|
|
2179
|
+
cancelStep = null;
|
|
2180
|
+
if (cancelled) return;
|
|
2181
|
+
const at = now();
|
|
2182
|
+
const elapsed = clamp(at - previous, 0, MAX_STEP_MS);
|
|
2183
|
+
previous = at;
|
|
2184
|
+
const length = textRef.current.length;
|
|
2185
|
+
const pending = length - cursorRef.current;
|
|
2186
|
+
if (pending <= 0) {
|
|
2187
|
+
commit(at, true);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
const perSecond = clamp(
|
|
2191
|
+
pending / (DRAIN_TARGET_MS / 1e3),
|
|
2192
|
+
MIN_CHARS_PER_SECOND,
|
|
2193
|
+
MAX_CHARS_PER_SECOND
|
|
2194
|
+
);
|
|
2195
|
+
cursorRef.current = Math.min(
|
|
2196
|
+
length,
|
|
2197
|
+
cursorRef.current + perSecond * elapsed / 1e3
|
|
2198
|
+
);
|
|
2199
|
+
const done = cursorRef.current >= length;
|
|
2200
|
+
commit(at, done);
|
|
2201
|
+
if (!done) schedule();
|
|
2202
|
+
};
|
|
2203
|
+
const schedule = () => {
|
|
2204
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
2205
|
+
const id2 = window.requestAnimationFrame(step);
|
|
2206
|
+
cancelStep = () => window.cancelAnimationFrame(id2);
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
const id = setTimeout(step, FALLBACK_STEP_MS);
|
|
2210
|
+
cancelStep = () => clearTimeout(id);
|
|
2211
|
+
};
|
|
2212
|
+
schedule();
|
|
2213
|
+
return () => {
|
|
2214
|
+
cancelled = true;
|
|
2215
|
+
cancelStep?.();
|
|
2216
|
+
};
|
|
2217
|
+
}, [text, immediate]);
|
|
2218
|
+
return text.slice(0, cut);
|
|
2219
|
+
}
|
|
1750
2220
|
|
|
1751
2221
|
exports.DEFAULT_EXPLORE_EXAMPLE_PROMPTS = DEFAULT_EXPLORE_EXAMPLE_PROMPTS;
|
|
1752
2222
|
exports.FALLBACK_EXPLORE_LIMITS = FALLBACK_EXPLORE_LIMITS;
|
|
1753
2223
|
exports.classifyExploreError = classifyExploreError;
|
|
2224
|
+
exports.stopSpeaking = stopSpeaking;
|
|
1754
2225
|
exports.useExploreChat = useExploreChat;
|
|
2226
|
+
exports.useProgressiveReveal = useProgressiveReveal;
|
|
1755
2227
|
exports.useSpeechInput = useSpeechInput;
|
|
2228
|
+
exports.useSpeechOutput = useSpeechOutput;
|
|
1756
2229
|
//# sourceMappingURL=index.js.map
|
|
1757
2230
|
//# sourceMappingURL=index.js.map
|