react-native-chatbot-ai 0.1.19 → 0.1.22

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 (263) hide show
  1. package/lib/module/components/Drawer/DeleteSessionPopup.js +113 -0
  2. package/lib/module/components/Drawer/DeleteSessionPopup.js.map +1 -0
  3. package/lib/module/components/Drawer/DrawerContent.js +21 -8
  4. package/lib/module/components/Drawer/DrawerContent.js.map +1 -1
  5. package/lib/module/components/Drawer/RenameSessionPopup.js +121 -0
  6. package/lib/module/components/Drawer/RenameSessionPopup.js.map +1 -0
  7. package/lib/module/components/Drawer/SearchInput.js +10 -3
  8. package/lib/module/components/Drawer/SearchInput.js.map +1 -1
  9. package/lib/module/components/Drawer/SessionItem.js +36 -20
  10. package/lib/module/components/Drawer/SessionItem.js.map +1 -1
  11. package/lib/module/components/Drawer/SessionList.js +2 -5
  12. package/lib/module/components/Drawer/SessionList.js.map +1 -1
  13. package/lib/module/components/Drawer/SessionOptionsBottomSheet.js +129 -0
  14. package/lib/module/components/Drawer/SessionOptionsBottomSheet.js.map +1 -0
  15. package/lib/module/components/Drawer/ShareSessionPopup.js +132 -0
  16. package/lib/module/components/Drawer/ShareSessionPopup.js.map +1 -0
  17. package/lib/module/components/chat/ChatEmpty.js +15 -4
  18. package/lib/module/components/chat/ChatEmpty.js.map +1 -1
  19. package/lib/module/components/chat/ChatHeader.js +10 -4
  20. package/lib/module/components/chat/ChatHeader.js.map +1 -1
  21. package/lib/module/components/chat/ChatMessageList.js +75 -24
  22. package/lib/module/components/chat/ChatMessageList.js.map +1 -1
  23. package/lib/module/components/chat/SuggestionItem.js +2 -1
  24. package/lib/module/components/chat/SuggestionItem.js.map +1 -1
  25. package/lib/module/components/chat/footer/index.js +77 -15
  26. package/lib/module/components/chat/footer/index.js.map +1 -1
  27. package/lib/module/components/chat/footer/item/UploadImageItem.js +21 -1
  28. package/lib/module/components/chat/footer/item/UploadImageItem.js.map +1 -1
  29. package/lib/module/components/chat/index.js +7 -6
  30. package/lib/module/components/chat/index.js.map +1 -1
  31. package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js +36 -5
  32. package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js.map +1 -1
  33. package/lib/module/components/chat/item/DeeplinkItem.js +30 -2
  34. package/lib/module/components/chat/item/DeeplinkItem.js.map +1 -1
  35. package/lib/module/components/chat/item/MessageActionsBar.js +243 -0
  36. package/lib/module/components/chat/item/MessageActionsBar.js.map +1 -0
  37. package/lib/module/components/chat/item/actions/ActionButton.js +56 -0
  38. package/lib/module/components/chat/item/actions/ActionButton.js.map +1 -0
  39. package/lib/module/components/portal/BottomSheet.js +245 -0
  40. package/lib/module/components/portal/BottomSheet.js.map +1 -0
  41. package/lib/module/components/portal/Popup.js +278 -0
  42. package/lib/module/components/portal/Popup.js.map +1 -0
  43. package/lib/module/components/portal/index.js +11 -5
  44. package/lib/module/components/portal/index.js.map +1 -1
  45. package/lib/module/components/product/CardHorizontal.js +44 -7
  46. package/lib/module/components/product/CardHorizontal.js.map +1 -1
  47. package/lib/module/constants/events.js +33 -0
  48. package/lib/module/constants/events.js.map +1 -1
  49. package/lib/module/constants/index.js +5 -1
  50. package/lib/module/constants/index.js.map +1 -1
  51. package/lib/module/constants/query.js +5 -1
  52. package/lib/module/constants/query.js.map +1 -1
  53. package/lib/module/context/ChatContext.js +9 -3
  54. package/lib/module/context/ChatContext.js.map +1 -1
  55. package/lib/module/hooks/message/useSendMessage.js +21 -3
  56. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  57. package/lib/module/hooks/message/useStreamMessage.js +10 -3
  58. package/lib/module/hooks/message/useStreamMessage.js.map +1 -1
  59. package/lib/module/hooks/messageActions/index.js +13 -0
  60. package/lib/module/hooks/messageActions/index.js.map +1 -0
  61. package/lib/module/hooks/messageActions/useAudioPlayer.js +269 -0
  62. package/lib/module/hooks/messageActions/useAudioPlayer.js.map +1 -0
  63. package/lib/module/hooks/messageActions/useCopyToClipboard.js +38 -0
  64. package/lib/module/hooks/messageActions/useCopyToClipboard.js.map +1 -0
  65. package/lib/module/hooks/messageActions/useFeedback.js +93 -0
  66. package/lib/module/hooks/messageActions/useFeedback.js.map +1 -0
  67. package/lib/module/hooks/messageActions/useSendFeedback.js +24 -0
  68. package/lib/module/hooks/messageActions/useSendFeedback.js.map +1 -0
  69. package/lib/module/hooks/messageActions/useShareMessage.js +128 -0
  70. package/lib/module/hooks/messageActions/useShareMessage.js.map +1 -0
  71. package/lib/module/hooks/session/useDeleteSession.js +23 -0
  72. package/lib/module/hooks/session/useDeleteSession.js.map +1 -0
  73. package/lib/module/hooks/session/useRenameSession.js +28 -0
  74. package/lib/module/hooks/session/useRenameSession.js.map +1 -0
  75. package/lib/module/hooks/session/useSearchSessions.js +5 -1
  76. package/lib/module/hooks/session/useSearchSessions.js.map +1 -1
  77. package/lib/module/hooks/session/useShareSession.js +16 -0
  78. package/lib/module/hooks/session/useShareSession.js.map +1 -0
  79. package/lib/module/hooks/upload/useImageUpload.js +2 -1
  80. package/lib/module/hooks/upload/useImageUpload.js.map +1 -1
  81. package/lib/module/services/endpoints.js +6 -1
  82. package/lib/module/services/endpoints.js.map +1 -1
  83. package/lib/module/services/index.js +1 -0
  84. package/lib/module/services/index.js.map +1 -1
  85. package/lib/module/services/playbackService.js +22 -0
  86. package/lib/module/services/playbackService.js.map +1 -0
  87. package/lib/module/store/audioPlayer.js +75 -0
  88. package/lib/module/store/audioPlayer.js.map +1 -0
  89. package/lib/module/store/messageActions.js +160 -0
  90. package/lib/module/store/messageActions.js.map +1 -0
  91. package/lib/module/store/session.js +6 -3
  92. package/lib/module/store/session.js.map +1 -1
  93. package/lib/module/translation/index.js +21 -25
  94. package/lib/module/translation/index.js.map +1 -1
  95. package/lib/module/translation/resources/i18n.js +39 -0
  96. package/lib/module/translation/resources/i18n.js.map +1 -0
  97. package/lib/module/types/chat.js +12 -1
  98. package/lib/module/types/chat.js.map +1 -1
  99. package/lib/module/types/index.js +1 -0
  100. package/lib/module/types/index.js.map +1 -1
  101. package/lib/module/types/messageActions.js +56 -0
  102. package/lib/module/types/messageActions.js.map +1 -0
  103. package/lib/module/utils/textCleaner.js +67 -0
  104. package/lib/module/utils/textCleaner.js.map +1 -0
  105. package/lib/module/utils/ui.js +27 -1
  106. package/lib/module/utils/ui.js.map +1 -1
  107. package/lib/typescript/src/components/Drawer/DeleteSessionPopup.d.ts +3 -0
  108. package/lib/typescript/src/components/Drawer/DeleteSessionPopup.d.ts.map +1 -0
  109. package/lib/typescript/src/components/Drawer/DrawerContent.d.ts.map +1 -1
  110. package/lib/typescript/src/components/Drawer/RenameSessionPopup.d.ts +3 -0
  111. package/lib/typescript/src/components/Drawer/RenameSessionPopup.d.ts.map +1 -0
  112. package/lib/typescript/src/components/Drawer/SearchInput.d.ts.map +1 -1
  113. package/lib/typescript/src/components/Drawer/SessionItem.d.ts.map +1 -1
  114. package/lib/typescript/src/components/Drawer/SessionList.d.ts.map +1 -1
  115. package/lib/typescript/src/components/Drawer/SessionOptionsBottomSheet.d.ts +3 -0
  116. package/lib/typescript/src/components/Drawer/SessionOptionsBottomSheet.d.ts.map +1 -0
  117. package/lib/typescript/src/components/Drawer/ShareSessionPopup.d.ts +3 -0
  118. package/lib/typescript/src/components/Drawer/ShareSessionPopup.d.ts.map +1 -0
  119. package/lib/typescript/src/components/chat/ChatEmpty.d.ts.map +1 -1
  120. package/lib/typescript/src/components/chat/ChatHeader.d.ts.map +1 -1
  121. package/lib/typescript/src/components/chat/ChatMessageList.d.ts.map +1 -1
  122. package/lib/typescript/src/components/chat/SuggestionItem.d.ts +3 -2
  123. package/lib/typescript/src/components/chat/SuggestionItem.d.ts.map +1 -1
  124. package/lib/typescript/src/components/chat/footer/index.d.ts.map +1 -1
  125. package/lib/typescript/src/components/chat/footer/item/UploadImageItem.d.ts.map +1 -1
  126. package/lib/typescript/src/components/chat/index.d.ts.map +1 -1
  127. package/lib/typescript/src/components/chat/item/ChatAIAnswerMessageItem.d.ts.map +1 -1
  128. package/lib/typescript/src/components/chat/item/DeeplinkItem.d.ts +2 -1
  129. package/lib/typescript/src/components/chat/item/DeeplinkItem.d.ts.map +1 -1
  130. package/lib/typescript/src/components/chat/item/MessageActionsBar.d.ts +16 -0
  131. package/lib/typescript/src/components/chat/item/MessageActionsBar.d.ts.map +1 -0
  132. package/lib/typescript/src/components/chat/item/actions/ActionButton.d.ts +19 -0
  133. package/lib/typescript/src/components/chat/item/actions/ActionButton.d.ts.map +1 -0
  134. package/lib/typescript/src/components/portal/BottomSheet.d.ts +8 -0
  135. package/lib/typescript/src/components/portal/BottomSheet.d.ts.map +1 -0
  136. package/lib/typescript/src/components/portal/Popup.d.ts +4 -0
  137. package/lib/typescript/src/components/portal/Popup.d.ts.map +1 -0
  138. package/lib/typescript/src/components/portal/index.d.ts.map +1 -1
  139. package/lib/typescript/src/components/product/CardHorizontal.d.ts +3 -2
  140. package/lib/typescript/src/components/product/CardHorizontal.d.ts.map +1 -1
  141. package/lib/typescript/src/constants/events.d.ts +33 -0
  142. package/lib/typescript/src/constants/events.d.ts.map +1 -1
  143. package/lib/typescript/src/constants/index.d.ts +5 -1
  144. package/lib/typescript/src/constants/index.d.ts.map +1 -1
  145. package/lib/typescript/src/constants/query.d.ts +4 -0
  146. package/lib/typescript/src/constants/query.d.ts.map +1 -1
  147. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
  148. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -2
  149. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  150. package/lib/typescript/src/hooks/message/useStreamMessage.d.ts.map +1 -1
  151. package/lib/typescript/src/hooks/messageActions/index.d.ts +10 -0
  152. package/lib/typescript/src/hooks/messageActions/index.d.ts.map +1 -0
  153. package/lib/typescript/src/hooks/messageActions/useAudioPlayer.d.ts +14 -0
  154. package/lib/typescript/src/hooks/messageActions/useAudioPlayer.d.ts.map +1 -0
  155. package/lib/typescript/src/hooks/messageActions/useCopyToClipboard.d.ts +9 -0
  156. package/lib/typescript/src/hooks/messageActions/useCopyToClipboard.d.ts.map +1 -0
  157. package/lib/typescript/src/hooks/messageActions/useFeedback.d.ts +18 -0
  158. package/lib/typescript/src/hooks/messageActions/useFeedback.d.ts.map +1 -0
  159. package/lib/typescript/src/hooks/messageActions/useSendFeedback.d.ts +6 -0
  160. package/lib/typescript/src/hooks/messageActions/useSendFeedback.d.ts.map +1 -0
  161. package/lib/typescript/src/hooks/messageActions/useShareMessage.d.ts +12 -0
  162. package/lib/typescript/src/hooks/messageActions/useShareMessage.d.ts.map +1 -0
  163. package/lib/typescript/src/hooks/session/useDeleteSession.d.ts +2 -0
  164. package/lib/typescript/src/hooks/session/useDeleteSession.d.ts.map +1 -0
  165. package/lib/typescript/src/hooks/session/useRenameSession.d.ts +2 -0
  166. package/lib/typescript/src/hooks/session/useRenameSession.d.ts.map +1 -0
  167. package/lib/typescript/src/hooks/session/useSearchSessions.d.ts +1 -1
  168. package/lib/typescript/src/hooks/session/useSearchSessions.d.ts.map +1 -1
  169. package/lib/typescript/src/hooks/session/useShareSession.d.ts +2 -0
  170. package/lib/typescript/src/hooks/session/useShareSession.d.ts.map +1 -0
  171. package/lib/typescript/src/hooks/upload/useImageUpload.d.ts +1 -1
  172. package/lib/typescript/src/hooks/upload/useImageUpload.d.ts.map +1 -1
  173. package/lib/typescript/src/services/endpoints.d.ts +5 -0
  174. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  175. package/lib/typescript/src/services/index.d.ts +1 -0
  176. package/lib/typescript/src/services/index.d.ts.map +1 -1
  177. package/lib/typescript/src/services/playbackService.d.ts +2 -0
  178. package/lib/typescript/src/services/playbackService.d.ts.map +1 -0
  179. package/lib/typescript/src/store/audioPlayer.d.ts +27 -0
  180. package/lib/typescript/src/store/audioPlayer.d.ts.map +1 -0
  181. package/lib/typescript/src/store/messageActions.d.ts +9 -0
  182. package/lib/typescript/src/store/messageActions.d.ts.map +1 -0
  183. package/lib/typescript/src/store/session.d.ts.map +1 -1
  184. package/lib/typescript/src/translation/index.d.ts +3 -4
  185. package/lib/typescript/src/translation/index.d.ts.map +1 -1
  186. package/lib/typescript/src/translation/resources/i18n.d.ts +5 -0
  187. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -0
  188. package/lib/typescript/src/types/chat.d.ts +16 -1
  189. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  190. package/lib/typescript/src/types/dto.d.ts +11 -0
  191. package/lib/typescript/src/types/dto.d.ts.map +1 -1
  192. package/lib/typescript/src/types/index.d.ts +1 -0
  193. package/lib/typescript/src/types/index.d.ts.map +1 -1
  194. package/lib/typescript/src/types/messageActions.d.ts +85 -0
  195. package/lib/typescript/src/types/messageActions.d.ts.map +1 -0
  196. package/lib/typescript/src/types/ui.d.ts +16 -1
  197. package/lib/typescript/src/types/ui.d.ts.map +1 -1
  198. package/lib/typescript/src/utils/textCleaner.d.ts +30 -0
  199. package/lib/typescript/src/utils/textCleaner.d.ts.map +1 -0
  200. package/lib/typescript/src/utils/ui.d.ts +12 -1
  201. package/lib/typescript/src/utils/ui.d.ts.map +1 -1
  202. package/package.json +6 -6
  203. package/src/components/Drawer/DeleteSessionPopup.tsx +121 -0
  204. package/src/components/Drawer/DrawerContent.tsx +23 -7
  205. package/src/components/Drawer/RenameSessionPopup.tsx +145 -0
  206. package/src/components/Drawer/SearchInput.tsx +11 -2
  207. package/src/components/Drawer/SessionItem.tsx +22 -8
  208. package/src/components/Drawer/SessionList.tsx +0 -2
  209. package/src/components/Drawer/SessionOptionsBottomSheet.tsx +138 -0
  210. package/src/components/Drawer/ShareSessionPopup.tsx +145 -0
  211. package/src/components/chat/ChatEmpty.tsx +15 -5
  212. package/src/components/chat/ChatHeader.tsx +9 -4
  213. package/src/components/chat/ChatMessageList.tsx +78 -18
  214. package/src/components/chat/SuggestionItem.tsx +4 -3
  215. package/src/components/chat/footer/index.tsx +95 -14
  216. package/src/components/chat/footer/item/UploadImageItem.tsx +21 -1
  217. package/src/components/chat/index.tsx +8 -11
  218. package/src/components/chat/item/ChatAIAnswerMessageItem.tsx +55 -6
  219. package/src/components/chat/item/DeeplinkItem.tsx +30 -2
  220. package/src/components/chat/item/MessageActionsBar.tsx +326 -0
  221. package/src/components/chat/item/actions/ActionButton.tsx +65 -0
  222. package/src/components/portal/BottomSheet.tsx +307 -0
  223. package/src/components/portal/Popup.tsx +345 -0
  224. package/src/components/portal/index.tsx +5 -1
  225. package/src/components/product/CardHorizontal.tsx +45 -10
  226. package/src/constants/events.ts +34 -0
  227. package/src/constants/index.ts +6 -1
  228. package/src/constants/query.ts +4 -0
  229. package/src/context/ChatContext.tsx +6 -0
  230. package/src/hooks/message/useSendMessage.ts +47 -4
  231. package/src/hooks/message/useStreamMessage.ts +15 -4
  232. package/src/hooks/messageActions/index.ts +10 -0
  233. package/src/hooks/messageActions/useAudioPlayer.ts +346 -0
  234. package/src/hooks/messageActions/useCopyToClipboard.ts +38 -0
  235. package/src/hooks/messageActions/useFeedback.ts +114 -0
  236. package/src/hooks/messageActions/useSendFeedback.ts +31 -0
  237. package/src/hooks/messageActions/useShareMessage.ts +146 -0
  238. package/src/hooks/session/useDeleteSession.ts +25 -0
  239. package/src/hooks/session/useRenameSession.ts +37 -0
  240. package/src/hooks/session/useSearchSessions.ts +6 -1
  241. package/src/hooks/session/useShareSession.ts +22 -0
  242. package/src/hooks/upload/useImageUpload.ts +6 -2
  243. package/src/ignore.d.ts +20 -1
  244. package/src/services/endpoints.ts +10 -0
  245. package/src/services/index.ts +1 -0
  246. package/src/services/playbackService.ts +22 -0
  247. package/src/store/audioPlayer.ts +112 -0
  248. package/src/store/messageActions.ts +161 -0
  249. package/src/store/session.ts +4 -2
  250. package/src/translation/index.ts +27 -19
  251. package/src/translation/resources/i18n.ts +45 -0
  252. package/src/types/chat.ts +21 -1
  253. package/src/types/dto.ts +14 -0
  254. package/src/types/index.ts +1 -0
  255. package/src/types/messageActions.ts +131 -0
  256. package/src/types/ui.ts +19 -1
  257. package/src/utils/textCleaner.ts +65 -0
  258. package/src/utils/ui.tsx +29 -2
  259. package/lib/module/translation/resources/vi.js +0 -12
  260. package/lib/module/translation/resources/vi.js.map +0 -1
  261. package/lib/typescript/src/translation/resources/vi.d.ts +0 -11
  262. package/lib/typescript/src/translation/resources/vi.d.ts.map +0 -1
  263. package/src/translation/resources/vi.ts +0 -10
@@ -0,0 +1,38 @@
1
+ /**
2
+ * useCopyToClipboard Hook
3
+ * Custom hook for copying text to clipboard with markdown cleanup
4
+ */
5
+
6
+ import { useCallback, useState } from 'react';
7
+ import Clipboard from '@react-native-clipboard/clipboard';
8
+ import UIUtils from '../../utils/ui';
9
+ import { cleanMarkdown } from '../../utils/textCleaner';
10
+
11
+ export const useCopyToClipboard = () => {
12
+ 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 có 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.showSuccess('Đã 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
+ }, []);
36
+
37
+ return { copyToClipboard, isCopying };
38
+ };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * useFeedback Hook
3
+ * Custom hook for handling Like/Dislike feedback logic
4
+ */
5
+
6
+ import { useCallback } from 'react';
7
+ import useMessageActionsStore from '../../store/messageActions';
8
+ import { FeedbackType } from '../../types/messageActions';
9
+ import { useSendFeedback } from './useSendFeedback';
10
+ import UIUtils from '../../utils/ui';
11
+ import type { IMessageItem } from '../../types/dto';
12
+ import useSessionStore from '../../store/session';
13
+ import { useChatContext } from '../../context/ChatContext';
14
+ import { GAEvents } from 'src/constants/events';
15
+
16
+ export const useFeedback = (messageId: string, message?: IMessageItem) => {
17
+ const logGA = useChatContext().logGA;
18
+ const sessionId = useSessionStore((state) => state.sessionId);
19
+
20
+ const feedback = useMessageActionsStore((state) =>
21
+ state.getFeedback(messageId)
22
+ );
23
+ const setFeedback = useMessageActionsStore((state) => state.setFeedback);
24
+ const activeFeedbackList = useMessageActionsStore(
25
+ (state) => state.activeFeedbackList
26
+ );
27
+ const setActiveFeedbackList = useMessageActionsStore(
28
+ (state) => state.setActiveFeedbackList
29
+ );
30
+
31
+ const { mutateAsync: sendFeedback, isLoading } = useSendFeedback();
32
+
33
+ // Check feedback from server (message) or local store
34
+ const serverFeedbackType = message?.feedback_type;
35
+ const serverFeedbackContent = message?.feedback_content;
36
+
37
+ const effectiveFeedback =
38
+ feedback ||
39
+ (serverFeedbackType && serverFeedbackContent
40
+ ? {
41
+ messageId,
42
+ feedbackType: serverFeedbackType as FeedbackType,
43
+ reason: serverFeedbackContent,
44
+ timestamp: message.modified_at,
45
+ }
46
+ : null);
47
+
48
+ const isLikeActive = effectiveFeedback?.feedbackType === FeedbackType.like;
49
+ const isDislikeActive =
50
+ effectiveFeedback?.feedbackType === FeedbackType.dislike;
51
+ const isLikeListOpen = activeFeedbackList === `${messageId}-like`;
52
+ const isDislikeListOpen = activeFeedbackList === `${messageId}-dislike`;
53
+
54
+ const toggleLikeList = useCallback(() => {
55
+ if (isLikeActive) return; // Already selected, cannot change
56
+ setActiveFeedbackList(isLikeListOpen ? null : `${messageId}-like`);
57
+ }, [messageId, isLikeActive, isLikeListOpen, setActiveFeedbackList]);
58
+
59
+ const toggleDislikeList = useCallback(() => {
60
+ if (isDislikeActive) return; // Already selected, cannot change
61
+ setActiveFeedbackList(isDislikeListOpen ? null : `${messageId}-dislike`);
62
+ }, [messageId, isDislikeActive, isDislikeListOpen, setActiveFeedbackList]);
63
+
64
+ const selectReason = useCallback(
65
+ async (type: FeedbackType, reason: string) => {
66
+ console.log('selectReason', sessionId);
67
+ if (!sessionId) {
68
+ console.error('Session ID is required to send feedback');
69
+ UIUtils.toast.open({
70
+ title: 'Không thể gửi phản hồi. Vui lòng thử lại.',
71
+ });
72
+ return;
73
+ }
74
+
75
+ try {
76
+ // Send to backend FIRST (no optimistic update)
77
+ await sendFeedback({
78
+ sessionId,
79
+ payload: {
80
+ message_id: messageId,
81
+ feedback_type: type,
82
+ feedback_content: reason,
83
+ },
84
+ });
85
+
86
+ logGA(GAEvents.chatDetailLikeDislikeBtnTap, {
87
+ fb_type: type,
88
+ message_id: messageId,
89
+ });
90
+
91
+ // Update local state AFTER successful API call
92
+ setFeedback(messageId, type, reason);
93
+
94
+ UIUtils.toast.showSuccess('Cảm ơn phản hồi của bạn!');
95
+ } catch (error) {
96
+ console.error('Failed to send feedback:', error);
97
+ UIUtils.toast.showError(error);
98
+ }
99
+ },
100
+ [messageId, setFeedback, sendFeedback, sessionId, logGA]
101
+ );
102
+
103
+ return {
104
+ feedback: effectiveFeedback,
105
+ isLikeActive,
106
+ isDislikeActive,
107
+ isLikeListOpen,
108
+ isDislikeListOpen,
109
+ isLoading,
110
+ toggleLikeList,
111
+ toggleDislikeList,
112
+ selectReason,
113
+ };
114
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * useSendFeedback Hook
3
+ * React Query mutation hook for sending feedback to backend
4
+ */
5
+
6
+ import { useMutation } from '@tanstack/react-query';
7
+ import { apiInstance } from '../../services/apis';
8
+ import { ENDPOINTS } from '../../services/endpoints';
9
+ import { SendFeedbackRequest, SendFeedbackResponse } from '../../types/dto';
10
+ import { QUERY_KEYS } from '../../constants/query';
11
+ import { BaseResponse } from 'src/types';
12
+
13
+ interface SendFeedbackParams {
14
+ sessionId: string;
15
+ payload: SendFeedbackRequest;
16
+ }
17
+
18
+ export const useSendFeedback = () =>
19
+ useMutation({
20
+ mutationKey: [QUERY_KEYS.SEND_FEEDBACK],
21
+ mutationFn: async ({ sessionId, payload }: SendFeedbackParams) => {
22
+ const res = await apiInstance?.put<BaseResponse<SendFeedbackResponse>>(
23
+ ENDPOINTS.assistantService.sendFeedback(sessionId),
24
+ payload
25
+ );
26
+ return res?.data?.data;
27
+ },
28
+ retry: 2,
29
+ retryDelay: (attemptIndex: number) =>
30
+ Math.min(1000 * 2 ** attemptIndex, 30000),
31
+ });
@@ -0,0 +1,146 @@
1
+ /**
2
+ * useShareMessage Hook
3
+ * Custom hook for sharing message content via native share sheet
4
+ * Supports platform-specific sharing (Zalo, Facebook, Messenger)
5
+ */
6
+
7
+ import { useCallback } from 'react';
8
+ import Share from 'react-native-share';
9
+ import RNFetchBlob from 'react-native-blob-util';
10
+ import useMessageActionsStore from '../../store/messageActions';
11
+ import { ShareState } from '../../types/messageActions';
12
+ import UIUtils from '../../utils/ui';
13
+ import { cleanMarkdown, truncateText } from '../../utils/textCleaner';
14
+ import { useChatContext } from '../../context/ChatContext';
15
+ import { GAEvents } from '../../constants/events';
16
+ import useSessionStore from '../../store/session';
17
+
18
+ const MAX_SHARE_TEXT_LENGTH = 3000;
19
+
20
+ export const useShareMessage = (
21
+ messageId: string,
22
+ messageContent: string,
23
+ productImages?: string[]
24
+ ) => {
25
+ const logGA = useChatContext().logGA;
26
+ const shareState = useMessageActionsStore((state) =>
27
+ state.getShareState(messageId)
28
+ );
29
+ const setShareState = useMessageActionsStore((state) => state.setShareState);
30
+
31
+ const currentState = shareState?.state || ShareState.idle;
32
+
33
+ const shareMessage = useCallback(async () => {
34
+ if (!messageContent || messageContent.trim().length === 0) {
35
+ UIUtils.toast.showError('Không có nội dung để chia sẻ');
36
+ return;
37
+ }
38
+
39
+ try {
40
+ setShareState(messageId, ShareState.loading);
41
+
42
+ // Clean and truncate text
43
+ const cleanText = cleanMarkdown(messageContent);
44
+ const truncatedText = truncateText(cleanText, MAX_SHARE_TEXT_LENGTH);
45
+
46
+ const localImagePaths: string[] = [];
47
+
48
+ // Download all images to local cache if available
49
+ if (productImages && productImages.length > 0) {
50
+ for (let i = 0; i < productImages.length; i++) {
51
+ const imageUrl = productImages[i];
52
+ if (!imageUrl || imageUrl.trim() === '') {
53
+ console.warn(`[Share] Skipping empty image URL at index ${i}`);
54
+ continue;
55
+ }
56
+
57
+ try {
58
+ // Use unique timestamp for each image to avoid conflicts
59
+ const timestamp = Date.now() + i;
60
+ const imagePath = `${RNFetchBlob.fs.dirs.CacheDir}/share_image_${timestamp}.jpg`;
61
+
62
+ await RNFetchBlob.config({
63
+ path: imagePath,
64
+ }).fetch('GET', imageUrl);
65
+
66
+ // Verify file exists and has content
67
+ const fileExists = await RNFetchBlob.fs.exists(imagePath);
68
+ if (!fileExists) {
69
+ continue;
70
+ }
71
+
72
+ const fileInfo = await RNFetchBlob.fs.stat(imagePath);
73
+ if (fileInfo.size === 0) {
74
+ await RNFetchBlob.fs.unlink(imagePath);
75
+ continue;
76
+ }
77
+
78
+ localImagePaths.push(`file://${imagePath}`);
79
+ } catch (imageError) {
80
+ console.error(
81
+ `[Share] Failed to download image ${i} (${imageUrl}):`,
82
+ imageError
83
+ );
84
+ // Continue with other images
85
+ }
86
+ }
87
+ }
88
+
89
+ // Prepare share options
90
+ const shareOptions: any = {
91
+ title: 'Chia sẻ từ Chatbot AI',
92
+ message: truncatedText,
93
+ failOnCancel: false,
94
+ };
95
+
96
+ // Add image URLs if available
97
+ if (localImagePaths.length === 1) {
98
+ shareOptions.url = localImagePaths[0];
99
+ } else if (localImagePaths.length > 1) {
100
+ shareOptions.urls = localImagePaths;
101
+ }
102
+
103
+ // Open share dialog
104
+ // Platform-specific sharing:
105
+ const result = await Share.open(shareOptions);
106
+
107
+ // Handle success
108
+ if (result) {
109
+ setShareState(messageId, ShareState.success);
110
+
111
+ // Clean up temporary image files
112
+ for (const localImagePath of localImagePaths) {
113
+ try {
114
+ const cleanPath = localImagePath.replace('file://', '');
115
+ await RNFetchBlob.fs.unlink(cleanPath);
116
+ } catch (cleanupError) {
117
+ console.warn('[Share] Failed to cleanup temp image:', cleanupError);
118
+ }
119
+ }
120
+ logGA(GAEvents.chatDetailShareMessBtnTap, {
121
+ conversation_id: useSessionStore.getState().sessionId,
122
+ message_id: messageId,
123
+ });
124
+ } else {
125
+ setShareState(messageId, ShareState.idle);
126
+ }
127
+ } catch (error: any) {
128
+ console.error('Share error:', error);
129
+
130
+ // User cancelled share dialog
131
+ if (error?.message?.includes('User did not share')) {
132
+ setShareState(messageId, ShareState.idle);
133
+ return;
134
+ }
135
+
136
+ setShareState(messageId, ShareState.error);
137
+ UIUtils.toast.showError('Không thể chia sẻ. Vui lòng thử lại.');
138
+ }
139
+ }, [messageId, messageContent, productImages, setShareState, logGA]);
140
+
141
+ return {
142
+ shareState: currentState,
143
+ isLoading: currentState === ShareState.loading,
144
+ shareMessage,
145
+ };
146
+ };
@@ -0,0 +1,25 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import { QUERY_KEYS } from '../../constants/query';
3
+ import { apiInstance } from '../../services/apis';
4
+ import { ENDPOINTS } from '../../services/endpoints';
5
+ import { BaseResponse } from '../../types/common';
6
+
7
+ export const useDeleteSession = () => {
8
+ const queryClient = useQueryClient();
9
+
10
+ return useMutation({
11
+ mutationKey: [QUERY_KEYS.DELETE_SESSION],
12
+ mutationFn: async (sessionId: string) => {
13
+ const res = await apiInstance?.delete<BaseResponse<any>>(
14
+ ENDPOINTS.assistantService.deleteSession(sessionId)
15
+ );
16
+ return res?.data;
17
+ },
18
+ onSuccess: () => {
19
+ // Refetch sessions list after successful delete
20
+ queryClient.refetchQueries({
21
+ queryKey: [QUERY_KEYS.SEARCH_SESSIONS],
22
+ });
23
+ },
24
+ });
25
+ };
@@ -0,0 +1,37 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import { QUERY_KEYS } from '../../constants/query';
3
+ import { apiInstance } from '../../services/apis';
4
+ import { ENDPOINTS } from '../../services/endpoints';
5
+ import { BaseResponse } from '../../types/common';
6
+ import { SessionSearchItem } from '../../types/dto';
7
+
8
+ interface RenameSessionRequest {
9
+ title: string;
10
+ }
11
+
12
+ export const useRenameSession = () => {
13
+ const queryClient = useQueryClient();
14
+
15
+ return useMutation({
16
+ mutationKey: [QUERY_KEYS.UPDATE_SESSION],
17
+ mutationFn: async ({
18
+ sessionId,
19
+ title,
20
+ }: {
21
+ sessionId: string;
22
+ title: string;
23
+ }) => {
24
+ const res = await apiInstance?.put<BaseResponse<SessionSearchItem>>(
25
+ ENDPOINTS.assistantService.updateSession(sessionId),
26
+ { title } as RenameSessionRequest
27
+ );
28
+ return res?.data?.data;
29
+ },
30
+ onSuccess: () => {
31
+ // Refetch sessions list after successful rename
32
+ queryClient.refetchQueries({
33
+ queryKey: [QUERY_KEYS.SEARCH_SESSIONS],
34
+ });
35
+ },
36
+ });
37
+ };
@@ -10,7 +10,8 @@ const LIMIT = 20;
10
10
 
11
11
  export const useSearchSessions = (
12
12
  enabled: boolean = true,
13
- searchTerm?: string
13
+ searchTerm?: string,
14
+ cb?: (isSuccess?: boolean) => void
14
15
  ) => {
15
16
  const queryKey = [QUERY_KEYS.SEARCH_SESSIONS, searchTerm];
16
17
 
@@ -30,11 +31,13 @@ export const useSearchSessions = (
30
31
 
31
32
  // Validate response structure
32
33
  if (!response) {
34
+ cb?.(false);
33
35
  throw new Error('No response received from API');
34
36
  }
35
37
 
36
38
  // Check for successful response: HTTP status 200 and API statusCode 0
37
39
  if (res?.status !== 200 || response?.statusCode !== 0) {
40
+ cb?.(false);
38
41
  return {
39
42
  ...response,
40
43
  data: [],
@@ -44,12 +47,14 @@ export const useSearchSessions = (
44
47
  // Ensure data is an array
45
48
  if (!Array.isArray(response?.data)) {
46
49
  // If data is not an array, return empty array to prevent crashes
50
+ cb?.(false);
47
51
  return {
48
52
  ...response,
49
53
  data: [],
50
54
  } as BaseResponse<SearchSessionsResponse>;
51
55
  }
52
56
 
57
+ cb?.(true);
53
58
  return response as BaseResponse<SearchSessionsResponse>;
54
59
  },
55
60
  getNextPageParam: (
@@ -0,0 +1,22 @@
1
+ import { useMutation } from '@tanstack/react-query';
2
+ import { QUERY_KEYS } from '../../constants/query';
3
+ import { apiInstance } from '../../services/apis';
4
+ import { ENDPOINTS } from '../../services/endpoints';
5
+ import { BaseResponse } from '../../types/common';
6
+
7
+ interface ShareSessionResponse {
8
+ share_url: string;
9
+ }
10
+
11
+ export const useShareSession = () => {
12
+ return useMutation({
13
+ mutationKey: [QUERY_KEYS.SHARE_SESSION],
14
+ mutationFn: async (sessionId: string) => {
15
+ const res = await apiInstance?.put<BaseResponse<ShareSessionResponse>>(
16
+ ENDPOINTS.assistantService.shareSession(sessionId),
17
+ {}
18
+ );
19
+ return res?.data?.data;
20
+ },
21
+ });
22
+ };
@@ -16,7 +16,7 @@ type RNBlobTask = {
16
16
 
17
17
  interface UseImageUploadProps {
18
18
  file: ImageUpload;
19
- onSuccess?: (data: any) => void;
19
+ onSuccess?: (data: any, duration?: number) => void;
20
20
  onError?: (err: any) => void;
21
21
  }
22
22
 
@@ -70,6 +70,7 @@ export const useImageUpload = ({
70
70
 
71
71
  cancelRef.current = task;
72
72
 
73
+ const startUploadTime = Date.now();
73
74
  task
74
75
  .uploadProgress?.({ interval: 300 }, (written, total) => {
75
76
  if (isMounted && total > 0) {
@@ -87,7 +88,10 @@ export const useImageUpload = ({
87
88
  setIsUploading(false);
88
89
  setProgress(100);
89
90
  setIsSuccess(true);
90
- onSuccess?.(response?.data);
91
+ onSuccess?.(
92
+ response?.data,
93
+ (Date.now() - startUploadTime) / 1000
94
+ );
91
95
  })
92
96
  .catch((err: any) => {
93
97
  if (!isMounted) return;
package/src/ignore.d.ts CHANGED
@@ -1,3 +1,22 @@
1
1
  declare module '@droppii/libs/*';
2
- declare module '@droppii/libs';
2
+ declare module '@droppii/libs' {
3
+ export type KBottomSheetProps = any;
4
+ export type KPopupProps = any;
5
+ export type KToastBarProps = any;
6
+
7
+ export const KColors: any;
8
+ export const KSpacingValue: any;
9
+ export const KContainer: any;
10
+ export const KLabel: any;
11
+ export const KImage: any;
12
+ export const KButton: any;
13
+ export const KInput: any;
14
+ export const KRadiusValue: any;
15
+ export const KDivider: any;
16
+ export const KListItem: any;
17
+ export const KDims: any;
18
+ export const KBars: any;
19
+ export const KRating: any;
20
+ export const KPromotionTag: any;
21
+ }
3
22
  declare module '*';
@@ -7,6 +7,16 @@ export const ENDPOINTS = {
7
7
  `/assistant-service/v1/app/chat/sessions/${sessionId}/stream`,
8
8
  createSession: '/assistant-service/v1/app/chat/sessions',
9
9
  searchSessions: '/assistant-service/v1/app/chat/sessions/search',
10
+ updateSession: (sessionId: string) =>
11
+ `/assistant-service/v1/app/chat/sessions/${sessionId}`,
12
+ shareSession: (sessionId: string) =>
13
+ `/assistant-service/v1/app/chat/sessions/${sessionId}/share`,
14
+ deleteSession: (sessionId: string) =>
15
+ `/assistant-service/v1/app/chat/sessions/${sessionId}`,
16
+ sendFeedback: (sessionId: string) =>
17
+ `/assistant-service/v1/app/chat/sessions/${sessionId}/feedback`,
18
+ generateAudio: (sessionId: string, messageId: string) =>
19
+ `/assistant-service/v1/app/chat/sessions/${sessionId}/messages/${messageId}/speech`,
10
20
  },
11
21
  uploaderService: {
12
22
  upload: '/uploader-service/v1/uploader/permanently',
@@ -1 +1,2 @@
1
1
  export * from './apis';
2
+ export { PlaybackService } from './playbackService';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Playback Service for react-native-track-player
3
+ * Handles remote control events from lock screen, control center, and headphones
4
+ */
5
+ import TrackPlayer, { Event } from 'react-native-track-player';
6
+
7
+ export async function PlaybackService() {
8
+ TrackPlayer.addEventListener(Event.RemotePlay, async () => {
9
+ console.log('[PlaybackService] RemotePlay event');
10
+ await TrackPlayer.play();
11
+ });
12
+
13
+ TrackPlayer.addEventListener(Event.RemotePause, async () => {
14
+ console.log('[PlaybackService] RemotePause event');
15
+ await TrackPlayer.pause();
16
+ });
17
+
18
+ TrackPlayer.addEventListener(Event.RemoteStop, async () => {
19
+ console.log('[PlaybackService] RemoteStop event');
20
+ await TrackPlayer.stop();
21
+ });
22
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Audio Player Store
3
+ * Manages audio playback state across messages
4
+ * Handles play/pause/stop for each message and session cleanup
5
+ */
6
+
7
+ import { create } from 'zustand';
8
+
9
+ export interface AudioPlayerState {
10
+ // Currently playing message ID
11
+ currentMessageId: string | null;
12
+ // Current session ID
13
+ currentSessionId: string | null;
14
+ // Playing state
15
+ isPlaying: boolean;
16
+ // Loading state (fetching audio from server)
17
+ isLoading: boolean;
18
+ // Audio URL cache: messageId -> audioUrl
19
+ audioCache: Map<string, string>;
20
+ // Error state
21
+ error: string | null;
22
+ }
23
+
24
+ export interface AudioPlayerActions {
25
+ // Set currently playing message
26
+ setCurrentMessage: (messageId: string | null, sessionId: string) => void;
27
+ // Set playing state
28
+ setIsPlaying: (isPlaying: boolean) => void;
29
+ // Set loading state
30
+ setIsLoading: (isLoading: boolean) => void;
31
+ // Cache audio URL for a message
32
+ cacheAudioUrl: (messageId: string, audioUrl: string) => void;
33
+ // Get cached audio URL
34
+ getCachedAudioUrl: (messageId: string) => string | null;
35
+ // Set error
36
+ setError: (error: string | null) => void;
37
+ // Clear all state (when session changes)
38
+ clearSession: () => void;
39
+ // Stop current playback
40
+ stopPlayback: () => void;
41
+ }
42
+
43
+ export type AudioPlayerStore = AudioPlayerState & AudioPlayerActions;
44
+
45
+ const useAudioPlayerStore = create<AudioPlayerStore>((set, get) => ({
46
+ // Initial state
47
+ currentMessageId: null,
48
+ currentSessionId: null,
49
+ isPlaying: false,
50
+ isLoading: false,
51
+ audioCache: new Map(),
52
+ error: null,
53
+
54
+ // Actions
55
+ setCurrentMessage: (messageId, sessionId) => {
56
+ const state = get();
57
+
58
+ // If switching to a different session, clear the cache
59
+ if (state.currentSessionId && state.currentSessionId !== sessionId) {
60
+ set({
61
+ currentMessageId: messageId,
62
+ currentSessionId: sessionId,
63
+ audioCache: new Map(),
64
+ isPlaying: false,
65
+ error: null,
66
+ });
67
+ } else {
68
+ set({
69
+ currentMessageId: messageId,
70
+ currentSessionId: sessionId,
71
+ error: null,
72
+ });
73
+ }
74
+ },
75
+
76
+ setIsPlaying: (isPlaying) => set({ isPlaying }),
77
+
78
+ setIsLoading: (isLoading) => set({ isLoading }),
79
+
80
+ cacheAudioUrl: (messageId, audioUrl) => {
81
+ const state = get();
82
+ const newCache = new Map(state.audioCache);
83
+ newCache.set(messageId, audioUrl);
84
+ set({ audioCache: newCache });
85
+ },
86
+
87
+ getCachedAudioUrl: (messageId) => {
88
+ const state = get();
89
+ return state.audioCache.get(messageId) || null;
90
+ },
91
+
92
+ setError: (error) => set({ error }),
93
+
94
+ clearSession: () =>
95
+ set({
96
+ currentMessageId: null,
97
+ currentSessionId: null,
98
+ isPlaying: false,
99
+ isLoading: false,
100
+ audioCache: new Map(),
101
+ error: null,
102
+ }),
103
+
104
+ stopPlayback: () =>
105
+ set({
106
+ currentMessageId: null,
107
+ isPlaying: false,
108
+ error: null,
109
+ }),
110
+ }));
111
+
112
+ export default useAudioPlayerStore;