@burdenoff/website-sdk 2026.828.4 → 2026.828.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-cta.js +9 -7
- package/dist/components/explore-cta.js.map +1 -1
- package/dist/components/explore-cta.mjs +9 -7
- package/dist/components/explore-cta.mjs.map +1 -1
- package/dist/components/explore.js +427 -41
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +429 -43
- package/dist/components/explore.mjs.map +1 -1
- package/dist/hooks/index.d.mts +24 -1
- package/dist/hooks/index.d.ts +24 -1
- package/dist/hooks/index.js +235 -5
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +235 -6
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +436 -48
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +437 -49
- package/dist/index.mjs.map +1 -1
- package/dist/{index-CUJTTlYx.d.mts → use-explore-chat-DhvZhH8f.d.mts} +21 -2
- package/dist/{index-CUJTTlYx.d.ts → use-explore-chat-DhvZhH8f.d.ts} +21 -2
- package/package.json +1 -1
|
@@ -806,6 +806,8 @@ async function getRecaptchaV3Token(siteKey, action = EXPLORE_RECAPTCHA_ACTION) {
|
|
|
806
806
|
|
|
807
807
|
// src/hooks/use-explore-chat.ts
|
|
808
808
|
var DEFAULT_STORAGE_KEY = "boff.explore.v1";
|
|
809
|
+
var HISTORY_SUFFIX = ".history";
|
|
810
|
+
var MAX_ARCHIVED_CONVERSATIONS = 15;
|
|
809
811
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
810
812
|
var DEFAULT_POLL_TIMEOUT_MS = 9e4;
|
|
811
813
|
var POLL_REQUEST_TIMEOUT_MS = 1e4;
|
|
@@ -944,10 +946,58 @@ function classifyExploreError(code, serverMessage) {
|
|
|
944
946
|
retryable: true
|
|
945
947
|
};
|
|
946
948
|
}
|
|
949
|
+
function historyKey(key) {
|
|
950
|
+
return `${key}${HISTORY_SUFFIX}`;
|
|
951
|
+
}
|
|
952
|
+
function readArchive(key) {
|
|
953
|
+
if (typeof window === "undefined") return [];
|
|
954
|
+
try {
|
|
955
|
+
const raw = window.localStorage.getItem(historyKey(key));
|
|
956
|
+
if (!raw) return [];
|
|
957
|
+
const parsed = JSON.parse(raw);
|
|
958
|
+
if (!Array.isArray(parsed)) return [];
|
|
959
|
+
return parsed.filter(
|
|
960
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry.token === "string" && Array.isArray(entry.messages)
|
|
961
|
+
);
|
|
962
|
+
} catch {
|
|
963
|
+
return [];
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function writeArchive(key, entries) {
|
|
967
|
+
if (typeof window === "undefined") return;
|
|
968
|
+
try {
|
|
969
|
+
if (entries.length === 0) {
|
|
970
|
+
window.localStorage.removeItem(historyKey(key));
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
window.localStorage.setItem(historyKey(key), JSON.stringify(entries));
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function upsertArchive(key, thread) {
|
|
978
|
+
if (!thread.token) return readArchive(key);
|
|
979
|
+
const real = thread.messages.filter(
|
|
980
|
+
(m) => m.content.trim() !== "" || m.role === "USER"
|
|
981
|
+
);
|
|
982
|
+
if (real.length === 0) return readArchive(key);
|
|
983
|
+
const firstUser = real.find((m) => m.role === "USER");
|
|
984
|
+
const entry = {
|
|
985
|
+
token: thread.token,
|
|
986
|
+
title: (firstUser?.content ?? "Conversation").trim().slice(0, 80),
|
|
987
|
+
updatedAt: real[real.length - 1]?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
988
|
+
messageCount: real.length,
|
|
989
|
+
focusProduct: thread.focusProduct,
|
|
990
|
+
messages: real.slice(-20)
|
|
991
|
+
};
|
|
992
|
+
const rest = readArchive(key).filter((e) => e.token !== entry.token);
|
|
993
|
+
const next = [entry, ...rest].slice(0, MAX_ARCHIVED_CONVERSATIONS);
|
|
994
|
+
writeArchive(key, next);
|
|
995
|
+
return next;
|
|
996
|
+
}
|
|
947
997
|
function readPersisted(key) {
|
|
948
998
|
if (typeof window === "undefined") return null;
|
|
949
999
|
try {
|
|
950
|
-
const raw = window.
|
|
1000
|
+
const raw = window.localStorage.getItem(key);
|
|
951
1001
|
if (!raw) return null;
|
|
952
1002
|
const parsed = JSON.parse(raw);
|
|
953
1003
|
if (!parsed || typeof parsed !== "object") return null;
|
|
@@ -992,6 +1042,8 @@ function useExploreChat(options = {}) {
|
|
|
992
1042
|
const [remainingToday, setRemainingToday] = React.useState(null);
|
|
993
1043
|
const [focusProduct, setFocusProductState] = React.useState(null);
|
|
994
1044
|
const [turnCount, setTurnCount] = React.useState(0);
|
|
1045
|
+
const messagesRef = React.useRef([]);
|
|
1046
|
+
const [archive, setArchive] = React.useState([]);
|
|
995
1047
|
const [hydrated, setHydrated] = React.useState(false);
|
|
996
1048
|
const mountedRef = React.useRef(true);
|
|
997
1049
|
const pollGenerationRef = React.useRef(0);
|
|
@@ -1029,6 +1081,7 @@ function useExploreChat(options = {}) {
|
|
|
1029
1081
|
setHydrated(true);
|
|
1030
1082
|
return;
|
|
1031
1083
|
}
|
|
1084
|
+
setArchive(readArchive(storageKey));
|
|
1032
1085
|
const stored = readPersisted(storageKey);
|
|
1033
1086
|
if (stored) {
|
|
1034
1087
|
conversationTokenRef.current = stored.conversationToken;
|
|
@@ -1057,13 +1110,28 @@ function useExploreChat(options = {}) {
|
|
|
1057
1110
|
focusProduct
|
|
1058
1111
|
};
|
|
1059
1112
|
if (!payload.conversationToken && payload.messages.length === 0 && !payload.focusProduct) {
|
|
1060
|
-
window.
|
|
1113
|
+
window.localStorage.removeItem(storageKey);
|
|
1061
1114
|
return;
|
|
1062
1115
|
}
|
|
1063
|
-
window.
|
|
1116
|
+
window.localStorage.setItem(storageKey, JSON.stringify(payload));
|
|
1064
1117
|
} catch {
|
|
1065
1118
|
}
|
|
1066
1119
|
}, [persist, hydrated, storageKey, messages, focusProduct]);
|
|
1120
|
+
React.useEffect(() => {
|
|
1121
|
+
messagesRef.current = messages;
|
|
1122
|
+
if (!persist || !hydrated) return;
|
|
1123
|
+
const settled = messages.some(
|
|
1124
|
+
(m) => m.role === "ASSISTANT" && (m.status === "COMPLETED" || m.status === "FAILED")
|
|
1125
|
+
);
|
|
1126
|
+
if (!settled) return;
|
|
1127
|
+
setArchive(
|
|
1128
|
+
upsertArchive(storageKey, {
|
|
1129
|
+
token: conversationTokenRef.current,
|
|
1130
|
+
messages,
|
|
1131
|
+
focusProduct: focusProductRef.current
|
|
1132
|
+
})
|
|
1133
|
+
);
|
|
1134
|
+
}, [messages, persist, hydrated, storageKey]);
|
|
1067
1135
|
React.useEffect(() => {
|
|
1068
1136
|
let cancelled = false;
|
|
1069
1137
|
const load = async () => {
|
|
@@ -1349,6 +1417,13 @@ function useExploreChat(options = {}) {
|
|
|
1349
1417
|
void send(text);
|
|
1350
1418
|
}, [cancelPolling, send]);
|
|
1351
1419
|
const reset = React.useCallback(() => {
|
|
1420
|
+
setArchive(
|
|
1421
|
+
upsertArchive(storageKey, {
|
|
1422
|
+
token: conversationTokenRef.current,
|
|
1423
|
+
messages: messagesRef.current,
|
|
1424
|
+
focusProduct: focusProductRef.current
|
|
1425
|
+
})
|
|
1426
|
+
);
|
|
1352
1427
|
cancelPolling();
|
|
1353
1428
|
inFlightRef.current = false;
|
|
1354
1429
|
conversationTokenRef.current = null;
|
|
@@ -1361,11 +1436,48 @@ function useExploreChat(options = {}) {
|
|
|
1361
1436
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
1362
1437
|
if (typeof window !== "undefined") {
|
|
1363
1438
|
try {
|
|
1364
|
-
window.
|
|
1439
|
+
window.localStorage.removeItem(storageKey);
|
|
1365
1440
|
} catch {
|
|
1366
1441
|
}
|
|
1367
1442
|
}
|
|
1368
1443
|
}, [cancelPolling, storageKey]);
|
|
1444
|
+
const openConversation = React.useCallback(
|
|
1445
|
+
(token) => {
|
|
1446
|
+
const entry = readArchive(storageKey).find((e) => e.token === token);
|
|
1447
|
+
if (!entry) return;
|
|
1448
|
+
upsertArchive(storageKey, {
|
|
1449
|
+
token: conversationTokenRef.current,
|
|
1450
|
+
messages: messagesRef.current,
|
|
1451
|
+
focusProduct: focusProductRef.current
|
|
1452
|
+
});
|
|
1453
|
+
cancelPolling();
|
|
1454
|
+
inFlightRef.current = false;
|
|
1455
|
+
conversationTokenRef.current = entry.token;
|
|
1456
|
+
turnCountRef.current = entry.messages.filter(
|
|
1457
|
+
(m) => m.role === "USER"
|
|
1458
|
+
).length;
|
|
1459
|
+
setTurnCount(turnCountRef.current);
|
|
1460
|
+
setMessages(entry.messages);
|
|
1461
|
+
focusProductRef.current = entry.focusProduct;
|
|
1462
|
+
setFocusProductState(entry.focusProduct);
|
|
1463
|
+
setError(null);
|
|
1464
|
+
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
1465
|
+
setArchive(readArchive(storageKey));
|
|
1466
|
+
},
|
|
1467
|
+
[cancelPolling, storageKey]
|
|
1468
|
+
);
|
|
1469
|
+
const deleteConversation = React.useCallback(
|
|
1470
|
+
(token) => {
|
|
1471
|
+
const next = readArchive(storageKey).filter((e) => e.token !== token);
|
|
1472
|
+
writeArchive(storageKey, next);
|
|
1473
|
+
setArchive(next);
|
|
1474
|
+
},
|
|
1475
|
+
[storageKey]
|
|
1476
|
+
);
|
|
1477
|
+
const clearHistory = React.useCallback(() => {
|
|
1478
|
+
writeArchive(storageKey, []);
|
|
1479
|
+
setArchive([]);
|
|
1480
|
+
}, [storageKey]);
|
|
1369
1481
|
const setFocusProduct = React.useCallback((slug) => {
|
|
1370
1482
|
focusProductRef.current = slug;
|
|
1371
1483
|
setFocusProductState(slug);
|
|
@@ -1394,7 +1506,11 @@ function useExploreChat(options = {}) {
|
|
|
1394
1506
|
stop,
|
|
1395
1507
|
retry,
|
|
1396
1508
|
reset,
|
|
1397
|
-
canSend
|
|
1509
|
+
canSend,
|
|
1510
|
+
history: archive,
|
|
1511
|
+
openConversation,
|
|
1512
|
+
deleteConversation,
|
|
1513
|
+
clearHistory
|
|
1398
1514
|
};
|
|
1399
1515
|
}
|
|
1400
1516
|
var optimisticCounter = 0;
|
|
@@ -1402,6 +1518,119 @@ function makeOptimisticId() {
|
|
|
1402
1518
|
optimisticCounter += 1;
|
|
1403
1519
|
return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
|
|
1404
1520
|
}
|
|
1521
|
+
function getRecognitionCtor() {
|
|
1522
|
+
if (typeof window === "undefined") return null;
|
|
1523
|
+
const w = window;
|
|
1524
|
+
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1525
|
+
}
|
|
1526
|
+
function describeError(code) {
|
|
1527
|
+
switch (code) {
|
|
1528
|
+
case "not-allowed":
|
|
1529
|
+
case "service-not-allowed":
|
|
1530
|
+
return "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
1531
|
+
case "no-speech":
|
|
1532
|
+
return "I didn't catch anything \u2014 try again a little closer to the mic.";
|
|
1533
|
+
case "audio-capture":
|
|
1534
|
+
return "No microphone was found.";
|
|
1535
|
+
case "network":
|
|
1536
|
+
return "Speech recognition needs a network connection.";
|
|
1537
|
+
case "aborted":
|
|
1538
|
+
return "";
|
|
1539
|
+
default:
|
|
1540
|
+
return "Dictation stopped unexpectedly. You can type instead.";
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
function useSpeechInput({
|
|
1544
|
+
onFinalTranscript,
|
|
1545
|
+
lang,
|
|
1546
|
+
onError
|
|
1547
|
+
}) {
|
|
1548
|
+
const [supported] = React.useState(() => getRecognitionCtor() !== null);
|
|
1549
|
+
const [listening, setListening] = React.useState(false);
|
|
1550
|
+
const [interim, setInterim] = React.useState("");
|
|
1551
|
+
const [error, setError] = React.useState(null);
|
|
1552
|
+
const recognitionRef = React.useRef(null);
|
|
1553
|
+
const finalRef = React.useRef(onFinalTranscript);
|
|
1554
|
+
const errorRef = React.useRef(onError);
|
|
1555
|
+
finalRef.current = onFinalTranscript;
|
|
1556
|
+
errorRef.current = onError;
|
|
1557
|
+
const stop = React.useCallback(() => {
|
|
1558
|
+
const recognition = recognitionRef.current;
|
|
1559
|
+
if (!recognition) return;
|
|
1560
|
+
try {
|
|
1561
|
+
recognition.stop();
|
|
1562
|
+
} catch {
|
|
1563
|
+
}
|
|
1564
|
+
setListening(false);
|
|
1565
|
+
setInterim("");
|
|
1566
|
+
}, []);
|
|
1567
|
+
const start = React.useCallback(() => {
|
|
1568
|
+
const Ctor = getRecognitionCtor();
|
|
1569
|
+
if (!Ctor) return;
|
|
1570
|
+
if (recognitionRef.current) stop();
|
|
1571
|
+
const recognition = new Ctor();
|
|
1572
|
+
recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
1573
|
+
recognition.continuous = true;
|
|
1574
|
+
recognition.interimResults = true;
|
|
1575
|
+
recognition.maxAlternatives = 1;
|
|
1576
|
+
recognition.onstart = () => {
|
|
1577
|
+
setError(null);
|
|
1578
|
+
setListening(true);
|
|
1579
|
+
};
|
|
1580
|
+
recognition.onresult = (event) => {
|
|
1581
|
+
let settled = "";
|
|
1582
|
+
let pending = "";
|
|
1583
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
1584
|
+
const result = event.results[i];
|
|
1585
|
+
if (!result) continue;
|
|
1586
|
+
const text = result[0]?.transcript ?? "";
|
|
1587
|
+
if (result.isFinal) settled += text;
|
|
1588
|
+
else pending += text;
|
|
1589
|
+
}
|
|
1590
|
+
setInterim(pending);
|
|
1591
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
1592
|
+
};
|
|
1593
|
+
recognition.onerror = (event) => {
|
|
1594
|
+
const message = describeError(event.error);
|
|
1595
|
+
setListening(false);
|
|
1596
|
+
setInterim("");
|
|
1597
|
+
if (message !== "") {
|
|
1598
|
+
setError(message);
|
|
1599
|
+
errorRef.current?.(message);
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
recognition.onend = () => {
|
|
1603
|
+
setListening(false);
|
|
1604
|
+
setInterim("");
|
|
1605
|
+
};
|
|
1606
|
+
recognitionRef.current = recognition;
|
|
1607
|
+
try {
|
|
1608
|
+
recognition.start();
|
|
1609
|
+
} catch {
|
|
1610
|
+
setListening(false);
|
|
1611
|
+
}
|
|
1612
|
+
}, [lang, stop]);
|
|
1613
|
+
const toggle = React.useCallback(() => {
|
|
1614
|
+
if (listening) stop();
|
|
1615
|
+
else start();
|
|
1616
|
+
}, [listening, start, stop]);
|
|
1617
|
+
React.useEffect(
|
|
1618
|
+
() => () => {
|
|
1619
|
+
const recognition = recognitionRef.current;
|
|
1620
|
+
if (!recognition) return;
|
|
1621
|
+
recognition.onresult = null;
|
|
1622
|
+
recognition.onerror = null;
|
|
1623
|
+
recognition.onend = null;
|
|
1624
|
+
recognition.onstart = null;
|
|
1625
|
+
try {
|
|
1626
|
+
recognition.abort();
|
|
1627
|
+
} catch {
|
|
1628
|
+
}
|
|
1629
|
+
},
|
|
1630
|
+
[]
|
|
1631
|
+
);
|
|
1632
|
+
return { supported, listening, interim, error, start, stop, toggle };
|
|
1633
|
+
}
|
|
1405
1634
|
function canonicalFromLocation() {
|
|
1406
1635
|
if (typeof window === "undefined" || !window.location) return void 0;
|
|
1407
1636
|
const { origin, pathname } = window.location;
|
|
@@ -1954,10 +2183,29 @@ function ExplorePage({
|
|
|
1954
2183
|
stop,
|
|
1955
2184
|
retry,
|
|
1956
2185
|
reset,
|
|
1957
|
-
canSend
|
|
2186
|
+
canSend,
|
|
2187
|
+
history,
|
|
2188
|
+
openConversation,
|
|
2189
|
+
deleteConversation,
|
|
2190
|
+
clearHistory
|
|
1958
2191
|
} = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
|
|
1959
2192
|
const [draft, setDraft] = React.useState("");
|
|
1960
2193
|
const textareaRef = React.useRef(null);
|
|
2194
|
+
const [historyOpen, setHistoryOpen] = React.useState(false);
|
|
2195
|
+
const speechStopRef = React.useRef(() => void 0);
|
|
2196
|
+
const stopDictation = React.useCallback(() => {
|
|
2197
|
+
speechStopRef.current();
|
|
2198
|
+
}, []);
|
|
2199
|
+
const speech = useSpeechInput({
|
|
2200
|
+
onFinalTranscript: (text) => {
|
|
2201
|
+
setDraft((current) => {
|
|
2202
|
+
const joined = current.trim() === "" ? text.trimStart() : `${current.trimEnd()} ${text.trim()}`;
|
|
2203
|
+
return joined;
|
|
2204
|
+
});
|
|
2205
|
+
textareaRef.current?.focus();
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
speechStopRef.current = speech.stop;
|
|
1961
2209
|
const scrollRef = React.useRef(null);
|
|
1962
2210
|
const stickToBottomRef = React.useRef(true);
|
|
1963
2211
|
const composingRef = React.useRef(false);
|
|
@@ -1993,11 +2241,12 @@ function ExplorePage({
|
|
|
1993
2241
|
const submitDraft = React.useCallback(() => {
|
|
1994
2242
|
const text = draft.trim();
|
|
1995
2243
|
if (!text || overLimit || busy || !canSend) return;
|
|
2244
|
+
stopDictation();
|
|
1996
2245
|
setDraft("");
|
|
1997
2246
|
stickToBottomRef.current = true;
|
|
1998
2247
|
void send(text);
|
|
1999
2248
|
textareaRef.current?.focus();
|
|
2000
|
-
}, [busy, canSend, draft, overLimit, send]);
|
|
2249
|
+
}, [busy, canSend, draft, overLimit, send, stopDictation]);
|
|
2001
2250
|
const sendPrompt = React.useCallback(
|
|
2002
2251
|
(prompt) => {
|
|
2003
2252
|
if (!canSend || busy) return;
|
|
@@ -2070,8 +2319,96 @@ function ExplorePage({
|
|
|
2070
2319
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "New chat" })
|
|
2071
2320
|
]
|
|
2072
2321
|
}
|
|
2073
|
-
)
|
|
2322
|
+
),
|
|
2323
|
+
history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2324
|
+
Button,
|
|
2325
|
+
{
|
|
2326
|
+
"data-boff-explore": "history-toggle",
|
|
2327
|
+
type: "button",
|
|
2328
|
+
size: "sm",
|
|
2329
|
+
variant: "ghost",
|
|
2330
|
+
className: "shrink-0 text-muted-foreground",
|
|
2331
|
+
"aria-expanded": historyOpen,
|
|
2332
|
+
onClick: () => {
|
|
2333
|
+
setHistoryOpen((open) => !open);
|
|
2334
|
+
},
|
|
2335
|
+
children: [
|
|
2336
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.History, { className: "h-3.5 w-3.5" }),
|
|
2337
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden sm:inline", children: [
|
|
2338
|
+
"History (",
|
|
2339
|
+
history.length,
|
|
2340
|
+
")"
|
|
2341
|
+
] })
|
|
2342
|
+
]
|
|
2343
|
+
}
|
|
2344
|
+
) : null
|
|
2074
2345
|
] }) : null,
|
|
2346
|
+
historyOpen && history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2347
|
+
"div",
|
|
2348
|
+
{
|
|
2349
|
+
"data-boff-explore": "history-panel",
|
|
2350
|
+
className: "border-b border-border bg-muted/30 px-4 py-3",
|
|
2351
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl", children: [
|
|
2352
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
|
|
2353
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Your recent conversations" }),
|
|
2354
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2355
|
+
Button,
|
|
2356
|
+
{
|
|
2357
|
+
"data-boff-explore": "history-clear",
|
|
2358
|
+
type: "button",
|
|
2359
|
+
size: "sm",
|
|
2360
|
+
variant: "ghost",
|
|
2361
|
+
className: "h-7 shrink-0 text-xs text-muted-foreground hover:text-destructive",
|
|
2362
|
+
onClick: () => {
|
|
2363
|
+
clearHistory();
|
|
2364
|
+
setHistoryOpen(false);
|
|
2365
|
+
},
|
|
2366
|
+
children: [
|
|
2367
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }),
|
|
2368
|
+
"Clear history"
|
|
2369
|
+
]
|
|
2370
|
+
}
|
|
2371
|
+
)
|
|
2372
|
+
] }),
|
|
2373
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1", children: history.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [
|
|
2374
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2375
|
+
"button",
|
|
2376
|
+
{
|
|
2377
|
+
"data-boff-explore": "history-item",
|
|
2378
|
+
type: "button",
|
|
2379
|
+
className: "flex-1 truncate rounded-md px-2 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
2380
|
+
onClick: () => {
|
|
2381
|
+
openConversation(entry.token);
|
|
2382
|
+
setHistoryOpen(false);
|
|
2383
|
+
},
|
|
2384
|
+
children: [
|
|
2385
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: entry.title }),
|
|
2386
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
|
|
2387
|
+
entry.messageCount,
|
|
2388
|
+
" message",
|
|
2389
|
+
entry.messageCount === 1 ? "" : "s"
|
|
2390
|
+
] })
|
|
2391
|
+
]
|
|
2392
|
+
}
|
|
2393
|
+
),
|
|
2394
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2395
|
+
Button,
|
|
2396
|
+
{
|
|
2397
|
+
type: "button",
|
|
2398
|
+
size: "icon",
|
|
2399
|
+
variant: "ghost",
|
|
2400
|
+
className: "h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive",
|
|
2401
|
+
"aria-label": `Delete conversation: ${entry.title}`,
|
|
2402
|
+
onClick: () => {
|
|
2403
|
+
deleteConversation(entry.token);
|
|
2404
|
+
},
|
|
2405
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
|
|
2406
|
+
}
|
|
2407
|
+
)
|
|
2408
|
+
] }, entry.token)) })
|
|
2409
|
+
] })
|
|
2410
|
+
}
|
|
2411
|
+
) : null,
|
|
2075
2412
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2076
2413
|
"div",
|
|
2077
2414
|
{
|
|
@@ -2146,6 +2483,23 @@ function ExplorePage({
|
|
|
2146
2483
|
className: "max-h-[200px] min-h-[44px] flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
|
|
2147
2484
|
}
|
|
2148
2485
|
),
|
|
2486
|
+
speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2487
|
+
Button,
|
|
2488
|
+
{
|
|
2489
|
+
"data-boff-explore": "mic",
|
|
2490
|
+
"data-listening": speech.listening ? "true" : "false",
|
|
2491
|
+
type: "button",
|
|
2492
|
+
size: "icon",
|
|
2493
|
+
variant: speech.listening ? "default" : "ghost",
|
|
2494
|
+
disabled: composerDisabled,
|
|
2495
|
+
"aria-label": speech.listening ? "Stop dictating" : "Dictate your question",
|
|
2496
|
+
"aria-pressed": speech.listening,
|
|
2497
|
+
title: speech.listening ? "Stop dictating" : "Dictate your question",
|
|
2498
|
+
onClick: speech.toggle,
|
|
2499
|
+
className: cn(speech.listening && "animate-pulse"),
|
|
2500
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-4 w-4" })
|
|
2501
|
+
}
|
|
2502
|
+
) : null,
|
|
2149
2503
|
busy ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2150
2504
|
Button,
|
|
2151
2505
|
{
|
|
@@ -2171,6 +2525,29 @@ function ExplorePage({
|
|
|
2171
2525
|
]
|
|
2172
2526
|
}
|
|
2173
2527
|
),
|
|
2528
|
+
speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2529
|
+
"p",
|
|
2530
|
+
{
|
|
2531
|
+
"data-boff-explore": "dictation",
|
|
2532
|
+
className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
|
|
2533
|
+
"aria-live": "polite",
|
|
2534
|
+
children: [
|
|
2535
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
|
|
2536
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
|
|
2537
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
|
|
2538
|
+
] }),
|
|
2539
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
|
|
2540
|
+
]
|
|
2541
|
+
}
|
|
2542
|
+
) : null,
|
|
2543
|
+
speech.error !== null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2544
|
+
"p",
|
|
2545
|
+
{
|
|
2546
|
+
"data-boff-explore": "dictation-error",
|
|
2547
|
+
className: "mt-2 text-xs text-destructive",
|
|
2548
|
+
children: speech.error
|
|
2549
|
+
}
|
|
2550
|
+
) : null,
|
|
2174
2551
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
2175
2552
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
|
|
2176
2553
|
remainingToday !== null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-boff-explore": "remaining", children: [
|
|
@@ -2194,39 +2571,48 @@ function ExplorePage({
|
|
|
2194
2571
|
}
|
|
2195
2572
|
)
|
|
2196
2573
|
] }),
|
|
2197
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2198
|
-
"
|
|
2199
|
-
|
|
2200
|
-
" ",
|
|
2201
|
-
"
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
"
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2574
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2575
|
+
"p",
|
|
2576
|
+
{
|
|
2577
|
+
"data-boff-explore": "disclaimer",
|
|
2578
|
+
className: "mt-2 text-xs leading-relaxed text-muted-foreground",
|
|
2579
|
+
children: [
|
|
2580
|
+
"Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
|
|
2581
|
+
" ",
|
|
2582
|
+
"Conversations are saved in this browser so you can come back to them, and are also stored on our servers to help us improve these answers. Please don't share personal or confidential information.",
|
|
2583
|
+
captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2584
|
+
" ",
|
|
2585
|
+
"This site is protected by reCAPTCHA; the Google",
|
|
2586
|
+
" ",
|
|
2587
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2588
|
+
"a",
|
|
2589
|
+
{
|
|
2590
|
+
href: "https://policies.google.com/privacy",
|
|
2591
|
+
target: "_blank",
|
|
2592
|
+
rel: "noopener noreferrer",
|
|
2593
|
+
className: "underline underline-offset-2 hover:text-foreground",
|
|
2594
|
+
children: "Privacy Policy"
|
|
2595
|
+
}
|
|
2596
|
+
),
|
|
2597
|
+
" ",
|
|
2598
|
+
"and",
|
|
2599
|
+
" ",
|
|
2600
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2601
|
+
"a",
|
|
2602
|
+
{
|
|
2603
|
+
href: "https://policies.google.com/terms",
|
|
2604
|
+
target: "_blank",
|
|
2605
|
+
rel: "noopener noreferrer",
|
|
2606
|
+
className: "underline underline-offset-2 hover:text-foreground",
|
|
2607
|
+
children: "Terms of Service"
|
|
2608
|
+
}
|
|
2609
|
+
),
|
|
2610
|
+
" ",
|
|
2611
|
+
"apply."
|
|
2612
|
+
] }) : null
|
|
2613
|
+
]
|
|
2614
|
+
}
|
|
2615
|
+
)
|
|
2230
2616
|
]
|
|
2231
2617
|
}
|
|
2232
2618
|
) })
|