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,138 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import {
3
+ KContainer,
4
+ KLabel,
5
+ KColors,
6
+ KSpacingValue,
7
+ KImage,
8
+ } from '@droppii/libs';
9
+ import UIUtils from '../../utils/ui';
10
+ import { trans } from '../../translation';
11
+ import { SessionSearchItem } from '../../types/dto';
12
+ import { openRenameSessionPopup } from './RenameSessionPopup';
13
+ import { openShareSessionPopup } from './ShareSessionPopup';
14
+ import { openDeleteSessionPopup } from './DeleteSessionPopup';
15
+ import { GAEvents } from '../../constants/events';
16
+
17
+ interface OptionItemProps {
18
+ icon: string;
19
+ label: string;
20
+ onPress: () => void;
21
+ isDanger?: boolean;
22
+ }
23
+
24
+ const OptionItem = ({ icon, label, onPress, isDanger }: OptionItemProps) => {
25
+ const color = isDanger ? KColors.danger.normal : KColors.gray.dark;
26
+
27
+ return (
28
+ // @ts-ignore
29
+ <KContainer.Touchable onPress={onPress} style={styles.optionItem}>
30
+ <KImage.VectorIcons name={icon} size={20} color={color} />
31
+ {/* @ts-ignore */}
32
+ <KLabel.Text typo="TextMdMedium" color={color} style={styles.optionLabel}>
33
+ {label}
34
+ </KLabel.Text>
35
+ </KContainer.Touchable>
36
+ );
37
+ };
38
+
39
+ export const openSessionOptionsBottomSheet = (
40
+ session: SessionSearchItem,
41
+ closeDrawer?: () => void,
42
+ logGA?: (event: string, params?: any) => void
43
+ ) => {
44
+ const handleRename = () => {
45
+ UIUtils.bottomSheet.dismiss();
46
+ // Close drawer first, then open popup
47
+ closeDrawer?.();
48
+ setTimeout(() => {
49
+ openRenameSessionPopup(session);
50
+ }, 300);
51
+ logGA?.(GAEvents.chatDetailRenameBtnTap, {
52
+ conversation_id: session?.id,
53
+ });
54
+ };
55
+
56
+ const handleShare = () => {
57
+ UIUtils.bottomSheet.dismiss();
58
+ // Close drawer first, then open popup
59
+ closeDrawer?.();
60
+ setTimeout(() => {
61
+ openShareSessionPopup(session);
62
+ }, 300);
63
+ logGA?.(GAEvents.chatDetailShareBtnTap, {
64
+ conversation_id: session?.id,
65
+ });
66
+ };
67
+
68
+ const handleDelete = () => {
69
+ UIUtils.bottomSheet.dismiss();
70
+ // Close drawer first, then open popup
71
+ closeDrawer?.();
72
+ setTimeout(() => {
73
+ openDeleteSessionPopup(session, logGA);
74
+ }, 300);
75
+ };
76
+
77
+ const OptionsContent = () => {
78
+ return (
79
+ // @ts-ignore
80
+ <KContainer.View style={styles.container}>
81
+ <OptionItem
82
+ icon="edit-o"
83
+ label={trans('chatbot:session_rename')}
84
+ onPress={handleRename}
85
+ />
86
+ {/* @ts-ignore */}
87
+ <KContainer.View style={styles.divider} />
88
+ <OptionItem
89
+ icon="share-o"
90
+ label={trans('chatbot:session_share')}
91
+ onPress={handleShare}
92
+ />
93
+ {/* @ts-ignore */}
94
+ <KContainer.View style={styles.divider} />
95
+ <OptionItem
96
+ icon="trash-alt-o-1"
97
+ label={trans('chatbot:session_delete')}
98
+ onPress={handleDelete}
99
+ isDanger
100
+ />
101
+ </KContainer.View>
102
+ );
103
+ };
104
+
105
+ UIUtils.bottomSheet.open({
106
+ header: {
107
+ title: {
108
+ text: trans('chatbot:session_options_title'),
109
+ },
110
+ showCloseButton: true,
111
+ },
112
+ body: {
113
+ renderContent: () => <OptionsContent />,
114
+ },
115
+ touchOutsideToDismiss: true,
116
+ });
117
+ };
118
+
119
+ const styles = StyleSheet.create({
120
+ container: {
121
+ paddingHorizontal: KSpacingValue['1rem'],
122
+ paddingVertical: KSpacingValue['0.5rem'],
123
+ },
124
+ optionItem: {
125
+ flexDirection: 'row',
126
+ alignItems: 'center',
127
+ paddingVertical: KSpacingValue['1rem'],
128
+ paddingHorizontal: KSpacingValue['0.5rem'],
129
+ },
130
+ optionLabel: {
131
+ marginLeft: KSpacingValue['0.75rem'],
132
+ },
133
+ divider: {
134
+ height: 1,
135
+ backgroundColor: KColors.border.light,
136
+ marginHorizontal: KSpacingValue['0.5rem'],
137
+ },
138
+ });
@@ -0,0 +1,145 @@
1
+ import { useCallback, useState, useEffect } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import {
4
+ KContainer,
5
+ KLabel,
6
+ KColors,
7
+ KSpacingValue,
8
+ KInput,
9
+ } from '@droppii/libs';
10
+ import { useShareSession } from '../../hooks/session/useShareSession';
11
+ import UIUtils from '../../utils/ui';
12
+ import { trans } from '../../translation';
13
+ import { SessionSearchItem } from '../../types/dto';
14
+ import Clipboard from '@react-native-clipboard/clipboard';
15
+ import { useChatContext } from '../../context/ChatContext';
16
+ import { GAEvents } from '../../constants/events';
17
+
18
+ const truncateText = (text: string, maxLength: number) => {
19
+ if (text.length <= maxLength) return text;
20
+ return text.substring(0, maxLength) + '…';
21
+ };
22
+
23
+ interface ShareContentProps {
24
+ session: SessionSearchItem;
25
+ onConfirmRef: React.RefObject<(() => Promise<void>) | null>;
26
+ }
27
+
28
+ const ShareContent = ({ session, onConfirmRef }: ShareContentProps) => {
29
+ const { mutateAsync: shareSession } = useShareSession();
30
+ const { chatbotUrl, logGA } = useChatContext();
31
+ const [shareUrl, setShareUrl] = useState('');
32
+
33
+ // Generate share URL based on chatbotUrl
34
+ useEffect(() => {
35
+ setShareUrl(`${chatbotUrl}/shared/${session.id}`);
36
+ }, [chatbotUrl, session.id]);
37
+
38
+ // Expose handleConfirm via ref
39
+ onConfirmRef.current = useCallback(async () => {
40
+ try {
41
+ const response = await shareSession(session.id);
42
+ if (response) {
43
+ // Copy the generated share URL to clipboard
44
+ Clipboard.setString(shareUrl);
45
+ UIUtils.toast.showSuccess(trans('chatbot:share_session_success'));
46
+ UIUtils.popup.dismiss();
47
+ logGA(GAEvents.shareChatModalCopyBtnTap, {
48
+ conversation_id: session.id,
49
+ });
50
+ }
51
+ } catch (err) {
52
+ UIUtils.toast.showError(err);
53
+ }
54
+ }, [session.id, shareSession, shareUrl, logGA]);
55
+
56
+ const truncatedName = truncateText(session.title, 50);
57
+ const descriptionTemplate = trans('chatbot:share_session_description');
58
+ const parts = descriptionTemplate.split('{sessionName}');
59
+
60
+ return (
61
+ // @ts-ignore
62
+ <KContainer.View style={styles.container}>
63
+ {/* @ts-ignore */}
64
+ <KLabel.Text
65
+ typo="TextMdNormal"
66
+ color={KColors.gray.dark}
67
+ style={styles.description}
68
+ >
69
+ {parts[0]}
70
+ {/* @ts-ignore */}
71
+ <KLabel.Text typo="TextMdBold" color={KColors.gray.dark}>
72
+ {truncatedName}
73
+ </KLabel.Text>
74
+ {parts[1]}
75
+ </KLabel.Text>
76
+
77
+ {/* Share URL Input */}
78
+ {/* @ts-ignore */}
79
+ <KContainer.View style={[styles.inputWrapper]}>
80
+ <KInput.TextBox
81
+ value={shareUrl}
82
+ label={trans('chatbot:share_session_link_label')}
83
+ labelTypo="TextMdBold"
84
+ disabled
85
+ withBorder
86
+ height={40}
87
+ border="entire"
88
+ radius="borderless"
89
+ />
90
+ </KContainer.View>
91
+ </KContainer.View>
92
+ );
93
+ };
94
+
95
+ export const openShareSessionPopup = (session: SessionSearchItem) => {
96
+ const onConfirmRef = { current: null } as React.RefObject<
97
+ (() => Promise<void>) | null
98
+ >;
99
+
100
+ UIUtils.popup.open({
101
+ header: {
102
+ title: {
103
+ text: trans('chatbot:share_session_title'),
104
+ },
105
+ showCloseButton: true,
106
+ alignment: 'left',
107
+ },
108
+ body: {
109
+ scrollable: false,
110
+ renderContent: () => (
111
+ <ShareContent session={session} onConfirmRef={onConfirmRef} />
112
+ ),
113
+ },
114
+ actions: {
115
+ type: 'horizontal-button',
116
+ buttons: [
117
+ {
118
+ kind: 'light',
119
+ label: trans('chatbot:share_session_cancel'),
120
+ weight: 'medium',
121
+ onPress: () => UIUtils.popup.dismiss(),
122
+ },
123
+ {
124
+ kind: 'primary',
125
+ label: trans('chatbot:share_session_confirm'),
126
+ weight: 'medium',
127
+ onPress: () => onConfirmRef.current?.(),
128
+ },
129
+ ],
130
+ },
131
+ });
132
+ };
133
+
134
+ const styles = StyleSheet.create({
135
+ container: {
136
+ paddingVertical: KSpacingValue['1rem'],
137
+ paddingHorizontal: KSpacingValue['1rem'],
138
+ },
139
+ description: {
140
+ marginBottom: KSpacingValue['1rem'],
141
+ },
142
+ inputWrapper: {
143
+ paddingBottom: KSpacingValue['0.5rem'],
144
+ },
145
+ });
@@ -4,15 +4,24 @@ import { useFetchSuggestions } from '../../hooks/suggestions/useFetchSuggestions
4
4
  import SuggestionItem from './SuggestionItem';
5
5
  import { useSendMessage } from '../../hooks/message/useSendMessage';
6
6
  import debounce from 'lodash/debounce';
7
- import { type ISuggestionItem } from '../../types';
8
- import trans from '../../translation';
7
+ import { SendActionLogType, type ISuggestionItem } from '../../types';
8
+ import { trans } from '../../translation';
9
+ import { useChatContext } from '../../context/ChatContext';
10
+ import { GAEvents } from '../../constants/events';
9
11
 
10
12
  const ChatEmpty = () => {
11
13
  const { data } = useFetchSuggestions();
12
14
  const { onSendMessage } = useSendMessage();
15
+ const logGA = useChatContext().logGA;
13
16
 
14
- const onPress = debounce((item: ISuggestionItem) => {
15
- onSendMessage(item.content);
17
+ const onPress = debounce((item: ISuggestionItem, index: number) => {
18
+ logGA(GAEvents.newChatNewChatPrmSuggTap, {
19
+ suggestion_id: item.suggestion_id,
20
+ suggestion_content: item.content,
21
+ suggestion_cate: item.category,
22
+ order: index,
23
+ });
24
+ onSendMessage(item.content, undefined, SendActionLogType.promptSuggestion);
16
25
  }, 200);
17
26
 
18
27
  return (
@@ -31,10 +40,11 @@ const ChatEmpty = () => {
31
40
  {trans('chat_empty_description_2')}
32
41
  </KLabel.Text>
33
42
  </KContainer.View>
34
- {data?.suggestions?.map((item: ISuggestionItem) => (
43
+ {data?.suggestions?.map((item: ISuggestionItem, index: number) => (
35
44
  <SuggestionItem
36
45
  key={item.suggestion_id}
37
46
  item={item}
47
+ index={index}
38
48
  onPressItem={onPress}
39
49
  />
40
50
  ))}
@@ -12,6 +12,8 @@ import { ImageBackground, StatusBar, StyleSheet } from 'react-native';
12
12
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
13
13
  import useSessionStore from '../../store/session';
14
14
  import { useChatContext } from '../../context/ChatContext';
15
+ import { SessionLogType } from '../../types/chat';
16
+ import { GAEvents } from '../../constants/events';
15
17
 
16
18
  // Type assertion to fix TypeScript issue with React Native components
17
19
  const StatusBarComponent = StatusBar as unknown as ComponentType<{
@@ -23,11 +25,14 @@ const ImageBackgroundComponent =
23
25
  export const ChatHeader = () => {
24
26
  const insets = useSafeAreaInsets();
25
27
  const setSessionId = useSessionStore((state) => state.setSessionId);
26
- const { cartButton, openDrawer } = useChatContext();
28
+ const { cartButton, openDrawer, logGA } = useChatContext();
27
29
 
28
- const onPressNewSession = () => {
29
- setSessionId(undefined);
30
- };
30
+ const onPressNewSession = useCallback(() => {
31
+ setSessionId(undefined, SessionLogType.newChatBtn);
32
+ logGA(GAEvents.newChatCreateBtnTap, {
33
+ button_type: 'quick_button',
34
+ });
35
+ }, [setSessionId, logGA]);
31
36
 
32
37
  const renderStatusBar = useCallback(() => {
33
38
  return (
@@ -1,25 +1,38 @@
1
- import { FlatList, StyleSheet, DeviceEventEmitter } from 'react-native';
1
+ import { FlatList, StyleSheet, DeviceEventEmitter, View } from 'react-native';
2
2
  import type { ComponentType } from 'react';
3
+ import { useCallback, useRef, useEffect, useState } from 'react';
3
4
  import ChatEmpty from './ChatEmpty';
4
5
  import ChatItem from './item';
5
- import { KSpacingValue } from '@droppii/libs';
6
+ import { KSpacingValue, KColors, KImage, KContainer } from '@droppii/libs';
6
7
  import type { IMessageItem } from '../../types';
7
- import { useCallback, useRef } from 'react';
8
- import { useEffect } from 'react';
9
- import { events } from '../../constants/events';
8
+ import { events, GAEvents } from '../../constants/events';
9
+ import { useChatContext } from '../../context/ChatContext';
10
+ import useSessionStore from '../../store/session';
10
11
 
11
- // Type assertion to fix TypeScript issue with React Native components
12
12
  const FlatListComponent = FlatList as unknown as ComponentType<any>;
13
13
 
14
14
  interface IChatMessageListProps {
15
15
  messageList?: IMessageItem[];
16
16
  }
17
+
17
18
  const ChatMessageList = ({ messageList = [] }: IChatMessageListProps) => {
19
+ const logGA = useChatContext().logGA;
20
+ const sessionId = useSessionStore((state) => state.sessionId);
21
+
18
22
  const flatListRef = useRef<any>(null);
19
23
  const scrollOffsetRef = useRef(0);
24
+ const [showScrollToBottom, setShowScrollToBottom] = useState(false);
20
25
 
21
26
  const onScroll = useCallback((event: any) => {
22
- scrollOffsetRef.current = event?.nativeEvent?.contentOffset?.y;
27
+ const offsetY = event?.nativeEvent?.contentOffset?.y ?? 0;
28
+ scrollOffsetRef.current = offsetY;
29
+
30
+ const threshold = 50;
31
+ setShowScrollToBottom(offsetY > threshold);
32
+ }, []);
33
+
34
+ const scrollToBottom = useCallback(() => {
35
+ flatListRef.current?.scrollToOffset({ offset: 0, animated: true }); // inverted => bottom = offset 0
23
36
  }, []);
24
37
 
25
38
  useEffect(() => {
@@ -45,19 +58,51 @@ const ChatMessageList = ({ messageList = [] }: IChatMessageListProps) => {
45
58
  };
46
59
  }, []);
47
60
 
61
+ useEffect(() => {
62
+ if (!sessionId) {
63
+ logGA(GAEvents.newChatPageView);
64
+ } else {
65
+ logGA(GAEvents.chatDetailPageView, {
66
+ conversation_id: sessionId,
67
+ });
68
+ }
69
+ }, [sessionId, logGA]);
70
+
48
71
  return (
49
- <FlatListComponent
50
- ref={flatListRef}
51
- data={messageList}
52
- renderItem={({ item, index }: { item: IMessageItem; index: number }) => (
53
- <ChatItem item={item} index={index} />
72
+ <View style={{ flex: 1 }}>
73
+ <FlatListComponent
74
+ ref={flatListRef}
75
+ data={messageList}
76
+ renderItem={({
77
+ item,
78
+ index,
79
+ }: {
80
+ item: IMessageItem;
81
+ index: number;
82
+ }) => <ChatItem item={item} index={index} />}
83
+ keyExtractor={(item: IMessageItem) => item.id}
84
+ contentContainerStyle={styles.container}
85
+ inverted
86
+ ListEmptyComponent={<ChatEmpty />}
87
+ onScroll={onScroll}
88
+ scrollEventThrottle={16}
89
+ />
90
+
91
+ {showScrollToBottom && (
92
+ <KContainer.Touchable
93
+ style={styles.scrollButton}
94
+ onPress={scrollToBottom}
95
+ activeOpacity={0.7}
96
+ center
97
+ >
98
+ <KImage.VectorIcons
99
+ name="arrow-down-o"
100
+ size={16}
101
+ color={KColors.palette.gray.w400}
102
+ />
103
+ </KContainer.Touchable>
54
104
  )}
55
- keyExtractor={(item: IMessageItem) => item.id}
56
- contentContainerStyle={styles.container}
57
- inverted
58
- ListEmptyComponent={<ChatEmpty />}
59
- onScroll={onScroll}
60
- />
105
+ </View>
61
106
  );
62
107
  };
63
108
 
@@ -70,4 +115,19 @@ const styles = StyleSheet.create({
70
115
  paddingVertical: KSpacingValue['1rem'],
71
116
  justifyContent: 'flex-end',
72
117
  },
118
+ scrollButton: {
119
+ position: 'absolute',
120
+ bottom: 20,
121
+ right: 16,
122
+ backgroundColor: KColors.white,
123
+ width: 32,
124
+ height: 32,
125
+ borderRadius: 16,
126
+ shadowColor: '#000',
127
+ shadowOpacity: 0.15,
128
+ shadowRadius: 4,
129
+ elevation: 4,
130
+ justifyContent: 'center',
131
+ alignItems: 'center',
132
+ },
73
133
  });
@@ -13,10 +13,11 @@ import { StyleSheet } from 'react-native';
13
13
 
14
14
  interface SuggestionItemProps {
15
15
  item: ISuggestionItem;
16
- onPressItem: (item: ISuggestionItem) => void;
16
+ index: number;
17
+ onPressItem: (item: ISuggestionItem, index: number) => void;
17
18
  }
18
19
 
19
- const SuggestionItem = ({ item, onPressItem }: SuggestionItemProps) => {
20
+ const SuggestionItem = ({ item, index, onPressItem }: SuggestionItemProps) => {
20
21
  const icon = useMemo(() => {
21
22
  switch (item.category) {
22
23
  case SuggestionCategory.product:
@@ -81,7 +82,7 @@ const SuggestionItem = ({ item, onPressItem }: SuggestionItemProps) => {
81
82
  return (
82
83
  <KContainer.Touchable
83
84
  style={styles.container}
84
- onPress={() => onPressItem(item)}
85
+ onPress={() => onPressItem(item, index)}
85
86
  >
86
87
  {icon}
87
88
  <KLabel.Text flex typo="TextNmNormal">
@@ -44,9 +44,13 @@ import {
44
44
  IMessageItem,
45
45
  ISuggestionItem,
46
46
  MessageType,
47
+ SendActionLogType,
47
48
  } from '../../../types';
48
49
  import UIUtils from '../../../utils/ui';
49
- import trans from '../../../translation';
50
+ import { trans } from '../../../translation';
51
+ import { useChatContext } from '../../../context/ChatContext';
52
+ import { GAEvents } from '../../../constants/events';
53
+ import useSessionStore from '../../../store/session';
50
54
 
51
55
  const { Popover } = renderers;
52
56
  export interface ImageUpload extends Image {
@@ -66,13 +70,17 @@ export type UploadItem = ImageUpload | FileUpload;
66
70
  const MAX_FILE_UPLOAD = 3;
67
71
  const MAX_FILE_SIZE = 30 * 1024 * 1024;
68
72
  const MAX_IMAGE_SIZE = 7 * 1024 * 1024;
73
+ const SCROLL_IDLE_TIME = 60000;
69
74
 
70
75
  interface IChatFooterProps {
71
76
  lastMessage?: IMessageItem;
72
77
  }
73
78
 
74
79
  const ChatFooter = ({ lastMessage }: IChatFooterProps) => {
80
+ const idleTimerRef = useRef<NodeJS.Timeout | null>(null);
81
+ const hasScrolledRef = useRef(false);
75
82
  const menuRef = useRef(null);
83
+ const logGA = useChatContext().logGA;
76
84
  const { onSendMessage, stopStream } = useSendMessage();
77
85
  const [message, setMessage] = useState('');
78
86
  const isStreaming = useStreamMessageStore((state) => state.isStreaming);
@@ -139,7 +147,12 @@ const ChatFooter = ({ lastMessage }: IChatFooterProps) => {
139
147
  return [...prev, ...uniqueImages];
140
148
  });
141
149
  }
142
- }, [fileUpload]);
150
+
151
+ logGA(GAEvents.chatDetailMultimodalTap, {
152
+ conversation_id: useSessionStore.getState().sessionId,
153
+ button: 'image',
154
+ });
155
+ }, [fileUpload, logGA]);
143
156
 
144
157
  const onPressCameraPicker = useCallback(async () => {
145
158
  Keyboard.dismiss();
@@ -168,7 +181,11 @@ const ChatFooter = ({ lastMessage }: IChatFooterProps) => {
168
181
  return [...prev, { ...newImage, uploadType: 'image' }];
169
182
  });
170
183
  }
171
- }, [fileUpload]);
184
+ logGA(GAEvents.chatDetailMultimodalTap, {
185
+ conversation_id: useSessionStore.getState().sessionId,
186
+ button: 'image',
187
+ });
188
+ }, [fileUpload, logGA]);
172
189
 
173
190
  const menuItems = useMemo(() => {
174
191
  return [
@@ -188,6 +205,9 @@ const ChatFooter = ({ lastMessage }: IChatFooterProps) => {
188
205
  const onPressSend = useCallback(() => {
189
206
  if (isStreaming) {
190
207
  stopStream();
208
+ logGA(GAEvents.chatDetailStopChatBtnTap, {
209
+ conversation_id: useSessionStore.getState().sessionId,
210
+ });
191
211
  return;
192
212
  }
193
213
  const attachments: IAttachment[] = fileUpload.map((i) => ({
@@ -201,10 +221,10 @@ const ChatFooter = ({ lastMessage }: IChatFooterProps) => {
201
221
  : i.type) as IAttachment['mime_type'],
202
222
  }));
203
223
 
204
- onSendMessage(message?.trim(), attachments);
224
+ onSendMessage(message?.trim(), attachments, SendActionLogType.sendBtn);
205
225
  setMessage('');
206
226
  setFileUpload([]);
207
- }, [message, isStreaming, onSendMessage, stopStream, fileUpload]);
227
+ }, [message, isStreaming, onSendMessage, stopStream, fileUpload, logGA]);
208
228
 
209
229
  const handleUploadSuccess = useCallback((item: UploadItem) => {
210
230
  if (item.uploadType === 'image') {
@@ -305,18 +325,37 @@ const ChatFooter = ({ lastMessage }: IChatFooterProps) => {
305
325
  return [...prev, { ...newDocument, uploadType: 'file' }];
306
326
  });
307
327
  }
308
- }, [fileUpload]);
328
+ logGA(GAEvents.chatDetailMultimodalTap, {
329
+ conversation_id: useSessionStore.getState().sessionId,
330
+ button: 'file',
331
+ });
332
+ }, [fileUpload, logGA]);
309
333
 
310
- const onPressItemSuggestion = debounce((item: ISuggestionItem) => {
311
- onSendMessage(item.content);
312
- }, 200);
334
+ const onPressItemSuggestion = debounce(
335
+ (item: ISuggestionItem, index: number) => {
336
+ onSendMessage(
337
+ item.content,
338
+ undefined,
339
+ SendActionLogType.promptSuggestion
340
+ );
341
+ logGA(GAEvents.chatDetailInConvPrmSuggTap, {
342
+ conversation_id: useSessionStore.getState().sessionId,
343
+ group_id: lastMessage?.group_suggestion_id,
344
+ suggestion_id: item.suggestion_id,
345
+ suggestion_content: item.content,
346
+ suggestion_cate: item.category,
347
+ order: index,
348
+ });
349
+ },
350
+ 200
351
+ );
313
352
 
314
353
  const renderSuggestionItem = useCallback(
315
- (item: ISuggestionItem) => {
354
+ (item: ISuggestionItem, index: number) => {
316
355
  return (
317
356
  <KContainer.Touchable
318
357
  style={styles.suggestionItem}
319
- onPress={() => onPressItemSuggestion(item)}
358
+ onPress={() => onPressItemSuggestion(item, index)}
320
359
  >
321
360
  <KLabel.Text typo="TextSmNormal">{item?.content || ''}</KLabel.Text>
322
361
  </KContainer.Touchable>
@@ -325,17 +364,58 @@ const ChatFooter = ({ lastMessage }: IChatFooterProps) => {
325
364
  [onPressItemSuggestion]
326
365
  );
327
366
 
367
+ const clearIdleTimer = () => {
368
+ if (idleTimerRef.current) {
369
+ clearTimeout(idleTimerRef.current);
370
+ idleTimerRef.current = null;
371
+ }
372
+ };
373
+
374
+ const onUserIdleAfterScroll = useCallback(() => {
375
+ logGA(GAEvents.chatDetailInConvPrmSuggScroll, {
376
+ group_id: lastMessage?.group_suggestion_id,
377
+ message_id: lastMessage?.id,
378
+ });
379
+ }, [lastMessage, logGA]);
380
+
381
+ const resetIdleTimer = useCallback(() => {
382
+ clearIdleTimer();
383
+
384
+ hasScrolledRef.current = true;
385
+
386
+ idleTimerRef.current = setTimeout(() => {
387
+ if (hasScrolledRef.current) {
388
+ onUserIdleAfterScroll();
389
+ }
390
+ }, SCROLL_IDLE_TIME);
391
+ }, [onUserIdleAfterScroll]);
392
+
393
+ const handleScroll = () => {
394
+ resetIdleTimer();
395
+ };
396
+
397
+ const handleMomentumEnd = () => {
398
+ resetIdleTimer();
399
+ };
400
+
328
401
  return (
329
402
  <KContainer.View style={styles.container}>
330
403
  <KContainer.VisibleView visible={isShowSuggestions}>
331
404
  <FlatListComponent
332
405
  data={lastMessage?.suggestions || []}
333
- renderItem={({ item }: { item: ISuggestionItem }) =>
334
- renderSuggestionItem(item)
335
- }
406
+ renderItem={({
407
+ item,
408
+ index,
409
+ }: {
410
+ item: ISuggestionItem;
411
+ index: number;
412
+ }) => renderSuggestionItem(item, index)}
336
413
  horizontal
337
414
  keyExtractor={(item: ISuggestionItem) => item.suggestion_id}
338
415
  contentContainerStyle={styles.suggessionContainer}
416
+ onScroll={handleScroll}
417
+ onMomentumScrollEnd={handleMomentumEnd}
418
+ scrollEventThrottle={200}
339
419
  />
340
420
  </KContainer.VisibleView>
341
421
  <KContainer.VisibleView visible={fileUpload.length > 0}>
@@ -429,6 +509,7 @@ const styles = StyleSheet.create({
429
509
  borderBottomWidth: 0,
430
510
  borderTopLeftRadius: KSpacingValue['1.25rem'],
431
511
  borderTopRightRadius: KSpacingValue['1.25rem'],
512
+ backgroundColor: KColors.white,
432
513
  },
433
514
  actions: {
434
515
  flexDirection: 'row',