@anakin824/prdg-chat-ui 0.3.1 → 0.3.3
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 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +204 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +204 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -482,6 +482,8 @@ type Props = {
|
|
|
482
482
|
};
|
|
483
483
|
declare function TypingIndicator({ names }: Props): react.JSX.Element | null;
|
|
484
484
|
|
|
485
|
+
/** UUIDs are case-insensitive; normalize so NATS + UI always share one cache entry. */
|
|
486
|
+
declare function normalizeConversationCacheId(conversationId: string | null): string | null;
|
|
485
487
|
/** Stable React Query keys for chat — use with invalidateQueries / host integrations. */
|
|
486
488
|
declare const chatKeys: {
|
|
487
489
|
all: readonly ["chat"];
|
|
@@ -734,4 +736,4 @@ declare function useVoiceRecorder(): {
|
|
|
734
736
|
} | null>;
|
|
735
737
|
};
|
|
736
738
|
|
|
737
|
-
export { type AppUser, ChatAPI, type ChatAppearance, MessageInput as ChatComposer, type ChatConfig, type ChatContextValue, ChatConversationView, type ChatConversationViewProps, ChatInboxSidebar, type ChatInboxSidebarProps, ChatMainColumn, type ChatMainColumnProps, ChatPanel, type ChatPanelProps, ChatProvider, type ChatProviderProps, type ChatTheme, ChatWidget, type ChatWidgetProps, type Conversation, ConversationList, type EntityConversationContext as EntityContext, INITIAL_MESSAGE_PAGE_SIZE, type Message, type MessageAttachment, MessageBubble, MessageInput, type MessagePage, MessageThread, NewChatModal, type SidebarTab, StandaloneChatPage, type StandaloneChatPageProps, TypingIndicator, type UserIdentityMode, type WSServerEvent, chatKeys, convDisplayName, convLabel, formatConvTime, initials, mergeMessagePages, useChat, useChatActions, useChatPanelController, useConversationMembers, useConversations, useMessages, usePresignedUrl, useUpload, useVoiceRecorder };
|
|
739
|
+
export { type AppUser, ChatAPI, type ChatAppearance, MessageInput as ChatComposer, type ChatConfig, type ChatContextValue, ChatConversationView, type ChatConversationViewProps, ChatInboxSidebar, type ChatInboxSidebarProps, ChatMainColumn, type ChatMainColumnProps, ChatPanel, type ChatPanelProps, ChatProvider, type ChatProviderProps, type ChatTheme, ChatWidget, type ChatWidgetProps, type Conversation, ConversationList, type EntityConversationContext as EntityContext, INITIAL_MESSAGE_PAGE_SIZE, type Message, type MessageAttachment, MessageBubble, MessageInput, type MessagePage, MessageThread, NewChatModal, type SidebarTab, StandaloneChatPage, type StandaloneChatPageProps, TypingIndicator, type UserIdentityMode, type WSServerEvent, chatKeys, convDisplayName, convLabel, formatConvTime, initials, mergeMessagePages, normalizeConversationCacheId, useChat, useChatActions, useChatPanelController, useConversationMembers, useConversations, useMessages, usePresignedUrl, useUpload, useVoiceRecorder };
|
package/dist/index.d.ts
CHANGED
|
@@ -482,6 +482,8 @@ type Props = {
|
|
|
482
482
|
};
|
|
483
483
|
declare function TypingIndicator({ names }: Props): react.JSX.Element | null;
|
|
484
484
|
|
|
485
|
+
/** UUIDs are case-insensitive; normalize so NATS + UI always share one cache entry. */
|
|
486
|
+
declare function normalizeConversationCacheId(conversationId: string | null): string | null;
|
|
485
487
|
/** Stable React Query keys for chat — use with invalidateQueries / host integrations. */
|
|
486
488
|
declare const chatKeys: {
|
|
487
489
|
all: readonly ["chat"];
|
|
@@ -734,4 +736,4 @@ declare function useVoiceRecorder(): {
|
|
|
734
736
|
} | null>;
|
|
735
737
|
};
|
|
736
738
|
|
|
737
|
-
export { type AppUser, ChatAPI, type ChatAppearance, MessageInput as ChatComposer, type ChatConfig, type ChatContextValue, ChatConversationView, type ChatConversationViewProps, ChatInboxSidebar, type ChatInboxSidebarProps, ChatMainColumn, type ChatMainColumnProps, ChatPanel, type ChatPanelProps, ChatProvider, type ChatProviderProps, type ChatTheme, ChatWidget, type ChatWidgetProps, type Conversation, ConversationList, type EntityConversationContext as EntityContext, INITIAL_MESSAGE_PAGE_SIZE, type Message, type MessageAttachment, MessageBubble, MessageInput, type MessagePage, MessageThread, NewChatModal, type SidebarTab, StandaloneChatPage, type StandaloneChatPageProps, TypingIndicator, type UserIdentityMode, type WSServerEvent, chatKeys, convDisplayName, convLabel, formatConvTime, initials, mergeMessagePages, useChat, useChatActions, useChatPanelController, useConversationMembers, useConversations, useMessages, usePresignedUrl, useUpload, useVoiceRecorder };
|
|
739
|
+
export { type AppUser, ChatAPI, type ChatAppearance, MessageInput as ChatComposer, type ChatConfig, type ChatContextValue, ChatConversationView, type ChatConversationViewProps, ChatInboxSidebar, type ChatInboxSidebarProps, ChatMainColumn, type ChatMainColumnProps, ChatPanel, type ChatPanelProps, ChatProvider, type ChatProviderProps, type ChatTheme, ChatWidget, type ChatWidgetProps, type Conversation, ConversationList, type EntityConversationContext as EntityContext, INITIAL_MESSAGE_PAGE_SIZE, type Message, type MessageAttachment, MessageBubble, MessageInput, type MessagePage, MessageThread, NewChatModal, type SidebarTab, StandaloneChatPage, type StandaloneChatPageProps, TypingIndicator, type UserIdentityMode, type WSServerEvent, chatKeys, convDisplayName, convLabel, formatConvTime, initials, mergeMessagePages, normalizeConversationCacheId, useChat, useChatActions, useChatPanelController, useConversationMembers, useConversations, useMessages, usePresignedUrl, useUpload, useVoiceRecorder };
|
package/dist/index.js
CHANGED
|
@@ -185,10 +185,15 @@ function chatDebugWarn(debug, ...args) {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
// src/chat/lib/queryKeys.ts
|
|
188
|
+
function normalizeConversationCacheId(conversationId) {
|
|
189
|
+
if (conversationId == null) return null;
|
|
190
|
+
const t = conversationId.trim();
|
|
191
|
+
return t ? t.toLowerCase() : null;
|
|
192
|
+
}
|
|
188
193
|
var chatKeys = {
|
|
189
194
|
all: ["chat"],
|
|
190
195
|
conversations: () => [...chatKeys.all, "conversations"],
|
|
191
|
-
messages: (conversationId) => [...chatKeys.all, "messages", conversationId],
|
|
196
|
+
messages: (conversationId) => [...chatKeys.all, "messages", normalizeConversationCacheId(conversationId)],
|
|
192
197
|
members: (conversationId) => [...chatKeys.all, "members", conversationId],
|
|
193
198
|
contacts: (q) => [...chatKeys.all, "contacts", q]
|
|
194
199
|
};
|
|
@@ -227,74 +232,149 @@ function userInboxSubject(natsTenantId, userId) {
|
|
|
227
232
|
function entityConversationSubject(natsTenantId, conversationId) {
|
|
228
233
|
return `chat.${natsTenantId}.conversation.${conversationId}`;
|
|
229
234
|
}
|
|
235
|
+
function parseMessage(raw) {
|
|
236
|
+
if (typeof raw.id !== "string" || typeof raw.conversation_id !== "string") {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
id: raw.id,
|
|
241
|
+
conversation_id: raw.conversation_id,
|
|
242
|
+
sender_id: typeof raw.sender_id === "string" ? raw.sender_id : "",
|
|
243
|
+
body: typeof raw.body === "string" ? raw.body : null,
|
|
244
|
+
created_at: typeof raw.created_at === "string" ? raw.created_at : (/* @__PURE__ */ new Date()).toISOString(),
|
|
245
|
+
edited_at: typeof raw.edited_at === "string" ? raw.edited_at : null,
|
|
246
|
+
deleted_at: typeof raw.deleted_at === "string" ? raw.deleted_at : null,
|
|
247
|
+
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
248
|
+
mentioned_user_ids: Array.isArray(raw.mentioned_user_ids) ? raw.mentioned_user_ids : []
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function getMessagePageSummary(queryClient, conversationId) {
|
|
252
|
+
const page = queryClient.getQueryData(chatKeys.messages(conversationId));
|
|
253
|
+
const lastItem = page && page.items.length > 0 ? page.items[page.items.length - 1] : null;
|
|
254
|
+
return {
|
|
255
|
+
count: page?.items.length ?? 0,
|
|
256
|
+
firstId: page?.items[0]?.id ?? null,
|
|
257
|
+
lastId: lastItem?.id ?? null
|
|
258
|
+
};
|
|
259
|
+
}
|
|
230
260
|
function pushMessageToCache(queryClient, msg) {
|
|
231
|
-
let
|
|
232
|
-
queryClient.setQueryData(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (prev.items.some((m) => m.id === msg.id)) return prev;
|
|
237
|
-
inserted = true;
|
|
238
|
-
return { ...prev, items: [msg, ...prev.items] };
|
|
261
|
+
let result = "missing-cache";
|
|
262
|
+
queryClient.setQueryData(chatKeys.messages(msg.conversation_id), (prev) => {
|
|
263
|
+
if (!prev) {
|
|
264
|
+
result = "seeded";
|
|
265
|
+
return { items: [msg], next_cursor: "" };
|
|
239
266
|
}
|
|
240
|
-
|
|
241
|
-
|
|
267
|
+
if (prev.items.some((m) => m.id === msg.id)) {
|
|
268
|
+
result = "duplicate";
|
|
269
|
+
return prev;
|
|
270
|
+
}
|
|
271
|
+
result = "updated";
|
|
272
|
+
return { ...prev, items: [msg, ...prev.items] };
|
|
273
|
+
});
|
|
274
|
+
return result;
|
|
242
275
|
}
|
|
243
276
|
function markDeletedInCache(queryClient, conversationId, messageId, deletedAt) {
|
|
277
|
+
let result = "missing-cache";
|
|
244
278
|
queryClient.setQueryData(
|
|
245
279
|
chatKeys.messages(conversationId),
|
|
246
280
|
(prev) => {
|
|
247
281
|
if (!prev) return void 0;
|
|
248
282
|
const idx = prev.items.findIndex((m) => m.id === messageId);
|
|
249
|
-
if (idx === -1)
|
|
283
|
+
if (idx === -1) {
|
|
284
|
+
result = "missing-message";
|
|
285
|
+
return prev;
|
|
286
|
+
}
|
|
250
287
|
const updated = [...prev.items];
|
|
251
288
|
updated[idx] = { ...updated[idx], deleted_at: deletedAt };
|
|
289
|
+
result = "updated";
|
|
252
290
|
return { ...prev, items: updated };
|
|
253
291
|
}
|
|
254
292
|
);
|
|
293
|
+
return result;
|
|
255
294
|
}
|
|
256
295
|
function applyEditInCache(queryClient, updated) {
|
|
296
|
+
let result = "missing-cache";
|
|
257
297
|
queryClient.setQueryData(
|
|
258
298
|
chatKeys.messages(updated.conversation_id),
|
|
259
299
|
(prev) => {
|
|
260
300
|
if (!prev) return void 0;
|
|
261
301
|
const idx = prev.items.findIndex((m) => m.id === updated.id);
|
|
262
|
-
if (idx === -1)
|
|
302
|
+
if (idx === -1) {
|
|
303
|
+
result = "missing-message";
|
|
304
|
+
return prev;
|
|
305
|
+
}
|
|
263
306
|
const items = [...prev.items];
|
|
264
307
|
items[idx] = { ...items[idx], ...updated };
|
|
308
|
+
result = "updated";
|
|
265
309
|
return { ...prev, items };
|
|
266
310
|
}
|
|
267
311
|
);
|
|
312
|
+
return result;
|
|
268
313
|
}
|
|
269
|
-
function invalidateQueriesFromNatsPayload(payload, queryClient) {
|
|
270
|
-
if (!payload || typeof payload !== "object")
|
|
314
|
+
function invalidateQueriesFromNatsPayload(payload, queryClient, debug = false) {
|
|
315
|
+
if (!payload || typeof payload !== "object") {
|
|
316
|
+
chatDebugWarn(debug, "NATS cache update ignored: payload was not an object", payload);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
271
319
|
const p = payload;
|
|
272
320
|
const eventType = p.type;
|
|
273
|
-
if (typeof eventType !== "string")
|
|
321
|
+
if (typeof eventType !== "string") {
|
|
322
|
+
chatDebugWarn(debug, "NATS cache update ignored: payload.type missing", p);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
274
325
|
const ed = p.event_data;
|
|
275
|
-
if (!ed || typeof ed !== "object")
|
|
326
|
+
if (!ed || typeof ed !== "object") {
|
|
327
|
+
chatDebugWarn(debug, "NATS cache update ignored: payload.event_data missing", {
|
|
328
|
+
eventType,
|
|
329
|
+
payload: p
|
|
330
|
+
});
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
276
333
|
const eventData = ed;
|
|
334
|
+
const conversationId = typeof eventData.conversation_id === "string" ? eventData.conversation_id : null;
|
|
335
|
+
chatDebugLog(debug, "NATS cache update received", {
|
|
336
|
+
eventType,
|
|
337
|
+
conversationId,
|
|
338
|
+
hasMessage: !!eventData.message && typeof eventData.message === "object"
|
|
339
|
+
});
|
|
277
340
|
if (eventType === "chat.message.new") {
|
|
278
341
|
const convId = eventData.conversation_id;
|
|
279
|
-
if (typeof convId !== "string" || !convId.trim())
|
|
342
|
+
if (typeof convId !== "string" || !convId.trim()) {
|
|
343
|
+
chatDebugWarn(debug, "NATS message.new ignored: missing conversation_id", eventData);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const before = getMessagePageSummary(queryClient, convId);
|
|
280
347
|
const msgRaw = eventData.message;
|
|
281
348
|
if (msgRaw && typeof msgRaw === "object") {
|
|
282
349
|
const raw = msgRaw;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
350
|
+
const msg = parseMessage(raw);
|
|
351
|
+
if (msg) {
|
|
352
|
+
const mutation = pushMessageToCache(queryClient, msg);
|
|
353
|
+
const after = getMessagePageSummary(queryClient, convId);
|
|
354
|
+
chatDebugLog(debug, "NATS message.new cache mutation", {
|
|
355
|
+
conversationId: convId,
|
|
356
|
+
messageId: msg.id,
|
|
357
|
+
mutation,
|
|
358
|
+
before,
|
|
359
|
+
after
|
|
360
|
+
});
|
|
361
|
+
} else {
|
|
362
|
+
chatDebugWarn(debug, "NATS message.new ignored: event_data.message missing required ids", {
|
|
363
|
+
conversationId: convId,
|
|
364
|
+
message: raw
|
|
365
|
+
});
|
|
296
366
|
}
|
|
367
|
+
} else {
|
|
368
|
+
chatDebugWarn(debug, "NATS message.new ignored: event_data.message missing or not an object", {
|
|
369
|
+
conversationId: convId,
|
|
370
|
+
eventData
|
|
371
|
+
});
|
|
297
372
|
}
|
|
373
|
+
chatDebugLog(debug, "NATS message.new invalidating queries", {
|
|
374
|
+
conversationId: convId,
|
|
375
|
+
messageQueryKey: chatKeys.messages(convId),
|
|
376
|
+
conversationsQueryKey: chatKeys.conversations()
|
|
377
|
+
});
|
|
298
378
|
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(convId) });
|
|
299
379
|
void queryClient.invalidateQueries({ queryKey: chatKeys.conversations() });
|
|
300
380
|
return;
|
|
@@ -303,21 +383,20 @@ function invalidateQueriesFromNatsPayload(payload, queryClient) {
|
|
|
303
383
|
const msgRaw = eventData.message;
|
|
304
384
|
if (msgRaw && typeof msgRaw === "object") {
|
|
305
385
|
const raw = msgRaw;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
deleted_at: typeof raw.deleted_at === "string" ? raw.deleted_at : null,
|
|
315
|
-
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
316
|
-
mentioned_user_ids: Array.isArray(raw.mentioned_user_ids) ? raw.mentioned_user_ids : []
|
|
317
|
-
};
|
|
318
|
-
applyEditInCache(queryClient, updated);
|
|
386
|
+
const updated = parseMessage(raw);
|
|
387
|
+
if (updated) {
|
|
388
|
+
const mutation = applyEditInCache(queryClient, updated);
|
|
389
|
+
chatDebugLog(debug, "NATS message.edited cache mutation", {
|
|
390
|
+
conversationId: updated.conversation_id,
|
|
391
|
+
messageId: updated.id,
|
|
392
|
+
mutation
|
|
393
|
+
});
|
|
319
394
|
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(updated.conversation_id) });
|
|
395
|
+
} else {
|
|
396
|
+
chatDebugWarn(debug, "NATS message.edited ignored: event_data.message missing required ids", raw);
|
|
320
397
|
}
|
|
398
|
+
} else {
|
|
399
|
+
chatDebugWarn(debug, "NATS message.edited ignored: event_data.message missing or not an object", eventData);
|
|
321
400
|
}
|
|
322
401
|
return;
|
|
323
402
|
}
|
|
@@ -329,16 +408,27 @@ function invalidateQueriesFromNatsPayload(payload, queryClient) {
|
|
|
329
408
|
const convId = raw.conversation_id;
|
|
330
409
|
const deletedAt = typeof raw.deleted_at === "string" ? raw.deleted_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
331
410
|
if (typeof msgId === "string" && typeof convId === "string") {
|
|
332
|
-
markDeletedInCache(queryClient, convId, msgId, deletedAt);
|
|
411
|
+
const mutation = markDeletedInCache(queryClient, convId, msgId, deletedAt);
|
|
412
|
+
chatDebugLog(debug, "NATS message.deleted cache mutation", {
|
|
413
|
+
conversationId: convId,
|
|
414
|
+
messageId: msgId,
|
|
415
|
+
mutation
|
|
416
|
+
});
|
|
333
417
|
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(convId) });
|
|
418
|
+
} else {
|
|
419
|
+
chatDebugWarn(debug, "NATS message.deleted ignored: event_data.message missing required ids", raw);
|
|
334
420
|
}
|
|
421
|
+
} else {
|
|
422
|
+
chatDebugWarn(debug, "NATS message.deleted ignored: event_data.message missing or not an object", eventData);
|
|
335
423
|
}
|
|
424
|
+
return;
|
|
336
425
|
}
|
|
426
|
+
chatDebugLog(debug, "NATS payload ignored: unsupported event type", { eventType, eventData });
|
|
337
427
|
}
|
|
338
428
|
|
|
339
429
|
// src/chat/provider/ChatNatsBridge.tsx
|
|
340
430
|
function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
341
|
-
const { natsTenantId, userId, queryClient } = useChat();
|
|
431
|
+
const { natsTenantId, userId, queryClient, debug } = useChat();
|
|
342
432
|
const { data: convData } = useConversations();
|
|
343
433
|
const entityConversationIds = react.useMemo(() => {
|
|
344
434
|
const items = convData?.items ?? [];
|
|
@@ -351,6 +441,13 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
351
441
|
const connRef = react.useRef(null);
|
|
352
442
|
react.useEffect(() => {
|
|
353
443
|
if (servers.length === 0) return;
|
|
444
|
+
const entityConvIds = entityKey ? entityKey.split(",").filter(Boolean) : [];
|
|
445
|
+
chatDebugLog(debug, "ChatNatsBridge: starting subscriptions", {
|
|
446
|
+
userId,
|
|
447
|
+
natsTenantId,
|
|
448
|
+
servers,
|
|
449
|
+
entityConversationIds: entityConvIds
|
|
450
|
+
});
|
|
354
451
|
let cancelled = false;
|
|
355
452
|
void (async () => {
|
|
356
453
|
try {
|
|
@@ -369,10 +466,21 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
369
466
|
}
|
|
370
467
|
connRef.current = conn;
|
|
371
468
|
onConnectedChangeRef.current(true);
|
|
469
|
+
chatDebugLog(debug, "ChatNatsBridge: NATS connected", {
|
|
470
|
+
userId,
|
|
471
|
+
natsTenantId,
|
|
472
|
+
servers
|
|
473
|
+
});
|
|
372
474
|
const handleMsg = (data, subject) => {
|
|
475
|
+
const payloadText = new TextDecoder().decode(data);
|
|
373
476
|
if (process.env.NODE_ENV === "development") {
|
|
374
477
|
console.debug("[NATS] raw message received on subject:", subject, "bytes:", data.length);
|
|
375
478
|
}
|
|
479
|
+
chatDebugLog(debug, "ChatNatsBridge: NATS payload received", {
|
|
480
|
+
subject,
|
|
481
|
+
bytes: data.length,
|
|
482
|
+
payloadText
|
|
483
|
+
});
|
|
376
484
|
let payload;
|
|
377
485
|
try {
|
|
378
486
|
payload = jc.decode(data);
|
|
@@ -385,7 +493,7 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
385
493
|
if (process.env.NODE_ENV === "development") {
|
|
386
494
|
console.debug("[NATS] decoded payload:", payload);
|
|
387
495
|
}
|
|
388
|
-
invalidateQueriesFromNatsPayload(payload, queryClient);
|
|
496
|
+
invalidateQueriesFromNatsPayload(payload, queryClient, debug);
|
|
389
497
|
if (process.env.NODE_ENV === "development") {
|
|
390
498
|
if (payload && typeof payload === "object" && payload.type === "chat.message.new") {
|
|
391
499
|
const p = payload;
|
|
@@ -423,6 +531,7 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
423
531
|
}
|
|
424
532
|
};
|
|
425
533
|
const inbox = userInboxSubject(natsTenantId, userId);
|
|
534
|
+
chatDebugLog(debug, "ChatNatsBridge: subscribing inbox", { inbox });
|
|
426
535
|
const subInbox = conn.subscribe(inbox);
|
|
427
536
|
void (async () => {
|
|
428
537
|
for await (const m of subInbox) {
|
|
@@ -430,8 +539,12 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
430
539
|
handleMsg(m.data, m.subject);
|
|
431
540
|
}
|
|
432
541
|
})();
|
|
433
|
-
for (const convId of
|
|
542
|
+
for (const convId of entityConvIds) {
|
|
434
543
|
const subj = entityConversationSubject(natsTenantId, convId);
|
|
544
|
+
chatDebugLog(debug, "ChatNatsBridge: subscribing entity conversation", {
|
|
545
|
+
conversationId: convId,
|
|
546
|
+
subject: subj
|
|
547
|
+
});
|
|
435
548
|
const sub = conn.subscribe(subj);
|
|
436
549
|
void (async () => {
|
|
437
550
|
for await (const m of sub) {
|
|
@@ -443,7 +556,7 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
443
556
|
if (process.env.NODE_ENV === "development") {
|
|
444
557
|
const subjectList = [
|
|
445
558
|
inbox,
|
|
446
|
-
...
|
|
559
|
+
...entityConvIds.map((id) => entityConversationSubject(natsTenantId, id))
|
|
447
560
|
];
|
|
448
561
|
console.info("[NATS] WebSocket connected \u2014 servers:", servers, "subjects:", subjectList);
|
|
449
562
|
void (async () => {
|
|
@@ -455,23 +568,26 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
455
568
|
}
|
|
456
569
|
await conn.closed();
|
|
457
570
|
connRef.current = null;
|
|
571
|
+
chatDebugWarn(debug, "ChatNatsBridge: NATS connection closed");
|
|
458
572
|
if (!cancelled) onConnectedChangeRef.current(false);
|
|
459
573
|
} catch (err) {
|
|
460
574
|
connRef.current = null;
|
|
461
575
|
if (process.env.NODE_ENV === "development") {
|
|
462
576
|
console.error("[NATS] WebSocket connect failed \u2014 check ws:// URL, NATS websocket block, and CORS/mixed content:", err);
|
|
463
577
|
}
|
|
578
|
+
chatDebugWarn(debug, "ChatNatsBridge: NATS connection failed", err);
|
|
464
579
|
if (!cancelled) onConnectedChangeRef.current(false);
|
|
465
580
|
}
|
|
466
581
|
})();
|
|
467
582
|
return () => {
|
|
468
583
|
cancelled = true;
|
|
584
|
+
chatDebugLog(debug, "ChatNatsBridge: cleaning up subscriptions");
|
|
469
585
|
onConnectedChangeRef.current(false);
|
|
470
586
|
const c = connRef.current;
|
|
471
587
|
connRef.current = null;
|
|
472
588
|
void c?.drain();
|
|
473
589
|
};
|
|
474
|
-
}, [servers, natsToken, natsTenantId, userId, entityKey, queryClient,
|
|
590
|
+
}, [servers, natsToken, natsTenantId, userId, entityKey, queryClient, debug]);
|
|
475
591
|
return null;
|
|
476
592
|
}
|
|
477
593
|
|
|
@@ -847,16 +963,28 @@ function appendOlderMessages(current, older) {
|
|
|
847
963
|
// src/chat/hooks/useMessages.ts
|
|
848
964
|
var INITIAL_MESSAGE_PAGE_SIZE = 30;
|
|
849
965
|
function useMessages(conversationId) {
|
|
850
|
-
const { api, wsConnected, config, queryClient } = useChat();
|
|
966
|
+
const { api, wsConnected, config, queryClient, debug } = useChat();
|
|
851
967
|
const interval = config.pollIntervalMs ?? 3e4;
|
|
852
968
|
const [isFetchingOlder, setIsFetchingOlder] = react.useState(false);
|
|
853
969
|
const fetchingRef = react.useRef(false);
|
|
854
970
|
const query = reactQuery.useQuery({
|
|
855
971
|
queryKey: chatKeys.messages(conversationId),
|
|
856
972
|
queryFn: async () => {
|
|
973
|
+
chatDebugLog(debug, "useMessages: queryFn start", {
|
|
974
|
+
conversationId,
|
|
975
|
+
wsConnected
|
|
976
|
+
});
|
|
857
977
|
const res = await api.listMessages(conversationId, void 0, INITIAL_MESSAGE_PAGE_SIZE);
|
|
858
978
|
const prev = queryClient.getQueryData(chatKeys.messages(conversationId));
|
|
859
|
-
|
|
979
|
+
const merged = mergeMessagePages(res, prev);
|
|
980
|
+
chatDebugLog(debug, "useMessages: queryFn success", {
|
|
981
|
+
conversationId,
|
|
982
|
+
serverCount: res.items.length,
|
|
983
|
+
previousCachedCount: prev?.items.length ?? 0,
|
|
984
|
+
mergedCount: merged.items.length,
|
|
985
|
+
firstMessageId: merged.items[0]?.id ?? null
|
|
986
|
+
});
|
|
987
|
+
return merged;
|
|
860
988
|
},
|
|
861
989
|
enabled: !!conversationId,
|
|
862
990
|
refetchInterval: wsConnected ? false : interval
|
|
@@ -880,6 +1008,29 @@ function useMessages(conversationId) {
|
|
|
880
1008
|
}, [conversationId, queryClient, api]);
|
|
881
1009
|
const cached = queryClient.getQueryData(chatKeys.messages(conversationId));
|
|
882
1010
|
const hasMoreMessages = !!cached?.next_cursor;
|
|
1011
|
+
react.useEffect(() => {
|
|
1012
|
+
if (!conversationId) return;
|
|
1013
|
+
chatDebugLog(debug, "useMessages: hook snapshot", {
|
|
1014
|
+
conversationId,
|
|
1015
|
+
wsConnected,
|
|
1016
|
+
status: query.status,
|
|
1017
|
+
fetchStatus: query.fetchStatus,
|
|
1018
|
+
dataCount: query.data?.items.length ?? 0,
|
|
1019
|
+
cachedCount: cached?.items.length ?? 0,
|
|
1020
|
+
firstDataMessageId: query.data?.items[0]?.id ?? null,
|
|
1021
|
+
firstCachedMessageId: cached?.items[0]?.id ?? null
|
|
1022
|
+
});
|
|
1023
|
+
}, [
|
|
1024
|
+
cached?.items.length,
|
|
1025
|
+
cached?.items[0]?.id,
|
|
1026
|
+
conversationId,
|
|
1027
|
+
debug,
|
|
1028
|
+
query.data?.items.length,
|
|
1029
|
+
query.data?.items[0]?.id,
|
|
1030
|
+
query.fetchStatus,
|
|
1031
|
+
query.status,
|
|
1032
|
+
wsConnected
|
|
1033
|
+
]);
|
|
883
1034
|
return {
|
|
884
1035
|
...query,
|
|
885
1036
|
fetchOlderMessages,
|
|
@@ -2280,6 +2431,7 @@ exports.convLabel = convLabel;
|
|
|
2280
2431
|
exports.formatConvTime = formatConvTime;
|
|
2281
2432
|
exports.initials = initials2;
|
|
2282
2433
|
exports.mergeMessagePages = mergeMessagePages;
|
|
2434
|
+
exports.normalizeConversationCacheId = normalizeConversationCacheId;
|
|
2283
2435
|
exports.useChat = useChat;
|
|
2284
2436
|
exports.useChatActions = useChatActions;
|
|
2285
2437
|
exports.useChatPanelController = useChatPanelController;
|