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