@anakin824/prdg-chat-ui 0.2.0 → 0.2.1

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 CHANGED
@@ -169,9 +169,14 @@ type ChatProviderProps = {
169
169
  natsWsUrl?: string;
170
170
  /** Optional NATS auth token (when the server uses token authentication). */
171
171
  natsToken?: string;
172
+ /**
173
+ * When true, logs `[prdg-chat]` diagnostics (identity resolution, message `sender_id` vs context `userId`).
174
+ * If omitted, set env `NEXT_PUBLIC_PRDG_CHAT_DEBUG=true` in the host app instead.
175
+ */
176
+ debug?: boolean;
172
177
  children: ReactNode;
173
178
  };
174
- declare function ChatProvider({ apiUrl, token, userId: userIdProp, tenantId: tenantIdProp, conduitlyTenantId: conduitlyTenantIdProp, theme, pollIntervalMs, onUnreadChange: _onUnreadChange, onTokenRefresh, callEnabled, natsWsUrl, natsToken, children, }: ChatProviderProps): react.JSX.Element | null;
179
+ declare function ChatProvider({ apiUrl, token, userId: userIdProp, tenantId: tenantIdProp, conduitlyTenantId: conduitlyTenantIdProp, theme, pollIntervalMs, onUnreadChange: _onUnreadChange, onTokenRefresh, callEnabled, natsWsUrl, natsToken, debug: debugProp, children, }: ChatProviderProps): react.JSX.Element | null;
175
180
 
176
181
  type ListConversationsRes = {
177
182
  items: Conversation[];
@@ -274,6 +279,8 @@ type ChatContextValue = {
274
279
  * Key: conversationId. Value: array of user IDs (never includes the current user).
275
280
  */
276
281
  typingByConversation: Record<string, string[]>;
282
+ /** When true, logs identity + message ownership diagnostics to the console. */
283
+ debug: boolean;
277
284
  };
278
285
  declare function useChat(): ChatContextValue;
279
286
 
package/dist/index.d.ts CHANGED
@@ -169,9 +169,14 @@ type ChatProviderProps = {
169
169
  natsWsUrl?: string;
170
170
  /** Optional NATS auth token (when the server uses token authentication). */
171
171
  natsToken?: string;
172
+ /**
173
+ * When true, logs `[prdg-chat]` diagnostics (identity resolution, message `sender_id` vs context `userId`).
174
+ * If omitted, set env `NEXT_PUBLIC_PRDG_CHAT_DEBUG=true` in the host app instead.
175
+ */
176
+ debug?: boolean;
172
177
  children: ReactNode;
173
178
  };
174
- declare function ChatProvider({ apiUrl, token, userId: userIdProp, tenantId: tenantIdProp, conduitlyTenantId: conduitlyTenantIdProp, theme, pollIntervalMs, onUnreadChange: _onUnreadChange, onTokenRefresh, callEnabled, natsWsUrl, natsToken, children, }: ChatProviderProps): react.JSX.Element | null;
179
+ declare function ChatProvider({ apiUrl, token, userId: userIdProp, tenantId: tenantIdProp, conduitlyTenantId: conduitlyTenantIdProp, theme, pollIntervalMs, onUnreadChange: _onUnreadChange, onTokenRefresh, callEnabled, natsWsUrl, natsToken, debug: debugProp, children, }: ChatProviderProps): react.JSX.Element | null;
175
180
 
176
181
  type ListConversationsRes = {
177
182
  items: Conversation[];
@@ -274,6 +279,8 @@ type ChatContextValue = {
274
279
  * Key: conversationId. Value: array of user IDs (never includes the current user).
275
280
  */
276
281
  typingByConversation: Record<string, string[]>;
282
+ /** When true, logs identity + message ownership diagnostics to the console. */
283
+ debug: boolean;
277
284
  };
278
285
  declare function useChat(): ChatContextValue;
279
286
 
package/dist/index.js CHANGED
@@ -118,6 +118,25 @@ var ChatAPI = class {
118
118
  }
119
119
  };
120
120
 
121
+ // src/chat/lib/chatDebugLog.ts
122
+ function resolveChatDebug(debugProp) {
123
+ if (debugProp === true) return true;
124
+ if (debugProp === false) return false;
125
+ try {
126
+ return typeof process !== "undefined" && process.env?.NEXT_PUBLIC_PRDG_CHAT_DEBUG === "true";
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+ function chatDebugLog(debug, ...args) {
132
+ if (!debug) return;
133
+ console.log("[prdg-chat]", ...args);
134
+ }
135
+ function chatDebugWarn(debug, ...args) {
136
+ if (!debug) return;
137
+ console.warn("[prdg-chat]", ...args);
138
+ }
139
+
121
140
  // src/chat/lib/queryKeys.ts
122
141
  var chatKeys = {
123
142
  all: ["chat"],
@@ -337,8 +356,10 @@ function ChatProvider({
337
356
  callEnabled = false,
338
357
  natsWsUrl,
339
358
  natsToken,
359
+ debug: debugProp,
340
360
  children
341
361
  }) {
362
+ const debug = resolveChatDebug(debugProp);
342
363
  const queryClient = react.useMemo(
343
364
  () => new reactQuery.QueryClient({
344
365
  defaultOptions: {
@@ -367,15 +388,41 @@ function ChatProvider({
367
388
  conduitlyTenantIdProp
368
389
  );
369
390
  const [meError, setMeError] = react.useState(null);
391
+ react.useEffect(() => {
392
+ chatDebugLog(debug, "ChatProvider: session inputs", {
393
+ apiUrl: apiUrl.replace(/\/$/, ""),
394
+ hasToken: Boolean(token?.trim?.()),
395
+ userIdProp: userIdProp ?? null,
396
+ tenantIdProp: tenantIdProp ?? null,
397
+ conduitlyTenantIdProp: conduitlyTenantIdProp ?? null
398
+ });
399
+ }, [debug, apiUrl, token, userIdProp, tenantIdProp, conduitlyTenantIdProp]);
370
400
  react.useEffect(() => {
371
401
  setMeError(null);
372
402
  if (userIdProp) setResolvedUserId(userIdProp);
373
403
  if (tenantIdProp) setResolvedTenantId(tenantIdProp);
374
404
  if (conduitlyTenantIdProp) setResolvedConduitlyTenantId(conduitlyTenantIdProp);
375
- if (userIdProp && tenantIdProp) return;
405
+ if (userIdProp && tenantIdProp) {
406
+ chatDebugLog(debug, "identity: using userId + tenantId props (skipping GET /me)", {
407
+ userId: userIdProp,
408
+ tenantId: tenantIdProp
409
+ });
410
+ return;
411
+ }
412
+ chatDebugLog(debug, "identity: fetching GET /me \u2026", {
413
+ missingUserId: !userIdProp,
414
+ missingTenantId: !tenantIdProp
415
+ });
376
416
  let cancelled = false;
377
417
  api.getMe().then((me) => {
378
418
  if (cancelled) return;
419
+ chatDebugLog(debug, "identity: GET /me ok", {
420
+ id: me.id,
421
+ tenant_id: me.tenant_id,
422
+ display_name: me.display_name,
423
+ ext_user_id: me.ext_user_id ?? null,
424
+ conduitly_tenant_id: me.conduitly_tenant_id ?? null
425
+ });
379
426
  if (!userIdProp) setResolvedUserId(me.id);
380
427
  if (!tenantIdProp) setResolvedTenantId(me.tenant_id);
381
428
  if (!conduitlyTenantIdProp && me.conduitly_tenant_id) {
@@ -383,11 +430,12 @@ function ChatProvider({
383
430
  }
384
431
  }).catch((err) => {
385
432
  if (!cancelled) setMeError(err instanceof Error ? err.message : String(err));
433
+ chatDebugWarn(debug, "identity: GET /me failed", err);
386
434
  });
387
435
  return () => {
388
436
  cancelled = true;
389
437
  };
390
- }, [api, token, userIdProp, tenantIdProp, conduitlyTenantIdProp]);
438
+ }, [api, token, userIdProp, tenantIdProp, conduitlyTenantIdProp, debug]);
391
439
  const userId = resolvedUserId ?? "";
392
440
  const tenantId = resolvedTenantId ?? "";
393
441
  const mergedTheme = react.useMemo(() => ({ ...defaultTheme, ...theme }), [theme]);
@@ -451,7 +499,8 @@ function ChatProvider({
451
499
  sendTyping,
452
500
  sendTypingStop,
453
501
  sendRead,
454
- typingByConversation
502
+ typingByConversation,
503
+ debug
455
504
  }),
456
505
  [
457
506
  api,
@@ -465,9 +514,18 @@ function ChatProvider({
465
514
  sendTyping,
466
515
  sendTypingStop,
467
516
  sendRead,
468
- typingByConversation
517
+ typingByConversation,
518
+ debug
469
519
  ]
470
520
  );
521
+ react.useEffect(() => {
522
+ if (!resolvedUserId || !resolvedTenantId) return;
523
+ chatDebugLog(debug, "ChatProvider: context ready", {
524
+ userId: resolvedUserId,
525
+ tenantId: resolvedTenantId,
526
+ natsTenantId
527
+ });
528
+ }, [debug, resolvedUserId, resolvedTenantId, natsTenantId]);
471
529
  const layoutStyle = {
472
530
  flex: 1,
473
531
  minHeight: 0,
@@ -815,7 +873,7 @@ function escapeReg(s) {
815
873
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
816
874
  }
817
875
  function MessageInput({ conversationId }) {
818
- const { api, sendTyping, sendTypingStop, userId } = useChat();
876
+ const { api, sendTyping, sendTypingStop, userId, debug } = useChat();
819
877
  const qc = reactQuery.useQueryClient();
820
878
  const { uploadFile } = useUpload();
821
879
  const { recording, start, stop } = useVoiceRecorder();
@@ -955,6 +1013,17 @@ function MessageInput({ conversationId }) {
955
1013
  });
956
1014
  },
957
1015
  onSuccess: (confirmedMessage, _payload, context) => {
1016
+ if (debug && confirmedMessage.sender_id !== userId) {
1017
+ chatDebugWarn(
1018
+ debug,
1019
+ "MessageInput: confirmed sender_id !== context userId (own-bubble alignment will be wrong)",
1020
+ {
1021
+ contextUserId: userId,
1022
+ serverSenderId: confirmedMessage.sender_id,
1023
+ messageId: confirmedMessage.id
1024
+ }
1025
+ );
1026
+ }
958
1027
  qc.setQueryData(chatKeys.messages(conversationId), (old) => {
959
1028
  if (!old) return old;
960
1029
  const optimisticIdx = old.items.findIndex((m) => m.id === context?.optimisticId);
@@ -1297,10 +1366,30 @@ function MessageThread({
1297
1366
  isFetchingOlder,
1298
1367
  hasMoreMessages
1299
1368
  }) {
1369
+ const { userId, debug } = useChat();
1300
1370
  const scrollRef = react.useRef(null);
1301
1371
  const bottomRef = react.useRef(null);
1302
1372
  const sentinelRef = react.useRef(null);
1303
1373
  const ordered = [...messages].reverse();
1374
+ react.useEffect(() => {
1375
+ if (!debug || messages.length === 0) return;
1376
+ const senders = new Set(messages.map((m) => m.sender_id));
1377
+ const ownCount = messages.filter((m) => m.sender_id === userId).length;
1378
+ const mismatchSample = messages.find((m) => m.sender_id !== userId);
1379
+ chatDebugLog(debug, "MessageThread: sender snapshot", {
1380
+ messageCount: messages.length,
1381
+ contextUserId: userId,
1382
+ uniqueSenderIds: [...senders],
1383
+ ownMessageCount: ownCount,
1384
+ contextMatchesAnySender: userId ? senders.has(userId) : false
1385
+ });
1386
+ if (userId && ownCount === 0 && messages.length > 0) {
1387
+ chatDebugWarn(debug, "MessageThread: no messages match context userId (all bubbles show as \u201Cother\u201D)", {
1388
+ contextUserId: userId,
1389
+ sampleSenderId: mismatchSample?.sender_id ?? null
1390
+ });
1391
+ }
1392
+ }, [debug, messages, userId]);
1304
1393
  const seenIdsRef = react.useRef(/* @__PURE__ */ new Set());
1305
1394
  const initializedRef = react.useRef(false);
1306
1395
  const prevCountRef = react.useRef(0);