@thrillee/aegischat 0.1.5 → 0.1.6
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/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +568 -330
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +568 -330
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/hooks/useChat.ts +721 -389
package/dist/index.mjs
CHANGED
|
@@ -245,15 +245,28 @@ var MAX_RECONNECT_ATTEMPTS = 5;
|
|
|
245
245
|
var MAX_RECONNECT_DELAY = 3e4;
|
|
246
246
|
var PING_INTERVAL = 3e4;
|
|
247
247
|
var SESSION_STORAGE_KEY = "@aegischat/activeChannel";
|
|
248
|
-
function useChat(options) {
|
|
249
|
-
const {
|
|
250
|
-
|
|
248
|
+
function useChat(options = {}) {
|
|
249
|
+
const {
|
|
250
|
+
config,
|
|
251
|
+
role,
|
|
252
|
+
clientId,
|
|
253
|
+
initialSession,
|
|
254
|
+
autoConnect = true,
|
|
255
|
+
onMessage,
|
|
256
|
+
onTyping,
|
|
257
|
+
onConnectionChange
|
|
258
|
+
} = options;
|
|
259
|
+
const [session, setSession] = useState(null);
|
|
251
260
|
const [isConnected, setIsConnected] = useState(false);
|
|
252
261
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
253
|
-
const [activeChannelId, setActiveChannelIdState] = useState(
|
|
262
|
+
const [activeChannelId, setActiveChannelIdState] = useState(
|
|
263
|
+
null
|
|
264
|
+
);
|
|
254
265
|
const [channels, setChannels] = useState([]);
|
|
255
266
|
const [messages, setMessages] = useState([]);
|
|
256
|
-
const [typingUsers, setTypingUsers] = useState(
|
|
267
|
+
const [typingUsers, setTypingUsers] = useState(
|
|
268
|
+
{}
|
|
269
|
+
);
|
|
257
270
|
const [isLoadingChannels, setIsLoadingChannels] = useState(false);
|
|
258
271
|
const [isLoadingMessages, setIsLoadingMessages] = useState(false);
|
|
259
272
|
const [hasMoreMessages, setHasMoreMessages] = useState(true);
|
|
@@ -266,23 +279,19 @@ function useChat(options) {
|
|
|
266
279
|
const isManualDisconnect = useRef(false);
|
|
267
280
|
const oldestMessageId = useRef(null);
|
|
268
281
|
const activeChannelIdRef = useRef(null);
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
useEffect(() => {
|
|
278
|
-
configRef.current = config;
|
|
279
|
-
}, [config]);
|
|
282
|
+
const sessionRef = useRef(null);
|
|
283
|
+
const roleRef = useRef(void 0);
|
|
284
|
+
const clientIdRef = useRef(void 0);
|
|
285
|
+
const autoConnectRef = useRef(true);
|
|
286
|
+
const onMessageRef = useRef(void 0);
|
|
287
|
+
const onTypingRef = useRef(void 0);
|
|
288
|
+
const onConnectionChangeRef = useRef(void 0);
|
|
280
289
|
useEffect(() => {
|
|
281
290
|
activeChannelIdRef.current = activeChannelId;
|
|
282
291
|
}, [activeChannelId]);
|
|
283
292
|
useEffect(() => {
|
|
284
|
-
|
|
285
|
-
}, [
|
|
293
|
+
activeChannelIdRef.current = activeChannelId;
|
|
294
|
+
}, [activeChannelId]);
|
|
286
295
|
const getActiveChannelId = useCallback(() => {
|
|
287
296
|
if (typeof window === "undefined") return null;
|
|
288
297
|
return sessionStorage.getItem(SESSION_STORAGE_KEY);
|
|
@@ -297,26 +306,29 @@ function useChat(options) {
|
|
|
297
306
|
}
|
|
298
307
|
}
|
|
299
308
|
}, []);
|
|
300
|
-
const fetchFromComms = useCallback(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const response = await fetch(`${currentSession.api_url}${path}`, {
|
|
306
|
-
...fetchOptions,
|
|
307
|
-
headers: {
|
|
308
|
-
"Content-Type": "application/json",
|
|
309
|
-
Authorization: `Bearer ${currentSession.access_token}`,
|
|
310
|
-
...fetchOptions.headers
|
|
309
|
+
const fetchFromComms = useCallback(
|
|
310
|
+
async (path, fetchOptions = {}) => {
|
|
311
|
+
const currentSession = sessionRef.current;
|
|
312
|
+
if (!currentSession) {
|
|
313
|
+
throw new Error("Chat session not initialized");
|
|
311
314
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
315
|
+
const response = await fetch(`${currentSession.api_url}${path}`, {
|
|
316
|
+
...fetchOptions,
|
|
317
|
+
headers: {
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
Authorization: `Bearer ${currentSession.access_token}`,
|
|
320
|
+
...fetchOptions.headers
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
const error = await response.json().catch(() => ({}));
|
|
325
|
+
throw new Error(error.message || `HTTP ${response.status}`);
|
|
326
|
+
}
|
|
327
|
+
const data = await response.json();
|
|
328
|
+
return data.data || data;
|
|
329
|
+
},
|
|
330
|
+
[]
|
|
331
|
+
);
|
|
320
332
|
const clearTimers = useCallback(() => {
|
|
321
333
|
if (reconnectTimeout.current) {
|
|
322
334
|
clearTimeout(reconnectTimeout.current);
|
|
@@ -327,111 +339,141 @@ function useChat(options) {
|
|
|
327
339
|
pingInterval.current = null;
|
|
328
340
|
}
|
|
329
341
|
}, []);
|
|
330
|
-
const handleWebSocketMessage = useCallback(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
342
|
+
const handleWebSocketMessage = useCallback(
|
|
343
|
+
(data) => {
|
|
344
|
+
const currentActiveChannelId = activeChannelIdRef.current;
|
|
345
|
+
console.log("[AegisChat] WebSocket message received:", data.type, data);
|
|
346
|
+
switch (data.type) {
|
|
347
|
+
case "message.new": {
|
|
348
|
+
const newMessage = data.payload;
|
|
349
|
+
if (newMessage.channel_id === currentActiveChannelId) {
|
|
350
|
+
setMessages((prev) => {
|
|
351
|
+
const existingIndex = prev.findIndex(
|
|
352
|
+
(m) => m.tempId && m.content === newMessage.content && m.status === "sending"
|
|
353
|
+
);
|
|
354
|
+
if (existingIndex !== -1) {
|
|
355
|
+
const updated = [...prev];
|
|
356
|
+
updated[existingIndex] = { ...newMessage, status: "sent" };
|
|
357
|
+
return updated;
|
|
358
|
+
}
|
|
359
|
+
if (prev.some((m) => m.id === newMessage.id)) return prev;
|
|
360
|
+
return [...prev, { ...newMessage, status: "delivered" }];
|
|
361
|
+
});
|
|
362
|
+
onMessageRef.current?.(newMessage);
|
|
363
|
+
}
|
|
364
|
+
setChannels((prev) => {
|
|
365
|
+
const updated = prev.map(
|
|
366
|
+
(ch) => ch.id === newMessage.channel_id ? {
|
|
367
|
+
...ch,
|
|
368
|
+
last_message: {
|
|
369
|
+
id: newMessage.id,
|
|
370
|
+
content: newMessage.content,
|
|
371
|
+
created_at: newMessage.created_at,
|
|
372
|
+
sender: {
|
|
373
|
+
id: newMessage.sender_id,
|
|
374
|
+
display_name: "Unknown",
|
|
375
|
+
status: "online"
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
unread_count: ch.id === currentActiveChannelId ? 0 : ch.unread_count + 1
|
|
379
|
+
} : ch
|
|
340
380
|
);
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
return
|
|
345
|
-
}
|
|
346
|
-
if (prev.some((m) => m.id === newMessage.id)) return prev;
|
|
347
|
-
return [...prev, { ...newMessage, status: "delivered" }];
|
|
381
|
+
return updated.sort((a, b) => {
|
|
382
|
+
const timeA = a.last_message?.created_at || "";
|
|
383
|
+
const timeB = b.last_message?.created_at || "";
|
|
384
|
+
return timeB.localeCompare(timeA);
|
|
385
|
+
});
|
|
348
386
|
});
|
|
349
|
-
|
|
387
|
+
break;
|
|
350
388
|
}
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
(ch) => ch.id === newMessage.channel_id ? {
|
|
354
|
-
...ch,
|
|
355
|
-
last_message: {
|
|
356
|
-
id: newMessage.id,
|
|
357
|
-
content: newMessage.content,
|
|
358
|
-
created_at: newMessage.created_at,
|
|
359
|
-
sender: { id: newMessage.sender_id, display_name: "Unknown", status: "online" }
|
|
360
|
-
},
|
|
361
|
-
unread_count: ch.id === currentActiveChannelId ? 0 : ch.unread_count + 1
|
|
362
|
-
} : ch
|
|
363
|
-
);
|
|
364
|
-
return updated.sort((a, b) => {
|
|
365
|
-
const timeA = a.last_message?.created_at || "";
|
|
366
|
-
const timeB = b.last_message?.created_at || "";
|
|
367
|
-
return timeB.localeCompare(timeA);
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
case "message.updated": {
|
|
373
|
-
const updatedMessage = data.payload;
|
|
374
|
-
setMessages((prev) => prev.map((m) => m.id === updatedMessage.id ? updatedMessage : m));
|
|
375
|
-
break;
|
|
376
|
-
}
|
|
377
|
-
case "message.deleted": {
|
|
378
|
-
const { message_id } = data.payload;
|
|
379
|
-
setMessages((prev) => prev.map((m) => m.id === message_id ? { ...m, deleted: true } : m));
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
case "message.delivered":
|
|
383
|
-
case "message.read": {
|
|
384
|
-
const { message_id, channel_id, status } = data.payload;
|
|
385
|
-
if (channel_id === currentActiveChannelId) {
|
|
389
|
+
case "message.updated": {
|
|
390
|
+
const updatedMessage = data.payload;
|
|
386
391
|
setMessages(
|
|
387
|
-
(prev) => prev.map((m) => m.id ===
|
|
392
|
+
(prev) => prev.map((m) => m.id === updatedMessage.id ? updatedMessage : m)
|
|
388
393
|
);
|
|
394
|
+
break;
|
|
389
395
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
case "message.delivered.batch":
|
|
393
|
-
case "message.read.batch": {
|
|
394
|
-
const { channel_id } = data.payload;
|
|
395
|
-
if (channel_id === currentActiveChannelId) {
|
|
396
|
+
case "message.deleted": {
|
|
397
|
+
const { message_id } = data.payload;
|
|
396
398
|
setMessages(
|
|
397
|
-
(prev) => prev.map(
|
|
399
|
+
(prev) => prev.map(
|
|
400
|
+
(m) => m.id === message_id ? { ...m, deleted: true } : m
|
|
401
|
+
)
|
|
398
402
|
);
|
|
403
|
+
break;
|
|
399
404
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
405
|
+
case "message.delivered":
|
|
406
|
+
case "message.read": {
|
|
407
|
+
const { message_id, channel_id, status } = data.payload;
|
|
408
|
+
if (channel_id === currentActiveChannelId) {
|
|
409
|
+
setMessages(
|
|
410
|
+
(prev) => prev.map(
|
|
411
|
+
(m) => m.id === message_id ? {
|
|
412
|
+
...m,
|
|
413
|
+
status: status || "delivered"
|
|
414
|
+
} : m
|
|
415
|
+
)
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
case "message.delivered.batch":
|
|
421
|
+
case "message.read.batch": {
|
|
422
|
+
const { channel_id } = data.payload;
|
|
423
|
+
if (channel_id === currentActiveChannelId) {
|
|
424
|
+
setMessages(
|
|
425
|
+
(prev) => prev.map(
|
|
426
|
+
(m) => m.status === "sent" || m.status === "delivered" ? {
|
|
427
|
+
...m,
|
|
428
|
+
status: data.type === "message.delivered.batch" ? "delivered" : "read"
|
|
429
|
+
} : m
|
|
430
|
+
)
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case "typing.start": {
|
|
436
|
+
const { channel_id, user } = data.payload;
|
|
437
|
+
const typingUser = {
|
|
438
|
+
id: user.id,
|
|
439
|
+
displayName: user.display_name,
|
|
440
|
+
avatarUrl: user.avatar_url,
|
|
441
|
+
startedAt: Date.now()
|
|
442
|
+
};
|
|
443
|
+
setTypingUsers((prev) => ({
|
|
444
|
+
...prev,
|
|
445
|
+
[channel_id]: [
|
|
446
|
+
...(prev[channel_id] || []).filter((u) => u.id !== user.id),
|
|
447
|
+
typingUser
|
|
448
|
+
]
|
|
449
|
+
}));
|
|
450
|
+
onTypingRef.current?.(channel_id, typingUser);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case "typing.stop": {
|
|
454
|
+
const { channel_id, user_id } = data.payload;
|
|
455
|
+
setTypingUsers((prev) => ({
|
|
456
|
+
...prev,
|
|
457
|
+
[channel_id]: (prev[channel_id] || []).filter(
|
|
458
|
+
(u) => u.id !== user_id
|
|
459
|
+
)
|
|
460
|
+
}));
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case "pong":
|
|
464
|
+
break;
|
|
465
|
+
default:
|
|
466
|
+
console.log("[AegisChat] Unhandled message type:", data.type);
|
|
424
467
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
console.log("[AegisChat] Unhandled message type:", data.type);
|
|
429
|
-
}
|
|
430
|
-
}, [onMessage, onTyping]);
|
|
468
|
+
},
|
|
469
|
+
[]
|
|
470
|
+
);
|
|
431
471
|
const connectWebSocket = useCallback(() => {
|
|
432
472
|
const currentSession = sessionRef.current;
|
|
433
473
|
if (!currentSession?.websocket_url || !currentSession?.access_token) {
|
|
434
|
-
console.warn(
|
|
474
|
+
console.warn(
|
|
475
|
+
"[AegisChat] Cannot connect WebSocket - missing session or token"
|
|
476
|
+
);
|
|
435
477
|
return;
|
|
436
478
|
}
|
|
437
479
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
@@ -448,14 +490,19 @@ function useChat(options) {
|
|
|
448
490
|
setIsConnected(true);
|
|
449
491
|
setIsConnecting(false);
|
|
450
492
|
reconnectAttempts.current = 0;
|
|
451
|
-
|
|
493
|
+
onConnectionChangeRef.current?.(true);
|
|
452
494
|
pingInterval.current = setInterval(() => {
|
|
453
495
|
if (ws.readyState === WebSocket.OPEN) {
|
|
454
496
|
ws.send(JSON.stringify({ type: "ping" }));
|
|
455
497
|
}
|
|
456
498
|
}, PING_INTERVAL);
|
|
457
499
|
if (activeChannelIdRef.current) {
|
|
458
|
-
ws.send(
|
|
500
|
+
ws.send(
|
|
501
|
+
JSON.stringify({
|
|
502
|
+
type: "channel.join",
|
|
503
|
+
payload: { channel_id: activeChannelIdRef.current }
|
|
504
|
+
})
|
|
505
|
+
);
|
|
459
506
|
}
|
|
460
507
|
};
|
|
461
508
|
ws.onmessage = (event) => {
|
|
@@ -471,9 +518,12 @@ function useChat(options) {
|
|
|
471
518
|
setIsConnected(false);
|
|
472
519
|
setIsConnecting(false);
|
|
473
520
|
clearTimers();
|
|
474
|
-
|
|
521
|
+
onConnectionChangeRef.current?.(false);
|
|
475
522
|
if (!isManualDisconnect.current && reconnectAttempts.current < MAX_RECONNECT_ATTEMPTS) {
|
|
476
|
-
const delay = Math.min(
|
|
523
|
+
const delay = Math.min(
|
|
524
|
+
RECONNECT_INTERVAL * Math.pow(2, reconnectAttempts.current),
|
|
525
|
+
MAX_RECONNECT_DELAY
|
|
526
|
+
);
|
|
477
527
|
console.log(`[AegisChat] Reconnecting in ${delay}ms...`);
|
|
478
528
|
reconnectTimeout.current = setTimeout(() => {
|
|
479
529
|
reconnectAttempts.current++;
|
|
@@ -485,14 +535,18 @@ function useChat(options) {
|
|
|
485
535
|
console.error("[AegisChat] WebSocket error:", error);
|
|
486
536
|
};
|
|
487
537
|
wsRef.current = ws;
|
|
488
|
-
}, [clearTimers, handleWebSocketMessage
|
|
538
|
+
}, [clearTimers, handleWebSocketMessage]);
|
|
489
539
|
const connect = useCallback(async () => {
|
|
490
540
|
console.log("[AegisChat] connect() called");
|
|
491
|
-
const targetSession = sessionRef.current
|
|
541
|
+
const targetSession = sessionRef.current;
|
|
492
542
|
if (!targetSession) {
|
|
493
543
|
console.log("[AegisChat] No session available, skipping connect");
|
|
494
544
|
return;
|
|
495
545
|
}
|
|
546
|
+
if (!autoConnectRef.current) {
|
|
547
|
+
console.log("[AegisChat] autoConnect is false, skipping connect");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
496
550
|
connectWebSocket();
|
|
497
551
|
}, [connectWebSocket]);
|
|
498
552
|
const disconnect = useCallback(() => {
|
|
@@ -520,48 +574,72 @@ function useChat(options) {
|
|
|
520
574
|
setIsLoadingChannels(false);
|
|
521
575
|
}
|
|
522
576
|
}, []);
|
|
523
|
-
const selectChannel = useCallback(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (
|
|
531
|
-
|
|
577
|
+
const selectChannel = useCallback(
|
|
578
|
+
async (channelId) => {
|
|
579
|
+
const currentActiveChannelId = activeChannelIdRef.current;
|
|
580
|
+
setActiveChannelId(channelId);
|
|
581
|
+
setMessages([]);
|
|
582
|
+
setHasMoreMessages(true);
|
|
583
|
+
oldestMessageId.current = null;
|
|
584
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
585
|
+
if (currentActiveChannelId) {
|
|
586
|
+
wsRef.current.send(
|
|
587
|
+
JSON.stringify({
|
|
588
|
+
type: "channel.leave",
|
|
589
|
+
payload: { channel_id: currentActiveChannelId }
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
wsRef.current.send(
|
|
594
|
+
JSON.stringify({
|
|
595
|
+
type: "channel.join",
|
|
596
|
+
payload: { channel_id: channelId }
|
|
597
|
+
})
|
|
598
|
+
);
|
|
532
599
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
600
|
+
setIsLoadingMessages(true);
|
|
601
|
+
try {
|
|
602
|
+
const response = await fetchFromComms(
|
|
603
|
+
`/channels/${channelId}/messages?limit=50`
|
|
604
|
+
);
|
|
605
|
+
setMessages(response.messages || []);
|
|
606
|
+
setHasMoreMessages(response.has_more);
|
|
607
|
+
if (response.oldest_id) {
|
|
608
|
+
oldestMessageId.current = response.oldest_id;
|
|
609
|
+
}
|
|
610
|
+
await markAsRead(channelId);
|
|
611
|
+
setChannels(
|
|
612
|
+
(prev) => prev.map(
|
|
613
|
+
(ch) => ch.id === channelId ? { ...ch, unread_count: 0 } : ch
|
|
614
|
+
)
|
|
615
|
+
);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
console.error("[AegisChat] Failed to load messages:", error);
|
|
618
|
+
setMessages([]);
|
|
619
|
+
} finally {
|
|
620
|
+
setIsLoadingMessages(false);
|
|
542
621
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
console.error("[AegisChat] Failed to mark as read:", error);
|
|
557
|
-
}
|
|
558
|
-
}, [fetchFromComms]);
|
|
622
|
+
},
|
|
623
|
+
[setActiveChannelId, fetchFromComms]
|
|
624
|
+
);
|
|
625
|
+
const markAsRead = useCallback(
|
|
626
|
+
async (channelId) => {
|
|
627
|
+
try {
|
|
628
|
+
await fetchFromComms(`/channels/${channelId}/read`, { method: "POST" });
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.error("[AegisChat] Failed to mark as read:", error);
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
[fetchFromComms]
|
|
634
|
+
);
|
|
559
635
|
const loadMoreMessages = useCallback(async () => {
|
|
560
636
|
if (!activeChannelId || !hasMoreMessages || isLoadingMessages) return;
|
|
561
637
|
setIsLoadingMessages(true);
|
|
562
638
|
try {
|
|
563
639
|
const params = oldestMessageId.current ? `?before=${oldestMessageId.current}&limit=50` : "?limit=50";
|
|
564
|
-
const response = await fetchFromComms(
|
|
640
|
+
const response = await fetchFromComms(
|
|
641
|
+
`/channels/${activeChannelId}/messages${params}`
|
|
642
|
+
);
|
|
565
643
|
setMessages((prev) => [...response.messages || [], ...prev]);
|
|
566
644
|
setHasMoreMessages(response.has_more);
|
|
567
645
|
if (response.oldest_id) {
|
|
@@ -573,138 +651,234 @@ function useChat(options) {
|
|
|
573
651
|
setIsLoadingMessages(false);
|
|
574
652
|
}
|
|
575
653
|
}, [activeChannelId, hasMoreMessages, isLoadingMessages, fetchFromComms]);
|
|
576
|
-
const sendMessage = useCallback(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
});
|
|
619
|
-
} catch (error) {
|
|
620
|
-
console.error("[AegisChat] Failed to send message:", error);
|
|
621
|
-
setMessages(
|
|
622
|
-
(prev) => prev.map(
|
|
623
|
-
(m) => m.tempId === tempId ? { ...m, status: "failed", errorMessage: error instanceof Error ? error.message : "Failed to send" } : m
|
|
624
|
-
)
|
|
625
|
-
);
|
|
626
|
-
throw error;
|
|
627
|
-
}
|
|
628
|
-
}, [fetchFromComms]);
|
|
629
|
-
const uploadFile = useCallback(async (file) => {
|
|
630
|
-
const currentSession = sessionRef.current;
|
|
631
|
-
if (!currentSession) return null;
|
|
632
|
-
const fileId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
633
|
-
setUploadProgress((prev) => [...prev, { fileId, fileName: file.name, progress: 0, status: "pending" }]);
|
|
634
|
-
try {
|
|
635
|
-
setUploadProgress((prev) => prev.map((p) => p.fileId === fileId ? { ...p, status: "uploading", progress: 10 } : p));
|
|
636
|
-
const uploadUrlResponse = await fetchFromComms("/files/upload-url", {
|
|
637
|
-
method: "POST",
|
|
638
|
-
body: JSON.stringify({ file_name: file.name, file_type: file.type || "application/octet-stream", file_size: file.size })
|
|
639
|
-
});
|
|
640
|
-
setUploadProgress((prev) => prev.map((p) => p.fileId === fileId ? { ...p, fileId: uploadUrlResponse.file_id, progress: 30 } : p));
|
|
641
|
-
const uploadResponse = await fetch(uploadUrlResponse.upload_url, {
|
|
642
|
-
method: "PUT",
|
|
643
|
-
body: file,
|
|
644
|
-
headers: { "Content-Type": file.type || "application/octet-stream" }
|
|
645
|
-
});
|
|
646
|
-
if (!uploadResponse.ok) throw new Error(`Upload failed: ${uploadResponse.statusText}`);
|
|
647
|
-
setUploadProgress((prev) => prev.map((p) => p.fileId === uploadUrlResponse.file_id ? { ...p, status: "confirming", progress: 70 } : p));
|
|
648
|
-
const confirmResponse = await fetchFromComms("/files", {
|
|
649
|
-
method: "POST",
|
|
650
|
-
body: JSON.stringify({ file_id: uploadUrlResponse.file_id })
|
|
654
|
+
const sendMessage = useCallback(
|
|
655
|
+
async (content, msgOptions = {}) => {
|
|
656
|
+
const currentActiveChannelId = activeChannelIdRef.current;
|
|
657
|
+
const currentSession = sessionRef.current;
|
|
658
|
+
if (!currentActiveChannelId || !content.trim() || !currentSession) return;
|
|
659
|
+
const tempId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
660
|
+
const trimmedContent = content.trim();
|
|
661
|
+
const optimisticMessage = {
|
|
662
|
+
id: tempId,
|
|
663
|
+
tempId,
|
|
664
|
+
channel_id: currentActiveChannelId,
|
|
665
|
+
sender_id: currentSession.comms_user_id,
|
|
666
|
+
content: trimmedContent,
|
|
667
|
+
type: msgOptions.type || "text",
|
|
668
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
669
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
670
|
+
status: "sending",
|
|
671
|
+
metadata: msgOptions.metadata || {}
|
|
672
|
+
};
|
|
673
|
+
setMessages((prev) => [...prev, optimisticMessage]);
|
|
674
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
675
|
+
setChannels((prev) => {
|
|
676
|
+
const updated = prev.map(
|
|
677
|
+
(ch) => ch.id === currentActiveChannelId ? {
|
|
678
|
+
...ch,
|
|
679
|
+
last_message: {
|
|
680
|
+
id: tempId,
|
|
681
|
+
content: trimmedContent,
|
|
682
|
+
created_at: now,
|
|
683
|
+
sender: {
|
|
684
|
+
id: currentSession.comms_user_id,
|
|
685
|
+
display_name: "You",
|
|
686
|
+
status: "online"
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
} : ch
|
|
690
|
+
);
|
|
691
|
+
return updated.sort((a, b) => {
|
|
692
|
+
const timeA = a.last_message?.created_at || "";
|
|
693
|
+
const timeB = b.last_message?.created_at || "";
|
|
694
|
+
return timeB.localeCompare(timeA);
|
|
695
|
+
});
|
|
651
696
|
});
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
status: "sending",
|
|
678
|
-
metadata: { ...msgOptions.metadata, files: files.map((f) => ({ id: `temp-${f.name}`, filename: f.name, mime_type: f.type, size: f.size, url: "" })) }
|
|
679
|
-
};
|
|
680
|
-
setMessages((prev) => [...prev, optimisticMessage]);
|
|
681
|
-
try {
|
|
682
|
-
const uploadedFiles = [];
|
|
683
|
-
for (const file of files) {
|
|
684
|
-
const attachment = await uploadFile(file);
|
|
685
|
-
if (attachment) uploadedFiles.push(attachment);
|
|
697
|
+
try {
|
|
698
|
+
await fetchFromComms(
|
|
699
|
+
`/channels/${currentActiveChannelId}/messages`,
|
|
700
|
+
{
|
|
701
|
+
method: "POST",
|
|
702
|
+
body: JSON.stringify({
|
|
703
|
+
content: trimmedContent,
|
|
704
|
+
type: msgOptions.type || "text",
|
|
705
|
+
parent_id: msgOptions.parent_id,
|
|
706
|
+
metadata: msgOptions.metadata
|
|
707
|
+
})
|
|
708
|
+
}
|
|
709
|
+
);
|
|
710
|
+
} catch (error) {
|
|
711
|
+
console.error("[AegisChat] Failed to send message:", error);
|
|
712
|
+
setMessages(
|
|
713
|
+
(prev) => prev.map(
|
|
714
|
+
(m) => m.tempId === tempId ? {
|
|
715
|
+
...m,
|
|
716
|
+
status: "failed",
|
|
717
|
+
errorMessage: error instanceof Error ? error.message : "Failed to send"
|
|
718
|
+
} : m
|
|
719
|
+
)
|
|
720
|
+
);
|
|
721
|
+
throw error;
|
|
686
722
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
723
|
+
},
|
|
724
|
+
[fetchFromComms]
|
|
725
|
+
);
|
|
726
|
+
const uploadFile = useCallback(
|
|
727
|
+
async (file) => {
|
|
728
|
+
const currentSession = sessionRef.current;
|
|
729
|
+
if (!currentSession) return null;
|
|
730
|
+
const fileId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
731
|
+
setUploadProgress((prev) => [
|
|
732
|
+
...prev,
|
|
733
|
+
{ fileId, fileName: file.name, progress: 0, status: "pending" }
|
|
734
|
+
]);
|
|
735
|
+
try {
|
|
736
|
+
setUploadProgress(
|
|
737
|
+
(prev) => prev.map(
|
|
738
|
+
(p) => p.fileId === fileId ? { ...p, status: "uploading", progress: 10 } : p
|
|
739
|
+
)
|
|
740
|
+
);
|
|
741
|
+
const uploadUrlResponse = await fetchFromComms("/files/upload-url", {
|
|
742
|
+
method: "POST",
|
|
743
|
+
body: JSON.stringify({
|
|
744
|
+
file_name: file.name,
|
|
745
|
+
file_type: file.type || "application/octet-stream",
|
|
746
|
+
file_size: file.size
|
|
747
|
+
})
|
|
748
|
+
});
|
|
749
|
+
setUploadProgress(
|
|
750
|
+
(prev) => prev.map(
|
|
751
|
+
(p) => p.fileId === fileId ? { ...p, fileId: uploadUrlResponse.file_id, progress: 30 } : p
|
|
752
|
+
)
|
|
753
|
+
);
|
|
754
|
+
const uploadResponse = await fetch(uploadUrlResponse.upload_url, {
|
|
755
|
+
method: "PUT",
|
|
756
|
+
body: file,
|
|
757
|
+
headers: { "Content-Type": file.type || "application/octet-stream" }
|
|
758
|
+
});
|
|
759
|
+
if (!uploadResponse.ok)
|
|
760
|
+
throw new Error(`Upload failed: ${uploadResponse.statusText}`);
|
|
761
|
+
setUploadProgress(
|
|
762
|
+
(prev) => prev.map(
|
|
763
|
+
(p) => p.fileId === uploadUrlResponse.file_id ? { ...p, status: "confirming", progress: 70 } : p
|
|
764
|
+
)
|
|
765
|
+
);
|
|
766
|
+
const confirmResponse = await fetchFromComms(
|
|
767
|
+
"/files",
|
|
768
|
+
{
|
|
769
|
+
method: "POST",
|
|
770
|
+
body: JSON.stringify({ file_id: uploadUrlResponse.file_id })
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
setUploadProgress(
|
|
774
|
+
(prev) => prev.map(
|
|
775
|
+
(p) => p.fileId === uploadUrlResponse.file_id ? { ...p, status: "complete", progress: 100 } : p
|
|
776
|
+
)
|
|
777
|
+
);
|
|
778
|
+
setTimeout(
|
|
779
|
+
() => setUploadProgress(
|
|
780
|
+
(prev) => prev.filter((p) => p.fileId !== uploadUrlResponse.file_id)
|
|
781
|
+
),
|
|
782
|
+
2e3
|
|
783
|
+
);
|
|
784
|
+
return confirmResponse.file;
|
|
785
|
+
} catch (error) {
|
|
786
|
+
console.error("[AegisChat] Failed to upload file:", error);
|
|
787
|
+
setUploadProgress(
|
|
788
|
+
(prev) => prev.map(
|
|
789
|
+
(p) => p.fileId === fileId ? {
|
|
790
|
+
...p,
|
|
791
|
+
status: "error",
|
|
792
|
+
error: error instanceof Error ? error.message : "Upload failed"
|
|
793
|
+
} : p
|
|
794
|
+
)
|
|
795
|
+
);
|
|
796
|
+
setTimeout(
|
|
797
|
+
() => setUploadProgress(
|
|
798
|
+
(prev) => prev.filter((p) => p.fileId !== fileId)
|
|
799
|
+
),
|
|
800
|
+
5e3
|
|
801
|
+
);
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
[fetchFromComms]
|
|
806
|
+
);
|
|
807
|
+
const sendMessageWithFiles = useCallback(
|
|
808
|
+
async (content, files, msgOptions = {}) => {
|
|
809
|
+
const currentActiveChannelId = activeChannelIdRef.current;
|
|
810
|
+
const currentSession = sessionRef.current;
|
|
811
|
+
if (!currentActiveChannelId || !content.trim() && files.length === 0 || !currentSession)
|
|
812
|
+
return;
|
|
813
|
+
const tempId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
814
|
+
const trimmedContent = content.trim();
|
|
815
|
+
const optimisticMessage = {
|
|
816
|
+
id: tempId,
|
|
817
|
+
tempId,
|
|
818
|
+
channel_id: currentActiveChannelId,
|
|
819
|
+
sender_id: currentSession.comms_user_id,
|
|
820
|
+
content: trimmedContent || `Uploading ${files.length} file(s)...`,
|
|
821
|
+
type: "file",
|
|
822
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
823
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
824
|
+
status: "sending",
|
|
825
|
+
metadata: {
|
|
826
|
+
...msgOptions.metadata,
|
|
827
|
+
files: files.map((f) => ({
|
|
828
|
+
id: `temp-${f.name}`,
|
|
829
|
+
filename: f.name,
|
|
830
|
+
mime_type: f.type,
|
|
831
|
+
size: f.size,
|
|
832
|
+
url: ""
|
|
833
|
+
}))
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
setMessages((prev) => [...prev, optimisticMessage]);
|
|
837
|
+
try {
|
|
838
|
+
const uploadedFiles = [];
|
|
839
|
+
for (const file of files) {
|
|
840
|
+
const attachment = await uploadFile(file);
|
|
841
|
+
if (attachment) uploadedFiles.push(attachment);
|
|
842
|
+
}
|
|
843
|
+
const messageType = uploadedFiles.length > 0 && !trimmedContent ? "file" : "text";
|
|
844
|
+
await fetchFromComms(
|
|
845
|
+
`/channels/${currentActiveChannelId}/messages`,
|
|
846
|
+
{
|
|
847
|
+
method: "POST",
|
|
848
|
+
body: JSON.stringify({
|
|
849
|
+
content: trimmedContent || (uploadedFiles.length > 0 ? `Shared ${uploadedFiles.length} file(s)` : ""),
|
|
850
|
+
type: msgOptions.type || messageType,
|
|
851
|
+
parent_id: msgOptions.parent_id,
|
|
852
|
+
metadata: { ...msgOptions.metadata, files: uploadedFiles },
|
|
853
|
+
file_ids: uploadedFiles.map((f) => f.id)
|
|
854
|
+
})
|
|
855
|
+
}
|
|
856
|
+
);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
console.error("[AegisChat] Failed to send message with files:", error);
|
|
859
|
+
setMessages(
|
|
860
|
+
(prev) => prev.map(
|
|
861
|
+
(m) => m.tempId === tempId ? {
|
|
862
|
+
...m,
|
|
863
|
+
status: "failed",
|
|
864
|
+
errorMessage: error instanceof Error ? error.message : "Failed to send"
|
|
865
|
+
} : m
|
|
866
|
+
)
|
|
867
|
+
);
|
|
868
|
+
throw error;
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
[fetchFromComms, uploadFile]
|
|
872
|
+
);
|
|
704
873
|
const stopTyping = useCallback(() => {
|
|
705
874
|
const currentActiveChannelId = activeChannelIdRef.current;
|
|
706
875
|
if (!currentActiveChannelId || !wsRef.current) return;
|
|
707
|
-
wsRef.current.send(
|
|
876
|
+
wsRef.current.send(
|
|
877
|
+
JSON.stringify({
|
|
878
|
+
type: "typing.stop",
|
|
879
|
+
payload: { channel_id: currentActiveChannelId }
|
|
880
|
+
})
|
|
881
|
+
);
|
|
708
882
|
if (typingTimeout.current) {
|
|
709
883
|
clearTimeout(typingTimeout.current);
|
|
710
884
|
typingTimeout.current = null;
|
|
@@ -713,46 +887,106 @@ function useChat(options) {
|
|
|
713
887
|
const startTyping = useCallback(() => {
|
|
714
888
|
const currentActiveChannelId = activeChannelIdRef.current;
|
|
715
889
|
if (!currentActiveChannelId || !wsRef.current) return;
|
|
716
|
-
wsRef.current.send(
|
|
890
|
+
wsRef.current.send(
|
|
891
|
+
JSON.stringify({
|
|
892
|
+
type: "typing.start",
|
|
893
|
+
payload: { channel_id: currentActiveChannelId }
|
|
894
|
+
})
|
|
895
|
+
);
|
|
717
896
|
if (typingTimeout.current) clearTimeout(typingTimeout.current);
|
|
718
897
|
typingTimeout.current = setTimeout(stopTyping, TYPING_TIMEOUT);
|
|
719
898
|
}, [stopTyping]);
|
|
720
|
-
const createDMWithUser = useCallback(
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
899
|
+
const createDMWithUser = useCallback(
|
|
900
|
+
async (userId) => {
|
|
901
|
+
try {
|
|
902
|
+
const channel = await fetchFromComms("/channels/dm", {
|
|
903
|
+
method: "POST",
|
|
904
|
+
body: JSON.stringify({ user_id: userId })
|
|
905
|
+
});
|
|
906
|
+
await refreshChannels();
|
|
907
|
+
return channel.id;
|
|
908
|
+
} catch (error) {
|
|
909
|
+
console.error("[AegisChat] Failed to create DM:", error);
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
[fetchFromComms, refreshChannels]
|
|
914
|
+
);
|
|
915
|
+
const retryMessage = useCallback(
|
|
916
|
+
async (tempId) => {
|
|
917
|
+
const failedMessage = messages.find(
|
|
918
|
+
(m) => m.tempId === tempId && m.status === "failed"
|
|
919
|
+
);
|
|
920
|
+
const currentActiveChannelId = activeChannelIdRef.current;
|
|
921
|
+
if (!failedMessage || !currentActiveChannelId) return;
|
|
922
|
+
setMessages(
|
|
923
|
+
(prev) => prev.map(
|
|
924
|
+
(m) => m.tempId === tempId ? { ...m, status: "sending", errorMessage: void 0 } : m
|
|
925
|
+
)
|
|
926
|
+
);
|
|
927
|
+
try {
|
|
928
|
+
await fetchFromComms(
|
|
929
|
+
`/channels/${currentActiveChannelId}/messages`,
|
|
930
|
+
{
|
|
931
|
+
method: "POST",
|
|
932
|
+
body: JSON.stringify({
|
|
933
|
+
content: failedMessage.content,
|
|
934
|
+
type: failedMessage.type,
|
|
935
|
+
metadata: failedMessage.metadata
|
|
936
|
+
})
|
|
937
|
+
}
|
|
938
|
+
);
|
|
939
|
+
} catch (error) {
|
|
940
|
+
console.error("[AegisChat] Failed to retry message:", error);
|
|
941
|
+
setMessages(
|
|
942
|
+
(prev) => prev.map(
|
|
943
|
+
(m) => m.tempId === tempId ? {
|
|
944
|
+
...m,
|
|
945
|
+
status: "failed",
|
|
946
|
+
errorMessage: error instanceof Error ? error.message : "Failed to send"
|
|
947
|
+
} : m
|
|
948
|
+
)
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
[messages, fetchFromComms]
|
|
953
|
+
);
|
|
748
954
|
const deleteFailedMessage = useCallback((tempId) => {
|
|
749
955
|
setMessages((prev) => prev.filter((m) => m.tempId !== tempId));
|
|
750
956
|
}, []);
|
|
957
|
+
const setup = useCallback((options2) => {
|
|
958
|
+
const {
|
|
959
|
+
config: config2,
|
|
960
|
+
role: role2,
|
|
961
|
+
clientId: clientId2,
|
|
962
|
+
initialSession: initialSession2,
|
|
963
|
+
autoConnect: autoConnect2 = true,
|
|
964
|
+
onMessage: onMessage2,
|
|
965
|
+
onTyping: onTyping2,
|
|
966
|
+
onConnectionChange: onConnectionChange2
|
|
967
|
+
} = options2;
|
|
968
|
+
roleRef.current = role2;
|
|
969
|
+
clientIdRef.current = clientId2;
|
|
970
|
+
autoConnectRef.current = autoConnect2;
|
|
971
|
+
onMessageRef.current = onMessage2;
|
|
972
|
+
onTypingRef.current = onTyping2;
|
|
973
|
+
onConnectionChangeRef.current = onConnectionChange2;
|
|
974
|
+
if (initialSession2) {
|
|
975
|
+
sessionRef.current = initialSession2;
|
|
976
|
+
if (!config2) {
|
|
977
|
+
configureApiClient({
|
|
978
|
+
baseUrl: initialSession2.api_url,
|
|
979
|
+
getAccessToken: async () => sessionRef.current?.access_token || ""
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
setSession(initialSession2);
|
|
983
|
+
}
|
|
984
|
+
}, []);
|
|
751
985
|
useEffect(() => {
|
|
752
|
-
if (
|
|
986
|
+
if (session && !isConnected && !isConnecting && autoConnectRef.current) {
|
|
753
987
|
connectWebSocket();
|
|
754
988
|
}
|
|
755
|
-
}, [
|
|
989
|
+
}, [session, isConnected, isConnecting, connectWebSocket]);
|
|
756
990
|
useEffect(() => {
|
|
757
991
|
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
|
758
992
|
wsRef.current.onmessage = (event) => {
|
|
@@ -760,7 +994,10 @@ function useChat(options) {
|
|
|
760
994
|
const data = JSON.parse(event.data);
|
|
761
995
|
handleWebSocketMessage(data);
|
|
762
996
|
} catch (error) {
|
|
763
|
-
console.error(
|
|
997
|
+
console.error(
|
|
998
|
+
"[AegisChat] Failed to parse WebSocket message:",
|
|
999
|
+
error
|
|
1000
|
+
);
|
|
764
1001
|
}
|
|
765
1002
|
};
|
|
766
1003
|
}
|
|
@@ -812,7 +1049,8 @@ function useChat(options) {
|
|
|
812
1049
|
createDMWithUser,
|
|
813
1050
|
retryMessage,
|
|
814
1051
|
deleteFailedMessage,
|
|
815
|
-
markAsRead
|
|
1052
|
+
markAsRead,
|
|
1053
|
+
setup
|
|
816
1054
|
};
|
|
817
1055
|
}
|
|
818
1056
|
|