@anakin824/prdg-chat-ui 0.3.1 → 0.3.2
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 +191 -49
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -227,74 +227,146 @@ function userInboxSubject(natsTenantId, userId) {
|
|
|
227
227
|
function entityConversationSubject(natsTenantId, conversationId) {
|
|
228
228
|
return `chat.${natsTenantId}.conversation.${conversationId}`;
|
|
229
229
|
}
|
|
230
|
+
function parseMessage(raw) {
|
|
231
|
+
if (typeof raw.id !== "string" || typeof raw.conversation_id !== "string") {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
id: raw.id,
|
|
236
|
+
conversation_id: raw.conversation_id,
|
|
237
|
+
sender_id: typeof raw.sender_id === "string" ? raw.sender_id : "",
|
|
238
|
+
body: typeof raw.body === "string" ? raw.body : null,
|
|
239
|
+
created_at: typeof raw.created_at === "string" ? raw.created_at : (/* @__PURE__ */ new Date()).toISOString(),
|
|
240
|
+
edited_at: typeof raw.edited_at === "string" ? raw.edited_at : null,
|
|
241
|
+
deleted_at: typeof raw.deleted_at === "string" ? raw.deleted_at : null,
|
|
242
|
+
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
243
|
+
mentioned_user_ids: Array.isArray(raw.mentioned_user_ids) ? raw.mentioned_user_ids : []
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function getMessagePageSummary(queryClient, conversationId) {
|
|
247
|
+
const page = queryClient.getQueryData(chatKeys.messages(conversationId));
|
|
248
|
+
const lastItem = page && page.items.length > 0 ? page.items[page.items.length - 1] : null;
|
|
249
|
+
return {
|
|
250
|
+
count: page?.items.length ?? 0,
|
|
251
|
+
firstId: page?.items[0]?.id ?? null,
|
|
252
|
+
lastId: lastItem?.id ?? null
|
|
253
|
+
};
|
|
254
|
+
}
|
|
230
255
|
function pushMessageToCache(queryClient, msg) {
|
|
231
|
-
let
|
|
232
|
-
queryClient.setQueryData(
|
|
233
|
-
|
|
234
|
-
(prev) => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
inserted = true;
|
|
238
|
-
return { ...prev, items: [msg, ...prev.items] };
|
|
256
|
+
let result = "missing-cache";
|
|
257
|
+
queryClient.setQueryData(chatKeys.messages(msg.conversation_id), (prev) => {
|
|
258
|
+
if (!prev) return void 0;
|
|
259
|
+
if (prev.items.some((m) => m.id === msg.id)) {
|
|
260
|
+
result = "duplicate";
|
|
261
|
+
return prev;
|
|
239
262
|
}
|
|
240
|
-
|
|
241
|
-
|
|
263
|
+
result = "updated";
|
|
264
|
+
return { ...prev, items: [msg, ...prev.items] };
|
|
265
|
+
});
|
|
266
|
+
return result;
|
|
242
267
|
}
|
|
243
268
|
function markDeletedInCache(queryClient, conversationId, messageId, deletedAt) {
|
|
269
|
+
let result = "missing-cache";
|
|
244
270
|
queryClient.setQueryData(
|
|
245
271
|
chatKeys.messages(conversationId),
|
|
246
272
|
(prev) => {
|
|
247
273
|
if (!prev) return void 0;
|
|
248
274
|
const idx = prev.items.findIndex((m) => m.id === messageId);
|
|
249
|
-
if (idx === -1)
|
|
275
|
+
if (idx === -1) {
|
|
276
|
+
result = "missing-message";
|
|
277
|
+
return prev;
|
|
278
|
+
}
|
|
250
279
|
const updated = [...prev.items];
|
|
251
280
|
updated[idx] = { ...updated[idx], deleted_at: deletedAt };
|
|
281
|
+
result = "updated";
|
|
252
282
|
return { ...prev, items: updated };
|
|
253
283
|
}
|
|
254
284
|
);
|
|
285
|
+
return result;
|
|
255
286
|
}
|
|
256
287
|
function applyEditInCache(queryClient, updated) {
|
|
288
|
+
let result = "missing-cache";
|
|
257
289
|
queryClient.setQueryData(
|
|
258
290
|
chatKeys.messages(updated.conversation_id),
|
|
259
291
|
(prev) => {
|
|
260
292
|
if (!prev) return void 0;
|
|
261
293
|
const idx = prev.items.findIndex((m) => m.id === updated.id);
|
|
262
|
-
if (idx === -1)
|
|
294
|
+
if (idx === -1) {
|
|
295
|
+
result = "missing-message";
|
|
296
|
+
return prev;
|
|
297
|
+
}
|
|
263
298
|
const items = [...prev.items];
|
|
264
299
|
items[idx] = { ...items[idx], ...updated };
|
|
300
|
+
result = "updated";
|
|
265
301
|
return { ...prev, items };
|
|
266
302
|
}
|
|
267
303
|
);
|
|
304
|
+
return result;
|
|
268
305
|
}
|
|
269
|
-
function invalidateQueriesFromNatsPayload(payload, queryClient) {
|
|
270
|
-
if (!payload || typeof payload !== "object")
|
|
306
|
+
function invalidateQueriesFromNatsPayload(payload, queryClient, debug = false) {
|
|
307
|
+
if (!payload || typeof payload !== "object") {
|
|
308
|
+
chatDebugWarn(debug, "NATS cache update ignored: payload was not an object", payload);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
271
311
|
const p = payload;
|
|
272
312
|
const eventType = p.type;
|
|
273
|
-
if (typeof eventType !== "string")
|
|
313
|
+
if (typeof eventType !== "string") {
|
|
314
|
+
chatDebugWarn(debug, "NATS cache update ignored: payload.type missing", p);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
274
317
|
const ed = p.event_data;
|
|
275
|
-
if (!ed || typeof ed !== "object")
|
|
318
|
+
if (!ed || typeof ed !== "object") {
|
|
319
|
+
chatDebugWarn(debug, "NATS cache update ignored: payload.event_data missing", {
|
|
320
|
+
eventType,
|
|
321
|
+
payload: p
|
|
322
|
+
});
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
276
325
|
const eventData = ed;
|
|
326
|
+
const conversationId = typeof eventData.conversation_id === "string" ? eventData.conversation_id : null;
|
|
327
|
+
chatDebugLog(debug, "NATS cache update received", {
|
|
328
|
+
eventType,
|
|
329
|
+
conversationId,
|
|
330
|
+
hasMessage: !!eventData.message && typeof eventData.message === "object"
|
|
331
|
+
});
|
|
277
332
|
if (eventType === "chat.message.new") {
|
|
278
333
|
const convId = eventData.conversation_id;
|
|
279
|
-
if (typeof convId !== "string" || !convId.trim())
|
|
334
|
+
if (typeof convId !== "string" || !convId.trim()) {
|
|
335
|
+
chatDebugWarn(debug, "NATS message.new ignored: missing conversation_id", eventData);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const before = getMessagePageSummary(queryClient, convId);
|
|
280
339
|
const msgRaw = eventData.message;
|
|
281
340
|
if (msgRaw && typeof msgRaw === "object") {
|
|
282
341
|
const raw = msgRaw;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
342
|
+
const msg = parseMessage(raw);
|
|
343
|
+
if (msg) {
|
|
344
|
+
const mutation = pushMessageToCache(queryClient, msg);
|
|
345
|
+
const after = getMessagePageSummary(queryClient, convId);
|
|
346
|
+
chatDebugLog(debug, "NATS message.new cache mutation", {
|
|
347
|
+
conversationId: convId,
|
|
348
|
+
messageId: msg.id,
|
|
349
|
+
mutation,
|
|
350
|
+
before,
|
|
351
|
+
after
|
|
352
|
+
});
|
|
353
|
+
} else {
|
|
354
|
+
chatDebugWarn(debug, "NATS message.new ignored: event_data.message missing required ids", {
|
|
355
|
+
conversationId: convId,
|
|
356
|
+
message: raw
|
|
357
|
+
});
|
|
296
358
|
}
|
|
359
|
+
} else {
|
|
360
|
+
chatDebugWarn(debug, "NATS message.new ignored: event_data.message missing or not an object", {
|
|
361
|
+
conversationId: convId,
|
|
362
|
+
eventData
|
|
363
|
+
});
|
|
297
364
|
}
|
|
365
|
+
chatDebugLog(debug, "NATS message.new invalidating queries", {
|
|
366
|
+
conversationId: convId,
|
|
367
|
+
messageQueryKey: chatKeys.messages(convId),
|
|
368
|
+
conversationsQueryKey: chatKeys.conversations()
|
|
369
|
+
});
|
|
298
370
|
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(convId) });
|
|
299
371
|
void queryClient.invalidateQueries({ queryKey: chatKeys.conversations() });
|
|
300
372
|
return;
|
|
@@ -303,21 +375,20 @@ function invalidateQueriesFromNatsPayload(payload, queryClient) {
|
|
|
303
375
|
const msgRaw = eventData.message;
|
|
304
376
|
if (msgRaw && typeof msgRaw === "object") {
|
|
305
377
|
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);
|
|
378
|
+
const updated = parseMessage(raw);
|
|
379
|
+
if (updated) {
|
|
380
|
+
const mutation = applyEditInCache(queryClient, updated);
|
|
381
|
+
chatDebugLog(debug, "NATS message.edited cache mutation", {
|
|
382
|
+
conversationId: updated.conversation_id,
|
|
383
|
+
messageId: updated.id,
|
|
384
|
+
mutation
|
|
385
|
+
});
|
|
319
386
|
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(updated.conversation_id) });
|
|
387
|
+
} else {
|
|
388
|
+
chatDebugWarn(debug, "NATS message.edited ignored: event_data.message missing required ids", raw);
|
|
320
389
|
}
|
|
390
|
+
} else {
|
|
391
|
+
chatDebugWarn(debug, "NATS message.edited ignored: event_data.message missing or not an object", eventData);
|
|
321
392
|
}
|
|
322
393
|
return;
|
|
323
394
|
}
|
|
@@ -329,16 +400,27 @@ function invalidateQueriesFromNatsPayload(payload, queryClient) {
|
|
|
329
400
|
const convId = raw.conversation_id;
|
|
330
401
|
const deletedAt = typeof raw.deleted_at === "string" ? raw.deleted_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
331
402
|
if (typeof msgId === "string" && typeof convId === "string") {
|
|
332
|
-
markDeletedInCache(queryClient, convId, msgId, deletedAt);
|
|
403
|
+
const mutation = markDeletedInCache(queryClient, convId, msgId, deletedAt);
|
|
404
|
+
chatDebugLog(debug, "NATS message.deleted cache mutation", {
|
|
405
|
+
conversationId: convId,
|
|
406
|
+
messageId: msgId,
|
|
407
|
+
mutation
|
|
408
|
+
});
|
|
333
409
|
void queryClient.invalidateQueries({ queryKey: chatKeys.messages(convId) });
|
|
410
|
+
} else {
|
|
411
|
+
chatDebugWarn(debug, "NATS message.deleted ignored: event_data.message missing required ids", raw);
|
|
334
412
|
}
|
|
413
|
+
} else {
|
|
414
|
+
chatDebugWarn(debug, "NATS message.deleted ignored: event_data.message missing or not an object", eventData);
|
|
335
415
|
}
|
|
416
|
+
return;
|
|
336
417
|
}
|
|
418
|
+
chatDebugLog(debug, "NATS payload ignored: unsupported event type", { eventType, eventData });
|
|
337
419
|
}
|
|
338
420
|
|
|
339
421
|
// src/chat/provider/ChatNatsBridge.tsx
|
|
340
422
|
function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
341
|
-
const { natsTenantId, userId, queryClient } = useChat();
|
|
423
|
+
const { natsTenantId, userId, queryClient, debug } = useChat();
|
|
342
424
|
const { data: convData } = useConversations();
|
|
343
425
|
const entityConversationIds = react.useMemo(() => {
|
|
344
426
|
const items = convData?.items ?? [];
|
|
@@ -351,6 +433,12 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
351
433
|
const connRef = react.useRef(null);
|
|
352
434
|
react.useEffect(() => {
|
|
353
435
|
if (servers.length === 0) return;
|
|
436
|
+
chatDebugLog(debug, "ChatNatsBridge: starting subscriptions", {
|
|
437
|
+
userId,
|
|
438
|
+
natsTenantId,
|
|
439
|
+
servers,
|
|
440
|
+
entityConversationIds
|
|
441
|
+
});
|
|
354
442
|
let cancelled = false;
|
|
355
443
|
void (async () => {
|
|
356
444
|
try {
|
|
@@ -369,10 +457,21 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
369
457
|
}
|
|
370
458
|
connRef.current = conn;
|
|
371
459
|
onConnectedChangeRef.current(true);
|
|
460
|
+
chatDebugLog(debug, "ChatNatsBridge: NATS connected", {
|
|
461
|
+
userId,
|
|
462
|
+
natsTenantId,
|
|
463
|
+
servers
|
|
464
|
+
});
|
|
372
465
|
const handleMsg = (data, subject) => {
|
|
466
|
+
const payloadText = new TextDecoder().decode(data);
|
|
373
467
|
if (process.env.NODE_ENV === "development") {
|
|
374
468
|
console.debug("[NATS] raw message received on subject:", subject, "bytes:", data.length);
|
|
375
469
|
}
|
|
470
|
+
chatDebugLog(debug, "ChatNatsBridge: NATS payload received", {
|
|
471
|
+
subject,
|
|
472
|
+
bytes: data.length,
|
|
473
|
+
payloadText
|
|
474
|
+
});
|
|
376
475
|
let payload;
|
|
377
476
|
try {
|
|
378
477
|
payload = jc.decode(data);
|
|
@@ -385,7 +484,7 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
385
484
|
if (process.env.NODE_ENV === "development") {
|
|
386
485
|
console.debug("[NATS] decoded payload:", payload);
|
|
387
486
|
}
|
|
388
|
-
invalidateQueriesFromNatsPayload(payload, queryClient);
|
|
487
|
+
invalidateQueriesFromNatsPayload(payload, queryClient, debug);
|
|
389
488
|
if (process.env.NODE_ENV === "development") {
|
|
390
489
|
if (payload && typeof payload === "object" && payload.type === "chat.message.new") {
|
|
391
490
|
const p = payload;
|
|
@@ -423,6 +522,7 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
423
522
|
}
|
|
424
523
|
};
|
|
425
524
|
const inbox = userInboxSubject(natsTenantId, userId);
|
|
525
|
+
chatDebugLog(debug, "ChatNatsBridge: subscribing inbox", { inbox });
|
|
426
526
|
const subInbox = conn.subscribe(inbox);
|
|
427
527
|
void (async () => {
|
|
428
528
|
for await (const m of subInbox) {
|
|
@@ -432,6 +532,10 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
432
532
|
})();
|
|
433
533
|
for (const convId of entityConversationIds) {
|
|
434
534
|
const subj = entityConversationSubject(natsTenantId, convId);
|
|
535
|
+
chatDebugLog(debug, "ChatNatsBridge: subscribing entity conversation", {
|
|
536
|
+
conversationId: convId,
|
|
537
|
+
subject: subj
|
|
538
|
+
});
|
|
435
539
|
const sub = conn.subscribe(subj);
|
|
436
540
|
void (async () => {
|
|
437
541
|
for await (const m of sub) {
|
|
@@ -455,23 +559,26 @@ function ChatNatsBridge({ natsWsUrl, natsToken, onConnectedChange }) {
|
|
|
455
559
|
}
|
|
456
560
|
await conn.closed();
|
|
457
561
|
connRef.current = null;
|
|
562
|
+
chatDebugWarn(debug, "ChatNatsBridge: NATS connection closed");
|
|
458
563
|
if (!cancelled) onConnectedChangeRef.current(false);
|
|
459
564
|
} catch (err) {
|
|
460
565
|
connRef.current = null;
|
|
461
566
|
if (process.env.NODE_ENV === "development") {
|
|
462
567
|
console.error("[NATS] WebSocket connect failed \u2014 check ws:// URL, NATS websocket block, and CORS/mixed content:", err);
|
|
463
568
|
}
|
|
569
|
+
chatDebugWarn(debug, "ChatNatsBridge: NATS connection failed", err);
|
|
464
570
|
if (!cancelled) onConnectedChangeRef.current(false);
|
|
465
571
|
}
|
|
466
572
|
})();
|
|
467
573
|
return () => {
|
|
468
574
|
cancelled = true;
|
|
575
|
+
chatDebugLog(debug, "ChatNatsBridge: cleaning up subscriptions");
|
|
469
576
|
onConnectedChangeRef.current(false);
|
|
470
577
|
const c = connRef.current;
|
|
471
578
|
connRef.current = null;
|
|
472
579
|
void c?.drain();
|
|
473
580
|
};
|
|
474
|
-
}, [servers, natsToken, natsTenantId, userId, entityKey, queryClient, entityConversationIds]);
|
|
581
|
+
}, [servers, natsToken, natsTenantId, userId, entityKey, queryClient, entityConversationIds, debug]);
|
|
475
582
|
return null;
|
|
476
583
|
}
|
|
477
584
|
|
|
@@ -847,16 +954,28 @@ function appendOlderMessages(current, older) {
|
|
|
847
954
|
// src/chat/hooks/useMessages.ts
|
|
848
955
|
var INITIAL_MESSAGE_PAGE_SIZE = 30;
|
|
849
956
|
function useMessages(conversationId) {
|
|
850
|
-
const { api, wsConnected, config, queryClient } = useChat();
|
|
957
|
+
const { api, wsConnected, config, queryClient, debug } = useChat();
|
|
851
958
|
const interval = config.pollIntervalMs ?? 3e4;
|
|
852
959
|
const [isFetchingOlder, setIsFetchingOlder] = react.useState(false);
|
|
853
960
|
const fetchingRef = react.useRef(false);
|
|
854
961
|
const query = reactQuery.useQuery({
|
|
855
962
|
queryKey: chatKeys.messages(conversationId),
|
|
856
963
|
queryFn: async () => {
|
|
964
|
+
chatDebugLog(debug, "useMessages: queryFn start", {
|
|
965
|
+
conversationId,
|
|
966
|
+
wsConnected
|
|
967
|
+
});
|
|
857
968
|
const res = await api.listMessages(conversationId, void 0, INITIAL_MESSAGE_PAGE_SIZE);
|
|
858
969
|
const prev = queryClient.getQueryData(chatKeys.messages(conversationId));
|
|
859
|
-
|
|
970
|
+
const merged = mergeMessagePages(res, prev);
|
|
971
|
+
chatDebugLog(debug, "useMessages: queryFn success", {
|
|
972
|
+
conversationId,
|
|
973
|
+
serverCount: res.items.length,
|
|
974
|
+
previousCachedCount: prev?.items.length ?? 0,
|
|
975
|
+
mergedCount: merged.items.length,
|
|
976
|
+
firstMessageId: merged.items[0]?.id ?? null
|
|
977
|
+
});
|
|
978
|
+
return merged;
|
|
860
979
|
},
|
|
861
980
|
enabled: !!conversationId,
|
|
862
981
|
refetchInterval: wsConnected ? false : interval
|
|
@@ -880,6 +999,29 @@ function useMessages(conversationId) {
|
|
|
880
999
|
}, [conversationId, queryClient, api]);
|
|
881
1000
|
const cached = queryClient.getQueryData(chatKeys.messages(conversationId));
|
|
882
1001
|
const hasMoreMessages = !!cached?.next_cursor;
|
|
1002
|
+
react.useEffect(() => {
|
|
1003
|
+
if (!conversationId) return;
|
|
1004
|
+
chatDebugLog(debug, "useMessages: hook snapshot", {
|
|
1005
|
+
conversationId,
|
|
1006
|
+
wsConnected,
|
|
1007
|
+
status: query.status,
|
|
1008
|
+
fetchStatus: query.fetchStatus,
|
|
1009
|
+
dataCount: query.data?.items.length ?? 0,
|
|
1010
|
+
cachedCount: cached?.items.length ?? 0,
|
|
1011
|
+
firstDataMessageId: query.data?.items[0]?.id ?? null,
|
|
1012
|
+
firstCachedMessageId: cached?.items[0]?.id ?? null
|
|
1013
|
+
});
|
|
1014
|
+
}, [
|
|
1015
|
+
cached?.items.length,
|
|
1016
|
+
cached?.items[0]?.id,
|
|
1017
|
+
conversationId,
|
|
1018
|
+
debug,
|
|
1019
|
+
query.data?.items.length,
|
|
1020
|
+
query.data?.items[0]?.id,
|
|
1021
|
+
query.fetchStatus,
|
|
1022
|
+
query.status,
|
|
1023
|
+
wsConnected
|
|
1024
|
+
]);
|
|
883
1025
|
return {
|
|
884
1026
|
...query,
|
|
885
1027
|
fetchOlderMessages,
|