react-native-chatbot-ai 0.1.43 → 0.1.44

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.
Files changed (52) hide show
  1. package/lib/module/components/Drawer/DeleteSessionPopup.js +13 -1
  2. package/lib/module/components/Drawer/DeleteSessionPopup.js.map +1 -1
  3. package/lib/module/components/Drawer/RenameSessionPopup.js +11 -2
  4. package/lib/module/components/Drawer/RenameSessionPopup.js.map +1 -1
  5. package/lib/module/components/Drawer/ShareSessionPopup.js +11 -2
  6. package/lib/module/components/Drawer/ShareSessionPopup.js.map +1 -1
  7. package/lib/module/components/chat/footer/item/UploadFileItem.js +2 -2
  8. package/lib/module/components/chat/footer/item/UploadFileItem.js.map +1 -1
  9. package/lib/module/components/chat/footer/item/UploadImageItem.js +2 -2
  10. package/lib/module/components/chat/footer/item/UploadImageItem.js.map +1 -1
  11. package/lib/module/context/ChatContext.js +6 -3
  12. package/lib/module/context/ChatContext.js.map +1 -1
  13. package/lib/module/hooks/message/useStreamMessage.js +19 -2
  14. package/lib/module/hooks/message/useStreamMessage.js.map +1 -1
  15. package/lib/module/hooks/messageActions/useAudioPlayer.js +40 -9
  16. package/lib/module/hooks/messageActions/useAudioPlayer.js.map +1 -1
  17. package/lib/module/hooks/messageActions/useCopyToClipboard.js +13 -2
  18. package/lib/module/hooks/messageActions/useCopyToClipboard.js.map +1 -1
  19. package/lib/module/hooks/messageActions/useFeedback.js +20 -4
  20. package/lib/module/hooks/messageActions/useFeedback.js.map +1 -1
  21. package/lib/module/hooks/messageActions/useShareMessage.js +21 -4
  22. package/lib/module/hooks/messageActions/useShareMessage.js.map +1 -1
  23. package/lib/module/hooks/upload/useUploadItem.js +17 -4
  24. package/lib/module/hooks/upload/useUploadItem.js.map +1 -1
  25. package/lib/module/types/chat.js.map +1 -1
  26. package/lib/typescript/src/components/Drawer/DeleteSessionPopup.d.ts.map +1 -1
  27. package/lib/typescript/src/components/Drawer/RenameSessionPopup.d.ts.map +1 -1
  28. package/lib/typescript/src/components/Drawer/ShareSessionPopup.d.ts.map +1 -1
  29. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
  30. package/lib/typescript/src/hooks/message/useStreamMessage.d.ts.map +1 -1
  31. package/lib/typescript/src/hooks/messageActions/useAudioPlayer.d.ts.map +1 -1
  32. package/lib/typescript/src/hooks/messageActions/useCopyToClipboard.d.ts.map +1 -1
  33. package/lib/typescript/src/hooks/messageActions/useFeedback.d.ts.map +1 -1
  34. package/lib/typescript/src/hooks/messageActions/useShareMessage.d.ts.map +1 -1
  35. package/lib/typescript/src/hooks/upload/useUploadItem.d.ts +1 -1
  36. package/lib/typescript/src/hooks/upload/useUploadItem.d.ts.map +1 -1
  37. package/lib/typescript/src/types/chat.d.ts +9 -0
  38. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/components/Drawer/DeleteSessionPopup.tsx +14 -1
  41. package/src/components/Drawer/RenameSessionPopup.tsx +21 -2
  42. package/src/components/Drawer/ShareSessionPopup.tsx +13 -2
  43. package/src/components/chat/footer/item/UploadFileItem.tsx +2 -2
  44. package/src/components/chat/footer/item/UploadImageItem.tsx +1 -1
  45. package/src/context/ChatContext.tsx +3 -0
  46. package/src/hooks/message/useStreamMessage.ts +32 -2
  47. package/src/hooks/messageActions/useAudioPlayer.ts +49 -8
  48. package/src/hooks/messageActions/useCopyToClipboard.ts +39 -23
  49. package/src/hooks/messageActions/useFeedback.ts +24 -4
  50. package/src/hooks/messageActions/useShareMessage.ts +34 -6
  51. package/src/hooks/upload/useUploadItem.ts +23 -7
  52. package/src/types/chat.ts +13 -0
@@ -16,7 +16,7 @@ import useSessionStore from '../../store/session';
16
16
  export const useStreamMessage = () => {
17
17
  const sessionId = useSessionStore((state) => state.sessionId);
18
18
  const sessionIdRef = useRef<string | undefined>(sessionId);
19
- const { apiAddress, logGA } = useChatContext();
19
+ const { apiAddress, logGA, notifyError } = useChatContext();
20
20
  const setStreamMessage = useStreamMessageStore(
21
21
  (state) => state.setStreamMessage
22
22
  );
@@ -143,6 +143,18 @@ export const useStreamMessage = () => {
143
143
  throttledFlush();
144
144
  }
145
145
  } catch (error) {
146
+ const errorObj =
147
+ error instanceof Error ? error : new Error(String(error));
148
+ notifyError?.(
149
+ {
150
+ errorClass: 'ChatbotAI',
151
+ errorMessage: errorObj.message,
152
+ name: 'StreamMessageError',
153
+ message: `Stream message error id: ${currentSessionId}, error: ${errorObj.message}`,
154
+ stack: errorObj?.stack,
155
+ },
156
+ 'error'
157
+ );
146
158
  es.close();
147
159
  setIsStreaming(false);
148
160
  }
@@ -150,6 +162,17 @@ export const useStreamMessage = () => {
150
162
 
151
163
  es.addEventListener('error', (err) => {
152
164
  console.log('❌ Stream error:', err);
165
+ const errorObj = err instanceof Error ? err : new Error(String(err));
166
+ notifyError?.(
167
+ {
168
+ errorClass: 'ChatbotAI',
169
+ errorMessage: errorObj.message,
170
+ name: 'StreamMessageEventSourceError',
171
+ message: `Stream message error id: ${currentSessionId}, error: ${errorObj.message}`,
172
+ stack: errorObj?.stack,
173
+ },
174
+ 'error'
175
+ );
153
176
  onStreamMessageEnd(true);
154
177
  });
155
178
 
@@ -159,7 +182,14 @@ export const useStreamMessage = () => {
159
182
  console.log('✅ Stream closed');
160
183
  });
161
184
  },
162
- [apiAddress, setIsStreaming, setStreamMessage, logGA, sessionId]
185
+ [
186
+ apiAddress,
187
+ setIsStreaming,
188
+ setStreamMessage,
189
+ logGA,
190
+ sessionId,
191
+ notifyError,
192
+ ]
163
193
  );
164
194
 
165
195
  const retryStream = useCallback(
@@ -84,7 +84,7 @@ const setupPlayer = async () => {
84
84
  export const useAudioPlayer = ({
85
85
  messageId,
86
86
  }: UseAudioPlayerProps): UseAudioPlayerReturn => {
87
- const logGA = useChatContext().logGA;
87
+ const { logGA, notifyError } = useChatContext();
88
88
  const sessionId = useSessionStore().sessionId;
89
89
  const {
90
90
  currentMessageId,
@@ -160,11 +160,21 @@ export const useAudioPlayer = ({
160
160
  err?.response?.data?.message ||
161
161
  err?.message ||
162
162
  'Failed to fetch audio';
163
- console.error('Fetch audio error:', msg);
163
+ const errorObj = err instanceof Error ? err : new Error(String(err));
164
+ notifyError?.(
165
+ {
166
+ errorClass: 'ChatbotAI',
167
+ errorMessage: errorObj.message,
168
+ name: 'FetchAudioError',
169
+ message: `Fetch audio error: ${msg}`,
170
+ stack: errorObj?.stack,
171
+ },
172
+ 'error'
173
+ );
164
174
  throw new Error(msg);
165
175
  }
166
176
  },
167
- [getCachedAudioUrl, cacheAudioUrl]
177
+ [getCachedAudioUrl, cacheAudioUrl, notifyError]
168
178
  );
169
179
 
170
180
  // === PLAY AUDIO ===
@@ -200,11 +210,21 @@ export const useAudioPlayer = ({
200
210
  }, 300);
201
211
  } catch (err) {
202
212
  isUserActionInProgressRef.current = false;
203
- console.error('Failed to play audio:', err);
213
+ const errorObj = err instanceof Error ? err : new Error(String(err));
214
+ notifyError?.(
215
+ {
216
+ errorClass: 'ChatbotAI',
217
+ errorMessage: errorObj.message,
218
+ name: 'PlayAudioError',
219
+ message: `Failed to play audio: ${errorObj.message}`,
220
+ stack: errorObj?.stack,
221
+ },
222
+ 'error'
223
+ );
204
224
  throw new Error('Không thể phát âm thanh');
205
225
  }
206
226
  },
207
- [messageId, setIsPlaying]
227
+ [messageId, setIsPlaying, notifyError]
208
228
  );
209
229
 
210
230
  // === PAUSE ===
@@ -312,7 +332,17 @@ export const useAudioPlayer = ({
312
332
  const msg = err?.message || 'Failed to play audio';
313
333
  setError(msg);
314
334
  setIsPlaying(false);
315
- console.error('togglePlayback error:', msg);
335
+ const errorObj = err instanceof Error ? err : new Error(String(err));
336
+ notifyError?.(
337
+ {
338
+ errorClass: 'ChatbotAI',
339
+ errorMessage: errorObj.message,
340
+ name: 'TogglePlaybackError',
341
+ message: `togglePlayback error: ${msg}`,
342
+ stack: errorObj?.stack,
343
+ },
344
+ 'error'
345
+ );
316
346
  storeStopPlayback();
317
347
  UIUtils.toast.showError(trans('error_play_audio'));
318
348
  } finally {
@@ -335,6 +365,7 @@ export const useAudioPlayer = ({
335
365
  stopPlayback,
336
366
  setIsPlaying,
337
367
  storeStopPlayback,
368
+ notifyError,
338
369
  ]);
339
370
 
340
371
  // === PLAYER EVENTS ===
@@ -408,9 +439,19 @@ export const useAudioPlayer = ({
408
439
  const exists = await ReactNativeBlobUtil.fs.exists(audioDir);
409
440
  if (exists) await ReactNativeBlobUtil.fs.unlink(audioDir);
410
441
  } catch (e) {
411
- console.error('Cleanup cache error:', e);
442
+ const errorObj = e instanceof Error ? e : new Error(String(e));
443
+ notifyError?.(
444
+ {
445
+ errorClass: 'ChatbotAI',
446
+ errorMessage: errorObj.message,
447
+ name: 'CleanupCacheError',
448
+ message: `Cleanup cache error: ${errorObj.message}`,
449
+ stack: errorObj?.stack,
450
+ },
451
+ 'error'
452
+ );
412
453
  }
413
- }, []);
454
+ }, [notifyError]);
414
455
 
415
456
  useEffect(() => {
416
457
  if (currentSessionId && currentSessionId !== sessionId) {
@@ -7,32 +7,48 @@ import { useCallback, useState } from 'react';
7
7
  import Clipboard from '@react-native-clipboard/clipboard';
8
8
  import UIUtils from '../../utils/ui';
9
9
  import { cleanMarkdown } from '../../utils/textCleaner';
10
+ import { useChatContext } from '../../context/ChatContext';
10
11
 
11
12
  export const useCopyToClipboard = () => {
12
13
  const [isCopying, setIsCopying] = useState(false);
13
-
14
- const copyToClipboard = useCallback(async (text: string) => {
15
- if (!text || text.trim().length === 0) {
16
- UIUtils.toast.showError('Không nội dung để sao chép');
17
- return;
18
- }
19
-
20
- try {
21
- setIsCopying(true);
22
-
23
- // Remove markdown formatting for cleaner copy
24
- const cleanText = cleanMarkdown(text);
25
-
26
- Clipboard.setString(cleanText);
27
-
28
- UIUtils.toast.open({ title: 'Đã sao chép vào clipboard' });
29
- } catch (error) {
30
- console.error('Copy to clipboard error:', error);
31
- UIUtils.toast.showError(error);
32
- } finally {
33
- setIsCopying(false);
34
- }
35
- }, []);
14
+ const { notifyError } = useChatContext();
15
+
16
+ const copyToClipboard = useCallback(
17
+ async (text: string) => {
18
+ if (!text || text.trim().length === 0) {
19
+ UIUtils.toast.showError('Không có nội dung để sao chép');
20
+ return;
21
+ }
22
+
23
+ try {
24
+ setIsCopying(true);
25
+
26
+ // Remove markdown formatting for cleaner copy
27
+ const cleanText = cleanMarkdown(text);
28
+
29
+ Clipboard.setString(cleanText);
30
+
31
+ UIUtils.toast.open({ title: 'Đã sao chép vào clipboard' });
32
+ } catch (error) {
33
+ const errorObj =
34
+ error instanceof Error ? error : new Error(String(error));
35
+ notifyError?.(
36
+ {
37
+ errorClass: 'ChatbotAI',
38
+ errorMessage: errorObj.message,
39
+ name: 'CopyToClipboardError',
40
+ message: `Copy to clipboard error: ${errorObj.message}`,
41
+ stack: errorObj.stack,
42
+ },
43
+ 'error'
44
+ );
45
+ UIUtils.toast.showError(error);
46
+ } finally {
47
+ setIsCopying(false);
48
+ }
49
+ },
50
+ [notifyError]
51
+ );
36
52
 
37
53
  return { copyToClipboard, isCopying };
38
54
  };
@@ -14,7 +14,7 @@ import { useChatContext } from '../../context/ChatContext';
14
14
  import { GAEvents } from '../../constants/events';
15
15
 
16
16
  export const useFeedback = (messageId: string, message?: IMessageItem) => {
17
- const logGA = useChatContext().logGA;
17
+ const { logGA, notifyError } = useChatContext();
18
18
  const sessionId = useSessionStore((state) => state.sessionId);
19
19
 
20
20
  const feedback = useMessageActionsStore((state) =>
@@ -65,7 +65,16 @@ export const useFeedback = (messageId: string, message?: IMessageItem) => {
65
65
  async (type: FeedbackType, reason: string) => {
66
66
  console.log('selectReason', sessionId);
67
67
  if (!sessionId) {
68
- console.error('Session ID is required to send feedback');
68
+ notifyError?.(
69
+ {
70
+ errorClass: 'ChatbotAI',
71
+ errorMessage: 'Session ID is required to send feedback',
72
+ name: 'FeedbackError',
73
+ message: 'Session ID is required to send feedback',
74
+ stack: undefined,
75
+ },
76
+ 'error'
77
+ );
69
78
  UIUtils.toast.open({
70
79
  title: 'Không thể gửi phản hồi. Vui lòng thử lại.',
71
80
  });
@@ -93,11 +102,22 @@ export const useFeedback = (messageId: string, message?: IMessageItem) => {
93
102
 
94
103
  UIUtils.toast.open({ title: 'Cảm ơn phản hồi của bạn!' });
95
104
  } catch (error) {
96
- console.error('Failed to send feedback:', error);
105
+ const errorObj =
106
+ error instanceof Error ? error : new Error(String(error));
107
+ notifyError?.(
108
+ {
109
+ errorClass: 'ChatbotAI',
110
+ errorMessage: errorObj.message,
111
+ name: 'SendFeedbackError',
112
+ message: `Failed to send feedback: ${errorObj.message}`,
113
+ stack: errorObj?.stack,
114
+ },
115
+ 'error'
116
+ );
97
117
  UIUtils.toast.showError(error);
98
118
  }
99
119
  },
100
- [messageId, setFeedback, sendFeedback, sessionId, logGA]
120
+ [messageId, setFeedback, sendFeedback, sessionId, logGA, notifyError]
101
121
  );
102
122
 
103
123
  return {
@@ -22,7 +22,7 @@ export const useShareMessage = (
22
22
  messageContent: string,
23
23
  productImages?: string[]
24
24
  ) => {
25
- const logGA = useChatContext().logGA;
25
+ const { logGA, notifyError } = useChatContext();
26
26
  const shareState = useMessageActionsStore((state) =>
27
27
  state.getShareState(messageId)
28
28
  );
@@ -77,9 +77,19 @@ export const useShareMessage = (
77
77
 
78
78
  localImagePaths.push(`file://${imagePath}`);
79
79
  } catch (imageError) {
80
- console.error(
81
- `[Share] Failed to download image ${i} (${imageUrl}):`,
82
- imageError
80
+ const errorObj =
81
+ imageError instanceof Error
82
+ ? imageError
83
+ : new Error(String(imageError));
84
+ notifyError?.(
85
+ {
86
+ errorClass: 'ChatbotAI',
87
+ errorMessage: errorObj.message,
88
+ name: 'ShareImageDownloadError',
89
+ message: `[Share] Failed to download image ${i} (${imageUrl}): ${errorObj.message}`,
90
+ stack: errorObj?.stack,
91
+ },
92
+ 'error'
83
93
  );
84
94
  // Continue with other images
85
95
  }
@@ -125,7 +135,18 @@ export const useShareMessage = (
125
135
  setShareState(messageId, ShareState.idle);
126
136
  }
127
137
  } catch (error: any) {
128
- console.error('Share error:', error);
138
+ const errorObj =
139
+ error instanceof Error ? error : new Error(String(error));
140
+ notifyError?.(
141
+ {
142
+ errorClass: 'ChatbotAI',
143
+ errorMessage: errorObj.message,
144
+ name: 'ShareError',
145
+ message: `Share error: ${errorObj.message}`,
146
+ stack: errorObj?.stack,
147
+ },
148
+ 'error'
149
+ );
129
150
 
130
151
  // User cancelled share dialog
131
152
  if (error?.message?.includes('User did not share')) {
@@ -136,7 +157,14 @@ export const useShareMessage = (
136
157
  setShareState(messageId, ShareState.error);
137
158
  UIUtils.toast.showError('Không thể chia sẻ. Vui lòng thử lại.');
138
159
  }
139
- }, [messageId, messageContent, productImages, setShareState, logGA]);
160
+ }, [
161
+ messageId,
162
+ messageContent,
163
+ productImages,
164
+ setShareState,
165
+ logGA,
166
+ notifyError,
167
+ ]);
140
168
 
141
169
  return {
142
170
  shareState: currentState,
@@ -12,6 +12,8 @@ import {
12
12
  } from '../../utils/device';
13
13
  import { GAEvents } from '../../constants/events';
14
14
  import useSessionStore from '../../store/session';
15
+ import cloneDeep from 'lodash/cloneDeep';
16
+ import debounce from 'lodash/debounce';
15
17
 
16
18
  export interface ImageUpload extends Image {
17
19
  streamId?: string;
@@ -190,13 +192,27 @@ export const useUploadItem = ({ logGA }: Props) => {
190
192
  }
191
193
  }, []);
192
194
 
193
- const handleRemoveUploadItem = useCallback((item: UploadItem) => {
194
- if (item.uploadType === 'image') {
195
- setImageUpload((prev) => prev.filter((i) => i?.path !== item?.path));
196
- } else {
197
- setDocumentUpload((prev) => prev.filter((i) => i?.uri !== item?.uri));
198
- }
199
- }, []);
195
+ // eslint-disable-next-line react-hooks/exhaustive-deps
196
+ const handleRemoveUploadItem = useCallback(
197
+ debounce(
198
+ (item: UploadItem) => {
199
+ if (item.uploadType === 'image') {
200
+ setImageUpload((prev) => {
201
+ const mImage = cloneDeep(prev);
202
+ return mImage.filter((i) => i?.path !== item?.path);
203
+ });
204
+ } else {
205
+ setDocumentUpload((prev) => {
206
+ const mDocument = cloneDeep(prev);
207
+ return mDocument.filter((i) => i?.uri !== item?.uri);
208
+ });
209
+ }
210
+ },
211
+ 200,
212
+ { leading: false, trailing: true }
213
+ ),
214
+ []
215
+ );
200
216
 
201
217
  const clearFileUpload = useCallback(() => {
202
218
  setImageUpload([]);
package/src/types/chat.ts CHANGED
@@ -16,6 +16,7 @@ export interface ChatContextType {
16
16
  closeDrawer: () => void;
17
17
  chatbotUrl: string;
18
18
  logGA: (event: string, params?: any) => void;
19
+ notifyError?: ErrorLogger;
19
20
  }
20
21
 
21
22
  export interface ChatProviderProps {
@@ -30,8 +31,20 @@ export interface ChatProviderProps {
30
31
  pushLinkTo?: (url: string, resParams?: any) => void;
31
32
  chatbotUrl: string;
32
33
  logGA: (event: string, params?: any) => void;
34
+ notifyError?: ErrorLogger;
33
35
  }
34
36
 
37
+ export type ErrorLogger = (
38
+ p: {
39
+ errorClass: string | undefined;
40
+ errorMessage: string | undefined;
41
+ name: string;
42
+ message: string;
43
+ stack: string | undefined;
44
+ },
45
+ evt?: 'error' | 'info' | 'warning' | undefined
46
+ ) => void;
47
+
35
48
  export enum SessionLogType {
36
49
  accessed = 'accessed',
37
50
  newChatBtn = 'new_chat_btn',