@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
package/dist/index.js
CHANGED
|
@@ -5276,6 +5276,8 @@ async function getRecaptchaV3Token(siteKey, action = EXPLORE_RECAPTCHA_ACTION) {
|
|
|
5276
5276
|
|
|
5277
5277
|
// src/hooks/use-explore-chat.ts
|
|
5278
5278
|
var DEFAULT_STORAGE_KEY = "boff.explore.v1";
|
|
5279
|
+
var HISTORY_SUFFIX = ".history";
|
|
5280
|
+
var MAX_ARCHIVED_CONVERSATIONS = 15;
|
|
5279
5281
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
5280
5282
|
var DEFAULT_POLL_TIMEOUT_MS = 9e4;
|
|
5281
5283
|
var POLL_REQUEST_TIMEOUT_MS = 1e4;
|
|
@@ -5414,10 +5416,58 @@ function classifyExploreError(code, serverMessage) {
|
|
|
5414
5416
|
retryable: true
|
|
5415
5417
|
};
|
|
5416
5418
|
}
|
|
5419
|
+
function historyKey(key) {
|
|
5420
|
+
return `${key}${HISTORY_SUFFIX}`;
|
|
5421
|
+
}
|
|
5422
|
+
function readArchive(key) {
|
|
5423
|
+
if (typeof window === "undefined") return [];
|
|
5424
|
+
try {
|
|
5425
|
+
const raw = window.localStorage.getItem(historyKey(key));
|
|
5426
|
+
if (!raw) return [];
|
|
5427
|
+
const parsed = JSON.parse(raw);
|
|
5428
|
+
if (!Array.isArray(parsed)) return [];
|
|
5429
|
+
return parsed.filter(
|
|
5430
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry.token === "string" && Array.isArray(entry.messages)
|
|
5431
|
+
);
|
|
5432
|
+
} catch {
|
|
5433
|
+
return [];
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
function writeArchive(key, entries) {
|
|
5437
|
+
if (typeof window === "undefined") return;
|
|
5438
|
+
try {
|
|
5439
|
+
if (entries.length === 0) {
|
|
5440
|
+
window.localStorage.removeItem(historyKey(key));
|
|
5441
|
+
return;
|
|
5442
|
+
}
|
|
5443
|
+
window.localStorage.setItem(historyKey(key), JSON.stringify(entries));
|
|
5444
|
+
} catch {
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
function upsertArchive(key, thread) {
|
|
5448
|
+
if (!thread.token) return readArchive(key);
|
|
5449
|
+
const real = thread.messages.filter(
|
|
5450
|
+
(m) => m.content.trim() !== "" || m.role === "USER"
|
|
5451
|
+
);
|
|
5452
|
+
if (real.length === 0) return readArchive(key);
|
|
5453
|
+
const firstUser = real.find((m) => m.role === "USER");
|
|
5454
|
+
const entry = {
|
|
5455
|
+
token: thread.token,
|
|
5456
|
+
title: (firstUser?.content ?? "Conversation").trim().slice(0, 80),
|
|
5457
|
+
updatedAt: real[real.length - 1]?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
5458
|
+
messageCount: real.length,
|
|
5459
|
+
focusProduct: thread.focusProduct,
|
|
5460
|
+
messages: real.slice(-20)
|
|
5461
|
+
};
|
|
5462
|
+
const rest = readArchive(key).filter((e) => e.token !== entry.token);
|
|
5463
|
+
const next = [entry, ...rest].slice(0, MAX_ARCHIVED_CONVERSATIONS);
|
|
5464
|
+
writeArchive(key, next);
|
|
5465
|
+
return next;
|
|
5466
|
+
}
|
|
5417
5467
|
function readPersisted(key) {
|
|
5418
5468
|
if (typeof window === "undefined") return null;
|
|
5419
5469
|
try {
|
|
5420
|
-
const raw = window.
|
|
5470
|
+
const raw = window.localStorage.getItem(key);
|
|
5421
5471
|
if (!raw) return null;
|
|
5422
5472
|
const parsed = JSON.parse(raw);
|
|
5423
5473
|
if (!parsed || typeof parsed !== "object") return null;
|
|
@@ -5462,6 +5512,8 @@ function useExploreChat(options = {}) {
|
|
|
5462
5512
|
const [remainingToday, setRemainingToday] = React.useState(null);
|
|
5463
5513
|
const [focusProduct, setFocusProductState] = React.useState(null);
|
|
5464
5514
|
const [turnCount, setTurnCount] = React.useState(0);
|
|
5515
|
+
const messagesRef = React.useRef([]);
|
|
5516
|
+
const [archive, setArchive] = React.useState([]);
|
|
5465
5517
|
const [hydrated, setHydrated] = React.useState(false);
|
|
5466
5518
|
const mountedRef = React.useRef(true);
|
|
5467
5519
|
const pollGenerationRef = React.useRef(0);
|
|
@@ -5499,6 +5551,7 @@ function useExploreChat(options = {}) {
|
|
|
5499
5551
|
setHydrated(true);
|
|
5500
5552
|
return;
|
|
5501
5553
|
}
|
|
5554
|
+
setArchive(readArchive(storageKey));
|
|
5502
5555
|
const stored = readPersisted(storageKey);
|
|
5503
5556
|
if (stored) {
|
|
5504
5557
|
conversationTokenRef.current = stored.conversationToken;
|
|
@@ -5527,13 +5580,28 @@ function useExploreChat(options = {}) {
|
|
|
5527
5580
|
focusProduct
|
|
5528
5581
|
};
|
|
5529
5582
|
if (!payload.conversationToken && payload.messages.length === 0 && !payload.focusProduct) {
|
|
5530
|
-
window.
|
|
5583
|
+
window.localStorage.removeItem(storageKey);
|
|
5531
5584
|
return;
|
|
5532
5585
|
}
|
|
5533
|
-
window.
|
|
5586
|
+
window.localStorage.setItem(storageKey, JSON.stringify(payload));
|
|
5534
5587
|
} catch {
|
|
5535
5588
|
}
|
|
5536
5589
|
}, [persist, hydrated, storageKey, messages, focusProduct]);
|
|
5590
|
+
React.useEffect(() => {
|
|
5591
|
+
messagesRef.current = messages;
|
|
5592
|
+
if (!persist || !hydrated) return;
|
|
5593
|
+
const settled = messages.some(
|
|
5594
|
+
(m) => m.role === "ASSISTANT" && (m.status === "COMPLETED" || m.status === "FAILED")
|
|
5595
|
+
);
|
|
5596
|
+
if (!settled) return;
|
|
5597
|
+
setArchive(
|
|
5598
|
+
upsertArchive(storageKey, {
|
|
5599
|
+
token: conversationTokenRef.current,
|
|
5600
|
+
messages,
|
|
5601
|
+
focusProduct: focusProductRef.current
|
|
5602
|
+
})
|
|
5603
|
+
);
|
|
5604
|
+
}, [messages, persist, hydrated, storageKey]);
|
|
5537
5605
|
React.useEffect(() => {
|
|
5538
5606
|
let cancelled = false;
|
|
5539
5607
|
const load = async () => {
|
|
@@ -5819,6 +5887,13 @@ function useExploreChat(options = {}) {
|
|
|
5819
5887
|
void send(text);
|
|
5820
5888
|
}, [cancelPolling, send]);
|
|
5821
5889
|
const reset = React.useCallback(() => {
|
|
5890
|
+
setArchive(
|
|
5891
|
+
upsertArchive(storageKey, {
|
|
5892
|
+
token: conversationTokenRef.current,
|
|
5893
|
+
messages: messagesRef.current,
|
|
5894
|
+
focusProduct: focusProductRef.current
|
|
5895
|
+
})
|
|
5896
|
+
);
|
|
5822
5897
|
cancelPolling();
|
|
5823
5898
|
inFlightRef.current = false;
|
|
5824
5899
|
conversationTokenRef.current = null;
|
|
@@ -5831,11 +5906,48 @@ function useExploreChat(options = {}) {
|
|
|
5831
5906
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
5832
5907
|
if (typeof window !== "undefined") {
|
|
5833
5908
|
try {
|
|
5834
|
-
window.
|
|
5909
|
+
window.localStorage.removeItem(storageKey);
|
|
5835
5910
|
} catch {
|
|
5836
5911
|
}
|
|
5837
5912
|
}
|
|
5838
5913
|
}, [cancelPolling, storageKey]);
|
|
5914
|
+
const openConversation = React.useCallback(
|
|
5915
|
+
(token) => {
|
|
5916
|
+
const entry = readArchive(storageKey).find((e) => e.token === token);
|
|
5917
|
+
if (!entry) return;
|
|
5918
|
+
upsertArchive(storageKey, {
|
|
5919
|
+
token: conversationTokenRef.current,
|
|
5920
|
+
messages: messagesRef.current,
|
|
5921
|
+
focusProduct: focusProductRef.current
|
|
5922
|
+
});
|
|
5923
|
+
cancelPolling();
|
|
5924
|
+
inFlightRef.current = false;
|
|
5925
|
+
conversationTokenRef.current = entry.token;
|
|
5926
|
+
turnCountRef.current = entry.messages.filter(
|
|
5927
|
+
(m) => m.role === "USER"
|
|
5928
|
+
).length;
|
|
5929
|
+
setTurnCount(turnCountRef.current);
|
|
5930
|
+
setMessages(entry.messages);
|
|
5931
|
+
focusProductRef.current = entry.focusProduct;
|
|
5932
|
+
setFocusProductState(entry.focusProduct);
|
|
5933
|
+
setError(null);
|
|
5934
|
+
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
5935
|
+
setArchive(readArchive(storageKey));
|
|
5936
|
+
},
|
|
5937
|
+
[cancelPolling, storageKey]
|
|
5938
|
+
);
|
|
5939
|
+
const deleteConversation = React.useCallback(
|
|
5940
|
+
(token) => {
|
|
5941
|
+
const next = readArchive(storageKey).filter((e) => e.token !== token);
|
|
5942
|
+
writeArchive(storageKey, next);
|
|
5943
|
+
setArchive(next);
|
|
5944
|
+
},
|
|
5945
|
+
[storageKey]
|
|
5946
|
+
);
|
|
5947
|
+
const clearHistory = React.useCallback(() => {
|
|
5948
|
+
writeArchive(storageKey, []);
|
|
5949
|
+
setArchive([]);
|
|
5950
|
+
}, [storageKey]);
|
|
5839
5951
|
const setFocusProduct = React.useCallback((slug) => {
|
|
5840
5952
|
focusProductRef.current = slug;
|
|
5841
5953
|
setFocusProductState(slug);
|
|
@@ -5864,7 +5976,11 @@ function useExploreChat(options = {}) {
|
|
|
5864
5976
|
stop,
|
|
5865
5977
|
retry,
|
|
5866
5978
|
reset,
|
|
5867
|
-
canSend
|
|
5979
|
+
canSend,
|
|
5980
|
+
history: archive,
|
|
5981
|
+
openConversation,
|
|
5982
|
+
deleteConversation,
|
|
5983
|
+
clearHistory
|
|
5868
5984
|
};
|
|
5869
5985
|
}
|
|
5870
5986
|
var optimisticCounter = 0;
|
|
@@ -5872,6 +5988,119 @@ function makeOptimisticId() {
|
|
|
5872
5988
|
optimisticCounter += 1;
|
|
5873
5989
|
return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
|
|
5874
5990
|
}
|
|
5991
|
+
function getRecognitionCtor() {
|
|
5992
|
+
if (typeof window === "undefined") return null;
|
|
5993
|
+
const w = window;
|
|
5994
|
+
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
5995
|
+
}
|
|
5996
|
+
function describeError(code) {
|
|
5997
|
+
switch (code) {
|
|
5998
|
+
case "not-allowed":
|
|
5999
|
+
case "service-not-allowed":
|
|
6000
|
+
return "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
6001
|
+
case "no-speech":
|
|
6002
|
+
return "I didn't catch anything \u2014 try again a little closer to the mic.";
|
|
6003
|
+
case "audio-capture":
|
|
6004
|
+
return "No microphone was found.";
|
|
6005
|
+
case "network":
|
|
6006
|
+
return "Speech recognition needs a network connection.";
|
|
6007
|
+
case "aborted":
|
|
6008
|
+
return "";
|
|
6009
|
+
default:
|
|
6010
|
+
return "Dictation stopped unexpectedly. You can type instead.";
|
|
6011
|
+
}
|
|
6012
|
+
}
|
|
6013
|
+
function useSpeechInput({
|
|
6014
|
+
onFinalTranscript,
|
|
6015
|
+
lang,
|
|
6016
|
+
onError
|
|
6017
|
+
}) {
|
|
6018
|
+
const [supported] = React.useState(() => getRecognitionCtor() !== null);
|
|
6019
|
+
const [listening, setListening] = React.useState(false);
|
|
6020
|
+
const [interim, setInterim] = React.useState("");
|
|
6021
|
+
const [error, setError] = React.useState(null);
|
|
6022
|
+
const recognitionRef = React.useRef(null);
|
|
6023
|
+
const finalRef = React.useRef(onFinalTranscript);
|
|
6024
|
+
const errorRef = React.useRef(onError);
|
|
6025
|
+
finalRef.current = onFinalTranscript;
|
|
6026
|
+
errorRef.current = onError;
|
|
6027
|
+
const stop = React.useCallback(() => {
|
|
6028
|
+
const recognition = recognitionRef.current;
|
|
6029
|
+
if (!recognition) return;
|
|
6030
|
+
try {
|
|
6031
|
+
recognition.stop();
|
|
6032
|
+
} catch {
|
|
6033
|
+
}
|
|
6034
|
+
setListening(false);
|
|
6035
|
+
setInterim("");
|
|
6036
|
+
}, []);
|
|
6037
|
+
const start = React.useCallback(() => {
|
|
6038
|
+
const Ctor = getRecognitionCtor();
|
|
6039
|
+
if (!Ctor) return;
|
|
6040
|
+
if (recognitionRef.current) stop();
|
|
6041
|
+
const recognition = new Ctor();
|
|
6042
|
+
recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
6043
|
+
recognition.continuous = true;
|
|
6044
|
+
recognition.interimResults = true;
|
|
6045
|
+
recognition.maxAlternatives = 1;
|
|
6046
|
+
recognition.onstart = () => {
|
|
6047
|
+
setError(null);
|
|
6048
|
+
setListening(true);
|
|
6049
|
+
};
|
|
6050
|
+
recognition.onresult = (event) => {
|
|
6051
|
+
let settled = "";
|
|
6052
|
+
let pending = "";
|
|
6053
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
6054
|
+
const result = event.results[i];
|
|
6055
|
+
if (!result) continue;
|
|
6056
|
+
const text = result[0]?.transcript ?? "";
|
|
6057
|
+
if (result.isFinal) settled += text;
|
|
6058
|
+
else pending += text;
|
|
6059
|
+
}
|
|
6060
|
+
setInterim(pending);
|
|
6061
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
6062
|
+
};
|
|
6063
|
+
recognition.onerror = (event) => {
|
|
6064
|
+
const message = describeError(event.error);
|
|
6065
|
+
setListening(false);
|
|
6066
|
+
setInterim("");
|
|
6067
|
+
if (message !== "") {
|
|
6068
|
+
setError(message);
|
|
6069
|
+
errorRef.current?.(message);
|
|
6070
|
+
}
|
|
6071
|
+
};
|
|
6072
|
+
recognition.onend = () => {
|
|
6073
|
+
setListening(false);
|
|
6074
|
+
setInterim("");
|
|
6075
|
+
};
|
|
6076
|
+
recognitionRef.current = recognition;
|
|
6077
|
+
try {
|
|
6078
|
+
recognition.start();
|
|
6079
|
+
} catch {
|
|
6080
|
+
setListening(false);
|
|
6081
|
+
}
|
|
6082
|
+
}, [lang, stop]);
|
|
6083
|
+
const toggle = React.useCallback(() => {
|
|
6084
|
+
if (listening) stop();
|
|
6085
|
+
else start();
|
|
6086
|
+
}, [listening, start, stop]);
|
|
6087
|
+
React.useEffect(
|
|
6088
|
+
() => () => {
|
|
6089
|
+
const recognition = recognitionRef.current;
|
|
6090
|
+
if (!recognition) return;
|
|
6091
|
+
recognition.onresult = null;
|
|
6092
|
+
recognition.onerror = null;
|
|
6093
|
+
recognition.onend = null;
|
|
6094
|
+
recognition.onstart = null;
|
|
6095
|
+
try {
|
|
6096
|
+
recognition.abort();
|
|
6097
|
+
} catch {
|
|
6098
|
+
}
|
|
6099
|
+
},
|
|
6100
|
+
[]
|
|
6101
|
+
);
|
|
6102
|
+
return { supported, listening, interim, error, start, stop, toggle };
|
|
6103
|
+
}
|
|
5875
6104
|
var EXPLORE_CSS = `
|
|
5876
6105
|
@keyframes boff-explore-pulse {
|
|
5877
6106
|
0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
|
|
@@ -6299,10 +6528,29 @@ function ExplorePage({
|
|
|
6299
6528
|
stop,
|
|
6300
6529
|
retry,
|
|
6301
6530
|
reset,
|
|
6302
|
-
canSend
|
|
6531
|
+
canSend,
|
|
6532
|
+
history,
|
|
6533
|
+
openConversation,
|
|
6534
|
+
deleteConversation,
|
|
6535
|
+
clearHistory
|
|
6303
6536
|
} = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
|
|
6304
6537
|
const [draft, setDraft] = React.useState("");
|
|
6305
6538
|
const textareaRef = React.useRef(null);
|
|
6539
|
+
const [historyOpen, setHistoryOpen] = React.useState(false);
|
|
6540
|
+
const speechStopRef = React.useRef(() => void 0);
|
|
6541
|
+
const stopDictation = React.useCallback(() => {
|
|
6542
|
+
speechStopRef.current();
|
|
6543
|
+
}, []);
|
|
6544
|
+
const speech = useSpeechInput({
|
|
6545
|
+
onFinalTranscript: (text) => {
|
|
6546
|
+
setDraft((current) => {
|
|
6547
|
+
const joined = current.trim() === "" ? text.trimStart() : `${current.trimEnd()} ${text.trim()}`;
|
|
6548
|
+
return joined;
|
|
6549
|
+
});
|
|
6550
|
+
textareaRef.current?.focus();
|
|
6551
|
+
}
|
|
6552
|
+
});
|
|
6553
|
+
speechStopRef.current = speech.stop;
|
|
6306
6554
|
const scrollRef = React.useRef(null);
|
|
6307
6555
|
const stickToBottomRef = React.useRef(true);
|
|
6308
6556
|
const composingRef = React.useRef(false);
|
|
@@ -6338,11 +6586,12 @@ function ExplorePage({
|
|
|
6338
6586
|
const submitDraft = React.useCallback(() => {
|
|
6339
6587
|
const text = draft.trim();
|
|
6340
6588
|
if (!text || overLimit || busy || !canSend) return;
|
|
6589
|
+
stopDictation();
|
|
6341
6590
|
setDraft("");
|
|
6342
6591
|
stickToBottomRef.current = true;
|
|
6343
6592
|
void send(text);
|
|
6344
6593
|
textareaRef.current?.focus();
|
|
6345
|
-
}, [busy, canSend, draft, overLimit, send]);
|
|
6594
|
+
}, [busy, canSend, draft, overLimit, send, stopDictation]);
|
|
6346
6595
|
const sendPrompt = React.useCallback(
|
|
6347
6596
|
(prompt) => {
|
|
6348
6597
|
if (!canSend || busy) return;
|
|
@@ -6415,8 +6664,96 @@ function ExplorePage({
|
|
|
6415
6664
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "New chat" })
|
|
6416
6665
|
]
|
|
6417
6666
|
}
|
|
6418
|
-
)
|
|
6667
|
+
),
|
|
6668
|
+
history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6669
|
+
Button,
|
|
6670
|
+
{
|
|
6671
|
+
"data-boff-explore": "history-toggle",
|
|
6672
|
+
type: "button",
|
|
6673
|
+
size: "sm",
|
|
6674
|
+
variant: "ghost",
|
|
6675
|
+
className: "shrink-0 text-muted-foreground",
|
|
6676
|
+
"aria-expanded": historyOpen,
|
|
6677
|
+
onClick: () => {
|
|
6678
|
+
setHistoryOpen((open) => !open);
|
|
6679
|
+
},
|
|
6680
|
+
children: [
|
|
6681
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.History, { className: "h-3.5 w-3.5" }),
|
|
6682
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden sm:inline", children: [
|
|
6683
|
+
"History (",
|
|
6684
|
+
history.length,
|
|
6685
|
+
")"
|
|
6686
|
+
] })
|
|
6687
|
+
]
|
|
6688
|
+
}
|
|
6689
|
+
) : null
|
|
6419
6690
|
] }) : null,
|
|
6691
|
+
historyOpen && history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6692
|
+
"div",
|
|
6693
|
+
{
|
|
6694
|
+
"data-boff-explore": "history-panel",
|
|
6695
|
+
className: "border-b border-border bg-muted/30 px-4 py-3",
|
|
6696
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl", children: [
|
|
6697
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
|
|
6698
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Your recent conversations" }),
|
|
6699
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6700
|
+
Button,
|
|
6701
|
+
{
|
|
6702
|
+
"data-boff-explore": "history-clear",
|
|
6703
|
+
type: "button",
|
|
6704
|
+
size: "sm",
|
|
6705
|
+
variant: "ghost",
|
|
6706
|
+
className: "h-7 shrink-0 text-xs text-muted-foreground hover:text-destructive",
|
|
6707
|
+
onClick: () => {
|
|
6708
|
+
clearHistory();
|
|
6709
|
+
setHistoryOpen(false);
|
|
6710
|
+
},
|
|
6711
|
+
children: [
|
|
6712
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }),
|
|
6713
|
+
"Clear history"
|
|
6714
|
+
]
|
|
6715
|
+
}
|
|
6716
|
+
)
|
|
6717
|
+
] }),
|
|
6718
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1", children: history.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [
|
|
6719
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6720
|
+
"button",
|
|
6721
|
+
{
|
|
6722
|
+
"data-boff-explore": "history-item",
|
|
6723
|
+
type: "button",
|
|
6724
|
+
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",
|
|
6725
|
+
onClick: () => {
|
|
6726
|
+
openConversation(entry.token);
|
|
6727
|
+
setHistoryOpen(false);
|
|
6728
|
+
},
|
|
6729
|
+
children: [
|
|
6730
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: entry.title }),
|
|
6731
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
|
|
6732
|
+
entry.messageCount,
|
|
6733
|
+
" message",
|
|
6734
|
+
entry.messageCount === 1 ? "" : "s"
|
|
6735
|
+
] })
|
|
6736
|
+
]
|
|
6737
|
+
}
|
|
6738
|
+
),
|
|
6739
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6740
|
+
Button,
|
|
6741
|
+
{
|
|
6742
|
+
type: "button",
|
|
6743
|
+
size: "icon",
|
|
6744
|
+
variant: "ghost",
|
|
6745
|
+
className: "h-7 w-7 shrink-0 text-muted-foreground hover:text-destructive",
|
|
6746
|
+
"aria-label": `Delete conversation: ${entry.title}`,
|
|
6747
|
+
onClick: () => {
|
|
6748
|
+
deleteConversation(entry.token);
|
|
6749
|
+
},
|
|
6750
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
|
|
6751
|
+
}
|
|
6752
|
+
)
|
|
6753
|
+
] }, entry.token)) })
|
|
6754
|
+
] })
|
|
6755
|
+
}
|
|
6756
|
+
) : null,
|
|
6420
6757
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6421
6758
|
"div",
|
|
6422
6759
|
{
|
|
@@ -6491,6 +6828,23 @@ function ExplorePage({
|
|
|
6491
6828
|
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"
|
|
6492
6829
|
}
|
|
6493
6830
|
),
|
|
6831
|
+
speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6832
|
+
Button,
|
|
6833
|
+
{
|
|
6834
|
+
"data-boff-explore": "mic",
|
|
6835
|
+
"data-listening": speech.listening ? "true" : "false",
|
|
6836
|
+
type: "button",
|
|
6837
|
+
size: "icon",
|
|
6838
|
+
variant: speech.listening ? "default" : "ghost",
|
|
6839
|
+
disabled: composerDisabled,
|
|
6840
|
+
"aria-label": speech.listening ? "Stop dictating" : "Dictate your question",
|
|
6841
|
+
"aria-pressed": speech.listening,
|
|
6842
|
+
title: speech.listening ? "Stop dictating" : "Dictate your question",
|
|
6843
|
+
onClick: speech.toggle,
|
|
6844
|
+
className: cn(speech.listening && "animate-pulse"),
|
|
6845
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-4 w-4" })
|
|
6846
|
+
}
|
|
6847
|
+
) : null,
|
|
6494
6848
|
busy ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6495
6849
|
Button,
|
|
6496
6850
|
{
|
|
@@ -6516,6 +6870,29 @@ function ExplorePage({
|
|
|
6516
6870
|
]
|
|
6517
6871
|
}
|
|
6518
6872
|
),
|
|
6873
|
+
speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6874
|
+
"p",
|
|
6875
|
+
{
|
|
6876
|
+
"data-boff-explore": "dictation",
|
|
6877
|
+
className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
|
|
6878
|
+
"aria-live": "polite",
|
|
6879
|
+
children: [
|
|
6880
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
|
|
6881
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
|
|
6882
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
|
|
6883
|
+
] }),
|
|
6884
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
|
|
6885
|
+
]
|
|
6886
|
+
}
|
|
6887
|
+
) : null,
|
|
6888
|
+
speech.error !== null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6889
|
+
"p",
|
|
6890
|
+
{
|
|
6891
|
+
"data-boff-explore": "dictation-error",
|
|
6892
|
+
className: "mt-2 text-xs text-destructive",
|
|
6893
|
+
children: speech.error
|
|
6894
|
+
}
|
|
6895
|
+
) : null,
|
|
6519
6896
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
6520
6897
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
|
|
6521
6898
|
remainingToday !== null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-boff-explore": "remaining", children: [
|
|
@@ -6539,39 +6916,48 @@ function ExplorePage({
|
|
|
6539
6916
|
}
|
|
6540
6917
|
)
|
|
6541
6918
|
] }),
|
|
6542
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6543
|
-
"
|
|
6544
|
-
|
|
6545
|
-
" ",
|
|
6546
|
-
"
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
"
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6919
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6920
|
+
"p",
|
|
6921
|
+
{
|
|
6922
|
+
"data-boff-explore": "disclaimer",
|
|
6923
|
+
className: "mt-2 text-xs leading-relaxed text-muted-foreground",
|
|
6924
|
+
children: [
|
|
6925
|
+
"Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
|
|
6926
|
+
" ",
|
|
6927
|
+
"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.",
|
|
6928
|
+
captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
6929
|
+
" ",
|
|
6930
|
+
"This site is protected by reCAPTCHA; the Google",
|
|
6931
|
+
" ",
|
|
6932
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6933
|
+
"a",
|
|
6934
|
+
{
|
|
6935
|
+
href: "https://policies.google.com/privacy",
|
|
6936
|
+
target: "_blank",
|
|
6937
|
+
rel: "noopener noreferrer",
|
|
6938
|
+
className: "underline underline-offset-2 hover:text-foreground",
|
|
6939
|
+
children: "Privacy Policy"
|
|
6940
|
+
}
|
|
6941
|
+
),
|
|
6942
|
+
" ",
|
|
6943
|
+
"and",
|
|
6944
|
+
" ",
|
|
6945
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6946
|
+
"a",
|
|
6947
|
+
{
|
|
6948
|
+
href: "https://policies.google.com/terms",
|
|
6949
|
+
target: "_blank",
|
|
6950
|
+
rel: "noopener noreferrer",
|
|
6951
|
+
className: "underline underline-offset-2 hover:text-foreground",
|
|
6952
|
+
children: "Terms of Service"
|
|
6953
|
+
}
|
|
6954
|
+
),
|
|
6955
|
+
" ",
|
|
6956
|
+
"apply."
|
|
6957
|
+
] }) : null
|
|
6958
|
+
]
|
|
6959
|
+
}
|
|
6960
|
+
)
|
|
6575
6961
|
]
|
|
6576
6962
|
}
|
|
6577
6963
|
) })
|
|
@@ -6621,11 +7007,12 @@ function ExploreCta({
|
|
|
6621
7007
|
onNavigate(href);
|
|
6622
7008
|
};
|
|
6623
7009
|
const classes = {
|
|
6624
|
-
//
|
|
6625
|
-
//
|
|
6626
|
-
//
|
|
6627
|
-
//
|
|
6628
|
-
|
|
7010
|
+
// A quiet icon control, deliberately NOT a second gradient pill: a header should carry one
|
|
7011
|
+
// primary CTA ("Get started"), and a competing coloured button next to it reads as clutter.
|
|
7012
|
+
// This matches the theme toggle's visual weight, so the row scans as [utilities] [CTA].
|
|
7013
|
+
// The prominent, labelled entry point to /explore is the floating bubble, which is visible
|
|
7014
|
+
// on every page without scrolling. `shrink-0` keeps it from squeezing the nav.
|
|
7015
|
+
header: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
6629
7016
|
mobile: "inline-flex w-full items-center gap-2 rounded-lg border border-border bg-card px-3 py-2 text-sm font-medium text-card-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
6630
7017
|
floating: "fixed bottom-5 right-5 z-40 inline-flex items-center gap-2 rounded-full bg-gradient-to-r from-primary to-primary/70 px-4 py-3 text-sm font-medium text-primary-foreground shadow-lg transition-opacity hover:opacity-90"
|
|
6631
7018
|
};
|
|
@@ -6653,7 +7040,7 @@ function ExploreCta({
|
|
|
6653
7040
|
{
|
|
6654
7041
|
className: cn(
|
|
6655
7042
|
"shrink-0",
|
|
6656
|
-
variant === "floating" ? "h-4 w-4" : "h-3.5 w-3.5"
|
|
7043
|
+
variant === "floating" ? "h-4 w-4" : variant === "header" ? "h-5 w-5" : "h-3.5 w-3.5"
|
|
6657
7044
|
),
|
|
6658
7045
|
"aria-hidden": "true"
|
|
6659
7046
|
}
|
|
@@ -6663,7 +7050,8 @@ function ExploreCta({
|
|
|
6663
7050
|
{
|
|
6664
7051
|
className: cn(
|
|
6665
7052
|
variant === "floating" && "hidden sm:inline",
|
|
6666
|
-
|
|
7053
|
+
// Never labelled in the header — see the class comment above.
|
|
7054
|
+
variant === "header" && "hidden"
|
|
6667
7055
|
),
|
|
6668
7056
|
children: label
|
|
6669
7057
|
}
|