@scalemule/chat 0.0.8 → 0.0.9

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 (80) hide show
  1. package/dist/ChatClient-DQPHdUHX.d.cts +107 -0
  2. package/dist/ChatClient-DtUKF-4c.d.ts +107 -0
  3. package/dist/constants.d.ts +9 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/core/ChatClient.d.ts +96 -0
  6. package/dist/core/ChatClient.d.ts.map +1 -0
  7. package/dist/core/EventEmitter.d.ts +11 -0
  8. package/dist/core/EventEmitter.d.ts.map +1 -0
  9. package/dist/core/MessageCache.d.ts +18 -0
  10. package/dist/core/MessageCache.d.ts.map +1 -0
  11. package/dist/core/OfflineQueue.d.ts +19 -0
  12. package/dist/core/OfflineQueue.d.ts.map +1 -0
  13. package/dist/element.d.cts +2 -0
  14. package/dist/element.d.ts +2 -0
  15. package/dist/element.d.ts.map +1 -0
  16. package/dist/embed/index.d.ts +2 -0
  17. package/dist/embed/index.d.ts.map +1 -0
  18. package/dist/factory.d.ts +8 -0
  19. package/dist/factory.d.ts.map +1 -0
  20. package/dist/iframe.d.cts +17 -0
  21. package/dist/iframe.d.ts +15 -0
  22. package/dist/iframe.d.ts.map +1 -0
  23. package/dist/index.d.cts +166 -0
  24. package/dist/index.d.ts +8 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/react-components/ChatInput.d.ts +24 -0
  27. package/dist/react-components/ChatInput.d.ts.map +1 -0
  28. package/dist/react-components/ChatMessageItem.d.ts +24 -0
  29. package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
  30. package/dist/react-components/ChatMessageList.d.ts +31 -0
  31. package/dist/react-components/ChatMessageList.d.ts.map +1 -0
  32. package/dist/react-components/ChatThread.d.ts +19 -0
  33. package/dist/react-components/ChatThread.d.ts.map +1 -0
  34. package/dist/react-components/ConversationList.d.ts +13 -0
  35. package/dist/react-components/ConversationList.d.ts.map +1 -0
  36. package/dist/react-components/EmojiPicker.d.ts +15 -0
  37. package/dist/react-components/EmojiPicker.d.ts.map +1 -0
  38. package/dist/react-components/ReactionBar.d.ts +10 -0
  39. package/dist/react-components/ReactionBar.d.ts.map +1 -0
  40. package/dist/react-components/ReportDialog.d.ts +30 -0
  41. package/dist/react-components/ReportDialog.d.ts.map +1 -0
  42. package/dist/react-components/index.d.ts +10 -0
  43. package/dist/react-components/index.d.ts.map +1 -0
  44. package/dist/react-components/theme.d.ts +19 -0
  45. package/dist/react-components/theme.d.ts.map +1 -0
  46. package/dist/react-components/utils.d.ts +4 -0
  47. package/dist/react-components/utils.d.ts.map +1 -0
  48. package/dist/react.cjs +1833 -459
  49. package/dist/react.d.cts +229 -0
  50. package/dist/react.d.ts +72 -0
  51. package/dist/react.d.ts.map +1 -0
  52. package/dist/react.js +1832 -461
  53. package/dist/shared/ChatController.d.ts +65 -0
  54. package/dist/shared/ChatController.d.ts.map +1 -0
  55. package/dist/shared/upload.d.ts +3 -0
  56. package/dist/shared/upload.d.ts.map +1 -0
  57. package/dist/support-widget.global.js +50 -25
  58. package/dist/support.d.ts +99 -0
  59. package/dist/support.d.ts.map +1 -0
  60. package/dist/transport/HttpTransport.d.ts +20 -0
  61. package/dist/transport/HttpTransport.d.ts.map +1 -0
  62. package/dist/transport/WebSocketTransport.d.ts +78 -0
  63. package/dist/transport/WebSocketTransport.d.ts.map +1 -0
  64. package/dist/types-COPVrm3K.d.cts +256 -0
  65. package/dist/types-COPVrm3K.d.ts +256 -0
  66. package/dist/types.d.ts +271 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/umd.d.ts +6 -0
  69. package/dist/umd.d.ts.map +1 -0
  70. package/dist/version.d.ts +2 -0
  71. package/dist/version.d.ts.map +1 -0
  72. package/dist/widget/icons.d.ts +8 -0
  73. package/dist/widget/icons.d.ts.map +1 -0
  74. package/dist/widget/index.d.ts +2 -0
  75. package/dist/widget/index.d.ts.map +1 -0
  76. package/dist/widget/storage.d.ts +5 -0
  77. package/dist/widget/storage.d.ts.map +1 -0
  78. package/dist/widget/styles.d.ts +3 -0
  79. package/dist/widget/styles.d.ts.map +1 -0
  80. package/package.json +1 -1
package/dist/react.cjs CHANGED
@@ -13,225 +13,562 @@ function ChatInput({
13
13
  onSend,
14
14
  onTypingChange,
15
15
  onUploadAttachment,
16
- placeholder = "Type a message..."
16
+ onDeleteAttachment,
17
+ onValidateFile,
18
+ placeholder = "Type a message...",
19
+ disabled = false,
20
+ maxAttachments = 5,
21
+ accept = "image/*,video/*"
17
22
  }) {
18
- const [content, setContent] = React4.useState("");
23
+ const [text, setText] = React4.useState("");
19
24
  const [attachments, setAttachments] = React4.useState([]);
25
+ const [isDragOver, setIsDragOver] = React4.useState(false);
20
26
  const [isSending, setIsSending] = React4.useState(false);
21
- const [isDragging, setIsDragging] = React4.useState(false);
27
+ const textareaRef = React4.useRef(null);
22
28
  const fileInputRef = React4.useRef(null);
23
- const typingTimeoutRef = React4.useRef(null);
24
- const readyAttachments = React4.useMemo(
25
- () => attachments.filter((attachment) => attachment.attachment).map((attachment) => attachment.attachment),
26
- [attachments]
29
+ const typingTimerRef = React4.useRef(null);
30
+ const dragCounterRef = React4.useRef(0);
31
+ const hasReadyAttachments = attachments.some((a) => a.status === "ready");
32
+ const hasUploadingAttachments = attachments.some(
33
+ (a) => a.status === "uploading"
27
34
  );
28
- const uploadingCount = attachments.filter((attachment) => !attachment.attachment && !attachment.error).length;
29
- const emitTyping = () => {
30
- onTypingChange?.(true);
31
- if (typingTimeoutRef.current) {
32
- clearTimeout(typingTimeoutRef.current);
33
- }
34
- typingTimeoutRef.current = setTimeout(() => {
35
- onTypingChange?.(false);
36
- }, 2500);
37
- };
38
- const handleFiles = async (fileList) => {
39
- if (!onUploadAttachment) return;
40
- const files = Array.from(fileList);
41
- for (const file of files) {
42
- const id = `${file.name}:${file.size}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
43
- setAttachments((current) => [
44
- ...current,
45
- {
46
- id,
47
- fileName: file.name,
48
- progress: 0
35
+ const canSend = (text.trim() || hasReadyAttachments) && !hasUploadingAttachments && !isSending;
36
+ React4.useEffect(() => {
37
+ return () => {
38
+ if (typingTimerRef.current) clearTimeout(typingTimerRef.current);
39
+ for (const att of attachments) {
40
+ att.abortController?.abort();
41
+ URL.revokeObjectURL(att.preview);
42
+ if (att.status === "ready" && att.attachment?.file_id) {
43
+ void onDeleteAttachment?.(att.attachment.file_id);
49
44
  }
50
- ]);
51
- const result = await onUploadAttachment(file, (progress) => {
52
- setAttachments(
53
- (current) => current.map(
54
- (attachment) => attachment.id === id ? { ...attachment, progress } : attachment
55
- )
56
- );
57
- });
58
- setAttachments(
59
- (current) => current.map((attachment) => {
60
- if (attachment.id !== id) return attachment;
45
+ }
46
+ };
47
+ }, []);
48
+ const handleTyping = React4.useCallback(() => {
49
+ if (!onTypingChange) return;
50
+ onTypingChange(true);
51
+ if (typingTimerRef.current) clearTimeout(typingTimerRef.current);
52
+ typingTimerRef.current = setTimeout(() => onTypingChange(false), 2e3);
53
+ }, [onTypingChange]);
54
+ const addFiles = React4.useCallback(
55
+ (files) => {
56
+ if (!onUploadAttachment) return;
57
+ const remaining = maxAttachments - attachments.length;
58
+ const toAdd = files.slice(0, remaining);
59
+ for (const file of toAdd) {
60
+ if (onValidateFile) {
61
+ const validation = onValidateFile(file);
62
+ if (!validation.valid) {
63
+ console.warn("File rejected:", validation.error);
64
+ continue;
65
+ }
66
+ }
67
+ const id = crypto.randomUUID();
68
+ const preview = URL.createObjectURL(file);
69
+ const abortController = new AbortController();
70
+ const pending = {
71
+ id,
72
+ file,
73
+ preview,
74
+ status: "uploading",
75
+ progress: 0,
76
+ abortController
77
+ };
78
+ setAttachments((prev) => [...prev, pending]);
79
+ onUploadAttachment(
80
+ file,
81
+ (progress) => {
82
+ setAttachments(
83
+ (prev) => prev.map(
84
+ (a) => a.id === id ? { ...a, progress } : a
85
+ )
86
+ );
87
+ },
88
+ abortController.signal
89
+ ).then((result) => {
61
90
  if (result?.data) {
62
- return {
63
- ...attachment,
64
- progress: 100,
65
- attachment: result.data
66
- };
91
+ setAttachments(
92
+ (prev) => prev.map(
93
+ (a) => a.id === id ? {
94
+ ...a,
95
+ status: "ready",
96
+ progress: 100,
97
+ attachment: result.data
98
+ } : a
99
+ )
100
+ );
101
+ } else {
102
+ setAttachments(
103
+ (prev) => prev.map(
104
+ (a) => a.id === id ? {
105
+ ...a,
106
+ status: "error",
107
+ error: result?.error?.message ?? "Upload failed"
108
+ } : a
109
+ )
110
+ );
67
111
  }
68
- return {
69
- ...attachment,
70
- error: result?.error?.message ?? "Upload failed"
71
- };
72
- })
73
- );
74
- }
75
- };
76
- const handleSubmit = async () => {
77
- const trimmed = content.trim();
78
- if (!trimmed && !readyAttachments.length || isSending || uploadingCount > 0) return;
112
+ }).catch((err) => {
113
+ if (err.name === "AbortError") return;
114
+ setAttachments(
115
+ (prev) => prev.map(
116
+ (a) => a.id === id ? { ...a, status: "error", error: err.message } : a
117
+ )
118
+ );
119
+ });
120
+ }
121
+ },
122
+ [attachments.length, maxAttachments, onUploadAttachment, onValidateFile]
123
+ );
124
+ const removeAttachment = React4.useCallback(
125
+ (id) => {
126
+ setAttachments((prev) => {
127
+ const att = prev.find((a) => a.id === id);
128
+ if (att) {
129
+ att.abortController?.abort();
130
+ URL.revokeObjectURL(att.preview);
131
+ if (att.status === "ready" && att.attachment?.file_id) {
132
+ void onDeleteAttachment?.(att.attachment.file_id);
133
+ }
134
+ }
135
+ return prev.filter((a) => a.id !== id);
136
+ });
137
+ },
138
+ [onDeleteAttachment]
139
+ );
140
+ const handleSend = React4.useCallback(async () => {
141
+ if (!canSend) return;
142
+ const readyAttachments = attachments.filter((a) => a.status === "ready" && a.attachment).map((a) => a.attachment);
79
143
  setIsSending(true);
80
144
  try {
81
- await onSend(trimmed, readyAttachments);
82
- setContent("");
145
+ await onSend(
146
+ text.trim(),
147
+ readyAttachments.length > 0 ? readyAttachments : void 0
148
+ );
149
+ for (const att of attachments) {
150
+ URL.revokeObjectURL(att.preview);
151
+ }
152
+ setText("");
83
153
  setAttachments([]);
84
- onTypingChange?.(false);
154
+ if (onTypingChange) onTypingChange(false);
155
+ if (typingTimerRef.current) clearTimeout(typingTimerRef.current);
156
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
157
+ textareaRef.current?.focus();
85
158
  } finally {
86
159
  setIsSending(false);
87
160
  }
88
- };
161
+ }, [text, attachments, canSend, onSend, onTypingChange]);
162
+ const handleKeyDown = React4.useCallback(
163
+ (e) => {
164
+ if (e.key === "Enter" && !e.shiftKey) {
165
+ e.preventDefault();
166
+ void handleSend();
167
+ }
168
+ },
169
+ [handleSend]
170
+ );
171
+ const handleChange = React4.useCallback(
172
+ (e) => {
173
+ setText(e.target.value);
174
+ handleTyping();
175
+ const ta = e.target;
176
+ ta.style.height = "auto";
177
+ ta.style.height = Math.min(ta.scrollHeight, 120) + "px";
178
+ },
179
+ [handleTyping]
180
+ );
181
+ const handleDragEnter = React4.useCallback(
182
+ (e) => {
183
+ e.preventDefault();
184
+ dragCounterRef.current++;
185
+ if (e.dataTransfer.types.includes("Files") && onUploadAttachment) {
186
+ setIsDragOver(true);
187
+ }
188
+ },
189
+ [onUploadAttachment]
190
+ );
191
+ const handleDragLeave = React4.useCallback((e) => {
192
+ e.preventDefault();
193
+ dragCounterRef.current--;
194
+ if (dragCounterRef.current === 0) {
195
+ setIsDragOver(false);
196
+ }
197
+ }, []);
198
+ const handleDragOver = React4.useCallback((e) => {
199
+ e.preventDefault();
200
+ }, []);
201
+ const handleDrop = React4.useCallback(
202
+ (e) => {
203
+ e.preventDefault();
204
+ setIsDragOver(false);
205
+ dragCounterRef.current = 0;
206
+ const files = Array.from(e.dataTransfer.files);
207
+ if (files.length > 0) addFiles(files);
208
+ },
209
+ [addFiles]
210
+ );
211
+ const handleFileSelect = React4.useCallback(
212
+ (e) => {
213
+ const files = Array.from(e.target.files ?? []);
214
+ if (files.length > 0) addFiles(files);
215
+ e.target.value = "";
216
+ },
217
+ [addFiles]
218
+ );
89
219
  return /* @__PURE__ */ jsxRuntime.jsxs(
90
220
  "div",
91
221
  {
92
- onDragOver: (event) => {
93
- event.preventDefault();
94
- if (onUploadAttachment) {
95
- setIsDragging(true);
96
- }
97
- },
98
- onDragLeave: () => setIsDragging(false),
99
- onDrop: (event) => {
100
- event.preventDefault();
101
- setIsDragging(false);
102
- void handleFiles(event.dataTransfer.files);
103
- },
104
222
  style: {
223
+ position: "relative",
105
224
  borderTop: "1px solid var(--sm-border-color, #e5e7eb)",
106
- background: isDragging ? "rgba(37, 99, 235, 0.06)" : "var(--sm-surface, #fff)",
107
- padding: 12,
108
- display: "flex",
109
- flexDirection: "column",
110
- gap: 10
225
+ background: "var(--sm-surface, #fff)"
111
226
  },
227
+ onDragEnter: handleDragEnter,
228
+ onDragLeave: handleDragLeave,
229
+ onDragOver: handleDragOver,
230
+ onDrop: handleDrop,
112
231
  children: [
113
- attachments.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsxs(
232
+ isDragOver && /* @__PURE__ */ jsxRuntime.jsx(
114
233
  "div",
115
234
  {
116
235
  style: {
117
- display: "inline-flex",
236
+ position: "absolute",
237
+ inset: 0,
238
+ zIndex: 50,
239
+ background: "rgba(37, 99, 235, 0.1)",
240
+ backdropFilter: "blur(4px)",
241
+ borderRadius: "0 0 var(--sm-border-radius, 16px) var(--sm-border-radius, 16px)",
242
+ display: "flex",
118
243
  alignItems: "center",
244
+ justifyContent: "center"
245
+ },
246
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center" }, children: [
247
+ /* @__PURE__ */ jsxRuntime.jsxs(
248
+ "svg",
249
+ {
250
+ xmlns: "http://www.w3.org/2000/svg",
251
+ width: "32",
252
+ height: "32",
253
+ viewBox: "0 0 24 24",
254
+ fill: "none",
255
+ stroke: "var(--sm-primary, #2563eb)",
256
+ strokeWidth: "2",
257
+ style: { margin: "0 auto 8px" },
258
+ children: [
259
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
260
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "17 8 12 3 7 8" }),
261
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
262
+ ]
263
+ }
264
+ ),
265
+ /* @__PURE__ */ jsxRuntime.jsx(
266
+ "p",
267
+ {
268
+ style: {
269
+ margin: 0,
270
+ color: "var(--sm-primary, #2563eb)",
271
+ fontSize: 14,
272
+ fontWeight: 500
273
+ },
274
+ children: "Drop to attach"
275
+ }
276
+ )
277
+ ] })
278
+ }
279
+ ),
280
+ attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
281
+ "div",
282
+ {
283
+ style: {
284
+ display: "flex",
119
285
  gap: 8,
120
- padding: "6px 10px",
121
- borderRadius: 999,
122
- background: "var(--sm-surface-muted, #f8fafc)",
123
- border: "1px solid var(--sm-border-color, #e5e7eb)",
124
- fontSize: 12
286
+ padding: "8px 12px 4px",
287
+ overflowX: "auto"
288
+ },
289
+ children: attachments.map((att) => /* @__PURE__ */ jsxRuntime.jsxs(
290
+ "div",
291
+ {
292
+ style: {
293
+ position: "relative",
294
+ display: "flex",
295
+ alignItems: "center",
296
+ gap: 6,
297
+ background: att.status === "error" ? "#fef2f2" : "var(--sm-surface-muted, #f8fafc)",
298
+ border: att.status === "error" ? "1px solid #fecaca" : "1px solid var(--sm-border-color, #e5e7eb)",
299
+ borderRadius: 8,
300
+ padding: "6px 8px",
301
+ fontSize: 12,
302
+ flexShrink: 0,
303
+ maxWidth: 180
304
+ },
305
+ children: [
306
+ att.file.type.startsWith("image/") ? /* @__PURE__ */ jsxRuntime.jsx(
307
+ "img",
308
+ {
309
+ src: att.preview,
310
+ alt: "",
311
+ style: {
312
+ width: 32,
313
+ height: 32,
314
+ borderRadius: 4,
315
+ objectFit: "cover",
316
+ flexShrink: 0
317
+ }
318
+ }
319
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
320
+ "div",
321
+ {
322
+ style: {
323
+ width: 32,
324
+ height: 32,
325
+ borderRadius: 4,
326
+ background: "var(--sm-border-color, #e5e7eb)",
327
+ display: "flex",
328
+ alignItems: "center",
329
+ justifyContent: "center",
330
+ flexShrink: 0
331
+ },
332
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
333
+ "svg",
334
+ {
335
+ xmlns: "http://www.w3.org/2000/svg",
336
+ width: "14",
337
+ height: "14",
338
+ viewBox: "0 0 24 24",
339
+ fill: "none",
340
+ stroke: "currentColor",
341
+ strokeWidth: "2",
342
+ style: { color: "var(--sm-muted-text, #6b7280)" },
343
+ children: [
344
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "23 7 16 12 23 17 23 7" }),
345
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2", ry: "2" })
346
+ ]
347
+ }
348
+ )
349
+ }
350
+ ),
351
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
352
+ /* @__PURE__ */ jsxRuntime.jsx(
353
+ "p",
354
+ {
355
+ style: {
356
+ margin: 0,
357
+ overflow: "hidden",
358
+ textOverflow: "ellipsis",
359
+ whiteSpace: "nowrap",
360
+ color: "var(--sm-text-color, #111827)"
361
+ },
362
+ children: att.file.name
363
+ }
364
+ ),
365
+ att.status === "uploading" && /* @__PURE__ */ jsxRuntime.jsx(
366
+ "div",
367
+ {
368
+ style: {
369
+ width: "100%",
370
+ height: 3,
371
+ background: "var(--sm-border-color, #e5e7eb)",
372
+ borderRadius: 999,
373
+ marginTop: 2,
374
+ overflow: "hidden"
375
+ },
376
+ children: /* @__PURE__ */ jsxRuntime.jsx(
377
+ "div",
378
+ {
379
+ style: {
380
+ height: "100%",
381
+ background: "var(--sm-primary, #2563eb)",
382
+ borderRadius: 999,
383
+ transition: "width 0.2s ease",
384
+ width: `${att.progress}%`
385
+ }
386
+ }
387
+ )
388
+ }
389
+ ),
390
+ att.status === "error" && /* @__PURE__ */ jsxRuntime.jsx(
391
+ "p",
392
+ {
393
+ style: {
394
+ margin: 0,
395
+ color: "#dc2626",
396
+ overflow: "hidden",
397
+ textOverflow: "ellipsis",
398
+ whiteSpace: "nowrap"
399
+ },
400
+ children: att.error
401
+ }
402
+ )
403
+ ] }),
404
+ /* @__PURE__ */ jsxRuntime.jsx(
405
+ "button",
406
+ {
407
+ type: "button",
408
+ onClick: () => removeAttachment(att.id),
409
+ style: {
410
+ flexShrink: 0,
411
+ width: 16,
412
+ height: 16,
413
+ display: "flex",
414
+ alignItems: "center",
415
+ justifyContent: "center",
416
+ borderRadius: 999,
417
+ background: "var(--sm-muted-text, #9ca3af)",
418
+ color: "#fff",
419
+ border: "none",
420
+ cursor: "pointer",
421
+ fontSize: 10,
422
+ lineHeight: 1,
423
+ padding: 0
424
+ },
425
+ children: "\xD7"
426
+ }
427
+ )
428
+ ]
429
+ },
430
+ att.id
431
+ ))
432
+ }
433
+ ),
434
+ /* @__PURE__ */ jsxRuntime.jsxs(
435
+ "div",
436
+ {
437
+ style: {
438
+ display: "flex",
439
+ alignItems: "flex-end",
440
+ gap: 8,
441
+ padding: "8px 12px"
125
442
  },
126
443
  children: [
127
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: attachment.fileName }),
128
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: attachment.error ? "#dc2626" : "var(--sm-muted-text, #6b7280)" }, children: attachment.error ?? `${attachment.progress}%` }),
444
+ onUploadAttachment && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
445
+ /* @__PURE__ */ jsxRuntime.jsx(
446
+ "button",
447
+ {
448
+ type: "button",
449
+ onClick: () => fileInputRef.current?.click(),
450
+ disabled: disabled || attachments.length >= maxAttachments,
451
+ "aria-label": "Attach file",
452
+ style: {
453
+ flexShrink: 0,
454
+ width: 32,
455
+ height: 32,
456
+ display: "flex",
457
+ alignItems: "center",
458
+ justifyContent: "center",
459
+ borderRadius: 999,
460
+ border: "none",
461
+ background: "transparent",
462
+ cursor: disabled || attachments.length >= maxAttachments ? "not-allowed" : "pointer",
463
+ color: "var(--sm-muted-text, #6b7280)",
464
+ opacity: disabled || attachments.length >= maxAttachments ? 0.4 : 1,
465
+ marginBottom: 2
466
+ },
467
+ children: /* @__PURE__ */ jsxRuntime.jsx(
468
+ "svg",
469
+ {
470
+ xmlns: "http://www.w3.org/2000/svg",
471
+ width: "18",
472
+ height: "18",
473
+ viewBox: "0 0 24 24",
474
+ fill: "none",
475
+ stroke: "currentColor",
476
+ strokeWidth: "2",
477
+ strokeLinecap: "round",
478
+ strokeLinejoin: "round",
479
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
480
+ }
481
+ )
482
+ }
483
+ ),
484
+ /* @__PURE__ */ jsxRuntime.jsx(
485
+ "input",
486
+ {
487
+ ref: fileInputRef,
488
+ type: "file",
489
+ accept,
490
+ multiple: true,
491
+ style: { display: "none" },
492
+ onChange: handleFileSelect
493
+ }
494
+ )
495
+ ] }),
496
+ /* @__PURE__ */ jsxRuntime.jsx(
497
+ "textarea",
498
+ {
499
+ ref: textareaRef,
500
+ value: text,
501
+ onChange: handleChange,
502
+ onKeyDown: handleKeyDown,
503
+ placeholder,
504
+ disabled,
505
+ rows: 1,
506
+ style: {
507
+ flex: 1,
508
+ fontSize: 14,
509
+ background: "var(--sm-surface-muted, #f8fafc)",
510
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
511
+ borderRadius: 16,
512
+ padding: "8px 16px",
513
+ fontFamily: "var(--sm-font-family, system-ui, -apple-system, sans-serif)",
514
+ color: "var(--sm-text-color, #111827)",
515
+ resize: "none",
516
+ overflow: "hidden",
517
+ outline: "none",
518
+ minHeight: 36,
519
+ lineHeight: "20px",
520
+ opacity: disabled ? 0.5 : 1,
521
+ cursor: disabled ? "not-allowed" : "text",
522
+ boxSizing: "border-box"
523
+ }
524
+ }
525
+ ),
129
526
  /* @__PURE__ */ jsxRuntime.jsx(
130
527
  "button",
131
528
  {
529
+ onClick: () => void handleSend(),
530
+ disabled: disabled || !canSend,
132
531
  type: "button",
133
- onClick: () => {
134
- setAttachments((current) => current.filter((item) => item.id !== attachment.id));
135
- },
532
+ "aria-label": "Send message",
136
533
  style: {
534
+ flexShrink: 0,
535
+ width: 32,
536
+ height: 32,
537
+ display: "flex",
538
+ alignItems: "center",
539
+ justifyContent: "center",
540
+ borderRadius: 999,
137
541
  border: "none",
138
- background: "transparent",
139
- cursor: "pointer",
140
- color: "var(--sm-muted-text, #6b7280)"
542
+ background: "var(--sm-primary, #2563eb)",
543
+ color: "#fff",
544
+ cursor: disabled || !canSend ? "not-allowed" : "pointer",
545
+ opacity: disabled || !canSend ? 0.4 : 1,
546
+ marginBottom: 2,
547
+ transition: "opacity 0.15s ease"
141
548
  },
142
- children: "x"
549
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
550
+ "svg",
551
+ {
552
+ xmlns: "http://www.w3.org/2000/svg",
553
+ width: "16",
554
+ height: "16",
555
+ viewBox: "0 0 24 24",
556
+ fill: "none",
557
+ stroke: "currentColor",
558
+ strokeWidth: "2",
559
+ strokeLinecap: "round",
560
+ strokeLinejoin: "round",
561
+ children: [
562
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
563
+ /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
564
+ ]
565
+ }
566
+ )
143
567
  }
144
568
  )
145
569
  ]
146
- },
147
- attachment.id
148
- )) }) : null,
149
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 10, alignItems: "flex-end" }, children: [
150
- /* @__PURE__ */ jsxRuntime.jsx(
151
- "textarea",
152
- {
153
- value: content,
154
- onChange: (event) => {
155
- setContent(event.target.value);
156
- emitTyping();
157
- },
158
- onKeyDown: (event) => {
159
- if (event.key === "Enter" && !event.shiftKey) {
160
- event.preventDefault();
161
- void handleSubmit();
162
- }
163
- },
164
- rows: 1,
165
- placeholder,
166
- style: {
167
- flex: 1,
168
- minHeight: 44,
169
- maxHeight: 120,
170
- resize: "vertical",
171
- borderRadius: 14,
172
- border: "1px solid var(--sm-border-color, #e5e7eb)",
173
- padding: "12px 14px",
174
- font: "inherit",
175
- color: "var(--sm-text-color, #111827)"
176
- }
177
- }
178
- ),
179
- onUploadAttachment ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
180
- /* @__PURE__ */ jsxRuntime.jsx(
181
- "input",
182
- {
183
- ref: fileInputRef,
184
- type: "file",
185
- hidden: true,
186
- multiple: true,
187
- accept: "image/*,video/*,audio/*",
188
- onChange: (event) => {
189
- if (event.target.files) {
190
- void handleFiles(event.target.files);
191
- event.target.value = "";
192
- }
193
- }
194
- }
195
- ),
196
- /* @__PURE__ */ jsxRuntime.jsx(
197
- "button",
198
- {
199
- type: "button",
200
- onClick: () => fileInputRef.current?.click(),
201
- "aria-label": "Attach files",
202
- style: {
203
- width: 44,
204
- height: 44,
205
- borderRadius: 14,
206
- border: "1px solid var(--sm-border-color, #e5e7eb)",
207
- background: "var(--sm-surface, #fff)",
208
- cursor: "pointer",
209
- color: "var(--sm-text-color, #111827)"
210
- },
211
- children: "+"
212
- }
213
- )
214
- ] }) : null,
215
- /* @__PURE__ */ jsxRuntime.jsx(
216
- "button",
217
- {
218
- type: "button",
219
- onClick: () => void handleSubmit(),
220
- disabled: isSending || uploadingCount > 0 || !content.trim() && !readyAttachments.length,
221
- style: {
222
- height: 44,
223
- padding: "0 16px",
224
- borderRadius: 14,
225
- border: "none",
226
- background: "var(--sm-primary, #2563eb)",
227
- color: "#fff",
228
- cursor: isSending ? "wait" : "pointer",
229
- opacity: isSending || uploadingCount > 0 ? 0.75 : 1
230
- },
231
- children: "Send"
232
- }
233
- )
234
- ] })
570
+ }
571
+ )
235
572
  ]
236
573
  }
237
574
  );
@@ -322,288 +659,870 @@ function EmojiPicker({
322
659
  document.body
323
660
  );
324
661
  }
662
+ function EmojiPickerTrigger({
663
+ onSelect,
664
+ emojis
665
+ }) {
666
+ const [open, setOpen] = React4.useState(false);
667
+ const buttonRef = React4.useRef(null);
668
+ const handleClose = React4.useCallback(() => setOpen(false), []);
669
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
670
+ /* @__PURE__ */ jsxRuntime.jsx(
671
+ "button",
672
+ {
673
+ ref: buttonRef,
674
+ type: "button",
675
+ onClick: () => setOpen(!open),
676
+ "aria-label": "Add reaction",
677
+ style: {
678
+ padding: 6,
679
+ border: "none",
680
+ background: "transparent",
681
+ cursor: "pointer",
682
+ color: "var(--sm-muted-text, #6b7280)",
683
+ borderRadius: 8,
684
+ display: "flex",
685
+ alignItems: "center",
686
+ justifyContent: "center",
687
+ transition: "color 0.15s ease"
688
+ },
689
+ onMouseEnter: (e) => {
690
+ e.target.style.color = "var(--sm-text-color, #111827)";
691
+ },
692
+ onMouseLeave: (e) => {
693
+ e.target.style.color = "var(--sm-muted-text, #6b7280)";
694
+ },
695
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
696
+ "svg",
697
+ {
698
+ xmlns: "http://www.w3.org/2000/svg",
699
+ width: "18",
700
+ height: "18",
701
+ viewBox: "0 0 24 24",
702
+ fill: "none",
703
+ stroke: "currentColor",
704
+ strokeWidth: "2",
705
+ strokeLinecap: "round",
706
+ strokeLinejoin: "round",
707
+ children: [
708
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
709
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
710
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "9", x2: "9.01", y2: "9" }),
711
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "15", y1: "9", x2: "15.01", y2: "9" })
712
+ ]
713
+ }
714
+ )
715
+ }
716
+ ),
717
+ open && /* @__PURE__ */ jsxRuntime.jsx(
718
+ EmojiPicker,
719
+ {
720
+ onSelect,
721
+ onClose: handleClose,
722
+ anchorRef: buttonRef,
723
+ emojis
724
+ }
725
+ )
726
+ ] });
727
+ }
728
+ function ReactionBar({
729
+ reactions,
730
+ currentUserId,
731
+ onToggleReaction
732
+ }) {
733
+ if (!reactions || reactions.length === 0) return null;
734
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginTop: 4 }, children: reactions.map((r) => {
735
+ const hasReacted = currentUserId ? r.user_ids.includes(currentUserId) : false;
736
+ return /* @__PURE__ */ jsxRuntime.jsxs(
737
+ "button",
738
+ {
739
+ onClick: () => onToggleReaction(r.emoji),
740
+ type: "button",
741
+ style: {
742
+ display: "inline-flex",
743
+ alignItems: "center",
744
+ gap: 4,
745
+ padding: "2px 8px",
746
+ borderRadius: 999,
747
+ fontSize: 12,
748
+ border: hasReacted ? "1px solid var(--sm-reaction-active-border, rgba(37, 99, 235, 0.4))" : "1px solid var(--sm-border-color, #e5e7eb)",
749
+ background: hasReacted ? "var(--sm-reaction-active-bg, rgba(37, 99, 235, 0.08))" : "var(--sm-surface-muted, #f8fafc)",
750
+ color: hasReacted ? "var(--sm-primary, #2563eb)" : "var(--sm-text-color, #111827)",
751
+ cursor: "pointer",
752
+ lineHeight: "18px"
753
+ },
754
+ children: [
755
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: r.emoji }),
756
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: r.count })
757
+ ]
758
+ },
759
+ r.emoji
760
+ );
761
+ }) });
762
+ }
325
763
 
326
764
  // src/react-components/utils.ts
327
765
  var timeFormatter = new Intl.DateTimeFormat(void 0, {
328
766
  hour: "numeric",
329
767
  minute: "2-digit"
330
768
  });
331
- var dateFormatter = new Intl.DateTimeFormat(void 0, {
332
- month: "short",
333
- day: "numeric",
334
- year: "numeric"
335
- });
336
769
  function formatMessageTime(value) {
337
770
  if (!value) return "";
338
771
  return timeFormatter.format(new Date(value));
339
772
  }
340
- function formatDayLabel(value) {
341
- if (!value) return "";
342
- return dateFormatter.format(new Date(value));
343
- }
344
- function isSameDay(left, right) {
345
- if (!left || !right) return false;
346
- const leftDate = new Date(left);
347
- const rightDate = new Date(right);
348
- return leftDate.getFullYear() === rightDate.getFullYear() && leftDate.getMonth() === rightDate.getMonth() && leftDate.getDate() === rightDate.getDate();
773
+ function useAttachmentUrl(fileId, hasUrl, fetcher) {
774
+ const [url, setUrl] = React4.useState(null);
775
+ React4.useEffect(() => {
776
+ if (hasUrl || !fileId || !fetcher) return;
777
+ fetcher(fileId).then((viewUrl) => {
778
+ if (viewUrl) setUrl(viewUrl);
779
+ }).catch(() => {
780
+ });
781
+ }, [fileId, hasUrl, fetcher]);
782
+ return url;
349
783
  }
350
- function renderAttachment(messageId, attachment) {
351
- const key = `${messageId}:${attachment.file_id}`;
352
- const url = attachment.presigned_url;
353
- if (!url) {
354
- return /* @__PURE__ */ jsxRuntime.jsx(
355
- "div",
356
- {
357
- style: {
358
- padding: "10px 12px",
359
- borderRadius: 12,
360
- background: "rgba(255,255,255,0.16)",
361
- fontSize: 13
362
- },
363
- children: attachment.file_name
364
- },
365
- key
366
- );
367
- }
368
- if (attachment.mime_type.startsWith("image/")) {
369
- return /* @__PURE__ */ jsxRuntime.jsx(
370
- "img",
371
- {
372
- src: url,
373
- alt: attachment.file_name,
374
- loading: "lazy",
375
- style: {
376
- display: "block",
377
- maxWidth: "100%",
378
- borderRadius: 12,
379
- marginTop: 8
784
+ function AttachmentRenderer({
785
+ att,
786
+ fetcher
787
+ }) {
788
+ const [expanded, setExpanded] = React4.useState(false);
789
+ const fetchedUrl = useAttachmentUrl(att.file_id, !!att.presigned_url, fetcher);
790
+ const viewUrl = att.presigned_url || fetchedUrl;
791
+ const isImage = att.mime_type?.startsWith("image/");
792
+ const isVideo = att.mime_type?.startsWith("video/");
793
+ const isAudio = att.mime_type?.startsWith("audio/");
794
+ if (isImage && viewUrl) {
795
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
796
+ /* @__PURE__ */ jsxRuntime.jsx(
797
+ "img",
798
+ {
799
+ src: viewUrl,
800
+ alt: att.file_name,
801
+ loading: "lazy",
802
+ onClick: () => setExpanded(true),
803
+ style: {
804
+ display: "block",
805
+ maxHeight: 240,
806
+ maxWidth: "100%",
807
+ objectFit: "contain",
808
+ cursor: "pointer",
809
+ borderRadius: 8
810
+ }
380
811
  }
381
- },
382
- key
383
- );
812
+ ),
813
+ expanded && /* @__PURE__ */ jsxRuntime.jsx(
814
+ "div",
815
+ {
816
+ onClick: () => setExpanded(false),
817
+ style: {
818
+ position: "fixed",
819
+ inset: 0,
820
+ zIndex: 9999,
821
+ background: "rgba(0, 0, 0, 0.8)",
822
+ display: "flex",
823
+ alignItems: "center",
824
+ justifyContent: "center",
825
+ padding: 16,
826
+ cursor: "pointer"
827
+ },
828
+ children: /* @__PURE__ */ jsxRuntime.jsx(
829
+ "img",
830
+ {
831
+ src: viewUrl,
832
+ alt: att.file_name,
833
+ style: {
834
+ maxWidth: "100%",
835
+ maxHeight: "100%",
836
+ objectFit: "contain",
837
+ borderRadius: 8
838
+ }
839
+ }
840
+ )
841
+ }
842
+ )
843
+ ] });
384
844
  }
385
- if (attachment.mime_type.startsWith("video/")) {
845
+ if (isVideo && viewUrl) {
386
846
  return /* @__PURE__ */ jsxRuntime.jsx(
387
847
  "video",
388
848
  {
849
+ src: viewUrl,
389
850
  controls: true,
390
- src: url,
851
+ preload: "metadata",
852
+ poster: att.thumbnail_url,
391
853
  style: {
392
854
  display: "block",
393
- width: "100%",
394
- maxWidth: 320,
395
- borderRadius: 12,
396
- marginTop: 8
855
+ maxHeight: 240,
856
+ maxWidth: "100%",
857
+ borderRadius: 8
397
858
  }
398
- },
399
- key
859
+ }
400
860
  );
401
861
  }
402
- if (attachment.mime_type.startsWith("audio/")) {
862
+ if (isAudio && viewUrl) {
403
863
  return /* @__PURE__ */ jsxRuntime.jsx(
404
864
  "audio",
405
865
  {
866
+ src: viewUrl,
406
867
  controls: true,
407
- src: url,
868
+ style: { display: "block", width: "100%", marginTop: 4 }
869
+ }
870
+ );
871
+ }
872
+ if (isImage && !viewUrl) {
873
+ return /* @__PURE__ */ jsxRuntime.jsxs(
874
+ "div",
875
+ {
408
876
  style: {
409
- display: "block",
410
- width: "100%",
411
- marginTop: 8
412
- }
413
- },
414
- key
877
+ display: "flex",
878
+ alignItems: "center",
879
+ gap: 8,
880
+ padding: "8px 12px",
881
+ fontSize: 12,
882
+ color: "var(--sm-muted-text, #6b7280)",
883
+ background: "var(--sm-surface-muted, #f8fafc)",
884
+ borderRadius: 8
885
+ },
886
+ children: [
887
+ /* @__PURE__ */ jsxRuntime.jsx(
888
+ "div",
889
+ {
890
+ style: {
891
+ width: 16,
892
+ height: 16,
893
+ border: "2px solid var(--sm-border-color, #e5e7eb)",
894
+ borderTopColor: "var(--sm-primary, #2563eb)",
895
+ borderRadius: 999,
896
+ animation: "sm-spin 0.8s linear infinite"
897
+ }
898
+ }
899
+ ),
900
+ "Loading image..."
901
+ ]
902
+ }
415
903
  );
416
904
  }
417
- return /* @__PURE__ */ jsxRuntime.jsx(
418
- "a",
905
+ return /* @__PURE__ */ jsxRuntime.jsxs(
906
+ "div",
419
907
  {
420
- href: url,
421
- target: "_blank",
422
- rel: "noreferrer",
423
908
  style: {
424
- display: "inline-block",
425
- marginTop: 8,
426
- color: "inherit",
427
- fontSize: 13
909
+ display: "flex",
910
+ alignItems: "center",
911
+ gap: 8,
912
+ padding: "8px 12px",
913
+ fontSize: 14,
914
+ color: "var(--sm-text-color, #111827)",
915
+ background: "var(--sm-surface-muted, #f8fafc)",
916
+ borderRadius: 8,
917
+ border: "1px solid var(--sm-border-color, #e5e7eb)"
428
918
  },
429
- children: attachment.file_name
430
- },
431
- key
919
+ children: [
920
+ /* @__PURE__ */ jsxRuntime.jsxs(
921
+ "svg",
922
+ {
923
+ xmlns: "http://www.w3.org/2000/svg",
924
+ width: "16",
925
+ height: "16",
926
+ viewBox: "0 0 24 24",
927
+ fill: "none",
928
+ stroke: "currentColor",
929
+ strokeWidth: "2",
930
+ children: [
931
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
932
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "14 2 14 8 20 8" })
933
+ ]
934
+ }
935
+ ),
936
+ viewUrl ? /* @__PURE__ */ jsxRuntime.jsx(
937
+ "a",
938
+ {
939
+ href: viewUrl,
940
+ target: "_blank",
941
+ rel: "noreferrer",
942
+ style: { color: "inherit", textDecoration: "underline" },
943
+ children: att.file_name
944
+ }
945
+ ) : att.file_name
946
+ ]
947
+ }
432
948
  );
433
949
  }
434
950
  function ChatMessageItem({
435
951
  message,
436
952
  currentUserId,
953
+ conversationId,
954
+ profile,
437
955
  onAddReaction,
438
956
  onRemoveReaction,
957
+ onEdit,
958
+ onDelete,
439
959
  onReport,
960
+ onFetchAttachmentUrl,
961
+ isOwnMessage: isOwnMessageProp,
440
962
  highlight = false
441
963
  }) {
442
- const [showPicker, setShowPicker] = React4.useState(false);
443
- const isOwn = Boolean(currentUserId && message.sender_id === currentUserId);
964
+ const [showActions, setShowActions] = React4.useState(false);
965
+ const [editing, setEditing] = React4.useState(false);
966
+ const [editContent, setEditContent] = React4.useState(message.content);
967
+ const isOwn = isOwnMessageProp !== void 0 ? isOwnMessageProp : Boolean(currentUserId && message.sender_id === currentUserId);
968
+ const displayName = profile?.display_name ?? "User";
969
+ const username = profile?.username;
970
+ const avatarUrl = profile?.avatar_url;
971
+ const initials = displayName.charAt(0).toUpperCase();
444
972
  const canReact = Boolean(onAddReaction || onRemoveReaction);
445
- const reactionEntries = React4.useMemo(() => message.reactions ?? [], [message.reactions]);
973
+ function handleToggleReaction(emoji) {
974
+ const hasReacted = message.reactions?.some(
975
+ (r) => r.emoji === emoji && currentUserId && r.user_ids.includes(currentUserId)
976
+ );
977
+ if (hasReacted) {
978
+ void onRemoveReaction?.(message.id, emoji);
979
+ } else {
980
+ void onAddReaction?.(message.id, emoji);
981
+ }
982
+ }
983
+ function handleSaveEdit() {
984
+ if (editContent.trim() && editContent !== message.content) {
985
+ void onEdit?.(message.id, editContent.trim());
986
+ }
987
+ setEditing(false);
988
+ }
989
+ if (message.message_type === "system") {
990
+ return /* @__PURE__ */ jsxRuntime.jsx(
991
+ "div",
992
+ {
993
+ style: {
994
+ textAlign: "center",
995
+ fontSize: 12,
996
+ color: "var(--sm-muted-text, #6b7280)",
997
+ padding: "8px 0",
998
+ fontStyle: "italic"
999
+ },
1000
+ children: message.content
1001
+ }
1002
+ );
1003
+ }
446
1004
  return /* @__PURE__ */ jsxRuntime.jsxs(
447
1005
  "div",
448
1006
  {
449
1007
  style: {
450
1008
  display: "flex",
451
- flexDirection: "column",
452
- alignItems: isOwn ? "flex-end" : "flex-start",
453
- gap: 6
1009
+ justifyContent: isOwn ? "flex-end" : "flex-start",
1010
+ padding: "3px 16px",
1011
+ position: "relative"
454
1012
  },
1013
+ onMouseEnter: () => setShowActions(true),
1014
+ onMouseLeave: () => setShowActions(false),
455
1015
  children: [
456
- /* @__PURE__ */ jsxRuntime.jsxs(
457
- "div",
458
- {
459
- style: {
460
- maxWidth: "min(82%, 560px)",
461
- padding: message.attachments?.length ? 10 : "10px 12px",
462
- borderRadius: "var(--sm-border-radius, 16px)",
463
- background: isOwn ? "var(--sm-own-bubble, #2563eb)" : "var(--sm-other-bubble, #f3f4f6)",
464
- color: isOwn ? "var(--sm-own-text, #fff)" : "var(--sm-other-text, #111827)",
465
- boxShadow: highlight ? "0 0 0 2px rgba(37, 99, 235, 0.22)" : "none",
466
- transition: "box-shadow 0.2s ease"
467
- },
468
- children: [
469
- message.content ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word", fontSize: 14 }, children: message.content }) : null,
470
- message.attachments?.map((attachment) => renderAttachment(message.id, attachment))
471
- ]
472
- }
473
- ),
474
- /* @__PURE__ */ jsxRuntime.jsxs(
1016
+ !isOwn && /* @__PURE__ */ jsxRuntime.jsx(
475
1017
  "div",
476
1018
  {
477
1019
  style: {
1020
+ flexShrink: 0,
1021
+ width: 32,
1022
+ height: 32,
1023
+ borderRadius: 999,
1024
+ background: "var(--sm-surface-muted, #f3f4f6)",
1025
+ overflow: "hidden",
478
1026
  display: "flex",
479
1027
  alignItems: "center",
480
- gap: 8,
1028
+ justifyContent: "center",
1029
+ fontSize: 12,
1030
+ fontWeight: 500,
481
1031
  color: "var(--sm-muted-text, #6b7280)",
482
- fontSize: 12
1032
+ marginRight: 10,
1033
+ marginTop: 2
483
1034
  },
484
- children: [
485
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatMessageTime(message.created_at) }),
486
- message.is_edited ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "edited" }) : null,
487
- onReport && !isOwn ? /* @__PURE__ */ jsxRuntime.jsx(
488
- "button",
489
- {
490
- type: "button",
491
- onClick: () => void onReport(message.id),
492
- style: {
493
- border: "none",
494
- background: "transparent",
495
- color: "inherit",
496
- cursor: "pointer",
497
- fontSize: 12,
498
- padding: 0
499
- },
500
- children: "Report"
501
- }
502
- ) : null,
503
- canReact ? /* @__PURE__ */ jsxRuntime.jsx(
504
- "button",
505
- {
506
- type: "button",
507
- onClick: () => setShowPicker((value) => !value),
508
- style: {
509
- border: "none",
510
- background: "transparent",
511
- color: "inherit",
512
- cursor: "pointer",
513
- fontSize: 12,
514
- padding: 0
515
- },
516
- children: "React"
517
- }
518
- ) : null
519
- ]
1035
+ children: avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx(
1036
+ "img",
1037
+ {
1038
+ src: avatarUrl,
1039
+ alt: displayName,
1040
+ style: { width: "100%", height: "100%", objectFit: "cover" }
1041
+ }
1042
+ ) : initials
520
1043
  }
521
1044
  ),
522
- showPicker && canReact ? /* @__PURE__ */ jsxRuntime.jsx(
523
- EmojiPicker,
524
- {
525
- onSelect: (emoji) => {
526
- setShowPicker(false);
527
- void onAddReaction?.(message.id, emoji);
1045
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", maxWidth: "75%", minWidth: 0 }, children: [
1046
+ showActions && canReact && /* @__PURE__ */ jsxRuntime.jsxs(
1047
+ "div",
1048
+ {
1049
+ style: {
1050
+ position: "absolute",
1051
+ top: -12,
1052
+ [isOwn ? "right" : "left"]: 4,
1053
+ zIndex: 50,
1054
+ display: "flex",
1055
+ alignItems: "center",
1056
+ gap: 2,
1057
+ background: "var(--sm-surface, #fff)",
1058
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
1059
+ borderRadius: 8,
1060
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
1061
+ padding: "2px 4px"
1062
+ },
1063
+ children: [
1064
+ /* @__PURE__ */ jsxRuntime.jsx(
1065
+ EmojiPickerTrigger,
1066
+ {
1067
+ onSelect: (emoji) => void onAddReaction?.(message.id, emoji)
1068
+ }
1069
+ ),
1070
+ !isOwn && onReport && /* @__PURE__ */ jsxRuntime.jsx(
1071
+ "button",
1072
+ {
1073
+ onClick: () => {
1074
+ setShowActions(false);
1075
+ onReport(message.id);
1076
+ },
1077
+ type: "button",
1078
+ "aria-label": "Report",
1079
+ title: "Report message",
1080
+ style: {
1081
+ padding: 6,
1082
+ border: "none",
1083
+ background: "transparent",
1084
+ cursor: "pointer",
1085
+ color: "var(--sm-muted-text, #6b7280)",
1086
+ borderRadius: 8,
1087
+ display: "flex",
1088
+ alignItems: "center",
1089
+ justifyContent: "center"
1090
+ },
1091
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1092
+ "svg",
1093
+ {
1094
+ xmlns: "http://www.w3.org/2000/svg",
1095
+ width: "14",
1096
+ height: "14",
1097
+ viewBox: "0 0 24 24",
1098
+ fill: "none",
1099
+ stroke: "currentColor",
1100
+ strokeWidth: "2",
1101
+ children: [
1102
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
1103
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4", y1: "22", x2: "4", y2: "15" })
1104
+ ]
1105
+ }
1106
+ )
1107
+ }
1108
+ ),
1109
+ isOwn && onEdit && /* @__PURE__ */ jsxRuntime.jsx(
1110
+ "button",
1111
+ {
1112
+ onClick: () => setEditing(true),
1113
+ type: "button",
1114
+ "aria-label": "Edit",
1115
+ style: {
1116
+ padding: 6,
1117
+ border: "none",
1118
+ background: "transparent",
1119
+ cursor: "pointer",
1120
+ color: "var(--sm-muted-text, #6b7280)",
1121
+ borderRadius: 8,
1122
+ display: "flex",
1123
+ alignItems: "center",
1124
+ justifyContent: "center"
1125
+ },
1126
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1127
+ "svg",
1128
+ {
1129
+ xmlns: "http://www.w3.org/2000/svg",
1130
+ width: "14",
1131
+ height: "14",
1132
+ viewBox: "0 0 24 24",
1133
+ fill: "none",
1134
+ stroke: "currentColor",
1135
+ strokeWidth: "2",
1136
+ children: [
1137
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
1138
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
1139
+ ]
1140
+ }
1141
+ )
1142
+ }
1143
+ ),
1144
+ isOwn && onDelete && /* @__PURE__ */ jsxRuntime.jsx(
1145
+ "button",
1146
+ {
1147
+ onClick: () => void onDelete(message.id),
1148
+ type: "button",
1149
+ "aria-label": "Delete",
1150
+ style: {
1151
+ padding: 6,
1152
+ border: "none",
1153
+ background: "transparent",
1154
+ cursor: "pointer",
1155
+ color: "var(--sm-muted-text, #6b7280)",
1156
+ borderRadius: 8,
1157
+ display: "flex",
1158
+ alignItems: "center",
1159
+ justifyContent: "center"
1160
+ },
1161
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1162
+ "svg",
1163
+ {
1164
+ xmlns: "http://www.w3.org/2000/svg",
1165
+ width: "14",
1166
+ height: "14",
1167
+ viewBox: "0 0 24 24",
1168
+ fill: "none",
1169
+ stroke: "currentColor",
1170
+ strokeWidth: "2",
1171
+ children: [
1172
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "3 6 5 6 21 6" }),
1173
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
1174
+ ]
1175
+ }
1176
+ )
1177
+ }
1178
+ )
1179
+ ]
528
1180
  }
529
- }
530
- ) : null,
531
- reactionEntries.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: reactionEntries.map((reaction) => {
532
- const reacted = Boolean(currentUserId && reaction.user_ids.includes(currentUserId));
533
- return /* @__PURE__ */ jsxRuntime.jsxs(
534
- "button",
1181
+ ),
1182
+ /* @__PURE__ */ jsxRuntime.jsxs(
1183
+ "div",
535
1184
  {
536
- type: "button",
537
- onClick: () => {
538
- if (reacted) {
539
- void onRemoveReaction?.(message.id, reaction.emoji);
540
- return;
541
- }
542
- void onAddReaction?.(message.id, reaction.emoji);
1185
+ style: {
1186
+ borderRadius: "var(--sm-border-radius, 16px)",
1187
+ ...isOwn ? { borderBottomRightRadius: 6 } : { borderBottomLeftRadius: 6 },
1188
+ padding: "8px 14px",
1189
+ background: isOwn ? "var(--sm-own-bubble-bg, var(--sm-own-bubble, var(--sm-primary, #2563eb)))" : "var(--sm-other-bubble-bg, var(--sm-other-bubble, #f3f4f6))",
1190
+ color: isOwn ? "var(--sm-own-bubble-text, var(--sm-own-text, #ffffff))" : "var(--sm-other-bubble-text, var(--sm-other-text, #111827))",
1191
+ fontSize: "var(--sm-font-size, 14px)",
1192
+ fontFamily: "var(--sm-font-family, system-ui, -apple-system, sans-serif)",
1193
+ boxShadow: highlight ? "0 0 0 2px rgba(37, 99, 235, 0.22)" : "none",
1194
+ transition: "box-shadow 0.2s ease"
543
1195
  },
1196
+ children: [
1197
+ !isOwn && /* @__PURE__ */ jsxRuntime.jsxs(
1198
+ "div",
1199
+ {
1200
+ style: {
1201
+ display: "flex",
1202
+ alignItems: "baseline",
1203
+ gap: 6,
1204
+ marginBottom: 2
1205
+ },
1206
+ children: [
1207
+ /* @__PURE__ */ jsxRuntime.jsx(
1208
+ "span",
1209
+ {
1210
+ style: {
1211
+ fontSize: 12,
1212
+ fontWeight: 600,
1213
+ color: "var(--sm-other-bubble-text, var(--sm-other-text, #111827))"
1214
+ },
1215
+ children: displayName
1216
+ }
1217
+ ),
1218
+ username && /* @__PURE__ */ jsxRuntime.jsxs(
1219
+ "span",
1220
+ {
1221
+ style: {
1222
+ fontSize: 12,
1223
+ color: "var(--sm-muted-text, #9ca3af)"
1224
+ },
1225
+ children: [
1226
+ "@",
1227
+ username
1228
+ ]
1229
+ }
1230
+ )
1231
+ ]
1232
+ }
1233
+ ),
1234
+ editing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8 }, children: [
1235
+ /* @__PURE__ */ jsxRuntime.jsx(
1236
+ "input",
1237
+ {
1238
+ type: "text",
1239
+ value: editContent,
1240
+ onChange: (e) => setEditContent(e.target.value),
1241
+ onKeyDown: (e) => {
1242
+ if (e.key === "Enter") handleSaveEdit();
1243
+ if (e.key === "Escape") setEditing(false);
1244
+ },
1245
+ autoFocus: true,
1246
+ style: {
1247
+ flex: 1,
1248
+ fontSize: 14,
1249
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
1250
+ borderRadius: 6,
1251
+ padding: "4px 8px",
1252
+ outline: "none",
1253
+ color: "var(--sm-text-color, #111827)",
1254
+ background: "var(--sm-surface, #fff)"
1255
+ }
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ jsxRuntime.jsx(
1259
+ "button",
1260
+ {
1261
+ onClick: handleSaveEdit,
1262
+ type: "button",
1263
+ style: {
1264
+ fontSize: 12,
1265
+ fontWeight: 500,
1266
+ border: "none",
1267
+ background: "transparent",
1268
+ cursor: "pointer",
1269
+ color: isOwn ? "rgba(255,255,255,0.7)" : "var(--sm-primary, #2563eb)"
1270
+ },
1271
+ children: "Save"
1272
+ }
1273
+ ),
1274
+ /* @__PURE__ */ jsxRuntime.jsx(
1275
+ "button",
1276
+ {
1277
+ onClick: () => setEditing(false),
1278
+ type: "button",
1279
+ style: {
1280
+ fontSize: 12,
1281
+ border: "none",
1282
+ background: "transparent",
1283
+ cursor: "pointer",
1284
+ color: isOwn ? "rgba(255,255,255,0.7)" : "var(--sm-muted-text, #6b7280)"
1285
+ },
1286
+ children: "Cancel"
1287
+ }
1288
+ )
1289
+ ] }) : message.content ? /* @__PURE__ */ jsxRuntime.jsx(
1290
+ "p",
1291
+ {
1292
+ style: {
1293
+ margin: 0,
1294
+ whiteSpace: "pre-wrap",
1295
+ wordBreak: "break-word"
1296
+ },
1297
+ children: message.content
1298
+ }
1299
+ ) : null,
1300
+ /* @__PURE__ */ jsxRuntime.jsxs(
1301
+ "div",
1302
+ {
1303
+ style: {
1304
+ display: "flex",
1305
+ alignItems: "center",
1306
+ gap: 4,
1307
+ marginTop: 2,
1308
+ justifyContent: isOwn ? "flex-end" : "flex-start"
1309
+ },
1310
+ children: [
1311
+ /* @__PURE__ */ jsxRuntime.jsx(
1312
+ "span",
1313
+ {
1314
+ style: {
1315
+ fontSize: 10,
1316
+ color: isOwn ? "rgba(255,255,255,0.6)" : "var(--sm-muted-text, #9ca3af)"
1317
+ },
1318
+ children: formatMessageTime(message.created_at)
1319
+ }
1320
+ ),
1321
+ message.is_edited && /* @__PURE__ */ jsxRuntime.jsx(
1322
+ "span",
1323
+ {
1324
+ style: {
1325
+ fontSize: 10,
1326
+ fontStyle: "italic",
1327
+ color: isOwn ? "rgba(255,255,255,0.6)" : "var(--sm-muted-text, #9ca3af)"
1328
+ },
1329
+ children: "(edited)"
1330
+ }
1331
+ )
1332
+ ]
1333
+ }
1334
+ )
1335
+ ]
1336
+ }
1337
+ ),
1338
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1339
+ "div",
1340
+ {
544
1341
  style: {
545
- border: reacted ? "1px solid rgba(37, 99, 235, 0.4)" : "1px solid var(--sm-border-color, #e5e7eb)",
546
- background: reacted ? "rgba(37, 99, 235, 0.08)" : "var(--sm-surface, #fff)",
547
- color: "var(--sm-text-color, #111827)",
548
- borderRadius: 999,
549
- padding: "4px 8px",
550
- cursor: "pointer",
551
- fontSize: 12
1342
+ marginTop: 4,
1343
+ display: "flex",
1344
+ flexWrap: "wrap",
1345
+ gap: 8,
1346
+ justifyContent: isOwn ? "flex-end" : "flex-start"
552
1347
  },
553
- children: [
554
- reaction.emoji,
555
- " ",
556
- reaction.count
557
- ]
558
- },
559
- `${message.id}:${reaction.emoji}`
560
- );
561
- }) }) : null
1348
+ children: message.attachments.map((att) => /* @__PURE__ */ jsxRuntime.jsx(
1349
+ "div",
1350
+ {
1351
+ style: {
1352
+ borderRadius: 8,
1353
+ overflow: "hidden",
1354
+ maxWidth: 320
1355
+ },
1356
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1357
+ AttachmentRenderer,
1358
+ {
1359
+ att,
1360
+ fetcher: onFetchAttachmentUrl
1361
+ }
1362
+ )
1363
+ },
1364
+ att.file_id
1365
+ ))
1366
+ }
1367
+ ),
1368
+ /* @__PURE__ */ jsxRuntime.jsx(
1369
+ ReactionBar,
1370
+ {
1371
+ reactions: message.reactions ?? [],
1372
+ currentUserId,
1373
+ onToggleReaction: handleToggleReaction
1374
+ }
1375
+ )
1376
+ ] })
562
1377
  ]
563
1378
  }
564
1379
  );
565
1380
  }
566
- function getUnreadIndex(messages, unreadSince) {
567
- if (!unreadSince) return -1;
568
- return messages.findIndex((message) => new Date(message.created_at).getTime() > new Date(unreadSince).getTime());
1381
+ function getDateLabel(dateStr) {
1382
+ const date = new Date(dateStr);
1383
+ const now = /* @__PURE__ */ new Date();
1384
+ const dateDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
1385
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1386
+ const diffDays = Math.round(
1387
+ (today.getTime() - dateDay.getTime()) / (1e3 * 60 * 60 * 24)
1388
+ );
1389
+ if (diffDays === 0) return "Today";
1390
+ if (diffDays === 1) return "Yesterday";
1391
+ return date.toLocaleDateString(void 0, {
1392
+ month: "short",
1393
+ day: "numeric",
1394
+ year: date.getFullYear() !== now.getFullYear() ? "numeric" : void 0
1395
+ });
1396
+ }
1397
+ function isSameDay(a, b) {
1398
+ return new Date(a).toDateString() === new Date(b).toDateString();
569
1399
  }
570
1400
  function ChatMessageList({
571
1401
  messages,
572
1402
  currentUserId,
573
- unreadSince,
574
- scrollToUnreadOnMount = true,
1403
+ conversationId,
1404
+ profiles,
1405
+ hasMore = false,
1406
+ isLoading = false,
1407
+ onLoadMore,
575
1408
  onAddReaction,
576
1409
  onRemoveReaction,
1410
+ onEdit,
1411
+ onDelete,
577
1412
  onReport,
1413
+ onFetchAttachmentUrl,
1414
+ firstUnreadMessageId,
1415
+ unreadSince,
1416
+ isNearBottom: isNearBottomProp,
1417
+ onReachBottom,
578
1418
  emptyState
579
1419
  }) {
580
1420
  const containerRef = React4.useRef(null);
581
- const unreadMarkerRef = React4.useRef(null);
582
- const lastMessageCountRef = React4.useRef(messages.length);
583
- const didScrollToUnreadRef = React4.useRef(false);
584
- const [showJumpToLatest, setShowJumpToLatest] = React4.useState(false);
585
- const unreadIndex = React4.useMemo(() => getUnreadIndex(messages, unreadSince), [messages, unreadSince]);
1421
+ const bottomRef = React4.useRef(null);
1422
+ const unreadDividerRef = React4.useRef(null);
1423
+ const prevLengthRef = React4.useRef(messages.length);
1424
+ const isNearBottomRef = React4.useRef(true);
1425
+ const [showNewMessagesPill, setShowNewMessagesPill] = React4.useState(false);
1426
+ const resolvedFirstUnreadId = firstUnreadMessageId ?? (unreadSince ? messages.find(
1427
+ (m) => new Date(m.created_at).getTime() > new Date(unreadSince).getTime()
1428
+ )?.id : void 0);
586
1429
  React4.useEffect(() => {
587
- const container = containerRef.current;
588
- if (!container) return;
589
- const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
590
- const shouldStickToBottom = distanceFromBottom < 80;
591
- if (messages.length > lastMessageCountRef.current && shouldStickToBottom) {
592
- container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
593
- setShowJumpToLatest(false);
594
- } else if (messages.length > lastMessageCountRef.current) {
595
- setShowJumpToLatest(true);
1430
+ if (isNearBottomProp !== void 0) {
1431
+ isNearBottomRef.current = isNearBottomProp;
1432
+ }
1433
+ }, [isNearBottomProp]);
1434
+ const handleScroll = React4.useCallback(() => {
1435
+ const el = containerRef.current;
1436
+ if (!el) return;
1437
+ const threshold = 100;
1438
+ const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - threshold;
1439
+ isNearBottomRef.current = nearBottom;
1440
+ if (nearBottom) {
1441
+ setShowNewMessagesPill(false);
1442
+ }
1443
+ if (unreadDividerRef.current && el) {
1444
+ const dividerRect = unreadDividerRef.current.getBoundingClientRect();
1445
+ const containerRect = el.getBoundingClientRect();
1446
+ if (dividerRect.bottom < containerRect.bottom) {
1447
+ onReachBottom?.();
1448
+ }
1449
+ } else if (nearBottom) {
1450
+ onReachBottom?.();
1451
+ }
1452
+ }, [onReachBottom]);
1453
+ React4.useEffect(() => {
1454
+ if (messages.length > prevLengthRef.current) {
1455
+ if (isNearBottomRef.current) {
1456
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
1457
+ } else {
1458
+ setShowNewMessagesPill(true);
1459
+ }
596
1460
  }
597
- lastMessageCountRef.current = messages.length;
598
- }, [messages]);
1461
+ prevLengthRef.current = messages.length;
1462
+ }, [messages.length]);
599
1463
  React4.useEffect(() => {
600
- if (!scrollToUnreadOnMount || unreadIndex < 0 || didScrollToUnreadRef.current) {
601
- return;
1464
+ if (!isLoading && messages.length > 0) {
1465
+ if (resolvedFirstUnreadId && unreadDividerRef.current) {
1466
+ unreadDividerRef.current.scrollIntoView({
1467
+ block: "start",
1468
+ behavior: "instant"
1469
+ });
1470
+ } else {
1471
+ bottomRef.current?.scrollIntoView({
1472
+ behavior: "instant"
1473
+ });
1474
+ }
602
1475
  }
603
- unreadMarkerRef.current?.scrollIntoView({ block: "center" });
604
- didScrollToUnreadRef.current = true;
605
- }, [scrollToUnreadOnMount, unreadIndex, messages.length]);
606
- if (!messages.length) {
1476
+ }, [isLoading, resolvedFirstUnreadId]);
1477
+ React4.useEffect(() => {
1478
+ const container = containerRef.current;
1479
+ const bottom = bottomRef.current;
1480
+ if (!container || !bottom) return;
1481
+ const observer = new IntersectionObserver(
1482
+ (entries) => {
1483
+ if (entries[0]?.isIntersecting) {
1484
+ onReachBottom?.();
1485
+ }
1486
+ },
1487
+ { root: container, threshold: 0.1 }
1488
+ );
1489
+ observer.observe(bottom);
1490
+ return () => observer.disconnect();
1491
+ }, [onReachBottom]);
1492
+ const scrollToBottom = React4.useCallback(() => {
1493
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
1494
+ setShowNewMessagesPill(false);
1495
+ }, []);
1496
+ if (isLoading) {
1497
+ return /* @__PURE__ */ jsxRuntime.jsx(
1498
+ "div",
1499
+ {
1500
+ style: {
1501
+ flex: 1,
1502
+ display: "flex",
1503
+ alignItems: "center",
1504
+ justifyContent: "center",
1505
+ color: "var(--sm-muted-text, #6b7280)",
1506
+ fontSize: 14,
1507
+ padding: 24
1508
+ },
1509
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1510
+ "div",
1511
+ {
1512
+ style: {
1513
+ width: 20,
1514
+ height: 20,
1515
+ border: "2px solid var(--sm-border-color, #e5e7eb)",
1516
+ borderTopColor: "var(--sm-primary, #2563eb)",
1517
+ borderRadius: 999,
1518
+ animation: "sm-spin 0.8s linear infinite"
1519
+ }
1520
+ }
1521
+ )
1522
+ }
1523
+ );
1524
+ }
1525
+ if (messages.length === 0) {
607
1526
  return /* @__PURE__ */ jsxRuntime.jsx(
608
1527
  "div",
609
1528
  {
@@ -621,103 +1540,200 @@ function ChatMessageList({
621
1540
  );
622
1541
  }
623
1542
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", flex: 1, minHeight: 0 }, children: [
624
- /* @__PURE__ */ jsxRuntime.jsx(
1543
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes sm-spin { to { transform: rotate(360deg); } }` }),
1544
+ /* @__PURE__ */ jsxRuntime.jsxs(
625
1545
  "div",
626
1546
  {
627
1547
  ref: containerRef,
628
- onScroll: (event) => {
629
- const element = event.currentTarget;
630
- const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
631
- setShowJumpToLatest(distanceFromBottom > 120);
632
- },
1548
+ onScroll: handleScroll,
633
1549
  style: {
634
1550
  height: "100%",
635
1551
  overflowY: "auto",
636
- padding: 16,
637
- display: "flex",
638
- flexDirection: "column",
639
- gap: 12,
1552
+ position: "relative",
640
1553
  background: "var(--sm-surface-muted, #f8fafc)"
641
1554
  },
642
- children: messages.map((message, index) => {
643
- const previousMessage = messages[index - 1];
644
- const showDateDivider = !previousMessage || !isSameDay(previousMessage.created_at, message.created_at);
645
- const showUnreadDivider = unreadIndex === index;
646
- return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
647
- showDateDivider ? /* @__PURE__ */ jsxRuntime.jsx(
648
- "div",
649
- {
650
- style: {
651
- alignSelf: "center",
652
- fontSize: 12,
653
- color: "var(--sm-muted-text, #6b7280)",
654
- padding: "4px 10px",
655
- borderRadius: 999,
656
- background: "rgba(148, 163, 184, 0.12)"
657
- },
658
- children: formatDayLabel(message.created_at)
659
- }
660
- ) : null,
661
- showUnreadDivider ? /* @__PURE__ */ jsxRuntime.jsxs(
662
- "div",
663
- {
664
- ref: unreadMarkerRef,
665
- style: {
666
- display: "flex",
667
- alignItems: "center",
668
- gap: 10,
669
- color: "var(--sm-primary, #2563eb)",
670
- fontSize: 12,
671
- fontWeight: 600
672
- },
673
- children: [
674
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: 1, background: "rgba(37, 99, 235, 0.28)" } }),
675
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "New messages" }),
676
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: 1, background: "rgba(37, 99, 235, 0.28)" } })
677
- ]
678
- }
679
- ) : null,
680
- /* @__PURE__ */ jsxRuntime.jsx(
681
- ChatMessageItem,
682
- {
683
- message,
684
- currentUserId,
685
- onAddReaction,
686
- onRemoveReaction,
687
- onReport,
688
- highlight: showUnreadDivider
689
- }
690
- )
691
- ] }, message.id);
692
- })
1555
+ children: [
1556
+ hasMore && onLoadMore && /* @__PURE__ */ jsxRuntime.jsx(
1557
+ "div",
1558
+ {
1559
+ style: {
1560
+ display: "flex",
1561
+ justifyContent: "center",
1562
+ padding: "12px 0"
1563
+ },
1564
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1565
+ "button",
1566
+ {
1567
+ onClick: onLoadMore,
1568
+ type: "button",
1569
+ style: {
1570
+ fontSize: 12,
1571
+ color: "var(--sm-muted-text, #6b7280)",
1572
+ fontWeight: 500,
1573
+ padding: "4px 12px",
1574
+ borderRadius: 999,
1575
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
1576
+ background: "var(--sm-surface, #fff)",
1577
+ cursor: "pointer"
1578
+ },
1579
+ children: "Load earlier messages"
1580
+ }
1581
+ )
1582
+ }
1583
+ ),
1584
+ messages.map((msg, i) => {
1585
+ const prevMsg = i > 0 ? messages[i - 1] : null;
1586
+ const showDateSeparator = !prevMsg || !isSameDay(msg.created_at, prevMsg.created_at);
1587
+ const showUnreadDivider = resolvedFirstUnreadId === msg.id;
1588
+ const isOwn = msg.sender_id === currentUserId;
1589
+ return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
1590
+ showUnreadDivider && /* @__PURE__ */ jsxRuntime.jsxs(
1591
+ "div",
1592
+ {
1593
+ ref: unreadDividerRef,
1594
+ style: {
1595
+ display: "flex",
1596
+ alignItems: "center",
1597
+ gap: 12,
1598
+ padding: "8px 16px",
1599
+ margin: "4px 0"
1600
+ },
1601
+ children: [
1602
+ /* @__PURE__ */ jsxRuntime.jsx(
1603
+ "div",
1604
+ {
1605
+ style: {
1606
+ flex: 1,
1607
+ height: 1,
1608
+ background: "var(--sm-unread-divider-color, rgba(37, 99, 235, 0.4))"
1609
+ }
1610
+ }
1611
+ ),
1612
+ /* @__PURE__ */ jsxRuntime.jsx(
1613
+ "span",
1614
+ {
1615
+ style: {
1616
+ fontSize: 12,
1617
+ color: "var(--sm-primary, #2563eb)",
1618
+ fontWeight: 500,
1619
+ background: "var(--sm-unread-divider-bg, rgba(37, 99, 235, 0.06))",
1620
+ padding: "2px 8px",
1621
+ borderRadius: 999
1622
+ },
1623
+ children: "New messages"
1624
+ }
1625
+ ),
1626
+ /* @__PURE__ */ jsxRuntime.jsx(
1627
+ "div",
1628
+ {
1629
+ style: {
1630
+ flex: 1,
1631
+ height: 1,
1632
+ background: "var(--sm-unread-divider-color, rgba(37, 99, 235, 0.4))"
1633
+ }
1634
+ }
1635
+ )
1636
+ ]
1637
+ }
1638
+ ),
1639
+ showDateSeparator && /* @__PURE__ */ jsxRuntime.jsxs(
1640
+ "div",
1641
+ {
1642
+ style: {
1643
+ display: "flex",
1644
+ alignItems: "center",
1645
+ gap: 12,
1646
+ padding: "8px 16px",
1647
+ margin: "4px 0"
1648
+ },
1649
+ children: [
1650
+ /* @__PURE__ */ jsxRuntime.jsx(
1651
+ "div",
1652
+ {
1653
+ style: {
1654
+ flex: 1,
1655
+ height: 1,
1656
+ background: "var(--sm-border-color, #e5e7eb)"
1657
+ }
1658
+ }
1659
+ ),
1660
+ /* @__PURE__ */ jsxRuntime.jsx(
1661
+ "span",
1662
+ {
1663
+ style: {
1664
+ fontSize: 12,
1665
+ color: "var(--sm-muted-text, #6b7280)",
1666
+ fontWeight: 500
1667
+ },
1668
+ children: getDateLabel(msg.created_at)
1669
+ }
1670
+ ),
1671
+ /* @__PURE__ */ jsxRuntime.jsx(
1672
+ "div",
1673
+ {
1674
+ style: {
1675
+ flex: 1,
1676
+ height: 1,
1677
+ background: "var(--sm-border-color, #e5e7eb)"
1678
+ }
1679
+ }
1680
+ )
1681
+ ]
1682
+ }
1683
+ ),
1684
+ /* @__PURE__ */ jsxRuntime.jsx(
1685
+ ChatMessageItem,
1686
+ {
1687
+ message: msg,
1688
+ currentUserId,
1689
+ conversationId,
1690
+ profile: profiles?.get(msg.sender_id),
1691
+ onAddReaction,
1692
+ onRemoveReaction,
1693
+ onEdit,
1694
+ onDelete,
1695
+ onReport,
1696
+ onFetchAttachmentUrl,
1697
+ isOwnMessage: isOwn,
1698
+ highlight: showUnreadDivider
1699
+ }
1700
+ )
1701
+ ] }, msg.id);
1702
+ }),
1703
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottomRef })
1704
+ ]
693
1705
  }
694
1706
  ),
695
- showJumpToLatest ? /* @__PURE__ */ jsxRuntime.jsx(
1707
+ showNewMessagesPill && /* @__PURE__ */ jsxRuntime.jsxs(
696
1708
  "button",
697
1709
  {
1710
+ onClick: scrollToBottom,
698
1711
  type: "button",
699
- onClick: () => {
700
- containerRef.current?.scrollTo({
701
- top: containerRef.current.scrollHeight,
702
- behavior: "smooth"
703
- });
704
- setShowJumpToLatest(false);
705
- },
706
1712
  style: {
707
1713
  position: "absolute",
708
- right: 16,
709
- bottom: 16,
1714
+ bottom: 12,
1715
+ left: "50%",
1716
+ transform: "translateX(-50%)",
710
1717
  border: "none",
711
1718
  borderRadius: 999,
712
1719
  background: "var(--sm-primary, #2563eb)",
713
1720
  color: "#fff",
714
- padding: "10px 14px",
1721
+ padding: "6px 16px",
715
1722
  cursor: "pointer",
716
- boxShadow: "0 12px 28px rgba(37, 99, 235, 0.28)"
1723
+ boxShadow: "0 4px 12px rgba(37, 99, 235, 0.3)",
1724
+ fontSize: 12,
1725
+ fontWeight: 500,
1726
+ zIndex: 10,
1727
+ display: "flex",
1728
+ alignItems: "center",
1729
+ gap: 4
717
1730
  },
718
- children: "New messages"
1731
+ children: [
1732
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 14 }, children: "\u2193" }),
1733
+ " New messages"
1734
+ ]
719
1735
  }
720
- ) : null
1736
+ )
721
1737
  ] });
722
1738
  }
723
1739
 
@@ -749,8 +1765,10 @@ function ChatThread({
749
1765
  conversationId,
750
1766
  theme,
751
1767
  currentUserId,
1768
+ profiles,
752
1769
  title = "Chat",
753
- subtitle
1770
+ subtitle,
1771
+ onFetchAttachmentUrl
754
1772
  }) {
755
1773
  const client = useChatClient();
756
1774
  const resolvedUserId = currentUserId ?? client.userId;
@@ -759,8 +1777,12 @@ function ChatThread({
759
1777
  readStatuses,
760
1778
  isLoading,
761
1779
  error,
1780
+ hasMore,
762
1781
  sendMessage,
1782
+ loadMore,
763
1783
  markRead,
1784
+ editMessage,
1785
+ deleteMessage,
764
1786
  addReaction,
765
1787
  removeReaction,
766
1788
  reportMessage,
@@ -865,10 +1887,19 @@ function ChatThread({
865
1887
  {
866
1888
  messages,
867
1889
  currentUserId: resolvedUserId,
868
- unreadSince: ownReadStatus,
1890
+ conversationId,
1891
+ profiles,
1892
+ hasMore,
1893
+ isLoading,
1894
+ onLoadMore: loadMore,
869
1895
  onAddReaction: (messageId, emoji) => void addReaction(messageId, emoji),
870
1896
  onRemoveReaction: (messageId, emoji) => void removeReaction(messageId, emoji),
1897
+ onEdit: (messageId, content) => void editMessage(messageId, content),
1898
+ onDelete: (messageId) => void deleteMessage(messageId),
871
1899
  onReport: (messageId) => void reportMessage(messageId, "other"),
1900
+ onFetchAttachmentUrl,
1901
+ unreadSince: ownReadStatus,
1902
+ onReachBottom: () => void markRead(),
872
1903
  emptyState: isLoading ? "Loading messages..." : "Start the conversation"
873
1904
  }
874
1905
  ),
@@ -890,7 +1921,7 @@ function ChatThread({
890
1921
  onSend: async (content, attachments) => {
891
1922
  await sendMessage(content, {
892
1923
  attachments,
893
- message_type: inferMessageType(content, attachments)
1924
+ message_type: inferMessageType(content, attachments ?? [])
894
1925
  });
895
1926
  },
896
1927
  onTypingChange: (isTyping) => {
@@ -1067,6 +2098,346 @@ function ConversationList({
1067
2098
  }
1068
2099
  );
1069
2100
  }
2101
+ var REPORT_REASONS = [
2102
+ { value: "spam", label: "Spam" },
2103
+ { value: "harassment", label: "Harassment" },
2104
+ { value: "hate", label: "Hate speech" },
2105
+ { value: "violence", label: "Violence" },
2106
+ { value: "other", label: "Other" }
2107
+ ];
2108
+ function ReportDialog({
2109
+ messageId,
2110
+ onSubmit,
2111
+ onClose
2112
+ }) {
2113
+ const [reason, setReason] = React4.useState("spam");
2114
+ const [description, setDescription] = React4.useState("");
2115
+ const [submitting, setSubmitting] = React4.useState(false);
2116
+ const [error, setError] = React4.useState(null);
2117
+ const [submitted, setSubmitted] = React4.useState(false);
2118
+ async function handleSubmit() {
2119
+ setSubmitting(true);
2120
+ setError(null);
2121
+ try {
2122
+ await onSubmit({
2123
+ messageId,
2124
+ reason,
2125
+ description: description.trim() || void 0
2126
+ });
2127
+ setSubmitted(true);
2128
+ setTimeout(onClose, 1500);
2129
+ } catch (e) {
2130
+ setError(e instanceof Error ? e.message : "Failed to submit report");
2131
+ } finally {
2132
+ setSubmitting(false);
2133
+ }
2134
+ }
2135
+ return /* @__PURE__ */ jsxRuntime.jsx(
2136
+ "div",
2137
+ {
2138
+ style: {
2139
+ position: "fixed",
2140
+ inset: 0,
2141
+ zIndex: 9999,
2142
+ display: "flex",
2143
+ alignItems: "center",
2144
+ justifyContent: "center",
2145
+ background: "rgba(0, 0, 0, 0.5)",
2146
+ backdropFilter: "blur(4px)"
2147
+ },
2148
+ onClick: onClose,
2149
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2150
+ "div",
2151
+ {
2152
+ style: {
2153
+ background: "var(--sm-surface, #fff)",
2154
+ borderRadius: 16,
2155
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
2156
+ width: "100%",
2157
+ maxWidth: 448,
2158
+ margin: "0 16px",
2159
+ overflow: "hidden",
2160
+ color: "var(--sm-text-color, #111827)",
2161
+ fontFamily: "var(--sm-font-family, system-ui, -apple-system, sans-serif)"
2162
+ },
2163
+ onClick: (e) => e.stopPropagation(),
2164
+ children: [
2165
+ /* @__PURE__ */ jsxRuntime.jsxs(
2166
+ "div",
2167
+ {
2168
+ style: {
2169
+ padding: "16px 24px",
2170
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
2171
+ display: "flex",
2172
+ alignItems: "center",
2173
+ justifyContent: "space-between"
2174
+ },
2175
+ children: [
2176
+ /* @__PURE__ */ jsxRuntime.jsx(
2177
+ "h3",
2178
+ {
2179
+ style: {
2180
+ margin: 0,
2181
+ fontSize: 16,
2182
+ fontWeight: 600,
2183
+ color: "var(--sm-text-color, #111827)"
2184
+ },
2185
+ children: "Report Message"
2186
+ }
2187
+ ),
2188
+ /* @__PURE__ */ jsxRuntime.jsx(
2189
+ "button",
2190
+ {
2191
+ onClick: onClose,
2192
+ type: "button",
2193
+ "aria-label": "Close",
2194
+ style: {
2195
+ border: "none",
2196
+ background: "transparent",
2197
+ padding: 4,
2198
+ cursor: "pointer",
2199
+ color: "var(--sm-muted-text, #6b7280)",
2200
+ borderRadius: 8,
2201
+ display: "flex",
2202
+ alignItems: "center",
2203
+ justifyContent: "center"
2204
+ },
2205
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2206
+ "svg",
2207
+ {
2208
+ xmlns: "http://www.w3.org/2000/svg",
2209
+ width: "18",
2210
+ height: "18",
2211
+ viewBox: "0 0 24 24",
2212
+ fill: "none",
2213
+ stroke: "currentColor",
2214
+ strokeWidth: "2",
2215
+ children: [
2216
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
2217
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
2218
+ ]
2219
+ }
2220
+ )
2221
+ }
2222
+ )
2223
+ ]
2224
+ }
2225
+ ),
2226
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "16px 24px" }, children: submitted ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
2227
+ /* @__PURE__ */ jsxRuntime.jsx(
2228
+ "div",
2229
+ {
2230
+ style: {
2231
+ width: 40,
2232
+ height: 40,
2233
+ borderRadius: 999,
2234
+ background: "#dcfce7",
2235
+ display: "flex",
2236
+ alignItems: "center",
2237
+ justifyContent: "center",
2238
+ margin: "0 auto 12px"
2239
+ },
2240
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2241
+ "svg",
2242
+ {
2243
+ xmlns: "http://www.w3.org/2000/svg",
2244
+ width: "20",
2245
+ height: "20",
2246
+ viewBox: "0 0 24 24",
2247
+ fill: "none",
2248
+ stroke: "#16a34a",
2249
+ strokeWidth: "2",
2250
+ children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" })
2251
+ }
2252
+ )
2253
+ }
2254
+ ),
2255
+ /* @__PURE__ */ jsxRuntime.jsx(
2256
+ "p",
2257
+ {
2258
+ style: {
2259
+ margin: 0,
2260
+ fontSize: 14,
2261
+ fontWeight: 500,
2262
+ color: "var(--sm-text-color, #111827)"
2263
+ },
2264
+ children: "Report submitted"
2265
+ }
2266
+ ),
2267
+ /* @__PURE__ */ jsxRuntime.jsx(
2268
+ "p",
2269
+ {
2270
+ style: {
2271
+ margin: "4px 0 0",
2272
+ fontSize: 12,
2273
+ color: "var(--sm-muted-text, #6b7280)"
2274
+ },
2275
+ children: "We will review this message shortly."
2276
+ }
2277
+ )
2278
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
2279
+ "div",
2280
+ {
2281
+ style: { display: "flex", flexDirection: "column", gap: 16 },
2282
+ children: [
2283
+ error && /* @__PURE__ */ jsxRuntime.jsx(
2284
+ "div",
2285
+ {
2286
+ style: {
2287
+ padding: "8px 12px",
2288
+ background: "#fef2f2",
2289
+ color: "#dc2626",
2290
+ fontSize: 14,
2291
+ borderRadius: 8
2292
+ },
2293
+ children: error
2294
+ }
2295
+ ),
2296
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2297
+ /* @__PURE__ */ jsxRuntime.jsx(
2298
+ "label",
2299
+ {
2300
+ style: {
2301
+ display: "block",
2302
+ fontSize: 14,
2303
+ fontWeight: 500,
2304
+ color: "var(--sm-text-color, #111827)",
2305
+ marginBottom: 6
2306
+ },
2307
+ children: "Reason"
2308
+ }
2309
+ ),
2310
+ /* @__PURE__ */ jsxRuntime.jsx(
2311
+ "select",
2312
+ {
2313
+ value: reason,
2314
+ onChange: (e) => setReason(e.target.value),
2315
+ style: {
2316
+ width: "100%",
2317
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
2318
+ borderRadius: 8,
2319
+ padding: "8px 12px",
2320
+ fontSize: 14,
2321
+ background: "var(--sm-surface, #fff)",
2322
+ color: "var(--sm-text-color, #111827)",
2323
+ fontFamily: "inherit",
2324
+ outline: "none"
2325
+ },
2326
+ children: REPORT_REASONS.map((r) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: r.value, children: r.label }, r.value))
2327
+ }
2328
+ )
2329
+ ] }),
2330
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2331
+ /* @__PURE__ */ jsxRuntime.jsxs(
2332
+ "label",
2333
+ {
2334
+ style: {
2335
+ display: "block",
2336
+ fontSize: 14,
2337
+ fontWeight: 500,
2338
+ color: "var(--sm-text-color, #111827)",
2339
+ marginBottom: 6
2340
+ },
2341
+ children: [
2342
+ "Description",
2343
+ " ",
2344
+ /* @__PURE__ */ jsxRuntime.jsx(
2345
+ "span",
2346
+ {
2347
+ style: {
2348
+ fontWeight: 400,
2349
+ color: "var(--sm-muted-text, #6b7280)"
2350
+ },
2351
+ children: "(optional)"
2352
+ }
2353
+ )
2354
+ ]
2355
+ }
2356
+ ),
2357
+ /* @__PURE__ */ jsxRuntime.jsx(
2358
+ "textarea",
2359
+ {
2360
+ value: description,
2361
+ onChange: (e) => setDescription(e.target.value),
2362
+ placeholder: "Provide additional details...",
2363
+ rows: 3,
2364
+ maxLength: 1e3,
2365
+ style: {
2366
+ width: "100%",
2367
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
2368
+ borderRadius: 8,
2369
+ padding: "8px 12px",
2370
+ fontSize: 14,
2371
+ fontFamily: "inherit",
2372
+ color: "var(--sm-text-color, #111827)",
2373
+ resize: "none",
2374
+ outline: "none",
2375
+ boxSizing: "border-box"
2376
+ }
2377
+ }
2378
+ )
2379
+ ] })
2380
+ ]
2381
+ }
2382
+ ) }),
2383
+ !submitted && /* @__PURE__ */ jsxRuntime.jsxs(
2384
+ "div",
2385
+ {
2386
+ style: {
2387
+ padding: "16px 24px",
2388
+ borderTop: "1px solid var(--sm-border-color, #e5e7eb)",
2389
+ display: "flex",
2390
+ justifyContent: "flex-end",
2391
+ gap: 8
2392
+ },
2393
+ children: [
2394
+ /* @__PURE__ */ jsxRuntime.jsx(
2395
+ "button",
2396
+ {
2397
+ onClick: onClose,
2398
+ type: "button",
2399
+ style: {
2400
+ padding: "8px 16px",
2401
+ fontSize: 14,
2402
+ fontWeight: 500,
2403
+ color: "var(--sm-text-color, #111827)",
2404
+ background: "transparent",
2405
+ border: "none",
2406
+ borderRadius: 8,
2407
+ cursor: "pointer"
2408
+ },
2409
+ children: "Cancel"
2410
+ }
2411
+ ),
2412
+ /* @__PURE__ */ jsxRuntime.jsx(
2413
+ "button",
2414
+ {
2415
+ onClick: () => void handleSubmit(),
2416
+ disabled: submitting,
2417
+ type: "button",
2418
+ style: {
2419
+ padding: "8px 16px",
2420
+ fontSize: 14,
2421
+ fontWeight: 500,
2422
+ background: "var(--sm-primary, #2563eb)",
2423
+ color: "#fff",
2424
+ border: "none",
2425
+ borderRadius: 8,
2426
+ cursor: submitting ? "wait" : "pointer",
2427
+ opacity: submitting ? 0.5 : 1
2428
+ },
2429
+ children: submitting ? "Submitting..." : "Submit Report"
2430
+ }
2431
+ )
2432
+ ]
2433
+ }
2434
+ )
2435
+ ]
2436
+ }
2437
+ )
2438
+ }
2439
+ );
2440
+ }
1070
2441
  var ChatContext = React4.createContext(null);
1071
2442
  function useChatContext() {
1072
2443
  const ctx = React4.useContext(ChatContext);
@@ -1513,6 +2884,9 @@ exports.ChatProvider = ChatProvider;
1513
2884
  exports.ChatThread = ChatThread;
1514
2885
  exports.ConversationList = ConversationList;
1515
2886
  exports.EmojiPicker = EmojiPicker;
2887
+ exports.EmojiPickerTrigger = EmojiPickerTrigger;
2888
+ exports.ReactionBar = ReactionBar;
2889
+ exports.ReportDialog = ReportDialog;
1516
2890
  exports.useChat = useChat;
1517
2891
  exports.useChatClient = useChatClient;
1518
2892
  exports.useChatConfig = useChatConfig;