@customerhero/react 2.0.0 → 2.1.1
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 +531 -107
- package/dist/index.js +535 -109
- package/package.json +2 -2
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() {
|
|
@@ -711,9 +711,11 @@ function renderMarkdown(source, opts = {}) {
|
|
|
711
711
|
style: {
|
|
712
712
|
margin: "0 0 8px",
|
|
713
713
|
paddingLeft: 20,
|
|
714
|
-
lineHeight: 1.5
|
|
714
|
+
lineHeight: 1.5,
|
|
715
|
+
listStyleType: "disc",
|
|
716
|
+
listStylePosition: "outside"
|
|
715
717
|
},
|
|
716
|
-
children: block.lines.map((l, i) => /* @__PURE__ */ jsx4("li", { children: renderInline(l) }, i))
|
|
718
|
+
children: block.lines.map((l, i) => /* @__PURE__ */ jsx4("li", { style: { display: "list-item" }, children: renderInline(l) }, i))
|
|
717
719
|
},
|
|
718
720
|
key
|
|
719
721
|
);
|
|
@@ -724,9 +726,11 @@ function renderMarkdown(source, opts = {}) {
|
|
|
724
726
|
style: {
|
|
725
727
|
margin: "0 0 8px",
|
|
726
728
|
paddingLeft: 22,
|
|
727
|
-
lineHeight: 1.5
|
|
729
|
+
lineHeight: 1.5,
|
|
730
|
+
listStyleType: "decimal",
|
|
731
|
+
listStylePosition: "outside"
|
|
728
732
|
},
|
|
729
|
-
children: block.lines.map((l, i) => /* @__PURE__ */ jsx4("li", { children: renderInline(l) }, i))
|
|
733
|
+
children: block.lines.map((l, i) => /* @__PURE__ */ jsx4("li", { style: { display: "list-item" }, children: renderInline(l) }, i))
|
|
730
734
|
},
|
|
731
735
|
key
|
|
732
736
|
);
|
|
@@ -929,11 +933,11 @@ function MessageStatusPill({
|
|
|
929
933
|
const containerStyle = {
|
|
930
934
|
display: "flex",
|
|
931
935
|
alignItems: "center",
|
|
936
|
+
justifyContent: "flex-end",
|
|
932
937
|
gap: 4,
|
|
933
938
|
marginTop: 2,
|
|
934
939
|
fontSize: 11,
|
|
935
|
-
color: failed ? "#b91c1c" : "#888"
|
|
936
|
-
alignSelf: "flex-end"
|
|
940
|
+
color: failed ? "#b91c1c" : "#888"
|
|
937
941
|
};
|
|
938
942
|
const labelKey = status === "sending" ? "status_sending" : status === "sent" ? "status_sent" : "status_failed";
|
|
939
943
|
return /* @__PURE__ */ jsxs4("div", { style: containerStyle, "aria-live": "polite", children: [
|
|
@@ -1242,13 +1246,20 @@ function Message({
|
|
|
1242
1246
|
};
|
|
1243
1247
|
const linkColor = isUser ? "#ffffff" : config.primaryColor;
|
|
1244
1248
|
return /* @__PURE__ */ jsxs4(AnimatedMessage, { isUser, animate, reduced, children: [
|
|
1245
|
-
/* @__PURE__ */ jsx6(
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1249
|
+
/* @__PURE__ */ jsx6(
|
|
1250
|
+
"div",
|
|
1251
|
+
{
|
|
1252
|
+
style: bubbleStyle,
|
|
1253
|
+
"data-streaming-bubble": !isUser && message.streaming ? "true" : void 0,
|
|
1254
|
+
children: isUser ? message.content : /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1255
|
+
renderMarkdown(message.content, {
|
|
1256
|
+
sources: message.sources,
|
|
1257
|
+
linkColor
|
|
1258
|
+
}),
|
|
1259
|
+
message.streaming && /* @__PURE__ */ jsx6(StreamingCursor, { reduced })
|
|
1260
|
+
] })
|
|
1261
|
+
}
|
|
1262
|
+
),
|
|
1252
1263
|
isUser && message.status && /* @__PURE__ */ jsx6(MessageStatusPill, { status: message.status, t }),
|
|
1253
1264
|
!isUser && message.blocks?.map((block, i) => /* @__PURE__ */ jsx6(
|
|
1254
1265
|
BlockRenderer,
|
|
@@ -1333,16 +1344,51 @@ function ChatMessages() {
|
|
|
1333
1344
|
t
|
|
1334
1345
|
} = useChat();
|
|
1335
1346
|
const reduced = useReducedMotion();
|
|
1347
|
+
const containerRef = useRef3(null);
|
|
1336
1348
|
const messagesEndRef = useRef3(null);
|
|
1337
1349
|
const isFirstRender = useRef3(true);
|
|
1338
1350
|
const prevMessageCount = useRef3(0);
|
|
1351
|
+
const stickRef = useRef3(true);
|
|
1352
|
+
const suppressScrollRef = useRef3(false);
|
|
1353
|
+
const autoScrollTo = (top) => {
|
|
1354
|
+
const el = containerRef.current;
|
|
1355
|
+
if (!el) return;
|
|
1356
|
+
suppressScrollRef.current = true;
|
|
1357
|
+
el.scrollTop = top;
|
|
1358
|
+
requestAnimationFrame(() => {
|
|
1359
|
+
suppressScrollRef.current = false;
|
|
1360
|
+
});
|
|
1361
|
+
};
|
|
1362
|
+
const handleScroll = () => {
|
|
1363
|
+
if (suppressScrollRef.current) return;
|
|
1364
|
+
const el = containerRef.current;
|
|
1365
|
+
if (!el) return;
|
|
1366
|
+
const distFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
1367
|
+
stickRef.current = distFromBottom < 60;
|
|
1368
|
+
};
|
|
1339
1369
|
useEffect5(() => {
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1370
|
+
const el = containerRef.current;
|
|
1371
|
+
if (!el) return;
|
|
1372
|
+
const lastMsg2 = messages[messages.length - 1];
|
|
1373
|
+
if (messages.length > prevMessageCount.current && lastMsg2?.role === "user") {
|
|
1374
|
+
stickRef.current = true;
|
|
1345
1375
|
}
|
|
1376
|
+
if (!stickRef.current && !isFirstRender.current) {
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
const streamingBubble = el.querySelector(
|
|
1380
|
+
"[data-streaming-bubble='true']"
|
|
1381
|
+
);
|
|
1382
|
+
let target = el.scrollHeight - el.clientHeight;
|
|
1383
|
+
if (streamingBubble && streamingBubble.offsetHeight > el.clientHeight - 24) {
|
|
1384
|
+
const containerTop = el.getBoundingClientRect().top;
|
|
1385
|
+
const bubbleTop = streamingBubble.getBoundingClientRect().top;
|
|
1386
|
+
const TOP_GAP = 16;
|
|
1387
|
+
target = el.scrollTop + (bubbleTop - containerTop) - TOP_GAP;
|
|
1388
|
+
stickRef.current = false;
|
|
1389
|
+
}
|
|
1390
|
+
autoScrollTo(target);
|
|
1391
|
+
isFirstRender.current = false;
|
|
1346
1392
|
}, [messages, isLoading, reduced]);
|
|
1347
1393
|
const newStartIndex = isFirstRender.current ? messages.length : prevMessageCount.current;
|
|
1348
1394
|
useEffect5(() => {
|
|
@@ -1365,7 +1411,7 @@ function ChatMessages() {
|
|
|
1365
1411
|
};
|
|
1366
1412
|
const lastMsg = messages[messages.length - 1];
|
|
1367
1413
|
const waitingForFirstToken = isLoading && (lastMsg?.role !== "bot" || lastMsg.streaming !== true);
|
|
1368
|
-
return /* @__PURE__ */ jsxs4("div", { style: containerStyle, children: [
|
|
1414
|
+
return /* @__PURE__ */ jsxs4("div", { ref: containerRef, style: containerStyle, onScroll: handleScroll, children: [
|
|
1369
1415
|
messages.map((msg, i) => /* @__PURE__ */ jsx6(
|
|
1370
1416
|
Message,
|
|
1371
1417
|
{
|
|
@@ -1454,6 +1500,8 @@ function ChatSuggestions() {
|
|
|
1454
1500
|
// src/components/chat-input.tsx
|
|
1455
1501
|
import {
|
|
1456
1502
|
useEffect as useEffect6,
|
|
1503
|
+
useLayoutEffect,
|
|
1504
|
+
useRef as useRef4,
|
|
1457
1505
|
useState as useState6
|
|
1458
1506
|
} from "react";
|
|
1459
1507
|
import {
|
|
@@ -1463,6 +1511,16 @@ import {
|
|
|
1463
1511
|
} from "@customerhero/js";
|
|
1464
1512
|
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1465
1513
|
var MAX_ATTACHMENTS = 3;
|
|
1514
|
+
var ALLOWED_MIME_TYPES = [
|
|
1515
|
+
"image/png",
|
|
1516
|
+
"image/jpeg",
|
|
1517
|
+
"image/webp",
|
|
1518
|
+
"application/pdf"
|
|
1519
|
+
];
|
|
1520
|
+
var ACCEPT_ATTR = ALLOWED_MIME_TYPES.join(",");
|
|
1521
|
+
function isImageMime(mime) {
|
|
1522
|
+
return mime.startsWith("image/");
|
|
1523
|
+
}
|
|
1466
1524
|
function ChatInput() {
|
|
1467
1525
|
const {
|
|
1468
1526
|
sendMessage,
|
|
@@ -1477,9 +1535,27 @@ function ChatInput() {
|
|
|
1477
1535
|
const [value, setValue] = useState6("");
|
|
1478
1536
|
const [attachments, setAttachments] = useState6([]);
|
|
1479
1537
|
const [captureSupported, setCaptureSupported] = useState6(false);
|
|
1538
|
+
const [menuOpen, setMenuOpen] = useState6(false);
|
|
1539
|
+
const [dragActive, setDragActive] = useState6(false);
|
|
1540
|
+
const [transientError, setTransientError] = useState6(null);
|
|
1541
|
+
const fileInputRef = useRef4(null);
|
|
1542
|
+
const textInputRef = useRef4(null);
|
|
1543
|
+
const menuRef = useRef4(null);
|
|
1544
|
+
const menuButtonRef = useRef4(null);
|
|
1545
|
+
const dragCounterRef = useRef4(0);
|
|
1480
1546
|
useEffect6(() => {
|
|
1481
1547
|
setCaptureSupported(canCaptureScreenshot());
|
|
1482
1548
|
}, []);
|
|
1549
|
+
useEffect6(() => {
|
|
1550
|
+
const id = requestAnimationFrame(() => textInputRef.current?.focus());
|
|
1551
|
+
return () => cancelAnimationFrame(id);
|
|
1552
|
+
}, []);
|
|
1553
|
+
useLayoutEffect(() => {
|
|
1554
|
+
const el = textInputRef.current;
|
|
1555
|
+
if (!el) return;
|
|
1556
|
+
el.style.height = "auto";
|
|
1557
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
1558
|
+
}, [value]);
|
|
1483
1559
|
useEffect6(() => {
|
|
1484
1560
|
if (pendingPrefill === null) return;
|
|
1485
1561
|
const text = consumePendingPrefill();
|
|
@@ -1490,6 +1566,30 @@ function ChatInput() {
|
|
|
1490
1566
|
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1491
1567
|
};
|
|
1492
1568
|
}, []);
|
|
1569
|
+
useEffect6(() => {
|
|
1570
|
+
if (!menuOpen) return;
|
|
1571
|
+
const onClick = (e) => {
|
|
1572
|
+
const target = e.target;
|
|
1573
|
+
if (menuRef.current?.contains(target) || menuButtonRef.current?.contains(target)) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
setMenuOpen(false);
|
|
1577
|
+
};
|
|
1578
|
+
const onKey = (e) => {
|
|
1579
|
+
if (e.key === "Escape") setMenuOpen(false);
|
|
1580
|
+
};
|
|
1581
|
+
document.addEventListener("mousedown", onClick);
|
|
1582
|
+
document.addEventListener("keydown", onKey);
|
|
1583
|
+
return () => {
|
|
1584
|
+
document.removeEventListener("mousedown", onClick);
|
|
1585
|
+
document.removeEventListener("keydown", onKey);
|
|
1586
|
+
};
|
|
1587
|
+
}, [menuOpen]);
|
|
1588
|
+
useEffect6(() => {
|
|
1589
|
+
if (!transientError) return;
|
|
1590
|
+
const id = window.setTimeout(() => setTransientError(null), 4e3);
|
|
1591
|
+
return () => window.clearTimeout(id);
|
|
1592
|
+
}, [transientError]);
|
|
1493
1593
|
const updateAttachment = (id, patch) => {
|
|
1494
1594
|
setAttachments(
|
|
1495
1595
|
(current) => current.map(
|
|
@@ -1497,16 +1597,16 @@ function ChatInput() {
|
|
|
1497
1597
|
)
|
|
1498
1598
|
);
|
|
1499
1599
|
};
|
|
1500
|
-
const startUpload = async (blob) => {
|
|
1600
|
+
const startUpload = async (blob, filename) => {
|
|
1501
1601
|
if (attachments.length >= MAX_ATTACHMENTS) return;
|
|
1502
1602
|
const id = `att_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1503
1603
|
const previewUrl = URL.createObjectURL(blob);
|
|
1504
1604
|
setAttachments((current) => [
|
|
1505
1605
|
...current,
|
|
1506
|
-
{ id, status: "uploading", previewUrl, blob }
|
|
1606
|
+
{ id, status: "uploading", previewUrl, blob, filename }
|
|
1507
1607
|
]);
|
|
1508
1608
|
try {
|
|
1509
|
-
const { attachmentToken } = await uploadAttachment(blob);
|
|
1609
|
+
const { attachmentToken } = await uploadAttachment(blob, { filename });
|
|
1510
1610
|
updateAttachment(id, {
|
|
1511
1611
|
status: "ready",
|
|
1512
1612
|
token: attachmentToken
|
|
@@ -1515,7 +1615,31 @@ function ChatInput() {
|
|
|
1515
1615
|
updateAttachment(id, { status: "error" });
|
|
1516
1616
|
}
|
|
1517
1617
|
};
|
|
1618
|
+
const ingestFiles = (files) => {
|
|
1619
|
+
const remainingSlots = Math.max(0, MAX_ATTACHMENTS - attachments.length);
|
|
1620
|
+
if (remainingSlots === 0) return 0;
|
|
1621
|
+
let queued = 0;
|
|
1622
|
+
let rejectedAny = false;
|
|
1623
|
+
for (const f of Array.from(files)) {
|
|
1624
|
+
if (queued >= remainingSlots) break;
|
|
1625
|
+
const type = f.type;
|
|
1626
|
+
if (!ALLOWED_MIME_TYPES.includes(
|
|
1627
|
+
type
|
|
1628
|
+
)) {
|
|
1629
|
+
rejectedAny = true;
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
const filename = typeof f.name === "string" && f.name.length > 0 ? f.name : void 0;
|
|
1633
|
+
void startUpload(f, filename);
|
|
1634
|
+
queued += 1;
|
|
1635
|
+
}
|
|
1636
|
+
if (rejectedAny) {
|
|
1637
|
+
setTransientError(t("attachment_unsupported_type"));
|
|
1638
|
+
}
|
|
1639
|
+
return queued;
|
|
1640
|
+
};
|
|
1518
1641
|
const handleCapture = async () => {
|
|
1642
|
+
setMenuOpen(false);
|
|
1519
1643
|
try {
|
|
1520
1644
|
const blob = await captureScreenshot();
|
|
1521
1645
|
await startUpload(blob);
|
|
@@ -1523,6 +1647,17 @@ function ChatInput() {
|
|
|
1523
1647
|
if (e instanceof ScreenshotCancelled) return;
|
|
1524
1648
|
}
|
|
1525
1649
|
};
|
|
1650
|
+
const handlePickFile = () => {
|
|
1651
|
+
setMenuOpen(false);
|
|
1652
|
+
fileInputRef.current?.click();
|
|
1653
|
+
};
|
|
1654
|
+
const handleFileInputChange = (e) => {
|
|
1655
|
+
const files = e.target.files;
|
|
1656
|
+
if (files && files.length > 0) {
|
|
1657
|
+
ingestFiles(files);
|
|
1658
|
+
}
|
|
1659
|
+
e.target.value = "";
|
|
1660
|
+
};
|
|
1526
1661
|
const handleRemove = (id) => {
|
|
1527
1662
|
setAttachments((current) => {
|
|
1528
1663
|
const target = current.find((a) => a.id === id);
|
|
@@ -1530,6 +1665,45 @@ function ChatInput() {
|
|
|
1530
1665
|
return current.filter((a) => a.id !== id);
|
|
1531
1666
|
});
|
|
1532
1667
|
};
|
|
1668
|
+
const handlePaste = (e) => {
|
|
1669
|
+
const items = e.clipboardData?.items;
|
|
1670
|
+
if (!items || items.length === 0) return;
|
|
1671
|
+
const blobs = [];
|
|
1672
|
+
for (const item of Array.from(items)) {
|
|
1673
|
+
if (item.kind !== "file") continue;
|
|
1674
|
+
const blob = item.getAsFile();
|
|
1675
|
+
if (blob) blobs.push(blob);
|
|
1676
|
+
}
|
|
1677
|
+
if (blobs.length === 0) return;
|
|
1678
|
+
e.preventDefault();
|
|
1679
|
+
ingestFiles(blobs);
|
|
1680
|
+
};
|
|
1681
|
+
const handleDragEnter = (e) => {
|
|
1682
|
+
if (!hasFiles(e)) return;
|
|
1683
|
+
e.preventDefault();
|
|
1684
|
+
dragCounterRef.current += 1;
|
|
1685
|
+
setDragActive(true);
|
|
1686
|
+
};
|
|
1687
|
+
const handleDragOver = (e) => {
|
|
1688
|
+
if (!hasFiles(e)) return;
|
|
1689
|
+
e.preventDefault();
|
|
1690
|
+
};
|
|
1691
|
+
const handleDragLeave = (e) => {
|
|
1692
|
+
if (!hasFiles(e)) return;
|
|
1693
|
+
e.preventDefault();
|
|
1694
|
+
dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);
|
|
1695
|
+
if (dragCounterRef.current === 0) setDragActive(false);
|
|
1696
|
+
};
|
|
1697
|
+
const handleDrop = (e) => {
|
|
1698
|
+
if (!hasFiles(e)) return;
|
|
1699
|
+
e.preventDefault();
|
|
1700
|
+
dragCounterRef.current = 0;
|
|
1701
|
+
setDragActive(false);
|
|
1702
|
+
const files = e.dataTransfer?.files;
|
|
1703
|
+
if (files && files.length > 0) {
|
|
1704
|
+
ingestFiles(files);
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1533
1707
|
const readyTokens = attachments.filter(
|
|
1534
1708
|
(a) => a.status === "ready"
|
|
1535
1709
|
).map((a) => a.token);
|
|
@@ -1550,6 +1724,7 @@ function ChatInput() {
|
|
|
1550
1724
|
}
|
|
1551
1725
|
};
|
|
1552
1726
|
const containerStyle = {
|
|
1727
|
+
position: "relative",
|
|
1553
1728
|
padding: "12px 16px",
|
|
1554
1729
|
borderTop: "1px solid #eee",
|
|
1555
1730
|
display: "flex",
|
|
@@ -1558,19 +1733,27 @@ function ChatInput() {
|
|
|
1558
1733
|
};
|
|
1559
1734
|
const rowStyle = {
|
|
1560
1735
|
display: "flex",
|
|
1561
|
-
|
|
1736
|
+
// Anchor the icon buttons to the bottom of the row so a multi-line
|
|
1737
|
+
// textarea grows upward without dragging them along.
|
|
1738
|
+
alignItems: "flex-end",
|
|
1562
1739
|
gap: 8
|
|
1563
1740
|
};
|
|
1741
|
+
const TEXTAREA_MAX_HEIGHT = 140;
|
|
1564
1742
|
const inputStyle = {
|
|
1565
1743
|
flex: 1,
|
|
1566
1744
|
border: "1px solid #e0e0e0",
|
|
1567
|
-
borderRadius:
|
|
1745
|
+
borderRadius: 18,
|
|
1568
1746
|
padding: "10px 16px",
|
|
1569
1747
|
fontSize: 14,
|
|
1748
|
+
lineHeight: 1.4,
|
|
1570
1749
|
outline: "none",
|
|
1571
1750
|
background: "#fafafa",
|
|
1572
1751
|
color: config.textColor,
|
|
1573
|
-
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
1752
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
1753
|
+
resize: "none",
|
|
1754
|
+
overflowY: "auto",
|
|
1755
|
+
maxHeight: TEXTAREA_MAX_HEIGHT,
|
|
1756
|
+
boxSizing: "border-box"
|
|
1574
1757
|
};
|
|
1575
1758
|
const sendButtonStyle = {
|
|
1576
1759
|
width: 36,
|
|
@@ -1602,99 +1785,238 @@ function ChatInput() {
|
|
|
1602
1785
|
flexShrink: 0,
|
|
1603
1786
|
padding: 0
|
|
1604
1787
|
});
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1788
|
+
const menuStyle = {
|
|
1789
|
+
position: "absolute",
|
|
1790
|
+
bottom: "calc(100% + 4px)",
|
|
1791
|
+
left: 0,
|
|
1792
|
+
background: "white",
|
|
1793
|
+
border: "1px solid #e0e0e0",
|
|
1794
|
+
borderRadius: 8,
|
|
1795
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
|
|
1796
|
+
padding: 4,
|
|
1797
|
+
minWidth: 180,
|
|
1798
|
+
zIndex: 10,
|
|
1799
|
+
display: "flex",
|
|
1800
|
+
flexDirection: "column"
|
|
1801
|
+
};
|
|
1802
|
+
const menuItemStyle = {
|
|
1803
|
+
display: "flex",
|
|
1804
|
+
alignItems: "center",
|
|
1805
|
+
gap: 10,
|
|
1806
|
+
padding: "8px 12px",
|
|
1807
|
+
background: "transparent",
|
|
1808
|
+
border: "none",
|
|
1809
|
+
borderRadius: 4,
|
|
1810
|
+
cursor: "pointer",
|
|
1811
|
+
fontSize: 14,
|
|
1812
|
+
color: "#333",
|
|
1813
|
+
textAlign: "left",
|
|
1814
|
+
whiteSpace: "nowrap",
|
|
1815
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
1816
|
+
};
|
|
1817
|
+
const dropOverlayStyle = {
|
|
1818
|
+
position: "absolute",
|
|
1819
|
+
inset: 0,
|
|
1820
|
+
background: "rgba(255,255,255,0.92)",
|
|
1821
|
+
border: `2px dashed ${config.primaryColor}`,
|
|
1822
|
+
borderRadius: 4,
|
|
1823
|
+
display: "flex",
|
|
1824
|
+
alignItems: "center",
|
|
1825
|
+
justifyContent: "center",
|
|
1826
|
+
color: config.primaryColor,
|
|
1827
|
+
fontSize: 14,
|
|
1828
|
+
fontWeight: 500,
|
|
1829
|
+
pointerEvents: "none",
|
|
1830
|
+
zIndex: 11
|
|
1831
|
+
};
|
|
1832
|
+
const errorPillStyle = {
|
|
1833
|
+
alignSelf: "flex-start",
|
|
1834
|
+
background: "#fef2f2",
|
|
1835
|
+
color: "#b91c1c",
|
|
1836
|
+
border: "1px solid #fecaca",
|
|
1837
|
+
borderRadius: 12,
|
|
1838
|
+
padding: "4px 10px",
|
|
1839
|
+
fontSize: 12
|
|
1840
|
+
};
|
|
1841
|
+
const attachDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
|
|
1842
|
+
return /* @__PURE__ */ jsxs5(
|
|
1843
|
+
"div",
|
|
1844
|
+
{
|
|
1845
|
+
style: containerStyle,
|
|
1846
|
+
onDragEnter: handleDragEnter,
|
|
1847
|
+
onDragOver: handleDragOver,
|
|
1848
|
+
onDragLeave: handleDragLeave,
|
|
1849
|
+
onDrop: handleDrop,
|
|
1850
|
+
children: [
|
|
1851
|
+
dragActive && /* @__PURE__ */ jsx8("div", { style: dropOverlayStyle, "aria-hidden": "true", children: t("drop_files_here") }),
|
|
1852
|
+
transientError && /* @__PURE__ */ jsx8("div", { role: "alert", style: errorPillStyle, children: transientError }),
|
|
1853
|
+
attachments.length > 0 && /* @__PURE__ */ jsx8(
|
|
1854
|
+
"div",
|
|
1614
1855
|
{
|
|
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
|
-
|
|
1856
|
+
style: { display: "flex", gap: 8, flexWrap: "wrap" },
|
|
1857
|
+
"aria-label": "Attachments",
|
|
1858
|
+
children: attachments.map((a) => /* @__PURE__ */ jsx8(
|
|
1859
|
+
Thumbnail,
|
|
1860
|
+
{
|
|
1861
|
+
attachment: a,
|
|
1862
|
+
onRemove: () => handleRemove(a.id),
|
|
1863
|
+
t
|
|
1864
|
+
},
|
|
1865
|
+
a.id
|
|
1866
|
+
))
|
|
1867
|
+
}
|
|
1868
|
+
),
|
|
1869
|
+
/* @__PURE__ */ jsxs5("div", { style: rowStyle, children: [
|
|
1870
|
+
/* @__PURE__ */ jsxs5("div", { style: { position: "relative" }, children: [
|
|
1871
|
+
/* @__PURE__ */ jsx8(
|
|
1872
|
+
"button",
|
|
1873
|
+
{
|
|
1874
|
+
ref: menuButtonRef,
|
|
1875
|
+
type: "button",
|
|
1876
|
+
onClick: () => setMenuOpen((o) => !o),
|
|
1877
|
+
disabled: attachDisabled,
|
|
1878
|
+
style: iconButtonStyle(attachDisabled),
|
|
1879
|
+
"aria-label": t("attach_menu_open"),
|
|
1880
|
+
"aria-haspopup": "menu",
|
|
1881
|
+
"aria-expanded": menuOpen,
|
|
1882
|
+
title: t("attach_menu_open"),
|
|
1883
|
+
children: /* @__PURE__ */ jsx8(PaperclipIcon, {})
|
|
1884
|
+
}
|
|
1885
|
+
),
|
|
1886
|
+
menuOpen && /* @__PURE__ */ jsxs5("div", { ref: menuRef, role: "menu", style: menuStyle, children: [
|
|
1887
|
+
/* @__PURE__ */ jsxs5(
|
|
1888
|
+
"button",
|
|
1889
|
+
{
|
|
1890
|
+
type: "button",
|
|
1891
|
+
role: "menuitem",
|
|
1892
|
+
onClick: handlePickFile,
|
|
1893
|
+
style: menuItemStyle,
|
|
1894
|
+
onMouseEnter: (e) => {
|
|
1895
|
+
e.currentTarget.style.background = "#f5f5f5";
|
|
1896
|
+
},
|
|
1897
|
+
onMouseLeave: (e) => {
|
|
1898
|
+
e.currentTarget.style.background = "transparent";
|
|
1899
|
+
},
|
|
1900
|
+
children: [
|
|
1901
|
+
/* @__PURE__ */ jsx8(ImageIcon, {}),
|
|
1902
|
+
t("attach_photo")
|
|
1903
|
+
]
|
|
1904
|
+
}
|
|
1905
|
+
),
|
|
1906
|
+
captureSupported && /* @__PURE__ */ jsxs5(
|
|
1907
|
+
"button",
|
|
1908
|
+
{
|
|
1909
|
+
type: "button",
|
|
1910
|
+
role: "menuitem",
|
|
1911
|
+
onClick: handleCapture,
|
|
1912
|
+
style: menuItemStyle,
|
|
1913
|
+
onMouseEnter: (e) => {
|
|
1914
|
+
e.currentTarget.style.background = "#f5f5f5";
|
|
1915
|
+
},
|
|
1916
|
+
onMouseLeave: (e) => {
|
|
1917
|
+
e.currentTarget.style.background = "transparent";
|
|
1918
|
+
},
|
|
1919
|
+
children: [
|
|
1920
|
+
/* @__PURE__ */ jsx8(CameraIcon, {}),
|
|
1921
|
+
t("screenshot_capture")
|
|
1922
|
+
]
|
|
1923
|
+
}
|
|
1924
|
+
)
|
|
1925
|
+
] })
|
|
1926
|
+
] }),
|
|
1927
|
+
/* @__PURE__ */ jsx8(
|
|
1928
|
+
"input",
|
|
1664
1929
|
{
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1930
|
+
ref: fileInputRef,
|
|
1931
|
+
type: "file",
|
|
1932
|
+
accept: ACCEPT_ATTR,
|
|
1933
|
+
multiple: true,
|
|
1934
|
+
onChange: handleFileInputChange,
|
|
1935
|
+
style: { display: "none" },
|
|
1936
|
+
"aria-hidden": "true",
|
|
1937
|
+
tabIndex: -1
|
|
1938
|
+
}
|
|
1939
|
+
),
|
|
1940
|
+
/* @__PURE__ */ jsx8(
|
|
1941
|
+
"textarea",
|
|
1942
|
+
{
|
|
1943
|
+
ref: textInputRef,
|
|
1944
|
+
rows: 1,
|
|
1945
|
+
value,
|
|
1946
|
+
onChange: (e) => setValue(e.target.value),
|
|
1947
|
+
onKeyDown: handleKeyDown,
|
|
1948
|
+
onPaste: handlePaste,
|
|
1949
|
+
placeholder: config.placeholderText,
|
|
1950
|
+
style: inputStyle,
|
|
1951
|
+
disabled: isLoading
|
|
1952
|
+
}
|
|
1953
|
+
),
|
|
1954
|
+
/* @__PURE__ */ jsx8(
|
|
1955
|
+
"button",
|
|
1956
|
+
{
|
|
1957
|
+
onClick: handleSend,
|
|
1958
|
+
disabled: isLoading || !value.trim(),
|
|
1959
|
+
style: sendButtonStyle,
|
|
1960
|
+
"aria-label": t("send_message"),
|
|
1961
|
+
onMouseEnter: (e) => {
|
|
1962
|
+
if (!reduced && !isLoading)
|
|
1963
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
1964
|
+
},
|
|
1965
|
+
onMouseLeave: (e) => {
|
|
1966
|
+
if (!reduced) e.currentTarget.style.transform = "scale(1)";
|
|
1967
|
+
},
|
|
1968
|
+
children: /* @__PURE__ */ jsxs5(
|
|
1969
|
+
"svg",
|
|
1970
|
+
{
|
|
1971
|
+
width: "16",
|
|
1972
|
+
height: "16",
|
|
1973
|
+
viewBox: "0 0 24 24",
|
|
1974
|
+
fill: "none",
|
|
1975
|
+
stroke: "currentColor",
|
|
1976
|
+
strokeWidth: "2",
|
|
1977
|
+
strokeLinecap: "round",
|
|
1978
|
+
strokeLinejoin: "round",
|
|
1979
|
+
children: [
|
|
1980
|
+
/* @__PURE__ */ jsx8("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
1981
|
+
/* @__PURE__ */ jsx8("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
1982
|
+
]
|
|
1983
|
+
}
|
|
1984
|
+
)
|
|
1677
1985
|
}
|
|
1678
1986
|
)
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1987
|
+
] })
|
|
1988
|
+
]
|
|
1989
|
+
}
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
function hasFiles(e) {
|
|
1993
|
+
const types = e.dataTransfer?.types;
|
|
1994
|
+
if (!types) return false;
|
|
1995
|
+
for (let i = 0; i < types.length; i++) {
|
|
1996
|
+
if (types[i] === "Files") return true;
|
|
1997
|
+
}
|
|
1998
|
+
return false;
|
|
1683
1999
|
}
|
|
1684
2000
|
function Thumbnail({
|
|
1685
2001
|
attachment,
|
|
1686
2002
|
onRemove,
|
|
1687
2003
|
t
|
|
1688
2004
|
}) {
|
|
1689
|
-
const { status, previewUrl } = attachment;
|
|
2005
|
+
const { status, previewUrl, blob, filename } = attachment;
|
|
2006
|
+
const isImage = isImageMime(blob.type);
|
|
1690
2007
|
const wrap = {
|
|
1691
2008
|
position: "relative",
|
|
1692
|
-
width: 56,
|
|
2009
|
+
width: isImage ? 56 : 160,
|
|
1693
2010
|
height: 56,
|
|
1694
2011
|
borderRadius: 8,
|
|
1695
2012
|
overflow: "hidden",
|
|
1696
2013
|
border: status === "error" ? "2px solid #b91c1c" : "1px solid #e0e0e0",
|
|
1697
|
-
background: "#f5f5f5"
|
|
2014
|
+
background: "#f5f5f5",
|
|
2015
|
+
display: "flex",
|
|
2016
|
+
alignItems: "center",
|
|
2017
|
+
justifyContent: isImage ? "stretch" : "flex-start",
|
|
2018
|
+
gap: isImage ? 0 : 8,
|
|
2019
|
+
padding: isImage ? 0 : "0 8px"
|
|
1698
2020
|
};
|
|
1699
2021
|
const img = {
|
|
1700
2022
|
width: "100%",
|
|
@@ -1725,8 +2047,42 @@ function Thumbnail({
|
|
|
1725
2047
|
alignItems: "center",
|
|
1726
2048
|
justifyContent: "center"
|
|
1727
2049
|
};
|
|
2050
|
+
const docName = {
|
|
2051
|
+
fontSize: 12,
|
|
2052
|
+
fontWeight: 500,
|
|
2053
|
+
color: "#333",
|
|
2054
|
+
overflow: "hidden",
|
|
2055
|
+
textOverflow: "ellipsis",
|
|
2056
|
+
whiteSpace: "nowrap",
|
|
2057
|
+
maxWidth: 110,
|
|
2058
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
2059
|
+
};
|
|
2060
|
+
const docMeta = {
|
|
2061
|
+
fontSize: 11,
|
|
2062
|
+
color: "#888",
|
|
2063
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
2064
|
+
};
|
|
2065
|
+
const displayName = filename ?? (isImage ? "" : "Document");
|
|
2066
|
+
const sizeLabel = formatBytes(blob.size);
|
|
1728
2067
|
return /* @__PURE__ */ jsxs5("div", { style: wrap, children: [
|
|
1729
|
-
/* @__PURE__ */ jsx8("img", { src: previewUrl, alt: "", style: img }),
|
|
2068
|
+
isImage ? /* @__PURE__ */ jsx8("img", { src: previewUrl, alt: "", style: img }) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
2069
|
+
/* @__PURE__ */ jsx8(DocIcon, {}),
|
|
2070
|
+
/* @__PURE__ */ jsxs5(
|
|
2071
|
+
"div",
|
|
2072
|
+
{
|
|
2073
|
+
style: {
|
|
2074
|
+
display: "flex",
|
|
2075
|
+
flexDirection: "column",
|
|
2076
|
+
minWidth: 0,
|
|
2077
|
+
flex: 1
|
|
2078
|
+
},
|
|
2079
|
+
children: [
|
|
2080
|
+
/* @__PURE__ */ jsx8("span", { style: docName, title: displayName, children: displayName }),
|
|
2081
|
+
sizeLabel && /* @__PURE__ */ jsx8("span", { style: docMeta, children: sizeLabel })
|
|
2082
|
+
]
|
|
2083
|
+
}
|
|
2084
|
+
)
|
|
2085
|
+
] }),
|
|
1730
2086
|
status === "uploading" && /* @__PURE__ */ jsx8("div", { style: overlay, "aria-label": "Uploading", children: /* @__PURE__ */ jsx8(Spinner2, {}) }),
|
|
1731
2087
|
status === "error" && /* @__PURE__ */ jsx8(
|
|
1732
2088
|
"div",
|
|
@@ -1749,7 +2105,45 @@ function Thumbnail({
|
|
|
1749
2105
|
)
|
|
1750
2106
|
] });
|
|
1751
2107
|
}
|
|
1752
|
-
function
|
|
2108
|
+
function PaperclipIcon() {
|
|
2109
|
+
return /* @__PURE__ */ jsx8(
|
|
2110
|
+
"svg",
|
|
2111
|
+
{
|
|
2112
|
+
width: "20",
|
|
2113
|
+
height: "20",
|
|
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",
|
|
2121
|
+
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" })
|
|
2122
|
+
}
|
|
2123
|
+
);
|
|
2124
|
+
}
|
|
2125
|
+
function ImageIcon() {
|
|
2126
|
+
return /* @__PURE__ */ jsxs5(
|
|
2127
|
+
"svg",
|
|
2128
|
+
{
|
|
2129
|
+
width: "18",
|
|
2130
|
+
height: "18",
|
|
2131
|
+
viewBox: "0 0 24 24",
|
|
2132
|
+
fill: "none",
|
|
2133
|
+
stroke: "currentColor",
|
|
2134
|
+
strokeWidth: "2",
|
|
2135
|
+
strokeLinecap: "round",
|
|
2136
|
+
strokeLinejoin: "round",
|
|
2137
|
+
"aria-hidden": "true",
|
|
2138
|
+
children: [
|
|
2139
|
+
/* @__PURE__ */ jsx8("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
2140
|
+
/* @__PURE__ */ jsx8("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
2141
|
+
/* @__PURE__ */ jsx8("polyline", { points: "21 15 16 10 5 21" })
|
|
2142
|
+
]
|
|
2143
|
+
}
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
function DocIcon() {
|
|
1753
2147
|
return /* @__PURE__ */ jsxs5(
|
|
1754
2148
|
"svg",
|
|
1755
2149
|
{
|
|
@@ -1762,6 +2156,36 @@ function CameraIcon() {
|
|
|
1762
2156
|
strokeLinecap: "round",
|
|
1763
2157
|
strokeLinejoin: "round",
|
|
1764
2158
|
"aria-hidden": "true",
|
|
2159
|
+
style: { color: "#666", flexShrink: 0 },
|
|
2160
|
+
children: [
|
|
2161
|
+
/* @__PURE__ */ jsx8("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
2162
|
+
/* @__PURE__ */ jsx8("polyline", { points: "14 2 14 8 20 8" }),
|
|
2163
|
+
/* @__PURE__ */ jsx8("line", { x1: "16", y1: "13", x2: "8", y2: "13" }),
|
|
2164
|
+
/* @__PURE__ */ jsx8("line", { x1: "16", y1: "17", x2: "8", y2: "17" }),
|
|
2165
|
+
/* @__PURE__ */ jsx8("polyline", { points: "10 9 9 9 8 9" })
|
|
2166
|
+
]
|
|
2167
|
+
}
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
function formatBytes(bytes) {
|
|
2171
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
2172
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2173
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
2174
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2175
|
+
}
|
|
2176
|
+
function CameraIcon() {
|
|
2177
|
+
return /* @__PURE__ */ jsxs5(
|
|
2178
|
+
"svg",
|
|
2179
|
+
{
|
|
2180
|
+
width: "18",
|
|
2181
|
+
height: "18",
|
|
2182
|
+
viewBox: "0 0 24 24",
|
|
2183
|
+
fill: "none",
|
|
2184
|
+
stroke: "currentColor",
|
|
2185
|
+
strokeWidth: "2",
|
|
2186
|
+
strokeLinecap: "round",
|
|
2187
|
+
strokeLinejoin: "round",
|
|
2188
|
+
"aria-hidden": "true",
|
|
1765
2189
|
children: [
|
|
1766
2190
|
/* @__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
2191
|
/* @__PURE__ */ jsx8("circle", { cx: "12", cy: "13", r: "4" })
|
|
@@ -2163,7 +2587,8 @@ function ChatWindow() {
|
|
|
2163
2587
|
import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2164
2588
|
function ChatWidgetInner({ identity }) {
|
|
2165
2589
|
const client = useCustomerHeroClient();
|
|
2166
|
-
const
|
|
2590
|
+
const { configLoaded, configError } = useChat();
|
|
2591
|
+
const prevIdentityRef = useRef5(void 0);
|
|
2167
2592
|
useEffect8(() => {
|
|
2168
2593
|
const key = identity ? JSON.stringify(identity) : void 0;
|
|
2169
2594
|
if (key !== prevIdentityRef.current) {
|
|
@@ -2173,6 +2598,7 @@ function ChatWidgetInner({ identity }) {
|
|
|
2173
2598
|
}
|
|
2174
2599
|
}
|
|
2175
2600
|
}, [identity, client]);
|
|
2601
|
+
if (!configLoaded || configError) return null;
|
|
2176
2602
|
return /* @__PURE__ */ jsxs7(Fragment6, { children: [
|
|
2177
2603
|
/* @__PURE__ */ jsx10(ChatBubble, {}),
|
|
2178
2604
|
/* @__PURE__ */ jsx10(ChatWindow, {})
|