@cimplify/sdk 0.96.0 → 0.97.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/react.js +379 -66
- package/dist/react.mjs +380 -67
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/registry/chat-widget.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, isMultiDayService, serviceBillingMultiplier, PRODUCT_TYPE, BOUNDARY_KIND, resolveGallery, RENDER_HINT, DEFAULT_CDN_BASE_URL } from './chunk-RRBY35TJ.mjs';
|
|
3
3
|
export { getVariantDisplayName } from './chunk-RRBY35TJ.mjs';
|
|
4
|
-
import { messagePreview } from './chunk-U4MW6PYU.mjs';
|
|
4
|
+
import { messagePreview, splitGenUIContent } from './chunk-U4MW6PYU.mjs';
|
|
5
5
|
export { messagePreview, splitGenUIContent } from './chunk-U4MW6PYU.mjs';
|
|
6
6
|
import { SESSION_CLAIMED, SESSION_CLEARED, createCimplifyClient, MUTATION_ERROR } from './chunk-XMDLF5Y6.mjs';
|
|
7
7
|
import { generateIdempotencyKey, ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-VUSHG3HA.mjs';
|
|
@@ -3091,8 +3091,8 @@ function fmtScheduleWindow(start, end) {
|
|
|
3091
3091
|
const startStr = format(s, "EEE, MMM d \xB7 h:mm a");
|
|
3092
3092
|
const e = end ? parseISO(end) : null;
|
|
3093
3093
|
if (e && isValid(e)) {
|
|
3094
|
-
const
|
|
3095
|
-
return `${startStr} \u2013 ${format(e,
|
|
3094
|
+
const sameDay2 = s.toDateString() === e.toDateString();
|
|
3095
|
+
return `${startStr} \u2013 ${format(e, sameDay2 ? "h:mm a" : "EEE, MMM d \xB7 h:mm a")}`;
|
|
3096
3096
|
}
|
|
3097
3097
|
return startStr;
|
|
3098
3098
|
}
|
|
@@ -5883,6 +5883,33 @@ function useBootstrap(options = {}) {
|
|
|
5883
5883
|
const { data, isLoading, error, refetch } = bootstrapQuery(options);
|
|
5884
5884
|
return { bootstrap: data, isLoading, error, refetch };
|
|
5885
5885
|
}
|
|
5886
|
+
|
|
5887
|
+
// src/react/chat-model.ts
|
|
5888
|
+
var OPTIMISTIC_ID_PREFIX = "opt_";
|
|
5889
|
+
function optimisticId(clientId) {
|
|
5890
|
+
return `${OPTIMISTIC_ID_PREFIX}${clientId}`;
|
|
5891
|
+
}
|
|
5892
|
+
function isOptimistic(message) {
|
|
5893
|
+
return message.id.startsWith(OPTIMISTIC_ID_PREFIX);
|
|
5894
|
+
}
|
|
5895
|
+
function partitionMessages(messages) {
|
|
5896
|
+
const bubbles = [];
|
|
5897
|
+
const reactionsByTarget = /* @__PURE__ */ new Map();
|
|
5898
|
+
for (const message of messages) {
|
|
5899
|
+
if (message.content_type === "reaction") {
|
|
5900
|
+
const targetId = message.reply_to?.id;
|
|
5901
|
+
if (!targetId) continue;
|
|
5902
|
+
const list = reactionsByTarget.get(targetId);
|
|
5903
|
+
if (list) list.push(message);
|
|
5904
|
+
else reactionsByTarget.set(targetId, [message]);
|
|
5905
|
+
continue;
|
|
5906
|
+
}
|
|
5907
|
+
bubbles.push(message);
|
|
5908
|
+
}
|
|
5909
|
+
return { bubbles, reactionsByTarget };
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5912
|
+
// src/react/hooks/use-chat.ts
|
|
5886
5913
|
var POLL_INTERVAL_MS = 3e3;
|
|
5887
5914
|
var POLL_IDLE_INTERVAL_MS = 15e3;
|
|
5888
5915
|
var IDLE_TIMEOUT_MS = 6e4;
|
|
@@ -5897,10 +5924,10 @@ function mergeMessages(prev, incoming) {
|
|
|
5897
5924
|
(a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
|
|
5898
5925
|
);
|
|
5899
5926
|
const confirmed = new Set(
|
|
5900
|
-
merged.filter((m) => !m
|
|
5927
|
+
merged.filter((m) => !isOptimistic(m) && m.client_id).map((m) => m.client_id)
|
|
5901
5928
|
);
|
|
5902
5929
|
return merged.filter(
|
|
5903
|
-
(m) => !(m
|
|
5930
|
+
(m) => !(isOptimistic(m) && m.client_id && confirmed.has(m.client_id))
|
|
5904
5931
|
);
|
|
5905
5932
|
}
|
|
5906
5933
|
function useChat(options = {}) {
|
|
@@ -5963,7 +5990,7 @@ function useChat(options = {}) {
|
|
|
5963
5990
|
const lastConfirmedMessage = useCallback(() => {
|
|
5964
5991
|
const list = messagesRef.current;
|
|
5965
5992
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
5966
|
-
if (!list[i]
|
|
5993
|
+
if (!isOptimistic(list[i])) return list[i];
|
|
5967
5994
|
}
|
|
5968
5995
|
return void 0;
|
|
5969
5996
|
}, []);
|
|
@@ -6011,7 +6038,7 @@ function useChat(options = {}) {
|
|
|
6011
6038
|
const snapshotIds = new Set(snapshot.map((m) => m.id));
|
|
6012
6039
|
const tail = snapshot.length > 0 ? new Date(snapshot[snapshot.length - 1].created_at).getTime() : 0;
|
|
6013
6040
|
const extras = prev.filter(
|
|
6014
|
-
(m) => !snapshotIds.has(m.id) && (m
|
|
6041
|
+
(m) => !snapshotIds.has(m.id) && (isOptimistic(m) || new Date(m.created_at).getTime() > tail)
|
|
6015
6042
|
);
|
|
6016
6043
|
return mergeMessages(snapshot, extras);
|
|
6017
6044
|
});
|
|
@@ -6197,9 +6224,9 @@ function useChat(options = {}) {
|
|
|
6197
6224
|
}
|
|
6198
6225
|
const replyTo = opts?.replyTo;
|
|
6199
6226
|
const clientId = generateIdempotencyKey();
|
|
6200
|
-
const
|
|
6227
|
+
const localId = optimisticId(clientId);
|
|
6201
6228
|
const optimistic = {
|
|
6202
|
-
id:
|
|
6229
|
+
id: localId,
|
|
6203
6230
|
sender_type: "customer",
|
|
6204
6231
|
content: content.trim(),
|
|
6205
6232
|
content_type: attachments.length > 0 ? "image" : "text",
|
|
@@ -6229,14 +6256,14 @@ function useChat(options = {}) {
|
|
|
6229
6256
|
// The websocket may have already streamed the confirmed message
|
|
6230
6257
|
// in while the POST was in flight; drop the optimistic bubble
|
|
6231
6258
|
// instead of inserting a duplicate.
|
|
6232
|
-
prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !==
|
|
6259
|
+
prev.some((m) => m.id === confirmed.id) ? prev.filter((m) => m.id !== localId) : prev.map((m) => m.id === localId ? confirmed : m)
|
|
6233
6260
|
)
|
|
6234
6261
|
);
|
|
6235
6262
|
if (aiEnabledRef.current && !humanRequestedRef.current) {
|
|
6236
6263
|
startAwaitingReply();
|
|
6237
6264
|
}
|
|
6238
6265
|
} else {
|
|
6239
|
-
setMessages((prev) => prev.filter((m) => m.id !==
|
|
6266
|
+
setMessages((prev) => prev.filter((m) => m.id !== localId));
|
|
6240
6267
|
setError(result.error);
|
|
6241
6268
|
}
|
|
6242
6269
|
setIsSending(false);
|
|
@@ -6255,7 +6282,7 @@ function useChat(options = {}) {
|
|
|
6255
6282
|
);
|
|
6256
6283
|
const react = useCallback(
|
|
6257
6284
|
async (message, emoji) => {
|
|
6258
|
-
if (!client || message
|
|
6285
|
+
if (!client || isOptimistic(message)) return false;
|
|
6259
6286
|
const result = await client.support.reactToMessage(message.id, emoji);
|
|
6260
6287
|
if (result.ok) appendMessages([result.value]);
|
|
6261
6288
|
return result.ok;
|
|
@@ -10429,7 +10456,7 @@ function CartLineItemRow({
|
|
|
10429
10456
|
});
|
|
10430
10457
|
const hasComposite = item.compositeSelections && item.compositeSelections.length > 0;
|
|
10431
10458
|
const hasBundle = item.bundleSelections && item.bundleSelections.length > 0;
|
|
10432
|
-
const
|
|
10459
|
+
const isOptimistic2 = item.isOptimistic === true;
|
|
10433
10460
|
const lineTotal = parsePrice(unitPrice) * item.quantity;
|
|
10434
10461
|
const imageUrl = item.product.image_url;
|
|
10435
10462
|
const variantLabel2 = item.variant ? getVariantDisplayName(item.variant, item.product.name) : null;
|
|
@@ -10437,10 +10464,10 @@ function CartLineItemRow({
|
|
|
10437
10464
|
"div",
|
|
10438
10465
|
{
|
|
10439
10466
|
"data-cimplify-cart-item": true,
|
|
10440
|
-
"data-optimistic":
|
|
10467
|
+
"data-optimistic": isOptimistic2 ? "true" : void 0,
|
|
10441
10468
|
className: cn(
|
|
10442
10469
|
"group relative flex gap-3 py-4 border-b border-border last:border-b-0 transition-opacity",
|
|
10443
|
-
|
|
10470
|
+
isOptimistic2 && "opacity-60",
|
|
10444
10471
|
classNames?.item
|
|
10445
10472
|
),
|
|
10446
10473
|
children: [
|
|
@@ -10607,7 +10634,7 @@ function CartSummary({
|
|
|
10607
10634
|
tax,
|
|
10608
10635
|
total,
|
|
10609
10636
|
isEmpty,
|
|
10610
|
-
isOptimistic,
|
|
10637
|
+
isOptimistic: isOptimistic2,
|
|
10611
10638
|
pendingOpCount,
|
|
10612
10639
|
removeItem,
|
|
10613
10640
|
updateQuantity
|
|
@@ -10633,7 +10660,7 @@ function CartSummary({
|
|
|
10633
10660
|
"div",
|
|
10634
10661
|
{
|
|
10635
10662
|
"data-cimplify-cart-summary": true,
|
|
10636
|
-
"data-optimistic":
|
|
10663
|
+
"data-optimistic": isOptimistic2 ? "true" : void 0,
|
|
10637
10664
|
"data-pending-ops": pendingOpCount > 0 ? pendingOpCount : void 0,
|
|
10638
10665
|
className: cn(className, classNames?.root),
|
|
10639
10666
|
children: isEmpty ? /* @__PURE__ */ jsx("div", { "data-cimplify-cart-empty": true, className: classNames?.empty, children: /* @__PURE__ */ jsx("p", { children: emptyMessage }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -17119,25 +17146,12 @@ var ChatMarkdown = memo(function ChatMarkdown2({
|
|
|
17119
17146
|
}
|
|
17120
17147
|
return /* @__PURE__ */ jsx(SettledMarkdown, { text, className });
|
|
17121
17148
|
});
|
|
17122
|
-
|
|
17123
|
-
// src/react/chat-model.ts
|
|
17124
|
-
function partitionMessages(messages) {
|
|
17125
|
-
const bubbles = [];
|
|
17126
|
-
const reactionsByTarget = /* @__PURE__ */ new Map();
|
|
17127
|
-
for (const message of messages) {
|
|
17128
|
-
if (message.content_type === "reaction") {
|
|
17129
|
-
const targetId = message.reply_to?.id;
|
|
17130
|
-
if (!targetId) continue;
|
|
17131
|
-
const list = reactionsByTarget.get(targetId);
|
|
17132
|
-
if (list) list.push(message);
|
|
17133
|
-
else reactionsByTarget.set(targetId, [message]);
|
|
17134
|
-
continue;
|
|
17135
|
-
}
|
|
17136
|
-
bubbles.push(message);
|
|
17137
|
-
}
|
|
17138
|
-
return { bubbles, reactionsByTarget };
|
|
17139
|
-
}
|
|
17140
17149
|
var MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
17150
|
+
var KEEP_DISMISSED_KEY = "cimplify_keep_chat_dismissed";
|
|
17151
|
+
var AT_BOTTOM_SLACK_PX = 48;
|
|
17152
|
+
function proseOf(content) {
|
|
17153
|
+
return splitGenUIContent(content).filter((segment) => segment.kind === "prose").map((segment) => segment.text).join("\n\n").trim();
|
|
17154
|
+
}
|
|
17141
17155
|
function ChatWidget({
|
|
17142
17156
|
client: clientProp,
|
|
17143
17157
|
businessName = "Support",
|
|
@@ -17156,6 +17170,7 @@ function ChatWidget({
|
|
|
17156
17170
|
const [open, setOpen] = useState(defaultOpen);
|
|
17157
17171
|
const [readAt, setReadAt] = useState(null);
|
|
17158
17172
|
const [attachError, setAttachError] = useState(null);
|
|
17173
|
+
const [replyTo, setReplyTo] = useState(null);
|
|
17159
17174
|
const {
|
|
17160
17175
|
messages,
|
|
17161
17176
|
isLoading,
|
|
@@ -17164,11 +17179,15 @@ function ChatWidget({
|
|
|
17164
17179
|
seenAt,
|
|
17165
17180
|
error,
|
|
17166
17181
|
isActive,
|
|
17182
|
+
humanRequested,
|
|
17167
17183
|
claimedAs,
|
|
17168
17184
|
signOut,
|
|
17169
17185
|
send,
|
|
17170
17186
|
react,
|
|
17187
|
+
requestHuman,
|
|
17171
17188
|
startConversation,
|
|
17189
|
+
loadOlder,
|
|
17190
|
+
mayHaveOlder,
|
|
17172
17191
|
openIfExisting
|
|
17173
17192
|
} = useChat({ client, pollInterval });
|
|
17174
17193
|
const showTyping = isSending || awaitingReply;
|
|
@@ -17203,9 +17222,11 @@ function ChatWidget({
|
|
|
17203
17222
|
const handleSend = useCallback(
|
|
17204
17223
|
(content) => {
|
|
17205
17224
|
setAttachError(null);
|
|
17206
|
-
|
|
17225
|
+
const target = replyTo;
|
|
17226
|
+
setReplyTo(null);
|
|
17227
|
+
return send(content, target ? { replyTo: target } : void 0);
|
|
17207
17228
|
},
|
|
17208
|
-
[send]
|
|
17229
|
+
[send, replyTo]
|
|
17209
17230
|
);
|
|
17210
17231
|
const unread = open ? 0 : messages.filter(
|
|
17211
17232
|
(m) => m.sender_type !== "customer" && (!readAt || m.created_at > readAt)
|
|
@@ -17244,6 +17265,7 @@ function ChatWidget({
|
|
|
17244
17265
|
open && /* @__PURE__ */ jsx(
|
|
17245
17266
|
Panel,
|
|
17246
17267
|
{
|
|
17268
|
+
client,
|
|
17247
17269
|
businessName,
|
|
17248
17270
|
greeting,
|
|
17249
17271
|
subtitle,
|
|
@@ -17257,11 +17279,18 @@ function ChatWidget({
|
|
|
17257
17279
|
error: error ?? attachError,
|
|
17258
17280
|
isActive,
|
|
17259
17281
|
isLeft,
|
|
17282
|
+
humanRequested,
|
|
17283
|
+
mayHaveOlder,
|
|
17284
|
+
replyTo,
|
|
17260
17285
|
claimedAs,
|
|
17261
17286
|
onSignOut: () => void signOut(),
|
|
17262
17287
|
onClose: handleClose,
|
|
17263
17288
|
onSend: handleSend,
|
|
17264
17289
|
onReact: react,
|
|
17290
|
+
onReply: setReplyTo,
|
|
17291
|
+
onCancelReply: () => setReplyTo(null),
|
|
17292
|
+
onRequestHuman: () => void requestHuman(),
|
|
17293
|
+
onLoadOlder: () => void loadOlder(),
|
|
17265
17294
|
onAttach: attachPhoto,
|
|
17266
17295
|
onStartConversation: startConversation,
|
|
17267
17296
|
classNames
|
|
@@ -17296,7 +17325,7 @@ function Bubble({
|
|
|
17296
17325
|
{
|
|
17297
17326
|
className: cn(
|
|
17298
17327
|
"absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center",
|
|
17299
|
-
"rounded-full bg-
|
|
17328
|
+
"rounded-full bg-destructive text-[10px] font-bold text-white",
|
|
17300
17329
|
"ring-2 ring-background"
|
|
17301
17330
|
),
|
|
17302
17331
|
children: unread > 9 ? "9+" : unread
|
|
@@ -17307,6 +17336,7 @@ function Bubble({
|
|
|
17307
17336
|
);
|
|
17308
17337
|
}
|
|
17309
17338
|
function Panel({
|
|
17339
|
+
client,
|
|
17310
17340
|
businessName,
|
|
17311
17341
|
greeting,
|
|
17312
17342
|
subtitle,
|
|
@@ -17320,11 +17350,18 @@ function Panel({
|
|
|
17320
17350
|
error,
|
|
17321
17351
|
isActive,
|
|
17322
17352
|
isLeft,
|
|
17353
|
+
humanRequested,
|
|
17354
|
+
mayHaveOlder,
|
|
17355
|
+
replyTo,
|
|
17323
17356
|
claimedAs,
|
|
17324
17357
|
onSignOut,
|
|
17325
17358
|
onClose,
|
|
17326
17359
|
onSend,
|
|
17327
17360
|
onReact,
|
|
17361
|
+
onReply,
|
|
17362
|
+
onCancelReply,
|
|
17363
|
+
onRequestHuman,
|
|
17364
|
+
onLoadOlder,
|
|
17328
17365
|
onAttach,
|
|
17329
17366
|
onStartConversation,
|
|
17330
17367
|
classNames
|
|
@@ -17364,20 +17401,56 @@ function Panel({
|
|
|
17364
17401
|
MessageList,
|
|
17365
17402
|
{
|
|
17366
17403
|
messages,
|
|
17404
|
+
businessName,
|
|
17367
17405
|
isLoading,
|
|
17368
17406
|
isSending: isTyping,
|
|
17369
17407
|
seenAt,
|
|
17408
|
+
humanRequested,
|
|
17409
|
+
mayHaveOlder,
|
|
17370
17410
|
onReact,
|
|
17411
|
+
onReply,
|
|
17412
|
+
onRequestHuman,
|
|
17413
|
+
onLoadOlder,
|
|
17371
17414
|
className: classNames?.messages
|
|
17372
17415
|
}
|
|
17373
17416
|
),
|
|
17374
|
-
|
|
17417
|
+
isActive && !claimedAs && client && /* @__PURE__ */ jsx(KeepConversation, { client }),
|
|
17418
|
+
humanRequested && isActive && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t border-border bg-muted/50 px-4 py-2 text-[11px] text-muted-foreground", children: [
|
|
17419
|
+
/* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 flex-shrink-0 animate-pulse rounded-full bg-amber-500" }),
|
|
17420
|
+
"Waiting for ",
|
|
17421
|
+
businessName,
|
|
17422
|
+
" \u2014 usually replies in minutes"
|
|
17423
|
+
] }),
|
|
17424
|
+
error != null && /* @__PURE__ */ jsx("div", { className: "border-t border-destructive/20 bg-destructive/10 px-4 py-2 text-xs text-destructive", children: error.userMessage || "Something went wrong. Please try again." }),
|
|
17425
|
+
replyTo && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 border-t border-border bg-muted/50 px-4 py-2", children: [
|
|
17426
|
+
/* @__PURE__ */ jsx("span", { className: "w-[3px] shrink-0 self-stretch rounded-full bg-foreground/40" }),
|
|
17427
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
17428
|
+
/* @__PURE__ */ jsxs("div", { className: "text-[11px] font-semibold text-foreground", children: [
|
|
17429
|
+
"Replying to ",
|
|
17430
|
+
replyTo.sender_type === "customer" ? "yourself" : businessName
|
|
17431
|
+
] }),
|
|
17432
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: proseOf(replyTo.content) || "Photo" })
|
|
17433
|
+
] }),
|
|
17434
|
+
/* @__PURE__ */ jsx(
|
|
17435
|
+
"button",
|
|
17436
|
+
{
|
|
17437
|
+
type: "button",
|
|
17438
|
+
"aria-label": "Cancel reply",
|
|
17439
|
+
onClick: onCancelReply,
|
|
17440
|
+
className: "flex-shrink-0 p-0.5 text-muted-foreground hover:text-foreground",
|
|
17441
|
+
children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3.5 w-3.5" })
|
|
17442
|
+
}
|
|
17443
|
+
)
|
|
17444
|
+
] }),
|
|
17375
17445
|
/* @__PURE__ */ jsx(
|
|
17376
17446
|
ChatInput,
|
|
17377
17447
|
{
|
|
17378
17448
|
placeholder,
|
|
17379
17449
|
isSending,
|
|
17380
|
-
onSend: isActive ? onSend :
|
|
17450
|
+
onSend: isActive ? onSend : async (text) => {
|
|
17451
|
+
await onStartConversation(text);
|
|
17452
|
+
return true;
|
|
17453
|
+
},
|
|
17381
17454
|
onAttach,
|
|
17382
17455
|
className: classNames?.input
|
|
17383
17456
|
}
|
|
@@ -17406,10 +17479,10 @@ function Header({
|
|
|
17406
17479
|
className
|
|
17407
17480
|
),
|
|
17408
17481
|
children: [
|
|
17409
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-
|
|
17482
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px] bg-background/15", children: /* @__PURE__ */ jsx(ChatIcon, { className: "h-5 w-5" }) }),
|
|
17410
17483
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
17411
17484
|
/* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
|
|
17412
|
-
claimedAs ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-
|
|
17485
|
+
claimedAs ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 truncate text-xs text-background/60", children: [
|
|
17413
17486
|
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
17414
17487
|
"Chatting as ",
|
|
17415
17488
|
claimedAs
|
|
@@ -17420,11 +17493,11 @@ function Header({
|
|
|
17420
17493
|
{
|
|
17421
17494
|
type: "button",
|
|
17422
17495
|
onClick: onSignOut,
|
|
17423
|
-
className: "flex-shrink-0 underline underline-offset-2 hover:text-
|
|
17496
|
+
className: "flex-shrink-0 underline underline-offset-2 hover:text-background",
|
|
17424
17497
|
children: "Not you?"
|
|
17425
17498
|
}
|
|
17426
17499
|
)
|
|
17427
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-
|
|
17500
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-background/60", children: [
|
|
17428
17501
|
/* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
|
|
17429
17502
|
"Online"
|
|
17430
17503
|
] })
|
|
@@ -17437,7 +17510,7 @@ function Header({
|
|
|
17437
17510
|
"aria-label": "Close chat",
|
|
17438
17511
|
className: cn(
|
|
17439
17512
|
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg",
|
|
17440
|
-
"bg-
|
|
17513
|
+
"bg-background/10 text-background/70 transition-colors hover:bg-background/20 hover:text-background"
|
|
17441
17514
|
),
|
|
17442
17515
|
children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-[18px] w-[18px]" })
|
|
17443
17516
|
}
|
|
@@ -17500,32 +17573,73 @@ function lastSeenCustomerMessageId(bubbles, seenAt) {
|
|
|
17500
17573
|
}
|
|
17501
17574
|
return null;
|
|
17502
17575
|
}
|
|
17576
|
+
function dayLabel(at) {
|
|
17577
|
+
const date = new Date(at);
|
|
17578
|
+
const today = /* @__PURE__ */ new Date();
|
|
17579
|
+
if (date.toDateString() === today.toDateString()) return "Today";
|
|
17580
|
+
const yesterday = new Date(today);
|
|
17581
|
+
yesterday.setDate(today.getDate() - 1);
|
|
17582
|
+
if (date.toDateString() === yesterday.toDateString()) return "Yesterday";
|
|
17583
|
+
return date.toLocaleDateString([], { weekday: "short", day: "numeric", month: "short" });
|
|
17584
|
+
}
|
|
17503
17585
|
function MessageList({
|
|
17504
17586
|
messages,
|
|
17587
|
+
businessName,
|
|
17505
17588
|
isLoading,
|
|
17506
17589
|
isSending,
|
|
17507
17590
|
seenAt,
|
|
17591
|
+
humanRequested,
|
|
17592
|
+
mayHaveOlder,
|
|
17508
17593
|
onReact,
|
|
17594
|
+
onReply,
|
|
17595
|
+
onRequestHuman,
|
|
17596
|
+
onLoadOlder,
|
|
17509
17597
|
className
|
|
17510
17598
|
}) {
|
|
17511
|
-
const
|
|
17599
|
+
const scrolledUpRef = useRef(false);
|
|
17600
|
+
const partitioned = partitionMessages(messages);
|
|
17601
|
+
const bubbles = partitioned.bubbles.filter(
|
|
17602
|
+
(m) => m.sender_type === "system" || m.attachments.length > 0 || (m.sender_type === "customer" ? m.content.trim() !== "" : proseOf(m.content) !== "")
|
|
17603
|
+
);
|
|
17604
|
+
const reactionsByTarget = partitioned.reactionsByTarget;
|
|
17512
17605
|
const seenMessageId = lastSeenCustomerMessageId(bubbles, seenAt);
|
|
17606
|
+
const lastBubble = bubbles[bubbles.length - 1];
|
|
17607
|
+
const offerHuman = !humanRequested && lastBubble?.sender_type === "bot" && !isSending;
|
|
17608
|
+
const handleScroll = (event) => {
|
|
17609
|
+
const node = event.currentTarget;
|
|
17610
|
+
scrolledUpRef.current = node.scrollHeight - node.scrollTop - node.clientHeight >= AT_BOTTOM_SLACK_PX;
|
|
17611
|
+
};
|
|
17513
17612
|
return /* @__PURE__ */ jsxs(
|
|
17514
17613
|
"div",
|
|
17515
17614
|
{
|
|
17615
|
+
onScroll: handleScroll,
|
|
17516
17616
|
className: cn(
|
|
17517
17617
|
"flex flex-1 flex-col gap-1 overflow-y-auto bg-muted/50 px-5 py-5",
|
|
17518
17618
|
className
|
|
17519
17619
|
),
|
|
17520
17620
|
children: [
|
|
17521
17621
|
isLoading && messages.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
|
|
17622
|
+
mayHaveOlder && /* @__PURE__ */ jsx("div", { className: "flex justify-center pb-2", children: /* @__PURE__ */ jsx(
|
|
17623
|
+
"button",
|
|
17624
|
+
{
|
|
17625
|
+
type: "button",
|
|
17626
|
+
onClick: onLoadOlder,
|
|
17627
|
+
className: "rounded-full border border-border bg-background px-3 py-1 text-[11px] font-semibold text-muted-foreground hover:text-foreground",
|
|
17628
|
+
children: "Show earlier messages"
|
|
17629
|
+
}
|
|
17630
|
+
) }),
|
|
17522
17631
|
bubbles.map((msg, i) => {
|
|
17523
17632
|
const isCustomer = msg.sender_type === "customer";
|
|
17524
17633
|
const prev = bubbles[i - 1];
|
|
17525
17634
|
const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
|
|
17526
|
-
const
|
|
17635
|
+
const optimistic = isOptimistic(msg);
|
|
17527
17636
|
const reactions = reactionsByTarget.get(msg.id);
|
|
17637
|
+
const newDay = !prev || !sameDay(prev.created_at, msg.created_at);
|
|
17638
|
+
if (msg.sender_type === "system") {
|
|
17639
|
+
return /* @__PURE__ */ jsx("div", { className: "my-2 text-center text-[11px] font-medium text-muted-foreground", children: msg.content }, msg.id);
|
|
17640
|
+
}
|
|
17528
17641
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
17642
|
+
newDay && /* @__PURE__ */ jsx("div", { className: "my-2 text-center text-[10px] font-semibold tracking-wide text-muted-foreground", children: dayLabel(msg.created_at) }),
|
|
17529
17643
|
!sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
|
|
17530
17644
|
/* @__PURE__ */ jsxs(
|
|
17531
17645
|
"div",
|
|
@@ -17535,17 +17649,7 @@ function MessageList({
|
|
|
17535
17649
|
isCustomer ? "ml-auto" : "mr-auto"
|
|
17536
17650
|
),
|
|
17537
17651
|
children: [
|
|
17538
|
-
!isCustomer && !
|
|
17539
|
-
"button",
|
|
17540
|
-
{
|
|
17541
|
-
type: "button",
|
|
17542
|
-
"aria-label": `React ${emoji}`,
|
|
17543
|
-
onClick: () => void onReact(msg, emoji),
|
|
17544
|
-
className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
|
|
17545
|
-
children: emoji
|
|
17546
|
-
},
|
|
17547
|
-
emoji
|
|
17548
|
-
)) }),
|
|
17652
|
+
!isCustomer && !optimistic && /* @__PURE__ */ jsx(HoverRail, { message: msg, onReact, onReply }),
|
|
17549
17653
|
/* @__PURE__ */ jsxs(
|
|
17550
17654
|
"div",
|
|
17551
17655
|
{
|
|
@@ -17553,13 +17657,10 @@ function MessageList({
|
|
|
17553
17657
|
"rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
|
|
17554
17658
|
isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
|
|
17555
17659
|
isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
|
|
17556
|
-
|
|
17660
|
+
optimistic && "opacity-70"
|
|
17557
17661
|
),
|
|
17558
17662
|
children: [
|
|
17559
|
-
!
|
|
17560
|
-
/* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
|
|
17561
|
-
"AI"
|
|
17562
|
-
] }),
|
|
17663
|
+
!sameSenderAsPrev && /* @__PURE__ */ jsx(SenderChip, { sender: msg.sender_type, businessName }),
|
|
17563
17664
|
msg.reply_to && /* @__PURE__ */ jsxs(
|
|
17564
17665
|
"div",
|
|
17565
17666
|
{
|
|
@@ -17607,7 +17708,7 @@ function MessageList({
|
|
|
17607
17708
|
isCustomer ? msg.content && /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content }) : /* @__PURE__ */ jsx(
|
|
17608
17709
|
ChatMarkdown,
|
|
17609
17710
|
{
|
|
17610
|
-
text: msg.content,
|
|
17711
|
+
text: proseOf(msg.content),
|
|
17611
17712
|
className: "[&_p]:my-0 [&_p+p]:mt-1.5 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-4 [&_ol]:my-1 [&_ol]:list-decimal [&_ol]:pl-4 [&_li]:my-0.5 [&_h1]:text-sm [&_h1]:font-bold [&_h2]:text-sm [&_h2]:font-bold [&_h3]:text-sm [&_h3]:font-semibold [&_hr]:my-2 [&_table]:my-1 [&_table]:w-full [&_th]:pb-1 [&_th]:text-left [&_th]:text-[10px] [&_th]:font-semibold [&_th]:uppercase [&_td]:py-1 [&_td:last-child]:text-right [&_td:last-child]:font-semibold [&_td:last-child]:tabular-nums [&_a]:underline"
|
|
17612
17713
|
}
|
|
17613
17714
|
)
|
|
@@ -17629,11 +17730,20 @@ function MessageList({
|
|
|
17629
17730
|
] }, msg.id);
|
|
17630
17731
|
}),
|
|
17631
17732
|
isSending && /* @__PURE__ */ jsx("div", { className: "mr-auto mt-1", children: /* @__PURE__ */ jsx(TypingDots, {}) }),
|
|
17733
|
+
offerHuman && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
|
|
17734
|
+
"button",
|
|
17735
|
+
{
|
|
17736
|
+
type: "button",
|
|
17737
|
+
onClick: onRequestHuman,
|
|
17738
|
+
className: "rounded-full border border-border bg-background px-3 py-1.5 text-xs font-semibold text-foreground hover:border-foreground",
|
|
17739
|
+
children: "Talk to a person"
|
|
17740
|
+
}
|
|
17741
|
+
) }),
|
|
17632
17742
|
/* @__PURE__ */ jsx(
|
|
17633
17743
|
"div",
|
|
17634
17744
|
{
|
|
17635
17745
|
ref: (node) => {
|
|
17636
|
-
node
|
|
17746
|
+
if (node && !scrolledUpRef.current) node.scrollIntoView({ behavior: "smooth" });
|
|
17637
17747
|
}
|
|
17638
17748
|
},
|
|
17639
17749
|
`${messages.length}:${isSending}`
|
|
@@ -17642,6 +17752,190 @@ function MessageList({
|
|
|
17642
17752
|
}
|
|
17643
17753
|
);
|
|
17644
17754
|
}
|
|
17755
|
+
function sameDay(a, b) {
|
|
17756
|
+
return new Date(a).toDateString() === new Date(b).toDateString();
|
|
17757
|
+
}
|
|
17758
|
+
function SenderChip({
|
|
17759
|
+
sender,
|
|
17760
|
+
businessName
|
|
17761
|
+
}) {
|
|
17762
|
+
if (sender === "bot") {
|
|
17763
|
+
return /* @__PURE__ */ jsxs("span", { className: "mb-1 inline-flex items-center gap-1 rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold text-primary", children: [
|
|
17764
|
+
/* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
|
|
17765
|
+
"AI"
|
|
17766
|
+
] });
|
|
17767
|
+
}
|
|
17768
|
+
if (sender === "agent") {
|
|
17769
|
+
return /* @__PURE__ */ jsx("span", { className: "mb-1 inline-flex items-center rounded bg-muted px-1.5 py-0.5 text-[10px] font-semibold text-muted-foreground", children: businessName });
|
|
17770
|
+
}
|
|
17771
|
+
return null;
|
|
17772
|
+
}
|
|
17773
|
+
function HoverRail({
|
|
17774
|
+
message,
|
|
17775
|
+
onReact,
|
|
17776
|
+
onReply
|
|
17777
|
+
}) {
|
|
17778
|
+
return /* @__PURE__ */ jsxs("div", { className: "absolute -top-3 right-0 z-10 hidden gap-0.5 rounded-full border border-border bg-background px-1 py-0.5 shadow-sm group-hover:flex", children: [
|
|
17779
|
+
QUICK_REACTIONS.map((emoji) => /* @__PURE__ */ jsx(
|
|
17780
|
+
"button",
|
|
17781
|
+
{
|
|
17782
|
+
type: "button",
|
|
17783
|
+
"aria-label": `React ${emoji}`,
|
|
17784
|
+
onClick: () => void onReact(message, emoji),
|
|
17785
|
+
className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
|
|
17786
|
+
children: emoji
|
|
17787
|
+
},
|
|
17788
|
+
emoji
|
|
17789
|
+
)),
|
|
17790
|
+
/* @__PURE__ */ jsx(
|
|
17791
|
+
"button",
|
|
17792
|
+
{
|
|
17793
|
+
type: "button",
|
|
17794
|
+
"aria-label": "Reply",
|
|
17795
|
+
onClick: () => onReply(message),
|
|
17796
|
+
className: "flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
17797
|
+
children: /* @__PURE__ */ jsx(ReplyIcon, { className: "h-3.5 w-3.5" })
|
|
17798
|
+
}
|
|
17799
|
+
)
|
|
17800
|
+
] });
|
|
17801
|
+
}
|
|
17802
|
+
function readKeepDismissed() {
|
|
17803
|
+
try {
|
|
17804
|
+
return typeof window !== "undefined" && window.localStorage.getItem(KEEP_DISMISSED_KEY) === "1";
|
|
17805
|
+
} catch {
|
|
17806
|
+
return false;
|
|
17807
|
+
}
|
|
17808
|
+
}
|
|
17809
|
+
function writeKeepDismissed() {
|
|
17810
|
+
try {
|
|
17811
|
+
window.localStorage.setItem(KEEP_DISMISSED_KEY, "1");
|
|
17812
|
+
} catch {
|
|
17813
|
+
}
|
|
17814
|
+
}
|
|
17815
|
+
function KeepConversation({ client }) {
|
|
17816
|
+
const [step, setStep] = useState("row");
|
|
17817
|
+
const [dismissed, setDismissed] = useState(readKeepDismissed);
|
|
17818
|
+
const [contact, setContact] = useState("");
|
|
17819
|
+
const [code, setCode] = useState("");
|
|
17820
|
+
const [busy, setBusy] = useState(false);
|
|
17821
|
+
const [verifyError, setVerifyError] = useState(null);
|
|
17822
|
+
if (dismissed) return null;
|
|
17823
|
+
const contactType = contact.includes("@") ? "email" : "phone";
|
|
17824
|
+
const requestCode = async () => {
|
|
17825
|
+
const trimmed = contact.trim();
|
|
17826
|
+
if (!trimmed || busy) return;
|
|
17827
|
+
setBusy(true);
|
|
17828
|
+
setVerifyError(null);
|
|
17829
|
+
const result = await client.link.requestOtp({ contact: trimmed, contact_type: contactType });
|
|
17830
|
+
setBusy(false);
|
|
17831
|
+
if (result.ok) {
|
|
17832
|
+
setCode("");
|
|
17833
|
+
setStep("code");
|
|
17834
|
+
} else {
|
|
17835
|
+
setVerifyError(result.error.userMessage || "Couldn't send a code \u2014 try again.");
|
|
17836
|
+
}
|
|
17837
|
+
};
|
|
17838
|
+
const verify = async (value) => {
|
|
17839
|
+
if (value.length !== 6 || busy) return;
|
|
17840
|
+
setBusy(true);
|
|
17841
|
+
setVerifyError(null);
|
|
17842
|
+
const result = await client.link.verifyOtp({
|
|
17843
|
+
contact: contact.trim(),
|
|
17844
|
+
contact_type: contactType,
|
|
17845
|
+
otp_code: value
|
|
17846
|
+
});
|
|
17847
|
+
setBusy(false);
|
|
17848
|
+
if (!result.ok) {
|
|
17849
|
+
setCode("");
|
|
17850
|
+
setVerifyError(result.error.userMessage || "Invalid or expired code.");
|
|
17851
|
+
}
|
|
17852
|
+
};
|
|
17853
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-border bg-muted/50 px-4 py-2.5", children: [
|
|
17854
|
+
step === "row" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
17855
|
+
/* @__PURE__ */ jsxs(
|
|
17856
|
+
"button",
|
|
17857
|
+
{
|
|
17858
|
+
type: "button",
|
|
17859
|
+
onClick: () => setStep("contact"),
|
|
17860
|
+
className: "flex min-w-0 flex-1 flex-col text-left",
|
|
17861
|
+
children: [
|
|
17862
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-foreground", children: "Keep this conversation" }),
|
|
17863
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: "Verify your number and it follows you \u2014 any device" })
|
|
17864
|
+
]
|
|
17865
|
+
}
|
|
17866
|
+
),
|
|
17867
|
+
/* @__PURE__ */ jsx(
|
|
17868
|
+
"button",
|
|
17869
|
+
{
|
|
17870
|
+
type: "button",
|
|
17871
|
+
"aria-label": "Dismiss",
|
|
17872
|
+
onClick: () => {
|
|
17873
|
+
writeKeepDismissed();
|
|
17874
|
+
setDismissed(true);
|
|
17875
|
+
},
|
|
17876
|
+
className: "flex-shrink-0 p-1 text-muted-foreground hover:text-foreground",
|
|
17877
|
+
children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3.5 w-3.5" })
|
|
17878
|
+
}
|
|
17879
|
+
)
|
|
17880
|
+
] }),
|
|
17881
|
+
step === "contact" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
17882
|
+
/* @__PURE__ */ jsx(
|
|
17883
|
+
"input",
|
|
17884
|
+
{
|
|
17885
|
+
type: "text",
|
|
17886
|
+
inputMode: "email",
|
|
17887
|
+
value: contact,
|
|
17888
|
+
onChange: (e) => setContact(e.target.value),
|
|
17889
|
+
onKeyDown: (e) => e.key === "Enter" && void requestCode(),
|
|
17890
|
+
placeholder: "Phone or email",
|
|
17891
|
+
autoFocus: true,
|
|
17892
|
+
className: "h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-3 text-xs text-foreground outline-none placeholder:text-muted-foreground focus:border-foreground"
|
|
17893
|
+
}
|
|
17894
|
+
),
|
|
17895
|
+
/* @__PURE__ */ jsx(
|
|
17896
|
+
"button",
|
|
17897
|
+
{
|
|
17898
|
+
type: "button",
|
|
17899
|
+
onClick: () => void requestCode(),
|
|
17900
|
+
disabled: !contact.trim() || busy,
|
|
17901
|
+
className: "h-9 flex-shrink-0 rounded-lg bg-foreground px-3 text-xs font-semibold text-background disabled:opacity-50",
|
|
17902
|
+
children: busy ? "Sending\u2026" : "Send code"
|
|
17903
|
+
}
|
|
17904
|
+
)
|
|
17905
|
+
] }),
|
|
17906
|
+
step === "code" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
17907
|
+
/* @__PURE__ */ jsx(
|
|
17908
|
+
"input",
|
|
17909
|
+
{
|
|
17910
|
+
type: "text",
|
|
17911
|
+
inputMode: "numeric",
|
|
17912
|
+
autoComplete: "one-time-code",
|
|
17913
|
+
maxLength: 6,
|
|
17914
|
+
value: code,
|
|
17915
|
+
onChange: (e) => {
|
|
17916
|
+
const digits = e.target.value.replace(/\D/g, "").slice(0, 6);
|
|
17917
|
+
setCode(digits);
|
|
17918
|
+
if (digits.length === 6) void verify(digits);
|
|
17919
|
+
},
|
|
17920
|
+
placeholder: "6-digit code",
|
|
17921
|
+
autoFocus: true,
|
|
17922
|
+
className: "h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-3 text-xs tracking-[0.2em] text-foreground outline-none placeholder:tracking-normal placeholder:text-muted-foreground focus:border-foreground"
|
|
17923
|
+
}
|
|
17924
|
+
),
|
|
17925
|
+
/* @__PURE__ */ jsx(
|
|
17926
|
+
"button",
|
|
17927
|
+
{
|
|
17928
|
+
type: "button",
|
|
17929
|
+
onClick: () => void requestCode(),
|
|
17930
|
+
disabled: busy,
|
|
17931
|
+
className: "h-9 flex-shrink-0 rounded-lg px-2 text-[11px] font-medium text-muted-foreground hover:text-foreground disabled:opacity-50",
|
|
17932
|
+
children: "Resend"
|
|
17933
|
+
}
|
|
17934
|
+
)
|
|
17935
|
+
] }),
|
|
17936
|
+
verifyError && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-[11px] text-destructive", children: verifyError })
|
|
17937
|
+
] });
|
|
17938
|
+
}
|
|
17645
17939
|
function ChatInput({
|
|
17646
17940
|
placeholder,
|
|
17647
17941
|
isSending,
|
|
@@ -17664,14 +17958,15 @@ function ChatInput({
|
|
|
17664
17958
|
},
|
|
17665
17959
|
[onAttach]
|
|
17666
17960
|
);
|
|
17667
|
-
const handleSend = useCallback(() => {
|
|
17961
|
+
const handleSend = useCallback(async () => {
|
|
17668
17962
|
const trimmed = value.trim();
|
|
17669
17963
|
if (!trimmed || isSending) return;
|
|
17670
17964
|
setValue("");
|
|
17671
17965
|
if (textareaRef.current) {
|
|
17672
17966
|
textareaRef.current.style.height = "auto";
|
|
17673
17967
|
}
|
|
17674
|
-
onSend(trimmed);
|
|
17968
|
+
const sent = await onSend(trimmed);
|
|
17969
|
+
if (!sent) setValue((current) => current || trimmed);
|
|
17675
17970
|
}, [value, isSending, onSend]);
|
|
17676
17971
|
const handleKeyDown = useCallback(
|
|
17677
17972
|
(e) => {
|
|
@@ -17849,6 +18144,24 @@ function ImageIcon({ className }) {
|
|
|
17849
18144
|
}
|
|
17850
18145
|
);
|
|
17851
18146
|
}
|
|
18147
|
+
function ReplyIcon({ className }) {
|
|
18148
|
+
return /* @__PURE__ */ jsxs(
|
|
18149
|
+
"svg",
|
|
18150
|
+
{
|
|
18151
|
+
className,
|
|
18152
|
+
viewBox: "0 0 24 24",
|
|
18153
|
+
fill: "none",
|
|
18154
|
+
stroke: "currentColor",
|
|
18155
|
+
strokeWidth: 2,
|
|
18156
|
+
strokeLinecap: "round",
|
|
18157
|
+
strokeLinejoin: "round",
|
|
18158
|
+
children: [
|
|
18159
|
+
/* @__PURE__ */ jsx("polyline", { points: "9 17 4 12 9 7" }),
|
|
18160
|
+
/* @__PURE__ */ jsx("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
|
|
18161
|
+
]
|
|
18162
|
+
}
|
|
18163
|
+
);
|
|
18164
|
+
}
|
|
17852
18165
|
function SparkleIcon({ className }) {
|
|
17853
18166
|
return /* @__PURE__ */ jsx(
|
|
17854
18167
|
"svg",
|