@cimplify/sdk 0.95.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.d.mts +10 -2
- package/dist/react.d.ts +10 -2
- package/dist/react.js +392 -69
- package/dist/react.mjs +393 -70
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/registry/chat-widget.json +1 -1
- package/registry/date-slot-picker.json +1 -1
- package/registry/slot-picker.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;
|
|
@@ -9239,6 +9266,7 @@ function SlotPicker({
|
|
|
9239
9266
|
hideElapsedSlots = true,
|
|
9240
9267
|
minLeadMinutes = 0,
|
|
9241
9268
|
emptyMessage = "No available slots",
|
|
9269
|
+
contactPhone,
|
|
9242
9270
|
className,
|
|
9243
9271
|
classNames
|
|
9244
9272
|
}) {
|
|
@@ -9271,13 +9299,20 @@ function SlotPicker({
|
|
|
9271
9299
|
);
|
|
9272
9300
|
}
|
|
9273
9301
|
if (slots.length === 0) {
|
|
9274
|
-
return /* @__PURE__ */
|
|
9302
|
+
return /* @__PURE__ */ jsxs(
|
|
9275
9303
|
"div",
|
|
9276
9304
|
{
|
|
9277
9305
|
"data-cimplify-slot-picker": true,
|
|
9278
9306
|
"data-empty": true,
|
|
9279
9307
|
className: cn(className, classNames?.root, classNames?.empty),
|
|
9280
|
-
children:
|
|
9308
|
+
children: [
|
|
9309
|
+
/* @__PURE__ */ jsx("p", { children: emptyMessage }),
|
|
9310
|
+
contactPhone ? /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsxs("a", { href: `tel:${contactPhone.replace(/\s+/g, "")}`, children: [
|
|
9311
|
+
"Call ",
|
|
9312
|
+
contactPhone,
|
|
9313
|
+
" to book"
|
|
9314
|
+
] }) }) : null
|
|
9315
|
+
]
|
|
9281
9316
|
}
|
|
9282
9317
|
);
|
|
9283
9318
|
}
|
|
@@ -9404,6 +9439,7 @@ function DateSlotPicker({
|
|
|
9404
9439
|
durationValue,
|
|
9405
9440
|
hideElapsedSlots = true,
|
|
9406
9441
|
minLeadMinutes = 0,
|
|
9442
|
+
contactPhone,
|
|
9407
9443
|
className,
|
|
9408
9444
|
classNames
|
|
9409
9445
|
}) {
|
|
@@ -9553,7 +9589,8 @@ function DateSlotPicker({
|
|
|
9553
9589
|
durationUnit,
|
|
9554
9590
|
durationValue,
|
|
9555
9591
|
hideElapsedSlots,
|
|
9556
|
-
minLeadMinutes
|
|
9592
|
+
minLeadMinutes,
|
|
9593
|
+
contactPhone
|
|
9557
9594
|
}
|
|
9558
9595
|
) })
|
|
9559
9596
|
]
|
|
@@ -10419,7 +10456,7 @@ function CartLineItemRow({
|
|
|
10419
10456
|
});
|
|
10420
10457
|
const hasComposite = item.compositeSelections && item.compositeSelections.length > 0;
|
|
10421
10458
|
const hasBundle = item.bundleSelections && item.bundleSelections.length > 0;
|
|
10422
|
-
const
|
|
10459
|
+
const isOptimistic2 = item.isOptimistic === true;
|
|
10423
10460
|
const lineTotal = parsePrice(unitPrice) * item.quantity;
|
|
10424
10461
|
const imageUrl = item.product.image_url;
|
|
10425
10462
|
const variantLabel2 = item.variant ? getVariantDisplayName(item.variant, item.product.name) : null;
|
|
@@ -10427,10 +10464,10 @@ function CartLineItemRow({
|
|
|
10427
10464
|
"div",
|
|
10428
10465
|
{
|
|
10429
10466
|
"data-cimplify-cart-item": true,
|
|
10430
|
-
"data-optimistic":
|
|
10467
|
+
"data-optimistic": isOptimistic2 ? "true" : void 0,
|
|
10431
10468
|
className: cn(
|
|
10432
10469
|
"group relative flex gap-3 py-4 border-b border-border last:border-b-0 transition-opacity",
|
|
10433
|
-
|
|
10470
|
+
isOptimistic2 && "opacity-60",
|
|
10434
10471
|
classNames?.item
|
|
10435
10472
|
),
|
|
10436
10473
|
children: [
|
|
@@ -10597,7 +10634,7 @@ function CartSummary({
|
|
|
10597
10634
|
tax,
|
|
10598
10635
|
total,
|
|
10599
10636
|
isEmpty,
|
|
10600
|
-
isOptimistic,
|
|
10637
|
+
isOptimistic: isOptimistic2,
|
|
10601
10638
|
pendingOpCount,
|
|
10602
10639
|
removeItem,
|
|
10603
10640
|
updateQuantity
|
|
@@ -10623,7 +10660,7 @@ function CartSummary({
|
|
|
10623
10660
|
"div",
|
|
10624
10661
|
{
|
|
10625
10662
|
"data-cimplify-cart-summary": true,
|
|
10626
|
-
"data-optimistic":
|
|
10663
|
+
"data-optimistic": isOptimistic2 ? "true" : void 0,
|
|
10627
10664
|
"data-pending-ops": pendingOpCount > 0 ? pendingOpCount : void 0,
|
|
10628
10665
|
className: cn(className, classNames?.root),
|
|
10629
10666
|
children: isEmpty ? /* @__PURE__ */ jsx("div", { "data-cimplify-cart-empty": true, className: classNames?.empty, children: /* @__PURE__ */ jsx("p", { children: emptyMessage }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -17109,25 +17146,12 @@ var ChatMarkdown = memo(function ChatMarkdown2({
|
|
|
17109
17146
|
}
|
|
17110
17147
|
return /* @__PURE__ */ jsx(SettledMarkdown, { text, className });
|
|
17111
17148
|
});
|
|
17112
|
-
|
|
17113
|
-
// src/react/chat-model.ts
|
|
17114
|
-
function partitionMessages(messages) {
|
|
17115
|
-
const bubbles = [];
|
|
17116
|
-
const reactionsByTarget = /* @__PURE__ */ new Map();
|
|
17117
|
-
for (const message of messages) {
|
|
17118
|
-
if (message.content_type === "reaction") {
|
|
17119
|
-
const targetId = message.reply_to?.id;
|
|
17120
|
-
if (!targetId) continue;
|
|
17121
|
-
const list = reactionsByTarget.get(targetId);
|
|
17122
|
-
if (list) list.push(message);
|
|
17123
|
-
else reactionsByTarget.set(targetId, [message]);
|
|
17124
|
-
continue;
|
|
17125
|
-
}
|
|
17126
|
-
bubbles.push(message);
|
|
17127
|
-
}
|
|
17128
|
-
return { bubbles, reactionsByTarget };
|
|
17129
|
-
}
|
|
17130
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
|
+
}
|
|
17131
17155
|
function ChatWidget({
|
|
17132
17156
|
client: clientProp,
|
|
17133
17157
|
businessName = "Support",
|
|
@@ -17146,6 +17170,7 @@ function ChatWidget({
|
|
|
17146
17170
|
const [open, setOpen] = useState(defaultOpen);
|
|
17147
17171
|
const [readAt, setReadAt] = useState(null);
|
|
17148
17172
|
const [attachError, setAttachError] = useState(null);
|
|
17173
|
+
const [replyTo, setReplyTo] = useState(null);
|
|
17149
17174
|
const {
|
|
17150
17175
|
messages,
|
|
17151
17176
|
isLoading,
|
|
@@ -17154,11 +17179,15 @@ function ChatWidget({
|
|
|
17154
17179
|
seenAt,
|
|
17155
17180
|
error,
|
|
17156
17181
|
isActive,
|
|
17182
|
+
humanRequested,
|
|
17157
17183
|
claimedAs,
|
|
17158
17184
|
signOut,
|
|
17159
17185
|
send,
|
|
17160
17186
|
react,
|
|
17187
|
+
requestHuman,
|
|
17161
17188
|
startConversation,
|
|
17189
|
+
loadOlder,
|
|
17190
|
+
mayHaveOlder,
|
|
17162
17191
|
openIfExisting
|
|
17163
17192
|
} = useChat({ client, pollInterval });
|
|
17164
17193
|
const showTyping = isSending || awaitingReply;
|
|
@@ -17193,9 +17222,11 @@ function ChatWidget({
|
|
|
17193
17222
|
const handleSend = useCallback(
|
|
17194
17223
|
(content) => {
|
|
17195
17224
|
setAttachError(null);
|
|
17196
|
-
|
|
17225
|
+
const target = replyTo;
|
|
17226
|
+
setReplyTo(null);
|
|
17227
|
+
return send(content, target ? { replyTo: target } : void 0);
|
|
17197
17228
|
},
|
|
17198
|
-
[send]
|
|
17229
|
+
[send, replyTo]
|
|
17199
17230
|
);
|
|
17200
17231
|
const unread = open ? 0 : messages.filter(
|
|
17201
17232
|
(m) => m.sender_type !== "customer" && (!readAt || m.created_at > readAt)
|
|
@@ -17234,6 +17265,7 @@ function ChatWidget({
|
|
|
17234
17265
|
open && /* @__PURE__ */ jsx(
|
|
17235
17266
|
Panel,
|
|
17236
17267
|
{
|
|
17268
|
+
client,
|
|
17237
17269
|
businessName,
|
|
17238
17270
|
greeting,
|
|
17239
17271
|
subtitle,
|
|
@@ -17247,11 +17279,18 @@ function ChatWidget({
|
|
|
17247
17279
|
error: error ?? attachError,
|
|
17248
17280
|
isActive,
|
|
17249
17281
|
isLeft,
|
|
17282
|
+
humanRequested,
|
|
17283
|
+
mayHaveOlder,
|
|
17284
|
+
replyTo,
|
|
17250
17285
|
claimedAs,
|
|
17251
17286
|
onSignOut: () => void signOut(),
|
|
17252
17287
|
onClose: handleClose,
|
|
17253
17288
|
onSend: handleSend,
|
|
17254
17289
|
onReact: react,
|
|
17290
|
+
onReply: setReplyTo,
|
|
17291
|
+
onCancelReply: () => setReplyTo(null),
|
|
17292
|
+
onRequestHuman: () => void requestHuman(),
|
|
17293
|
+
onLoadOlder: () => void loadOlder(),
|
|
17255
17294
|
onAttach: attachPhoto,
|
|
17256
17295
|
onStartConversation: startConversation,
|
|
17257
17296
|
classNames
|
|
@@ -17286,7 +17325,7 @@ function Bubble({
|
|
|
17286
17325
|
{
|
|
17287
17326
|
className: cn(
|
|
17288
17327
|
"absolute -right-0.5 -top-0.5 flex h-5 w-5 items-center justify-center",
|
|
17289
|
-
"rounded-full bg-
|
|
17328
|
+
"rounded-full bg-destructive text-[10px] font-bold text-white",
|
|
17290
17329
|
"ring-2 ring-background"
|
|
17291
17330
|
),
|
|
17292
17331
|
children: unread > 9 ? "9+" : unread
|
|
@@ -17297,6 +17336,7 @@ function Bubble({
|
|
|
17297
17336
|
);
|
|
17298
17337
|
}
|
|
17299
17338
|
function Panel({
|
|
17339
|
+
client,
|
|
17300
17340
|
businessName,
|
|
17301
17341
|
greeting,
|
|
17302
17342
|
subtitle,
|
|
@@ -17310,11 +17350,18 @@ function Panel({
|
|
|
17310
17350
|
error,
|
|
17311
17351
|
isActive,
|
|
17312
17352
|
isLeft,
|
|
17353
|
+
humanRequested,
|
|
17354
|
+
mayHaveOlder,
|
|
17355
|
+
replyTo,
|
|
17313
17356
|
claimedAs,
|
|
17314
17357
|
onSignOut,
|
|
17315
17358
|
onClose,
|
|
17316
17359
|
onSend,
|
|
17317
17360
|
onReact,
|
|
17361
|
+
onReply,
|
|
17362
|
+
onCancelReply,
|
|
17363
|
+
onRequestHuman,
|
|
17364
|
+
onLoadOlder,
|
|
17318
17365
|
onAttach,
|
|
17319
17366
|
onStartConversation,
|
|
17320
17367
|
classNames
|
|
@@ -17354,20 +17401,56 @@ function Panel({
|
|
|
17354
17401
|
MessageList,
|
|
17355
17402
|
{
|
|
17356
17403
|
messages,
|
|
17404
|
+
businessName,
|
|
17357
17405
|
isLoading,
|
|
17358
17406
|
isSending: isTyping,
|
|
17359
17407
|
seenAt,
|
|
17408
|
+
humanRequested,
|
|
17409
|
+
mayHaveOlder,
|
|
17360
17410
|
onReact,
|
|
17411
|
+
onReply,
|
|
17412
|
+
onRequestHuman,
|
|
17413
|
+
onLoadOlder,
|
|
17361
17414
|
className: classNames?.messages
|
|
17362
17415
|
}
|
|
17363
17416
|
),
|
|
17364
|
-
|
|
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
|
+
] }),
|
|
17365
17445
|
/* @__PURE__ */ jsx(
|
|
17366
17446
|
ChatInput,
|
|
17367
17447
|
{
|
|
17368
17448
|
placeholder,
|
|
17369
17449
|
isSending,
|
|
17370
|
-
onSend: isActive ? onSend :
|
|
17450
|
+
onSend: isActive ? onSend : async (text) => {
|
|
17451
|
+
await onStartConversation(text);
|
|
17452
|
+
return true;
|
|
17453
|
+
},
|
|
17371
17454
|
onAttach,
|
|
17372
17455
|
className: classNames?.input
|
|
17373
17456
|
}
|
|
@@ -17396,10 +17479,10 @@ function Header({
|
|
|
17396
17479
|
className
|
|
17397
17480
|
),
|
|
17398
17481
|
children: [
|
|
17399
|
-
/* @__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" }) }),
|
|
17400
17483
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
17401
17484
|
/* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-semibold leading-tight", children: businessName }),
|
|
17402
|
-
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: [
|
|
17403
17486
|
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
17404
17487
|
"Chatting as ",
|
|
17405
17488
|
claimedAs
|
|
@@ -17410,11 +17493,11 @@ function Header({
|
|
|
17410
17493
|
{
|
|
17411
17494
|
type: "button",
|
|
17412
17495
|
onClick: onSignOut,
|
|
17413
|
-
className: "flex-shrink-0 underline underline-offset-2 hover:text-
|
|
17496
|
+
className: "flex-shrink-0 underline underline-offset-2 hover:text-background",
|
|
17414
17497
|
children: "Not you?"
|
|
17415
17498
|
}
|
|
17416
17499
|
)
|
|
17417
|
-
] }) : /* @__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: [
|
|
17418
17501
|
/* @__PURE__ */ jsx("span", { className: "h-[7px] w-[7px] flex-shrink-0 rounded-full bg-emerald-400" }),
|
|
17419
17502
|
"Online"
|
|
17420
17503
|
] })
|
|
@@ -17427,7 +17510,7 @@ function Header({
|
|
|
17427
17510
|
"aria-label": "Close chat",
|
|
17428
17511
|
className: cn(
|
|
17429
17512
|
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg",
|
|
17430
|
-
"bg-
|
|
17513
|
+
"bg-background/10 text-background/70 transition-colors hover:bg-background/20 hover:text-background"
|
|
17431
17514
|
),
|
|
17432
17515
|
children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-[18px] w-[18px]" })
|
|
17433
17516
|
}
|
|
@@ -17490,32 +17573,73 @@ function lastSeenCustomerMessageId(bubbles, seenAt) {
|
|
|
17490
17573
|
}
|
|
17491
17574
|
return null;
|
|
17492
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
|
+
}
|
|
17493
17585
|
function MessageList({
|
|
17494
17586
|
messages,
|
|
17587
|
+
businessName,
|
|
17495
17588
|
isLoading,
|
|
17496
17589
|
isSending,
|
|
17497
17590
|
seenAt,
|
|
17591
|
+
humanRequested,
|
|
17592
|
+
mayHaveOlder,
|
|
17498
17593
|
onReact,
|
|
17594
|
+
onReply,
|
|
17595
|
+
onRequestHuman,
|
|
17596
|
+
onLoadOlder,
|
|
17499
17597
|
className
|
|
17500
17598
|
}) {
|
|
17501
|
-
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;
|
|
17502
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
|
+
};
|
|
17503
17612
|
return /* @__PURE__ */ jsxs(
|
|
17504
17613
|
"div",
|
|
17505
17614
|
{
|
|
17615
|
+
onScroll: handleScroll,
|
|
17506
17616
|
className: cn(
|
|
17507
17617
|
"flex flex-1 flex-col gap-1 overflow-y-auto bg-muted/50 px-5 py-5",
|
|
17508
17618
|
className
|
|
17509
17619
|
),
|
|
17510
17620
|
children: [
|
|
17511
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
|
+
) }),
|
|
17512
17631
|
bubbles.map((msg, i) => {
|
|
17513
17632
|
const isCustomer = msg.sender_type === "customer";
|
|
17514
17633
|
const prev = bubbles[i - 1];
|
|
17515
17634
|
const sameSenderAsPrev = prev?.sender_type === msg.sender_type;
|
|
17516
|
-
const
|
|
17635
|
+
const optimistic = isOptimistic(msg);
|
|
17517
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
|
+
}
|
|
17518
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) }),
|
|
17519
17643
|
!sameSenderAsPrev && i > 0 && /* @__PURE__ */ jsx("div", { className: "h-3" }),
|
|
17520
17644
|
/* @__PURE__ */ jsxs(
|
|
17521
17645
|
"div",
|
|
@@ -17525,17 +17649,7 @@ function MessageList({
|
|
|
17525
17649
|
isCustomer ? "ml-auto" : "mr-auto"
|
|
17526
17650
|
),
|
|
17527
17651
|
children: [
|
|
17528
|
-
!isCustomer && !
|
|
17529
|
-
"button",
|
|
17530
|
-
{
|
|
17531
|
-
type: "button",
|
|
17532
|
-
"aria-label": `React ${emoji}`,
|
|
17533
|
-
onClick: () => void onReact(msg, emoji),
|
|
17534
|
-
className: "flex h-6 w-6 items-center justify-center rounded-full text-[13px] hover:bg-muted",
|
|
17535
|
-
children: emoji
|
|
17536
|
-
},
|
|
17537
|
-
emoji
|
|
17538
|
-
)) }),
|
|
17652
|
+
!isCustomer && !optimistic && /* @__PURE__ */ jsx(HoverRail, { message: msg, onReact, onReply }),
|
|
17539
17653
|
/* @__PURE__ */ jsxs(
|
|
17540
17654
|
"div",
|
|
17541
17655
|
{
|
|
@@ -17543,13 +17657,10 @@ function MessageList({
|
|
|
17543
17657
|
"rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
|
|
17544
17658
|
isCustomer ? "bg-foreground text-background" : "border border-border bg-background text-foreground",
|
|
17545
17659
|
isCustomer ? sameSenderAsPrev ? "rounded-tr" : "rounded-br" : sameSenderAsPrev ? "rounded-tl" : "rounded-bl",
|
|
17546
|
-
|
|
17660
|
+
optimistic && "opacity-70"
|
|
17547
17661
|
),
|
|
17548
17662
|
children: [
|
|
17549
|
-
!
|
|
17550
|
-
/* @__PURE__ */ jsx(SparkleIcon, { className: "h-[11px] w-[11px]" }),
|
|
17551
|
-
"AI"
|
|
17552
|
-
] }),
|
|
17663
|
+
!sameSenderAsPrev && /* @__PURE__ */ jsx(SenderChip, { sender: msg.sender_type, businessName }),
|
|
17553
17664
|
msg.reply_to && /* @__PURE__ */ jsxs(
|
|
17554
17665
|
"div",
|
|
17555
17666
|
{
|
|
@@ -17597,7 +17708,7 @@ function MessageList({
|
|
|
17597
17708
|
isCustomer ? msg.content && /* @__PURE__ */ jsx("div", { className: "whitespace-pre-wrap", children: msg.content }) : /* @__PURE__ */ jsx(
|
|
17598
17709
|
ChatMarkdown,
|
|
17599
17710
|
{
|
|
17600
|
-
text: msg.content,
|
|
17711
|
+
text: proseOf(msg.content),
|
|
17601
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"
|
|
17602
17713
|
}
|
|
17603
17714
|
)
|
|
@@ -17619,11 +17730,20 @@ function MessageList({
|
|
|
17619
17730
|
] }, msg.id);
|
|
17620
17731
|
}),
|
|
17621
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
|
+
) }),
|
|
17622
17742
|
/* @__PURE__ */ jsx(
|
|
17623
17743
|
"div",
|
|
17624
17744
|
{
|
|
17625
17745
|
ref: (node) => {
|
|
17626
|
-
node
|
|
17746
|
+
if (node && !scrolledUpRef.current) node.scrollIntoView({ behavior: "smooth" });
|
|
17627
17747
|
}
|
|
17628
17748
|
},
|
|
17629
17749
|
`${messages.length}:${isSending}`
|
|
@@ -17632,6 +17752,190 @@ function MessageList({
|
|
|
17632
17752
|
}
|
|
17633
17753
|
);
|
|
17634
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
|
+
}
|
|
17635
17939
|
function ChatInput({
|
|
17636
17940
|
placeholder,
|
|
17637
17941
|
isSending,
|
|
@@ -17654,14 +17958,15 @@ function ChatInput({
|
|
|
17654
17958
|
},
|
|
17655
17959
|
[onAttach]
|
|
17656
17960
|
);
|
|
17657
|
-
const handleSend = useCallback(() => {
|
|
17961
|
+
const handleSend = useCallback(async () => {
|
|
17658
17962
|
const trimmed = value.trim();
|
|
17659
17963
|
if (!trimmed || isSending) return;
|
|
17660
17964
|
setValue("");
|
|
17661
17965
|
if (textareaRef.current) {
|
|
17662
17966
|
textareaRef.current.style.height = "auto";
|
|
17663
17967
|
}
|
|
17664
|
-
onSend(trimmed);
|
|
17968
|
+
const sent = await onSend(trimmed);
|
|
17969
|
+
if (!sent) setValue((current) => current || trimmed);
|
|
17665
17970
|
}, [value, isSending, onSend]);
|
|
17666
17971
|
const handleKeyDown = useCallback(
|
|
17667
17972
|
(e) => {
|
|
@@ -17839,6 +18144,24 @@ function ImageIcon({ className }) {
|
|
|
17839
18144
|
}
|
|
17840
18145
|
);
|
|
17841
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
|
+
}
|
|
17842
18165
|
function SparkleIcon({ className }) {
|
|
17843
18166
|
return /* @__PURE__ */ jsx(
|
|
17844
18167
|
"svg",
|