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