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
@@ -9,6 +9,9 @@ import { ImageUpload } from '..';
9
9
  import { StyleSheet } from 'react-native';
10
10
  import { useImageUpload } from '../../../../hooks/upload/useImageUpload';
11
11
  import UIUtils from '../../../../utils/ui';
12
+ import { useChatContext } from '../../../../context/ChatContext';
13
+ import { GAEvents } from '../../../../constants/events';
14
+ import useSessionStore from '../../../../store/session';
12
15
 
13
16
  interface Props {
14
17
  item: ImageUpload;
@@ -17,9 +20,10 @@ interface Props {
17
20
  }
18
21
 
19
22
  const UploadImageItem = ({ item, onSuccess, onRemove }: Props) => {
23
+ const logGA = useChatContext().logGA;
20
24
  const { isSuccess, isUploading, progress } = useImageUpload({
21
25
  file: item,
22
- onSuccess: (data: any) => {
26
+ onSuccess: (data: any, duration?: number) => {
23
27
  const mItem = {
24
28
  ...item,
25
29
  remoteUrl: data?.[0]?.publicUrl,
@@ -27,6 +31,14 @@ const UploadImageItem = ({ item, onSuccess, onRemove }: Props) => {
27
31
  };
28
32
  if (mItem.remoteUrl) {
29
33
  onSuccess?.(mItem);
34
+ logGA(GAEvents.chatDetailMultimodalUpload, {
35
+ conversation_id: useSessionStore.getState().sessionId,
36
+ file_type: 'image',
37
+ duration: duration || 0,
38
+ size_mb: item.size / 1024 / 1024,
39
+ status: 'success',
40
+ format: item.mime,
41
+ });
30
42
  }
31
43
  },
32
44
  onError: () => {
@@ -35,6 +47,14 @@ const UploadImageItem = ({ item, onSuccess, onRemove }: Props) => {
35
47
  theme: 'danger',
36
48
  });
37
49
  onRemove?.(item);
50
+ logGA(GAEvents.chatDetailMultimodalUpload, {
51
+ conversation_id: useSessionStore.getState().sessionId,
52
+ file_type: 'image',
53
+ duration: 0,
54
+ size_mb: item.size / 1024 / 1024,
55
+ status: 'failed',
56
+ format: item.mime,
57
+ });
38
58
  },
39
59
  });
40
60
 
@@ -2,12 +2,8 @@ import { KContainer } from '@droppii/libs';
2
2
  import ChatHeader from './ChatHeader';
3
3
  import ChatMessageList from './ChatMessageList';
4
4
  import ChatFooter from './footer';
5
- import { KeyboardAvoidingView, Platform, StyleSheet } from 'react-native';
6
- import type { ComponentType } from 'react';
7
-
8
- // Type assertion to fix TypeScript issue with React Native components
9
- const KeyboardAvoidingViewComponent =
10
- KeyboardAvoidingView as unknown as ComponentType<any>;
5
+ import { StyleSheet } from 'react-native';
6
+ import { KeyboardAvoidingView } from 'react-native-keyboard-controller';
11
7
  import { useMessage } from '../../hooks/message/useMessage';
12
8
 
13
9
  const ChatBotAI = () => {
@@ -16,15 +12,16 @@ const ChatBotAI = () => {
16
12
  const lastMessage = messageState?.messages?.[0];
17
13
 
18
14
  return (
19
- <KContainer.Page>
20
- <KeyboardAvoidingViewComponent
15
+ <KContainer.Page flex>
16
+ <KeyboardAvoidingView
21
17
  style={styles.container}
22
- behavior={Platform.OS === 'ios' ? 'padding' : undefined}
18
+ behavior={'padding'}
19
+ keyboardVerticalOffset={0}
23
20
  >
24
21
  <ChatHeader />
25
22
  <ChatMessageList messageList={messageState?.messages || []} />
26
23
  <ChatFooter lastMessage={lastMessage} />
27
- </KeyboardAvoidingViewComponent>
24
+ </KeyboardAvoidingView>
28
25
  </KContainer.Page>
29
26
  );
30
27
  };
@@ -32,5 +29,5 @@ const ChatBotAI = () => {
32
29
  export default ChatBotAI;
33
30
 
34
31
  const styles = StyleSheet.create({
35
- container: { flex: 1 },
32
+ container: { flexGrow: 1 },
36
33
  });
@@ -21,7 +21,9 @@ import ProductHorizontalCard from '../../product/CardHorizontal';
21
21
  import ChatTable from './ChatTable';
22
22
  import DeeplinkItem from './DeeplinkItem';
23
23
  import { useChatContext } from '../../../context/ChatContext';
24
- import trans from '../../../translation';
24
+ import { trans } from '../../../translation';
25
+ import MessageActionsBar from './MessageActionsBar';
26
+ import useProductsStore from '../../../store/products';
25
27
 
26
28
  interface ChatAIAnswerMessageItemProps {
27
29
  item: IMessageItem;
@@ -167,20 +169,27 @@ export function useStreamingMarkdownBlocks(text: string) {
167
169
 
168
170
  class CustomRenderer extends Renderer implements RendererInterface {
169
171
  constructor(
170
- openImageViewer?: (images: { url: string }[], index: number) => void
172
+ openImageViewer?: (images: { url: string }[], index: number) => void,
173
+ messageId?: string
171
174
  ) {
172
175
  super();
173
176
  this.openImageViewer = openImageViewer;
177
+ this.messageId = messageId;
174
178
  }
175
179
 
176
180
  openImageViewer?: (images: { url: string }[], index: number) => void;
181
+ messageId?: string;
177
182
 
178
183
  html(text: string): ReactNode {
179
184
  const match = /^<!--\s*@component:(\w+)\(id:([\w-]+)\)\s*-->/.exec(text);
180
185
  switch (match?.[1]) {
181
186
  case 'product':
182
187
  return (
183
- <ProductHorizontalCard productId={match?.[2]} key={this.getKey()} />
188
+ <ProductHorizontalCard
189
+ productId={match?.[2]}
190
+ key={this.getKey()}
191
+ messageId={this.messageId}
192
+ />
184
193
  );
185
194
  default:
186
195
  return (
@@ -303,7 +312,11 @@ class CustomRenderer extends Renderer implements RendererInterface {
303
312
  link(children: string | ReactNode[], href: string, _styles?: any): ReactNode {
304
313
  if (href?.startsWith('/@component:deeplink')) {
305
314
  return (
306
- <DeeplinkItem key={this.getKey()} href={href}>
315
+ <DeeplinkItem
316
+ key={this.getKey()}
317
+ href={href}
318
+ messageId={this.messageId}
319
+ >
307
320
  {children}
308
321
  </DeeplinkItem>
309
322
  );
@@ -397,12 +410,37 @@ const ChatAIAnswerMessageItem = ({
397
410
 
398
411
  const {} = useSearchProduct({ productIds });
399
412
 
413
+ const products = useProductsStore((state) => state.products);
414
+
400
415
  const renderer = useMemo(
401
- () => new CustomRenderer(openImageViewer),
402
- [openImageViewer]
416
+ () => new CustomRenderer(openImageViewer, item.id),
417
+ [openImageViewer, item.id]
403
418
  );
404
419
  const tokenizer = useMemo(() => new CustomTokenizer(), []);
405
420
 
421
+ // Extract all product images for sharing
422
+ const productImages = useMemo(() => {
423
+ if (productIds.length === 0) {
424
+ return undefined;
425
+ }
426
+
427
+ const images: string[] = [];
428
+
429
+ productIds.forEach((productId) => {
430
+ const product = products.find((p) => p.id === productId);
431
+
432
+ if (!product) {
433
+ return;
434
+ }
435
+
436
+ const imageUrl = product?.images?.['410x410']?.[0]?.url || product?.image;
437
+ if (imageUrl) {
438
+ images.push(imageUrl);
439
+ }
440
+ });
441
+ return images.length > 0 ? images : undefined;
442
+ }, [productIds, products]);
443
+
406
444
  return (
407
445
  <KContainer.View style={styles.container}>
408
446
  {blocks.map((chunk, index) => (
@@ -420,6 +458,17 @@ const ChatAIAnswerMessageItem = ({
420
458
  }}
421
459
  />
422
460
  ))}
461
+
462
+ {/* Message Actions Bar */}
463
+ <MessageActionsBar
464
+ messageId={item.id}
465
+ messageContent={item.content}
466
+ isVisible={!isStreaming}
467
+ productImages={productImages}
468
+ message={item}
469
+ />
470
+
471
+ {/* Disclaimer */}
423
472
  <KContainer.VisibleView visible={isLast && !isStreaming}>
424
473
  <KLabel.Text
425
474
  typo="TextSmNormal"
@@ -1,10 +1,13 @@
1
1
  import { KContainer, KLabel, KColors } from '@droppii/libs';
2
2
  import { useCallback, useMemo } from 'react';
3
3
  import { useChatContext } from '../../../context/ChatContext';
4
+ import { GAEvents } from '../../../constants/events';
5
+ import useSessionStore from '../../../store/session';
4
6
 
5
7
  interface DeeplinkItemProps {
6
8
  children?: React.ReactNode;
7
9
  href?: string;
10
+ messageId?: string;
8
11
  }
9
12
 
10
13
  export function parseDeepLinkHref(href: string): Record<string, string> | null {
@@ -26,8 +29,9 @@ export function parseDeepLinkHref(href: string): Record<string, string> | null {
26
29
  return result;
27
30
  }
28
31
 
29
- const DeeplinkItem = ({ children, href }: DeeplinkItemProps) => {
32
+ const DeeplinkItem = ({ children, href, messageId }: DeeplinkItemProps) => {
30
33
  const pushLinkTo = useChatContext().pushLinkTo;
34
+ const logGA = useChatContext().logGA;
31
35
  const csTeamId = useChatContext().csTeamId;
32
36
 
33
37
  const linkData = useMemo(() => {
@@ -52,11 +56,35 @@ const DeeplinkItem = ({ children, href }: DeeplinkItemProps) => {
52
56
 
53
57
  const onPressDeeplink = useCallback(() => {
54
58
  if (linkData) {
59
+ const data = parseDeepLinkHref(href || '');
55
60
  pushLinkTo?.(linkData, {
56
61
  source_from: 'chatbot',
57
62
  });
63
+ if (data?.type === 'gift') {
64
+ logGA(GAEvents.chatDetailGiftDetailBtnTap, {
65
+ conversation_id: useSessionStore.getState().sessionId,
66
+ message_id: messageId,
67
+ gift_id: data?.id,
68
+ });
69
+ return;
70
+ }
71
+ if (data?.type === 'faq') {
72
+ logGA(GAEvents.chatDetailFAQTap, {
73
+ conversation_id: useSessionStore.getState().sessionId,
74
+ message_id: messageId,
75
+ deeplink_id: data?.id,
76
+ });
77
+ return;
78
+ }
79
+ if (data?.type === 'cx-support') {
80
+ logGA(GAEvents.chatDetailLivechatBtnTap, {
81
+ conversation_id: useSessionStore.getState().sessionId,
82
+ message_id: messageId,
83
+ });
84
+ return;
85
+ }
58
86
  }
59
- }, [linkData, pushLinkTo]);
87
+ }, [linkData, pushLinkTo, logGA, href, messageId]);
60
88
 
61
89
  return (
62
90
  <KContainer.Touchable onPress={onPressDeeplink}>
@@ -0,0 +1,326 @@
1
+ /**
2
+ * MessageActionsBar Component
3
+ * Main container that orchestrates all action buttons (Like, Dislike, Audio, Copy, Share)
4
+ */
5
+
6
+ import React, { useCallback, useMemo, useRef } from 'react';
7
+ import { StyleSheet, TouchableWithoutFeedback } from 'react-native';
8
+ import {
9
+ KColors,
10
+ KSpacingValue,
11
+ KLabel,
12
+ KImage,
13
+ KContainer,
14
+ } from '@droppii/libs';
15
+ import {
16
+ Menu,
17
+ MenuTrigger,
18
+ MenuOption,
19
+ MenuOptions,
20
+ renderers,
21
+ } from 'react-native-popup-menu';
22
+ import {
23
+ useFeedback,
24
+ useCopyToClipboard,
25
+ useShareMessage,
26
+ useAudioPlayer,
27
+ } from '../../../hooks/messageActions';
28
+ import {
29
+ FeedbackType,
30
+ LikeReason,
31
+ DislikeReason,
32
+ } from '../../../types/messageActions';
33
+ import ActionButton from './actions/ActionButton';
34
+
35
+ const { Popover } = renderers;
36
+
37
+ import type { IMessageItem } from '../../../types/dto';
38
+ import { useChatContext } from '../../../context/ChatContext';
39
+ import { GAEvents } from '../../../constants/events';
40
+
41
+ interface MessageActionsBarProps {
42
+ messageId: string;
43
+ messageContent: string;
44
+ isVisible: boolean;
45
+ productImages?: string[];
46
+ message?: IMessageItem;
47
+ }
48
+
49
+ const MessageActionsBar = ({
50
+ messageId,
51
+ messageContent,
52
+ isVisible,
53
+ productImages,
54
+ message,
55
+ }: MessageActionsBarProps) => {
56
+ // Refs
57
+ const likeMenuRef = useRef(null);
58
+ const dislikeMenuRef = useRef(null);
59
+ const logGA = useChatContext().logGA;
60
+
61
+ // Hooks
62
+ const { feedback, isLikeActive, isDislikeActive, selectReason } = useFeedback(
63
+ messageId,
64
+ message
65
+ );
66
+
67
+ const { copyToClipboard, isCopying } = useCopyToClipboard();
68
+ const { shareMessage, isLoading: isSharing } = useShareMessage(
69
+ messageId,
70
+ messageContent,
71
+ productImages
72
+ );
73
+ const {
74
+ isPlaying: isAudioPlaying,
75
+ isPaused: isAudioPaused,
76
+ isLoading: isAudioLoading,
77
+ togglePlayback,
78
+ } = useAudioPlayer({
79
+ messageId,
80
+ });
81
+
82
+ // Like reasons menu items
83
+ const likeReasons = useMemo(() => {
84
+ return Object.values(LikeReason).map((reason) => ({
85
+ label: reason,
86
+ onPress: () => selectReason(FeedbackType.like, reason),
87
+ }));
88
+ }, [selectReason]);
89
+
90
+ // Dislike reasons menu items
91
+ const dislikeReasons = useMemo(() => {
92
+ return Object.values(DislikeReason).map((reason) => ({
93
+ label: reason,
94
+ onPress: () => selectReason(FeedbackType.dislike, reason),
95
+ }));
96
+ }, [selectReason]);
97
+
98
+ // Handlers
99
+ const handleCopy = useCallback(() => {
100
+ logGA(GAEvents.chatDetailCopyBtnTap, {
101
+ message_id: messageId,
102
+ });
103
+ copyToClipboard(messageContent);
104
+ }, [messageContent, copyToClipboard, logGA, messageId]);
105
+
106
+ // Audio button states
107
+ const getAudioButtonProps = useCallback(() => {
108
+ if (isAudioPlaying) {
109
+ return {
110
+ icon: 'pause-o' as const,
111
+ iconColor: KColors.primary.normal,
112
+ isActive: true,
113
+ };
114
+ }
115
+
116
+ if (isAudioPaused) {
117
+ return {
118
+ icon: 'play-o' as const,
119
+ iconColor: KColors.primary.normal,
120
+ isActive: true,
121
+ };
122
+ }
123
+
124
+ return {
125
+ icon: 'volume-up-o' as const,
126
+ iconColor: KColors.gray.light,
127
+ isActive: false,
128
+ };
129
+ }, [isAudioPlaying, isAudioPaused]);
130
+
131
+ const audioButtonProps = getAudioButtonProps();
132
+
133
+ if (!isVisible) return null;
134
+
135
+ return (
136
+ <KContainer.View style={styles.container}>
137
+ {/* Action Buttons Row */}
138
+ <KContainer.View style={styles.buttonsRow}>
139
+ {/* Like Button with Menu - Hidden when Dislike is active */}
140
+ {!isDislikeActive && (
141
+ <Menu
142
+ ref={likeMenuRef}
143
+ renderer={Popover}
144
+ rendererProps={{
145
+ placement: 'top',
146
+ anchorStyle: { display: 'none' },
147
+ }}
148
+ >
149
+ <MenuTrigger
150
+ disabled={isLikeActive}
151
+ customStyles={{
152
+ TriggerTouchableComponent: TouchableWithoutFeedback,
153
+ }}
154
+ >
155
+ <KContainer.View
156
+ style={[
157
+ styles.actionButtonWrapper,
158
+ isLikeActive && styles.actionButtonActive,
159
+ ]}
160
+ >
161
+ <KImage.VectorIcons
162
+ name="like-thumbup-b"
163
+ size={16}
164
+ color={
165
+ isLikeActive ? KColors.primary.normal : KColors.gray.light
166
+ }
167
+ />
168
+ </KContainer.View>
169
+ </MenuTrigger>
170
+ <MenuOptions optionsContainerStyle={styles.popover}>
171
+ {likeReasons.map((item) => (
172
+ <MenuOption
173
+ key={item.label}
174
+ onSelect={item.onPress}
175
+ style={styles.menuItem}
176
+ disabled={feedback?.reason === item.label}
177
+ >
178
+ {/* @ts-ignore - KLabel.Text does support children */}
179
+ <KLabel.Text
180
+ typo="TextMdMedium"
181
+ color={
182
+ feedback?.reason === item.label
183
+ ? KColors.primary.normal
184
+ : KColors.gray.dark
185
+ }
186
+ >
187
+ {item.label}
188
+ </KLabel.Text>
189
+ </MenuOption>
190
+ ))}
191
+ </MenuOptions>
192
+ </Menu>
193
+ )}
194
+
195
+ {/* Dislike Button with Menu - Hidden when Like is active */}
196
+ {!isLikeActive && (
197
+ <Menu
198
+ ref={dislikeMenuRef}
199
+ renderer={Popover}
200
+ rendererProps={{
201
+ placement: 'top',
202
+ anchorStyle: { display: 'none' },
203
+ }}
204
+ >
205
+ <MenuTrigger
206
+ disabled={isDislikeActive}
207
+ customStyles={{
208
+ TriggerTouchableComponent: TouchableWithoutFeedback,
209
+ }}
210
+ >
211
+ <KContainer.View
212
+ style={[
213
+ styles.actionButtonWrapper,
214
+ isDislikeActive && styles.actionButtonActive,
215
+ ]}
216
+ >
217
+ <KImage.VectorIcons
218
+ name="like-thumbdown-b"
219
+ size={16}
220
+ color={
221
+ isDislikeActive
222
+ ? KColors.primary.normal
223
+ : KColors.gray.light
224
+ }
225
+ />
226
+ </KContainer.View>
227
+ </MenuTrigger>
228
+ <MenuOptions optionsContainerStyle={styles.popover}>
229
+ {dislikeReasons.map((item) => (
230
+ <MenuOption
231
+ key={item.label}
232
+ onSelect={item.onPress}
233
+ style={styles.menuItem}
234
+ disabled={feedback?.reason === item.label}
235
+ >
236
+ {/* @ts-ignore - KLabel.Text does support children */}
237
+ <KLabel.Text
238
+ typo="TextMdMedium"
239
+ color={
240
+ feedback?.reason === item.label
241
+ ? KColors.primary.normal
242
+ : KColors.gray.dark
243
+ }
244
+ >
245
+ {item.label}
246
+ </KLabel.Text>
247
+ </MenuOption>
248
+ ))}
249
+ </MenuOptions>
250
+ </Menu>
251
+ )}
252
+
253
+ {/* Audio Button */}
254
+ <ActionButton
255
+ icon={audioButtonProps.icon}
256
+ iconSize={16}
257
+ iconColor={audioButtonProps.iconColor}
258
+ isActive={audioButtonProps.isActive}
259
+ isLoading={isAudioLoading}
260
+ onPress={togglePlayback}
261
+ testID="audio-button"
262
+ />
263
+
264
+ {/* Copy Button */}
265
+ <ActionButton
266
+ icon="copy-o"
267
+ iconSize={16}
268
+ isLoading={isCopying}
269
+ onPress={handleCopy}
270
+ testID="copy-button"
271
+ />
272
+
273
+ {/* Share Button */}
274
+ <ActionButton
275
+ icon="share-o"
276
+ iconSize={16}
277
+ isLoading={isSharing}
278
+ onPress={shareMessage}
279
+ testID="share-button"
280
+ />
281
+ </KContainer.View>
282
+ </KContainer.View>
283
+ );
284
+ };
285
+
286
+ export default React.memo(MessageActionsBar);
287
+
288
+ const styles = StyleSheet.create({
289
+ container: {
290
+ gap: KSpacingValue['0.5rem'],
291
+ },
292
+ buttonsRow: {
293
+ flexDirection: 'row',
294
+ alignItems: 'center',
295
+ gap: KSpacingValue['0.75rem'],
296
+ flexWrap: 'wrap',
297
+ },
298
+ actionButtonWrapper: {
299
+ width: 32,
300
+ height: 32,
301
+ justifyContent: 'center',
302
+ alignItems: 'center',
303
+ borderRadius: KSpacingValue['0.5rem'],
304
+ },
305
+ actionButtonActive: {
306
+ backgroundColor: KColors.hexToRgba(KColors.primary.normal, 0.1),
307
+ },
308
+ popover: {
309
+ backgroundColor: KColors.white,
310
+ borderRadius: KSpacingValue['0.5rem'],
311
+ paddingVertical: KSpacingValue['0.5rem'],
312
+ shadowColor: KColors.black,
313
+ shadowOffset: {
314
+ width: 0,
315
+ height: 2,
316
+ },
317
+ shadowOpacity: 0.1,
318
+ shadowRadius: 8,
319
+ elevation: 5,
320
+ minWidth: 200,
321
+ },
322
+ menuItem: {
323
+ paddingVertical: KSpacingValue['0.75rem'],
324
+ paddingHorizontal: KSpacingValue['1rem'],
325
+ },
326
+ });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * ActionButton Component
3
+ * Reusable button component for all message action types
4
+ */
5
+
6
+ import React from 'react';
7
+ import { StyleSheet, ActivityIndicator } from 'react-native';
8
+ import { KContainer, KImage, KColors, KSpacingValue } from '@droppii/libs';
9
+
10
+ interface ActionButtonProps {
11
+ icon: string;
12
+ iconSize?: number;
13
+ iconColor?: string;
14
+ isActive?: boolean;
15
+ isLoading?: boolean;
16
+ disabled?: boolean;
17
+ onPress: () => void;
18
+ testID?: string;
19
+ accessibilityLabel?: string;
20
+ }
21
+
22
+ const ActionButton = ({
23
+ icon,
24
+ iconSize = 16,
25
+ iconColor = KColors.gray.light,
26
+ isActive = false,
27
+ isLoading = false,
28
+ disabled = false,
29
+ onPress,
30
+ testID,
31
+ accessibilityLabel,
32
+ }: ActionButtonProps) => {
33
+ return (
34
+ <KContainer.Touchable
35
+ style={[styles.button, isActive && styles.buttonActive]}
36
+ onPress={onPress}
37
+ disabled={disabled || isLoading}
38
+ testID={testID}
39
+ accessibilityLabel={accessibilityLabel}
40
+ accessibilityRole="button"
41
+ accessibilityState={{ disabled: disabled || isLoading }}
42
+ >
43
+ {isLoading ? (
44
+ <ActivityIndicator size="small" color={iconColor} />
45
+ ) : (
46
+ <KImage.VectorIcons name={icon} size={iconSize} color={iconColor} />
47
+ )}
48
+ </KContainer.Touchable>
49
+ );
50
+ };
51
+
52
+ export default React.memo(ActionButton);
53
+
54
+ const styles = StyleSheet.create({
55
+ button: {
56
+ width: 32,
57
+ height: 32,
58
+ justifyContent: 'center',
59
+ alignItems: 'center',
60
+ borderRadius: KSpacingValue['0.5rem'],
61
+ },
62
+ buttonActive: {
63
+ backgroundColor: KColors.hexToRgba(KColors.primary.normal, 0.1),
64
+ },
65
+ });