react-native-chatbot-ai 0.1.12 → 0.1.15

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 (136) hide show
  1. package/lib/module/components/Drawer/DrawerContent.js +85 -0
  2. package/lib/module/components/Drawer/DrawerContent.js.map +1 -0
  3. package/lib/module/components/Drawer/EmptyState.js +28 -0
  4. package/lib/module/components/Drawer/EmptyState.js.map +1 -0
  5. package/lib/module/components/Drawer/LoadingState.js +28 -0
  6. package/lib/module/components/Drawer/LoadingState.js.map +1 -0
  7. package/lib/module/components/Drawer/NewChatButton.js +24 -0
  8. package/lib/module/components/Drawer/NewChatButton.js.map +1 -0
  9. package/lib/module/components/Drawer/SearchInput.js +34 -0
  10. package/lib/module/components/Drawer/SearchInput.js.map +1 -0
  11. package/lib/module/components/Drawer/SessionItem.js +53 -0
  12. package/lib/module/components/Drawer/SessionItem.js.map +1 -0
  13. package/lib/module/components/Drawer/SessionList.js +55 -0
  14. package/lib/module/components/Drawer/SessionList.js.map +1 -0
  15. package/lib/module/components/Drawer/index.js +51 -0
  16. package/lib/module/components/Drawer/index.js.map +1 -0
  17. package/lib/module/components/chat/ChatEmpty.js +5 -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 +8 -7
  22. package/lib/module/components/chat/ChatMessageList.js.map +1 -1
  23. package/lib/module/components/chat/footer/index.js +51 -6
  24. package/lib/module/components/chat/footer/index.js.map +1 -1
  25. package/lib/module/components/chat/index.js +13 -2
  26. package/lib/module/components/chat/index.js.map +1 -1
  27. package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js +3 -2
  28. package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js.map +1 -1
  29. package/lib/module/components/chat/item/ChatUserMessageItem.js +13 -4
  30. package/lib/module/components/chat/item/ChatUserMessageItem.js.map +1 -1
  31. package/lib/module/components/chat/item/index.js +4 -2
  32. package/lib/module/components/chat/item/index.js.map +1 -1
  33. package/lib/module/components/product/CardHorizontal.js +5 -0
  34. package/lib/module/components/product/CardHorizontal.js.map +1 -1
  35. package/lib/module/constants/events.js +2 -1
  36. package/lib/module/constants/events.js.map +1 -1
  37. package/lib/module/constants/query.js +2 -1
  38. package/lib/module/constants/query.js.map +1 -1
  39. package/lib/module/context/ChatContext.js +23 -6
  40. package/lib/module/context/ChatContext.js.map +1 -1
  41. package/lib/module/hooks/message/useMessage.js +36 -4
  42. package/lib/module/hooks/message/useMessage.js.map +1 -1
  43. package/lib/module/hooks/message/useSendMessage.js +19 -6
  44. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  45. package/lib/module/hooks/message/useStreamMessage.js +39 -10
  46. package/lib/module/hooks/message/useStreamMessage.js.map +1 -1
  47. package/lib/module/hooks/session/useSearchSessions.js +51 -0
  48. package/lib/module/hooks/session/useSearchSessions.js.map +1 -0
  49. package/lib/module/services/endpoints.js +2 -1
  50. package/lib/module/services/endpoints.js.map +1 -1
  51. package/lib/module/translation/index.js +28 -0
  52. package/lib/module/translation/index.js.map +1 -0
  53. package/lib/module/translation/resources/vi.js +12 -0
  54. package/lib/module/translation/resources/vi.js.map +1 -0
  55. package/lib/module/utils/device.js +2 -2
  56. package/lib/module/utils/device.js.map +1 -1
  57. package/lib/typescript/src/components/Drawer/DrawerContent.d.ts +3 -0
  58. package/lib/typescript/src/components/Drawer/DrawerContent.d.ts.map +1 -0
  59. package/lib/typescript/src/components/Drawer/EmptyState.d.ts +3 -0
  60. package/lib/typescript/src/components/Drawer/EmptyState.d.ts.map +1 -0
  61. package/lib/typescript/src/components/Drawer/LoadingState.d.ts +3 -0
  62. package/lib/typescript/src/components/Drawer/LoadingState.d.ts.map +1 -0
  63. package/lib/typescript/src/components/Drawer/NewChatButton.d.ts +6 -0
  64. package/lib/typescript/src/components/Drawer/NewChatButton.d.ts.map +1 -0
  65. package/lib/typescript/src/components/Drawer/SearchInput.d.ts +7 -0
  66. package/lib/typescript/src/components/Drawer/SearchInput.d.ts.map +1 -0
  67. package/lib/typescript/src/components/Drawer/SessionItem.d.ts +9 -0
  68. package/lib/typescript/src/components/Drawer/SessionItem.d.ts.map +1 -0
  69. package/lib/typescript/src/components/Drawer/SessionList.d.ts +15 -0
  70. package/lib/typescript/src/components/Drawer/SessionList.d.ts.map +1 -0
  71. package/lib/typescript/src/components/Drawer/index.d.ts +7 -0
  72. package/lib/typescript/src/components/Drawer/index.d.ts.map +1 -0
  73. package/lib/typescript/src/components/chat/ChatEmpty.d.ts.map +1 -1
  74. package/lib/typescript/src/components/chat/ChatHeader.d.ts.map +1 -1
  75. package/lib/typescript/src/components/chat/ChatMessageList.d.ts +5 -1
  76. package/lib/typescript/src/components/chat/ChatMessageList.d.ts.map +1 -1
  77. package/lib/typescript/src/components/chat/footer/index.d.ts +5 -1
  78. package/lib/typescript/src/components/chat/footer/index.d.ts.map +1 -1
  79. package/lib/typescript/src/components/chat/index.d.ts.map +1 -1
  80. package/lib/typescript/src/components/chat/item/ChatAIAnswerMessageItem.d.ts.map +1 -1
  81. package/lib/typescript/src/components/chat/item/ChatUserMessageItem.d.ts +2 -1
  82. package/lib/typescript/src/components/chat/item/ChatUserMessageItem.d.ts.map +1 -1
  83. package/lib/typescript/src/constants/events.d.ts +1 -0
  84. package/lib/typescript/src/constants/events.d.ts.map +1 -1
  85. package/lib/typescript/src/constants/query.d.ts +1 -0
  86. package/lib/typescript/src/constants/query.d.ts.map +1 -1
  87. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
  88. package/lib/typescript/src/hooks/message/useMessage.d.ts.map +1 -1
  89. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -1
  90. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  91. package/lib/typescript/src/hooks/message/useStreamMessage.d.ts +3 -2
  92. package/lib/typescript/src/hooks/message/useStreamMessage.d.ts.map +1 -1
  93. package/lib/typescript/src/hooks/session/useSearchSessions.d.ts +2 -0
  94. package/lib/typescript/src/hooks/session/useSearchSessions.d.ts.map +1 -0
  95. package/lib/typescript/src/services/endpoints.d.ts +1 -0
  96. package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
  97. package/lib/typescript/src/translation/index.d.ts +5 -0
  98. package/lib/typescript/src/translation/index.d.ts.map +1 -0
  99. package/lib/typescript/src/translation/resources/vi.d.ts +11 -0
  100. package/lib/typescript/src/translation/resources/vi.d.ts.map +1 -0
  101. package/lib/typescript/src/types/chat.d.ts +2 -0
  102. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  103. package/lib/typescript/src/types/dto.d.ts +16 -0
  104. package/lib/typescript/src/types/dto.d.ts.map +1 -1
  105. package/lib/typescript/src/utils/device.d.ts.map +1 -1
  106. package/package.json +5 -1
  107. package/src/components/Drawer/DrawerContent.tsx +94 -0
  108. package/src/components/Drawer/EmptyState.tsx +26 -0
  109. package/src/components/Drawer/LoadingState.tsx +26 -0
  110. package/src/components/Drawer/NewChatButton.tsx +25 -0
  111. package/src/components/Drawer/SearchInput.tsx +37 -0
  112. package/src/components/Drawer/SessionItem.tsx +68 -0
  113. package/src/components/Drawer/SessionList.tsx +71 -0
  114. package/src/components/Drawer/index.tsx +57 -0
  115. package/src/components/chat/ChatEmpty.tsx +5 -6
  116. package/src/components/chat/ChatHeader.tsx +14 -6
  117. package/src/components/chat/ChatMessageList.tsx +9 -5
  118. package/src/components/chat/footer/index.tsx +69 -7
  119. package/src/components/chat/index.tsx +14 -4
  120. package/src/components/chat/item/ChatAIAnswerMessageItem.tsx +3 -4
  121. package/src/components/chat/item/ChatUserMessageItem.tsx +13 -5
  122. package/src/components/chat/item/index.tsx +2 -2
  123. package/src/components/product/CardHorizontal.tsx +5 -0
  124. package/src/constants/events.ts +1 -0
  125. package/src/constants/query.ts +1 -0
  126. package/src/context/ChatContext.tsx +21 -3
  127. package/src/hooks/message/useMessage.ts +54 -4
  128. package/src/hooks/message/useSendMessage.ts +22 -5
  129. package/src/hooks/message/useStreamMessage.ts +59 -12
  130. package/src/hooks/session/useSearchSessions.ts +62 -0
  131. package/src/services/endpoints.ts +1 -0
  132. package/src/translation/index.ts +22 -0
  133. package/src/translation/resources/vi.ts +10 -0
  134. package/src/types/chat.ts +2 -0
  135. package/src/types/dto.ts +19 -0
  136. package/src/utils/device.ts +9 -8
@@ -0,0 +1,71 @@
1
+ import { memo, useCallback } from 'react';
2
+ import { StyleSheet, RefreshControl } from 'react-native';
3
+ import { FlashList } from '@shopify/flash-list';
4
+ import { KSpacingValue } from '@droppii/libs';
5
+ import { SessionSearchItem } from '../../types/dto';
6
+ import SessionItem from './SessionItem';
7
+ import EmptyState from './EmptyState';
8
+ import { KContainer } from '@droppii/libs';
9
+
10
+ const ItemSeparator = () => <KContainer.View height={6} />;
11
+
12
+ interface SessionListProps {
13
+ sessions: SessionSearchItem[];
14
+ activeSessionId?: string;
15
+ isLoading: boolean;
16
+ isRefetching: boolean;
17
+ hasNextPage: boolean;
18
+ isFetchingNextPage: boolean;
19
+ onPressSession: (item: SessionSearchItem) => void;
20
+ onRefresh: () => void;
21
+ onEndReached: () => void;
22
+ }
23
+
24
+ const SessionList = memo<SessionListProps>(
25
+ ({
26
+ sessions,
27
+ activeSessionId: _activeSessionId,
28
+ isLoading,
29
+ isRefetching,
30
+ onPressSession,
31
+ onRefresh,
32
+ onEndReached,
33
+ }) => {
34
+ const renderItem = useCallback(
35
+ ({ item }: { item: SessionSearchItem }) => (
36
+ <SessionItem item={item} isActive={false} onPress={onPressSession} />
37
+ ),
38
+ [onPressSession]
39
+ );
40
+
41
+ const keyExtractor = useCallback((item: SessionSearchItem) => item.id, []);
42
+
43
+ return (
44
+ <FlashList
45
+ data={sessions}
46
+ renderItem={renderItem}
47
+ keyExtractor={keyExtractor}
48
+ contentContainerStyle={styles.listContainer}
49
+ refreshControl={
50
+ <RefreshControl refreshing={isRefetching} onRefresh={onRefresh} />
51
+ }
52
+ onEndReached={onEndReached}
53
+ onEndReachedThreshold={0.5}
54
+ ItemSeparatorComponent={ItemSeparator}
55
+ estimatedItemSize={80}
56
+ ListEmptyComponent={!isLoading ? <EmptyState /> : null}
57
+ />
58
+ );
59
+ }
60
+ );
61
+
62
+ SessionList.displayName = 'SessionList';
63
+
64
+ const styles = StyleSheet.create({
65
+ listContainer: {
66
+ flexGrow: 1,
67
+ paddingHorizontal: KSpacingValue['1rem'],
68
+ },
69
+ });
70
+
71
+ export default SessionList;
@@ -0,0 +1,57 @@
1
+ import React from 'react';
2
+ import { useWindowDimensions } from 'react-native';
3
+ import { StyleSheet, View } from 'react-native';
4
+
5
+ import ReanimatedDrawerLayout, {
6
+ DrawerType,
7
+ DrawerPosition,
8
+ } from 'react-native-gesture-handler/ReanimatedDrawerLayout';
9
+ import DrawerContent from './DrawerContent';
10
+
11
+ interface ReanimatedDrawerExampleProps {
12
+ children: React.ReactNode;
13
+ }
14
+
15
+ const ReanimatedDrawerExample = React.forwardRef<
16
+ React.ComponentRef<typeof ReanimatedDrawerLayout>,
17
+ ReanimatedDrawerExampleProps
18
+ >(({ children }, ref) => {
19
+ const { width } = useWindowDimensions();
20
+ // const tapGesture = Gesture.Tap()
21
+ // .runOnJS(true)
22
+ // .onStart(() => {
23
+ // if (ref && 'current' in ref) {
24
+ // ref.current?.openDrawer();
25
+ // }
26
+ // });
27
+
28
+ return (
29
+ <ReanimatedDrawerLayout
30
+ ref={ref}
31
+ renderNavigationView={() => <DrawerContent />}
32
+ drawerPosition={DrawerPosition.LEFT}
33
+ drawerType={DrawerType.FRONT}
34
+ drawerWidth={width * 0.8}
35
+ >
36
+ <View style={styles.innerContainer}>{children}</View>
37
+ </ReanimatedDrawerLayout>
38
+ );
39
+ });
40
+
41
+ const styles = StyleSheet.create({
42
+ drawerContainer: {
43
+ flex: 1,
44
+ justifyContent: 'center',
45
+ alignItems: 'center',
46
+ backgroundColor: 'pink',
47
+ },
48
+ innerContainer: {
49
+ flex: 1,
50
+ },
51
+ box: {
52
+ padding: 20,
53
+ backgroundColor: 'pink',
54
+ },
55
+ });
56
+
57
+ export default ReanimatedDrawerExample;
@@ -5,6 +5,7 @@ import SuggestionItem from './SuggestionItem';
5
5
  import { useSendMessage } from '../../hooks/message/useSendMessage';
6
6
  import debounce from 'lodash/debounce';
7
7
  import { type ISuggestionItem } from '../../types';
8
+ import trans from '../../translation';
8
9
 
9
10
  const ChatEmpty = () => {
10
11
  const { data } = useFetchSuggestions();
@@ -20,14 +21,14 @@ const ChatEmpty = () => {
20
21
  style={styles.transformStyle}
21
22
  >
22
23
  <KLabel.Text typo="TextXLgMedium" center>
23
- {'Bạn cần hỗ trợ gì hôm nay?'}
24
+ {trans('chat_empty_title')}
24
25
  </KLabel.Text>
25
26
  <KContainer.View center>
26
27
  <KLabel.Text typo="TextMdNormal" center color={KColors.gray.normal}>
27
- {'Hãy đặt câu hỏi hoặc chia sẻ vấn đề của bạn!'}
28
+ {trans('chat_empty_description_1')}
28
29
  </KLabel.Text>
29
30
  <KLabel.Text typo="TextMdNormal" center color={KColors.gray.normal}>
30
- {'Những gợi ý dành cho bạn:'}
31
+ {trans('chat_empty_description_2')}
31
32
  </KLabel.Text>
32
33
  </KContainer.View>
33
34
  {data?.suggestions?.map((item: ISuggestionItem) => (
@@ -42,9 +43,7 @@ const ChatEmpty = () => {
42
43
  color={KColors.gray.light}
43
44
  textAlign="center"
44
45
  >
45
- {
46
- 'Lưu ý: Thông tin từ AI chỉ mang tính tham khảo, cần xác minh với chuyên gia hoặc nguồn uy tín.'
47
- }
46
+ {trans('ai_answer_note')}
48
47
  </KLabel.Text>
49
48
  </KContainer.ScrollView>
50
49
  );
@@ -1,3 +1,5 @@
1
+ import { useCallback } from 'react';
2
+ import type { ComponentType } from 'react';
1
3
  import {
2
4
  KColors,
3
5
  KContainer,
@@ -7,15 +9,21 @@ import {
7
9
  } from '@droppii/libs';
8
10
  import images from '../../assets/images';
9
11
  import { ImageBackground, StatusBar, StyleSheet } from 'react-native';
10
- import { useCallback } from 'react';
11
12
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
12
13
  import useSessionStore from '../../store/session';
13
14
  import { useChatContext } from '../../context/ChatContext';
14
15
 
16
+ // Type assertion to fix TypeScript issue with React Native components
17
+ const StatusBarComponent = StatusBar as unknown as ComponentType<{
18
+ translucent?: boolean;
19
+ }>;
20
+ const ImageBackgroundComponent =
21
+ ImageBackground as unknown as ComponentType<any>;
22
+
15
23
  export const ChatHeader = () => {
16
24
  const insets = useSafeAreaInsets();
17
25
  const setSessionId = useSessionStore((state) => state.setSessionId);
18
- const { cartButton } = useChatContext();
26
+ const { cartButton, openDrawer } = useChatContext();
19
27
 
20
28
  const onPressNewSession = () => {
21
29
  setSessionId(undefined);
@@ -24,15 +32,15 @@ export const ChatHeader = () => {
24
32
  const renderStatusBar = useCallback(() => {
25
33
  return (
26
34
  <KContainer.View height={insets.top} background="transparent">
27
- <StatusBar translucent />
35
+ <StatusBarComponent translucent />
28
36
  </KContainer.View>
29
37
  );
30
38
  }, [insets.top]);
31
39
  return (
32
- <ImageBackground source={images.bg_header} resizeMode="cover">
40
+ <ImageBackgroundComponent source={images.bg_header} resizeMode="cover">
33
41
  {renderStatusBar()}
34
42
  <KContainer.View style={styles.header}>
35
- <KContainer.Touchable style={styles.button}>
43
+ <KContainer.Touchable style={styles.button} onPress={openDrawer}>
36
44
  <KImage.VectorIcons
37
45
  name="menu-ai-o"
38
46
  size={24}
@@ -51,7 +59,7 @@ export const ChatHeader = () => {
51
59
  </KContainer.Touchable>
52
60
  {cartButton}
53
61
  </KContainer.View>
54
- </ImageBackground>
62
+ </ImageBackgroundComponent>
55
63
  );
56
64
  };
57
65
 
@@ -1,6 +1,6 @@
1
1
  import { FlatList, StyleSheet, DeviceEventEmitter } from 'react-native';
2
+ import type { ComponentType } from 'react';
2
3
  import ChatEmpty from './ChatEmpty';
3
- import { useMessage } from '../../hooks/message/useMessage';
4
4
  import ChatItem from './item';
5
5
  import { KSpacingValue } from '@droppii/libs';
6
6
  import type { IMessageItem } from '../../types';
@@ -8,9 +8,13 @@ import { useCallback, useRef } from 'react';
8
8
  import { useEffect } from 'react';
9
9
  import { events } from '../../constants/events';
10
10
 
11
- const ChatMessageList = () => {
12
- const { messageState } = useMessage();
11
+ // Type assertion to fix TypeScript issue with React Native components
12
+ const FlatListComponent = FlatList as unknown as ComponentType<any>;
13
13
 
14
+ interface IChatMessageListProps {
15
+ messageList?: IMessageItem[];
16
+ }
17
+ const ChatMessageList = ({ messageList = [] }: IChatMessageListProps) => {
14
18
  const flatListRef = useRef<any>(null);
15
19
  const scrollOffsetRef = useRef(0);
16
20
 
@@ -42,9 +46,9 @@ const ChatMessageList = () => {
42
46
  }, []);
43
47
 
44
48
  return (
45
- <FlatList
49
+ <FlatListComponent
46
50
  ref={flatListRef}
47
- data={messageState?.messages || []}
51
+ data={messageList}
48
52
  renderItem={({ item, index }: { item: IMessageItem; index: number }) => (
49
53
  <ChatItem item={item} index={index} />
50
54
  )}
@@ -9,12 +9,16 @@ import {
9
9
  KSpacingValue,
10
10
  } from '@droppii/libs';
11
11
  import { useCallback, useState, useRef, useMemo } from 'react';
12
+ import type { ComponentType } from 'react';
12
13
  import {
13
14
  FlatList,
14
15
  StyleSheet,
15
16
  TouchableWithoutFeedback,
16
17
  Keyboard,
17
18
  } from 'react-native';
19
+
20
+ // Type assertion to fix TypeScript issue with React Native components
21
+ const FlatListComponent = FlatList as unknown as ComponentType<any>;
18
22
  import debounce from 'lodash/debounce';
19
23
  import { useSendMessage } from '../../../hooks/message/useSendMessage';
20
24
  import useStreamMessageStore from '../../../store/streamMessage';
@@ -35,8 +39,14 @@ import UploadImageItem from './item/UploadImageItem';
35
39
  import { isSameFile, shortenFileName } from '../../../utils/common';
36
40
  import { DocumentPickerResponse, types } from '@react-native-documents/picker';
37
41
  import UploadFileItem from './item/UploadFileItem';
38
- import { IAttachment } from '../../../types';
42
+ import {
43
+ IAttachment,
44
+ IMessageItem,
45
+ ISuggestionItem,
46
+ MessageType,
47
+ } from '../../../types';
39
48
  import UIUtils from '../../../utils/ui';
49
+ import trans from '../../../translation';
40
50
 
41
51
  const { Popover } = renderers;
42
52
  export interface ImageUpload extends Image {
@@ -57,7 +67,11 @@ const MAX_FILE_UPLOAD = 3;
57
67
  const MAX_FILE_SIZE = 30 * 1024 * 1024;
58
68
  const MAX_IMAGE_SIZE = 7 * 1024 * 1024;
59
69
 
60
- const ChatFooter = () => {
70
+ interface IChatFooterProps {
71
+ lastMessage?: IMessageItem;
72
+ }
73
+
74
+ const ChatFooter = ({ lastMessage }: IChatFooterProps) => {
61
75
  const menuRef = useRef(null);
62
76
  const { onSendMessage, stopStream } = useSendMessage();
63
77
  const [message, setMessage] = useState('');
@@ -76,6 +90,14 @@ const ChatFooter = () => {
76
90
  );
77
91
  }, [isStreaming, message, fileUpload]);
78
92
 
93
+ const isShowSuggestions = useMemo(() => {
94
+ return !!(
95
+ lastMessage?.type === MessageType.ai_answer &&
96
+ lastMessage?.suggestions &&
97
+ lastMessage?.suggestions?.length > 0
98
+ );
99
+ }, [lastMessage]);
100
+
79
101
  const onPressImagePicker = useCallback(async () => {
80
102
  Keyboard.dismiss();
81
103
  const totalImages = fileUpload.filter((i) => i.uploadType === 'image');
@@ -153,12 +175,12 @@ const ChatFooter = () => {
153
175
  {
154
176
  icon: 'image-o',
155
177
  onPress: onPressImagePicker,
156
- label: 'Thư viện ảnh',
178
+ label: trans('photo_library'),
157
179
  },
158
180
  {
159
181
  icon: 'camera-o',
160
182
  onPress: onPressCameraPicker,
161
- label: 'Chụp ảnh',
183
+ label: trans('camera'),
162
184
  },
163
185
  ];
164
186
  }, [onPressImagePicker, onPressCameraPicker]);
@@ -285,10 +307,39 @@ const ChatFooter = () => {
285
307
  }
286
308
  }, [fileUpload]);
287
309
 
310
+ const onPressItemSuggestion = debounce((item: ISuggestionItem) => {
311
+ onSendMessage(item.content);
312
+ }, 200);
313
+
314
+ const renderSuggestionItem = useCallback(
315
+ (item: ISuggestionItem) => {
316
+ return (
317
+ <KContainer.Touchable
318
+ style={styles.suggestionItem}
319
+ onPress={() => onPressItemSuggestion(item)}
320
+ >
321
+ <KLabel.Text typo="TextSmNormal">{item?.content || ''}</KLabel.Text>
322
+ </KContainer.Touchable>
323
+ );
324
+ },
325
+ [onPressItemSuggestion]
326
+ );
327
+
288
328
  return (
289
329
  <KContainer.View style={styles.container}>
330
+ <KContainer.VisibleView visible={isShowSuggestions}>
331
+ <FlatListComponent
332
+ data={lastMessage?.suggestions || []}
333
+ renderItem={({ item }: { item: ISuggestionItem }) =>
334
+ renderSuggestionItem(item)
335
+ }
336
+ horizontal
337
+ keyExtractor={(item: ISuggestionItem) => item.suggestion_id}
338
+ contentContainerStyle={styles.suggessionContainer}
339
+ />
340
+ </KContainer.VisibleView>
290
341
  <KContainer.VisibleView visible={fileUpload.length > 0}>
291
- <FlatList
342
+ <FlatListComponent
292
343
  data={fileUpload}
293
344
  renderItem={({ item }: { item: UploadItem }) =>
294
345
  renderUploadItem(item)
@@ -301,9 +352,9 @@ const ChatFooter = () => {
301
352
  />
302
353
  </KContainer.VisibleView>
303
354
  <KInput.TextArea
304
- paddingV={'0.25rem'}
355
+ paddingV={0}
305
356
  paddingH={'0.25rem'}
306
- placeholder="Bạn muốn hỏi gì hôm nay?"
357
+ placeholder={trans('input_placeholder')}
307
358
  clearButtonMode="hidden"
308
359
  onChangeText={debouncedMessage}
309
360
  value={message}
@@ -409,4 +460,15 @@ const styles = StyleSheet.create({
409
460
  gap: KSpacingValue['0.75rem'],
410
461
  paddingTop: 6,
411
462
  },
463
+ suggessionContainer: {
464
+ gap: KSpacingValue['0.25rem'],
465
+ },
466
+ suggestionItem: {
467
+ backgroundColor: KColors.palette.gray.w25,
468
+ borderRadius: KRadiusValue['4x'],
469
+ height: 32,
470
+ justifyContent: 'center',
471
+ alignItems: 'center',
472
+ paddingHorizontal: KSpacingValue['0.75rem'],
473
+ },
412
474
  });
@@ -3,18 +3,28 @@ import ChatHeader from './ChatHeader';
3
3
  import ChatMessageList from './ChatMessageList';
4
4
  import ChatFooter from './footer';
5
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>;
11
+ import { useMessage } from '../../hooks/message/useMessage';
6
12
 
7
13
  const ChatBotAI = () => {
14
+ const { messageState } = useMessage();
15
+
16
+ const lastMessage = messageState?.messages?.[0];
17
+
8
18
  return (
9
19
  <KContainer.Page>
10
- <KeyboardAvoidingView
20
+ <KeyboardAvoidingViewComponent
11
21
  style={styles.container}
12
22
  behavior={Platform.OS === 'ios' ? 'padding' : undefined}
13
23
  >
14
24
  <ChatHeader />
15
- <ChatMessageList />
16
- <ChatFooter />
17
- </KeyboardAvoidingView>
25
+ <ChatMessageList messageList={messageState?.messages || []} />
26
+ <ChatFooter lastMessage={lastMessage} />
27
+ </KeyboardAvoidingViewComponent>
18
28
  </KContainer.Page>
19
29
  );
20
30
  };
@@ -21,6 +21,7 @@ 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
25
 
25
26
  interface ChatAIAnswerMessageItemProps {
26
27
  item: IMessageItem;
@@ -288,7 +289,7 @@ class CustomRenderer extends Renderer implements RendererInterface {
288
289
  );
289
290
  }
290
291
 
291
- hr(_children: string | ReactNode[], _style?: any): ReactNode {
292
+ hr(_styles?: any): ReactNode {
292
293
  return (
293
294
  <KDivider
294
295
  key={this.getKey()}
@@ -425,9 +426,7 @@ const ChatAIAnswerMessageItem = ({
425
426
  color={KColors.gray.light}
426
427
  textAlign="center"
427
428
  >
428
- {
429
- 'Lưu ý: Thông tin từ AI chỉ mang tính tham khảo, cần xác minh với chuyên gia hoặc nguồn uy tín.'
430
- }
429
+ {trans('ai_answer_note')}
431
430
  </KLabel.Text>
432
431
  </KContainer.VisibleView>
433
432
  </KContainer.View>
@@ -17,15 +17,20 @@ import { IconPdf } from '../../../assets/svgIcon/IconPdf';
17
17
  import ReactNativeBlobUtil from 'react-native-blob-util';
18
18
  import UIUtils from '../../../utils/ui';
19
19
  import FileViewer from 'react-native-file-viewer';
20
+ import { useSendMessage } from '../../../hooks/message/useSendMessage';
20
21
 
21
22
  interface ChatUserMessageItemProps {
22
23
  item: IMessageItem;
24
+ isLast: boolean;
23
25
  }
24
26
 
25
27
  const IMAGE_SIZE =
26
28
  (Dimensions.get('window').width * 0.8 - KSpacingValue['3.5rem']) / 3;
27
- const ChatUserMessageItem = ({ item }: ChatUserMessageItemProps) => {
29
+
30
+ const ChatUserMessageItem = ({ item, isLast }: ChatUserMessageItemProps) => {
28
31
  const { openImageViewer } = useChatContext();
32
+ const { retryStream } = useSendMessage();
33
+
29
34
  const images = item?.attachments?.filter(
30
35
  (attachment) => attachment.type === 'image'
31
36
  );
@@ -60,6 +65,10 @@ const ChatUserMessageItem = ({ item }: ChatUserMessageItemProps) => {
60
65
  });
61
66
  }, []);
62
67
 
68
+ const onPressRetry = useCallback(() => {
69
+ retryStream(item);
70
+ }, [retryStream, item]);
71
+
63
72
  return (
64
73
  <KContainer.View style={styles.container}>
65
74
  <KContainer.View style={styles.content}>
@@ -110,9 +119,7 @@ const ChatUserMessageItem = ({ item }: ChatUserMessageItemProps) => {
110
119
  <IconChatArrow fill={'#0077FF'} />
111
120
  </KContainer.View>
112
121
  </KContainer.View>
113
- <KContainer.VisibleView
114
- visible={item?.metadata?.status === 'tempErrorStatus'}
115
- >
122
+ <KContainer.VisibleView visible={item?.metadata?.status === 'error'}>
116
123
  <KContainer.View style={styles.errorContainer}>
117
124
  <KImage.VectorIcons
118
125
  name="warning-circle-b"
@@ -123,7 +130,7 @@ const ChatUserMessageItem = ({ item }: ChatUserMessageItemProps) => {
123
130
  {'Chatbot đang lỗi. Vui lòng thử lại.'}
124
131
  </KLabel.Text>
125
132
  <KButton.Outline
126
- onPress={() => {}}
133
+ onPress={onPressRetry}
127
134
  kind="danger"
128
135
  label="Thử lại"
129
136
  size="sm"
@@ -132,6 +139,7 @@ const ChatUserMessageItem = ({ item }: ChatUserMessageItemProps) => {
132
139
  size: 16,
133
140
  tintColor: KColors.danger.normal,
134
141
  }}
142
+ disabled={!isLast}
135
143
  />
136
144
  </KContainer.View>
137
145
  </KContainer.VisibleView>
@@ -10,13 +10,13 @@ interface ChatItemProps {
10
10
  const ChatItem = ({ item, index }: ChatItemProps) => {
11
11
  switch (item.type) {
12
12
  case MessageType.user_message:
13
- return <ChatUserMessageItem item={item} />;
13
+ return <ChatUserMessageItem item={item} isLast={index === 0} />;
14
14
  case MessageType.ai_answer:
15
15
  return <ChatAIAnswerMessageItem item={item} isLast={index === 0} />;
16
16
  case MessageType.ai_thinking:
17
17
  return <ChatAIThinkingMessageItem item={item} isLast={index === 0} />;
18
18
  default:
19
- return <ChatUserMessageItem item={item} />;
19
+ return <ChatUserMessageItem item={item} isLast={index === 0} />;
20
20
  }
21
21
  };
22
22
 
@@ -387,4 +387,9 @@ const styles = StyleSheet.create({
387
387
  top: 0,
388
388
  right: 0,
389
389
  },
390
+ voucherContainer: {
391
+ flexDirection: 'row',
392
+ gap: KSpacingValue['0.25rem'],
393
+ marginTop: KSpacingValue['0.25rem'],
394
+ },
390
395
  });
@@ -3,4 +3,5 @@ export const events = {
3
3
  updateMultipleMessage: 'update_multiple_message',
4
4
  forceUpdateMessages: 'force_update_messages',
5
5
  expandThinkingStep: 'expand_thinking_step',
6
+ updateMessageError: 'update_message_error',
6
7
  };
@@ -3,4 +3,5 @@ export const QUERY_KEYS = {
3
3
  GET_SESSION_BY_ID: 'GET_SESSION_BY_ID',
4
4
  CREATE_SESSION: 'CREATE_SESSION',
5
5
  SEARCH_PRODUCT: 'SEARCH_PRODUCT',
6
+ SEARCH_SESSIONS: 'SEARCH_SESSIONS',
6
7
  };
@@ -1,7 +1,8 @@
1
- import { createContext, useContext } from 'react';
1
+ import { createContext, useContext, useRef } from 'react';
2
2
  import type { ChatContextType, ChatProviderProps } from '../types/chat';
3
3
  import ChatBotAI from '../components/chat';
4
4
  import Portal from '../components/portal';
5
+ import ReanimatedDrawerExample from '../components/Drawer';
5
6
 
6
7
  export const ChatContext = createContext<ChatContextType>({
7
8
  apiAddress: '',
@@ -12,11 +13,21 @@ export const ChatContext = createContext<ChatContextType>({
12
13
  onNavigateToProduct: () => {},
13
14
  csTeamId: '',
14
15
  pushLinkTo: () => {},
16
+ openDrawer: () => {},
17
+ closeDrawer: () => {},
15
18
  });
16
19
 
17
20
  export const useChatContext = () => useContext(ChatContext);
18
21
 
19
22
  export const ChatProvider = (props: ChatProviderProps) => {
23
+ const ref = useRef<any>(null);
24
+ const openDrawer = () => {
25
+ ref.current?.openDrawer();
26
+ };
27
+
28
+ const closeDrawer = () => {
29
+ ref.current?.closeDrawer();
30
+ };
20
31
  const {
21
32
  apiAddress,
22
33
  userId,
@@ -28,6 +39,7 @@ export const ChatProvider = (props: ChatProviderProps) => {
28
39
  csTeamId,
29
40
  pushLinkTo,
30
41
  } = props;
42
+
31
43
  return (
32
44
  <ChatContext.Provider
33
45
  value={{
@@ -40,10 +52,16 @@ export const ChatProvider = (props: ChatProviderProps) => {
40
52
  onNavigateToProduct,
41
53
  csTeamId,
42
54
  pushLinkTo,
55
+ openDrawer,
56
+ closeDrawer,
43
57
  }}
44
58
  >
45
- <ChatBotAI />
46
- <Portal />
59
+ <ReanimatedDrawerExample ref={ref}>
60
+ <>
61
+ <ChatBotAI />
62
+ <Portal />
63
+ </>
64
+ </ReanimatedDrawerExample>
47
65
  </ChatContext.Provider>
48
66
  );
49
67
  };