@customerhero/react 1.1.0 → 2.1.0
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/index.cjs +442 -85
- package/dist/index.js +445 -87
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -57,9 +57,6 @@ function useCustomerHeroClient() {
|
|
|
57
57
|
return client;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// src/components/chat-bubble.tsx
|
|
61
|
-
var import_react4 = require("react");
|
|
62
|
-
|
|
63
60
|
// src/use-chat.ts
|
|
64
61
|
var import_react2 = require("react");
|
|
65
62
|
function useChat() {
|
|
@@ -125,6 +122,9 @@ function useChat() {
|
|
|
125
122
|
};
|
|
126
123
|
}
|
|
127
124
|
|
|
125
|
+
// src/components/chat-bubble.tsx
|
|
126
|
+
var import_react4 = require("react");
|
|
127
|
+
|
|
128
128
|
// src/use-reduced-motion.ts
|
|
129
129
|
var import_react3 = require("react");
|
|
130
130
|
function useReducedMotion() {
|
|
@@ -1477,6 +1477,16 @@ var import_react8 = require("react");
|
|
|
1477
1477
|
var import_js2 = require("@customerhero/js");
|
|
1478
1478
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1479
1479
|
var MAX_ATTACHMENTS = 3;
|
|
1480
|
+
var ALLOWED_MIME_TYPES = [
|
|
1481
|
+
"image/png",
|
|
1482
|
+
"image/jpeg",
|
|
1483
|
+
"image/webp",
|
|
1484
|
+
"application/pdf"
|
|
1485
|
+
];
|
|
1486
|
+
var ACCEPT_ATTR = ALLOWED_MIME_TYPES.join(",");
|
|
1487
|
+
function isImageMime(mime) {
|
|
1488
|
+
return mime.startsWith("image/");
|
|
1489
|
+
}
|
|
1480
1490
|
function ChatInput() {
|
|
1481
1491
|
const {
|
|
1482
1492
|
sendMessage,
|
|
@@ -1491,6 +1501,13 @@ function ChatInput() {
|
|
|
1491
1501
|
const [value, setValue] = (0, import_react8.useState)("");
|
|
1492
1502
|
const [attachments, setAttachments] = (0, import_react8.useState)([]);
|
|
1493
1503
|
const [captureSupported, setCaptureSupported] = (0, import_react8.useState)(false);
|
|
1504
|
+
const [menuOpen, setMenuOpen] = (0, import_react8.useState)(false);
|
|
1505
|
+
const [dragActive, setDragActive] = (0, import_react8.useState)(false);
|
|
1506
|
+
const [transientError, setTransientError] = (0, import_react8.useState)(null);
|
|
1507
|
+
const fileInputRef = (0, import_react8.useRef)(null);
|
|
1508
|
+
const menuRef = (0, import_react8.useRef)(null);
|
|
1509
|
+
const menuButtonRef = (0, import_react8.useRef)(null);
|
|
1510
|
+
const dragCounterRef = (0, import_react8.useRef)(0);
|
|
1494
1511
|
(0, import_react8.useEffect)(() => {
|
|
1495
1512
|
setCaptureSupported((0, import_js2.canCaptureScreenshot)());
|
|
1496
1513
|
}, []);
|
|
@@ -1504,6 +1521,30 @@ function ChatInput() {
|
|
|
1504
1521
|
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1505
1522
|
};
|
|
1506
1523
|
}, []);
|
|
1524
|
+
(0, import_react8.useEffect)(() => {
|
|
1525
|
+
if (!menuOpen) return;
|
|
1526
|
+
const onClick = (e) => {
|
|
1527
|
+
const target = e.target;
|
|
1528
|
+
if (menuRef.current?.contains(target) || menuButtonRef.current?.contains(target)) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
setMenuOpen(false);
|
|
1532
|
+
};
|
|
1533
|
+
const onKey = (e) => {
|
|
1534
|
+
if (e.key === "Escape") setMenuOpen(false);
|
|
1535
|
+
};
|
|
1536
|
+
document.addEventListener("mousedown", onClick);
|
|
1537
|
+
document.addEventListener("keydown", onKey);
|
|
1538
|
+
return () => {
|
|
1539
|
+
document.removeEventListener("mousedown", onClick);
|
|
1540
|
+
document.removeEventListener("keydown", onKey);
|
|
1541
|
+
};
|
|
1542
|
+
}, [menuOpen]);
|
|
1543
|
+
(0, import_react8.useEffect)(() => {
|
|
1544
|
+
if (!transientError) return;
|
|
1545
|
+
const id = window.setTimeout(() => setTransientError(null), 4e3);
|
|
1546
|
+
return () => window.clearTimeout(id);
|
|
1547
|
+
}, [transientError]);
|
|
1507
1548
|
const updateAttachment = (id, patch) => {
|
|
1508
1549
|
setAttachments(
|
|
1509
1550
|
(current) => current.map(
|
|
@@ -1511,16 +1552,16 @@ function ChatInput() {
|
|
|
1511
1552
|
)
|
|
1512
1553
|
);
|
|
1513
1554
|
};
|
|
1514
|
-
const startUpload = async (blob) => {
|
|
1555
|
+
const startUpload = async (blob, filename) => {
|
|
1515
1556
|
if (attachments.length >= MAX_ATTACHMENTS) return;
|
|
1516
1557
|
const id = `att_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1517
1558
|
const previewUrl = URL.createObjectURL(blob);
|
|
1518
1559
|
setAttachments((current) => [
|
|
1519
1560
|
...current,
|
|
1520
|
-
{ id, status: "uploading", previewUrl, blob }
|
|
1561
|
+
{ id, status: "uploading", previewUrl, blob, filename }
|
|
1521
1562
|
]);
|
|
1522
1563
|
try {
|
|
1523
|
-
const { attachmentToken } = await uploadAttachment(blob);
|
|
1564
|
+
const { attachmentToken } = await uploadAttachment(blob, { filename });
|
|
1524
1565
|
updateAttachment(id, {
|
|
1525
1566
|
status: "ready",
|
|
1526
1567
|
token: attachmentToken
|
|
@@ -1529,7 +1570,31 @@ function ChatInput() {
|
|
|
1529
1570
|
updateAttachment(id, { status: "error" });
|
|
1530
1571
|
}
|
|
1531
1572
|
};
|
|
1573
|
+
const ingestFiles = (files) => {
|
|
1574
|
+
const remainingSlots = Math.max(0, MAX_ATTACHMENTS - attachments.length);
|
|
1575
|
+
if (remainingSlots === 0) return 0;
|
|
1576
|
+
let queued = 0;
|
|
1577
|
+
let rejectedAny = false;
|
|
1578
|
+
for (const f of Array.from(files)) {
|
|
1579
|
+
if (queued >= remainingSlots) break;
|
|
1580
|
+
const type = f.type;
|
|
1581
|
+
if (!ALLOWED_MIME_TYPES.includes(
|
|
1582
|
+
type
|
|
1583
|
+
)) {
|
|
1584
|
+
rejectedAny = true;
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
const filename = typeof f.name === "string" && f.name.length > 0 ? f.name : void 0;
|
|
1588
|
+
void startUpload(f, filename);
|
|
1589
|
+
queued += 1;
|
|
1590
|
+
}
|
|
1591
|
+
if (rejectedAny) {
|
|
1592
|
+
setTransientError(t("attachment_unsupported_type"));
|
|
1593
|
+
}
|
|
1594
|
+
return queued;
|
|
1595
|
+
};
|
|
1532
1596
|
const handleCapture = async () => {
|
|
1597
|
+
setMenuOpen(false);
|
|
1533
1598
|
try {
|
|
1534
1599
|
const blob = await (0, import_js2.captureScreenshot)();
|
|
1535
1600
|
await startUpload(blob);
|
|
@@ -1537,6 +1602,17 @@ function ChatInput() {
|
|
|
1537
1602
|
if (e instanceof import_js2.ScreenshotCancelled) return;
|
|
1538
1603
|
}
|
|
1539
1604
|
};
|
|
1605
|
+
const handlePickFile = () => {
|
|
1606
|
+
setMenuOpen(false);
|
|
1607
|
+
fileInputRef.current?.click();
|
|
1608
|
+
};
|
|
1609
|
+
const handleFileInputChange = (e) => {
|
|
1610
|
+
const files = e.target.files;
|
|
1611
|
+
if (files && files.length > 0) {
|
|
1612
|
+
ingestFiles(files);
|
|
1613
|
+
}
|
|
1614
|
+
e.target.value = "";
|
|
1615
|
+
};
|
|
1540
1616
|
const handleRemove = (id) => {
|
|
1541
1617
|
setAttachments((current) => {
|
|
1542
1618
|
const target = current.find((a) => a.id === id);
|
|
@@ -1544,6 +1620,45 @@ function ChatInput() {
|
|
|
1544
1620
|
return current.filter((a) => a.id !== id);
|
|
1545
1621
|
});
|
|
1546
1622
|
};
|
|
1623
|
+
const handlePaste = (e) => {
|
|
1624
|
+
const items = e.clipboardData?.items;
|
|
1625
|
+
if (!items || items.length === 0) return;
|
|
1626
|
+
const blobs = [];
|
|
1627
|
+
for (const item of Array.from(items)) {
|
|
1628
|
+
if (item.kind !== "file") continue;
|
|
1629
|
+
const blob = item.getAsFile();
|
|
1630
|
+
if (blob) blobs.push(blob);
|
|
1631
|
+
}
|
|
1632
|
+
if (blobs.length === 0) return;
|
|
1633
|
+
e.preventDefault();
|
|
1634
|
+
ingestFiles(blobs);
|
|
1635
|
+
};
|
|
1636
|
+
const handleDragEnter = (e) => {
|
|
1637
|
+
if (!hasFiles(e)) return;
|
|
1638
|
+
e.preventDefault();
|
|
1639
|
+
dragCounterRef.current += 1;
|
|
1640
|
+
setDragActive(true);
|
|
1641
|
+
};
|
|
1642
|
+
const handleDragOver = (e) => {
|
|
1643
|
+
if (!hasFiles(e)) return;
|
|
1644
|
+
e.preventDefault();
|
|
1645
|
+
};
|
|
1646
|
+
const handleDragLeave = (e) => {
|
|
1647
|
+
if (!hasFiles(e)) return;
|
|
1648
|
+
e.preventDefault();
|
|
1649
|
+
dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);
|
|
1650
|
+
if (dragCounterRef.current === 0) setDragActive(false);
|
|
1651
|
+
};
|
|
1652
|
+
const handleDrop = (e) => {
|
|
1653
|
+
if (!hasFiles(e)) return;
|
|
1654
|
+
e.preventDefault();
|
|
1655
|
+
dragCounterRef.current = 0;
|
|
1656
|
+
setDragActive(false);
|
|
1657
|
+
const files = e.dataTransfer?.files;
|
|
1658
|
+
if (files && files.length > 0) {
|
|
1659
|
+
ingestFiles(files);
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1547
1662
|
const readyTokens = attachments.filter(
|
|
1548
1663
|
(a) => a.status === "ready"
|
|
1549
1664
|
).map((a) => a.token);
|
|
@@ -1564,6 +1679,7 @@ function ChatInput() {
|
|
|
1564
1679
|
}
|
|
1565
1680
|
};
|
|
1566
1681
|
const containerStyle = {
|
|
1682
|
+
position: "relative",
|
|
1567
1683
|
padding: "12px 16px",
|
|
1568
1684
|
borderTop: "1px solid #eee",
|
|
1569
1685
|
display: "flex",
|
|
@@ -1616,99 +1732,236 @@ function ChatInput() {
|
|
|
1616
1732
|
flexShrink: 0,
|
|
1617
1733
|
padding: 0
|
|
1618
1734
|
});
|
|
1619
|
-
const
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1735
|
+
const menuStyle = {
|
|
1736
|
+
position: "absolute",
|
|
1737
|
+
bottom: "calc(100% + 4px)",
|
|
1738
|
+
left: 0,
|
|
1739
|
+
background: "white",
|
|
1740
|
+
border: "1px solid #e0e0e0",
|
|
1741
|
+
borderRadius: 8,
|
|
1742
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
|
|
1743
|
+
padding: 4,
|
|
1744
|
+
minWidth: 180,
|
|
1745
|
+
zIndex: 10,
|
|
1746
|
+
display: "flex",
|
|
1747
|
+
flexDirection: "column"
|
|
1748
|
+
};
|
|
1749
|
+
const menuItemStyle = {
|
|
1750
|
+
display: "flex",
|
|
1751
|
+
alignItems: "center",
|
|
1752
|
+
gap: 10,
|
|
1753
|
+
padding: "8px 12px",
|
|
1754
|
+
background: "transparent",
|
|
1755
|
+
border: "none",
|
|
1756
|
+
borderRadius: 4,
|
|
1757
|
+
cursor: "pointer",
|
|
1758
|
+
fontSize: 14,
|
|
1759
|
+
color: "#333",
|
|
1760
|
+
textAlign: "left",
|
|
1761
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
1762
|
+
};
|
|
1763
|
+
const dropOverlayStyle = {
|
|
1764
|
+
position: "absolute",
|
|
1765
|
+
inset: 0,
|
|
1766
|
+
background: "rgba(255,255,255,0.92)",
|
|
1767
|
+
border: `2px dashed ${config.primaryColor}`,
|
|
1768
|
+
borderRadius: 4,
|
|
1769
|
+
display: "flex",
|
|
1770
|
+
alignItems: "center",
|
|
1771
|
+
justifyContent: "center",
|
|
1772
|
+
color: config.primaryColor,
|
|
1773
|
+
fontSize: 14,
|
|
1774
|
+
fontWeight: 500,
|
|
1775
|
+
pointerEvents: "none",
|
|
1776
|
+
zIndex: 11
|
|
1777
|
+
};
|
|
1778
|
+
const errorPillStyle = {
|
|
1779
|
+
alignSelf: "flex-start",
|
|
1780
|
+
background: "#fef2f2",
|
|
1781
|
+
color: "#b91c1c",
|
|
1782
|
+
border: "1px solid #fecaca",
|
|
1783
|
+
borderRadius: 12,
|
|
1784
|
+
padding: "4px 10px",
|
|
1785
|
+
fontSize: 12
|
|
1786
|
+
};
|
|
1787
|
+
const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
|
|
1788
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1789
|
+
"div",
|
|
1790
|
+
{
|
|
1791
|
+
style: containerStyle,
|
|
1792
|
+
onDragEnter: handleDragEnter,
|
|
1793
|
+
onDragOver: handleDragOver,
|
|
1794
|
+
onDragLeave: handleDragLeave,
|
|
1795
|
+
onDrop: handleDrop,
|
|
1796
|
+
children: [
|
|
1797
|
+
dragActive && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: dropOverlayStyle, "aria-hidden": "true", children: t("drop_files_here") }),
|
|
1798
|
+
transientError && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "alert", style: errorPillStyle, children: transientError }),
|
|
1799
|
+
attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1800
|
+
"div",
|
|
1628
1801
|
{
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1802
|
+
style: { display: "flex", gap: 8, flexWrap: "wrap" },
|
|
1803
|
+
"aria-label": "Attachments",
|
|
1804
|
+
children: attachments.map((a) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1805
|
+
Thumbnail,
|
|
1806
|
+
{
|
|
1807
|
+
attachment: a,
|
|
1808
|
+
onRemove: () => handleRemove(a.id),
|
|
1809
|
+
t
|
|
1810
|
+
},
|
|
1811
|
+
a.id
|
|
1812
|
+
))
|
|
1813
|
+
}
|
|
1814
|
+
),
|
|
1815
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
|
|
1816
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { position: "relative" }, children: [
|
|
1817
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1818
|
+
"button",
|
|
1819
|
+
{
|
|
1820
|
+
ref: menuButtonRef,
|
|
1821
|
+
type: "button",
|
|
1822
|
+
onClick: () => setMenuOpen((o) => !o),
|
|
1823
|
+
disabled: attachDisabled,
|
|
1824
|
+
style: iconButtonStyle(attachDisabled),
|
|
1825
|
+
"aria-label": t("attach_menu_open"),
|
|
1826
|
+
"aria-haspopup": "menu",
|
|
1827
|
+
"aria-expanded": menuOpen,
|
|
1828
|
+
title: t("attach_menu_open"),
|
|
1829
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PaperclipIcon, {})
|
|
1830
|
+
}
|
|
1831
|
+
),
|
|
1832
|
+
menuOpen && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { ref: menuRef, role: "menu", style: menuStyle, children: [
|
|
1833
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1834
|
+
"button",
|
|
1835
|
+
{
|
|
1836
|
+
type: "button",
|
|
1837
|
+
role: "menuitem",
|
|
1838
|
+
onClick: handlePickFile,
|
|
1839
|
+
style: menuItemStyle,
|
|
1840
|
+
onMouseEnter: (e) => {
|
|
1841
|
+
e.currentTarget.style.background = "#f5f5f5";
|
|
1842
|
+
},
|
|
1843
|
+
onMouseLeave: (e) => {
|
|
1844
|
+
e.currentTarget.style.background = "transparent";
|
|
1845
|
+
},
|
|
1846
|
+
children: [
|
|
1847
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ImageIcon, {}),
|
|
1848
|
+
t("attach_photo")
|
|
1849
|
+
]
|
|
1850
|
+
}
|
|
1851
|
+
),
|
|
1852
|
+
captureSupported && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1853
|
+
"button",
|
|
1854
|
+
{
|
|
1855
|
+
type: "button",
|
|
1856
|
+
role: "menuitem",
|
|
1857
|
+
onClick: handleCapture,
|
|
1858
|
+
style: menuItemStyle,
|
|
1859
|
+
onMouseEnter: (e) => {
|
|
1860
|
+
e.currentTarget.style.background = "#f5f5f5";
|
|
1861
|
+
},
|
|
1862
|
+
onMouseLeave: (e) => {
|
|
1863
|
+
e.currentTarget.style.background = "transparent";
|
|
1864
|
+
},
|
|
1865
|
+
children: [
|
|
1866
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CameraIcon, {}),
|
|
1867
|
+
t("screenshot_capture")
|
|
1868
|
+
]
|
|
1869
|
+
}
|
|
1870
|
+
)
|
|
1871
|
+
] })
|
|
1872
|
+
] }),
|
|
1873
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1874
|
+
"input",
|
|
1678
1875
|
{
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1876
|
+
ref: fileInputRef,
|
|
1877
|
+
type: "file",
|
|
1878
|
+
accept: ACCEPT_ATTR,
|
|
1879
|
+
multiple: true,
|
|
1880
|
+
onChange: handleFileInputChange,
|
|
1881
|
+
style: { display: "none" },
|
|
1882
|
+
"aria-hidden": "true",
|
|
1883
|
+
tabIndex: -1
|
|
1884
|
+
}
|
|
1885
|
+
),
|
|
1886
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1887
|
+
"input",
|
|
1888
|
+
{
|
|
1889
|
+
type: "text",
|
|
1890
|
+
value,
|
|
1891
|
+
onChange: (e) => setValue(e.target.value),
|
|
1892
|
+
onKeyDown: handleKeyDown,
|
|
1893
|
+
onPaste: handlePaste,
|
|
1894
|
+
placeholder: config.placeholderText,
|
|
1895
|
+
style: inputStyle,
|
|
1896
|
+
disabled: isLoading
|
|
1897
|
+
}
|
|
1898
|
+
),
|
|
1899
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1900
|
+
"button",
|
|
1901
|
+
{
|
|
1902
|
+
onClick: handleSend,
|
|
1903
|
+
disabled: isLoading || !value.trim(),
|
|
1904
|
+
style: sendButtonStyle,
|
|
1905
|
+
"aria-label": t("send_message"),
|
|
1906
|
+
onMouseEnter: (e) => {
|
|
1907
|
+
if (!reduced && !isLoading)
|
|
1908
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
1909
|
+
},
|
|
1910
|
+
onMouseLeave: (e) => {
|
|
1911
|
+
if (!reduced) e.currentTarget.style.transform = "scale(1)";
|
|
1912
|
+
},
|
|
1913
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1914
|
+
"svg",
|
|
1915
|
+
{
|
|
1916
|
+
width: "16",
|
|
1917
|
+
height: "16",
|
|
1918
|
+
viewBox: "0 0 24 24",
|
|
1919
|
+
fill: "none",
|
|
1920
|
+
stroke: "currentColor",
|
|
1921
|
+
strokeWidth: "2",
|
|
1922
|
+
strokeLinecap: "round",
|
|
1923
|
+
strokeLinejoin: "round",
|
|
1924
|
+
children: [
|
|
1925
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
1926
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
1927
|
+
]
|
|
1928
|
+
}
|
|
1929
|
+
)
|
|
1691
1930
|
}
|
|
1692
1931
|
)
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1932
|
+
] })
|
|
1933
|
+
]
|
|
1934
|
+
}
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
function hasFiles(e) {
|
|
1938
|
+
const types = e.dataTransfer?.types;
|
|
1939
|
+
if (!types) return false;
|
|
1940
|
+
for (let i = 0; i < types.length; i++) {
|
|
1941
|
+
if (types[i] === "Files") return true;
|
|
1942
|
+
}
|
|
1943
|
+
return false;
|
|
1697
1944
|
}
|
|
1698
1945
|
function Thumbnail({
|
|
1699
1946
|
attachment,
|
|
1700
1947
|
onRemove,
|
|
1701
1948
|
t
|
|
1702
1949
|
}) {
|
|
1703
|
-
const { status, previewUrl } = attachment;
|
|
1950
|
+
const { status, previewUrl, blob, filename } = attachment;
|
|
1951
|
+
const isImage = isImageMime(blob.type);
|
|
1704
1952
|
const wrap = {
|
|
1705
1953
|
position: "relative",
|
|
1706
|
-
width: 56,
|
|
1954
|
+
width: isImage ? 56 : 160,
|
|
1707
1955
|
height: 56,
|
|
1708
1956
|
borderRadius: 8,
|
|
1709
1957
|
overflow: "hidden",
|
|
1710
1958
|
border: status === "error" ? "2px solid #b91c1c" : "1px solid #e0e0e0",
|
|
1711
|
-
background: "#f5f5f5"
|
|
1959
|
+
background: "#f5f5f5",
|
|
1960
|
+
display: "flex",
|
|
1961
|
+
alignItems: "center",
|
|
1962
|
+
justifyContent: isImage ? "stretch" : "flex-start",
|
|
1963
|
+
gap: isImage ? 0 : 8,
|
|
1964
|
+
padding: isImage ? 0 : "0 8px"
|
|
1712
1965
|
};
|
|
1713
1966
|
const img = {
|
|
1714
1967
|
width: "100%",
|
|
@@ -1739,8 +1992,42 @@ function Thumbnail({
|
|
|
1739
1992
|
alignItems: "center",
|
|
1740
1993
|
justifyContent: "center"
|
|
1741
1994
|
};
|
|
1995
|
+
const docName = {
|
|
1996
|
+
fontSize: 12,
|
|
1997
|
+
fontWeight: 500,
|
|
1998
|
+
color: "#333",
|
|
1999
|
+
overflow: "hidden",
|
|
2000
|
+
textOverflow: "ellipsis",
|
|
2001
|
+
whiteSpace: "nowrap",
|
|
2002
|
+
maxWidth: 110,
|
|
2003
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
2004
|
+
};
|
|
2005
|
+
const docMeta = {
|
|
2006
|
+
fontSize: 11,
|
|
2007
|
+
color: "#888",
|
|
2008
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
2009
|
+
};
|
|
2010
|
+
const displayName = filename ?? (isImage ? "" : "Document");
|
|
2011
|
+
const sizeLabel = formatBytes(blob.size);
|
|
1742
2012
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: wrap, children: [
|
|
1743
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: previewUrl, alt: "", style: img }),
|
|
2013
|
+
isImage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: previewUrl, alt: "", style: img }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2014
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocIcon, {}),
|
|
2015
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2016
|
+
"div",
|
|
2017
|
+
{
|
|
2018
|
+
style: {
|
|
2019
|
+
display: "flex",
|
|
2020
|
+
flexDirection: "column",
|
|
2021
|
+
minWidth: 0,
|
|
2022
|
+
flex: 1
|
|
2023
|
+
},
|
|
2024
|
+
children: [
|
|
2025
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: docName, title: displayName, children: displayName }),
|
|
2026
|
+
sizeLabel && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: docMeta, children: sizeLabel })
|
|
2027
|
+
]
|
|
2028
|
+
}
|
|
2029
|
+
)
|
|
2030
|
+
] }),
|
|
1744
2031
|
status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: overlay, "aria-label": "Uploading", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Spinner2, {}) }),
|
|
1745
2032
|
status === "error" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1746
2033
|
"div",
|
|
@@ -1763,7 +2050,45 @@ function Thumbnail({
|
|
|
1763
2050
|
)
|
|
1764
2051
|
] });
|
|
1765
2052
|
}
|
|
1766
|
-
function
|
|
2053
|
+
function PaperclipIcon() {
|
|
2054
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2055
|
+
"svg",
|
|
2056
|
+
{
|
|
2057
|
+
width: "20",
|
|
2058
|
+
height: "20",
|
|
2059
|
+
viewBox: "0 0 24 24",
|
|
2060
|
+
fill: "none",
|
|
2061
|
+
stroke: "currentColor",
|
|
2062
|
+
strokeWidth: "2",
|
|
2063
|
+
strokeLinecap: "round",
|
|
2064
|
+
strokeLinejoin: "round",
|
|
2065
|
+
"aria-hidden": "true",
|
|
2066
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
|
|
2067
|
+
}
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
function ImageIcon() {
|
|
2071
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2072
|
+
"svg",
|
|
2073
|
+
{
|
|
2074
|
+
width: "18",
|
|
2075
|
+
height: "18",
|
|
2076
|
+
viewBox: "0 0 24 24",
|
|
2077
|
+
fill: "none",
|
|
2078
|
+
stroke: "currentColor",
|
|
2079
|
+
strokeWidth: "2",
|
|
2080
|
+
strokeLinecap: "round",
|
|
2081
|
+
strokeLinejoin: "round",
|
|
2082
|
+
"aria-hidden": "true",
|
|
2083
|
+
children: [
|
|
2084
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
2085
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
2086
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polyline", { points: "21 15 16 10 5 21" })
|
|
2087
|
+
]
|
|
2088
|
+
}
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
function DocIcon() {
|
|
1767
2092
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1768
2093
|
"svg",
|
|
1769
2094
|
{
|
|
@@ -1776,6 +2101,36 @@ function CameraIcon() {
|
|
|
1776
2101
|
strokeLinecap: "round",
|
|
1777
2102
|
strokeLinejoin: "round",
|
|
1778
2103
|
"aria-hidden": "true",
|
|
2104
|
+
style: { color: "#666", flexShrink: 0 },
|
|
2105
|
+
children: [
|
|
2106
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
2107
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polyline", { points: "14 2 14 8 20 8" }),
|
|
2108
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
2109
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "16", y1: "17", x2: "8", y2: "17" }),
|
|
2110
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polyline", { points: "10 9 9 9 8 9" })
|
|
2111
|
+
]
|
|
2112
|
+
}
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
function formatBytes(bytes) {
|
|
2116
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
2117
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2118
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
2119
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2120
|
+
}
|
|
2121
|
+
function CameraIcon() {
|
|
2122
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2123
|
+
"svg",
|
|
2124
|
+
{
|
|
2125
|
+
width: "18",
|
|
2126
|
+
height: "18",
|
|
2127
|
+
viewBox: "0 0 24 24",
|
|
2128
|
+
fill: "none",
|
|
2129
|
+
stroke: "currentColor",
|
|
2130
|
+
strokeWidth: "2",
|
|
2131
|
+
strokeLinecap: "round",
|
|
2132
|
+
strokeLinejoin: "round",
|
|
2133
|
+
"aria-hidden": "true",
|
|
1779
2134
|
children: [
|
|
1780
2135
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" }),
|
|
1781
2136
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "13", r: "4" })
|
|
@@ -2177,6 +2532,7 @@ function ChatWindow() {
|
|
|
2177
2532
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2178
2533
|
function ChatWidgetInner({ identity }) {
|
|
2179
2534
|
const client = useCustomerHeroClient();
|
|
2535
|
+
const { configLoaded, configError } = useChat();
|
|
2180
2536
|
const prevIdentityRef = (0, import_react10.useRef)(void 0);
|
|
2181
2537
|
(0, import_react10.useEffect)(() => {
|
|
2182
2538
|
const key = identity ? JSON.stringify(identity) : void 0;
|
|
@@ -2187,6 +2543,7 @@ function ChatWidgetInner({ identity }) {
|
|
|
2187
2543
|
}
|
|
2188
2544
|
}
|
|
2189
2545
|
}, [identity, client]);
|
|
2546
|
+
if (!configLoaded || configError) return null;
|
|
2190
2547
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
2191
2548
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatBubble, {}),
|
|
2192
2549
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatWindow, {})
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/chat-widget.tsx
|
|
2
|
-
import { useEffect as useEffect8, useRef as
|
|
2
|
+
import { useEffect as useEffect8, useRef as useRef5 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/context.tsx
|
|
5
5
|
import {
|
|
@@ -36,9 +36,6 @@ function useCustomerHeroClient() {
|
|
|
36
36
|
return client;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// src/components/chat-bubble.tsx
|
|
40
|
-
import { useEffect as useEffect3, useState as useState2 } from "react";
|
|
41
|
-
|
|
42
39
|
// src/use-chat.ts
|
|
43
40
|
import { useCallback, useSyncExternalStore } from "react";
|
|
44
41
|
function useChat() {
|
|
@@ -104,6 +101,9 @@ function useChat() {
|
|
|
104
101
|
};
|
|
105
102
|
}
|
|
106
103
|
|
|
104
|
+
// src/components/chat-bubble.tsx
|
|
105
|
+
import { useEffect as useEffect3, useState as useState2 } from "react";
|
|
106
|
+
|
|
107
107
|
// src/use-reduced-motion.ts
|
|
108
108
|
import { useEffect as useEffect2, useState } from "react";
|
|
109
109
|
function useReducedMotion() {
|
|
@@ -1454,6 +1454,7 @@ function ChatSuggestions() {
|
|
|
1454
1454
|
// src/components/chat-input.tsx
|
|
1455
1455
|
import {
|
|
1456
1456
|
useEffect as useEffect6,
|
|
1457
|
+
useRef as useRef4,
|
|
1457
1458
|
useState as useState6
|
|
1458
1459
|
} from "react";
|
|
1459
1460
|
import {
|
|
@@ -1463,6 +1464,16 @@ import {
|
|
|
1463
1464
|
} from "@customerhero/js";
|
|
1464
1465
|
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1465
1466
|
var MAX_ATTACHMENTS = 3;
|
|
1467
|
+
var ALLOWED_MIME_TYPES = [
|
|
1468
|
+
"image/png",
|
|
1469
|
+
"image/jpeg",
|
|
1470
|
+
"image/webp",
|
|
1471
|
+
"application/pdf"
|
|
1472
|
+
];
|
|
1473
|
+
var ACCEPT_ATTR = ALLOWED_MIME_TYPES.join(",");
|
|
1474
|
+
function isImageMime(mime) {
|
|
1475
|
+
return mime.startsWith("image/");
|
|
1476
|
+
}
|
|
1466
1477
|
function ChatInput() {
|
|
1467
1478
|
const {
|
|
1468
1479
|
sendMessage,
|
|
@@ -1477,6 +1488,13 @@ function ChatInput() {
|
|
|
1477
1488
|
const [value, setValue] = useState6("");
|
|
1478
1489
|
const [attachments, setAttachments] = useState6([]);
|
|
1479
1490
|
const [captureSupported, setCaptureSupported] = useState6(false);
|
|
1491
|
+
const [menuOpen, setMenuOpen] = useState6(false);
|
|
1492
|
+
const [dragActive, setDragActive] = useState6(false);
|
|
1493
|
+
const [transientError, setTransientError] = useState6(null);
|
|
1494
|
+
const fileInputRef = useRef4(null);
|
|
1495
|
+
const menuRef = useRef4(null);
|
|
1496
|
+
const menuButtonRef = useRef4(null);
|
|
1497
|
+
const dragCounterRef = useRef4(0);
|
|
1480
1498
|
useEffect6(() => {
|
|
1481
1499
|
setCaptureSupported(canCaptureScreenshot());
|
|
1482
1500
|
}, []);
|
|
@@ -1490,6 +1508,30 @@ function ChatInput() {
|
|
|
1490
1508
|
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1491
1509
|
};
|
|
1492
1510
|
}, []);
|
|
1511
|
+
useEffect6(() => {
|
|
1512
|
+
if (!menuOpen) return;
|
|
1513
|
+
const onClick = (e) => {
|
|
1514
|
+
const target = e.target;
|
|
1515
|
+
if (menuRef.current?.contains(target) || menuButtonRef.current?.contains(target)) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
setMenuOpen(false);
|
|
1519
|
+
};
|
|
1520
|
+
const onKey = (e) => {
|
|
1521
|
+
if (e.key === "Escape") setMenuOpen(false);
|
|
1522
|
+
};
|
|
1523
|
+
document.addEventListener("mousedown", onClick);
|
|
1524
|
+
document.addEventListener("keydown", onKey);
|
|
1525
|
+
return () => {
|
|
1526
|
+
document.removeEventListener("mousedown", onClick);
|
|
1527
|
+
document.removeEventListener("keydown", onKey);
|
|
1528
|
+
};
|
|
1529
|
+
}, [menuOpen]);
|
|
1530
|
+
useEffect6(() => {
|
|
1531
|
+
if (!transientError) return;
|
|
1532
|
+
const id = window.setTimeout(() => setTransientError(null), 4e3);
|
|
1533
|
+
return () => window.clearTimeout(id);
|
|
1534
|
+
}, [transientError]);
|
|
1493
1535
|
const updateAttachment = (id, patch) => {
|
|
1494
1536
|
setAttachments(
|
|
1495
1537
|
(current) => current.map(
|
|
@@ -1497,16 +1539,16 @@ function ChatInput() {
|
|
|
1497
1539
|
)
|
|
1498
1540
|
);
|
|
1499
1541
|
};
|
|
1500
|
-
const startUpload = async (blob) => {
|
|
1542
|
+
const startUpload = async (blob, filename) => {
|
|
1501
1543
|
if (attachments.length >= MAX_ATTACHMENTS) return;
|
|
1502
1544
|
const id = `att_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1503
1545
|
const previewUrl = URL.createObjectURL(blob);
|
|
1504
1546
|
setAttachments((current) => [
|
|
1505
1547
|
...current,
|
|
1506
|
-
{ id, status: "uploading", previewUrl, blob }
|
|
1548
|
+
{ id, status: "uploading", previewUrl, blob, filename }
|
|
1507
1549
|
]);
|
|
1508
1550
|
try {
|
|
1509
|
-
const { attachmentToken } = await uploadAttachment(blob);
|
|
1551
|
+
const { attachmentToken } = await uploadAttachment(blob, { filename });
|
|
1510
1552
|
updateAttachment(id, {
|
|
1511
1553
|
status: "ready",
|
|
1512
1554
|
token: attachmentToken
|
|
@@ -1515,7 +1557,31 @@ function ChatInput() {
|
|
|
1515
1557
|
updateAttachment(id, { status: "error" });
|
|
1516
1558
|
}
|
|
1517
1559
|
};
|
|
1560
|
+
const ingestFiles = (files) => {
|
|
1561
|
+
const remainingSlots = Math.max(0, MAX_ATTACHMENTS - attachments.length);
|
|
1562
|
+
if (remainingSlots === 0) return 0;
|
|
1563
|
+
let queued = 0;
|
|
1564
|
+
let rejectedAny = false;
|
|
1565
|
+
for (const f of Array.from(files)) {
|
|
1566
|
+
if (queued >= remainingSlots) break;
|
|
1567
|
+
const type = f.type;
|
|
1568
|
+
if (!ALLOWED_MIME_TYPES.includes(
|
|
1569
|
+
type
|
|
1570
|
+
)) {
|
|
1571
|
+
rejectedAny = true;
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
const filename = typeof f.name === "string" && f.name.length > 0 ? f.name : void 0;
|
|
1575
|
+
void startUpload(f, filename);
|
|
1576
|
+
queued += 1;
|
|
1577
|
+
}
|
|
1578
|
+
if (rejectedAny) {
|
|
1579
|
+
setTransientError(t("attachment_unsupported_type"));
|
|
1580
|
+
}
|
|
1581
|
+
return queued;
|
|
1582
|
+
};
|
|
1518
1583
|
const handleCapture = async () => {
|
|
1584
|
+
setMenuOpen(false);
|
|
1519
1585
|
try {
|
|
1520
1586
|
const blob = await captureScreenshot();
|
|
1521
1587
|
await startUpload(blob);
|
|
@@ -1523,6 +1589,17 @@ function ChatInput() {
|
|
|
1523
1589
|
if (e instanceof ScreenshotCancelled) return;
|
|
1524
1590
|
}
|
|
1525
1591
|
};
|
|
1592
|
+
const handlePickFile = () => {
|
|
1593
|
+
setMenuOpen(false);
|
|
1594
|
+
fileInputRef.current?.click();
|
|
1595
|
+
};
|
|
1596
|
+
const handleFileInputChange = (e) => {
|
|
1597
|
+
const files = e.target.files;
|
|
1598
|
+
if (files && files.length > 0) {
|
|
1599
|
+
ingestFiles(files);
|
|
1600
|
+
}
|
|
1601
|
+
e.target.value = "";
|
|
1602
|
+
};
|
|
1526
1603
|
const handleRemove = (id) => {
|
|
1527
1604
|
setAttachments((current) => {
|
|
1528
1605
|
const target = current.find((a) => a.id === id);
|
|
@@ -1530,6 +1607,45 @@ function ChatInput() {
|
|
|
1530
1607
|
return current.filter((a) => a.id !== id);
|
|
1531
1608
|
});
|
|
1532
1609
|
};
|
|
1610
|
+
const handlePaste = (e) => {
|
|
1611
|
+
const items = e.clipboardData?.items;
|
|
1612
|
+
if (!items || items.length === 0) return;
|
|
1613
|
+
const blobs = [];
|
|
1614
|
+
for (const item of Array.from(items)) {
|
|
1615
|
+
if (item.kind !== "file") continue;
|
|
1616
|
+
const blob = item.getAsFile();
|
|
1617
|
+
if (blob) blobs.push(blob);
|
|
1618
|
+
}
|
|
1619
|
+
if (blobs.length === 0) return;
|
|
1620
|
+
e.preventDefault();
|
|
1621
|
+
ingestFiles(blobs);
|
|
1622
|
+
};
|
|
1623
|
+
const handleDragEnter = (e) => {
|
|
1624
|
+
if (!hasFiles(e)) return;
|
|
1625
|
+
e.preventDefault();
|
|
1626
|
+
dragCounterRef.current += 1;
|
|
1627
|
+
setDragActive(true);
|
|
1628
|
+
};
|
|
1629
|
+
const handleDragOver = (e) => {
|
|
1630
|
+
if (!hasFiles(e)) return;
|
|
1631
|
+
e.preventDefault();
|
|
1632
|
+
};
|
|
1633
|
+
const handleDragLeave = (e) => {
|
|
1634
|
+
if (!hasFiles(e)) return;
|
|
1635
|
+
e.preventDefault();
|
|
1636
|
+
dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);
|
|
1637
|
+
if (dragCounterRef.current === 0) setDragActive(false);
|
|
1638
|
+
};
|
|
1639
|
+
const handleDrop = (e) => {
|
|
1640
|
+
if (!hasFiles(e)) return;
|
|
1641
|
+
e.preventDefault();
|
|
1642
|
+
dragCounterRef.current = 0;
|
|
1643
|
+
setDragActive(false);
|
|
1644
|
+
const files = e.dataTransfer?.files;
|
|
1645
|
+
if (files && files.length > 0) {
|
|
1646
|
+
ingestFiles(files);
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1533
1649
|
const readyTokens = attachments.filter(
|
|
1534
1650
|
(a) => a.status === "ready"
|
|
1535
1651
|
).map((a) => a.token);
|
|
@@ -1550,6 +1666,7 @@ function ChatInput() {
|
|
|
1550
1666
|
}
|
|
1551
1667
|
};
|
|
1552
1668
|
const containerStyle = {
|
|
1669
|
+
position: "relative",
|
|
1553
1670
|
padding: "12px 16px",
|
|
1554
1671
|
borderTop: "1px solid #eee",
|
|
1555
1672
|
display: "flex",
|
|
@@ -1602,99 +1719,236 @@ function ChatInput() {
|
|
|
1602
1719
|
flexShrink: 0,
|
|
1603
1720
|
padding: 0
|
|
1604
1721
|
});
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1722
|
+
const menuStyle = {
|
|
1723
|
+
position: "absolute",
|
|
1724
|
+
bottom: "calc(100% + 4px)",
|
|
1725
|
+
left: 0,
|
|
1726
|
+
background: "white",
|
|
1727
|
+
border: "1px solid #e0e0e0",
|
|
1728
|
+
borderRadius: 8,
|
|
1729
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
|
|
1730
|
+
padding: 4,
|
|
1731
|
+
minWidth: 180,
|
|
1732
|
+
zIndex: 10,
|
|
1733
|
+
display: "flex",
|
|
1734
|
+
flexDirection: "column"
|
|
1735
|
+
};
|
|
1736
|
+
const menuItemStyle = {
|
|
1737
|
+
display: "flex",
|
|
1738
|
+
alignItems: "center",
|
|
1739
|
+
gap: 10,
|
|
1740
|
+
padding: "8px 12px",
|
|
1741
|
+
background: "transparent",
|
|
1742
|
+
border: "none",
|
|
1743
|
+
borderRadius: 4,
|
|
1744
|
+
cursor: "pointer",
|
|
1745
|
+
fontSize: 14,
|
|
1746
|
+
color: "#333",
|
|
1747
|
+
textAlign: "left",
|
|
1748
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
1749
|
+
};
|
|
1750
|
+
const dropOverlayStyle = {
|
|
1751
|
+
position: "absolute",
|
|
1752
|
+
inset: 0,
|
|
1753
|
+
background: "rgba(255,255,255,0.92)",
|
|
1754
|
+
border: `2px dashed ${config.primaryColor}`,
|
|
1755
|
+
borderRadius: 4,
|
|
1756
|
+
display: "flex",
|
|
1757
|
+
alignItems: "center",
|
|
1758
|
+
justifyContent: "center",
|
|
1759
|
+
color: config.primaryColor,
|
|
1760
|
+
fontSize: 14,
|
|
1761
|
+
fontWeight: 500,
|
|
1762
|
+
pointerEvents: "none",
|
|
1763
|
+
zIndex: 11
|
|
1764
|
+
};
|
|
1765
|
+
const errorPillStyle = {
|
|
1766
|
+
alignSelf: "flex-start",
|
|
1767
|
+
background: "#fef2f2",
|
|
1768
|
+
color: "#b91c1c",
|
|
1769
|
+
border: "1px solid #fecaca",
|
|
1770
|
+
borderRadius: 12,
|
|
1771
|
+
padding: "4px 10px",
|
|
1772
|
+
fontSize: 12
|
|
1773
|
+
};
|
|
1774
|
+
const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
|
|
1775
|
+
return /* @__PURE__ */ jsxs5(
|
|
1776
|
+
"div",
|
|
1777
|
+
{
|
|
1778
|
+
style: containerStyle,
|
|
1779
|
+
onDragEnter: handleDragEnter,
|
|
1780
|
+
onDragOver: handleDragOver,
|
|
1781
|
+
onDragLeave: handleDragLeave,
|
|
1782
|
+
onDrop: handleDrop,
|
|
1783
|
+
children: [
|
|
1784
|
+
dragActive && /* @__PURE__ */ jsx8("div", { style: dropOverlayStyle, "aria-hidden": "true", children: t("drop_files_here") }),
|
|
1785
|
+
transientError && /* @__PURE__ */ jsx8("div", { role: "alert", style: errorPillStyle, children: transientError }),
|
|
1786
|
+
attachments.length > 0 && /* @__PURE__ */ jsx8(
|
|
1787
|
+
"div",
|
|
1614
1788
|
{
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1789
|
+
style: { display: "flex", gap: 8, flexWrap: "wrap" },
|
|
1790
|
+
"aria-label": "Attachments",
|
|
1791
|
+
children: attachments.map((a) => /* @__PURE__ */ jsx8(
|
|
1792
|
+
Thumbnail,
|
|
1793
|
+
{
|
|
1794
|
+
attachment: a,
|
|
1795
|
+
onRemove: () => handleRemove(a.id),
|
|
1796
|
+
t
|
|
1797
|
+
},
|
|
1798
|
+
a.id
|
|
1799
|
+
))
|
|
1800
|
+
}
|
|
1801
|
+
),
|
|
1802
|
+
/* @__PURE__ */ jsxs5("div", { style: rowStyle, children: [
|
|
1803
|
+
/* @__PURE__ */ jsxs5("div", { style: { position: "relative" }, children: [
|
|
1804
|
+
/* @__PURE__ */ jsx8(
|
|
1805
|
+
"button",
|
|
1806
|
+
{
|
|
1807
|
+
ref: menuButtonRef,
|
|
1808
|
+
type: "button",
|
|
1809
|
+
onClick: () => setMenuOpen((o) => !o),
|
|
1810
|
+
disabled: attachDisabled,
|
|
1811
|
+
style: iconButtonStyle(attachDisabled),
|
|
1812
|
+
"aria-label": t("attach_menu_open"),
|
|
1813
|
+
"aria-haspopup": "menu",
|
|
1814
|
+
"aria-expanded": menuOpen,
|
|
1815
|
+
title: t("attach_menu_open"),
|
|
1816
|
+
children: /* @__PURE__ */ jsx8(PaperclipIcon, {})
|
|
1817
|
+
}
|
|
1818
|
+
),
|
|
1819
|
+
menuOpen && /* @__PURE__ */ jsxs5("div", { ref: menuRef, role: "menu", style: menuStyle, children: [
|
|
1820
|
+
/* @__PURE__ */ jsxs5(
|
|
1821
|
+
"button",
|
|
1822
|
+
{
|
|
1823
|
+
type: "button",
|
|
1824
|
+
role: "menuitem",
|
|
1825
|
+
onClick: handlePickFile,
|
|
1826
|
+
style: menuItemStyle,
|
|
1827
|
+
onMouseEnter: (e) => {
|
|
1828
|
+
e.currentTarget.style.background = "#f5f5f5";
|
|
1829
|
+
},
|
|
1830
|
+
onMouseLeave: (e) => {
|
|
1831
|
+
e.currentTarget.style.background = "transparent";
|
|
1832
|
+
},
|
|
1833
|
+
children: [
|
|
1834
|
+
/* @__PURE__ */ jsx8(ImageIcon, {}),
|
|
1835
|
+
t("attach_photo")
|
|
1836
|
+
]
|
|
1837
|
+
}
|
|
1838
|
+
),
|
|
1839
|
+
captureSupported && /* @__PURE__ */ jsxs5(
|
|
1840
|
+
"button",
|
|
1841
|
+
{
|
|
1842
|
+
type: "button",
|
|
1843
|
+
role: "menuitem",
|
|
1844
|
+
onClick: handleCapture,
|
|
1845
|
+
style: menuItemStyle,
|
|
1846
|
+
onMouseEnter: (e) => {
|
|
1847
|
+
e.currentTarget.style.background = "#f5f5f5";
|
|
1848
|
+
},
|
|
1849
|
+
onMouseLeave: (e) => {
|
|
1850
|
+
e.currentTarget.style.background = "transparent";
|
|
1851
|
+
},
|
|
1852
|
+
children: [
|
|
1853
|
+
/* @__PURE__ */ jsx8(CameraIcon, {}),
|
|
1854
|
+
t("screenshot_capture")
|
|
1855
|
+
]
|
|
1856
|
+
}
|
|
1857
|
+
)
|
|
1858
|
+
] })
|
|
1859
|
+
] }),
|
|
1860
|
+
/* @__PURE__ */ jsx8(
|
|
1861
|
+
"input",
|
|
1664
1862
|
{
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1863
|
+
ref: fileInputRef,
|
|
1864
|
+
type: "file",
|
|
1865
|
+
accept: ACCEPT_ATTR,
|
|
1866
|
+
multiple: true,
|
|
1867
|
+
onChange: handleFileInputChange,
|
|
1868
|
+
style: { display: "none" },
|
|
1869
|
+
"aria-hidden": "true",
|
|
1870
|
+
tabIndex: -1
|
|
1871
|
+
}
|
|
1872
|
+
),
|
|
1873
|
+
/* @__PURE__ */ jsx8(
|
|
1874
|
+
"input",
|
|
1875
|
+
{
|
|
1876
|
+
type: "text",
|
|
1877
|
+
value,
|
|
1878
|
+
onChange: (e) => setValue(e.target.value),
|
|
1879
|
+
onKeyDown: handleKeyDown,
|
|
1880
|
+
onPaste: handlePaste,
|
|
1881
|
+
placeholder: config.placeholderText,
|
|
1882
|
+
style: inputStyle,
|
|
1883
|
+
disabled: isLoading
|
|
1884
|
+
}
|
|
1885
|
+
),
|
|
1886
|
+
/* @__PURE__ */ jsx8(
|
|
1887
|
+
"button",
|
|
1888
|
+
{
|
|
1889
|
+
onClick: handleSend,
|
|
1890
|
+
disabled: isLoading || !value.trim(),
|
|
1891
|
+
style: sendButtonStyle,
|
|
1892
|
+
"aria-label": t("send_message"),
|
|
1893
|
+
onMouseEnter: (e) => {
|
|
1894
|
+
if (!reduced && !isLoading)
|
|
1895
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
1896
|
+
},
|
|
1897
|
+
onMouseLeave: (e) => {
|
|
1898
|
+
if (!reduced) e.currentTarget.style.transform = "scale(1)";
|
|
1899
|
+
},
|
|
1900
|
+
children: /* @__PURE__ */ jsxs5(
|
|
1901
|
+
"svg",
|
|
1902
|
+
{
|
|
1903
|
+
width: "16",
|
|
1904
|
+
height: "16",
|
|
1905
|
+
viewBox: "0 0 24 24",
|
|
1906
|
+
fill: "none",
|
|
1907
|
+
stroke: "currentColor",
|
|
1908
|
+
strokeWidth: "2",
|
|
1909
|
+
strokeLinecap: "round",
|
|
1910
|
+
strokeLinejoin: "round",
|
|
1911
|
+
children: [
|
|
1912
|
+
/* @__PURE__ */ jsx8("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
1913
|
+
/* @__PURE__ */ jsx8("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
1914
|
+
]
|
|
1915
|
+
}
|
|
1916
|
+
)
|
|
1677
1917
|
}
|
|
1678
1918
|
)
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1919
|
+
] })
|
|
1920
|
+
]
|
|
1921
|
+
}
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
function hasFiles(e) {
|
|
1925
|
+
const types = e.dataTransfer?.types;
|
|
1926
|
+
if (!types) return false;
|
|
1927
|
+
for (let i = 0; i < types.length; i++) {
|
|
1928
|
+
if (types[i] === "Files") return true;
|
|
1929
|
+
}
|
|
1930
|
+
return false;
|
|
1683
1931
|
}
|
|
1684
1932
|
function Thumbnail({
|
|
1685
1933
|
attachment,
|
|
1686
1934
|
onRemove,
|
|
1687
1935
|
t
|
|
1688
1936
|
}) {
|
|
1689
|
-
const { status, previewUrl } = attachment;
|
|
1937
|
+
const { status, previewUrl, blob, filename } = attachment;
|
|
1938
|
+
const isImage = isImageMime(blob.type);
|
|
1690
1939
|
const wrap = {
|
|
1691
1940
|
position: "relative",
|
|
1692
|
-
width: 56,
|
|
1941
|
+
width: isImage ? 56 : 160,
|
|
1693
1942
|
height: 56,
|
|
1694
1943
|
borderRadius: 8,
|
|
1695
1944
|
overflow: "hidden",
|
|
1696
1945
|
border: status === "error" ? "2px solid #b91c1c" : "1px solid #e0e0e0",
|
|
1697
|
-
background: "#f5f5f5"
|
|
1946
|
+
background: "#f5f5f5",
|
|
1947
|
+
display: "flex",
|
|
1948
|
+
alignItems: "center",
|
|
1949
|
+
justifyContent: isImage ? "stretch" : "flex-start",
|
|
1950
|
+
gap: isImage ? 0 : 8,
|
|
1951
|
+
padding: isImage ? 0 : "0 8px"
|
|
1698
1952
|
};
|
|
1699
1953
|
const img = {
|
|
1700
1954
|
width: "100%",
|
|
@@ -1725,8 +1979,42 @@ function Thumbnail({
|
|
|
1725
1979
|
alignItems: "center",
|
|
1726
1980
|
justifyContent: "center"
|
|
1727
1981
|
};
|
|
1982
|
+
const docName = {
|
|
1983
|
+
fontSize: 12,
|
|
1984
|
+
fontWeight: 500,
|
|
1985
|
+
color: "#333",
|
|
1986
|
+
overflow: "hidden",
|
|
1987
|
+
textOverflow: "ellipsis",
|
|
1988
|
+
whiteSpace: "nowrap",
|
|
1989
|
+
maxWidth: 110,
|
|
1990
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
1991
|
+
};
|
|
1992
|
+
const docMeta = {
|
|
1993
|
+
fontSize: 11,
|
|
1994
|
+
color: "#888",
|
|
1995
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
1996
|
+
};
|
|
1997
|
+
const displayName = filename ?? (isImage ? "" : "Document");
|
|
1998
|
+
const sizeLabel = formatBytes(blob.size);
|
|
1728
1999
|
return /* @__PURE__ */ jsxs5("div", { style: wrap, children: [
|
|
1729
|
-
/* @__PURE__ */ jsx8("img", { src: previewUrl, alt: "", style: img }),
|
|
2000
|
+
isImage ? /* @__PURE__ */ jsx8("img", { src: previewUrl, alt: "", style: img }) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
2001
|
+
/* @__PURE__ */ jsx8(DocIcon, {}),
|
|
2002
|
+
/* @__PURE__ */ jsxs5(
|
|
2003
|
+
"div",
|
|
2004
|
+
{
|
|
2005
|
+
style: {
|
|
2006
|
+
display: "flex",
|
|
2007
|
+
flexDirection: "column",
|
|
2008
|
+
minWidth: 0,
|
|
2009
|
+
flex: 1
|
|
2010
|
+
},
|
|
2011
|
+
children: [
|
|
2012
|
+
/* @__PURE__ */ jsx8("span", { style: docName, title: displayName, children: displayName }),
|
|
2013
|
+
sizeLabel && /* @__PURE__ */ jsx8("span", { style: docMeta, children: sizeLabel })
|
|
2014
|
+
]
|
|
2015
|
+
}
|
|
2016
|
+
)
|
|
2017
|
+
] }),
|
|
1730
2018
|
status === "uploading" && /* @__PURE__ */ jsx8("div", { style: overlay, "aria-label": "Uploading", children: /* @__PURE__ */ jsx8(Spinner2, {}) }),
|
|
1731
2019
|
status === "error" && /* @__PURE__ */ jsx8(
|
|
1732
2020
|
"div",
|
|
@@ -1749,7 +2037,45 @@ function Thumbnail({
|
|
|
1749
2037
|
)
|
|
1750
2038
|
] });
|
|
1751
2039
|
}
|
|
1752
|
-
function
|
|
2040
|
+
function PaperclipIcon() {
|
|
2041
|
+
return /* @__PURE__ */ jsx8(
|
|
2042
|
+
"svg",
|
|
2043
|
+
{
|
|
2044
|
+
width: "20",
|
|
2045
|
+
height: "20",
|
|
2046
|
+
viewBox: "0 0 24 24",
|
|
2047
|
+
fill: "none",
|
|
2048
|
+
stroke: "currentColor",
|
|
2049
|
+
strokeWidth: "2",
|
|
2050
|
+
strokeLinecap: "round",
|
|
2051
|
+
strokeLinejoin: "round",
|
|
2052
|
+
"aria-hidden": "true",
|
|
2053
|
+
children: /* @__PURE__ */ jsx8("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
|
|
2054
|
+
}
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
function ImageIcon() {
|
|
2058
|
+
return /* @__PURE__ */ jsxs5(
|
|
2059
|
+
"svg",
|
|
2060
|
+
{
|
|
2061
|
+
width: "18",
|
|
2062
|
+
height: "18",
|
|
2063
|
+
viewBox: "0 0 24 24",
|
|
2064
|
+
fill: "none",
|
|
2065
|
+
stroke: "currentColor",
|
|
2066
|
+
strokeWidth: "2",
|
|
2067
|
+
strokeLinecap: "round",
|
|
2068
|
+
strokeLinejoin: "round",
|
|
2069
|
+
"aria-hidden": "true",
|
|
2070
|
+
children: [
|
|
2071
|
+
/* @__PURE__ */ jsx8("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
2072
|
+
/* @__PURE__ */ jsx8("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
2073
|
+
/* @__PURE__ */ jsx8("polyline", { points: "21 15 16 10 5 21" })
|
|
2074
|
+
]
|
|
2075
|
+
}
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
function DocIcon() {
|
|
1753
2079
|
return /* @__PURE__ */ jsxs5(
|
|
1754
2080
|
"svg",
|
|
1755
2081
|
{
|
|
@@ -1762,6 +2088,36 @@ function CameraIcon() {
|
|
|
1762
2088
|
strokeLinecap: "round",
|
|
1763
2089
|
strokeLinejoin: "round",
|
|
1764
2090
|
"aria-hidden": "true",
|
|
2091
|
+
style: { color: "#666", flexShrink: 0 },
|
|
2092
|
+
children: [
|
|
2093
|
+
/* @__PURE__ */ jsx8("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
2094
|
+
/* @__PURE__ */ jsx8("polyline", { points: "14 2 14 8 20 8" }),
|
|
2095
|
+
/* @__PURE__ */ jsx8("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
2096
|
+
/* @__PURE__ */ jsx8("line", { x1: "16", y1: "17", x2: "8", y2: "17" }),
|
|
2097
|
+
/* @__PURE__ */ jsx8("polyline", { points: "10 9 9 9 8 9" })
|
|
2098
|
+
]
|
|
2099
|
+
}
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
function formatBytes(bytes) {
|
|
2103
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
2104
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2105
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
2106
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2107
|
+
}
|
|
2108
|
+
function CameraIcon() {
|
|
2109
|
+
return /* @__PURE__ */ jsxs5(
|
|
2110
|
+
"svg",
|
|
2111
|
+
{
|
|
2112
|
+
width: "18",
|
|
2113
|
+
height: "18",
|
|
2114
|
+
viewBox: "0 0 24 24",
|
|
2115
|
+
fill: "none",
|
|
2116
|
+
stroke: "currentColor",
|
|
2117
|
+
strokeWidth: "2",
|
|
2118
|
+
strokeLinecap: "round",
|
|
2119
|
+
strokeLinejoin: "round",
|
|
2120
|
+
"aria-hidden": "true",
|
|
1765
2121
|
children: [
|
|
1766
2122
|
/* @__PURE__ */ jsx8("path", { d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" }),
|
|
1767
2123
|
/* @__PURE__ */ jsx8("circle", { cx: "12", cy: "13", r: "4" })
|
|
@@ -2163,7 +2519,8 @@ function ChatWindow() {
|
|
|
2163
2519
|
import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2164
2520
|
function ChatWidgetInner({ identity }) {
|
|
2165
2521
|
const client = useCustomerHeroClient();
|
|
2166
|
-
const
|
|
2522
|
+
const { configLoaded, configError } = useChat();
|
|
2523
|
+
const prevIdentityRef = useRef5(void 0);
|
|
2167
2524
|
useEffect8(() => {
|
|
2168
2525
|
const key = identity ? JSON.stringify(identity) : void 0;
|
|
2169
2526
|
if (key !== prevIdentityRef.current) {
|
|
@@ -2173,6 +2530,7 @@ function ChatWidgetInner({ identity }) {
|
|
|
2173
2530
|
}
|
|
2174
2531
|
}
|
|
2175
2532
|
}, [identity, client]);
|
|
2533
|
+
if (!configLoaded || configError) return null;
|
|
2176
2534
|
return /* @__PURE__ */ jsxs7(Fragment6, { children: [
|
|
2177
2535
|
/* @__PURE__ */ jsx10(ChatBubble, {}),
|
|
2178
2536
|
/* @__PURE__ */ jsx10(ChatWindow, {})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@customerhero/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "React components for embedding the CustomerHero chat widget.",
|
|
6
6
|
"keywords": [
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"react": ">=18"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@customerhero/js": "^2.
|
|
61
|
+
"@customerhero/js": "^2.1.0",
|
|
62
62
|
"@testing-library/react": "^16.1.0",
|
|
63
63
|
"@types/react": "^19.0.0",
|
|
64
64
|
"@types/react-dom": "^19.0.0",
|