react-native-chatbot-ai 0.0.5 → 0.0.6

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 (155) hide show
  1. package/lib/module/assets/images/index.js.map +1 -0
  2. package/lib/module/assets/svgIcon/IconChatArrow.js +25 -0
  3. package/lib/module/assets/svgIcon/IconChatArrow.js.map +1 -0
  4. package/lib/module/assets/svgIcon/IconThinkingStep.js +45 -0
  5. package/lib/module/assets/svgIcon/IconThinkingStep.js.map +1 -0
  6. package/lib/module/components/chat/ChatEmpty.js +60 -0
  7. package/lib/module/components/chat/ChatEmpty.js.map +1 -0
  8. package/lib/module/components/chat/ChatFooter.js +89 -0
  9. package/lib/module/components/chat/ChatFooter.js.map +1 -0
  10. package/lib/module/components/chat/ChatHeader.js +27 -9
  11. package/lib/module/components/chat/ChatHeader.js.map +1 -1
  12. package/lib/module/components/chat/ChatMessageList.js +38 -0
  13. package/lib/module/components/chat/ChatMessageList.js.map +1 -0
  14. package/lib/module/components/chat/SuggestionItem.js +80 -0
  15. package/lib/module/components/chat/SuggestionItem.js.map +1 -0
  16. package/lib/module/components/chat/index.js +14 -2
  17. package/lib/module/components/chat/index.js.map +1 -1
  18. package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js +82 -0
  19. package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js.map +1 -0
  20. package/lib/module/components/chat/item/ChatAIThinkingMessageItem.js +126 -0
  21. package/lib/module/components/chat/item/ChatAIThinkingMessageItem.js.map +1 -0
  22. package/lib/module/components/chat/item/ChatUserMessageItem.js +41 -0
  23. package/lib/module/components/chat/item/ChatUserMessageItem.js.map +1 -0
  24. package/lib/module/components/chat/item/index.js +33 -0
  25. package/lib/module/components/chat/item/index.js.map +1 -0
  26. package/lib/module/constants/events.js +8 -0
  27. package/lib/module/constants/events.js.map +1 -0
  28. package/lib/module/constants/query.js +8 -0
  29. package/lib/module/constants/query.js.map +1 -0
  30. package/lib/module/context/ChatContext.js +17 -7
  31. package/lib/module/context/ChatContext.js.map +1 -1
  32. package/lib/module/hooks/message/useMessage.js +86 -0
  33. package/lib/module/hooks/message/useMessage.js.map +1 -0
  34. package/lib/module/hooks/message/useSendMessage.js +58 -0
  35. package/lib/module/hooks/message/useSendMessage.js.map +1 -0
  36. package/lib/module/hooks/message/useStreamMessage.js +113 -0
  37. package/lib/module/hooks/message/useStreamMessage.js.map +1 -0
  38. package/lib/module/hooks/session/useCreateSession.js +18 -0
  39. package/lib/module/hooks/session/useCreateSession.js.map +1 -0
  40. package/lib/module/hooks/session/useFetchSessionById.js +14 -0
  41. package/lib/module/hooks/session/useFetchSessionById.js.map +1 -0
  42. package/lib/module/hooks/suggestions/useFetchSuggestions.js +15 -0
  43. package/lib/module/hooks/suggestions/useFetchSuggestions.js.map +1 -0
  44. package/lib/module/index.js +2 -2
  45. package/lib/module/index.js.map +1 -1
  46. package/lib/module/services/endpoints.js +11 -0
  47. package/lib/module/services/endpoints.js.map +1 -0
  48. package/lib/module/store/session.js +11 -0
  49. package/lib/module/store/session.js.map +1 -0
  50. package/lib/module/store/streamMessage.js +26 -0
  51. package/lib/module/store/streamMessage.js.map +1 -0
  52. package/lib/module/types/common.js +23 -0
  53. package/lib/module/types/common.js.map +1 -0
  54. package/lib/module/types/dto.js +2 -0
  55. package/lib/module/types/index.js +6 -0
  56. package/lib/module/types/index.js.map +1 -0
  57. package/lib/typescript/src/assets/images/index.d.ts.map +1 -0
  58. package/lib/typescript/src/assets/svgIcon/IconChatArrow.d.ts +9 -0
  59. package/lib/typescript/src/assets/svgIcon/IconChatArrow.d.ts.map +1 -0
  60. package/lib/typescript/src/assets/svgIcon/IconThinkingStep.d.ts +7 -0
  61. package/lib/typescript/src/assets/svgIcon/IconThinkingStep.d.ts.map +1 -0
  62. package/lib/typescript/src/components/chat/ChatEmpty.d.ts +3 -0
  63. package/lib/typescript/src/components/chat/ChatEmpty.d.ts.map +1 -0
  64. package/lib/typescript/src/components/chat/ChatFooter.d.ts +3 -0
  65. package/lib/typescript/src/components/chat/ChatFooter.d.ts.map +1 -0
  66. package/lib/typescript/src/components/chat/ChatHeader.d.ts.map +1 -1
  67. package/lib/typescript/src/components/chat/ChatMessageList.d.ts +3 -0
  68. package/lib/typescript/src/components/chat/ChatMessageList.d.ts.map +1 -0
  69. package/lib/typescript/src/components/chat/SuggestionItem.d.ts +8 -0
  70. package/lib/typescript/src/components/chat/SuggestionItem.d.ts.map +1 -0
  71. package/lib/typescript/src/components/chat/index.d.ts.map +1 -1
  72. package/lib/typescript/src/components/chat/item/ChatAIAnswerMessageItem.d.ts +8 -0
  73. package/lib/typescript/src/components/chat/item/ChatAIAnswerMessageItem.d.ts.map +1 -0
  74. package/lib/typescript/src/components/chat/item/ChatAIThinkingMessageItem.d.ts +7 -0
  75. package/lib/typescript/src/components/chat/item/ChatAIThinkingMessageItem.d.ts.map +1 -0
  76. package/lib/typescript/src/components/chat/item/ChatUserMessageItem.d.ts +7 -0
  77. package/lib/typescript/src/components/chat/item/ChatUserMessageItem.d.ts.map +1 -0
  78. package/lib/typescript/src/components/chat/item/index.d.ts +8 -0
  79. package/lib/typescript/src/components/chat/item/index.d.ts.map +1 -0
  80. package/lib/typescript/src/constants/events.d.ts +6 -0
  81. package/lib/typescript/src/constants/events.d.ts.map +1 -0
  82. package/lib/typescript/src/constants/query.d.ts +6 -0
  83. package/lib/typescript/src/constants/query.d.ts.map +1 -0
  84. package/lib/typescript/src/context/ChatContext.d.ts +2 -1
  85. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
  86. package/lib/typescript/src/hooks/message/useMessage.d.ts +5 -0
  87. package/lib/typescript/src/hooks/message/useMessage.d.ts.map +1 -0
  88. package/lib/typescript/src/hooks/message/useSendMessage.d.ts +5 -0
  89. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -0
  90. package/lib/typescript/src/hooks/message/useStreamMessage.d.ts +6 -0
  91. package/lib/typescript/src/hooks/message/useStreamMessage.d.ts.map +1 -0
  92. package/lib/typescript/src/hooks/session/useCreateSession.d.ts +2 -0
  93. package/lib/typescript/src/hooks/session/useCreateSession.d.ts.map +1 -0
  94. package/lib/typescript/src/hooks/session/useFetchSessionById.d.ts +2 -0
  95. package/lib/typescript/src/hooks/session/useFetchSessionById.d.ts.map +1 -0
  96. package/lib/typescript/src/hooks/suggestions/useFetchSuggestions.d.ts +2 -0
  97. package/lib/typescript/src/hooks/suggestions/useFetchSuggestions.d.ts.map +1 -0
  98. package/lib/typescript/src/index.d.ts +2 -2
  99. package/lib/typescript/src/index.d.ts.map +1 -1
  100. package/lib/typescript/src/services/endpoints.d.ts +9 -0
  101. package/lib/typescript/src/services/endpoints.d.ts.map +1 -0
  102. package/lib/typescript/src/store/session.d.ts +4 -0
  103. package/lib/typescript/src/store/session.d.ts.map +1 -0
  104. package/lib/typescript/src/store/streamMessage.d.ts +4 -0
  105. package/lib/typescript/src/store/streamMessage.d.ts.map +1 -0
  106. package/lib/typescript/src/types/chat.d.ts +16 -2
  107. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  108. package/lib/typescript/src/types/common.d.ts +29 -0
  109. package/lib/typescript/src/types/common.d.ts.map +1 -0
  110. package/lib/typescript/src/types/dto.d.ts +66 -0
  111. package/lib/typescript/src/types/dto.d.ts.map +1 -1
  112. package/lib/typescript/src/types/index.d.ts +4 -0
  113. package/lib/typescript/src/types/index.d.ts.map +1 -0
  114. package/package.json +5 -2
  115. package/src/assets/svgIcon/IconChatArrow.tsx +21 -0
  116. package/src/assets/svgIcon/IconThinkingStep.tsx +35 -0
  117. package/src/components/chat/ChatEmpty.tsx +52 -0
  118. package/src/components/chat/ChatFooter.tsx +82 -0
  119. package/src/components/chat/ChatHeader.tsx +18 -2
  120. package/src/components/chat/ChatMessageList.tsx +33 -0
  121. package/src/components/chat/SuggestionItem.tsx +64 -0
  122. package/src/components/chat/index.tsx +15 -1
  123. package/src/components/chat/item/ChatAIAnswerMessageItem.tsx +84 -0
  124. package/src/components/chat/item/ChatAIThinkingMessageItem.tsx +130 -0
  125. package/src/components/chat/item/ChatUserMessageItem.tsx +39 -0
  126. package/src/components/chat/item/index.tsx +23 -0
  127. package/src/constants/events.ts +5 -0
  128. package/src/constants/query.ts +5 -0
  129. package/src/context/ChatContext.tsx +12 -5
  130. package/src/hooks/message/useMessage.ts +94 -0
  131. package/src/hooks/message/useSendMessage.ts +58 -0
  132. package/src/hooks/message/useStreamMessage.ts +130 -0
  133. package/src/hooks/session/useCreateSession.ts +23 -0
  134. package/src/hooks/session/useFetchSessionById.ts +17 -0
  135. package/src/hooks/suggestions/useFetchSuggestions.ts +18 -0
  136. package/src/index.ts +3 -2
  137. package/src/services/endpoints.ts +9 -0
  138. package/src/store/session.ts +9 -0
  139. package/src/store/streamMessage.ts +24 -0
  140. package/src/types/chat.ts +21 -3
  141. package/src/types/common.ts +32 -0
  142. package/src/types/dto.ts +74 -0
  143. package/src/types/index.ts +3 -0
  144. package/lib/module/images/index.js.map +0 -1
  145. package/lib/module/utils/index.js +0 -2
  146. package/lib/module/utils/index.js.map +0 -1
  147. package/lib/typescript/src/images/index.d.ts.map +0 -1
  148. package/lib/typescript/src/utils/index.d.ts +0 -1
  149. package/lib/typescript/src/utils/index.d.ts.map +0 -1
  150. package/src/utils/index.ts +0 -0
  151. /package/lib/module/{images → assets/images}/bg_header.png +0 -0
  152. /package/lib/module/{images → assets/images}/index.js +0 -0
  153. /package/lib/typescript/src/{images → assets/images}/index.d.ts +0 -0
  154. /package/src/{images → assets/images}/bg_header.png +0 -0
  155. /package/src/{images → assets/images}/index.ts +0 -0
@@ -0,0 +1,35 @@
1
+ import Svg, { G, Mask, Path } from 'react-native-svg';
2
+
3
+ interface Props {
4
+ width?: number;
5
+ height?: number;
6
+ }
7
+
8
+ export const IconThinkingStep = (props: Props) => {
9
+ const { width = 24, height = 24 } = props || {};
10
+ return (
11
+ <Svg width={width} height={height} viewBox="0 0 24 24" fill="none" {...props}>
12
+ <Mask id="a" maskUnits="userSpaceOnUse" x={-8} y={-8} width={40} height={40}>
13
+ <Path d="M32-8H-8v40h40V-8z" fill="#fff" />
14
+ </Mask>
15
+ <G mask="url(#a)">
16
+ <Path
17
+ d="M5.093 16.972a.976.976 0 01.815.064 8.75 8.75 0 004.37 1.151c4.74 0 8.583-3.692 8.583-8.25s-3.844-8.25-8.583-8.25c-4.739 0-8.583 3.692-8.583 8.25a7.96 7.96 0 001.003 3.877c.139.242.173.53.094.798l-.896 2.92a.344.344 0 00.445.424l2.752-.984z"
18
+ fill="#D5DBDF"
19
+ />
20
+ <Path
21
+ d="M6.164 8.563a1.376 1.376 0 11-.001 2.75 1.376 1.376 0 01.001-2.75zM10.289 8.563a1.376 1.376 0 11-.001 2.75 1.376 1.376 0 01.001-2.75zM14.414 8.563a1.376 1.376 0 11-.001 2.75 1.376 1.376 0 01.001-2.75z"
22
+ fill="#fff"
23
+ />
24
+ <Path
25
+ d="M18.9 21.099a.977.977 0 00-.813.064 8.76 8.76 0 01-4.371 1.15c-4.74 0-8.583-3.686-8.583-8.236 0-4.55 3.844-8.236 8.583-8.236 4.739 0 8.583 3.684 8.583 8.236a7.94 7.94 0 01-1.003 3.87c-.139.242-.173.53-.094.799l.894 2.912a.344.344 0 01-.445.424L18.9 21.1z"
26
+ fill="#002BEB"
27
+ />
28
+ <Path
29
+ d="M9.848 12.704a1.376 1.376 0 11-.281 2.737 1.376 1.376 0 01.281-2.737zM13.989 12.73a1.376 1.376 0 11-.281 2.737 1.376 1.376 0 01.28-2.736zM18.141 12.679a1.376 1.376 0 11-.281 2.737 1.376 1.376 0 01.281-2.737z"
30
+ fill="#fff"
31
+ />
32
+ </G>
33
+ </Svg>
34
+ );
35
+ };
@@ -0,0 +1,52 @@
1
+ import { KColors, KContainer, KLabel, KSpacingValue } from '@droppii/libs';
2
+ import { StyleSheet } from 'react-native';
3
+ import { useFetchSuggestions } from '../../hooks/suggestions/useFetchSuggestions';
4
+ import SuggestionItem from './SuggestionItem';
5
+ import { useSendMessage } from '../../hooks/message/useSendMessage';
6
+ import debounce from 'lodash/debounce';
7
+ import { type ISuggestionItem } from '../../types';
8
+
9
+ const ChatEmpty = () => {
10
+ const { data } = useFetchSuggestions();
11
+ const { onSendMessage } = useSendMessage();
12
+
13
+ const onPress = debounce((item: ISuggestionItem) => {
14
+ onSendMessage(item.content);
15
+ }, 200);
16
+
17
+ return (
18
+ <KContainer.ScrollView contentContainerStyle={styles.container}>
19
+ <KLabel.Text typo="TextXLgMedium" center>
20
+ {'Bạn cần hỗ trợ gì hôm nay?'}
21
+ </KLabel.Text>
22
+ <KContainer.View center>
23
+ <KLabel.Text typo="TextMdNormal" center color={KColors.gray.normal}>
24
+ {'Hãy đặt câu hỏi hoặc chia sẻ vấn đề của bạn.'}
25
+ </KLabel.Text>
26
+ <KLabel.Text typo="TextMdNormal" center color={KColors.gray.normal}>
27
+ {'Xem qua những gợi ý dành cho bạn:'}
28
+ </KLabel.Text>
29
+ </KContainer.View>
30
+ {data?.suggestions?.map((item: ISuggestionItem) => (
31
+ <SuggestionItem key={item.suggestion_id} item={item} onPressItem={onPress} />
32
+ ))}
33
+ <KLabel.Text typo="TextSmNormal" color={KColors.gray.light} textAlign="center">
34
+ {
35
+ '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.'
36
+ }
37
+ </KLabel.Text>
38
+ </KContainer.ScrollView>
39
+ );
40
+ };
41
+
42
+ export default ChatEmpty;
43
+
44
+ const styles = StyleSheet.create({
45
+ container: {
46
+ flexGrow: 1,
47
+ justifyContent: 'center',
48
+ alignItems: 'center',
49
+ padding: KSpacingValue['0.75rem'],
50
+ gap: KSpacingValue['0.5rem'],
51
+ },
52
+ });
@@ -0,0 +1,82 @@
1
+ import { KButton, KColors, KContainer, KImage, KInput, KSpacingValue } from '@droppii/libs';
2
+ import { useCallback, useState } from 'react';
3
+ import { StyleSheet } from 'react-native';
4
+ import debounce from 'lodash/debounce';
5
+ import { useSendMessage } from '../../hooks/message/useSendMessage';
6
+ import useStreamMessageStore from '../../store/streamMessage';
7
+
8
+ const ChatFooter = () => {
9
+ const { onSendMessage, stopStream } = useSendMessage();
10
+ const [message, setMessage] = useState('');
11
+ const isStreaming = useStreamMessageStore(state => state.isStreaming);
12
+
13
+ const debouncedMessage = debounce((message: string) => {
14
+ setMessage(message);
15
+ }, 200);
16
+
17
+ const onPressSend = useCallback(() => {
18
+ if (isStreaming) {
19
+ stopStream();
20
+ return;
21
+ }
22
+ onSendMessage(message?.trim());
23
+ setMessage('');
24
+ }, [message, isStreaming]);
25
+
26
+ return (
27
+ <KContainer.View style={styles.container}>
28
+ <KInput.TextArea
29
+ paddingV={'0.25rem'}
30
+ paddingH={'0.25rem'}
31
+ placeholder="Bạn muốn hỏi gì hôm nay?"
32
+ clearButtonMode="hidden"
33
+ onChangeText={debouncedMessage}
34
+ value={message}
35
+ multiline
36
+ style={styles.input}
37
+ />
38
+ <KContainer.View style={styles.actions}>
39
+ <KImage.VectorIcons name="image-o" size={24} color={KColors.gray.dark} />
40
+ <KImage.VectorIcons name="paperclip-o" size={24} color={KColors.gray.dark} />
41
+ <KContainer.View flex />
42
+ <KButton.Solid
43
+ kind="primary"
44
+ icon={{
45
+ vectorName: isStreaming ? 'square-b' : 'send-b',
46
+ size: 20,
47
+ tintColor: KColors.white,
48
+ }}
49
+ onPress={onPressSend}
50
+ br="round"
51
+ disabled={!isStreaming && message.trim() === ''}
52
+ />
53
+ </KContainer.View>
54
+ </KContainer.View>
55
+ );
56
+ };
57
+
58
+ export default ChatFooter;
59
+
60
+ const styles = StyleSheet.create({
61
+ container: {
62
+ paddingHorizontal: KSpacingValue['0.75rem'],
63
+ paddingVertical: KSpacingValue['0.5rem'],
64
+ gap: KSpacingValue['0.5rem'],
65
+ borderWidth: 1,
66
+ borderColor: KColors.hexToRgba(KColors.black, 0.15),
67
+ borderBottomWidth: 0,
68
+ borderTopLeftRadius: KSpacingValue['1.25rem'],
69
+ borderTopRightRadius: KSpacingValue['1.25rem'],
70
+ },
71
+ actions: {
72
+ flexDirection: 'row',
73
+ alignItems: 'center',
74
+ gap: KSpacingValue['1rem'],
75
+ },
76
+ sendButton: {
77
+ alignSelf: 'flex-end',
78
+ },
79
+ input: {
80
+ maxHeight: 100,
81
+ },
82
+ });
@@ -3,9 +3,15 @@ import images from '../../assets/images';
3
3
  import { ImageBackground, StatusBar, StyleSheet } from 'react-native';
4
4
  import { useCallback } from 'react';
5
5
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
+ import useSessionStore from '../../store/session';
6
7
 
7
8
  export const ChatHeader = () => {
8
9
  const insets = useSafeAreaInsets();
10
+ const setSessionId = useSessionStore(state => state.setSessionId);
11
+
12
+ const onPressNewSession = () => {
13
+ setSessionId(undefined);
14
+ };
9
15
 
10
16
  const renderStatusBar = useCallback(() => {
11
17
  return (
@@ -18,11 +24,15 @@ export const ChatHeader = () => {
18
24
  <ImageBackground source={images.bg_header} resizeMode="cover">
19
25
  {renderStatusBar()}
20
26
  <KContainer.View style={styles.header}>
21
- <KImage.VectorIcons name="menu-ai" size={24} color={KColors.white} />
27
+ <KContainer.Touchable style={styles.button}>
28
+ <KImage.VectorIcons name="menu-ai-o" size={24} color={KColors.white} />
29
+ </KContainer.Touchable>
22
30
  <KLabel.Text flex typo="TitleBlockMedium" color={KColors.white}>
23
31
  {'Chatbot AI'}
24
32
  </KLabel.Text>
25
- <KImage.VectorIcons name="chat-dot-plus" size={24} color={KColors.white} />
33
+ <KContainer.Touchable style={styles.button} onPress={onPressNewSession}>
34
+ <KImage.VectorIcons name="chat-dot-plus-o" size={24} color={KColors.white} />
35
+ </KContainer.Touchable>
26
36
  </KContainer.View>
27
37
  </ImageBackground>
28
38
  );
@@ -38,4 +48,10 @@ const styles = StyleSheet.create({
38
48
  gap: KSpacingValue['0.5rem'],
39
49
  alignItems: 'center',
40
50
  },
51
+ button: {
52
+ width: 32,
53
+ height: 32,
54
+ justifyContent: 'center',
55
+ alignItems: 'center',
56
+ },
41
57
  });
@@ -0,0 +1,33 @@
1
+ import { FlatList, StyleSheet } from 'react-native';
2
+ import ChatEmpty from './ChatEmpty';
3
+ import { useMessage } from '../../hooks/message/useMessage';
4
+ import ChatItem from './item';
5
+ import { KSpacingValue } from '@droppii/libs';
6
+ import type { IMessageItem } from 'src/types';
7
+
8
+ const ChatMessageList = () => {
9
+ const { messageState } = useMessage();
10
+
11
+ if (!messageState?.messages?.length) {
12
+ return <ChatEmpty />;
13
+ }
14
+ return (
15
+ <FlatList
16
+ data={messageState?.messages || []}
17
+ renderItem={({ item, index }: { item: IMessageItem; index: number }) => <ChatItem item={item} index={index} />}
18
+ keyExtractor={(item: IMessageItem) => item.id}
19
+ contentContainerStyle={styles.container}
20
+ inverted
21
+ />
22
+ );
23
+ };
24
+
25
+ export default ChatMessageList;
26
+
27
+ const styles = StyleSheet.create({
28
+ container: {
29
+ flexGrow: 1,
30
+ gap: KSpacingValue['0.5rem'],
31
+ paddingVertical: KSpacingValue['1rem'],
32
+ },
33
+ });
@@ -0,0 +1,64 @@
1
+ import { useMemo } from 'react';
2
+ import { ISuggestionItem } from '../../types/dto';
3
+ import { KColors, KContainer, KImage, KLabel, KRadiusValue, KSpacingValue } from '@droppii/libs';
4
+ import { SuggestionCategory } from '../../types/common';
5
+ import { StyleSheet } from 'react-native';
6
+
7
+ interface SuggestionItemProps {
8
+ item: ISuggestionItem;
9
+ onPressItem: (item: ISuggestionItem) => void;
10
+ }
11
+
12
+ const SuggestionItem = ({ item, onPressItem }: SuggestionItemProps) => {
13
+ const icon = useMemo(() => {
14
+ switch (item.category) {
15
+ case SuggestionCategory.product:
16
+ return (
17
+ <KImage.VectorIcons
18
+ name="shopping-basket-o"
19
+ size={16}
20
+ color={KColors.palette.primary.w400}
21
+ />
22
+ );
23
+ case SuggestionCategory.discount:
24
+ return (
25
+ <KImage.VectorIcons name="ticket-percent-o" size={16} color={KColors.danger.normal} />
26
+ );
27
+ case SuggestionCategory.gift:
28
+ return <KImage.VectorIcons name="gift-o" size={16} color={KColors.success.normal} />;
29
+ case SuggestionCategory.health_advice:
30
+ return (
31
+ <KImage.VectorIcons name="heart-rate-o" size={16} color={KColors.secondary.normal} />
32
+ );
33
+ case SuggestionCategory.content:
34
+ return <KImage.VectorIcons name="edit-o" size={16} color={KColors.warning.normal} />;
35
+ case SuggestionCategory.guide:
36
+ return <KImage.VectorIcons name="book" size={16} color={KColors.palette.blue.w400} />;
37
+ default:
38
+ return <KImage.VectorIcons name="edit-o" size={16} color={KColors.warning.normal} />;
39
+ }
40
+ }, [item.category]);
41
+
42
+ return (
43
+ <KContainer.Touchable style={styles.container} onPress={() => onPressItem(item)}>
44
+ {icon}
45
+ <KLabel.Text flex typo="TextNmNormal">
46
+ {item?.content || ''}
47
+ </KLabel.Text>
48
+ </KContainer.Touchable>
49
+ );
50
+ };
51
+
52
+ export default SuggestionItem;
53
+
54
+ const styles = StyleSheet.create({
55
+ container: {
56
+ flexDirection: 'row',
57
+ alignItems: 'center',
58
+ gap: KSpacingValue['0.75rem'],
59
+ paddingHorizontal: KSpacingValue['0.75rem'],
60
+ paddingVertical: 6,
61
+ borderRadius: KRadiusValue['4x'],
62
+ backgroundColor: KColors.hexToRgba(KColors.gray.dark, 0.05),
63
+ },
64
+ });
@@ -1,12 +1,26 @@
1
1
  import { KContainer } from '@droppii/libs';
2
2
  import ChatHeader from './ChatHeader';
3
+ import ChatMessageList from './ChatMessageList';
4
+ import ChatFooter from './ChatFooter';
5
+ import { KeyboardAvoidingView, Platform, StyleSheet } from 'react-native';
3
6
 
4
7
  const ChatBotAI = () => {
5
8
  return (
6
9
  <KContainer.Page>
7
- <ChatHeader />
10
+ <KeyboardAvoidingView
11
+ style={styles.container}
12
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
13
+ >
14
+ <ChatHeader />
15
+ <ChatMessageList />
16
+ <ChatFooter />
17
+ </KeyboardAvoidingView>
8
18
  </KContainer.Page>
9
19
  );
10
20
  };
11
21
 
12
22
  export default ChatBotAI;
23
+
24
+ const styles = StyleSheet.create({
25
+ container: { flex: 1 },
26
+ });
@@ -0,0 +1,84 @@
1
+ import { KColors, KContainer, KLabel, KSpacingValue } from '@droppii/libs';
2
+ import { IMessageItem } from '../../../types';
3
+ import Markdown, { MarkdownProps } from 'react-native-markdown-display';
4
+ import { StyleSheet } from 'react-native';
5
+ import useStreamMessageStore from '../../../store/streamMessage';
6
+ import { useEffect, useRef, useState } from 'react';
7
+
8
+ interface ChatAIAnswerMessageItemProps {
9
+ item: IMessageItem;
10
+ isLast?: boolean;
11
+ }
12
+ const markDownRules: MarkdownProps['rules'] = {
13
+ body: (node: any, children: any, _: any, styles: any) => (
14
+ <KContainer.View key={node.key} style={styles}>
15
+ {children}
16
+ </KContainer.View>
17
+ ),
18
+ paragraph: (node: any, children: any, _: any, styles: any) => (
19
+ <KLabel.Text key={node.key} typo="TextMdNormal" style={styles} color={KColors.black}>
20
+ {children}
21
+ </KLabel.Text>
22
+ ),
23
+ span: (node: any, children: any, _: any, styles: any) => (
24
+ <KLabel.Text key={node.key} typo="TextMdNormal" style={styles} color={KColors.black}>
25
+ {children}
26
+ </KLabel.Text>
27
+ ),
28
+ strong: (node: any, children: any, _: any, styles: any) => (
29
+ <KLabel.Text key={node.key} typo="TextMdBold" style={styles} color={KColors.black}>
30
+ {children}
31
+ </KLabel.Text>
32
+ ),
33
+ };
34
+
35
+ const ChatAIAnswerMessageItem = ({ item, isLast = false }: ChatAIAnswerMessageItemProps) => {
36
+ const streamMessage = useStreamMessageStore(state => state.streamMessage?.[item.id]);
37
+
38
+ const [display, setDisplay] = useState('');
39
+ const indexRef = useRef(0);
40
+
41
+ useEffect(() => {
42
+ if (!streamMessage) {
43
+ indexRef.current = 0;
44
+ setDisplay(item.content);
45
+ return;
46
+ }
47
+
48
+ const interval = setInterval(() => {
49
+ const remaining = streamMessage.content.slice(indexRef.current);
50
+ if (remaining.length === 0) {
51
+ clearInterval(interval);
52
+ return;
53
+ }
54
+
55
+ const chunkSize = 10;
56
+ const chunk = remaining.slice(0, chunkSize);
57
+
58
+ setDisplay(prev => prev + chunk);
59
+ indexRef.current += chunk.length;
60
+ }, 20);
61
+
62
+ return () => clearInterval(interval);
63
+ }, [streamMessage, item]);
64
+ return (
65
+ <KContainer.View style={styles.container}>
66
+ <Markdown rules={markDownRules}>{display}</Markdown>
67
+ <KContainer.VisibleView visible={isLast && !streamMessage}>
68
+ <KLabel.Text typo="TextSmNormal" color={KColors.gray.light} textAlign="center">
69
+ {
70
+ '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.'
71
+ }
72
+ </KLabel.Text>
73
+ </KContainer.VisibleView>
74
+ </KContainer.View>
75
+ );
76
+ };
77
+ export default ChatAIAnswerMessageItem;
78
+
79
+ const styles = StyleSheet.create({
80
+ container: {
81
+ paddingHorizontal: KSpacingValue['1rem'],
82
+ gap: KSpacingValue['0.5rem'],
83
+ },
84
+ });
@@ -0,0 +1,130 @@
1
+ import { KContainer, KLabel, KColors, KSpacingValue, KListItem } from '@droppii/libs';
2
+ import type { IMessageItem } from '../../../types';
3
+ import { StyleSheet } from 'react-native';
4
+ import Markdown, { type MarkdownProps } from 'react-native-markdown-display';
5
+ import { useCallback, useEffect, useRef, useState } from 'react';
6
+ import { IconThinkingStep } from '../../../assets/svgIcon/IconThinkingStep';
7
+ import { IconChatArrow } from '../../../assets/svgIcon/IconChatArrow';
8
+ import useStreamMessageStore from '../../../store/streamMessage';
9
+
10
+ interface ChatAIThinkingMessageItemProps {
11
+ item: IMessageItem;
12
+ }
13
+
14
+ const markDownRules: MarkdownProps['rules'] = {
15
+ body: (node: any, children: any, _: any, styles: any) => (
16
+ <KContainer.View key={node.key} style={styles}>
17
+ {children}
18
+ </KContainer.View>
19
+ ),
20
+ paragraph: (node: any, children: any, _: any, styles: any) => (
21
+ <KLabel.Text key={node.key} typo="TextSmNormal" style={styles} color={KColors.black}>
22
+ {children}
23
+ </KLabel.Text>
24
+ ),
25
+ span: (node: any, children: any, _: any, styles: any) => (
26
+ <KLabel.Text key={node.key} typo="TextSmNormal" style={styles} color={KColors.black}>
27
+ {children}
28
+ </KLabel.Text>
29
+ ),
30
+ strong: (node: any, children: any, _: any, styles: any) => (
31
+ <KLabel.Text key={node.key} typo="TextSmBold" style={styles} color={KColors.black}>
32
+ {children}
33
+ </KLabel.Text>
34
+ ),
35
+ };
36
+
37
+ const ChatAIThinkingMessageItem = ({ item }: ChatAIThinkingMessageItemProps) => {
38
+ const streamMessage = useStreamMessageStore(state => state.streamMessage?.[item.id]);
39
+
40
+ const [display, setDisplay] = useState('');
41
+ const indexRef = useRef(0);
42
+
43
+ const renderHeader = useCallback(() => {
44
+ return (
45
+ <KContainer.View style={styles.header}>
46
+ <IconThinkingStep />
47
+ <KLabel.Text typo="TextSmBold" color={KColors.gray.normal}>
48
+ {'Phân tích'}
49
+ </KLabel.Text>
50
+ </KContainer.View>
51
+ );
52
+ }, []);
53
+
54
+ useEffect(() => {
55
+ if (!streamMessage) {
56
+ indexRef.current = 0;
57
+ setDisplay(item.content);
58
+ return;
59
+ }
60
+
61
+ const interval = setInterval(() => {
62
+ const remaining = streamMessage.content.slice(indexRef.current);
63
+ if (remaining.length === 0) {
64
+ clearInterval(interval);
65
+ return;
66
+ }
67
+
68
+ const chunkSize = 2;
69
+ const chunk = remaining.slice(0, chunkSize);
70
+
71
+ setDisplay(prev => prev + chunk);
72
+ indexRef.current += chunk.length;
73
+ }, 20);
74
+
75
+ return () => clearInterval(interval);
76
+ }, [streamMessage, item]);
77
+
78
+ return (
79
+ <KListItem.Accordion
80
+ header={renderHeader}
81
+ key={item.id.toString()}
82
+ containerStyle={styles.container}
83
+ headerStyle={styles.mHeader}
84
+ value={!!streamMessage}
85
+ >
86
+ <KContainer.View style={styles.contentContainer}>
87
+ <KContainer.View style={styles.arrow}>
88
+ <IconChatArrow fill={'#0077FF'} />
89
+ </KContainer.View>
90
+ <KContainer.View style={styles.content}>
91
+ <Markdown rules={markDownRules}>{display}</Markdown>
92
+ </KContainer.View>
93
+ </KContainer.View>
94
+ </KListItem.Accordion>
95
+ );
96
+ };
97
+
98
+ export default ChatAIThinkingMessageItem;
99
+
100
+ const styles = StyleSheet.create({
101
+ container: {
102
+ paddingHorizontal: KSpacingValue['1rem'],
103
+ borderBottomWidth: 0,
104
+ paddingVertical: 0,
105
+ },
106
+ header: {
107
+ flexDirection: 'row',
108
+ alignItems: 'center',
109
+ paddingVertical: KSpacingValue['0.25rem'],
110
+ gap: KSpacingValue['0.5rem'],
111
+ },
112
+ mHeader: {
113
+ paddingVertical: 0,
114
+ marginBottom: KSpacingValue['0.25rem'],
115
+ },
116
+ contentContainer: {
117
+ flexDirection: 'row',
118
+ },
119
+ content: {
120
+ maxWidth: '90%',
121
+ paddingHorizontal: KSpacingValue['1rem'],
122
+ paddingVertical: KSpacingValue['0.5rem'],
123
+ backgroundColor: KColors.hexToRgba('#0077FF', 0.1),
124
+ borderRadius: KSpacingValue['1rem'],
125
+ borderTopLeftRadius: 0,
126
+ },
127
+ arrow: {
128
+ transform: [{ rotateY: '180deg' }],
129
+ },
130
+ });
@@ -0,0 +1,39 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { KColors, KContainer, KLabel, KSpacingValue } from '@droppii/libs';
3
+ import { IMessageItem } from '../../../types';
4
+ import { IconChatArrow } from '../../../assets/svgIcon/IconChatArrow';
5
+
6
+ interface ChatUserMessageItemProps {
7
+ item: IMessageItem;
8
+ }
9
+ const ChatUserMessageItem = ({ item }: ChatUserMessageItemProps) => {
10
+ return (
11
+ <KContainer.View style={styles.container}>
12
+ <KContainer.View style={styles.content}>
13
+ <KLabel.Text typo="TextMdNormal" color={KColors.black}>
14
+ {item.content}
15
+ </KLabel.Text>
16
+ </KContainer.View>
17
+ <IconChatArrow fill={'#0077FF'} />
18
+ </KContainer.View>
19
+ );
20
+ };
21
+
22
+ export default ChatUserMessageItem;
23
+
24
+ const styles = StyleSheet.create({
25
+ container: {
26
+ flexDirection: 'row',
27
+ justifyContent: 'flex-end',
28
+ paddingHorizontal: KSpacingValue['1rem'],
29
+ marginVertical: KSpacingValue['0.5rem'],
30
+ },
31
+ content: {
32
+ maxWidth: '70%',
33
+ paddingHorizontal: KSpacingValue['1rem'],
34
+ paddingVertical: KSpacingValue['0.5rem'],
35
+ backgroundColor: KColors.hexToRgba('#0077FF', 0.1),
36
+ borderRadius: KSpacingValue['1rem'],
37
+ borderTopRightRadius: 0,
38
+ },
39
+ });
@@ -0,0 +1,23 @@
1
+ import { IMessageItem, MessageType } from '../../../types';
2
+ import ChatAIAnswerMessageItem from './ChatAIAnswerMessageItem';
3
+ import ChatAIThinkingMessageItem from './ChatAIThinkingMessageItem';
4
+ import ChatUserMessageItem from './ChatUserMessageItem';
5
+
6
+ interface ChatItemProps {
7
+ item: IMessageItem;
8
+ index: number;
9
+ }
10
+ const ChatItem = ({ item, index }: ChatItemProps) => {
11
+ switch (item.type) {
12
+ case MessageType.user_message:
13
+ return <ChatUserMessageItem item={item} />;
14
+ case MessageType.ai_answer:
15
+ return <ChatAIAnswerMessageItem item={item} isLast={index === 0} />;
16
+ case MessageType.ai_thinking:
17
+ return <ChatAIThinkingMessageItem item={item} />;
18
+ default:
19
+ return <ChatUserMessageItem item={item} />;
20
+ }
21
+ };
22
+
23
+ export default ChatItem;
@@ -0,0 +1,5 @@
1
+ export const events = {
2
+ updateOneMessage: 'update_one_message',
3
+ updateMultipleMessage: 'update_multiple_message',
4
+ forceUpdateMessages: 'force_update_messages',
5
+ };
@@ -0,0 +1,5 @@
1
+ export const QUERY_KEYS = {
2
+ GET_SUGGESTIONS: 'GET_SUGGESTIONS',
3
+ GET_SESSION_BY_ID: 'GET_SESSION_BY_ID',
4
+ CREATE_SESSION: 'CREATE_SESSION',
5
+ };
@@ -1,12 +1,19 @@
1
- import { createContext } from 'react';
1
+ import { createContext, useContext } from 'react';
2
2
  import type { ChatContextType, ChatProviderProps } from '../types/chat';
3
+ import ChatBotAI from '../components/chat';
3
4
 
4
- export const ChatContext = createContext<ChatContextType>({});
5
+ export const ChatContext = createContext<ChatContextType>({
6
+ apiAddress: '',
7
+ userId: '',
8
+ });
5
9
 
6
- export const ChatProvider = ({ children }: ChatProviderProps) => {
10
+ export const useChatContext = () => useContext(ChatContext);
11
+
12
+ export const ChatProvider = (props: ChatProviderProps) => {
13
+ const { apiAddress, userId } = props;
7
14
  return (
8
- <ChatContext.Provider value={{}}>
9
- {children}
15
+ <ChatContext.Provider value={{ apiAddress, userId }}>
16
+ <ChatBotAI />
10
17
  </ChatContext.Provider>
11
18
  );
12
19
  };