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.
- package/lib/module/components/Drawer/DeleteSessionPopup.js +113 -0
- package/lib/module/components/Drawer/DeleteSessionPopup.js.map +1 -0
- package/lib/module/components/Drawer/DrawerContent.js +21 -8
- package/lib/module/components/Drawer/DrawerContent.js.map +1 -1
- package/lib/module/components/Drawer/RenameSessionPopup.js +121 -0
- package/lib/module/components/Drawer/RenameSessionPopup.js.map +1 -0
- package/lib/module/components/Drawer/SearchInput.js +10 -3
- package/lib/module/components/Drawer/SearchInput.js.map +1 -1
- package/lib/module/components/Drawer/SessionItem.js +36 -20
- package/lib/module/components/Drawer/SessionItem.js.map +1 -1
- package/lib/module/components/Drawer/SessionList.js +2 -5
- package/lib/module/components/Drawer/SessionList.js.map +1 -1
- package/lib/module/components/Drawer/SessionOptionsBottomSheet.js +129 -0
- package/lib/module/components/Drawer/SessionOptionsBottomSheet.js.map +1 -0
- package/lib/module/components/Drawer/ShareSessionPopup.js +132 -0
- package/lib/module/components/Drawer/ShareSessionPopup.js.map +1 -0
- package/lib/module/components/chat/ChatEmpty.js +15 -4
- package/lib/module/components/chat/ChatEmpty.js.map +1 -1
- package/lib/module/components/chat/ChatHeader.js +10 -4
- package/lib/module/components/chat/ChatHeader.js.map +1 -1
- package/lib/module/components/chat/ChatMessageList.js +75 -24
- package/lib/module/components/chat/ChatMessageList.js.map +1 -1
- package/lib/module/components/chat/SuggestionItem.js +2 -1
- package/lib/module/components/chat/SuggestionItem.js.map +1 -1
- package/lib/module/components/chat/footer/index.js +77 -15
- package/lib/module/components/chat/footer/index.js.map +1 -1
- package/lib/module/components/chat/footer/item/UploadImageItem.js +21 -1
- package/lib/module/components/chat/footer/item/UploadImageItem.js.map +1 -1
- package/lib/module/components/chat/index.js +7 -6
- package/lib/module/components/chat/index.js.map +1 -1
- package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js +36 -5
- package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js.map +1 -1
- package/lib/module/components/chat/item/DeeplinkItem.js +30 -2
- package/lib/module/components/chat/item/DeeplinkItem.js.map +1 -1
- package/lib/module/components/chat/item/MessageActionsBar.js +243 -0
- package/lib/module/components/chat/item/MessageActionsBar.js.map +1 -0
- package/lib/module/components/chat/item/actions/ActionButton.js +56 -0
- package/lib/module/components/chat/item/actions/ActionButton.js.map +1 -0
- package/lib/module/components/portal/BottomSheet.js +245 -0
- package/lib/module/components/portal/BottomSheet.js.map +1 -0
- package/lib/module/components/portal/Popup.js +278 -0
- package/lib/module/components/portal/Popup.js.map +1 -0
- package/lib/module/components/portal/index.js +11 -5
- package/lib/module/components/portal/index.js.map +1 -1
- package/lib/module/components/product/CardHorizontal.js +44 -7
- package/lib/module/components/product/CardHorizontal.js.map +1 -1
- package/lib/module/constants/events.js +33 -0
- package/lib/module/constants/events.js.map +1 -1
- package/lib/module/constants/index.js +5 -1
- package/lib/module/constants/index.js.map +1 -1
- package/lib/module/constants/query.js +5 -1
- package/lib/module/constants/query.js.map +1 -1
- package/lib/module/context/ChatContext.js +9 -3
- package/lib/module/context/ChatContext.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +21 -3
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/message/useStreamMessage.js +10 -3
- package/lib/module/hooks/message/useStreamMessage.js.map +1 -1
- package/lib/module/hooks/messageActions/index.js +13 -0
- package/lib/module/hooks/messageActions/index.js.map +1 -0
- package/lib/module/hooks/messageActions/useAudioPlayer.js +269 -0
- package/lib/module/hooks/messageActions/useAudioPlayer.js.map +1 -0
- package/lib/module/hooks/messageActions/useCopyToClipboard.js +38 -0
- package/lib/module/hooks/messageActions/useCopyToClipboard.js.map +1 -0
- package/lib/module/hooks/messageActions/useFeedback.js +93 -0
- package/lib/module/hooks/messageActions/useFeedback.js.map +1 -0
- package/lib/module/hooks/messageActions/useSendFeedback.js +24 -0
- package/lib/module/hooks/messageActions/useSendFeedback.js.map +1 -0
- package/lib/module/hooks/messageActions/useShareMessage.js +128 -0
- package/lib/module/hooks/messageActions/useShareMessage.js.map +1 -0
- package/lib/module/hooks/session/useDeleteSession.js +23 -0
- package/lib/module/hooks/session/useDeleteSession.js.map +1 -0
- package/lib/module/hooks/session/useRenameSession.js +28 -0
- package/lib/module/hooks/session/useRenameSession.js.map +1 -0
- package/lib/module/hooks/session/useSearchSessions.js +5 -1
- package/lib/module/hooks/session/useSearchSessions.js.map +1 -1
- package/lib/module/hooks/session/useShareSession.js +16 -0
- package/lib/module/hooks/session/useShareSession.js.map +1 -0
- package/lib/module/hooks/upload/useImageUpload.js +2 -1
- package/lib/module/hooks/upload/useImageUpload.js.map +1 -1
- package/lib/module/services/endpoints.js +6 -1
- package/lib/module/services/endpoints.js.map +1 -1
- package/lib/module/services/index.js +1 -0
- package/lib/module/services/index.js.map +1 -1
- package/lib/module/services/playbackService.js +22 -0
- package/lib/module/services/playbackService.js.map +1 -0
- package/lib/module/store/audioPlayer.js +75 -0
- package/lib/module/store/audioPlayer.js.map +1 -0
- package/lib/module/store/messageActions.js +160 -0
- package/lib/module/store/messageActions.js.map +1 -0
- package/lib/module/store/session.js +6 -3
- package/lib/module/store/session.js.map +1 -1
- package/lib/module/translation/index.js +21 -25
- package/lib/module/translation/index.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +39 -0
- package/lib/module/translation/resources/i18n.js.map +1 -0
- package/lib/module/types/chat.js +12 -1
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/types/index.js +1 -0
- package/lib/module/types/index.js.map +1 -1
- package/lib/module/types/messageActions.js +56 -0
- package/lib/module/types/messageActions.js.map +1 -0
- package/lib/module/utils/textCleaner.js +67 -0
- package/lib/module/utils/textCleaner.js.map +1 -0
- package/lib/module/utils/ui.js +27 -1
- package/lib/module/utils/ui.js.map +1 -1
- package/lib/typescript/src/components/Drawer/DeleteSessionPopup.d.ts +3 -0
- package/lib/typescript/src/components/Drawer/DeleteSessionPopup.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/DrawerContent.d.ts.map +1 -1
- package/lib/typescript/src/components/Drawer/RenameSessionPopup.d.ts +3 -0
- package/lib/typescript/src/components/Drawer/RenameSessionPopup.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/SearchInput.d.ts.map +1 -1
- package/lib/typescript/src/components/Drawer/SessionItem.d.ts.map +1 -1
- package/lib/typescript/src/components/Drawer/SessionList.d.ts.map +1 -1
- package/lib/typescript/src/components/Drawer/SessionOptionsBottomSheet.d.ts +3 -0
- package/lib/typescript/src/components/Drawer/SessionOptionsBottomSheet.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/ShareSessionPopup.d.ts +3 -0
- package/lib/typescript/src/components/Drawer/ShareSessionPopup.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/ChatEmpty.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/ChatMessageList.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/SuggestionItem.d.ts +3 -2
- package/lib/typescript/src/components/chat/SuggestionItem.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/footer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/footer/item/UploadImageItem.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/index.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/item/ChatAIAnswerMessageItem.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/item/DeeplinkItem.d.ts +2 -1
- package/lib/typescript/src/components/chat/item/DeeplinkItem.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/item/MessageActionsBar.d.ts +16 -0
- package/lib/typescript/src/components/chat/item/MessageActionsBar.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/item/actions/ActionButton.d.ts +19 -0
- package/lib/typescript/src/components/chat/item/actions/ActionButton.d.ts.map +1 -0
- package/lib/typescript/src/components/portal/BottomSheet.d.ts +8 -0
- package/lib/typescript/src/components/portal/BottomSheet.d.ts.map +1 -0
- package/lib/typescript/src/components/portal/Popup.d.ts +4 -0
- package/lib/typescript/src/components/portal/Popup.d.ts.map +1 -0
- package/lib/typescript/src/components/portal/index.d.ts.map +1 -1
- package/lib/typescript/src/components/product/CardHorizontal.d.ts +3 -2
- package/lib/typescript/src/components/product/CardHorizontal.d.ts.map +1 -1
- package/lib/typescript/src/constants/events.d.ts +33 -0
- package/lib/typescript/src/constants/events.d.ts.map +1 -1
- package/lib/typescript/src/constants/index.d.ts +5 -1
- package/lib/typescript/src/constants/index.d.ts.map +1 -1
- package/lib/typescript/src/constants/query.d.ts +4 -0
- package/lib/typescript/src/constants/query.d.ts.map +1 -1
- package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -2
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useStreamMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/messageActions/index.d.ts +10 -0
- package/lib/typescript/src/hooks/messageActions/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/messageActions/useAudioPlayer.d.ts +14 -0
- package/lib/typescript/src/hooks/messageActions/useAudioPlayer.d.ts.map +1 -0
- package/lib/typescript/src/hooks/messageActions/useCopyToClipboard.d.ts +9 -0
- package/lib/typescript/src/hooks/messageActions/useCopyToClipboard.d.ts.map +1 -0
- package/lib/typescript/src/hooks/messageActions/useFeedback.d.ts +18 -0
- package/lib/typescript/src/hooks/messageActions/useFeedback.d.ts.map +1 -0
- package/lib/typescript/src/hooks/messageActions/useSendFeedback.d.ts +6 -0
- package/lib/typescript/src/hooks/messageActions/useSendFeedback.d.ts.map +1 -0
- package/lib/typescript/src/hooks/messageActions/useShareMessage.d.ts +12 -0
- package/lib/typescript/src/hooks/messageActions/useShareMessage.d.ts.map +1 -0
- package/lib/typescript/src/hooks/session/useDeleteSession.d.ts +2 -0
- package/lib/typescript/src/hooks/session/useDeleteSession.d.ts.map +1 -0
- package/lib/typescript/src/hooks/session/useRenameSession.d.ts +2 -0
- package/lib/typescript/src/hooks/session/useRenameSession.d.ts.map +1 -0
- package/lib/typescript/src/hooks/session/useSearchSessions.d.ts +1 -1
- package/lib/typescript/src/hooks/session/useSearchSessions.d.ts.map +1 -1
- package/lib/typescript/src/hooks/session/useShareSession.d.ts +2 -0
- package/lib/typescript/src/hooks/session/useShareSession.d.ts.map +1 -0
- package/lib/typescript/src/hooks/upload/useImageUpload.d.ts +1 -1
- package/lib/typescript/src/hooks/upload/useImageUpload.d.ts.map +1 -1
- package/lib/typescript/src/services/endpoints.d.ts +5 -0
- package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
- package/lib/typescript/src/services/index.d.ts +1 -0
- package/lib/typescript/src/services/index.d.ts.map +1 -1
- package/lib/typescript/src/services/playbackService.d.ts +2 -0
- package/lib/typescript/src/services/playbackService.d.ts.map +1 -0
- package/lib/typescript/src/store/audioPlayer.d.ts +27 -0
- package/lib/typescript/src/store/audioPlayer.d.ts.map +1 -0
- package/lib/typescript/src/store/messageActions.d.ts +9 -0
- package/lib/typescript/src/store/messageActions.d.ts.map +1 -0
- package/lib/typescript/src/store/session.d.ts.map +1 -1
- package/lib/typescript/src/translation/index.d.ts +3 -4
- package/lib/typescript/src/translation/index.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts +5 -0
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -0
- package/lib/typescript/src/types/chat.d.ts +16 -1
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/dto.d.ts +11 -0
- package/lib/typescript/src/types/dto.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +1 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/types/messageActions.d.ts +85 -0
- package/lib/typescript/src/types/messageActions.d.ts.map +1 -0
- package/lib/typescript/src/types/ui.d.ts +16 -1
- package/lib/typescript/src/types/ui.d.ts.map +1 -1
- package/lib/typescript/src/utils/textCleaner.d.ts +30 -0
- package/lib/typescript/src/utils/textCleaner.d.ts.map +1 -0
- package/lib/typescript/src/utils/ui.d.ts +12 -1
- package/lib/typescript/src/utils/ui.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/components/Drawer/DeleteSessionPopup.tsx +121 -0
- package/src/components/Drawer/DrawerContent.tsx +23 -7
- package/src/components/Drawer/RenameSessionPopup.tsx +145 -0
- package/src/components/Drawer/SearchInput.tsx +11 -2
- package/src/components/Drawer/SessionItem.tsx +22 -8
- package/src/components/Drawer/SessionList.tsx +0 -2
- package/src/components/Drawer/SessionOptionsBottomSheet.tsx +138 -0
- package/src/components/Drawer/ShareSessionPopup.tsx +145 -0
- package/src/components/chat/ChatEmpty.tsx +15 -5
- package/src/components/chat/ChatHeader.tsx +9 -4
- package/src/components/chat/ChatMessageList.tsx +78 -18
- package/src/components/chat/SuggestionItem.tsx +4 -3
- package/src/components/chat/footer/index.tsx +95 -14
- package/src/components/chat/footer/item/UploadImageItem.tsx +21 -1
- package/src/components/chat/index.tsx +8 -11
- package/src/components/chat/item/ChatAIAnswerMessageItem.tsx +55 -6
- package/src/components/chat/item/DeeplinkItem.tsx +30 -2
- package/src/components/chat/item/MessageActionsBar.tsx +326 -0
- package/src/components/chat/item/actions/ActionButton.tsx +65 -0
- package/src/components/portal/BottomSheet.tsx +307 -0
- package/src/components/portal/Popup.tsx +345 -0
- package/src/components/portal/index.tsx +5 -1
- package/src/components/product/CardHorizontal.tsx +45 -10
- package/src/constants/events.ts +34 -0
- package/src/constants/index.ts +6 -1
- package/src/constants/query.ts +4 -0
- package/src/context/ChatContext.tsx +6 -0
- package/src/hooks/message/useSendMessage.ts +47 -4
- package/src/hooks/message/useStreamMessage.ts +15 -4
- package/src/hooks/messageActions/index.ts +10 -0
- package/src/hooks/messageActions/useAudioPlayer.ts +346 -0
- package/src/hooks/messageActions/useCopyToClipboard.ts +38 -0
- package/src/hooks/messageActions/useFeedback.ts +114 -0
- package/src/hooks/messageActions/useSendFeedback.ts +31 -0
- package/src/hooks/messageActions/useShareMessage.ts +146 -0
- package/src/hooks/session/useDeleteSession.ts +25 -0
- package/src/hooks/session/useRenameSession.ts +37 -0
- package/src/hooks/session/useSearchSessions.ts +6 -1
- package/src/hooks/session/useShareSession.ts +22 -0
- package/src/hooks/upload/useImageUpload.ts +6 -2
- package/src/ignore.d.ts +20 -1
- package/src/services/endpoints.ts +10 -0
- package/src/services/index.ts +1 -0
- package/src/services/playbackService.ts +22 -0
- package/src/store/audioPlayer.ts +112 -0
- package/src/store/messageActions.ts +161 -0
- package/src/store/session.ts +4 -2
- package/src/translation/index.ts +27 -19
- package/src/translation/resources/i18n.ts +45 -0
- package/src/types/chat.ts +21 -1
- package/src/types/dto.ts +14 -0
- package/src/types/index.ts +1 -0
- package/src/types/messageActions.ts +131 -0
- package/src/types/ui.ts +19 -1
- package/src/utils/textCleaner.ts +65 -0
- package/src/utils/ui.tsx +29 -2
- package/lib/module/translation/resources/vi.js +0 -12
- package/lib/module/translation/resources/vi.js.map +0 -1
- package/lib/typescript/src/translation/resources/vi.d.ts +0 -11
- package/lib/typescript/src/translation/resources/vi.d.ts.map +0 -1
- 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
|
|
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={
|
|
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={
|
|
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
|
|
356
|
+
export default ProductHorizontalCard;
|
|
322
357
|
|
|
323
358
|
const styles = StyleSheet.create({
|
|
324
359
|
abs: {
|
package/src/constants/events.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/constants/index.ts
CHANGED
|
@@ -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
|
};
|
package/src/constants/query.ts
CHANGED
|
@@ -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 {
|
|
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 (
|
|
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 {
|
|
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
|
+
};
|