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
@@ -11,25 +11,29 @@ import {
11
11
  KPromotionTag,
12
12
  } from '@droppii/libs';
13
13
  import { PTManager } from '../../utils/prototype';
14
- import { useMemo } from 'react';
14
+ import { useCallback, useMemo } from 'react';
15
15
  import { StyleSheet } from 'react-native';
16
16
  import useProductsStore from '../../store/products';
17
17
  import { PRODUCT_STATUSES } from '../../types';
18
18
  import { useChatContext } from '../../context/ChatContext';
19
19
  import SkeletonPlaceholder from 'react-native-skeleton-placeholder';
20
+ import { GAEvents } from '../../constants/events';
21
+ import useSessionStore from '../../store/session';
20
22
 
21
23
  interface IProductHorizontalCardProps {
22
24
  productId?: string;
25
+ messageId?: string;
23
26
  }
24
27
 
25
- const ProducHorizontalCard = (props: IProductHorizontalCardProps) => {
26
- const { productId } = props;
28
+ const ProductHorizontalCard = (props: IProductHorizontalCardProps) => {
29
+ const { productId, messageId } = props;
27
30
  const item = useProductsStore((state) =>
28
31
  state.products.find((p) => p.id === productId)
29
32
  );
30
33
  const onAddToCart = useChatContext()?.onAddToCart;
31
34
  const onBuyNow = useChatContext()?.onBuyNow;
32
35
  const onNavigateToProduct = useChatContext()?.onNavigateToProduct;
36
+ const logGA = useChatContext()?.logGA;
33
37
 
34
38
  const {
35
39
  avgRating,
@@ -132,6 +136,40 @@ const ProducHorizontalCard = (props: IProductHorizontalCardProps) => {
132
136
  return !isSuspended && !isSoldOut && typeof onBuyNow === 'function';
133
137
  }, [isSuspended, isSoldOut, onBuyNow]);
134
138
 
139
+ const onPress = useCallback(() => {
140
+ if (!item) return;
141
+ onNavigateToProduct?.(item);
142
+ logGA(GAEvents.chatDetailProductCardTap, {
143
+ conversation_id: useSessionStore.getState().sessionId,
144
+ product_id: item.id,
145
+ position_in_list: -1, //TODO: fix
146
+ });
147
+ }, [onNavigateToProduct, logGA, item]);
148
+
149
+ const onPressAddToCart = useCallback(() => {
150
+ if (!item) return;
151
+ onAddToCart?.(item);
152
+ logGA(GAEvents.chatDetailAddToCartBtnTap, {
153
+ conversation_id: useSessionStore.getState().sessionId,
154
+ product_id: item.id,
155
+ position_in_list: -1, //TODO: fix
156
+ pdp_id: item.pdpId,
157
+ message_id: messageId,
158
+ });
159
+ }, [onAddToCart, logGA, item, messageId]);
160
+
161
+ const onPressBuyNow = useCallback(() => {
162
+ if (!item) return;
163
+ onBuyNow?.(item);
164
+ logGA(GAEvents.chatDetailBuyNowBtnTap, {
165
+ conversation_id: useSessionStore.getState().sessionId,
166
+ product_id: item.id,
167
+ position_in_list: -1, //TODO: fix
168
+ pdp_id: item.pdpId,
169
+ message_id: messageId,
170
+ });
171
+ }, [onBuyNow, logGA, item, messageId]);
172
+
135
173
  if (!item) {
136
174
  return (
137
175
  <KContainer.View style={styles.container}>
@@ -170,10 +208,7 @@ const ProducHorizontalCard = (props: IProductHorizontalCardProps) => {
170
208
  }
171
209
 
172
210
  return (
173
- <KContainer.Touchable
174
- style={styles.container}
175
- onPress={() => onNavigateToProduct?.(item)}
176
- >
211
+ <KContainer.Touchable style={styles.container} onPress={onPress}>
177
212
  <KContainer.View style={styles.image}>
178
213
  <KImage.Base
179
214
  uri={imageUrl}
@@ -294,7 +329,7 @@ const ProducHorizontalCard = (props: IProductHorizontalCardProps) => {
294
329
  </KLabel.Text>
295
330
  <KContainer.View style={styles.actionsRow}>
296
331
  <KButton.Base
297
- onPress={() => onAddToCart?.(item)}
332
+ onPress={onPressAddToCart}
298
333
  background={KColors.palette.primary.w25}
299
334
  tintColor={KColors.primary.normal}
300
335
  icon={{
@@ -305,7 +340,7 @@ const ProducHorizontalCard = (props: IProductHorizontalCardProps) => {
305
340
  disabled={!isEnabledAddToCart}
306
341
  />
307
342
  <KButton.Solid
308
- onPress={() => onBuyNow?.(item)}
343
+ onPress={onPressBuyNow}
309
344
  size="sm"
310
345
  label="Mua ngay"
311
346
  kind="primary"
@@ -318,7 +353,7 @@ const ProducHorizontalCard = (props: IProductHorizontalCardProps) => {
318
353
  );
319
354
  };
320
355
 
321
- export default ProducHorizontalCard;
356
+ export default ProductHorizontalCard;
322
357
 
323
358
  const styles = StyleSheet.create({
324
359
  abs: {
@@ -5,3 +5,37 @@ export const events = {
5
5
  expandThinkingStep: 'expand_thinking_step',
6
6
  updateMessageError: 'update_message_error',
7
7
  };
8
+
9
+ export const GAEvents = {
10
+ newChatPageView: 'NewChat_Page_View',
11
+ chatDetailPageView: 'ChatDetail_Page_View',
12
+ chatDetailSendBtnTap: 'ChatDetail_SendBtn_Tap',
13
+ chatDetailLikeDislikeBtnTap: 'ChatDetail_LikeDislikeBtn_Tap',
14
+ chatDetailCopyBtnTap: 'ChatDetail_CopyBtn_Tap',
15
+ chatDetailVoiceBtnTap: 'ChatDetail_VoiceBtn_Tap',
16
+ chatDetailExportPDFBtnTap: 'ChatDetail_ExportPDFBtn_Tap',
17
+ chatDetailShareMessBtnTap: 'ChatDetail_ShareMessBtn_Tap',
18
+ chatDetailStopChatBtnTap: 'ChatDetail_StopChatBtn_Tap',
19
+ chatDetailMultimodalTap: 'ChatDetail_Multimodal_Tap',
20
+ chatDetailMultimodalUpload: 'ChatDetail_Multimodal_Upload',
21
+ chatDetailMultimodalSend: 'ChatDetail_Multimodal_Send',
22
+ chatDetailProductCardTap: 'ChatDetail_ProductCard_Tap',
23
+ chatDetailAddToCartBtnTap: 'ChatDetail_AddToCartBtn_Tap',
24
+ chatDetailBuyNowBtnTap: 'ChatDetail_BuyNowBtn_Tap',
25
+ newChatCreateBtnTap: 'NewChat_CreateBtn_Tap',
26
+ chatDetailShoppingCartBtnTap: 'ChatDetail_ShoppingCartBtn_Tap',
27
+ chatDetailShareBtnTap: 'ChatDetail_ShareBtn_Tap',
28
+ shareChatModalCopyBtnTap: 'ShareChatModal_CopyBtn_Tap',
29
+ chatDetailRenameBtnTap: 'ChatDetail_RenameBtn_Tap',
30
+ renameModalSaveBtnTap: 'RenameModal_SaveBtn_Tap',
31
+ deleteChatModalDeleteBtnTap: 'DeleteChatModal_DeleteBtn_Tap',
32
+ sidebarSearchConversationTap: 'Sidebar_SearchConversation_Tap',
33
+ sidebarSearchConversationSend: 'Sidebar_SearchConversation_Send',
34
+ newChatNewChatPrmSuggTap: 'NewChat_NewChatPrmSugg_Tap',
35
+ chatDetailInConvPrmSuggTap: 'ChatDetail_InConvPrmSugg_Tap',
36
+ chatDetailInConvPrmSuggScroll: 'ChatDetail_InConvPrmSugg_Scroll',
37
+ chatDetailInConvPrmSuggReturn: 'ChatDetail_InConvPrmSugg_Return',
38
+ chatDetailLivechatBtnTap: 'ChatDetail_LivechatBtn_Tap',
39
+ chatDetailFAQTap: 'ChatDetail_FAQ_Tap',
40
+ chatDetailGiftDetailBtnTap: 'ChatDetail_GiftDetailBtn_Tap',
41
+ };
@@ -1,9 +1,14 @@
1
1
  import { createRef } from 'react';
2
- import { WithToastProps } from '../types';
2
+ import { WithBottomSheetProps, WithPopupProps, WithToastProps } from '../types';
3
3
 
4
4
  const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER + 100;
5
5
 
6
6
  export const toastRef = createRef<WithToastProps>();
7
+ export const bottomSheetRef = createRef<WithBottomSheetProps>();
8
+ export const popupRef = createRef<WithPopupProps>();
9
+
7
10
  export const Z_INDEX_PRIORITY = {
8
11
  toast: MAX_SAFE_INTEGER - 1,
12
+ bottomSheet: MAX_SAFE_INTEGER - 2,
13
+ popup: MAX_SAFE_INTEGER, // Highest priority - always on top
9
14
  };
@@ -4,4 +4,8 @@ export const QUERY_KEYS = {
4
4
  CREATE_SESSION: 'CREATE_SESSION',
5
5
  SEARCH_PRODUCT: 'SEARCH_PRODUCT',
6
6
  SEARCH_SESSIONS: 'SEARCH_SESSIONS',
7
+ UPDATE_SESSION: 'UPDATE_SESSION',
8
+ SHARE_SESSION: 'SHARE_SESSION',
9
+ DELETE_SESSION: 'DELETE_SESSION',
10
+ SEND_FEEDBACK: 'SEND_FEEDBACK',
7
11
  };
@@ -15,6 +15,8 @@ export const ChatContext = createContext<ChatContextType>({
15
15
  pushLinkTo: () => {},
16
16
  openDrawer: () => {},
17
17
  closeDrawer: () => {},
18
+ chatbotUrl: '',
19
+ logGA: () => {},
18
20
  });
19
21
 
20
22
  export const useChatContext = () => useContext(ChatContext);
@@ -38,6 +40,8 @@ export const ChatProvider = (props: ChatProviderProps) => {
38
40
  onNavigateToProduct,
39
41
  csTeamId,
40
42
  pushLinkTo,
43
+ chatbotUrl,
44
+ logGA,
41
45
  } = props;
42
46
 
43
47
  return (
@@ -54,6 +58,8 @@ export const ChatProvider = (props: ChatProviderProps) => {
54
58
  pushLinkTo,
55
59
  openDrawer,
56
60
  closeDrawer,
61
+ chatbotUrl,
62
+ logGA,
57
63
  }}
58
64
  >
59
65
  <ReanimatedDrawerExample ref={ref}>
@@ -2,21 +2,34 @@ import { useCallback } from 'react';
2
2
  import { useQueryClient } from '@tanstack/react-query';
3
3
  import useSessionStore from '../../store/session';
4
4
  import { useStreamMessage } from './useStreamMessage';
5
- import { events } from '../../constants/events';
5
+ import { events, GAEvents } from '../../constants/events';
6
6
  import { DeviceEventEmitter } from 'react-native';
7
- import { IAttachment, IMessageItem, MessageType, RoleType } from '../../types';
7
+ import {
8
+ IAttachment,
9
+ IMessageItem,
10
+ MessageType,
11
+ RoleType,
12
+ SendActionLogType,
13
+ } from '../../types';
8
14
  import dayjs from 'dayjs';
9
15
  import { useCreateSession } from '../session/useCreateSession';
10
16
  import { QUERY_KEYS } from '../../constants/query';
11
17
  import { BaseResponse } from '../../types/common';
12
18
  import { SearchSessionsResponse } from '../../types/dto';
19
+ import { useChatContext } from '../../context/ChatContext';
13
20
 
14
21
  export const useSendMessage = () => {
15
22
  const { startStream, stopStream, retryStream } = useStreamMessage();
16
23
  const { mutateAsync: createSession } = useCreateSession();
17
24
  const queryClient = useQueryClient();
25
+ const logGA = useChatContext().logGA;
26
+
18
27
  const onSendMessage = useCallback(
19
- async (message: string, attachments?: IAttachment[]) => {
28
+ async (
29
+ message: string,
30
+ attachments?: IAttachment[],
31
+ actionType?: SendActionLogType
32
+ ) => {
20
33
  const messageId = dayjs().valueOf().toString();
21
34
  const messageItem: IMessageItem = {
22
35
  id: messageId,
@@ -50,6 +63,36 @@ export const useSendMessage = () => {
50
63
  ) => index === 0,
51
64
  });
52
65
  }
66
+
67
+ logGA(GAEvents.chatDetailSendBtnTap, {
68
+ chat_type: useSessionStore.getState().sessionLogType,
69
+ conversation_id:
70
+ latestSessionId || useSessionStore.getState().sessionId,
71
+ action_type: actionType,
72
+ });
73
+
74
+ if (attachments?.length && attachments.length > 0) {
75
+ const totalFileCount = attachments.filter(
76
+ (i) => i.type === 'file'
77
+ ).length;
78
+ const totalImageCount = attachments.filter(
79
+ (i) => i.type === 'image'
80
+ ).length;
81
+ const fileType = [
82
+ totalFileCount > 0 ? 'file' : null,
83
+ totalImageCount > 0 ? 'image' : null,
84
+ ]
85
+ .filter(Boolean)
86
+ .join(', ');
87
+ logGA(GAEvents.chatDetailMultimodalTap, {
88
+ conversation_id:
89
+ latestSessionId || useSessionStore.getState().sessionId,
90
+ file_type: fileType,
91
+ total_image_count: totalImageCount,
92
+ total_file_count: totalFileCount,
93
+ });
94
+ }
95
+
53
96
  startStream(
54
97
  {
55
98
  content: message,
@@ -64,7 +107,7 @@ export const useSendMessage = () => {
64
107
  messageId
65
108
  );
66
109
  },
67
- [startStream, createSession, queryClient]
110
+ [startStream, createSession, queryClient, logGA]
68
111
  );
69
112
 
70
113
  return {
@@ -1,19 +1,23 @@
1
1
  import { useEffect, useRef, useCallback } from 'react';
2
2
  import { useChatContext } from '../../context/ChatContext';
3
3
  import { ENDPOINTS } from '../../services/endpoints';
4
- import { type IMessageItem, type StreamMessageRequest } from '../../types';
4
+ import {
5
+ SendActionLogType,
6
+ type IMessageItem,
7
+ type StreamMessageRequest,
8
+ } from '../../types';
5
9
  import EventSource from 'react-native-sse';
6
10
  import useStreamMessageStore from '../../store/streamMessage';
7
11
  import throttle from 'lodash/throttle';
8
12
  import { DeviceEventEmitter } from 'react-native';
9
- import { events } from '../../constants/events';
13
+ import { events, GAEvents } from '../../constants/events';
10
14
  import useSessionStore from '../../store/session';
11
15
 
12
16
  export const useStreamMessage = () => {
13
17
  const sessionId = useSessionStore((state) => state.sessionId);
14
18
  const esRef = useRef<EventSource | null>(null);
15
19
  const sessionIdRef = useRef<string | undefined>(sessionId);
16
- const { apiAddress } = useChatContext();
20
+ const { apiAddress, logGA } = useChatContext();
17
21
  const setStreamMessage = useStreamMessageStore(
18
22
  (state) => state.setStreamMessage
19
23
  );
@@ -158,6 +162,13 @@ export const useStreamMessage = () => {
158
162
  ...messageItem,
159
163
  metadata: {},
160
164
  });
165
+
166
+ logGA(GAEvents.chatDetailSendBtnTap, {
167
+ chat_type: useSessionStore.getState().sessionLogType,
168
+ conversation_id: sessionId,
169
+ action_type: SendActionLogType.retry,
170
+ });
171
+
161
172
  startStream(
162
173
  {
163
174
  content: messageItem.content,
@@ -172,7 +183,7 @@ export const useStreamMessage = () => {
172
183
  messageItem.id
173
184
  );
174
185
  },
175
- [startStream]
186
+ [startStream, logGA, sessionId]
176
187
  );
177
188
 
178
189
  useEffect(() => {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Message Actions Hooks
3
+ * Barrel export for all message action hooks
4
+ */
5
+
6
+ export { useFeedback } from './useFeedback';
7
+ export { useCopyToClipboard } from './useCopyToClipboard';
8
+ export { useShareMessage } from './useShareMessage';
9
+ export { useSendFeedback } from './useSendFeedback';
10
+ export { useAudioPlayer } from './useAudioPlayer';
@@ -0,0 +1,346 @@
1
+ /**
2
+ * useAudioPlayer Hook - react-native-track-player version
3
+ */
4
+ import { useCallback, useEffect } from 'react';
5
+ import TrackPlayer, {
6
+ Event,
7
+ Capability,
8
+ State,
9
+ } from 'react-native-track-player';
10
+
11
+ import ReactNativeBlobUtil from 'react-native-blob-util';
12
+ import { apiInstance } from '../../services/apis';
13
+ import { ENDPOINTS } from '../../services/endpoints';
14
+ import useAudioPlayerStore from '../../store/audioPlayer';
15
+ import useSessionStore from '../../store/session';
16
+ import { PlaybackService } from '../../services';
17
+ import { useChatContext } from '../../context/ChatContext';
18
+ import { GAEvents } from '../../constants/events';
19
+
20
+ interface UseAudioPlayerProps {
21
+ messageId: string;
22
+ }
23
+
24
+ interface UseAudioPlayerReturn {
25
+ isPlaying: boolean;
26
+ isPaused: boolean;
27
+ isLoading: boolean;
28
+ error: string | null;
29
+ togglePlayback: () => Promise<void>;
30
+ stopPlayback: () => void;
31
+ }
32
+
33
+ // Register playback service BEFORE setupPlayer
34
+ TrackPlayer.registerPlaybackService(() => PlaybackService);
35
+
36
+ // --- INIT PLAYER ONE TIME ---
37
+ let playerInitialized = false;
38
+ const setupPlayer = async () => {
39
+ if (playerInitialized) return;
40
+
41
+ try {
42
+ await TrackPlayer.setupPlayer();
43
+
44
+ await TrackPlayer.updateOptions({
45
+ capabilities: [Capability.Play, Capability.Pause, Capability.Stop],
46
+ compactCapabilities: [Capability.Play, Capability.Pause],
47
+ notificationCapabilities: [
48
+ Capability.Play,
49
+ Capability.Pause,
50
+ Capability.Stop,
51
+ ],
52
+ });
53
+ playerInitialized = true;
54
+ } catch (e) {
55
+ console.error('Error initializing TrackPlayer:', e);
56
+ }
57
+ };
58
+
59
+ export const useAudioPlayer = ({
60
+ messageId,
61
+ }: UseAudioPlayerProps): UseAudioPlayerReturn => {
62
+ const logGA = useChatContext().logGA;
63
+ const sessionId = useSessionStore().sessionId;
64
+ const {
65
+ currentMessageId,
66
+ currentSessionId,
67
+ isPlaying,
68
+ isLoading,
69
+ error,
70
+ setCurrentMessage,
71
+ setIsPlaying,
72
+ setIsLoading,
73
+ setError,
74
+ cacheAudioUrl,
75
+ getCachedAudioUrl,
76
+ stopPlayback: storeStopPlayback,
77
+ } = useAudioPlayerStore();
78
+
79
+ const isThisMessagePlaying = currentMessageId === messageId && isPlaying;
80
+ const isThisMessageLoading = currentMessageId === messageId && isLoading;
81
+ const isThisMessagePaused =
82
+ currentMessageId === messageId && !isPlaying && !isLoading;
83
+
84
+ // === FETCH AUDIO ===
85
+ const fetchAudio = useCallback(
86
+ async (sid: string, mid: string): Promise<string> => {
87
+ try {
88
+ const cachedPath = getCachedAudioUrl(mid);
89
+ if (cachedPath) {
90
+ const exists = await ReactNativeBlobUtil.fs.exists(cachedPath);
91
+ if (exists) return cachedPath;
92
+ }
93
+
94
+ const endpoint = ENDPOINTS.assistantService.generateAudio(sid, mid);
95
+ const baseURL = apiInstance?.defaults?.baseURL || '';
96
+ const fullUrl = `${baseURL}${endpoint}`;
97
+
98
+ const token = (global as any)?.accessToken;
99
+ if (!token) throw new Error('Missing access token');
100
+
101
+ const headers = {
102
+ 'Authorization': `Bearer ${token}`,
103
+ 'Accept': 'application/json',
104
+ 'Content-Type': 'application/json',
105
+ };
106
+
107
+ const dirs = ReactNativeBlobUtil.fs.dirs;
108
+ const audioDir = `${dirs.CacheDir}/chatbot_audio`;
109
+ const filePath = `${audioDir}/${mid}.mp3`;
110
+
111
+ const dirExists = await ReactNativeBlobUtil.fs.exists(audioDir);
112
+ if (!dirExists) await ReactNativeBlobUtil.fs.mkdir(audioDir);
113
+
114
+ const response = await ReactNativeBlobUtil.config({
115
+ path: filePath,
116
+ fileCache: true,
117
+ }).fetch('GET', fullUrl, headers);
118
+
119
+ if (response.info().status !== 200) {
120
+ const text = await response.text();
121
+ throw new Error(`Server error: ${response.info().status} - ${text}`);
122
+ }
123
+
124
+ const path = response.path();
125
+ const stat = await ReactNativeBlobUtil.fs.stat(path);
126
+ if (stat.size === 0) throw new Error('Downloaded file is empty');
127
+ cacheAudioUrl(mid, path);
128
+ return path;
129
+ } catch (err: any) {
130
+ const msg =
131
+ err?.response?.data?.message ||
132
+ err?.message ||
133
+ 'Failed to fetch audio';
134
+ console.error('Fetch audio error:', msg);
135
+ throw new Error(msg);
136
+ }
137
+ },
138
+ [getCachedAudioUrl, cacheAudioUrl]
139
+ );
140
+
141
+ // === PLAY AUDIO ===
142
+ const playAudio = useCallback(
143
+ async (audioPath: string) => {
144
+ try {
145
+ await setupPlayer();
146
+ await TrackPlayer.reset();
147
+
148
+ const track = {
149
+ id: messageId,
150
+ url: `file://${audioPath}`,
151
+ title: `Message Audio`,
152
+ artist: 'Assistant',
153
+ };
154
+
155
+ await TrackPlayer.add([track]);
156
+ await TrackPlayer.play();
157
+ setIsPlaying(true);
158
+ } catch (err) {
159
+ console.error('Failed to play audio:', err);
160
+ throw new Error('Không thể phát âm thanh');
161
+ }
162
+ },
163
+ [messageId, setIsPlaying]
164
+ );
165
+
166
+ // === PAUSE ===
167
+ const pauseAudio = useCallback(async () => {
168
+ try {
169
+ await TrackPlayer.pause();
170
+ setIsPlaying(false);
171
+ } catch (e) {
172
+ console.warn('Pause failed:', e);
173
+ }
174
+ }, [setIsPlaying]);
175
+
176
+ // === STOP ===
177
+ const stopPlayback = useCallback(async () => {
178
+ try {
179
+ await TrackPlayer.stop();
180
+ await TrackPlayer.reset();
181
+ storeStopPlayback();
182
+ } catch (e) {
183
+ console.warn('Stop failed:', e);
184
+ }
185
+ }, [storeStopPlayback]);
186
+
187
+ // === TOGGLE ===
188
+ const togglePlayback = useCallback(async () => {
189
+ try {
190
+ if (!sessionId) throw new Error('No session ID');
191
+
192
+ // Case 1: Click on the SAME message that is currently playing → Pause it
193
+ if (isThisMessagePlaying) {
194
+ await pauseAudio();
195
+ logGA(GAEvents.chatDetailVoiceBtnTap, {
196
+ conversation_id: sessionId,
197
+ message_id: messageId,
198
+ button_status: 'stop',
199
+ duration: 0,
200
+ });
201
+ return;
202
+ }
203
+
204
+ // Case 2: Click on the SAME message that was paused → Resume it
205
+ if (currentMessageId === messageId && !isPlaying) {
206
+ await TrackPlayer.play();
207
+ setIsPlaying(true);
208
+ logGA(GAEvents.chatDetailVoiceBtnTap, {
209
+ conversation_id: sessionId,
210
+ message_id: messageId,
211
+ button_status: 'start',
212
+ duration: 0,
213
+ });
214
+
215
+ return;
216
+ }
217
+
218
+ // Case 3: Click on a DIFFERENT message → Stop current and play new one
219
+ if (currentMessageId && currentMessageId !== messageId) {
220
+ await stopPlayback();
221
+ logGA(GAEvents.chatDetailVoiceBtnTap, {
222
+ conversation_id: sessionId,
223
+ message_id: messageId,
224
+ button_status: 'stop',
225
+ duration: 0,
226
+ });
227
+ }
228
+
229
+ // Set new message as current and start loading
230
+ setCurrentMessage(messageId, sessionId);
231
+ setIsLoading(true);
232
+ setError(null);
233
+ const startStreamTime = Date.now();
234
+ const path = await fetchAudio(sessionId, messageId);
235
+ await playAudio(path);
236
+ logGA(GAEvents.chatDetailVoiceBtnTap, {
237
+ conversation_id: sessionId,
238
+ message_id: messageId,
239
+ button_status: 'start',
240
+ duration: (Date.now() - startStreamTime) / 1000,
241
+ });
242
+ } catch (err: any) {
243
+ const msg = err?.message || 'Failed to play audio';
244
+ setError(msg);
245
+ setIsPlaying(false);
246
+ console.error('togglePlayback error:', msg);
247
+ } finally {
248
+ setIsLoading(false);
249
+ }
250
+ }, [
251
+ sessionId,
252
+ messageId,
253
+ isThisMessagePlaying,
254
+ currentMessageId,
255
+ isPlaying,
256
+ setCurrentMessage,
257
+ setIsLoading,
258
+ setError,
259
+ setIsPlaying,
260
+ fetchAudio,
261
+ playAudio,
262
+ pauseAudio,
263
+ stopPlayback,
264
+ logGA,
265
+ ]);
266
+
267
+ // === PLAYER EVENTS ===
268
+ useEffect(() => {
269
+ // Listen for queue end
270
+ const queueEndSubscription = TrackPlayer.addEventListener(
271
+ Event.PlaybackQueueEnded,
272
+ () => {
273
+ if (currentMessageId === messageId) {
274
+ // Audio finished playing → reset to initial state
275
+ storeStopPlayback();
276
+ }
277
+ }
278
+ );
279
+
280
+ // Listen for playback state changes (from lock screen, control center, etc.)
281
+ const stateSubscription = TrackPlayer.addEventListener(
282
+ Event.PlaybackState,
283
+ async () => {
284
+ const state = await TrackPlayer.getState();
285
+
286
+ // Only update if this is the current message
287
+ if (currentMessageId === messageId) {
288
+ if (state === State.Playing) {
289
+ setIsPlaying(true);
290
+ } else if (state === State.Paused || state === State.Ready) {
291
+ setIsPlaying(false);
292
+ } else if (state === State.Stopped) {
293
+ storeStopPlayback();
294
+ }
295
+ }
296
+ }
297
+ );
298
+
299
+ return () => {
300
+ queueEndSubscription?.remove?.();
301
+ stateSubscription?.remove?.();
302
+ };
303
+ }, [currentMessageId, messageId, storeStopPlayback, setIsPlaying]);
304
+
305
+ // === CLEANUP CACHE WHEN SESSION CHANGES ===
306
+ const cleanupAudioCache = useCallback(async () => {
307
+ try {
308
+ const audioDir = `${ReactNativeBlobUtil.fs.dirs.CacheDir}/chatbot_audio`;
309
+ const exists = await ReactNativeBlobUtil.fs.exists(audioDir);
310
+ if (exists) await ReactNativeBlobUtil.fs.unlink(audioDir);
311
+ } catch (e) {
312
+ console.error('Cleanup cache error:', e);
313
+ }
314
+ }, []);
315
+
316
+ useEffect(() => {
317
+ if (currentSessionId && currentSessionId !== sessionId) {
318
+ if (currentMessageId === messageId) stopPlayback();
319
+ cleanupAudioCache();
320
+ }
321
+ }, [
322
+ sessionId,
323
+ currentSessionId,
324
+ currentMessageId,
325
+ messageId,
326
+ stopPlayback,
327
+ cleanupAudioCache,
328
+ ]);
329
+
330
+ useEffect(() => {
331
+ return () => {
332
+ if (currentMessageId === messageId) {
333
+ stopPlayback();
334
+ }
335
+ };
336
+ }, [currentMessageId, messageId, stopPlayback]);
337
+
338
+ return {
339
+ isPlaying: isThisMessagePlaying,
340
+ isPaused: isThisMessagePaused,
341
+ isLoading: isThisMessageLoading,
342
+ error: currentMessageId === messageId ? error : null,
343
+ togglePlayback,
344
+ stopPlayback,
345
+ };
346
+ };