@scalemule/chat 0.0.5 → 0.0.8

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.cjs CHANGED
@@ -1,50 +1,1117 @@
1
1
  'use strict';
2
2
 
3
- var chunkYDLRISR7_cjs = require('./chunk-YDLRISR7.cjs');
4
- var react = require('react');
3
+ var chunkW2PWFS3E_cjs = require('./chunk-W2PWFS3E.cjs');
4
+ var React4 = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var reactDom = require('react-dom');
6
7
 
7
- var ChatContext = react.createContext(null);
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
11
+
12
+ function ChatInput({
13
+ onSend,
14
+ onTypingChange,
15
+ onUploadAttachment,
16
+ placeholder = "Type a message..."
17
+ }) {
18
+ const [content, setContent] = React4.useState("");
19
+ const [attachments, setAttachments] = React4.useState([]);
20
+ const [isSending, setIsSending] = React4.useState(false);
21
+ const [isDragging, setIsDragging] = React4.useState(false);
22
+ const fileInputRef = React4.useRef(null);
23
+ const typingTimeoutRef = React4.useRef(null);
24
+ const readyAttachments = React4.useMemo(
25
+ () => attachments.filter((attachment) => attachment.attachment).map((attachment) => attachment.attachment),
26
+ [attachments]
27
+ );
28
+ const uploadingCount = attachments.filter((attachment) => !attachment.attachment && !attachment.error).length;
29
+ const emitTyping = () => {
30
+ onTypingChange?.(true);
31
+ if (typingTimeoutRef.current) {
32
+ clearTimeout(typingTimeoutRef.current);
33
+ }
34
+ typingTimeoutRef.current = setTimeout(() => {
35
+ onTypingChange?.(false);
36
+ }, 2500);
37
+ };
38
+ const handleFiles = async (fileList) => {
39
+ if (!onUploadAttachment) return;
40
+ const files = Array.from(fileList);
41
+ for (const file of files) {
42
+ const id = `${file.name}:${file.size}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
43
+ setAttachments((current) => [
44
+ ...current,
45
+ {
46
+ id,
47
+ fileName: file.name,
48
+ progress: 0
49
+ }
50
+ ]);
51
+ const result = await onUploadAttachment(file, (progress) => {
52
+ setAttachments(
53
+ (current) => current.map(
54
+ (attachment) => attachment.id === id ? { ...attachment, progress } : attachment
55
+ )
56
+ );
57
+ });
58
+ setAttachments(
59
+ (current) => current.map((attachment) => {
60
+ if (attachment.id !== id) return attachment;
61
+ if (result?.data) {
62
+ return {
63
+ ...attachment,
64
+ progress: 100,
65
+ attachment: result.data
66
+ };
67
+ }
68
+ return {
69
+ ...attachment,
70
+ error: result?.error?.message ?? "Upload failed"
71
+ };
72
+ })
73
+ );
74
+ }
75
+ };
76
+ const handleSubmit = async () => {
77
+ const trimmed = content.trim();
78
+ if (!trimmed && !readyAttachments.length || isSending || uploadingCount > 0) return;
79
+ setIsSending(true);
80
+ try {
81
+ await onSend(trimmed, readyAttachments);
82
+ setContent("");
83
+ setAttachments([]);
84
+ onTypingChange?.(false);
85
+ } finally {
86
+ setIsSending(false);
87
+ }
88
+ };
89
+ return /* @__PURE__ */ jsxRuntime.jsxs(
90
+ "div",
91
+ {
92
+ onDragOver: (event) => {
93
+ event.preventDefault();
94
+ if (onUploadAttachment) {
95
+ setIsDragging(true);
96
+ }
97
+ },
98
+ onDragLeave: () => setIsDragging(false),
99
+ onDrop: (event) => {
100
+ event.preventDefault();
101
+ setIsDragging(false);
102
+ void handleFiles(event.dataTransfer.files);
103
+ },
104
+ style: {
105
+ borderTop: "1px solid var(--sm-border-color, #e5e7eb)",
106
+ background: isDragging ? "rgba(37, 99, 235, 0.06)" : "var(--sm-surface, #fff)",
107
+ padding: 12,
108
+ display: "flex",
109
+ flexDirection: "column",
110
+ gap: 10
111
+ },
112
+ children: [
113
+ attachments.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsxs(
114
+ "div",
115
+ {
116
+ style: {
117
+ display: "inline-flex",
118
+ alignItems: "center",
119
+ gap: 8,
120
+ padding: "6px 10px",
121
+ borderRadius: 999,
122
+ background: "var(--sm-surface-muted, #f8fafc)",
123
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
124
+ fontSize: 12
125
+ },
126
+ children: [
127
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: attachment.fileName }),
128
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: attachment.error ? "#dc2626" : "var(--sm-muted-text, #6b7280)" }, children: attachment.error ?? `${attachment.progress}%` }),
129
+ /* @__PURE__ */ jsxRuntime.jsx(
130
+ "button",
131
+ {
132
+ type: "button",
133
+ onClick: () => {
134
+ setAttachments((current) => current.filter((item) => item.id !== attachment.id));
135
+ },
136
+ style: {
137
+ border: "none",
138
+ background: "transparent",
139
+ cursor: "pointer",
140
+ color: "var(--sm-muted-text, #6b7280)"
141
+ },
142
+ children: "x"
143
+ }
144
+ )
145
+ ]
146
+ },
147
+ attachment.id
148
+ )) }) : null,
149
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 10, alignItems: "flex-end" }, children: [
150
+ /* @__PURE__ */ jsxRuntime.jsx(
151
+ "textarea",
152
+ {
153
+ value: content,
154
+ onChange: (event) => {
155
+ setContent(event.target.value);
156
+ emitTyping();
157
+ },
158
+ onKeyDown: (event) => {
159
+ if (event.key === "Enter" && !event.shiftKey) {
160
+ event.preventDefault();
161
+ void handleSubmit();
162
+ }
163
+ },
164
+ rows: 1,
165
+ placeholder,
166
+ style: {
167
+ flex: 1,
168
+ minHeight: 44,
169
+ maxHeight: 120,
170
+ resize: "vertical",
171
+ borderRadius: 14,
172
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
173
+ padding: "12px 14px",
174
+ font: "inherit",
175
+ color: "var(--sm-text-color, #111827)"
176
+ }
177
+ }
178
+ ),
179
+ onUploadAttachment ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
180
+ /* @__PURE__ */ jsxRuntime.jsx(
181
+ "input",
182
+ {
183
+ ref: fileInputRef,
184
+ type: "file",
185
+ hidden: true,
186
+ multiple: true,
187
+ accept: "image/*,video/*,audio/*",
188
+ onChange: (event) => {
189
+ if (event.target.files) {
190
+ void handleFiles(event.target.files);
191
+ event.target.value = "";
192
+ }
193
+ }
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsxRuntime.jsx(
197
+ "button",
198
+ {
199
+ type: "button",
200
+ onClick: () => fileInputRef.current?.click(),
201
+ "aria-label": "Attach files",
202
+ style: {
203
+ width: 44,
204
+ height: 44,
205
+ borderRadius: 14,
206
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
207
+ background: "var(--sm-surface, #fff)",
208
+ cursor: "pointer",
209
+ color: "var(--sm-text-color, #111827)"
210
+ },
211
+ children: "+"
212
+ }
213
+ )
214
+ ] }) : null,
215
+ /* @__PURE__ */ jsxRuntime.jsx(
216
+ "button",
217
+ {
218
+ type: "button",
219
+ onClick: () => void handleSubmit(),
220
+ disabled: isSending || uploadingCount > 0 || !content.trim() && !readyAttachments.length,
221
+ style: {
222
+ height: 44,
223
+ padding: "0 16px",
224
+ borderRadius: 14,
225
+ border: "none",
226
+ background: "var(--sm-primary, #2563eb)",
227
+ color: "#fff",
228
+ cursor: isSending ? "wait" : "pointer",
229
+ opacity: isSending || uploadingCount > 0 ? 0.75 : 1
230
+ },
231
+ children: "Send"
232
+ }
233
+ )
234
+ ] })
235
+ ]
236
+ }
237
+ );
238
+ }
239
+ var QUICK_REACTIONS = ["\u2764\uFE0F", "\u{1F602}", "\u{1F44D}", "\u{1F525}", "\u{1F62E}", "\u{1F622}", "\u{1F44F}", "\u{1F64C}"];
240
+ function EmojiPicker({
241
+ onSelect,
242
+ onClose,
243
+ anchorRef,
244
+ emojis = QUICK_REACTIONS
245
+ }) {
246
+ const ref = React4.useRef(null);
247
+ const [position, setPosition] = React4.useState(null);
248
+ React4.useEffect(() => {
249
+ const anchor = anchorRef.current;
250
+ if (!anchor) return;
251
+ const rect = anchor.getBoundingClientRect();
252
+ const pickerWidth = emojis.length * 36 + 16;
253
+ let left = rect.left + rect.width / 2 - pickerWidth / 2;
254
+ if (left < 8) left = 8;
255
+ if (left + pickerWidth > window.innerWidth - 8)
256
+ left = window.innerWidth - 8 - pickerWidth;
257
+ setPosition({ top: rect.top - 8, left });
258
+ }, [anchorRef, emojis.length]);
259
+ React4.useEffect(() => {
260
+ function handleClickOutside(e) {
261
+ if (ref.current && !ref.current.contains(e.target) && anchorRef.current && !anchorRef.current.contains(e.target)) {
262
+ onClose();
263
+ }
264
+ }
265
+ document.addEventListener("mousedown", handleClickOutside);
266
+ return () => document.removeEventListener("mousedown", handleClickOutside);
267
+ }, [onClose, anchorRef]);
268
+ if (!position) return null;
269
+ return reactDom.createPortal(
270
+ /* @__PURE__ */ jsxRuntime.jsx(
271
+ "div",
272
+ {
273
+ ref,
274
+ style: {
275
+ position: "fixed",
276
+ top: position.top,
277
+ left: position.left,
278
+ transform: "translateY(-100%)",
279
+ background: "var(--sm-surface, #fff)",
280
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
281
+ borderRadius: 12,
282
+ boxShadow: "0 10px 25px rgba(15, 23, 42, 0.12)",
283
+ padding: 8,
284
+ display: "flex",
285
+ gap: 4,
286
+ zIndex: 9999
287
+ },
288
+ children: emojis.map((emoji) => /* @__PURE__ */ jsxRuntime.jsx(
289
+ "button",
290
+ {
291
+ onClick: () => {
292
+ onSelect(emoji);
293
+ onClose();
294
+ },
295
+ type: "button",
296
+ "aria-label": `React with ${emoji}`,
297
+ style: {
298
+ width: 32,
299
+ height: 32,
300
+ display: "flex",
301
+ alignItems: "center",
302
+ justifyContent: "center",
303
+ borderRadius: 8,
304
+ border: "none",
305
+ background: "transparent",
306
+ cursor: "pointer",
307
+ fontSize: 18,
308
+ transition: "background 0.15s ease"
309
+ },
310
+ onMouseEnter: (e) => {
311
+ e.target.style.background = "var(--sm-surface-muted, #f8fafc)";
312
+ },
313
+ onMouseLeave: (e) => {
314
+ e.target.style.background = "transparent";
315
+ },
316
+ children: emoji
317
+ },
318
+ emoji
319
+ ))
320
+ }
321
+ ),
322
+ document.body
323
+ );
324
+ }
325
+
326
+ // src/react-components/utils.ts
327
+ var timeFormatter = new Intl.DateTimeFormat(void 0, {
328
+ hour: "numeric",
329
+ minute: "2-digit"
330
+ });
331
+ var dateFormatter = new Intl.DateTimeFormat(void 0, {
332
+ month: "short",
333
+ day: "numeric",
334
+ year: "numeric"
335
+ });
336
+ function formatMessageTime(value) {
337
+ if (!value) return "";
338
+ return timeFormatter.format(new Date(value));
339
+ }
340
+ function formatDayLabel(value) {
341
+ if (!value) return "";
342
+ return dateFormatter.format(new Date(value));
343
+ }
344
+ function isSameDay(left, right) {
345
+ if (!left || !right) return false;
346
+ const leftDate = new Date(left);
347
+ const rightDate = new Date(right);
348
+ return leftDate.getFullYear() === rightDate.getFullYear() && leftDate.getMonth() === rightDate.getMonth() && leftDate.getDate() === rightDate.getDate();
349
+ }
350
+ function renderAttachment(messageId, attachment) {
351
+ const key = `${messageId}:${attachment.file_id}`;
352
+ const url = attachment.presigned_url;
353
+ if (!url) {
354
+ return /* @__PURE__ */ jsxRuntime.jsx(
355
+ "div",
356
+ {
357
+ style: {
358
+ padding: "10px 12px",
359
+ borderRadius: 12,
360
+ background: "rgba(255,255,255,0.16)",
361
+ fontSize: 13
362
+ },
363
+ children: attachment.file_name
364
+ },
365
+ key
366
+ );
367
+ }
368
+ if (attachment.mime_type.startsWith("image/")) {
369
+ return /* @__PURE__ */ jsxRuntime.jsx(
370
+ "img",
371
+ {
372
+ src: url,
373
+ alt: attachment.file_name,
374
+ loading: "lazy",
375
+ style: {
376
+ display: "block",
377
+ maxWidth: "100%",
378
+ borderRadius: 12,
379
+ marginTop: 8
380
+ }
381
+ },
382
+ key
383
+ );
384
+ }
385
+ if (attachment.mime_type.startsWith("video/")) {
386
+ return /* @__PURE__ */ jsxRuntime.jsx(
387
+ "video",
388
+ {
389
+ controls: true,
390
+ src: url,
391
+ style: {
392
+ display: "block",
393
+ width: "100%",
394
+ maxWidth: 320,
395
+ borderRadius: 12,
396
+ marginTop: 8
397
+ }
398
+ },
399
+ key
400
+ );
401
+ }
402
+ if (attachment.mime_type.startsWith("audio/")) {
403
+ return /* @__PURE__ */ jsxRuntime.jsx(
404
+ "audio",
405
+ {
406
+ controls: true,
407
+ src: url,
408
+ style: {
409
+ display: "block",
410
+ width: "100%",
411
+ marginTop: 8
412
+ }
413
+ },
414
+ key
415
+ );
416
+ }
417
+ return /* @__PURE__ */ jsxRuntime.jsx(
418
+ "a",
419
+ {
420
+ href: url,
421
+ target: "_blank",
422
+ rel: "noreferrer",
423
+ style: {
424
+ display: "inline-block",
425
+ marginTop: 8,
426
+ color: "inherit",
427
+ fontSize: 13
428
+ },
429
+ children: attachment.file_name
430
+ },
431
+ key
432
+ );
433
+ }
434
+ function ChatMessageItem({
435
+ message,
436
+ currentUserId,
437
+ onAddReaction,
438
+ onRemoveReaction,
439
+ onReport,
440
+ highlight = false
441
+ }) {
442
+ const [showPicker, setShowPicker] = React4.useState(false);
443
+ const isOwn = Boolean(currentUserId && message.sender_id === currentUserId);
444
+ const canReact = Boolean(onAddReaction || onRemoveReaction);
445
+ const reactionEntries = React4.useMemo(() => message.reactions ?? [], [message.reactions]);
446
+ return /* @__PURE__ */ jsxRuntime.jsxs(
447
+ "div",
448
+ {
449
+ style: {
450
+ display: "flex",
451
+ flexDirection: "column",
452
+ alignItems: isOwn ? "flex-end" : "flex-start",
453
+ gap: 6
454
+ },
455
+ children: [
456
+ /* @__PURE__ */ jsxRuntime.jsxs(
457
+ "div",
458
+ {
459
+ style: {
460
+ maxWidth: "min(82%, 560px)",
461
+ padding: message.attachments?.length ? 10 : "10px 12px",
462
+ borderRadius: "var(--sm-border-radius, 16px)",
463
+ background: isOwn ? "var(--sm-own-bubble, #2563eb)" : "var(--sm-other-bubble, #f3f4f6)",
464
+ color: isOwn ? "var(--sm-own-text, #fff)" : "var(--sm-other-text, #111827)",
465
+ boxShadow: highlight ? "0 0 0 2px rgba(37, 99, 235, 0.22)" : "none",
466
+ transition: "box-shadow 0.2s ease"
467
+ },
468
+ children: [
469
+ message.content ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word", fontSize: 14 }, children: message.content }) : null,
470
+ message.attachments?.map((attachment) => renderAttachment(message.id, attachment))
471
+ ]
472
+ }
473
+ ),
474
+ /* @__PURE__ */ jsxRuntime.jsxs(
475
+ "div",
476
+ {
477
+ style: {
478
+ display: "flex",
479
+ alignItems: "center",
480
+ gap: 8,
481
+ color: "var(--sm-muted-text, #6b7280)",
482
+ fontSize: 12
483
+ },
484
+ children: [
485
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatMessageTime(message.created_at) }),
486
+ message.is_edited ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "edited" }) : null,
487
+ onReport && !isOwn ? /* @__PURE__ */ jsxRuntime.jsx(
488
+ "button",
489
+ {
490
+ type: "button",
491
+ onClick: () => void onReport(message.id),
492
+ style: {
493
+ border: "none",
494
+ background: "transparent",
495
+ color: "inherit",
496
+ cursor: "pointer",
497
+ fontSize: 12,
498
+ padding: 0
499
+ },
500
+ children: "Report"
501
+ }
502
+ ) : null,
503
+ canReact ? /* @__PURE__ */ jsxRuntime.jsx(
504
+ "button",
505
+ {
506
+ type: "button",
507
+ onClick: () => setShowPicker((value) => !value),
508
+ style: {
509
+ border: "none",
510
+ background: "transparent",
511
+ color: "inherit",
512
+ cursor: "pointer",
513
+ fontSize: 12,
514
+ padding: 0
515
+ },
516
+ children: "React"
517
+ }
518
+ ) : null
519
+ ]
520
+ }
521
+ ),
522
+ showPicker && canReact ? /* @__PURE__ */ jsxRuntime.jsx(
523
+ EmojiPicker,
524
+ {
525
+ onSelect: (emoji) => {
526
+ setShowPicker(false);
527
+ void onAddReaction?.(message.id, emoji);
528
+ }
529
+ }
530
+ ) : null,
531
+ reactionEntries.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: reactionEntries.map((reaction) => {
532
+ const reacted = Boolean(currentUserId && reaction.user_ids.includes(currentUserId));
533
+ return /* @__PURE__ */ jsxRuntime.jsxs(
534
+ "button",
535
+ {
536
+ type: "button",
537
+ onClick: () => {
538
+ if (reacted) {
539
+ void onRemoveReaction?.(message.id, reaction.emoji);
540
+ return;
541
+ }
542
+ void onAddReaction?.(message.id, reaction.emoji);
543
+ },
544
+ style: {
545
+ border: reacted ? "1px solid rgba(37, 99, 235, 0.4)" : "1px solid var(--sm-border-color, #e5e7eb)",
546
+ background: reacted ? "rgba(37, 99, 235, 0.08)" : "var(--sm-surface, #fff)",
547
+ color: "var(--sm-text-color, #111827)",
548
+ borderRadius: 999,
549
+ padding: "4px 8px",
550
+ cursor: "pointer",
551
+ fontSize: 12
552
+ },
553
+ children: [
554
+ reaction.emoji,
555
+ " ",
556
+ reaction.count
557
+ ]
558
+ },
559
+ `${message.id}:${reaction.emoji}`
560
+ );
561
+ }) }) : null
562
+ ]
563
+ }
564
+ );
565
+ }
566
+ function getUnreadIndex(messages, unreadSince) {
567
+ if (!unreadSince) return -1;
568
+ return messages.findIndex((message) => new Date(message.created_at).getTime() > new Date(unreadSince).getTime());
569
+ }
570
+ function ChatMessageList({
571
+ messages,
572
+ currentUserId,
573
+ unreadSince,
574
+ scrollToUnreadOnMount = true,
575
+ onAddReaction,
576
+ onRemoveReaction,
577
+ onReport,
578
+ emptyState
579
+ }) {
580
+ const containerRef = React4.useRef(null);
581
+ const unreadMarkerRef = React4.useRef(null);
582
+ const lastMessageCountRef = React4.useRef(messages.length);
583
+ const didScrollToUnreadRef = React4.useRef(false);
584
+ const [showJumpToLatest, setShowJumpToLatest] = React4.useState(false);
585
+ const unreadIndex = React4.useMemo(() => getUnreadIndex(messages, unreadSince), [messages, unreadSince]);
586
+ React4.useEffect(() => {
587
+ const container = containerRef.current;
588
+ if (!container) return;
589
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
590
+ const shouldStickToBottom = distanceFromBottom < 80;
591
+ if (messages.length > lastMessageCountRef.current && shouldStickToBottom) {
592
+ container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
593
+ setShowJumpToLatest(false);
594
+ } else if (messages.length > lastMessageCountRef.current) {
595
+ setShowJumpToLatest(true);
596
+ }
597
+ lastMessageCountRef.current = messages.length;
598
+ }, [messages]);
599
+ React4.useEffect(() => {
600
+ if (!scrollToUnreadOnMount || unreadIndex < 0 || didScrollToUnreadRef.current) {
601
+ return;
602
+ }
603
+ unreadMarkerRef.current?.scrollIntoView({ block: "center" });
604
+ didScrollToUnreadRef.current = true;
605
+ }, [scrollToUnreadOnMount, unreadIndex, messages.length]);
606
+ if (!messages.length) {
607
+ return /* @__PURE__ */ jsxRuntime.jsx(
608
+ "div",
609
+ {
610
+ style: {
611
+ flex: 1,
612
+ display: "flex",
613
+ alignItems: "center",
614
+ justifyContent: "center",
615
+ color: "var(--sm-muted-text, #6b7280)",
616
+ fontSize: 14,
617
+ padding: 24
618
+ },
619
+ children: emptyState ?? "No messages yet"
620
+ }
621
+ );
622
+ }
623
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", flex: 1, minHeight: 0 }, children: [
624
+ /* @__PURE__ */ jsxRuntime.jsx(
625
+ "div",
626
+ {
627
+ ref: containerRef,
628
+ onScroll: (event) => {
629
+ const element = event.currentTarget;
630
+ const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
631
+ setShowJumpToLatest(distanceFromBottom > 120);
632
+ },
633
+ style: {
634
+ height: "100%",
635
+ overflowY: "auto",
636
+ padding: 16,
637
+ display: "flex",
638
+ flexDirection: "column",
639
+ gap: 12,
640
+ background: "var(--sm-surface-muted, #f8fafc)"
641
+ },
642
+ children: messages.map((message, index) => {
643
+ const previousMessage = messages[index - 1];
644
+ const showDateDivider = !previousMessage || !isSameDay(previousMessage.created_at, message.created_at);
645
+ const showUnreadDivider = unreadIndex === index;
646
+ return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
647
+ showDateDivider ? /* @__PURE__ */ jsxRuntime.jsx(
648
+ "div",
649
+ {
650
+ style: {
651
+ alignSelf: "center",
652
+ fontSize: 12,
653
+ color: "var(--sm-muted-text, #6b7280)",
654
+ padding: "4px 10px",
655
+ borderRadius: 999,
656
+ background: "rgba(148, 163, 184, 0.12)"
657
+ },
658
+ children: formatDayLabel(message.created_at)
659
+ }
660
+ ) : null,
661
+ showUnreadDivider ? /* @__PURE__ */ jsxRuntime.jsxs(
662
+ "div",
663
+ {
664
+ ref: unreadMarkerRef,
665
+ style: {
666
+ display: "flex",
667
+ alignItems: "center",
668
+ gap: 10,
669
+ color: "var(--sm-primary, #2563eb)",
670
+ fontSize: 12,
671
+ fontWeight: 600
672
+ },
673
+ children: [
674
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: 1, background: "rgba(37, 99, 235, 0.28)" } }),
675
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "New messages" }),
676
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: 1, background: "rgba(37, 99, 235, 0.28)" } })
677
+ ]
678
+ }
679
+ ) : null,
680
+ /* @__PURE__ */ jsxRuntime.jsx(
681
+ ChatMessageItem,
682
+ {
683
+ message,
684
+ currentUserId,
685
+ onAddReaction,
686
+ onRemoveReaction,
687
+ onReport,
688
+ highlight: showUnreadDivider
689
+ }
690
+ )
691
+ ] }, message.id);
692
+ })
693
+ }
694
+ ),
695
+ showJumpToLatest ? /* @__PURE__ */ jsxRuntime.jsx(
696
+ "button",
697
+ {
698
+ type: "button",
699
+ onClick: () => {
700
+ containerRef.current?.scrollTo({
701
+ top: containerRef.current.scrollHeight,
702
+ behavior: "smooth"
703
+ });
704
+ setShowJumpToLatest(false);
705
+ },
706
+ style: {
707
+ position: "absolute",
708
+ right: 16,
709
+ bottom: 16,
710
+ border: "none",
711
+ borderRadius: 999,
712
+ background: "var(--sm-primary, #2563eb)",
713
+ color: "#fff",
714
+ padding: "10px 14px",
715
+ cursor: "pointer",
716
+ boxShadow: "0 12px 28px rgba(37, 99, 235, 0.28)"
717
+ },
718
+ children: "New messages"
719
+ }
720
+ ) : null
721
+ ] });
722
+ }
723
+
724
+ // src/react-components/theme.ts
725
+ function themeToStyle(theme) {
726
+ return {
727
+ "--sm-primary": theme?.primary ?? "#2563eb",
728
+ "--sm-own-bubble": theme?.ownBubble ?? theme?.primary ?? "#2563eb",
729
+ "--sm-own-text": theme?.ownText ?? "#ffffff",
730
+ "--sm-other-bubble": theme?.otherBubble ?? "#f3f4f6",
731
+ "--sm-other-text": theme?.otherText ?? "#111827",
732
+ "--sm-surface": theme?.surface ?? "#ffffff",
733
+ "--sm-surface-muted": theme?.surfaceMuted ?? "#f8fafc",
734
+ "--sm-border-color": theme?.borderColor ?? "#e5e7eb",
735
+ "--sm-text-color": theme?.textColor ?? "#111827",
736
+ "--sm-muted-text": theme?.mutedText ?? "#6b7280",
737
+ "--sm-border-radius": typeof theme?.borderRadius === "number" ? `${theme.borderRadius}px` : theme?.borderRadius ?? "16px",
738
+ "--sm-font-family": theme?.fontFamily ?? 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
739
+ };
740
+ }
741
+ function inferMessageType(content, attachments) {
742
+ if (!attachments.length) return "text";
743
+ if (!content && attachments.every((attachment) => attachment.mime_type.startsWith("image/"))) {
744
+ return "image";
745
+ }
746
+ return "file";
747
+ }
748
+ function ChatThread({
749
+ conversationId,
750
+ theme,
751
+ currentUserId,
752
+ title = "Chat",
753
+ subtitle
754
+ }) {
755
+ const client = useChatClient();
756
+ const resolvedUserId = currentUserId ?? client.userId;
757
+ const {
758
+ messages,
759
+ readStatuses,
760
+ isLoading,
761
+ error,
762
+ sendMessage,
763
+ markRead,
764
+ addReaction,
765
+ removeReaction,
766
+ reportMessage,
767
+ uploadAttachment
768
+ } = useChat(conversationId);
769
+ const { typingUsers, sendTyping } = useTyping(conversationId);
770
+ const { members } = usePresence(conversationId);
771
+ const ownReadStatus = React4.useMemo(
772
+ () => resolvedUserId ? readStatuses.find((status) => status.user_id === resolvedUserId)?.last_read_at : void 0,
773
+ [readStatuses, resolvedUserId]
774
+ );
775
+ const otherTypingUsers = React4.useMemo(
776
+ () => typingUsers.filter((userId) => userId !== resolvedUserId),
777
+ [typingUsers, resolvedUserId]
778
+ );
779
+ const activeMembers = React4.useMemo(
780
+ () => members.filter((member) => member.userId !== resolvedUserId),
781
+ [members, resolvedUserId]
782
+ );
783
+ React4.useEffect(() => {
784
+ if (!messages.length) return;
785
+ void markRead();
786
+ }, [markRead, messages.length, messages[messages.length - 1]?.id]);
787
+ return /* @__PURE__ */ jsxRuntime.jsxs(
788
+ "div",
789
+ {
790
+ "data-scalemule-chat": "",
791
+ style: {
792
+ ...themeToStyle(theme),
793
+ display: "flex",
794
+ flexDirection: "column",
795
+ height: "100%",
796
+ minHeight: 320,
797
+ borderRadius: "var(--sm-border-radius, 16px)",
798
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
799
+ background: "var(--sm-surface, #fff)",
800
+ color: "var(--sm-text-color, #111827)",
801
+ fontFamily: "var(--sm-font-family)",
802
+ overflow: "hidden"
803
+ },
804
+ children: [
805
+ /* @__PURE__ */ jsxRuntime.jsxs(
806
+ "div",
807
+ {
808
+ style: {
809
+ padding: "16px 18px",
810
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
811
+ display: "flex",
812
+ justifyContent: "space-between",
813
+ alignItems: "center",
814
+ gap: 16
815
+ },
816
+ children: [
817
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
818
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 700 }, children: title }),
819
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, color: "var(--sm-muted-text, #6b7280)" }, children: otherTypingUsers.length ? "Typing..." : subtitle ?? (activeMembers.length ? `${activeMembers.length} online` : "No one online") })
820
+ ] }),
821
+ /* @__PURE__ */ jsxRuntime.jsxs(
822
+ "div",
823
+ {
824
+ style: {
825
+ display: "inline-flex",
826
+ alignItems: "center",
827
+ gap: 8,
828
+ fontSize: 12,
829
+ color: "var(--sm-muted-text, #6b7280)"
830
+ },
831
+ children: [
832
+ /* @__PURE__ */ jsxRuntime.jsx(
833
+ "span",
834
+ {
835
+ style: {
836
+ width: 10,
837
+ height: 10,
838
+ borderRadius: 999,
839
+ background: activeMembers.length ? "#22c55e" : "#94a3b8"
840
+ }
841
+ }
842
+ ),
843
+ activeMembers.length ? "Online" : "Away"
844
+ ]
845
+ }
846
+ )
847
+ ]
848
+ }
849
+ ),
850
+ error ? /* @__PURE__ */ jsxRuntime.jsx(
851
+ "div",
852
+ {
853
+ style: {
854
+ padding: "10px 16px",
855
+ fontSize: 13,
856
+ color: "#b91c1c",
857
+ background: "#fef2f2",
858
+ borderBottom: "1px solid #fecaca"
859
+ },
860
+ children: error
861
+ }
862
+ ) : null,
863
+ /* @__PURE__ */ jsxRuntime.jsx(
864
+ ChatMessageList,
865
+ {
866
+ messages,
867
+ currentUserId: resolvedUserId,
868
+ unreadSince: ownReadStatus,
869
+ onAddReaction: (messageId, emoji) => void addReaction(messageId, emoji),
870
+ onRemoveReaction: (messageId, emoji) => void removeReaction(messageId, emoji),
871
+ onReport: (messageId) => void reportMessage(messageId, "other"),
872
+ emptyState: isLoading ? "Loading messages..." : "Start the conversation"
873
+ }
874
+ ),
875
+ /* @__PURE__ */ jsxRuntime.jsx(
876
+ "div",
877
+ {
878
+ style: {
879
+ minHeight: otherTypingUsers.length ? 28 : 0,
880
+ padding: otherTypingUsers.length ? "0 16px 8px" : 0,
881
+ fontSize: 12,
882
+ color: "var(--sm-muted-text, #6b7280)"
883
+ },
884
+ children: otherTypingUsers.length ? "Someone is typing..." : null
885
+ }
886
+ ),
887
+ /* @__PURE__ */ jsxRuntime.jsx(
888
+ ChatInput,
889
+ {
890
+ onSend: async (content, attachments) => {
891
+ await sendMessage(content, {
892
+ attachments,
893
+ message_type: inferMessageType(content, attachments)
894
+ });
895
+ },
896
+ onTypingChange: (isTyping) => {
897
+ sendTyping(isTyping);
898
+ },
899
+ onUploadAttachment: uploadAttachment
900
+ }
901
+ )
902
+ ]
903
+ }
904
+ );
905
+ }
906
+ function formatPreview(conversation) {
907
+ if (conversation.last_message_preview) {
908
+ return conversation.last_message_preview;
909
+ }
910
+ return conversation.name ?? conversation.id;
911
+ }
912
+ function ConversationList({
913
+ conversationType,
914
+ selectedConversationId,
915
+ onSelect,
916
+ theme,
917
+ title = "Conversations"
918
+ }) {
919
+ const { conversations, isLoading } = useConversations({
920
+ conversationType
921
+ });
922
+ const [search, setSearch] = React4.useState("");
923
+ const filtered = React4.useMemo(() => {
924
+ const query = search.trim().toLowerCase();
925
+ if (!query) return conversations;
926
+ return conversations.filter((conversation) => {
927
+ const haystack = [
928
+ conversation.name,
929
+ conversation.last_message_preview,
930
+ conversation.counterparty_user_id
931
+ ].filter(Boolean).join(" ").toLowerCase();
932
+ return haystack.includes(query);
933
+ });
934
+ }, [conversations, search]);
935
+ return /* @__PURE__ */ jsxRuntime.jsxs(
936
+ "div",
937
+ {
938
+ "data-scalemule-chat": "",
939
+ style: {
940
+ ...themeToStyle(theme),
941
+ display: "flex",
942
+ flexDirection: "column",
943
+ height: "100%",
944
+ minHeight: 280,
945
+ borderRadius: "var(--sm-border-radius, 16px)",
946
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
947
+ background: "var(--sm-surface, #fff)",
948
+ color: "var(--sm-text-color, #111827)",
949
+ fontFamily: "var(--sm-font-family)",
950
+ overflow: "hidden"
951
+ },
952
+ children: [
953
+ /* @__PURE__ */ jsxRuntime.jsxs(
954
+ "div",
955
+ {
956
+ style: {
957
+ padding: 16,
958
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
959
+ display: "flex",
960
+ flexDirection: "column",
961
+ gap: 10
962
+ },
963
+ children: [
964
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 700 }, children: title }),
965
+ /* @__PURE__ */ jsxRuntime.jsx(
966
+ "input",
967
+ {
968
+ value: search,
969
+ onChange: (event) => setSearch(event.target.value),
970
+ placeholder: "Search conversations",
971
+ style: {
972
+ width: "100%",
973
+ borderRadius: 12,
974
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
975
+ padding: "10px 12px",
976
+ font: "inherit"
977
+ }
978
+ }
979
+ )
980
+ ]
981
+ }
982
+ ),
983
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, overflowY: "auto" }, children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
984
+ "div",
985
+ {
986
+ style: {
987
+ padding: 24,
988
+ fontSize: 14,
989
+ color: "var(--sm-muted-text, #6b7280)"
990
+ },
991
+ children: "Loading conversations..."
992
+ }
993
+ ) : !filtered.length ? /* @__PURE__ */ jsxRuntime.jsx(
994
+ "div",
995
+ {
996
+ style: {
997
+ padding: 24,
998
+ fontSize: 14,
999
+ color: "var(--sm-muted-text, #6b7280)"
1000
+ },
1001
+ children: "No conversations found"
1002
+ }
1003
+ ) : filtered.map((conversation) => {
1004
+ const selected = conversation.id === selectedConversationId;
1005
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1006
+ "button",
1007
+ {
1008
+ type: "button",
1009
+ onClick: () => onSelect?.(conversation),
1010
+ style: {
1011
+ width: "100%",
1012
+ border: "none",
1013
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
1014
+ padding: 16,
1015
+ textAlign: "left",
1016
+ background: selected ? "rgba(37, 99, 235, 0.08)" : "transparent",
1017
+ cursor: "pointer",
1018
+ display: "flex",
1019
+ flexDirection: "column",
1020
+ gap: 6
1021
+ },
1022
+ children: [
1023
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }, children: [
1024
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 14, fontWeight: 600 }, children: conversation.name ?? conversation.counterparty_user_id ?? "Conversation" }),
1025
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
1026
+ conversation.is_muted ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 11, color: "var(--sm-muted-text, #6b7280)" }, children: "Muted" }) : null,
1027
+ conversation.unread_count ? /* @__PURE__ */ jsxRuntime.jsx(
1028
+ "span",
1029
+ {
1030
+ style: {
1031
+ minWidth: 22,
1032
+ height: 22,
1033
+ borderRadius: 999,
1034
+ background: "var(--sm-primary, #2563eb)",
1035
+ color: "#fff",
1036
+ display: "inline-flex",
1037
+ alignItems: "center",
1038
+ justifyContent: "center",
1039
+ fontSize: 12,
1040
+ fontWeight: 700,
1041
+ padding: "0 6px"
1042
+ },
1043
+ children: conversation.unread_count
1044
+ }
1045
+ ) : null
1046
+ ] })
1047
+ ] }),
1048
+ /* @__PURE__ */ jsxRuntime.jsx(
1049
+ "div",
1050
+ {
1051
+ style: {
1052
+ fontSize: 13,
1053
+ color: "var(--sm-muted-text, #6b7280)",
1054
+ overflow: "hidden",
1055
+ textOverflow: "ellipsis",
1056
+ whiteSpace: "nowrap"
1057
+ },
1058
+ children: formatPreview(conversation)
1059
+ }
1060
+ )
1061
+ ]
1062
+ },
1063
+ conversation.id
1064
+ );
1065
+ }) })
1066
+ ]
1067
+ }
1068
+ );
1069
+ }
1070
+ var ChatContext = React4.createContext(null);
8
1071
  function useChatContext() {
9
- const ctx = react.useContext(ChatContext);
1072
+ const ctx = React4.useContext(ChatContext);
10
1073
  if (!ctx) throw new Error("useChatContext must be used within a ChatProvider");
11
1074
  return ctx;
12
1075
  }
13
1076
  function ChatProvider({ config, children }) {
14
- const [client] = react.useState(() => new chunkYDLRISR7_cjs.ChatClient(config));
15
- react.useEffect(() => {
1077
+ const [client] = React4.useState(() => new chunkW2PWFS3E_cjs.ChatClient(config));
1078
+ React4.useEffect(() => {
16
1079
  return () => {
17
1080
  client.destroy();
18
1081
  };
19
1082
  }, [client]);
20
- return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: { client }, children });
1083
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: { client, config }, children });
21
1084
  }
22
1085
  function useChatClient() {
23
1086
  return useChatContext().client;
24
1087
  }
1088
+ function useChatConfig() {
1089
+ return useChatContext().config;
1090
+ }
25
1091
  function useConnection() {
26
1092
  const { client } = useChatContext();
27
- const [status, setStatus] = react.useState(client.status);
28
- react.useEffect(() => {
1093
+ const [status, setStatus] = React4.useState(client.status);
1094
+ React4.useEffect(() => {
29
1095
  return client.on("connected", () => setStatus("connected"));
30
1096
  }, [client]);
31
- react.useEffect(() => {
1097
+ React4.useEffect(() => {
32
1098
  return client.on("disconnected", () => setStatus("disconnected"));
33
1099
  }, [client]);
34
- react.useEffect(() => {
1100
+ React4.useEffect(() => {
35
1101
  return client.on("reconnecting", () => setStatus("reconnecting"));
36
1102
  }, [client]);
37
- const connect = react.useCallback(() => client.connect(), [client]);
38
- const disconnect = react.useCallback(() => client.disconnect(), [client]);
1103
+ const connect = React4.useCallback(() => client.connect(), [client]);
1104
+ const disconnect = React4.useCallback(() => client.disconnect(), [client]);
39
1105
  return { status, connect, disconnect };
40
1106
  }
41
1107
  function useChat(conversationId) {
42
1108
  const { client } = useChatContext();
43
- const [messages, setMessages] = react.useState([]);
44
- const [isLoading, setIsLoading] = react.useState(false);
45
- const [error, setError] = react.useState(null);
46
- const [hasMore, setHasMore] = react.useState(false);
47
- react.useEffect(() => {
1109
+ const [messages, setMessages] = React4.useState([]);
1110
+ const [readStatuses, setReadStatuses] = React4.useState([]);
1111
+ const [isLoading, setIsLoading] = React4.useState(false);
1112
+ const [error, setError] = React4.useState(null);
1113
+ const [hasMore, setHasMore] = React4.useState(false);
1114
+ React4.useEffect(() => {
48
1115
  if (!conversationId) return;
49
1116
  setIsLoading(true);
50
1117
  setError(null);
@@ -61,6 +1128,11 @@ function useChat(conversationId) {
61
1128
  } else if (result.error) {
62
1129
  setError(result.error.message);
63
1130
  }
1131
+ const readStatusResult = await client.getReadStatus(conversationId);
1132
+ if (cancelled) return;
1133
+ if (readStatusResult.data?.statuses) {
1134
+ setReadStatuses(readStatusResult.data.statuses);
1135
+ }
64
1136
  setIsLoading(false);
65
1137
  unsub = client.subscribeToConversation(conversationId);
66
1138
  client.connect();
@@ -70,41 +1142,61 @@ function useChat(conversationId) {
70
1142
  unsub?.();
71
1143
  };
72
1144
  }, [client, conversationId]);
73
- react.useEffect(() => {
1145
+ React4.useEffect(() => {
74
1146
  if (!conversationId) return;
75
1147
  return client.on("message", ({ message, conversationId: convId }) => {
76
1148
  if (convId === conversationId) {
77
- setMessages((prev) => {
78
- if (prev.some((m) => m.id === message.id)) return prev;
79
- return [...prev, message];
80
- });
1149
+ setMessages([...client.getCachedMessages(conversationId)]);
81
1150
  }
82
1151
  });
83
1152
  }, [client, conversationId]);
84
- react.useEffect(() => {
1153
+ React4.useEffect(() => {
85
1154
  if (!conversationId) return;
86
1155
  return client.on("message:updated", ({ message, conversationId: convId }) => {
87
1156
  if (convId === conversationId) {
88
- setMessages((prev) => prev.map((m) => m.id === message.id ? message : m));
1157
+ setMessages([...client.getCachedMessages(conversationId)]);
89
1158
  }
90
1159
  });
91
1160
  }, [client, conversationId]);
92
- react.useEffect(() => {
1161
+ React4.useEffect(() => {
93
1162
  if (!conversationId) return;
94
1163
  return client.on("message:deleted", ({ messageId, conversationId: convId }) => {
95
1164
  if (convId === conversationId) {
96
- setMessages((prev) => prev.filter((m) => m.id !== messageId));
1165
+ setMessages([...client.getCachedMessages(conversationId)]);
1166
+ }
1167
+ });
1168
+ }, [client, conversationId]);
1169
+ React4.useEffect(() => {
1170
+ if (!conversationId) return;
1171
+ return client.on("reaction", ({ conversationId: convId }) => {
1172
+ if (convId === conversationId) {
1173
+ setMessages([...client.getCachedMessages(conversationId)]);
97
1174
  }
98
1175
  });
99
1176
  }, [client, conversationId]);
100
- const sendMessage = react.useCallback(
1177
+ React4.useEffect(() => {
1178
+ if (!conversationId) return;
1179
+ return client.on("read", ({ conversationId: convId, userId, lastReadAt }) => {
1180
+ if (convId !== conversationId) return;
1181
+ setReadStatuses((prev) => {
1182
+ const existingIndex = prev.findIndex((status) => status.user_id === userId);
1183
+ if (existingIndex < 0) {
1184
+ return [...prev, { user_id: userId, last_read_at: lastReadAt }];
1185
+ }
1186
+ return prev.map(
1187
+ (status) => status.user_id === userId ? { ...status, last_read_at: lastReadAt } : status
1188
+ );
1189
+ });
1190
+ });
1191
+ }, [client, conversationId]);
1192
+ const sendMessage = React4.useCallback(
101
1193
  async (content, options) => {
102
1194
  if (!conversationId) return;
103
1195
  return client.sendMessage(conversationId, { content, ...options });
104
1196
  },
105
1197
  [client, conversationId]
106
1198
  );
107
- const loadMore = react.useCallback(async () => {
1199
+ const loadMore = React4.useCallback(async () => {
108
1200
  if (!conversationId || !messages.length) return;
109
1201
  const oldestId = messages[0]?.id;
110
1202
  const result = await client.getMessages(conversationId, { before: oldestId });
@@ -113,24 +1205,135 @@ function useChat(conversationId) {
113
1205
  setHasMore(result.data.has_more ?? false);
114
1206
  }
115
1207
  }, [client, conversationId, messages]);
116
- const markRead = react.useCallback(async () => {
1208
+ const editMessage = React4.useCallback(
1209
+ async (messageId, content) => {
1210
+ const result = await client.editMessage(messageId, content);
1211
+ if (result.error) {
1212
+ setError(result.error.message);
1213
+ }
1214
+ return result;
1215
+ },
1216
+ [client]
1217
+ );
1218
+ const deleteMessage = React4.useCallback(
1219
+ async (messageId) => {
1220
+ const result = await client.deleteMessage(messageId);
1221
+ if (result.error) {
1222
+ setError(result.error.message);
1223
+ }
1224
+ return result;
1225
+ },
1226
+ [client]
1227
+ );
1228
+ const addReaction = React4.useCallback(
1229
+ async (messageId, emoji) => {
1230
+ const result = await client.addReaction(messageId, emoji);
1231
+ if (result.error) {
1232
+ setError(result.error.message);
1233
+ }
1234
+ return result;
1235
+ },
1236
+ [client]
1237
+ );
1238
+ const removeReaction = React4.useCallback(
1239
+ async (messageId, emoji) => {
1240
+ const result = await client.removeReaction(messageId, emoji);
1241
+ if (result.error) {
1242
+ setError(result.error.message);
1243
+ }
1244
+ return result;
1245
+ },
1246
+ [client]
1247
+ );
1248
+ const uploadAttachment = React4.useCallback(
1249
+ async (file, onProgress, signal) => {
1250
+ const result = await client.uploadAttachment(file, onProgress, signal);
1251
+ if (result.error) {
1252
+ setError(result.error.message);
1253
+ }
1254
+ return result;
1255
+ },
1256
+ [client]
1257
+ );
1258
+ const refreshAttachmentUrl = React4.useCallback(
1259
+ async (messageId, fileId) => {
1260
+ const result = await client.refreshAttachmentUrl(messageId, fileId);
1261
+ if (result.error) {
1262
+ setError(result.error.message);
1263
+ }
1264
+ return result;
1265
+ },
1266
+ [client]
1267
+ );
1268
+ const reportMessage = React4.useCallback(
1269
+ async (messageId, reason, description) => {
1270
+ const result = await client.reportMessage(messageId, reason, description);
1271
+ if (result.error) {
1272
+ setError(result.error.message);
1273
+ }
1274
+ return result;
1275
+ },
1276
+ [client]
1277
+ );
1278
+ const muteConversation = React4.useCallback(
1279
+ async (mutedUntil) => {
1280
+ if (!conversationId) return;
1281
+ const result = await client.muteConversation(conversationId, mutedUntil);
1282
+ if (result.error) {
1283
+ setError(result.error.message);
1284
+ }
1285
+ return result;
1286
+ },
1287
+ [client, conversationId]
1288
+ );
1289
+ const unmuteConversation = React4.useCallback(async () => {
117
1290
  if (!conversationId) return;
118
- await client.markRead(conversationId);
1291
+ const result = await client.unmuteConversation(conversationId);
1292
+ if (result.error) {
1293
+ setError(result.error.message);
1294
+ }
1295
+ return result;
1296
+ }, [client, conversationId]);
1297
+ const getReadStatus = React4.useCallback(async () => {
1298
+ if (!conversationId) return;
1299
+ const result = await client.getReadStatus(conversationId);
1300
+ if (result.data?.statuses) {
1301
+ setReadStatuses(result.data.statuses);
1302
+ } else if (result.error) {
1303
+ setError(result.error.message);
1304
+ }
1305
+ return result;
119
1306
  }, [client, conversationId]);
1307
+ const markRead = React4.useCallback(async () => {
1308
+ if (!conversationId) return;
1309
+ await client.markRead(conversationId);
1310
+ await getReadStatus();
1311
+ }, [client, conversationId, getReadStatus]);
120
1312
  return {
121
1313
  messages,
1314
+ readStatuses,
122
1315
  isLoading,
123
1316
  error,
124
1317
  hasMore,
125
1318
  sendMessage,
126
1319
  loadMore,
1320
+ editMessage,
1321
+ deleteMessage,
1322
+ addReaction,
1323
+ removeReaction,
1324
+ uploadAttachment,
1325
+ refreshAttachmentUrl,
1326
+ reportMessage,
1327
+ muteConversation,
1328
+ unmuteConversation,
1329
+ getReadStatus,
127
1330
  markRead
128
1331
  };
129
1332
  }
130
1333
  function usePresence(conversationId) {
131
1334
  const { client } = useChatContext();
132
- const [members, setMembers] = react.useState([]);
133
- react.useEffect(() => {
1335
+ const [members, setMembers] = React4.useState([]);
1336
+ React4.useEffect(() => {
134
1337
  if (!conversationId) return;
135
1338
  let cancelled = false;
136
1339
  (async () => {
@@ -187,9 +1390,9 @@ function usePresence(conversationId) {
187
1390
  }
188
1391
  function useTyping(conversationId) {
189
1392
  const { client } = useChatContext();
190
- const [typingUsers, setTypingUsers] = react.useState([]);
191
- const typingTimers = react.useRef(/* @__PURE__ */ new Map());
192
- react.useEffect(() => {
1393
+ const [typingUsers, setTypingUsers] = React4.useState([]);
1394
+ const typingTimers = React4.useRef(/* @__PURE__ */ new Map());
1395
+ React4.useEffect(() => {
193
1396
  if (!conversationId) return;
194
1397
  const unsubTyping = client.on("typing", ({ conversationId: convId, userId }) => {
195
1398
  if (convId !== conversationId) return;
@@ -222,7 +1425,7 @@ function useTyping(conversationId) {
222
1425
  typingTimers.current.clear();
223
1426
  };
224
1427
  }, [client, conversationId]);
225
- const sendTyping = react.useCallback(
1428
+ const sendTyping = React4.useCallback(
226
1429
  (isTyping = true) => {
227
1430
  if (!conversationId) return;
228
1431
  client.sendTyping(conversationId, isTyping);
@@ -233,10 +1436,10 @@ function useTyping(conversationId) {
233
1436
  }
234
1437
  function useConversations(options) {
235
1438
  const { client } = useChatContext();
236
- const [conversations, setConversations] = react.useState([]);
237
- const [isLoading, setIsLoading] = react.useState(true);
238
- const refreshTimer = react.useRef(null);
239
- const fetchConversations = react.useCallback(async () => {
1439
+ const [conversations, setConversations] = React4.useState([]);
1440
+ const [isLoading, setIsLoading] = React4.useState(true);
1441
+ const refreshTimer = React4.useRef(null);
1442
+ const fetchConversations = React4.useCallback(async () => {
240
1443
  const result = await client.listConversations({
241
1444
  conversation_type: options?.conversationType
242
1445
  });
@@ -245,10 +1448,10 @@ function useConversations(options) {
245
1448
  }
246
1449
  setIsLoading(false);
247
1450
  }, [client, options?.conversationType]);
248
- react.useEffect(() => {
1451
+ React4.useEffect(() => {
249
1452
  fetchConversations();
250
1453
  }, [fetchConversations]);
251
- react.useEffect(() => {
1454
+ React4.useEffect(() => {
252
1455
  return client.on("inbox:update", () => {
253
1456
  if (refreshTimer.current) clearTimeout(refreshTimer.current);
254
1457
  refreshTimer.current = setTimeout(() => {
@@ -256,14 +1459,14 @@ function useConversations(options) {
256
1459
  }, 500);
257
1460
  });
258
1461
  }, [client, fetchConversations]);
259
- react.useEffect(() => {
1462
+ React4.useEffect(() => {
260
1463
  return client.on("read", ({ conversationId }) => {
261
1464
  setConversations(
262
1465
  (prev) => prev.map((c) => c.id === conversationId ? { ...c, unread_count: 0 } : c)
263
1466
  );
264
1467
  });
265
1468
  }, [client]);
266
- react.useEffect(() => {
1469
+ React4.useEffect(() => {
267
1470
  return () => {
268
1471
  if (refreshTimer.current) clearTimeout(refreshTimer.current);
269
1472
  };
@@ -272,8 +1475,8 @@ function useConversations(options) {
272
1475
  }
273
1476
  function useUnreadCount() {
274
1477
  const { client } = useChatContext();
275
- const [totalUnread, setTotalUnread] = react.useState(0);
276
- react.useEffect(() => {
1478
+ const [totalUnread, setTotalUnread] = React4.useState(0);
1479
+ React4.useEffect(() => {
277
1480
  (async () => {
278
1481
  const result = await client.getUnreadTotal();
279
1482
  if (result.data) {
@@ -281,12 +1484,12 @@ function useUnreadCount() {
281
1484
  }
282
1485
  })();
283
1486
  }, [client]);
284
- react.useEffect(() => {
1487
+ React4.useEffect(() => {
285
1488
  return client.on("inbox:update", () => {
286
1489
  setTotalUnread((prev) => prev + 1);
287
1490
  });
288
1491
  }, [client]);
289
- react.useEffect(() => {
1492
+ React4.useEffect(() => {
290
1493
  return client.on("read", () => {
291
1494
  (async () => {
292
1495
  const result = await client.getUnreadTotal();
@@ -301,11 +1504,18 @@ function useUnreadCount() {
301
1504
 
302
1505
  Object.defineProperty(exports, "ChatClient", {
303
1506
  enumerable: true,
304
- get: function () { return chunkYDLRISR7_cjs.ChatClient; }
1507
+ get: function () { return chunkW2PWFS3E_cjs.ChatClient; }
305
1508
  });
1509
+ exports.ChatInput = ChatInput;
1510
+ exports.ChatMessageItem = ChatMessageItem;
1511
+ exports.ChatMessageList = ChatMessageList;
306
1512
  exports.ChatProvider = ChatProvider;
1513
+ exports.ChatThread = ChatThread;
1514
+ exports.ConversationList = ConversationList;
1515
+ exports.EmojiPicker = EmojiPicker;
307
1516
  exports.useChat = useChat;
308
1517
  exports.useChatClient = useChatClient;
1518
+ exports.useChatConfig = useChatConfig;
309
1519
  exports.useConnection = useConnection;
310
1520
  exports.useConversations = useConversations;
311
1521
  exports.usePresence = usePresence;