@burdenoff/website-sdk 2026.828.5 → 2026.828.7
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 +11 -1
- package/dist/components/explore.d.ts +11 -1
- package/dist/components/explore.js +175 -57
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +175 -58
- package/dist/components/explore.mjs.map +1 -1
- package/dist/hooks/index.js +104 -41
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +104 -41
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.js +174 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +174 -57
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5993,11 +5993,40 @@ function getRecognitionCtor() {
|
|
|
5993
5993
|
const w = window;
|
|
5994
5994
|
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
5995
5995
|
}
|
|
5996
|
+
var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
5997
|
+
async function ensureMicrophoneAccess() {
|
|
5998
|
+
const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
5999
|
+
if (!media?.getUserMedia) return { ok: true, confirmed: false };
|
|
6000
|
+
try {
|
|
6001
|
+
const status = await navigator.permissions?.query({
|
|
6002
|
+
name: "microphone"
|
|
6003
|
+
});
|
|
6004
|
+
if (status?.state === "granted") return { ok: true, confirmed: true };
|
|
6005
|
+
} catch {
|
|
6006
|
+
}
|
|
6007
|
+
try {
|
|
6008
|
+
const stream = await media.getUserMedia({ audio: true });
|
|
6009
|
+
for (const track of stream.getTracks()) track.stop();
|
|
6010
|
+
return { ok: true, confirmed: true };
|
|
6011
|
+
} catch (error) {
|
|
6012
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
6013
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
6014
|
+
return { ok: false, message: MIC_DENIED_MESSAGE };
|
|
6015
|
+
}
|
|
6016
|
+
if (name === "NotFoundError" || name === "DevicesNotFoundError") {
|
|
6017
|
+
return { ok: false, message: "No microphone was found." };
|
|
6018
|
+
}
|
|
6019
|
+
return {
|
|
6020
|
+
ok: false,
|
|
6021
|
+
message: "Dictation could not start. You can type instead."
|
|
6022
|
+
};
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
5996
6025
|
function describeError(code) {
|
|
5997
6026
|
switch (code) {
|
|
5998
6027
|
case "not-allowed":
|
|
5999
6028
|
case "service-not-allowed":
|
|
6000
|
-
return
|
|
6029
|
+
return MIC_DENIED_MESSAGE;
|
|
6001
6030
|
case "no-speech":
|
|
6002
6031
|
return "I didn't catch anything \u2014 try again a little closer to the mic.";
|
|
6003
6032
|
case "audio-capture":
|
|
@@ -6020,11 +6049,23 @@ function useSpeechInput({
|
|
|
6020
6049
|
const [interim, setInterim] = React.useState("");
|
|
6021
6050
|
const [error, setError] = React.useState(null);
|
|
6022
6051
|
const recognitionRef = React.useRef(null);
|
|
6052
|
+
const startTokenRef = React.useRef(0);
|
|
6053
|
+
const micConfirmedRef = React.useRef(false);
|
|
6054
|
+
const beginRecognitionRef = React.useRef(null);
|
|
6055
|
+
const beginRecognition = React.useCallback(
|
|
6056
|
+
(Ctor, token) => {
|
|
6057
|
+
beginRecognitionRef.current?.(Ctor, token);
|
|
6058
|
+
},
|
|
6059
|
+
[]
|
|
6060
|
+
);
|
|
6023
6061
|
const finalRef = React.useRef(onFinalTranscript);
|
|
6024
6062
|
const errorRef = React.useRef(onError);
|
|
6025
6063
|
finalRef.current = onFinalTranscript;
|
|
6026
6064
|
errorRef.current = onError;
|
|
6027
6065
|
const stop = React.useCallback(() => {
|
|
6066
|
+
startTokenRef.current += 1;
|
|
6067
|
+
setListening(false);
|
|
6068
|
+
setInterim("");
|
|
6028
6069
|
const recognition = recognitionRef.current;
|
|
6029
6070
|
if (!recognition) return;
|
|
6030
6071
|
try {
|
|
@@ -6038,48 +6079,70 @@ function useSpeechInput({
|
|
|
6038
6079
|
const Ctor = getRecognitionCtor();
|
|
6039
6080
|
if (!Ctor) return;
|
|
6040
6081
|
if (recognitionRef.current) stop();
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
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;
|
|
6082
|
+
setError(null);
|
|
6083
|
+
setListening(true);
|
|
6084
|
+
startTokenRef.current += 1;
|
|
6085
|
+
const token = startTokenRef.current;
|
|
6086
|
+
void ensureMicrophoneAccess().then((access) => {
|
|
6087
|
+
if (token !== startTokenRef.current) return;
|
|
6088
|
+
micConfirmedRef.current = access.ok && access.confirmed;
|
|
6089
|
+
if (!access.ok) {
|
|
6090
|
+
setListening(false);
|
|
6091
|
+
setError(access.message);
|
|
6092
|
+
errorRef.current?.(access.message);
|
|
6093
|
+
return;
|
|
6059
6094
|
}
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6095
|
+
beginRecognition(Ctor, token);
|
|
6096
|
+
});
|
|
6097
|
+
}, [beginRecognition, stop]);
|
|
6098
|
+
const beginRecognitionImpl = React.useCallback(
|
|
6099
|
+
(Ctor, token) => {
|
|
6100
|
+
const recognition = new Ctor();
|
|
6101
|
+
recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
6102
|
+
recognition.continuous = true;
|
|
6103
|
+
recognition.interimResults = true;
|
|
6104
|
+
recognition.maxAlternatives = 1;
|
|
6105
|
+
recognition.onstart = () => {
|
|
6106
|
+
if (token !== startTokenRef.current) return;
|
|
6107
|
+
setError(null);
|
|
6108
|
+
setListening(true);
|
|
6109
|
+
};
|
|
6110
|
+
recognition.onresult = (event) => {
|
|
6111
|
+
let settled = "";
|
|
6112
|
+
let pending = "";
|
|
6113
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
6114
|
+
const result = event.results[i];
|
|
6115
|
+
if (!result) continue;
|
|
6116
|
+
const text = result[0]?.transcript ?? "";
|
|
6117
|
+
if (result.isFinal) settled += text;
|
|
6118
|
+
else pending += text;
|
|
6119
|
+
}
|
|
6120
|
+
setInterim(pending);
|
|
6121
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
6122
|
+
};
|
|
6123
|
+
recognition.onerror = (event) => {
|
|
6124
|
+
const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? "Dictation isn't available in this browser. You can type instead." : describeError(event.error);
|
|
6125
|
+
setListening(false);
|
|
6126
|
+
setInterim("");
|
|
6127
|
+
if (message !== "") {
|
|
6128
|
+
setError(message);
|
|
6129
|
+
errorRef.current?.(message);
|
|
6130
|
+
}
|
|
6131
|
+
};
|
|
6132
|
+
recognition.onend = () => {
|
|
6133
|
+
setListening(false);
|
|
6134
|
+
setInterim("");
|
|
6135
|
+
};
|
|
6136
|
+
recognitionRef.current = recognition;
|
|
6137
|
+
try {
|
|
6138
|
+
recognition.start();
|
|
6139
|
+
} catch {
|
|
6140
|
+
setListening(false);
|
|
6070
6141
|
}
|
|
6071
|
-
}
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
};
|
|
6076
|
-
recognitionRef.current = recognition;
|
|
6077
|
-
try {
|
|
6078
|
-
recognition.start();
|
|
6079
|
-
} catch {
|
|
6080
|
-
setListening(false);
|
|
6081
|
-
}
|
|
6082
|
-
}, [lang, stop]);
|
|
6142
|
+
},
|
|
6143
|
+
[lang]
|
|
6144
|
+
);
|
|
6145
|
+
beginRecognitionRef.current = beginRecognitionImpl;
|
|
6083
6146
|
const toggle = React.useCallback(() => {
|
|
6084
6147
|
if (listening) stop();
|
|
6085
6148
|
else start();
|
|
@@ -6195,10 +6258,33 @@ function initialOf(value, fallback) {
|
|
|
6195
6258
|
const source = (value || fallback).trim();
|
|
6196
6259
|
return source ? source.slice(0, 1).toUpperCase() : "?";
|
|
6197
6260
|
}
|
|
6261
|
+
function logoUrlOf(url) {
|
|
6262
|
+
try {
|
|
6263
|
+
const { origin, protocol } = new URL(url);
|
|
6264
|
+
if (protocol !== "https:" && protocol !== "http:") return null;
|
|
6265
|
+
return `${origin}/favicon.svg`;
|
|
6266
|
+
} catch {
|
|
6267
|
+
return null;
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6198
6270
|
function ReferenceCard({ reference }) {
|
|
6199
6271
|
const [imageFailed, setImageFailed] = React.useState(false);
|
|
6272
|
+
const [logoFailed, setLogoFailed] = React.useState(false);
|
|
6200
6273
|
const host = hostnameOf(reference.url);
|
|
6274
|
+
const logoUrl = logoUrlOf(reference.url);
|
|
6201
6275
|
const showImage = Boolean(reference.imageUrl) && !imageFailed;
|
|
6276
|
+
const showLogo = Boolean(logoUrl) && !logoFailed;
|
|
6277
|
+
const letterPlate = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/25 via-primary/5 to-secondary", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-primary/70 sm:text-2xl", children: initialOf(reference.product, reference.title || host) }) });
|
|
6278
|
+
const logoImg = showLogo ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6279
|
+
"img",
|
|
6280
|
+
{
|
|
6281
|
+
src: logoUrl ?? "",
|
|
6282
|
+
alt: "",
|
|
6283
|
+
loading: "lazy",
|
|
6284
|
+
onError: () => setLogoFailed(true),
|
|
6285
|
+
className: "h-full w-full object-contain p-1.5 sm:p-0"
|
|
6286
|
+
}
|
|
6287
|
+
) : letterPlate;
|
|
6202
6288
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6203
6289
|
"a",
|
|
6204
6290
|
{
|
|
@@ -6206,9 +6292,10 @@ function ReferenceCard({ reference }) {
|
|
|
6206
6292
|
href: reference.url,
|
|
6207
6293
|
target: "_blank",
|
|
6208
6294
|
rel: "noopener noreferrer",
|
|
6209
|
-
className: "group flex flex-
|
|
6295
|
+
className: "group flex flex-row items-center gap-3 overflow-hidden rounded-xl border border-border bg-card p-2 transition-colors hover:border-primary/40 hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:flex-col sm:items-stretch sm:gap-0 sm:p-0",
|
|
6210
6296
|
children: [
|
|
6211
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative
|
|
6297
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-11 w-11 shrink-0 overflow-hidden rounded-lg bg-muted sm:hidden", children: logoImg }),
|
|
6298
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative hidden aspect-[16/9] w-full overflow-hidden bg-muted sm:block", children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
6212
6299
|
"img",
|
|
6213
6300
|
{
|
|
6214
6301
|
src: reference.imageUrl ?? "",
|
|
@@ -6217,17 +6304,22 @@ function ReferenceCard({ reference }) {
|
|
|
6217
6304
|
onError: () => setImageFailed(true),
|
|
6218
6305
|
className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
6219
6306
|
}
|
|
6220
|
-
) : (
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6307
|
+
) : showLogo ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/15 via-transparent to-secondary/60 p-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6308
|
+
"img",
|
|
6309
|
+
{
|
|
6310
|
+
src: logoUrl ?? "",
|
|
6311
|
+
alt: "",
|
|
6312
|
+
loading: "lazy",
|
|
6313
|
+
onError: () => setLogoFailed(true),
|
|
6314
|
+
className: "max-h-full max-w-full object-contain"
|
|
6315
|
+
}
|
|
6316
|
+
) }) : letterPlate }),
|
|
6317
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 sm:gap-1.5 sm:p-3", children: [
|
|
6226
6318
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
|
|
6227
|
-
reference.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: reference.description }) : null,
|
|
6228
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
6229
|
-
reference.product ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground", children: reference.product }) : null,
|
|
6230
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "
|
|
6319
|
+
reference.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground max-sm:hidden", children: reference.description }) : null,
|
|
6320
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:mt-auto sm:pt-2", children: [
|
|
6321
|
+
reference.product ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground max-sm:hidden", children: reference.product }) : null,
|
|
6322
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground sm:ml-auto", children: [
|
|
6231
6323
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3 shrink-0" }),
|
|
6232
6324
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: host })
|
|
6233
6325
|
] })
|
|
@@ -6313,7 +6405,8 @@ function UserBubble({ message }) {
|
|
|
6313
6405
|
function AssistantBubble({
|
|
6314
6406
|
message,
|
|
6315
6407
|
activity,
|
|
6316
|
-
onNavigate
|
|
6408
|
+
onNavigate,
|
|
6409
|
+
anchorRef
|
|
6317
6410
|
}) {
|
|
6318
6411
|
const streaming = message.status === "PENDING" || message.status === "RUNNING";
|
|
6319
6412
|
const body = message.content || message.partialContent || "";
|
|
@@ -6321,9 +6414,10 @@ function AssistantBubble({
|
|
|
6321
6414
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6322
6415
|
"div",
|
|
6323
6416
|
{
|
|
6417
|
+
ref: anchorRef,
|
|
6324
6418
|
"data-boff-explore": "assistant",
|
|
6325
6419
|
"data-status": message.status,
|
|
6326
|
-
className: "flex gap-3",
|
|
6420
|
+
className: "flex scroll-mt-4 gap-3",
|
|
6327
6421
|
children: [
|
|
6328
6422
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6329
6423
|
"span",
|
|
@@ -6552,6 +6646,8 @@ function ExplorePage({
|
|
|
6552
6646
|
});
|
|
6553
6647
|
speechStopRef.current = speech.stop;
|
|
6554
6648
|
const scrollRef = React.useRef(null);
|
|
6649
|
+
const latestAssistantRef = React.useRef(null);
|
|
6650
|
+
const alignedForRef = React.useRef(null);
|
|
6555
6651
|
const stickToBottomRef = React.useRef(true);
|
|
6556
6652
|
const composingRef = React.useRef(false);
|
|
6557
6653
|
const busy = phase === "sending" || phase === "streaming";
|
|
@@ -6577,12 +6673,32 @@ function ExplorePage({
|
|
|
6577
6673
|
if (!el) return;
|
|
6578
6674
|
stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
6579
6675
|
}, []);
|
|
6676
|
+
const latestAssistantId = React.useMemo(() => {
|
|
6677
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
6678
|
+
const message = messages[i];
|
|
6679
|
+
if (message && message.role === "ASSISTANT") return message.id;
|
|
6680
|
+
}
|
|
6681
|
+
return null;
|
|
6682
|
+
}, [messages]);
|
|
6683
|
+
React.useEffect(() => {
|
|
6684
|
+
const el = scrollRef.current;
|
|
6685
|
+
const anchor = latestAssistantRef.current;
|
|
6686
|
+
if (!el || !anchor || !latestAssistantId) return;
|
|
6687
|
+
if (alignedForRef.current === latestAssistantId) return;
|
|
6688
|
+
if (!stickToBottomRef.current) return;
|
|
6689
|
+
if (anchor.offsetHeight === 0) return;
|
|
6690
|
+
alignedForRef.current = latestAssistantId;
|
|
6691
|
+
const delta = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
|
|
6692
|
+
el.scrollTop = Math.max(0, el.scrollTop + delta - 12);
|
|
6693
|
+
}, [latestAssistantId, messages]);
|
|
6580
6694
|
React.useEffect(() => {
|
|
6581
6695
|
if (!stickToBottomRef.current) return;
|
|
6696
|
+
if (latestAssistantId && alignedForRef.current === latestAssistantId)
|
|
6697
|
+
return;
|
|
6582
6698
|
const el = scrollRef.current;
|
|
6583
6699
|
if (!el) return;
|
|
6584
6700
|
el.scrollTop = el.scrollHeight;
|
|
6585
|
-
}, [
|
|
6701
|
+
}, [activity, latestAssistantId, messages]);
|
|
6586
6702
|
const submitDraft = React.useCallback(() => {
|
|
6587
6703
|
const text = draft.trim();
|
|
6588
6704
|
if (!text || overLimit || busy || !canSend) return;
|
|
@@ -6782,7 +6898,8 @@ function ExplorePage({
|
|
|
6782
6898
|
{
|
|
6783
6899
|
message,
|
|
6784
6900
|
activity,
|
|
6785
|
-
onNavigate
|
|
6901
|
+
onNavigate,
|
|
6902
|
+
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0
|
|
6786
6903
|
},
|
|
6787
6904
|
message.id
|
|
6788
6905
|
)
|