hermes-chat-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs ADDED
@@ -0,0 +1,1935 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/react.ts
31
+ var react_exports = {};
32
+ __export(react_exports, {
33
+ ChatInput: () => ChatInput,
34
+ MediaMessage: () => MediaMessage,
35
+ MessageList: () => MessageList,
36
+ OnlineBadge: () => OnlineBadge,
37
+ ReactionPicker: () => ReactionPicker,
38
+ RoomList: () => RoomList,
39
+ TypingIndicator: () => TypingIndicator2,
40
+ useMessages: () => useMessages,
41
+ usePresence: () => usePresence,
42
+ useReactions: () => useReactions,
43
+ useReadReceipts: () => useReadReceipts,
44
+ useRooms: () => useRooms,
45
+ useTyping: () => useTyping,
46
+ useUpload: () => useUpload
47
+ });
48
+ module.exports = __toCommonJS(react_exports);
49
+
50
+ // src/react/hooks/useMessages.ts
51
+ var import_react = require("react");
52
+ var useMessages = (client, roomId) => {
53
+ const [messages, setMessages] = (0, import_react.useState)([]);
54
+ const [loading, setLoading] = (0, import_react.useState)(false);
55
+ const [loadingMore, setLoadingMore] = (0, import_react.useState)(false);
56
+ const [hasMore, setHasMore] = (0, import_react.useState)(false);
57
+ const [error, setError] = (0, import_react.useState)(null);
58
+ const [typingUsers, setTypingUsers] = (0, import_react.useState)([]);
59
+ const oldestMessageId = (0, import_react.useRef)(void 0);
60
+ (0, import_react.useEffect)(() => {
61
+ if (!roomId || !client.isConnected) return;
62
+ setMessages([]);
63
+ setHasMore(false);
64
+ oldestMessageId.current = void 0;
65
+ setLoading(true);
66
+ setError(null);
67
+ client.getHistory(roomId).then(({ messages: msgs, hasMore: more }) => {
68
+ setMessages(msgs);
69
+ setHasMore(more);
70
+ if (msgs.length > 0) oldestMessageId.current = msgs[0]._id;
71
+ }).catch((err) => setError(err.message)).finally(() => setLoading(false));
72
+ }, [roomId, client.isConnected]);
73
+ (0, import_react.useEffect)(() => {
74
+ if (!roomId) return;
75
+ const onReceive = (msg) => {
76
+ if (msg.roomId !== roomId) return;
77
+ setMessages((prev) => {
78
+ if (prev.find((m) => m._id === msg._id)) return prev;
79
+ return [...prev, msg];
80
+ });
81
+ };
82
+ const onDeleted = ({
83
+ messageId
84
+ }) => {
85
+ setMessages(
86
+ (prev) => prev.map(
87
+ (m) => m._id === messageId ? { ...m, isDeleted: true, text: void 0 } : m
88
+ )
89
+ );
90
+ };
91
+ const onEdited = (msg) => {
92
+ setMessages((prev) => prev.map((m) => m._id === msg._id ? msg : m));
93
+ };
94
+ client.on("message:receive", onReceive);
95
+ client.on("message:deleted", onDeleted);
96
+ client.on("message:edited", onEdited);
97
+ return () => {
98
+ client.off("message:receive", onReceive);
99
+ client.off("message:deleted", onDeleted);
100
+ client.off("message:edited", onEdited);
101
+ };
102
+ }, [roomId, client]);
103
+ (0, import_react.useEffect)(() => {
104
+ const onReaction = ({ messageId, reactions }) => {
105
+ setMessages(
106
+ (prev) => prev.map((m) => m._id === messageId ? { ...m, reactions } : m)
107
+ );
108
+ };
109
+ client.on("reaction:updated", onReaction);
110
+ return () => {
111
+ client.off("reaction:updated", onReaction);
112
+ };
113
+ }, [client]);
114
+ (0, import_react.useEffect)(() => {
115
+ if (!roomId) return;
116
+ const onStarted = ({ userId, displayName, roomId: rid }) => {
117
+ if (rid !== roomId) return;
118
+ setTypingUsers((prev) => [
119
+ ...prev.filter((u) => u.userId !== userId),
120
+ { userId, displayName }
121
+ ]);
122
+ };
123
+ const onStopped = ({ userId, roomId: rid }) => {
124
+ if (rid !== roomId) return;
125
+ setTypingUsers((prev) => prev.filter((u) => u.userId !== userId));
126
+ };
127
+ client.on("typing:started", onStarted);
128
+ client.on("typing:stopped", onStopped);
129
+ return () => {
130
+ client.off("typing:started", onStarted);
131
+ client.off("typing:stopped", onStopped);
132
+ setTypingUsers([]);
133
+ };
134
+ }, [roomId, client]);
135
+ const loadMore = (0, import_react.useCallback)(async () => {
136
+ if (!roomId || loadingMore || !hasMore) return;
137
+ setLoadingMore(true);
138
+ try {
139
+ const { messages: older, hasMore: more } = await client.getHistory(
140
+ roomId,
141
+ oldestMessageId.current
142
+ );
143
+ setMessages((prev) => [...older, ...prev]);
144
+ setHasMore(more);
145
+ if (older.length > 0) oldestMessageId.current = older[0]._id;
146
+ } catch (err) {
147
+ setError(err.message);
148
+ } finally {
149
+ setLoadingMore(false);
150
+ }
151
+ }, [roomId, loadingMore, hasMore, client]);
152
+ const sendMessage = (0, import_react.useCallback)(
153
+ async (input) => {
154
+ if (!roomId) throw new Error("No room selected");
155
+ return client.sendMessage({ ...input, roomId });
156
+ },
157
+ [roomId, client]
158
+ );
159
+ const editMessage = (0, import_react.useCallback)(
160
+ async (messageId, text) => {
161
+ if (!roomId) throw new Error("No room selected");
162
+ return client.editMessage(messageId, roomId, text);
163
+ },
164
+ [roomId, client]
165
+ );
166
+ const deleteMessage = (0, import_react.useCallback)(
167
+ async (messageId) => {
168
+ if (!roomId) throw new Error("No room selected");
169
+ return client.deleteMessage(messageId, roomId);
170
+ },
171
+ [roomId, client]
172
+ );
173
+ const addReaction = (0, import_react.useCallback)(
174
+ async (messageId, emoji) => {
175
+ if (!roomId) throw new Error("No room selected");
176
+ return client.addReaction(messageId, roomId, emoji);
177
+ },
178
+ [roomId, client]
179
+ );
180
+ return {
181
+ messages,
182
+ loading,
183
+ loadingMore,
184
+ hasMore,
185
+ error,
186
+ typingUsers,
187
+ sendMessage,
188
+ editMessage,
189
+ deleteMessage,
190
+ addReaction,
191
+ loadMore
192
+ };
193
+ };
194
+
195
+ // src/react/hooks/useRooms.ts
196
+ var import_react2 = require("react");
197
+ var useRooms = (client) => {
198
+ const [rooms, setRooms] = (0, import_react2.useState)([]);
199
+ const [loading, setLoading] = (0, import_react2.useState)(true);
200
+ const [error, setError] = (0, import_react2.useState)(null);
201
+ const fetchedRef = (0, import_react2.useRef)(false);
202
+ const fetchRooms = (0, import_react2.useCallback)(async () => {
203
+ setLoading(true);
204
+ setError(null);
205
+ await new Promise((resolve, reject) => {
206
+ if (client.isConnected) return resolve();
207
+ let attempts = 0;
208
+ const interval = setInterval(() => {
209
+ attempts++;
210
+ if (client.isConnected) {
211
+ clearInterval(interval);
212
+ resolve();
213
+ } else if (attempts > 50) {
214
+ clearInterval(interval);
215
+ reject(new Error("Connection timeout"));
216
+ }
217
+ }, 100);
218
+ });
219
+ try {
220
+ const data = await client.getRooms();
221
+ console.log("[useRooms] fetched:", data.length, "rooms");
222
+ setRooms(data);
223
+ fetchedRef.current = true;
224
+ } catch (err) {
225
+ console.error("[useRooms] fetch error:", err.message);
226
+ setError(err.message);
227
+ } finally {
228
+ setLoading(false);
229
+ }
230
+ }, [client]);
231
+ (0, import_react2.useEffect)(() => {
232
+ fetchRooms();
233
+ const onConnected = () => {
234
+ if (!fetchedRef.current) fetchRooms();
235
+ };
236
+ client.on("connected", onConnected);
237
+ return () => {
238
+ client.off("connected", onConnected);
239
+ };
240
+ }, [fetchRooms, client]);
241
+ (0, import_react2.useEffect)(() => {
242
+ const onCreated = (room) => {
243
+ setRooms((prev) => {
244
+ if (prev.find((r) => r._id === room._id)) return prev;
245
+ return [{ ...room, unreadCount: 0 }, ...prev];
246
+ });
247
+ };
248
+ const onDeleted = ({ roomId }) => setRooms((prev) => prev.filter((r) => r._id !== roomId));
249
+ const onMemberJoined = ({
250
+ roomId,
251
+ userId
252
+ }) => setRooms(
253
+ (prev) => prev.map(
254
+ (r) => r._id === roomId ? { ...r, members: [...r.members, userId] } : r
255
+ )
256
+ );
257
+ const onMemberLeft = ({
258
+ roomId,
259
+ userId
260
+ }) => setRooms(
261
+ (prev) => prev.map(
262
+ (r) => r._id === roomId ? { ...r, members: r.members.filter((m) => m !== userId) } : r
263
+ )
264
+ );
265
+ const onMessage = (msg) => setRooms((prev) => {
266
+ const idx = prev.findIndex((r) => r._id === msg.roomId);
267
+ if (idx === -1) return prev;
268
+ const updated = {
269
+ ...prev[idx],
270
+ lastMessage: msg,
271
+ lastActivity: msg.createdAt
272
+ };
273
+ return [updated, ...prev.filter((r) => r._id !== msg.roomId)];
274
+ });
275
+ client.on("room:created", onCreated);
276
+ client.on("room:deleted", onDeleted);
277
+ client.on("room:member:joined", onMemberJoined);
278
+ client.on("room:member:left", onMemberLeft);
279
+ client.on("message:receive", onMessage);
280
+ return () => {
281
+ client.off("room:created", onCreated);
282
+ client.off("room:deleted", onDeleted);
283
+ client.off("room:member:joined", onMemberJoined);
284
+ client.off("room:member:left", onMemberLeft);
285
+ client.off("message:receive", onMessage);
286
+ };
287
+ }, [client]);
288
+ const createDirect = (0, import_react2.useCallback)(
289
+ async (input) => {
290
+ const room = await client.createDirectRoom(input);
291
+ setRooms((prev) => {
292
+ if (prev.find((r) => r._id === room._id)) return prev;
293
+ return [{ ...room, unreadCount: 0 }, ...prev];
294
+ });
295
+ return room;
296
+ },
297
+ [client]
298
+ );
299
+ const createGroup = (0, import_react2.useCallback)(
300
+ async (input) => {
301
+ const room = await client.createGroupRoom(input);
302
+ setRooms((prev) => {
303
+ if (prev.find((r) => r._id === room._id)) return prev;
304
+ return [{ ...room, unreadCount: 0 }, ...prev];
305
+ });
306
+ return room;
307
+ },
308
+ [client]
309
+ );
310
+ const deleteRoom = (0, import_react2.useCallback)(
311
+ async (roomId) => {
312
+ await client.deleteRoom(roomId);
313
+ setRooms((prev) => prev.filter((r) => r._id !== roomId));
314
+ },
315
+ [client]
316
+ );
317
+ const addMember = (0, import_react2.useCallback)(
318
+ (roomId, userId) => client.addMember(roomId, userId),
319
+ [client]
320
+ );
321
+ const removeMember = (0, import_react2.useCallback)(
322
+ (roomId, userId) => client.removeMember(roomId, userId),
323
+ [client]
324
+ );
325
+ return {
326
+ rooms,
327
+ loading,
328
+ error,
329
+ createDirect,
330
+ createGroup,
331
+ deleteRoom,
332
+ addMember,
333
+ removeMember,
334
+ refetch: fetchRooms
335
+ };
336
+ };
337
+
338
+ // src/react/hooks/usePresence.ts
339
+ var import_react3 = require("react");
340
+ var usePresence = (client) => {
341
+ const [onlineMap, setOnlineMap] = (0, import_react3.useState)(/* @__PURE__ */ new Map());
342
+ (0, import_react3.useEffect)(() => {
343
+ const onOnline = ({ userId }) => {
344
+ setOnlineMap((prev) => new Map(prev).set(userId, true));
345
+ };
346
+ const onOffline = ({ userId }) => {
347
+ setOnlineMap((prev) => new Map(prev).set(userId, false));
348
+ };
349
+ client.on("user:online", onOnline);
350
+ client.on("user:offline", onOffline);
351
+ return () => {
352
+ client.off("user:online", onOnline);
353
+ client.off("user:offline", onOffline);
354
+ };
355
+ }, [client]);
356
+ const isOnline = (0, import_react3.useCallback)(
357
+ (userId) => onlineMap.get(userId) ?? false,
358
+ [onlineMap]
359
+ );
360
+ const onlineUsers = Array.from(onlineMap.entries()).filter(([, online]) => online).map(([userId]) => userId);
361
+ return { isOnline, onlineUsers, onlineMap };
362
+ };
363
+
364
+ // src/react/hooks/useTyping.ts
365
+ var import_react4 = require("react");
366
+ var useTyping = (client, roomId) => {
367
+ const [typingUsers, setTypingUsers] = (0, import_react4.useState)(
368
+ /* @__PURE__ */ new Map()
369
+ );
370
+ const timeouts = (0, import_react4.useRef)(
371
+ /* @__PURE__ */ new Map()
372
+ );
373
+ const typingRef = (0, import_react4.useRef)(false);
374
+ const stopTimeout = (0, import_react4.useRef)(null);
375
+ (0, import_react4.useEffect)(() => {
376
+ if (!roomId) return;
377
+ const onStart = (event) => {
378
+ if (event.roomId !== roomId) return;
379
+ if (event.userId === client.currentUser?.userId) return;
380
+ setTypingUsers(
381
+ (prev) => new Map(prev).set(event.userId, event.displayName)
382
+ );
383
+ const existing = timeouts.current.get(event.userId);
384
+ if (existing) clearTimeout(existing);
385
+ const t = setTimeout(() => {
386
+ setTypingUsers((prev) => {
387
+ const next = new Map(prev);
388
+ next.delete(event.userId);
389
+ return next;
390
+ });
391
+ }, 4e3);
392
+ timeouts.current.set(event.userId, t);
393
+ };
394
+ const onStop = (event) => {
395
+ if (event.roomId !== roomId) return;
396
+ setTypingUsers((prev) => {
397
+ const next = new Map(prev);
398
+ next.delete(event.userId);
399
+ return next;
400
+ });
401
+ const existing = timeouts.current.get(event.userId);
402
+ if (existing) clearTimeout(existing);
403
+ timeouts.current.delete(event.userId);
404
+ };
405
+ client.on("typing:started", onStart);
406
+ client.on("typing:stopped", onStop);
407
+ return () => {
408
+ client.off("typing:started", onStart);
409
+ client.off("typing:stopped", onStop);
410
+ timeouts.current.forEach(clearTimeout);
411
+ timeouts.current.clear();
412
+ };
413
+ }, [roomId, client]);
414
+ const startTyping = (0, import_react4.useCallback)(() => {
415
+ if (!roomId) return;
416
+ if (!typingRef.current) {
417
+ client.startTyping(roomId);
418
+ typingRef.current = true;
419
+ }
420
+ if (stopTimeout.current) clearTimeout(stopTimeout.current);
421
+ stopTimeout.current = setTimeout(() => {
422
+ client.stopTyping(roomId);
423
+ typingRef.current = false;
424
+ }, 3e3);
425
+ }, [roomId, client]);
426
+ const stopTyping = (0, import_react4.useCallback)(() => {
427
+ if (!roomId) return;
428
+ if (stopTimeout.current) clearTimeout(stopTimeout.current);
429
+ if (typingRef.current) {
430
+ client.stopTyping(roomId);
431
+ typingRef.current = false;
432
+ }
433
+ }, [roomId, client]);
434
+ const typingText = (() => {
435
+ const names = Array.from(typingUsers.values());
436
+ if (names.length === 0) return null;
437
+ if (names.length === 1) return `${names[0]} is typing...`;
438
+ if (names.length === 2) return `${names[0]} and ${names[1]} are typing...`;
439
+ return `${names[0]} and ${names.length - 1} others are typing...`;
440
+ })();
441
+ return {
442
+ typingUsers,
443
+ typingText,
444
+ isAnyoneTyping: typingUsers.size > 0,
445
+ startTyping,
446
+ stopTyping
447
+ };
448
+ };
449
+
450
+ // src/react/hooks/useReadReceipts.ts
451
+ var import_react5 = require("react");
452
+ var useReadReceipts = (client, roomId) => {
453
+ const [receipts, setReceipts] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
454
+ (0, import_react5.useEffect)(() => {
455
+ if (!roomId) return;
456
+ const onReceipt = (event) => {
457
+ if (event.roomId !== roomId) return;
458
+ setReceipts((prev) => {
459
+ const next = new Map(prev);
460
+ const existing = next.get(event.lastMessageId) ?? /* @__PURE__ */ new Set();
461
+ existing.add(event.userId);
462
+ next.set(event.lastMessageId, existing);
463
+ return next;
464
+ });
465
+ };
466
+ client.on("receipt:updated", onReceipt);
467
+ return () => client.off("receipt:updated", onReceipt);
468
+ }, [roomId, client]);
469
+ const markSeen = (0, import_react5.useCallback)(
470
+ async (lastMessageId) => {
471
+ if (!roomId) return;
472
+ await client.markSeen(roomId, lastMessageId);
473
+ },
474
+ [roomId, client]
475
+ );
476
+ const seenBy = (0, import_react5.useCallback)(
477
+ (messageId) => {
478
+ return Array.from(receipts.get(messageId) ?? []);
479
+ },
480
+ [receipts]
481
+ );
482
+ return { markSeen, seenBy, receipts };
483
+ };
484
+
485
+ // src/react/hooks/useReactions.ts
486
+ var import_react6 = require("react");
487
+ var useReactions = (client, roomId) => {
488
+ const react = (0, import_react6.useCallback)(
489
+ async (messageId, emoji) => {
490
+ if (!roomId) throw new Error("No room selected");
491
+ await client.addReaction(messageId, roomId, emoji);
492
+ },
493
+ [roomId, client]
494
+ );
495
+ const hasReacted = (0, import_react6.useCallback)(
496
+ (reactions, emoji) => {
497
+ const userId = client.currentUser?.userId;
498
+ if (!userId) return false;
499
+ return reactions.find((r) => r.emoji === emoji)?.users.includes(userId) ?? false;
500
+ },
501
+ [client]
502
+ );
503
+ const getCount = (0, import_react6.useCallback)(
504
+ (reactions, emoji) => {
505
+ return reactions.find((r) => r.emoji === emoji)?.users.length ?? 0;
506
+ },
507
+ []
508
+ );
509
+ const getEmojis = (0, import_react6.useCallback)((reactions) => {
510
+ return reactions.filter((r) => r.users.length > 0).map((r) => r.emoji);
511
+ }, []);
512
+ return { react, hasReacted, getCount, getEmojis };
513
+ };
514
+
515
+ // src/react/hooks/useUpload.ts
516
+ var import_react7 = require("react");
517
+ var useUpload = (client) => {
518
+ const [uploading, setUploading] = (0, import_react7.useState)(false);
519
+ const [error, setError] = (0, import_react7.useState)(null);
520
+ const [lastUpload, setLastUpload] = (0, import_react7.useState)(null);
521
+ const upload = (0, import_react7.useCallback)(
522
+ async (file) => {
523
+ setUploading(true);
524
+ setError(null);
525
+ try {
526
+ const result = await client.uploadFile(file);
527
+ setLastUpload(result);
528
+ return result;
529
+ } catch (err) {
530
+ setError(err.message);
531
+ return null;
532
+ } finally {
533
+ setUploading(false);
534
+ }
535
+ },
536
+ [client]
537
+ );
538
+ const sendFile = (0, import_react7.useCallback)(
539
+ async (roomId, file, replyTo) => {
540
+ setUploading(true);
541
+ setError(null);
542
+ try {
543
+ const uploaded = await client.uploadFile(file);
544
+ setLastUpload(uploaded);
545
+ const message = await client.sendMessage({
546
+ roomId,
547
+ type: uploaded.type,
548
+ url: uploaded.url,
549
+ fileName: uploaded.fileName,
550
+ fileSize: uploaded.fileSize,
551
+ mimeType: uploaded.mimeType,
552
+ thumbnail: uploaded.thumbnail,
553
+ replyTo
554
+ });
555
+ return message;
556
+ } catch (err) {
557
+ setError(err.message);
558
+ return null;
559
+ } finally {
560
+ setUploading(false);
561
+ }
562
+ },
563
+ [client]
564
+ );
565
+ const validate = (0, import_react7.useCallback)((file, maxMb = 50) => {
566
+ if (file.size > maxMb * 1024 * 1024) {
567
+ return `File too large. Max size is ${maxMb}MB.`;
568
+ }
569
+ const allowed = [
570
+ "image/jpeg",
571
+ "image/png",
572
+ "image/gif",
573
+ "image/webp",
574
+ "video/mp4",
575
+ "video/webm",
576
+ "audio/mpeg",
577
+ "audio/ogg",
578
+ "audio/wav",
579
+ "application/pdf",
580
+ "application/msword",
581
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
582
+ "text/plain"
583
+ ];
584
+ if (!allowed.includes(file.type)) {
585
+ return `File type not supported: ${file.type}`;
586
+ }
587
+ return null;
588
+ }, []);
589
+ return { upload, sendFile, validate, uploading, error, lastUpload };
590
+ };
591
+
592
+ // src/react/components/MessageList.tsx
593
+ var import_react8 = require("react");
594
+ var import_jsx_runtime = require("react/jsx-runtime");
595
+ var formatTime = (iso) => new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
596
+ var formatFileSize = (bytes) => {
597
+ if (!bytes) return "";
598
+ if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
599
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
600
+ return `${bytes} B`;
601
+ };
602
+ var REACTION_EMOJIS = [
603
+ "\u{1FAE0}",
604
+ "\u{1F979}",
605
+ "\u{1FAE1}",
606
+ "\u{1F90C}",
607
+ "\u{1FAF6}",
608
+ "\u{1F480}",
609
+ "\u{1F525}",
610
+ "\u2728",
611
+ "\u{1FAE3}",
612
+ "\u{1F62E}\u200D\u{1F4A8}",
613
+ "\u{1FA84}",
614
+ "\u{1F972}",
615
+ "\u{1F485}",
616
+ "\u{1FAE6}",
617
+ "\u{1F92F}",
618
+ "\u{1F31A}",
619
+ "\u{1F441}\uFE0F",
620
+ "\u{1FAC0}",
621
+ "\u{1F98B}",
622
+ "\u{1FA90}"
623
+ ];
624
+ var EmojiPicker = ({ onPick, onClose, isOwn }) => {
625
+ const ref = (0, import_react8.useRef)(null);
626
+ (0, import_react8.useEffect)(() => {
627
+ const handler = (e) => {
628
+ if (ref.current && !ref.current.contains(e.target)) onClose();
629
+ };
630
+ document.addEventListener("mousedown", handler);
631
+ return () => document.removeEventListener("mousedown", handler);
632
+ }, [onClose]);
633
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
634
+ "div",
635
+ {
636
+ ref,
637
+ style: {
638
+ position: "absolute",
639
+ bottom: "calc(100% + 8px)",
640
+ [isOwn ? "right" : "left"]: 0,
641
+ zIndex: 100,
642
+ background: "#1a1a2e",
643
+ border: "1px solid rgba(255,255,255,0.1)",
644
+ borderRadius: 14,
645
+ padding: "8px 10px",
646
+ boxShadow: "0 8px 32px rgba(0,0,0,0.4)",
647
+ display: "grid",
648
+ gridTemplateColumns: "repeat(5, 1fr)",
649
+ gap: 4,
650
+ animation: "hermes-pop 0.15s ease"
651
+ },
652
+ children: REACTION_EMOJIS.map((emoji) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
653
+ "button",
654
+ {
655
+ onClick: () => {
656
+ onPick(emoji);
657
+ onClose();
658
+ },
659
+ style: {
660
+ background: "none",
661
+ border: "none",
662
+ cursor: "pointer",
663
+ fontSize: 20,
664
+ padding: "4px",
665
+ borderRadius: 8,
666
+ lineHeight: 1,
667
+ transition: "transform 0.1s, background 0.1s"
668
+ },
669
+ onMouseEnter: (e) => {
670
+ e.currentTarget.style.transform = "scale(1.3)";
671
+ e.currentTarget.style.background = "rgba(255,255,255,0.08)";
672
+ },
673
+ onMouseLeave: (e) => {
674
+ e.currentTarget.style.transform = "scale(1)";
675
+ e.currentTarget.style.background = "none";
676
+ },
677
+ children: emoji
678
+ },
679
+ emoji
680
+ ))
681
+ }
682
+ );
683
+ };
684
+ var TypingIndicator = ({ typingUsers }) => {
685
+ if (!typingUsers.length) return null;
686
+ const text = typingUsers.length === 1 ? `${typingUsers[0].displayName} is typing` : typingUsers.length === 2 ? `${typingUsers[0].displayName} and ${typingUsers[1].displayName} are typing` : `${typingUsers[0].displayName} and ${typingUsers.length - 1} others are typing`;
687
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
688
+ "div",
689
+ {
690
+ style: {
691
+ display: "flex",
692
+ alignItems: "center",
693
+ gap: 8,
694
+ padding: "6px 16px 2px",
695
+ minHeight: 28
696
+ },
697
+ children: [
698
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
699
+ "div",
700
+ {
701
+ style: {
702
+ display: "flex",
703
+ alignItems: "center",
704
+ gap: 3,
705
+ background: "#f0f0f0",
706
+ borderRadius: 12,
707
+ padding: "6px 10px"
708
+ },
709
+ children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
710
+ "span",
711
+ {
712
+ style: {
713
+ width: 6,
714
+ height: 6,
715
+ borderRadius: "50%",
716
+ background: "#999",
717
+ display: "block",
718
+ animation: `hermes-bounce 1.2s ease-in-out ${i * 0.18}s infinite`
719
+ }
720
+ },
721
+ i
722
+ ))
723
+ }
724
+ ),
725
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, color: "#999" }, children: text })
726
+ ]
727
+ }
728
+ );
729
+ };
730
+ var DefaultMessage = ({ message, isOwn, onEdit, onDelete, onReact, onReply, renderAvatar }) => {
731
+ const [hovered, setHovered] = (0, import_react8.useState)(false);
732
+ const [pickerOpen, setPickerOpen] = (0, import_react8.useState)(false);
733
+ if (message.isDeleted) {
734
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
735
+ "div",
736
+ {
737
+ style: {
738
+ opacity: 0.5,
739
+ fontStyle: "italic",
740
+ padding: "4px 16px",
741
+ fontSize: 13
742
+ },
743
+ children: "This message was deleted."
744
+ }
745
+ );
746
+ }
747
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
748
+ "div",
749
+ {
750
+ onMouseEnter: () => setHovered(true),
751
+ onMouseLeave: () => {
752
+ setHovered(false);
753
+ },
754
+ style: {
755
+ display: "flex",
756
+ flexDirection: isOwn ? "row-reverse" : "row",
757
+ alignItems: "flex-end",
758
+ gap: 8,
759
+ marginBottom: 4,
760
+ position: "relative"
761
+ },
762
+ children: [
763
+ !isOwn && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flexShrink: 0 }, children: renderAvatar ? renderAvatar(message.senderId) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
764
+ "div",
765
+ {
766
+ style: {
767
+ width: 32,
768
+ height: 32,
769
+ borderRadius: "50%",
770
+ background: "#e0e0e0",
771
+ display: "flex",
772
+ alignItems: "center",
773
+ justifyContent: "center",
774
+ fontSize: 12,
775
+ fontWeight: 600
776
+ },
777
+ children: message.senderId.slice(-2).toUpperCase()
778
+ }
779
+ ) }),
780
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
781
+ "div",
782
+ {
783
+ style: {
784
+ maxWidth: "70%",
785
+ display: "flex",
786
+ flexDirection: "column",
787
+ alignItems: isOwn ? "flex-end" : "flex-start"
788
+ },
789
+ children: [
790
+ (onEdit || onDelete || onReact || onReply) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
791
+ "div",
792
+ {
793
+ style: {
794
+ display: "flex",
795
+ flexDirection: isOwn ? "row-reverse" : "row",
796
+ gap: 2,
797
+ marginBottom: 4,
798
+ opacity: hovered ? 1 : 0,
799
+ pointerEvents: hovered ? "auto" : "none",
800
+ transition: "opacity 0.15s ease",
801
+ position: "relative"
802
+ },
803
+ children: [
804
+ onReact && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
806
+ ActionBtn,
807
+ {
808
+ onClick: () => setPickerOpen((p) => !p),
809
+ title: "React",
810
+ children: "\u{1FAE0}"
811
+ }
812
+ ),
813
+ pickerOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
814
+ EmojiPicker,
815
+ {
816
+ isOwn,
817
+ onPick: (emoji) => onReact(message._id, emoji),
818
+ onClose: () => setPickerOpen(false)
819
+ }
820
+ )
821
+ ] }),
822
+ onReply && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionBtn, { onClick: () => onReply(message), title: "Reply", children: "\u21A9" }),
823
+ isOwn && onEdit && message.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
824
+ ActionBtn,
825
+ {
826
+ onClick: () => {
827
+ const text = window.prompt("Edit message:", message.text);
828
+ if (text) onEdit(message._id, text);
829
+ },
830
+ title: "Edit",
831
+ children: "\u270F\uFE0F"
832
+ }
833
+ ),
834
+ isOwn && onDelete && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionBtn, { onClick: () => onDelete(message._id), title: "Delete", children: "\u{1F5D1}" })
835
+ ]
836
+ }
837
+ ),
838
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
839
+ "div",
840
+ {
841
+ style: {
842
+ padding: "8px 12px",
843
+ borderRadius: isOwn ? "16px 16px 4px 16px" : "16px 16px 16px 4px",
844
+ background: isOwn ? "#0084ff" : "#f0f0f0",
845
+ color: isOwn ? "#fff" : "#000"
846
+ },
847
+ children: [
848
+ message.replyTo && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
849
+ "div",
850
+ {
851
+ style: {
852
+ borderLeft: "3px solid rgba(255,255,255,0.4)",
853
+ paddingLeft: 8,
854
+ marginBottom: 6,
855
+ fontSize: 12,
856
+ opacity: 0.75
857
+ },
858
+ children: "Replying to a message"
859
+ }
860
+ ),
861
+ message.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { margin: 0, wordBreak: "break-word" }, children: [
862
+ message.text,
863
+ message.editedAt && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10, opacity: 0.6, marginLeft: 6 }, children: "(edited)" })
864
+ ] }),
865
+ message.type === "link" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
866
+ message.text && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "0 0 4px" }, children: message.text }),
867
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
868
+ "a",
869
+ {
870
+ href: message.url,
871
+ target: "_blank",
872
+ rel: "noopener noreferrer",
873
+ style: {
874
+ color: isOwn ? "#cce4ff" : "#0084ff",
875
+ wordBreak: "break-all"
876
+ },
877
+ children: message.url
878
+ }
879
+ )
880
+ ] }),
881
+ message.type === "image" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
882
+ "img",
883
+ {
884
+ src: message.url,
885
+ alt: message.fileName || "image",
886
+ style: { maxWidth: "100%", borderRadius: 8, display: "block" }
887
+ }
888
+ ),
889
+ message.type === "video" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
890
+ "video",
891
+ {
892
+ src: message.url,
893
+ controls: true,
894
+ style: { maxWidth: "100%", borderRadius: 8 }
895
+ }
896
+ ),
897
+ message.type === "audio" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("audio", { src: message.url, controls: true, style: { width: "100%" } }),
898
+ message.type === "document" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
899
+ "a",
900
+ {
901
+ href: message.url,
902
+ target: "_blank",
903
+ rel: "noopener noreferrer",
904
+ style: {
905
+ display: "flex",
906
+ alignItems: "center",
907
+ gap: 8,
908
+ color: isOwn ? "#fff" : "#333",
909
+ textDecoration: "none"
910
+ },
911
+ children: [
912
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 24 }, children: "\u{1F4C4}" }),
913
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
914
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, fontSize: 13 }, children: message.fileName }),
915
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11, opacity: 0.7 }, children: formatFileSize(message.fileSize) })
916
+ ] })
917
+ ]
918
+ }
919
+ ),
920
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
921
+ "div",
922
+ {
923
+ style: {
924
+ fontSize: 10,
925
+ opacity: 0.6,
926
+ textAlign: "right",
927
+ marginTop: 4
928
+ },
929
+ children: formatTime(message.createdAt)
930
+ }
931
+ )
932
+ ]
933
+ }
934
+ ),
935
+ message.reactions?.filter((r) => r.users.length > 0).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
936
+ "div",
937
+ {
938
+ style: { display: "flex", gap: 4, flexWrap: "wrap", marginTop: 4 },
939
+ children: message.reactions.filter((r) => r.users.length > 0).map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
940
+ "span",
941
+ {
942
+ onClick: () => onReact?.(message._id, r.emoji),
943
+ style: {
944
+ background: "#f0f0f0",
945
+ border: "1px solid rgba(0,0,0,0.08)",
946
+ borderRadius: 20,
947
+ padding: "2px 8px",
948
+ fontSize: 13,
949
+ cursor: "pointer",
950
+ display: "flex",
951
+ alignItems: "center",
952
+ gap: 4,
953
+ transition: "transform 0.1s",
954
+ userSelect: "none"
955
+ },
956
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.1)",
957
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
958
+ children: [
959
+ r.emoji,
960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
961
+ "span",
962
+ {
963
+ style: { fontSize: 11, fontWeight: 600, color: "#555" },
964
+ children: r.users.length
965
+ }
966
+ )
967
+ ]
968
+ },
969
+ r.emoji
970
+ ))
971
+ }
972
+ )
973
+ ]
974
+ }
975
+ )
976
+ ]
977
+ }
978
+ );
979
+ };
980
+ var ActionBtn = ({ onClick, title, children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
981
+ "button",
982
+ {
983
+ onClick,
984
+ title,
985
+ style: {
986
+ background: "#fff",
987
+ border: "1px solid rgba(0,0,0,0.1)",
988
+ borderRadius: 8,
989
+ cursor: "pointer",
990
+ fontSize: 14,
991
+ padding: "3px 6px",
992
+ lineHeight: 1,
993
+ boxShadow: "0 1px 4px rgba(0,0,0,0.1)",
994
+ transition: "transform 0.1s"
995
+ },
996
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.15)",
997
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
998
+ children
999
+ }
1000
+ );
1001
+ var MessageList = ({
1002
+ messages,
1003
+ currentUser,
1004
+ loading = false,
1005
+ loadingMore = false,
1006
+ hasMore = false,
1007
+ onLoadMore,
1008
+ onEdit,
1009
+ onDelete,
1010
+ onReact,
1011
+ onReply,
1012
+ renderMessage,
1013
+ renderAvatar,
1014
+ className = "",
1015
+ autoScroll = true,
1016
+ typingUsers = []
1017
+ }) => {
1018
+ const bottomRef = (0, import_react8.useRef)(null);
1019
+ const containerRef = (0, import_react8.useRef)(null);
1020
+ (0, import_react8.useEffect)(() => {
1021
+ if (autoScroll && bottomRef.current) {
1022
+ bottomRef.current.scrollIntoView({ behavior: "smooth" });
1023
+ }
1024
+ }, [messages, autoScroll]);
1025
+ (0, import_react8.useEffect)(() => {
1026
+ const container = containerRef.current;
1027
+ if (!container || !onLoadMore) return;
1028
+ const onScroll = () => {
1029
+ if (container.scrollTop === 0 && hasMore && !loadingMore) onLoadMore();
1030
+ };
1031
+ container.addEventListener("scroll", onScroll);
1032
+ return () => container.removeEventListener("scroll", onScroll);
1033
+ }, [hasMore, loadingMore, onLoadMore]);
1034
+ if (loading) {
1035
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1036
+ "div",
1037
+ {
1038
+ style: {
1039
+ display: "flex",
1040
+ alignItems: "center",
1041
+ justifyContent: "center",
1042
+ height: "100%"
1043
+ },
1044
+ children: "Loading messages..."
1045
+ }
1046
+ );
1047
+ }
1048
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
1050
+ @keyframes hermes-bounce {
1051
+ 0%, 80%, 100% { transform: translateY(0); }
1052
+ 40% { transform: translateY(-5px); }
1053
+ }
1054
+ @keyframes hermes-pop {
1055
+ from { opacity: 0; transform: scale(0.85); }
1056
+ to { opacity: 1; transform: scale(1); }
1057
+ }
1058
+ ` }),
1059
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1060
+ "div",
1061
+ {
1062
+ ref: containerRef,
1063
+ className: `hermes-message-list ${className}`,
1064
+ style: {
1065
+ overflowY: "auto",
1066
+ display: "flex",
1067
+ flexDirection: "column",
1068
+ height: "100%",
1069
+ padding: "16px"
1070
+ },
1071
+ children: [
1072
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { textAlign: "center", marginBottom: 12 }, children: loadingMore ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, opacity: 0.5 }, children: "Loading older messages..." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1073
+ "button",
1074
+ {
1075
+ onClick: onLoadMore,
1076
+ style: {
1077
+ background: "none",
1078
+ border: "1px solid #ddd",
1079
+ borderRadius: 12,
1080
+ padding: "4px 12px",
1081
+ cursor: "pointer",
1082
+ fontSize: 12
1083
+ },
1084
+ children: "Load older messages"
1085
+ }
1086
+ ) }),
1087
+ messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1088
+ "div",
1089
+ {
1090
+ style: {
1091
+ textAlign: "center",
1092
+ opacity: 0.4,
1093
+ margin: "auto",
1094
+ fontSize: 14
1095
+ },
1096
+ children: "No messages yet. Say hello! \u{1F44B}"
1097
+ }
1098
+ ),
1099
+ messages.map((message) => {
1100
+ const isOwn = message.senderId === currentUser.userId;
1101
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: 8 }, children: renderMessage ? renderMessage(message, isOwn) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1102
+ DefaultMessage,
1103
+ {
1104
+ message,
1105
+ isOwn,
1106
+ onEdit,
1107
+ onDelete,
1108
+ onReact,
1109
+ onReply,
1110
+ renderAvatar
1111
+ }
1112
+ ) }, message._id);
1113
+ }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TypingIndicator, { typingUsers }),
1115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: bottomRef })
1116
+ ]
1117
+ }
1118
+ )
1119
+ ] });
1120
+ };
1121
+
1122
+ // src/react/components/ChatInput.tsx
1123
+ var import_react9 = require("react");
1124
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1125
+ var ChatInput = ({
1126
+ onSendText,
1127
+ onSendFile,
1128
+ onTypingStart,
1129
+ onTypingStop,
1130
+ replyingTo,
1131
+ onCancelReply,
1132
+ disabled = false,
1133
+ placeholder = "Type a message...",
1134
+ maxLength = 4e3,
1135
+ className = "",
1136
+ inputClassName = "",
1137
+ renderAttachIcon,
1138
+ renderSendIcon
1139
+ }) => {
1140
+ const [text, setText] = (0, import_react9.useState)("");
1141
+ const [sending, setSending] = (0, import_react9.useState)(false);
1142
+ const fileRef = (0, import_react9.useRef)(null);
1143
+ const textareaRef = (0, import_react9.useRef)(null);
1144
+ const resizeTextarea = (0, import_react9.useCallback)(() => {
1145
+ const el = textareaRef.current;
1146
+ if (!el) return;
1147
+ el.style.height = "auto";
1148
+ el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
1149
+ }, []);
1150
+ const handleChange = (e) => {
1151
+ setText(e.target.value);
1152
+ resizeTextarea();
1153
+ onTypingStart?.();
1154
+ };
1155
+ const handleSend = async () => {
1156
+ const trimmed = text.trim();
1157
+ if (!trimmed || sending || disabled) return;
1158
+ setSending(true);
1159
+ try {
1160
+ await onSendText(trimmed);
1161
+ setText("");
1162
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
1163
+ onTypingStop?.();
1164
+ } finally {
1165
+ setSending(false);
1166
+ }
1167
+ };
1168
+ const handleKeyDown = (e) => {
1169
+ if (e.key === "Enter" && !e.shiftKey) {
1170
+ e.preventDefault();
1171
+ handleSend();
1172
+ }
1173
+ };
1174
+ const handleFileChange = async (e) => {
1175
+ const file = e.target.files?.[0];
1176
+ if (!file || !onSendFile) return;
1177
+ await onSendFile(file);
1178
+ if (fileRef.current) fileRef.current.value = "";
1179
+ };
1180
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1181
+ "div",
1182
+ {
1183
+ className: `hermes-chat-input ${className}`,
1184
+ style: {
1185
+ display: "flex",
1186
+ flexDirection: "column",
1187
+ padding: "8px 12px",
1188
+ borderTop: "1px solid #e0e0e0"
1189
+ },
1190
+ children: [
1191
+ replyingTo && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1192
+ "div",
1193
+ {
1194
+ className: "hermes-chat-input__reply",
1195
+ style: {
1196
+ display: "flex",
1197
+ alignItems: "center",
1198
+ justifyContent: "space-between",
1199
+ padding: "6px 10px",
1200
+ marginBottom: 6,
1201
+ background: "#f5f5f5",
1202
+ borderRadius: 8,
1203
+ borderLeft: "3px solid #0084ff",
1204
+ fontSize: 12
1205
+ },
1206
+ children: [
1207
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { overflow: "hidden" }, children: [
1208
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontWeight: 600, marginRight: 4 }, children: "Replying to:" }),
1209
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { opacity: 0.7 }, children: replyingTo.type === "text" ? replyingTo.text?.slice(0, 60) : `[${replyingTo.type}]` })
1210
+ ] }),
1211
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1212
+ "button",
1213
+ {
1214
+ onClick: onCancelReply,
1215
+ style: {
1216
+ background: "none",
1217
+ border: "none",
1218
+ cursor: "pointer",
1219
+ fontSize: 16,
1220
+ lineHeight: 1
1221
+ },
1222
+ children: "\u2715"
1223
+ }
1224
+ )
1225
+ ]
1226
+ }
1227
+ ),
1228
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1229
+ "div",
1230
+ {
1231
+ className: "hermes-chat-input__row",
1232
+ style: { display: "flex", alignItems: "flex-end", gap: 8 },
1233
+ children: [
1234
+ onSendFile && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1235
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1236
+ "button",
1237
+ {
1238
+ onClick: () => fileRef.current?.click(),
1239
+ disabled,
1240
+ className: "hermes-chat-input__attach",
1241
+ style: {
1242
+ background: "none",
1243
+ border: "none",
1244
+ cursor: "pointer",
1245
+ padding: 6,
1246
+ flexShrink: 0,
1247
+ opacity: disabled ? 0.4 : 1
1248
+ },
1249
+ children: renderAttachIcon ? renderAttachIcon() : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1250
+ "svg",
1251
+ {
1252
+ width: "20",
1253
+ height: "20",
1254
+ viewBox: "0 0 24 24",
1255
+ fill: "none",
1256
+ stroke: "currentColor",
1257
+ strokeWidth: "2",
1258
+ strokeLinecap: "round",
1259
+ strokeLinejoin: "round",
1260
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" })
1261
+ }
1262
+ )
1263
+ }
1264
+ ),
1265
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1266
+ "input",
1267
+ {
1268
+ ref: fileRef,
1269
+ type: "file",
1270
+ style: { display: "none" },
1271
+ onChange: handleFileChange,
1272
+ accept: "image/*,video/*,audio/*,.pdf,.doc,.docx,.txt"
1273
+ }
1274
+ )
1275
+ ] }),
1276
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1277
+ "textarea",
1278
+ {
1279
+ ref: textareaRef,
1280
+ value: text,
1281
+ onChange: handleChange,
1282
+ onKeyDown: handleKeyDown,
1283
+ onBlur: () => onTypingStop?.(),
1284
+ placeholder,
1285
+ disabled,
1286
+ maxLength,
1287
+ rows: 1,
1288
+ className: `hermes-chat-input__textarea ${inputClassName}`,
1289
+ style: {
1290
+ flex: 1,
1291
+ resize: "none",
1292
+ border: "1px solid #e0e0e0",
1293
+ borderRadius: 20,
1294
+ padding: "8px 14px",
1295
+ fontSize: 14,
1296
+ lineHeight: 1.5,
1297
+ outline: "none",
1298
+ overflow: "hidden",
1299
+ background: disabled ? "#f5f5f5" : "#fff"
1300
+ }
1301
+ }
1302
+ ),
1303
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1304
+ "button",
1305
+ {
1306
+ onClick: handleSend,
1307
+ disabled: !text.trim() || sending || disabled,
1308
+ className: "hermes-chat-input__send",
1309
+ style: {
1310
+ background: "none",
1311
+ border: "none",
1312
+ cursor: "pointer",
1313
+ padding: 6,
1314
+ flexShrink: 0,
1315
+ opacity: !text.trim() || sending || disabled ? 0.4 : 1
1316
+ },
1317
+ children: renderSendIcon ? renderSendIcon() : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
1318
+ }
1319
+ )
1320
+ ]
1321
+ }
1322
+ ),
1323
+ text.length > maxLength * 0.8 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1324
+ "div",
1325
+ {
1326
+ style: {
1327
+ fontSize: 10,
1328
+ textAlign: "right",
1329
+ opacity: 0.5,
1330
+ marginTop: 2
1331
+ },
1332
+ children: [
1333
+ text.length,
1334
+ "/",
1335
+ maxLength
1336
+ ]
1337
+ }
1338
+ )
1339
+ ]
1340
+ }
1341
+ );
1342
+ };
1343
+
1344
+ // src/react/components/RoomList.tsx
1345
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1346
+ var formatLastActivity = (iso) => {
1347
+ const date = new Date(iso);
1348
+ const now = /* @__PURE__ */ new Date();
1349
+ const diffMs = now.getTime() - date.getTime();
1350
+ const diffMins = Math.floor(diffMs / 6e4);
1351
+ const diffHours = Math.floor(diffMins / 60);
1352
+ const diffDays = Math.floor(diffHours / 24);
1353
+ if (diffMins < 1) return "now";
1354
+ if (diffMins < 60) return `${diffMins}m`;
1355
+ if (diffHours < 24) return `${diffHours}h`;
1356
+ if (diffDays < 7) return `${diffDays}d`;
1357
+ return date.toLocaleDateString();
1358
+ };
1359
+ var getRoomName = (room, currentUserId) => {
1360
+ if (room.type === "group") return room.name ?? "Group";
1361
+ const other = room.members.find((m) => m !== currentUserId);
1362
+ return other ?? "Direct Message";
1363
+ };
1364
+ var getLastMessagePreview = (room) => {
1365
+ const msg = room.lastMessage;
1366
+ if (!msg) return "No messages yet";
1367
+ if (msg.isDeleted) return "Message deleted";
1368
+ if (msg.type === "text") return msg.text?.slice(0, 50) ?? "";
1369
+ if (msg.type === "image") return "\u{1F4F7} Image";
1370
+ if (msg.type === "video") return "\u{1F3A5} Video";
1371
+ if (msg.type === "audio") return "\u{1F3B5} Audio";
1372
+ if (msg.type === "document") return `\u{1F4C4} ${msg.fileName ?? "File"}`;
1373
+ if (msg.type === "link") return `\u{1F517} ${msg.url}`;
1374
+ return "";
1375
+ };
1376
+ var DefaultRoomItem = ({ room, isActive, currentUserId, renderAvatar, itemClassName }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1377
+ "div",
1378
+ {
1379
+ className: `hermes-room-item ${isActive ? "hermes-room-item--active" : ""} ${itemClassName ?? ""}`,
1380
+ style: {
1381
+ display: "flex",
1382
+ alignItems: "center",
1383
+ gap: 10,
1384
+ padding: "10px 12px",
1385
+ cursor: "pointer",
1386
+ background: isActive ? "rgba(0,132,255,0.08)" : "transparent",
1387
+ borderLeft: isActive ? "3px solid #0084ff" : "3px solid transparent"
1388
+ },
1389
+ children: [
1390
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { flexShrink: 0 }, children: renderAvatar ? renderAvatar(room) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1391
+ "div",
1392
+ {
1393
+ style: {
1394
+ width: 42,
1395
+ height: 42,
1396
+ borderRadius: "50%",
1397
+ background: "#e0e0e0",
1398
+ display: "flex",
1399
+ alignItems: "center",
1400
+ justifyContent: "center",
1401
+ fontWeight: 700,
1402
+ fontSize: 16
1403
+ },
1404
+ children: room.type === "group" ? "G" : "D"
1405
+ }
1406
+ ) }),
1407
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, overflow: "hidden" }, children: [
1408
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1409
+ "div",
1410
+ {
1411
+ style: {
1412
+ display: "flex",
1413
+ justifyContent: "space-between",
1414
+ alignItems: "baseline"
1415
+ },
1416
+ children: [
1417
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1418
+ "span",
1419
+ {
1420
+ style: {
1421
+ fontWeight: 600,
1422
+ fontSize: 14,
1423
+ overflow: "hidden",
1424
+ textOverflow: "ellipsis",
1425
+ whiteSpace: "nowrap"
1426
+ },
1427
+ children: getRoomName(room, currentUserId)
1428
+ }
1429
+ ),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1431
+ "span",
1432
+ {
1433
+ style: { fontSize: 11, opacity: 0.5, flexShrink: 0, marginLeft: 4 },
1434
+ children: formatLastActivity(room.lastActivity)
1435
+ }
1436
+ )
1437
+ ]
1438
+ }
1439
+ ),
1440
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1441
+ "div",
1442
+ {
1443
+ style: {
1444
+ display: "flex",
1445
+ justifyContent: "space-between",
1446
+ alignItems: "center",
1447
+ marginTop: 2
1448
+ },
1449
+ children: [
1450
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1451
+ "span",
1452
+ {
1453
+ style: {
1454
+ fontSize: 13,
1455
+ opacity: 0.6,
1456
+ overflow: "hidden",
1457
+ textOverflow: "ellipsis",
1458
+ whiteSpace: "nowrap"
1459
+ },
1460
+ children: getLastMessagePreview(room)
1461
+ }
1462
+ ),
1463
+ room.unreadCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1464
+ "span",
1465
+ {
1466
+ style: {
1467
+ background: "#0084ff",
1468
+ color: "#fff",
1469
+ borderRadius: 10,
1470
+ fontSize: 11,
1471
+ fontWeight: 700,
1472
+ padding: "1px 7px",
1473
+ flexShrink: 0,
1474
+ marginLeft: 4
1475
+ },
1476
+ children: room.unreadCount > 99 ? "99+" : room.unreadCount
1477
+ }
1478
+ )
1479
+ ]
1480
+ }
1481
+ )
1482
+ ] })
1483
+ ]
1484
+ }
1485
+ );
1486
+ var RoomList = ({
1487
+ rooms,
1488
+ activeRoomId,
1489
+ currentUserId,
1490
+ loading = false,
1491
+ onSelectRoom,
1492
+ onCreateDirect,
1493
+ onCreateGroup,
1494
+ renderRoomItem,
1495
+ renderAvatar,
1496
+ renderEmpty,
1497
+ className = "",
1498
+ itemClassName = ""
1499
+ }) => {
1500
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1501
+ "div",
1502
+ {
1503
+ className: `hermes-room-list ${className}`,
1504
+ style: {
1505
+ display: "flex",
1506
+ flexDirection: "column",
1507
+ height: "100%",
1508
+ overflowY: "auto"
1509
+ },
1510
+ children: [
1511
+ (onCreateDirect || onCreateGroup) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1512
+ "div",
1513
+ {
1514
+ style: {
1515
+ display: "flex",
1516
+ gap: 8,
1517
+ padding: "10px 12px",
1518
+ borderBottom: "1px solid #e0e0e0"
1519
+ },
1520
+ children: [
1521
+ onCreateDirect && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1522
+ "button",
1523
+ {
1524
+ onClick: onCreateDirect,
1525
+ style: {
1526
+ flex: 1,
1527
+ background: "#0084ff",
1528
+ color: "#fff",
1529
+ border: "none",
1530
+ borderRadius: 8,
1531
+ padding: "8px 10px",
1532
+ cursor: "pointer",
1533
+ fontSize: 13,
1534
+ fontWeight: 600
1535
+ },
1536
+ children: "+ Direct"
1537
+ }
1538
+ ),
1539
+ onCreateGroup && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1540
+ "button",
1541
+ {
1542
+ onClick: onCreateGroup,
1543
+ style: {
1544
+ flex: 1,
1545
+ background: "none",
1546
+ border: "1px solid #e0e0e0",
1547
+ borderRadius: 8,
1548
+ padding: "8px 10px",
1549
+ cursor: "pointer",
1550
+ fontSize: 13,
1551
+ fontWeight: 600
1552
+ },
1553
+ children: "+ Group"
1554
+ }
1555
+ )
1556
+ ]
1557
+ }
1558
+ ),
1559
+ loading && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: "12px 16px", opacity: 0.5, fontSize: 13 }, children: "Loading rooms..." }),
1560
+ !loading && rooms.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1561
+ "div",
1562
+ {
1563
+ style: {
1564
+ textAlign: "center",
1565
+ padding: 24,
1566
+ opacity: 0.4,
1567
+ fontSize: 13
1568
+ },
1569
+ children: renderEmpty ? renderEmpty() : "No conversations yet."
1570
+ }
1571
+ ),
1572
+ !loading && rooms.map((room) => {
1573
+ const isActive = room._id === activeRoomId;
1574
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { onClick: () => onSelectRoom(room), children: renderRoomItem ? renderRoomItem(room, isActive) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1575
+ DefaultRoomItem,
1576
+ {
1577
+ room,
1578
+ isActive,
1579
+ currentUserId,
1580
+ renderAvatar,
1581
+ itemClassName
1582
+ }
1583
+ ) }, room._id);
1584
+ })
1585
+ ]
1586
+ }
1587
+ );
1588
+ };
1589
+
1590
+ // src/react/components/TypingIndicator.tsx
1591
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1592
+ var TypingIndicator2 = ({
1593
+ typingText,
1594
+ className = ""
1595
+ }) => {
1596
+ if (!typingText) return null;
1597
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1598
+ "div",
1599
+ {
1600
+ className: `hermes-typing-indicator ${className}`,
1601
+ style: {
1602
+ display: "flex",
1603
+ alignItems: "center",
1604
+ gap: 6,
1605
+ padding: "4px 16px",
1606
+ minHeight: 24
1607
+ },
1608
+ children: [
1609
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { display: "flex", gap: 3 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1610
+ "span",
1611
+ {
1612
+ style: {
1613
+ width: 6,
1614
+ height: 6,
1615
+ borderRadius: "50%",
1616
+ background: "#aaa",
1617
+ display: "block",
1618
+ animation: `hermes-bounce 1.2s ease-in-out ${i * 0.2}s infinite`
1619
+ }
1620
+ },
1621
+ i
1622
+ )) }),
1623
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontSize: 12, opacity: 0.6 }, children: typingText }),
1624
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
1625
+ @keyframes hermes-bounce {
1626
+ 0%, 80%, 100% { transform: translateY(0); }
1627
+ 40% { transform: translateY(-4px); }
1628
+ }
1629
+ ` })
1630
+ ]
1631
+ }
1632
+ );
1633
+ };
1634
+
1635
+ // src/react/components/OnlineBadge.tsx
1636
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1637
+ var OnlineBadge = ({
1638
+ isOnline,
1639
+ size = 10,
1640
+ className = ""
1641
+ }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1642
+ "span",
1643
+ {
1644
+ className: `hermes-online-badge ${isOnline ? "hermes-online-badge--online" : "hermes-online-badge--offline"} ${className}`,
1645
+ "data-online": isOnline,
1646
+ style: {
1647
+ display: "inline-block",
1648
+ width: size,
1649
+ height: size,
1650
+ borderRadius: "50%",
1651
+ background: isOnline ? "#22c55e" : "#d1d5db",
1652
+ boxShadow: isOnline ? "0 0 0 2px #fff" : "none",
1653
+ flexShrink: 0
1654
+ }
1655
+ }
1656
+ );
1657
+
1658
+ // src/react/components/ReactionPicker.tsx
1659
+ var import_react10 = require("react");
1660
+ var import_emoji_picker_react = __toESM(require("emoji-picker-react"), 1);
1661
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1662
+ var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F525}", "\u{1F389}", "\u{1F44F}"];
1663
+ var ReactionPicker = ({
1664
+ onSelect,
1665
+ currentReactions = [],
1666
+ currentUserId,
1667
+ emojis = DEFAULT_EMOJIS,
1668
+ className = "",
1669
+ align = "left"
1670
+ }) => {
1671
+ const [showPicker, setShowPicker] = (0, import_react10.useState)(false);
1672
+ const containerRef = (0, import_react10.useRef)(null);
1673
+ const hasReacted = (emoji) => {
1674
+ if (!currentUserId) return false;
1675
+ return currentReactions.find((r) => r.emoji === emoji)?.users.includes(currentUserId) ?? false;
1676
+ };
1677
+ const handleEmojiClick = (emojiData) => {
1678
+ onSelect(emojiData.emoji);
1679
+ setShowPicker(false);
1680
+ };
1681
+ (0, import_react10.useEffect)(() => {
1682
+ const handleOutsideClick = (e) => {
1683
+ if (!containerRef.current) return;
1684
+ const target = e.target;
1685
+ if (!containerRef.current.contains(target)) {
1686
+ setShowPicker(false);
1687
+ }
1688
+ };
1689
+ if (showPicker) {
1690
+ window.addEventListener("click", handleOutsideClick);
1691
+ }
1692
+ return () => window.removeEventListener("click", handleOutsideClick);
1693
+ }, [showPicker]);
1694
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1695
+ "div",
1696
+ {
1697
+ ref: containerRef,
1698
+ style: { position: "relative", display: "inline-block" },
1699
+ className,
1700
+ children: [
1701
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1702
+ "div",
1703
+ {
1704
+ style: {
1705
+ display: "flex",
1706
+ gap: 4,
1707
+ flexWrap: "wrap",
1708
+ padding: "6px 8px",
1709
+ background: "#111",
1710
+ borderRadius: 12,
1711
+ border: "1px solid rgba(255,255,255,0.08)"
1712
+ },
1713
+ children: [
1714
+ emojis.map((emoji) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1715
+ "button",
1716
+ {
1717
+ onClick: () => onSelect(emoji),
1718
+ style: {
1719
+ background: hasReacted(emoji) ? "rgba(57,255,20,0.12)" : "transparent",
1720
+ border: hasReacted(emoji) ? "1px solid rgba(57,255,20,0.35)" : "1px solid transparent",
1721
+ borderRadius: 8,
1722
+ padding: "4px 6px",
1723
+ cursor: "pointer",
1724
+ fontSize: 18,
1725
+ lineHeight: 1,
1726
+ transition: "transform 0.12s ease"
1727
+ },
1728
+ onMouseEnter: (e) => e.currentTarget.style.transform = "scale(1.2)",
1729
+ onMouseLeave: (e) => e.currentTarget.style.transform = "scale(1)",
1730
+ children: emoji
1731
+ },
1732
+ emoji
1733
+ )),
1734
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1735
+ "button",
1736
+ {
1737
+ onClick: (e) => {
1738
+ e.stopPropagation();
1739
+ setShowPicker((v) => !v);
1740
+ },
1741
+ style: {
1742
+ borderRadius: 8,
1743
+ padding: "4px 6px",
1744
+ cursor: "pointer",
1745
+ fontSize: 18,
1746
+ border: "1px solid transparent",
1747
+ background: "transparent"
1748
+ },
1749
+ children: "\u2795"
1750
+ }
1751
+ )
1752
+ ]
1753
+ }
1754
+ ),
1755
+ showPicker && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1756
+ "div",
1757
+ {
1758
+ onMouseDown: (e) => e.stopPropagation(),
1759
+ onClick: (e) => e.stopPropagation(),
1760
+ style: {
1761
+ position: "absolute",
1762
+ bottom: "calc(100% + 6px)",
1763
+ [align === "right" ? "right" : "left"]: 0,
1764
+ zIndex: 50,
1765
+ animation: "hermes-pop 0.15s ease"
1766
+ },
1767
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1768
+ import_emoji_picker_react.default,
1769
+ {
1770
+ theme: import_emoji_picker_react.Theme.DARK,
1771
+ onEmojiClick: handleEmojiClick,
1772
+ height: 440,
1773
+ width: 360,
1774
+ searchPlaceHolder: "Search emoji...",
1775
+ lazyLoadEmojis: true
1776
+ }
1777
+ )
1778
+ }
1779
+ ),
1780
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: `
1781
+ @keyframes hermes-pop {
1782
+ from {
1783
+ opacity: 0;
1784
+ transform: scale(0.85);
1785
+ }
1786
+ to {
1787
+ opacity: 1;
1788
+ transform: scale(1);
1789
+ }
1790
+ }
1791
+ ` })
1792
+ ]
1793
+ }
1794
+ );
1795
+ };
1796
+
1797
+ // src/react/components/MediaMessage.tsx
1798
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1799
+ var formatFileSize2 = (bytes) => {
1800
+ if (!bytes) return "";
1801
+ if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
1802
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1803
+ return `${bytes} B`;
1804
+ };
1805
+ var MediaMessage = ({
1806
+ message,
1807
+ className = "",
1808
+ maxWidth = 300
1809
+ }) => {
1810
+ if (!message.url) return null;
1811
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1812
+ "div",
1813
+ {
1814
+ className: `hermes-media-message hermes-media-message--${message.type} ${className}`,
1815
+ style: { maxWidth },
1816
+ children: [
1817
+ message.type === "image" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1818
+ "img",
1819
+ {
1820
+ src: message.url,
1821
+ alt: message.fileName ?? "image",
1822
+ style: {
1823
+ width: "100%",
1824
+ borderRadius: 10,
1825
+ display: "block",
1826
+ cursor: "pointer"
1827
+ },
1828
+ onClick: () => window.open(message.url, "_blank")
1829
+ }
1830
+ ),
1831
+ message.type === "video" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1832
+ "video",
1833
+ {
1834
+ src: message.url,
1835
+ poster: message.thumbnail,
1836
+ controls: true,
1837
+ style: { width: "100%", borderRadius: 10 }
1838
+ }
1839
+ ),
1840
+ message.type === "audio" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1841
+ "div",
1842
+ {
1843
+ style: { display: "flex", alignItems: "center", gap: 8, padding: 8 },
1844
+ children: [
1845
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 20 }, children: "\u{1F3B5}" }),
1846
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("audio", { src: message.url, controls: true, style: { flex: 1, height: 36 } })
1847
+ ]
1848
+ }
1849
+ ),
1850
+ message.type === "document" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1851
+ "a",
1852
+ {
1853
+ href: message.url,
1854
+ target: "_blank",
1855
+ rel: "noopener noreferrer",
1856
+ style: {
1857
+ display: "flex",
1858
+ alignItems: "center",
1859
+ gap: 10,
1860
+ padding: "10px 12px",
1861
+ borderRadius: 10,
1862
+ border: "1px solid #e0e0e0",
1863
+ textDecoration: "none",
1864
+ color: "inherit"
1865
+ },
1866
+ children: [
1867
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 28, flexShrink: 0 }, children: "\u{1F4C4}" }),
1868
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { overflow: "hidden" }, children: [
1869
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1870
+ "div",
1871
+ {
1872
+ style: {
1873
+ fontWeight: 600,
1874
+ fontSize: 13,
1875
+ overflow: "hidden",
1876
+ textOverflow: "ellipsis",
1877
+ whiteSpace: "nowrap"
1878
+ },
1879
+ children: message.fileName ?? "Document"
1880
+ }
1881
+ ),
1882
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { fontSize: 11, opacity: 0.6 }, children: [
1883
+ formatFileSize2(message.fileSize),
1884
+ " \xB7 Click to download"
1885
+ ] })
1886
+ ] })
1887
+ ]
1888
+ }
1889
+ ),
1890
+ message.type === "link" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1891
+ "a",
1892
+ {
1893
+ href: message.url,
1894
+ target: "_blank",
1895
+ rel: "noopener noreferrer",
1896
+ style: {
1897
+ display: "flex",
1898
+ alignItems: "center",
1899
+ gap: 8,
1900
+ padding: "8px 12px",
1901
+ borderRadius: 10,
1902
+ border: "1px solid #e0e0e0",
1903
+ textDecoration: "none",
1904
+ color: "#0084ff",
1905
+ wordBreak: "break-all",
1906
+ fontSize: 13
1907
+ },
1908
+ children: [
1909
+ "\u{1F517} ",
1910
+ message.url
1911
+ ]
1912
+ }
1913
+ )
1914
+ ]
1915
+ }
1916
+ );
1917
+ };
1918
+ // Annotate the CommonJS export names for ESM import in node:
1919
+ 0 && (module.exports = {
1920
+ ChatInput,
1921
+ MediaMessage,
1922
+ MessageList,
1923
+ OnlineBadge,
1924
+ ReactionPicker,
1925
+ RoomList,
1926
+ TypingIndicator,
1927
+ useMessages,
1928
+ usePresence,
1929
+ useReactions,
1930
+ useReadReceipts,
1931
+ useRooms,
1932
+ useTyping,
1933
+ useUpload
1934
+ });
1935
+ //# sourceMappingURL=react.cjs.map