@thrillee/aegischat 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -75,7 +75,7 @@ var chatApi = {
75
75
  * Connect to chat session
76
76
  */
77
77
  async connect(params, signal) {
78
- return fetchWithAuth("/api/v1/chat/connect", {
78
+ return fetchWithAuth("/chat/connect", {
79
79
  method: "POST",
80
80
  body: JSON.stringify(params),
81
81
  signal
@@ -85,7 +85,7 @@ var chatApi = {
85
85
  * Refresh access token
86
86
  */
87
87
  async refreshToken(refreshToken, signal) {
88
- return fetchWithAuth("/api/v1/chat/refresh", {
88
+ return fetchWithAuth("/chat/refresh", {
89
89
  method: "POST",
90
90
  body: JSON.stringify({ refresh_token: refreshToken }),
91
91
  signal
@@ -101,19 +101,19 @@ var channelsApi = {
101
101
  if (options.type) params.append("type", options.type);
102
102
  if (options.limit) params.append("limit", String(options.limit));
103
103
  const query = params.toString() ? `?${params.toString()}` : "";
104
- return fetchWithAuth(`/api/v1/channels${query}`, { signal });
104
+ return fetchWithAuth(`/channels${query}`, { signal });
105
105
  },
106
106
  /**
107
107
  * Get channel by ID
108
108
  */
109
109
  async get(channelId, signal) {
110
- return fetchWithAuth(`/api/v1/channels/${channelId}`, { signal });
110
+ return fetchWithAuth(`/channels/${channelId}`, { signal });
111
111
  },
112
112
  /**
113
113
  * Get or create DM channel
114
114
  */
115
115
  async getOrCreateDM(userId, signal) {
116
- return fetchWithAuth("/api/v1/channels/dm", {
116
+ return fetchWithAuth("/channels/dm", {
117
117
  method: "POST",
118
118
  body: JSON.stringify({ user_id: userId }),
119
119
  signal
@@ -123,7 +123,7 @@ var channelsApi = {
123
123
  * Create channel
124
124
  */
125
125
  async create(data, signal) {
126
- return fetchWithAuth("/api/v1/channels", {
126
+ return fetchWithAuth("/channels", {
127
127
  method: "POST",
128
128
  body: JSON.stringify(data),
129
129
  signal
@@ -133,7 +133,7 @@ var channelsApi = {
133
133
  * Mark channel as read
134
134
  */
135
135
  async markAsRead(channelId, signal) {
136
- return fetchWithAuth(`/api/v1/channels/${channelId}/read`, {
136
+ return fetchWithAuth(`/channels/${channelId}/read`, {
137
137
  method: "POST",
138
138
  signal
139
139
  });
@@ -142,13 +142,13 @@ var channelsApi = {
142
142
  * Get channel members
143
143
  */
144
144
  async getMembers(channelId, signal) {
145
- return fetchWithAuth(`/api/v1/channels/${channelId}/members`, { signal });
145
+ return fetchWithAuth(`/channels/${channelId}/members`, { signal });
146
146
  },
147
147
  /**
148
148
  * Update channel
149
149
  */
150
150
  async update(channelId, data, signal) {
151
- return fetchWithAuth(`/api/v1/channels/${channelId}`, {
151
+ return fetchWithAuth(`/channels/${channelId}`, {
152
152
  method: "PATCH",
153
153
  body: JSON.stringify(data),
154
154
  signal
@@ -164,13 +164,13 @@ var messagesApi = {
164
164
  if (options.limit) params.append("limit", String(options.limit));
165
165
  if (options.before) params.append("before", options.before);
166
166
  const query = params.toString() ? `?${params.toString()}` : "";
167
- return fetchWithAuth(`/api/v1/channels/${channelId}/messages${query}`, { signal });
167
+ return fetchWithAuth(`/channels/${channelId}/messages${query}`, { signal });
168
168
  },
169
169
  /**
170
170
  * Send a message
171
171
  */
172
172
  async send(channelId, data, signal) {
173
- return fetchWithAuth(`/api/v1/channels/${channelId}/messages`, {
173
+ return fetchWithAuth(`/channels/${channelId}/messages`, {
174
174
  method: "POST",
175
175
  body: JSON.stringify(data),
176
176
  signal
@@ -180,7 +180,7 @@ var messagesApi = {
180
180
  * Update a message
181
181
  */
182
182
  async update(channelId, messageId, data, signal) {
183
- return fetchWithAuth(`/api/v1/channels/${channelId}/messages/${messageId}`, {
183
+ return fetchWithAuth(`/channels/${channelId}/messages/${messageId}`, {
184
184
  method: "PATCH",
185
185
  body: JSON.stringify(data),
186
186
  signal
@@ -190,7 +190,7 @@ var messagesApi = {
190
190
  * Delete a message
191
191
  */
192
192
  async delete(channelId, messageId, signal) {
193
- return fetchWithAuth(`/api/v1/channels/${channelId}/messages/${messageId}`, {
193
+ return fetchWithAuth(`/channels/${channelId}/messages/${messageId}`, {
194
194
  method: "DELETE",
195
195
  signal
196
196
  });
@@ -199,7 +199,7 @@ var messagesApi = {
199
199
  * Mark messages as delivered
200
200
  */
201
201
  async markDelivered(channelId, signal) {
202
- return fetchWithAuth(`/api/v1/channels/${channelId}/messages/delivered`, {
202
+ return fetchWithAuth(`/channels/${channelId}/messages/delivered`, {
203
203
  method: "POST",
204
204
  signal
205
205
  });
@@ -208,7 +208,7 @@ var messagesApi = {
208
208
  * Mark messages as read
209
209
  */
210
210
  async markRead(channelId, signal) {
211
- return fetchWithAuth(`/api/v1/channels/${channelId}/messages/read`, {
211
+ return fetchWithAuth(`/channels/${channelId}/messages/read`, {
212
212
  method: "POST",
213
213
  signal
214
214
  });
@@ -219,7 +219,7 @@ var reactionsApi = {
219
219
  * Add reaction to a message
220
220
  */
221
221
  async add(channelId, messageId, emoji, signal) {
222
- return fetchWithAuth(`/api/v1/channels/${channelId}/messages/${messageId}/reactions`, {
222
+ return fetchWithAuth(`/channels/${channelId}/messages/${messageId}/reactions`, {
223
223
  method: "POST",
224
224
  body: JSON.stringify({ emoji }),
225
225
  signal
@@ -230,7 +230,7 @@ var reactionsApi = {
230
230
  */
231
231
  async remove(channelId, messageId, emoji, signal) {
232
232
  return fetchWithAuth(
233
- `/api/v1/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`,
233
+ `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`,
234
234
  { method: "DELETE", signal }
235
235
  );
236
236
  }
@@ -240,7 +240,7 @@ var filesApi = {
240
240
  * Get upload URL
241
241
  */
242
242
  async getUploadUrl(data, signal) {
243
- return fetchWithAuth("/api/v1/files/upload-url", {
243
+ return fetchWithAuth("/files/upload-url", {
244
244
  method: "POST",
245
245
  body: JSON.stringify(data),
246
246
  signal
@@ -250,7 +250,7 @@ var filesApi = {
250
250
  * Confirm file upload
251
251
  */
252
252
  async confirm(fileId, signal) {
253
- return fetchWithAuth("/api/v1/files", {
253
+ return fetchWithAuth("/files", {
254
254
  method: "POST",
255
255
  body: JSON.stringify({ file_id: fileId }),
256
256
  signal
@@ -260,7 +260,7 @@ var filesApi = {
260
260
  * Get download URL
261
261
  */
262
262
  async getDownloadUrl(fileId, signal) {
263
- return fetchWithAuth(`/api/v1/files/${fileId}/download`, { signal });
263
+ return fetchWithAuth(`/files/${fileId}/download`, { signal });
264
264
  }
265
265
  };
266
266
  var usersApi = {
@@ -268,13 +268,13 @@ var usersApi = {
268
268
  * Search users
269
269
  */
270
270
  async search(query, signal) {
271
- return fetchWithAuth(`/api/v1/users/search?q=${encodeURIComponent(query)}`, { signal });
271
+ return fetchWithAuth(`/users/search?q=${encodeURIComponent(query)}`, { signal });
272
272
  },
273
273
  /**
274
274
  * Get user by ID
275
275
  */
276
276
  async get(userId, signal) {
277
- return fetchWithAuth(`/api/v1/users/${userId}`, { signal });
277
+ return fetchWithAuth(`/users/${userId}`, { signal });
278
278
  }
279
279
  };
280
280
 
@@ -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 { config, role, clientId, initialSession, autoConnect = true, onMessage, onTyping, onConnectionChange } = options;
290
- const [session, setSession] = (0, import_react.useState)(initialSession ?? null);
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)(null);
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,23 +319,19 @@ 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 configRef = (0, import_react.useRef)(config);
310
- const sessionRef = (0, import_react.useRef)(initialSession ?? null);
311
- if (initialSession && !config) {
312
- configureApiClient({
313
- baseUrl: initialSession.api_url,
314
- getAccessToken: async () => sessionRef.current?.access_token || ""
315
- });
316
- }
317
- (0, import_react.useEffect)(() => {
318
- configRef.current = config;
319
- }, [config]);
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);
320
329
  (0, import_react.useEffect)(() => {
321
330
  activeChannelIdRef.current = activeChannelId;
322
331
  }, [activeChannelId]);
323
332
  (0, import_react.useEffect)(() => {
324
- sessionRef.current = initialSession ?? null;
325
- }, [initialSession]);
333
+ activeChannelIdRef.current = activeChannelId;
334
+ }, [activeChannelId]);
326
335
  const getActiveChannelId = (0, import_react.useCallback)(() => {
327
336
  if (typeof window === "undefined") return null;
328
337
  return sessionStorage.getItem(SESSION_STORAGE_KEY);
@@ -337,26 +346,29 @@ function useChat(options) {
337
346
  }
338
347
  }
339
348
  }, []);
340
- const fetchFromComms = (0, import_react.useCallback)(async (path, fetchOptions = {}) => {
341
- const currentSession = sessionRef.current;
342
- if (!currentSession) {
343
- throw new Error("Chat session not initialized");
344
- }
345
- const response = await fetch(`${currentSession.api_url}${path}`, {
346
- ...fetchOptions,
347
- headers: {
348
- "Content-Type": "application/json",
349
- Authorization: `Bearer ${currentSession.access_token}`,
350
- ...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");
351
354
  }
352
- });
353
- if (!response.ok) {
354
- const error = await response.json().catch(() => ({}));
355
- throw new Error(error.message || `HTTP ${response.status}`);
356
- }
357
- const data = await response.json();
358
- return data.data || data;
359
- }, []);
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
+ );
360
372
  const clearTimers = (0, import_react.useCallback)(() => {
361
373
  if (reconnectTimeout.current) {
362
374
  clearTimeout(reconnectTimeout.current);
@@ -367,111 +379,141 @@ function useChat(options) {
367
379
  pingInterval.current = null;
368
380
  }
369
381
  }, []);
370
- const handleWebSocketMessage = (0, import_react.useCallback)((data) => {
371
- const currentActiveChannelId = activeChannelIdRef.current;
372
- console.log("[AegisChat] WebSocket message received:", data.type, data);
373
- switch (data.type) {
374
- case "message.new": {
375
- const newMessage = data.payload;
376
- if (newMessage.channel_id === currentActiveChannelId) {
377
- setMessages((prev) => {
378
- const existingIndex = prev.findIndex(
379
- (m) => m.tempId && m.content === newMessage.content && m.status === "sending"
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
380
420
  );
381
- if (existingIndex !== -1) {
382
- const updated = [...prev];
383
- updated[existingIndex] = { ...newMessage, status: "sent" };
384
- return updated;
385
- }
386
- if (prev.some((m) => m.id === newMessage.id)) return prev;
387
- 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
+ });
388
426
  });
389
- onMessage?.(newMessage);
427
+ break;
390
428
  }
391
- setChannels((prev) => {
392
- const updated = prev.map(
393
- (ch) => ch.id === newMessage.channel_id ? {
394
- ...ch,
395
- last_message: {
396
- id: newMessage.id,
397
- content: newMessage.content,
398
- created_at: newMessage.created_at,
399
- sender: { id: newMessage.sender_id, display_name: "Unknown", status: "online" }
400
- },
401
- unread_count: ch.id === currentActiveChannelId ? 0 : ch.unread_count + 1
402
- } : ch
403
- );
404
- return updated.sort((a, b) => {
405
- const timeA = a.last_message?.created_at || "";
406
- const timeB = b.last_message?.created_at || "";
407
- return timeB.localeCompare(timeA);
408
- });
409
- });
410
- break;
411
- }
412
- case "message.updated": {
413
- const updatedMessage = data.payload;
414
- setMessages((prev) => prev.map((m) => m.id === updatedMessage.id ? updatedMessage : m));
415
- break;
416
- }
417
- case "message.deleted": {
418
- const { message_id } = data.payload;
419
- setMessages((prev) => prev.map((m) => m.id === message_id ? { ...m, deleted: true } : m));
420
- break;
421
- }
422
- case "message.delivered":
423
- case "message.read": {
424
- const { message_id, channel_id, status } = data.payload;
425
- if (channel_id === currentActiveChannelId) {
429
+ case "message.updated": {
430
+ const updatedMessage = data.payload;
426
431
  setMessages(
427
- (prev) => prev.map((m) => m.id === message_id ? { ...m, status: status || "delivered" } : m)
432
+ (prev) => prev.map((m) => m.id === updatedMessage.id ? updatedMessage : m)
428
433
  );
434
+ break;
429
435
  }
430
- break;
431
- }
432
- case "message.delivered.batch":
433
- case "message.read.batch": {
434
- const { channel_id } = data.payload;
435
- if (channel_id === currentActiveChannelId) {
436
+ case "message.deleted": {
437
+ const { message_id } = data.payload;
436
438
  setMessages(
437
- (prev) => prev.map((m) => m.status === "sent" || m.status === "delivered" ? { ...m, status: data.type === "message.delivered.batch" ? "delivered" : "read" } : m)
439
+ (prev) => prev.map(
440
+ (m) => m.id === message_id ? { ...m, deleted: true } : m
441
+ )
438
442
  );
443
+ break;
439
444
  }
440
- break;
441
- }
442
- case "typing.start": {
443
- const { channel_id, user } = data.payload;
444
- const typingUser = {
445
- id: user.id,
446
- displayName: user.display_name,
447
- avatarUrl: user.avatar_url,
448
- startedAt: Date.now()
449
- };
450
- setTypingUsers((prev) => ({
451
- ...prev,
452
- [channel_id]: [...(prev[channel_id] || []).filter((u) => u.id !== user.id), typingUser]
453
- }));
454
- onTyping?.(channel_id, typingUser);
455
- break;
456
- }
457
- case "typing.stop": {
458
- const { channel_id, user_id } = data.payload;
459
- setTypingUsers((prev) => ({
460
- ...prev,
461
- [channel_id]: (prev[channel_id] || []).filter((u) => u.id !== user_id)
462
- }));
463
- break;
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);
464
507
  }
465
- case "pong":
466
- break;
467
- default:
468
- console.log("[AegisChat] Unhandled message type:", data.type);
469
- }
470
- }, [onMessage, onTyping]);
508
+ },
509
+ []
510
+ );
471
511
  const connectWebSocket = (0, import_react.useCallback)(() => {
472
512
  const currentSession = sessionRef.current;
473
513
  if (!currentSession?.websocket_url || !currentSession?.access_token) {
474
- console.warn("[AegisChat] Cannot connect WebSocket - missing session or token");
514
+ console.warn(
515
+ "[AegisChat] Cannot connect WebSocket - missing session or token"
516
+ );
475
517
  return;
476
518
  }
477
519
  if (wsRef.current?.readyState === WebSocket.OPEN) {
@@ -488,14 +530,19 @@ function useChat(options) {
488
530
  setIsConnected(true);
489
531
  setIsConnecting(false);
490
532
  reconnectAttempts.current = 0;
491
- onConnectionChange?.(true);
533
+ onConnectionChangeRef.current?.(true);
492
534
  pingInterval.current = setInterval(() => {
493
535
  if (ws.readyState === WebSocket.OPEN) {
494
536
  ws.send(JSON.stringify({ type: "ping" }));
495
537
  }
496
538
  }, PING_INTERVAL);
497
539
  if (activeChannelIdRef.current) {
498
- ws.send(JSON.stringify({ type: "channel.join", payload: { channel_id: activeChannelIdRef.current } }));
540
+ ws.send(
541
+ JSON.stringify({
542
+ type: "channel.join",
543
+ payload: { channel_id: activeChannelIdRef.current }
544
+ })
545
+ );
499
546
  }
500
547
  };
501
548
  ws.onmessage = (event) => {
@@ -511,9 +558,12 @@ function useChat(options) {
511
558
  setIsConnected(false);
512
559
  setIsConnecting(false);
513
560
  clearTimers();
514
- onConnectionChange?.(false);
561
+ onConnectionChangeRef.current?.(false);
515
562
  if (!isManualDisconnect.current && reconnectAttempts.current < MAX_RECONNECT_ATTEMPTS) {
516
- const delay = Math.min(RECONNECT_INTERVAL * Math.pow(2, reconnectAttempts.current), MAX_RECONNECT_DELAY);
563
+ const delay = Math.min(
564
+ RECONNECT_INTERVAL * Math.pow(2, reconnectAttempts.current),
565
+ MAX_RECONNECT_DELAY
566
+ );
517
567
  console.log(`[AegisChat] Reconnecting in ${delay}ms...`);
518
568
  reconnectTimeout.current = setTimeout(() => {
519
569
  reconnectAttempts.current++;
@@ -525,14 +575,18 @@ function useChat(options) {
525
575
  console.error("[AegisChat] WebSocket error:", error);
526
576
  };
527
577
  wsRef.current = ws;
528
- }, [clearTimers, handleWebSocketMessage, onConnectionChange]);
578
+ }, [clearTimers, handleWebSocketMessage]);
529
579
  const connect = (0, import_react.useCallback)(async () => {
530
580
  console.log("[AegisChat] connect() called");
531
- const targetSession = sessionRef.current ?? initialSession;
581
+ const targetSession = sessionRef.current;
532
582
  if (!targetSession) {
533
583
  console.log("[AegisChat] No session available, skipping connect");
534
584
  return;
535
585
  }
586
+ if (!autoConnectRef.current) {
587
+ console.log("[AegisChat] autoConnect is false, skipping connect");
588
+ return;
589
+ }
536
590
  connectWebSocket();
537
591
  }, [connectWebSocket]);
538
592
  const disconnect = (0, import_react.useCallback)(() => {
@@ -560,48 +614,72 @@ function useChat(options) {
560
614
  setIsLoadingChannels(false);
561
615
  }
562
616
  }, []);
563
- const selectChannel = (0, import_react.useCallback)(async (channelId) => {
564
- const currentActiveChannelId = activeChannelIdRef.current;
565
- setActiveChannelId(channelId);
566
- setMessages([]);
567
- setHasMoreMessages(true);
568
- oldestMessageId.current = null;
569
- if (wsRef.current?.readyState === WebSocket.OPEN) {
570
- if (currentActiveChannelId) {
571
- wsRef.current.send(JSON.stringify({ type: "channel.leave", payload: { channel_id: currentActiveChannelId } }));
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
+ );
572
639
  }
573
- wsRef.current.send(JSON.stringify({ type: "channel.join", payload: { channel_id: channelId } }));
574
- }
575
- setIsLoadingMessages(true);
576
- try {
577
- const response = await fetchFromComms(`/channels/${channelId}/messages?limit=50`);
578
- setMessages(response.messages || []);
579
- setHasMoreMessages(response.has_more);
580
- if (response.oldest_id) {
581
- oldestMessageId.current = response.oldest_id;
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);
582
661
  }
583
- await markAsRead(channelId);
584
- setChannels((prev) => prev.map((ch) => ch.id === channelId ? { ...ch, unread_count: 0 } : ch));
585
- } catch (error) {
586
- console.error("[AegisChat] Failed to load messages:", error);
587
- setMessages([]);
588
- } finally {
589
- setIsLoadingMessages(false);
590
- }
591
- }, [setActiveChannelId, fetchFromComms]);
592
- const markAsRead = (0, import_react.useCallback)(async (channelId) => {
593
- try {
594
- await fetchFromComms(`/channels/${channelId}/read`, { method: "POST" });
595
- } catch (error) {
596
- console.error("[AegisChat] Failed to mark as read:", error);
597
- }
598
- }, [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
+ );
599
675
  const loadMoreMessages = (0, import_react.useCallback)(async () => {
600
676
  if (!activeChannelId || !hasMoreMessages || isLoadingMessages) return;
601
677
  setIsLoadingMessages(true);
602
678
  try {
603
679
  const params = oldestMessageId.current ? `?before=${oldestMessageId.current}&limit=50` : "?limit=50";
604
- const response = await fetchFromComms(`/channels/${activeChannelId}/messages${params}`);
680
+ const response = await fetchFromComms(
681
+ `/channels/${activeChannelId}/messages${params}`
682
+ );
605
683
  setMessages((prev) => [...response.messages || [], ...prev]);
606
684
  setHasMoreMessages(response.has_more);
607
685
  if (response.oldest_id) {
@@ -613,138 +691,234 @@ function useChat(options) {
613
691
  setIsLoadingMessages(false);
614
692
  }
615
693
  }, [activeChannelId, hasMoreMessages, isLoadingMessages, fetchFromComms]);
616
- const sendMessage = (0, import_react.useCallback)(async (content, msgOptions = {}) => {
617
- const currentActiveChannelId = activeChannelIdRef.current;
618
- const currentSession = sessionRef.current;
619
- if (!currentActiveChannelId || !content.trim() || !currentSession) return;
620
- const tempId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
621
- const trimmedContent = content.trim();
622
- const optimisticMessage = {
623
- id: tempId,
624
- tempId,
625
- channel_id: currentActiveChannelId,
626
- sender_id: currentSession.comms_user_id,
627
- content: trimmedContent,
628
- type: msgOptions.type || "text",
629
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
630
- updated_at: (/* @__PURE__ */ new Date()).toISOString(),
631
- status: "sending",
632
- metadata: msgOptions.metadata || {}
633
- };
634
- setMessages((prev) => [...prev, optimisticMessage]);
635
- const now = (/* @__PURE__ */ new Date()).toISOString();
636
- setChannels((prev) => {
637
- const updated = prev.map(
638
- (ch) => ch.id === currentActiveChannelId ? {
639
- ...ch,
640
- last_message: {
641
- id: tempId,
642
- content: trimmedContent,
643
- created_at: now,
644
- sender: { id: currentSession.comms_user_id, display_name: "You", status: "online" }
645
- }
646
- } : ch
647
- );
648
- return updated.sort((a, b) => {
649
- const timeA = a.last_message?.created_at || "";
650
- const timeB = b.last_message?.created_at || "";
651
- return timeB.localeCompare(timeA);
652
- });
653
- });
654
- try {
655
- await fetchFromComms(`/channels/${currentActiveChannelId}/messages`, {
656
- method: "POST",
657
- body: JSON.stringify({ content: trimmedContent, type: msgOptions.type || "text", parent_id: msgOptions.parent_id, metadata: msgOptions.metadata })
658
- });
659
- } catch (error) {
660
- console.error("[AegisChat] Failed to send message:", error);
661
- setMessages(
662
- (prev) => prev.map(
663
- (m) => m.tempId === tempId ? { ...m, status: "failed", errorMessage: error instanceof Error ? error.message : "Failed to send" } : m
664
- )
665
- );
666
- throw error;
667
- }
668
- }, [fetchFromComms]);
669
- const uploadFile = (0, import_react.useCallback)(async (file) => {
670
- const currentSession = sessionRef.current;
671
- if (!currentSession) return null;
672
- const fileId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
673
- setUploadProgress((prev) => [...prev, { fileId, fileName: file.name, progress: 0, status: "pending" }]);
674
- try {
675
- setUploadProgress((prev) => prev.map((p) => p.fileId === fileId ? { ...p, status: "uploading", progress: 10 } : p));
676
- const uploadUrlResponse = await fetchFromComms("/files/upload-url", {
677
- method: "POST",
678
- body: JSON.stringify({ file_name: file.name, file_type: file.type || "application/octet-stream", file_size: file.size })
679
- });
680
- setUploadProgress((prev) => prev.map((p) => p.fileId === fileId ? { ...p, fileId: uploadUrlResponse.file_id, progress: 30 } : p));
681
- const uploadResponse = await fetch(uploadUrlResponse.upload_url, {
682
- method: "PUT",
683
- body: file,
684
- headers: { "Content-Type": file.type || "application/octet-stream" }
685
- });
686
- if (!uploadResponse.ok) throw new Error(`Upload failed: ${uploadResponse.statusText}`);
687
- setUploadProgress((prev) => prev.map((p) => p.fileId === uploadUrlResponse.file_id ? { ...p, status: "confirming", progress: 70 } : p));
688
- const confirmResponse = await fetchFromComms("/files", {
689
- method: "POST",
690
- 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
+ });
691
736
  });
692
- setUploadProgress((prev) => prev.map((p) => p.fileId === uploadUrlResponse.file_id ? { ...p, status: "complete", progress: 100 } : p));
693
- setTimeout(() => setUploadProgress((prev) => prev.filter((p) => p.fileId !== uploadUrlResponse.file_id)), 2e3);
694
- return confirmResponse.file;
695
- } catch (error) {
696
- console.error("[AegisChat] Failed to upload file:", error);
697
- setUploadProgress((prev) => prev.map((p) => p.fileId === fileId ? { ...p, status: "error", error: error instanceof Error ? error.message : "Upload failed" } : p));
698
- setTimeout(() => setUploadProgress((prev) => prev.filter((p) => p.fileId !== fileId)), 5e3);
699
- return null;
700
- }
701
- }, [fetchFromComms]);
702
- const sendMessageWithFiles = (0, import_react.useCallback)(async (content, files, msgOptions = {}) => {
703
- const currentActiveChannelId = activeChannelIdRef.current;
704
- const currentSession = sessionRef.current;
705
- if (!currentActiveChannelId || !content.trim() && files.length === 0 || !currentSession) return;
706
- const tempId = `temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
707
- const trimmedContent = content.trim();
708
- const optimisticMessage = {
709
- id: tempId,
710
- tempId,
711
- channel_id: currentActiveChannelId,
712
- sender_id: currentSession.comms_user_id,
713
- content: trimmedContent || `Uploading ${files.length} file(s)...`,
714
- type: "file",
715
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
716
- updated_at: (/* @__PURE__ */ new Date()).toISOString(),
717
- status: "sending",
718
- metadata: { ...msgOptions.metadata, files: files.map((f) => ({ id: `temp-${f.name}`, filename: f.name, mime_type: f.type, size: f.size, url: "" })) }
719
- };
720
- setMessages((prev) => [...prev, optimisticMessage]);
721
- try {
722
- const uploadedFiles = [];
723
- for (const file of files) {
724
- const attachment = await uploadFile(file);
725
- 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;
726
762
  }
727
- const messageType = uploadedFiles.length > 0 && !trimmedContent ? "file" : "text";
728
- await fetchFromComms(`/channels/${currentActiveChannelId}/messages`, {
729
- method: "POST",
730
- body: JSON.stringify({
731
- content: trimmedContent || (uploadedFiles.length > 0 ? `Shared ${uploadedFiles.length} file(s)` : ""),
732
- type: msgOptions.type || messageType,
733
- parent_id: msgOptions.parent_id,
734
- metadata: { ...msgOptions.metadata, files: uploadedFiles },
735
- file_ids: uploadedFiles.map((f) => f.id)
736
- })
737
- });
738
- } catch (error) {
739
- console.error("[AegisChat] Failed to send message with files:", error);
740
- setMessages((prev) => prev.map((m) => m.tempId === tempId ? { ...m, status: "failed", errorMessage: error instanceof Error ? error.message : "Failed to send" } : m));
741
- throw error;
742
- }
743
- }, [fetchFromComms, uploadFile]);
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
+ );
744
913
  const stopTyping = (0, import_react.useCallback)(() => {
745
914
  const currentActiveChannelId = activeChannelIdRef.current;
746
915
  if (!currentActiveChannelId || !wsRef.current) return;
747
- wsRef.current.send(JSON.stringify({ type: "typing.stop", payload: { channel_id: currentActiveChannelId } }));
916
+ wsRef.current.send(
917
+ JSON.stringify({
918
+ type: "typing.stop",
919
+ payload: { channel_id: currentActiveChannelId }
920
+ })
921
+ );
748
922
  if (typingTimeout.current) {
749
923
  clearTimeout(typingTimeout.current);
750
924
  typingTimeout.current = null;
@@ -753,46 +927,106 @@ function useChat(options) {
753
927
  const startTyping = (0, import_react.useCallback)(() => {
754
928
  const currentActiveChannelId = activeChannelIdRef.current;
755
929
  if (!currentActiveChannelId || !wsRef.current) return;
756
- wsRef.current.send(JSON.stringify({ type: "typing.start", payload: { channel_id: currentActiveChannelId } }));
930
+ wsRef.current.send(
931
+ JSON.stringify({
932
+ type: "typing.start",
933
+ payload: { channel_id: currentActiveChannelId }
934
+ })
935
+ );
757
936
  if (typingTimeout.current) clearTimeout(typingTimeout.current);
758
937
  typingTimeout.current = setTimeout(stopTyping, TYPING_TIMEOUT);
759
938
  }, [stopTyping]);
760
- const createDMWithUser = (0, import_react.useCallback)(async (userId) => {
761
- try {
762
- const channel = await fetchFromComms("/channels/dm", {
763
- method: "POST",
764
- body: JSON.stringify({ user_id: userId })
765
- });
766
- await refreshChannels();
767
- return channel.id;
768
- } catch (error) {
769
- console.error("[AegisChat] Failed to create DM:", error);
770
- return null;
771
- }
772
- }, [fetchFromComms, refreshChannels]);
773
- const retryMessage = (0, import_react.useCallback)(async (tempId) => {
774
- const failedMessage = messages.find((m) => m.tempId === tempId && m.status === "failed");
775
- const currentActiveChannelId = activeChannelIdRef.current;
776
- if (!failedMessage || !currentActiveChannelId) return;
777
- setMessages((prev) => prev.map((m) => m.tempId === tempId ? { ...m, status: "sending", errorMessage: void 0 } : m));
778
- try {
779
- await fetchFromComms(`/channels/${currentActiveChannelId}/messages`, {
780
- method: "POST",
781
- body: JSON.stringify({ content: failedMessage.content, type: failedMessage.type, metadata: failedMessage.metadata })
782
- });
783
- } catch (error) {
784
- console.error("[AegisChat] Failed to retry message:", error);
785
- setMessages((prev) => prev.map((m) => m.tempId === tempId ? { ...m, status: "failed", errorMessage: error instanceof Error ? error.message : "Failed to send" } : m));
786
- }
787
- }, [messages, fetchFromComms]);
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
+ );
788
994
  const deleteFailedMessage = (0, import_react.useCallback)((tempId) => {
789
995
  setMessages((prev) => prev.filter((m) => m.tempId !== tempId));
790
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.replace(/\/api\/v1\/?$/, ""),
1019
+ getAccessToken: async () => sessionRef.current?.access_token || ""
1020
+ });
1021
+ }
1022
+ setSession(initialSession2);
1023
+ }
1024
+ }, []);
791
1025
  (0, import_react.useEffect)(() => {
792
- if (initialSession && !isConnected && !isConnecting && autoConnect) {
1026
+ if (session && !isConnected && !isConnecting && autoConnectRef.current) {
793
1027
  connectWebSocket();
794
1028
  }
795
- }, [initialSession, session, isConnected, isConnecting, autoConnect, connectWebSocket]);
1029
+ }, [session, isConnected, isConnecting, connectWebSocket]);
796
1030
  (0, import_react.useEffect)(() => {
797
1031
  if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
798
1032
  wsRef.current.onmessage = (event) => {
@@ -800,7 +1034,10 @@ function useChat(options) {
800
1034
  const data = JSON.parse(event.data);
801
1035
  handleWebSocketMessage(data);
802
1036
  } catch (error) {
803
- console.error("[AegisChat] Failed to parse WebSocket message:", error);
1037
+ console.error(
1038
+ "[AegisChat] Failed to parse WebSocket message:",
1039
+ error
1040
+ );
804
1041
  }
805
1042
  };
806
1043
  }
@@ -852,7 +1089,8 @@ function useChat(options) {
852
1089
  createDMWithUser,
853
1090
  retryMessage,
854
1091
  deleteFailedMessage,
855
- markAsRead
1092
+ markAsRead,
1093
+ setup
856
1094
  };
857
1095
  }
858
1096