@scalemule/chat 0.0.4 → 0.0.7

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