@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
|
@@ -1497,11 +1497,40 @@ function getRecognitionCtor() {
|
|
|
1497
1497
|
const w = window;
|
|
1498
1498
|
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1499
1499
|
}
|
|
1500
|
+
var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
|
|
1501
|
+
async function ensureMicrophoneAccess() {
|
|
1502
|
+
const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
|
|
1503
|
+
if (!media?.getUserMedia) return { ok: true, confirmed: false };
|
|
1504
|
+
try {
|
|
1505
|
+
const status = await navigator.permissions?.query({
|
|
1506
|
+
name: "microphone"
|
|
1507
|
+
});
|
|
1508
|
+
if (status?.state === "granted") return { ok: true, confirmed: true };
|
|
1509
|
+
} catch {
|
|
1510
|
+
}
|
|
1511
|
+
try {
|
|
1512
|
+
const stream = await media.getUserMedia({ audio: true });
|
|
1513
|
+
for (const track of stream.getTracks()) track.stop();
|
|
1514
|
+
return { ok: true, confirmed: true };
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
1517
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
1518
|
+
return { ok: false, message: MIC_DENIED_MESSAGE };
|
|
1519
|
+
}
|
|
1520
|
+
if (name === "NotFoundError" || name === "DevicesNotFoundError") {
|
|
1521
|
+
return { ok: false, message: "No microphone was found." };
|
|
1522
|
+
}
|
|
1523
|
+
return {
|
|
1524
|
+
ok: false,
|
|
1525
|
+
message: "Dictation could not start. You can type instead."
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1500
1529
|
function describeError(code) {
|
|
1501
1530
|
switch (code) {
|
|
1502
1531
|
case "not-allowed":
|
|
1503
1532
|
case "service-not-allowed":
|
|
1504
|
-
return
|
|
1533
|
+
return MIC_DENIED_MESSAGE;
|
|
1505
1534
|
case "no-speech":
|
|
1506
1535
|
return "I didn't catch anything \u2014 try again a little closer to the mic.";
|
|
1507
1536
|
case "audio-capture":
|
|
@@ -1524,11 +1553,23 @@ function useSpeechInput({
|
|
|
1524
1553
|
const [interim, setInterim] = useState("");
|
|
1525
1554
|
const [error, setError] = useState(null);
|
|
1526
1555
|
const recognitionRef = useRef(null);
|
|
1556
|
+
const startTokenRef = useRef(0);
|
|
1557
|
+
const micConfirmedRef = useRef(false);
|
|
1558
|
+
const beginRecognitionRef = useRef(null);
|
|
1559
|
+
const beginRecognition = useCallback(
|
|
1560
|
+
(Ctor, token) => {
|
|
1561
|
+
beginRecognitionRef.current?.(Ctor, token);
|
|
1562
|
+
},
|
|
1563
|
+
[]
|
|
1564
|
+
);
|
|
1527
1565
|
const finalRef = useRef(onFinalTranscript);
|
|
1528
1566
|
const errorRef = useRef(onError);
|
|
1529
1567
|
finalRef.current = onFinalTranscript;
|
|
1530
1568
|
errorRef.current = onError;
|
|
1531
1569
|
const stop = useCallback(() => {
|
|
1570
|
+
startTokenRef.current += 1;
|
|
1571
|
+
setListening(false);
|
|
1572
|
+
setInterim("");
|
|
1532
1573
|
const recognition = recognitionRef.current;
|
|
1533
1574
|
if (!recognition) return;
|
|
1534
1575
|
try {
|
|
@@ -1542,48 +1583,70 @@ function useSpeechInput({
|
|
|
1542
1583
|
const Ctor = getRecognitionCtor();
|
|
1543
1584
|
if (!Ctor) return;
|
|
1544
1585
|
if (recognitionRef.current) stop();
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
1558
|
-
const result = event.results[i];
|
|
1559
|
-
if (!result) continue;
|
|
1560
|
-
const text = result[0]?.transcript ?? "";
|
|
1561
|
-
if (result.isFinal) settled += text;
|
|
1562
|
-
else pending += text;
|
|
1586
|
+
setError(null);
|
|
1587
|
+
setListening(true);
|
|
1588
|
+
startTokenRef.current += 1;
|
|
1589
|
+
const token = startTokenRef.current;
|
|
1590
|
+
void ensureMicrophoneAccess().then((access) => {
|
|
1591
|
+
if (token !== startTokenRef.current) return;
|
|
1592
|
+
micConfirmedRef.current = access.ok && access.confirmed;
|
|
1593
|
+
if (!access.ok) {
|
|
1594
|
+
setListening(false);
|
|
1595
|
+
setError(access.message);
|
|
1596
|
+
errorRef.current?.(access.message);
|
|
1597
|
+
return;
|
|
1563
1598
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1599
|
+
beginRecognition(Ctor, token);
|
|
1600
|
+
});
|
|
1601
|
+
}, [beginRecognition, stop]);
|
|
1602
|
+
const beginRecognitionImpl = useCallback(
|
|
1603
|
+
(Ctor, token) => {
|
|
1604
|
+
const recognition = new Ctor();
|
|
1605
|
+
recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
|
|
1606
|
+
recognition.continuous = true;
|
|
1607
|
+
recognition.interimResults = true;
|
|
1608
|
+
recognition.maxAlternatives = 1;
|
|
1609
|
+
recognition.onstart = () => {
|
|
1610
|
+
if (token !== startTokenRef.current) return;
|
|
1611
|
+
setError(null);
|
|
1612
|
+
setListening(true);
|
|
1613
|
+
};
|
|
1614
|
+
recognition.onresult = (event) => {
|
|
1615
|
+
let settled = "";
|
|
1616
|
+
let pending = "";
|
|
1617
|
+
for (let i = event.resultIndex; i < event.results.length; i += 1) {
|
|
1618
|
+
const result = event.results[i];
|
|
1619
|
+
if (!result) continue;
|
|
1620
|
+
const text = result[0]?.transcript ?? "";
|
|
1621
|
+
if (result.isFinal) settled += text;
|
|
1622
|
+
else pending += text;
|
|
1623
|
+
}
|
|
1624
|
+
setInterim(pending);
|
|
1625
|
+
if (settled.trim() !== "") finalRef.current(settled);
|
|
1626
|
+
};
|
|
1627
|
+
recognition.onerror = (event) => {
|
|
1628
|
+
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);
|
|
1629
|
+
setListening(false);
|
|
1630
|
+
setInterim("");
|
|
1631
|
+
if (message !== "") {
|
|
1632
|
+
setError(message);
|
|
1633
|
+
errorRef.current?.(message);
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
recognition.onend = () => {
|
|
1637
|
+
setListening(false);
|
|
1638
|
+
setInterim("");
|
|
1639
|
+
};
|
|
1640
|
+
recognitionRef.current = recognition;
|
|
1641
|
+
try {
|
|
1642
|
+
recognition.start();
|
|
1643
|
+
} catch {
|
|
1644
|
+
setListening(false);
|
|
1574
1645
|
}
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
};
|
|
1580
|
-
recognitionRef.current = recognition;
|
|
1581
|
-
try {
|
|
1582
|
-
recognition.start();
|
|
1583
|
-
} catch {
|
|
1584
|
-
setListening(false);
|
|
1585
|
-
}
|
|
1586
|
-
}, [lang, stop]);
|
|
1646
|
+
},
|
|
1647
|
+
[lang]
|
|
1648
|
+
);
|
|
1649
|
+
beginRecognitionRef.current = beginRecognitionImpl;
|
|
1587
1650
|
const toggle = useCallback(() => {
|
|
1588
1651
|
if (listening) stop();
|
|
1589
1652
|
else start();
|
|
@@ -1824,10 +1887,33 @@ function initialOf(value, fallback) {
|
|
|
1824
1887
|
const source = (value || fallback).trim();
|
|
1825
1888
|
return source ? source.slice(0, 1).toUpperCase() : "?";
|
|
1826
1889
|
}
|
|
1890
|
+
function logoUrlOf(url) {
|
|
1891
|
+
try {
|
|
1892
|
+
const { origin, protocol } = new URL(url);
|
|
1893
|
+
if (protocol !== "https:" && protocol !== "http:") return null;
|
|
1894
|
+
return `${origin}/favicon.svg`;
|
|
1895
|
+
} catch {
|
|
1896
|
+
return null;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1827
1899
|
function ReferenceCard({ reference }) {
|
|
1828
1900
|
const [imageFailed, setImageFailed] = useState(false);
|
|
1901
|
+
const [logoFailed, setLogoFailed] = useState(false);
|
|
1829
1902
|
const host = hostnameOf(reference.url);
|
|
1903
|
+
const logoUrl = logoUrlOf(reference.url);
|
|
1830
1904
|
const showImage = Boolean(reference.imageUrl) && !imageFailed;
|
|
1905
|
+
const showLogo = Boolean(logoUrl) && !logoFailed;
|
|
1906
|
+
const letterPlate = /* @__PURE__ */ 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__ */ jsx("span", { className: "text-sm font-semibold text-primary/70 sm:text-2xl", children: initialOf(reference.product, reference.title || host) }) });
|
|
1907
|
+
const logoImg = showLogo ? /* @__PURE__ */ jsx(
|
|
1908
|
+
"img",
|
|
1909
|
+
{
|
|
1910
|
+
src: logoUrl ?? "",
|
|
1911
|
+
alt: "",
|
|
1912
|
+
loading: "lazy",
|
|
1913
|
+
onError: () => setLogoFailed(true),
|
|
1914
|
+
className: "h-full w-full object-contain p-1.5 sm:p-0"
|
|
1915
|
+
}
|
|
1916
|
+
) : letterPlate;
|
|
1831
1917
|
return /* @__PURE__ */ jsxs(
|
|
1832
1918
|
"a",
|
|
1833
1919
|
{
|
|
@@ -1835,9 +1921,10 @@ function ReferenceCard({ reference }) {
|
|
|
1835
1921
|
href: reference.url,
|
|
1836
1922
|
target: "_blank",
|
|
1837
1923
|
rel: "noopener noreferrer",
|
|
1838
|
-
className: "group flex flex-
|
|
1924
|
+
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",
|
|
1839
1925
|
children: [
|
|
1840
|
-
/* @__PURE__ */ jsx("div", { className: "relative
|
|
1926
|
+
/* @__PURE__ */ jsx("div", { className: "relative h-11 w-11 shrink-0 overflow-hidden rounded-lg bg-muted sm:hidden", children: logoImg }),
|
|
1927
|
+
/* @__PURE__ */ jsx("div", { className: "relative hidden aspect-[16/9] w-full overflow-hidden bg-muted sm:block", children: showImage ? /* @__PURE__ */ jsx(
|
|
1841
1928
|
"img",
|
|
1842
1929
|
{
|
|
1843
1930
|
src: reference.imageUrl ?? "",
|
|
@@ -1846,17 +1933,22 @@ function ReferenceCard({ reference }) {
|
|
|
1846
1933
|
onError: () => setImageFailed(true),
|
|
1847
1934
|
className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
1848
1935
|
}
|
|
1849
|
-
) : (
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1936
|
+
) : showLogo ? /* @__PURE__ */ 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__ */ jsx(
|
|
1937
|
+
"img",
|
|
1938
|
+
{
|
|
1939
|
+
src: logoUrl ?? "",
|
|
1940
|
+
alt: "",
|
|
1941
|
+
loading: "lazy",
|
|
1942
|
+
onError: () => setLogoFailed(true),
|
|
1943
|
+
className: "max-h-full max-w-full object-contain"
|
|
1944
|
+
}
|
|
1945
|
+
) }) : letterPlate }),
|
|
1946
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 sm:gap-1.5 sm:p-3", children: [
|
|
1855
1947
|
/* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
|
|
1856
|
-
reference.description ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: reference.description }) : null,
|
|
1857
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
1858
|
-
reference.product ? /* @__PURE__ */ jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground", children: reference.product }) : null,
|
|
1859
|
-
/* @__PURE__ */ jsxs("span", { className: "
|
|
1948
|
+
reference.description ? /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground max-sm:hidden", children: reference.description }) : null,
|
|
1949
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:mt-auto sm:pt-2", children: [
|
|
1950
|
+
reference.product ? /* @__PURE__ */ 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,
|
|
1951
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground sm:ml-auto", children: [
|
|
1860
1952
|
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 shrink-0" }),
|
|
1861
1953
|
/* @__PURE__ */ jsx("span", { className: "truncate", children: host })
|
|
1862
1954
|
] })
|
|
@@ -1942,7 +2034,8 @@ function UserBubble({ message }) {
|
|
|
1942
2034
|
function AssistantBubble({
|
|
1943
2035
|
message,
|
|
1944
2036
|
activity,
|
|
1945
|
-
onNavigate
|
|
2037
|
+
onNavigate,
|
|
2038
|
+
anchorRef
|
|
1946
2039
|
}) {
|
|
1947
2040
|
const streaming = message.status === "PENDING" || message.status === "RUNNING";
|
|
1948
2041
|
const body = message.content || message.partialContent || "";
|
|
@@ -1950,9 +2043,10 @@ function AssistantBubble({
|
|
|
1950
2043
|
return /* @__PURE__ */ jsxs(
|
|
1951
2044
|
"div",
|
|
1952
2045
|
{
|
|
2046
|
+
ref: anchorRef,
|
|
1953
2047
|
"data-boff-explore": "assistant",
|
|
1954
2048
|
"data-status": message.status,
|
|
1955
|
-
className: "flex gap-3",
|
|
2049
|
+
className: "flex scroll-mt-4 gap-3",
|
|
1956
2050
|
children: [
|
|
1957
2051
|
/* @__PURE__ */ jsx(
|
|
1958
2052
|
"span",
|
|
@@ -2181,6 +2275,8 @@ function ExplorePage({
|
|
|
2181
2275
|
});
|
|
2182
2276
|
speechStopRef.current = speech.stop;
|
|
2183
2277
|
const scrollRef = useRef(null);
|
|
2278
|
+
const latestAssistantRef = useRef(null);
|
|
2279
|
+
const alignedForRef = useRef(null);
|
|
2184
2280
|
const stickToBottomRef = useRef(true);
|
|
2185
2281
|
const composingRef = useRef(false);
|
|
2186
2282
|
const busy = phase === "sending" || phase === "streaming";
|
|
@@ -2206,12 +2302,32 @@ function ExplorePage({
|
|
|
2206
2302
|
if (!el) return;
|
|
2207
2303
|
stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
2208
2304
|
}, []);
|
|
2305
|
+
const latestAssistantId = useMemo(() => {
|
|
2306
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
2307
|
+
const message = messages[i];
|
|
2308
|
+
if (message && message.role === "ASSISTANT") return message.id;
|
|
2309
|
+
}
|
|
2310
|
+
return null;
|
|
2311
|
+
}, [messages]);
|
|
2209
2312
|
useEffect(() => {
|
|
2313
|
+
const el = scrollRef.current;
|
|
2314
|
+
const anchor = latestAssistantRef.current;
|
|
2315
|
+
if (!el || !anchor || !latestAssistantId) return;
|
|
2316
|
+
if (alignedForRef.current === latestAssistantId) return;
|
|
2210
2317
|
if (!stickToBottomRef.current) return;
|
|
2318
|
+
if (anchor.offsetHeight === 0) return;
|
|
2319
|
+
alignedForRef.current = latestAssistantId;
|
|
2320
|
+
const delta = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
|
|
2321
|
+
el.scrollTop = Math.max(0, el.scrollTop + delta - 12);
|
|
2322
|
+
}, [latestAssistantId, messages]);
|
|
2323
|
+
useEffect(() => {
|
|
2324
|
+
if (!stickToBottomRef.current) return;
|
|
2325
|
+
if (latestAssistantId && alignedForRef.current === latestAssistantId)
|
|
2326
|
+
return;
|
|
2211
2327
|
const el = scrollRef.current;
|
|
2212
2328
|
if (!el) return;
|
|
2213
2329
|
el.scrollTop = el.scrollHeight;
|
|
2214
|
-
}, [
|
|
2330
|
+
}, [activity, latestAssistantId, messages]);
|
|
2215
2331
|
const submitDraft = useCallback(() => {
|
|
2216
2332
|
const text = draft.trim();
|
|
2217
2333
|
if (!text || overLimit || busy || !canSend) return;
|
|
@@ -2411,7 +2527,8 @@ function ExplorePage({
|
|
|
2411
2527
|
{
|
|
2412
2528
|
message,
|
|
2413
2529
|
activity,
|
|
2414
|
-
onNavigate
|
|
2530
|
+
onNavigate,
|
|
2531
|
+
anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0
|
|
2415
2532
|
},
|
|
2416
2533
|
message.id
|
|
2417
2534
|
)
|
|
@@ -2595,6 +2712,6 @@ function ExplorePage({
|
|
|
2595
2712
|
);
|
|
2596
2713
|
}
|
|
2597
2714
|
|
|
2598
|
-
export { ExplorePage };
|
|
2715
|
+
export { ExplorePage, logoUrlOf };
|
|
2599
2716
|
//# sourceMappingURL=explore.mjs.map
|
|
2600
2717
|
//# sourceMappingURL=explore.mjs.map
|