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.
- package/lib/module/assets/images/index.js.map +1 -0
- package/lib/module/assets/svgIcon/IconChatArrow.js +25 -0
- package/lib/module/assets/svgIcon/IconChatArrow.js.map +1 -0
- package/lib/module/assets/svgIcon/IconThinkingStep.js +45 -0
- package/lib/module/assets/svgIcon/IconThinkingStep.js.map +1 -0
- package/lib/module/components/chat/ChatEmpty.js +60 -0
- package/lib/module/components/chat/ChatEmpty.js.map +1 -0
- package/lib/module/components/chat/ChatFooter.js +89 -0
- package/lib/module/components/chat/ChatFooter.js.map +1 -0
- package/lib/module/components/chat/ChatHeader.js +27 -9
- package/lib/module/components/chat/ChatHeader.js.map +1 -1
- package/lib/module/components/chat/ChatMessageList.js +38 -0
- package/lib/module/components/chat/ChatMessageList.js.map +1 -0
- package/lib/module/components/chat/SuggestionItem.js +80 -0
- package/lib/module/components/chat/SuggestionItem.js.map +1 -0
- package/lib/module/components/chat/index.js +14 -2
- package/lib/module/components/chat/index.js.map +1 -1
- package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js +82 -0
- package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js.map +1 -0
- package/lib/module/components/chat/item/ChatAIThinkingMessageItem.js +126 -0
- package/lib/module/components/chat/item/ChatAIThinkingMessageItem.js.map +1 -0
- package/lib/module/components/chat/item/ChatUserMessageItem.js +41 -0
- package/lib/module/components/chat/item/ChatUserMessageItem.js.map +1 -0
- package/lib/module/components/chat/item/index.js +33 -0
- package/lib/module/components/chat/item/index.js.map +1 -0
- package/lib/module/constants/events.js +8 -0
- package/lib/module/constants/events.js.map +1 -0
- package/lib/module/constants/query.js +8 -0
- package/lib/module/constants/query.js.map +1 -0
- package/lib/module/context/ChatContext.js +17 -7
- package/lib/module/context/ChatContext.js.map +1 -1
- package/lib/module/hooks/message/useMessage.js +86 -0
- package/lib/module/hooks/message/useMessage.js.map +1 -0
- package/lib/module/hooks/message/useSendMessage.js +58 -0
- package/lib/module/hooks/message/useSendMessage.js.map +1 -0
- package/lib/module/hooks/message/useStreamMessage.js +113 -0
- package/lib/module/hooks/message/useStreamMessage.js.map +1 -0
- package/lib/module/hooks/session/useCreateSession.js +18 -0
- package/lib/module/hooks/session/useCreateSession.js.map +1 -0
- package/lib/module/hooks/session/useFetchSessionById.js +14 -0
- package/lib/module/hooks/session/useFetchSessionById.js.map +1 -0
- package/lib/module/hooks/suggestions/useFetchSuggestions.js +15 -0
- package/lib/module/hooks/suggestions/useFetchSuggestions.js.map +1 -0
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/endpoints.js +11 -0
- package/lib/module/services/endpoints.js.map +1 -0
- package/lib/module/store/session.js +11 -0
- package/lib/module/store/session.js.map +1 -0
- package/lib/module/store/streamMessage.js +26 -0
- package/lib/module/store/streamMessage.js.map +1 -0
- package/lib/module/types/common.js +23 -0
- package/lib/module/types/common.js.map +1 -0
- package/lib/module/types/dto.js +2 -0
- package/lib/module/types/index.js +6 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/typescript/src/assets/images/index.d.ts.map +1 -0
- package/lib/typescript/src/assets/svgIcon/IconChatArrow.d.ts +9 -0
- package/lib/typescript/src/assets/svgIcon/IconChatArrow.d.ts.map +1 -0
- package/lib/typescript/src/assets/svgIcon/IconThinkingStep.d.ts +7 -0
- package/lib/typescript/src/assets/svgIcon/IconThinkingStep.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/ChatEmpty.d.ts +3 -0
- package/lib/typescript/src/components/chat/ChatEmpty.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/ChatFooter.d.ts +3 -0
- package/lib/typescript/src/components/chat/ChatFooter.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/ChatMessageList.d.ts +3 -0
- package/lib/typescript/src/components/chat/ChatMessageList.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/SuggestionItem.d.ts +8 -0
- package/lib/typescript/src/components/chat/SuggestionItem.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/index.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/item/ChatAIAnswerMessageItem.d.ts +8 -0
- package/lib/typescript/src/components/chat/item/ChatAIAnswerMessageItem.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/item/ChatAIThinkingMessageItem.d.ts +7 -0
- package/lib/typescript/src/components/chat/item/ChatAIThinkingMessageItem.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/item/ChatUserMessageItem.d.ts +7 -0
- package/lib/typescript/src/components/chat/item/ChatUserMessageItem.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/item/index.d.ts +8 -0
- package/lib/typescript/src/components/chat/item/index.d.ts.map +1 -0
- package/lib/typescript/src/constants/events.d.ts +6 -0
- package/lib/typescript/src/constants/events.d.ts.map +1 -0
- package/lib/typescript/src/constants/query.d.ts +6 -0
- package/lib/typescript/src/constants/query.d.ts.map +1 -0
- package/lib/typescript/src/context/ChatContext.d.ts +2 -1
- package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useMessage.d.ts +5 -0
- package/lib/typescript/src/hooks/message/useMessage.d.ts.map +1 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +5 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -0
- package/lib/typescript/src/hooks/message/useStreamMessage.d.ts +6 -0
- package/lib/typescript/src/hooks/message/useStreamMessage.d.ts.map +1 -0
- package/lib/typescript/src/hooks/session/useCreateSession.d.ts +2 -0
- package/lib/typescript/src/hooks/session/useCreateSession.d.ts.map +1 -0
- package/lib/typescript/src/hooks/session/useFetchSessionById.d.ts +2 -0
- package/lib/typescript/src/hooks/session/useFetchSessionById.d.ts.map +1 -0
- package/lib/typescript/src/hooks/suggestions/useFetchSuggestions.d.ts +2 -0
- package/lib/typescript/src/hooks/suggestions/useFetchSuggestions.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/services/endpoints.d.ts +9 -0
- package/lib/typescript/src/services/endpoints.d.ts.map +1 -0
- package/lib/typescript/src/store/session.d.ts +4 -0
- package/lib/typescript/src/store/session.d.ts.map +1 -0
- package/lib/typescript/src/store/streamMessage.d.ts +4 -0
- package/lib/typescript/src/store/streamMessage.d.ts.map +1 -0
- package/lib/typescript/src/types/chat.d.ts +16 -2
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/common.d.ts +29 -0
- package/lib/typescript/src/types/common.d.ts.map +1 -0
- package/lib/typescript/src/types/dto.d.ts +66 -0
- package/lib/typescript/src/types/dto.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +4 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -0
- package/package.json +5 -2
- package/src/assets/svgIcon/IconChatArrow.tsx +21 -0
- package/src/assets/svgIcon/IconThinkingStep.tsx +35 -0
- package/src/components/chat/ChatEmpty.tsx +52 -0
- package/src/components/chat/ChatFooter.tsx +82 -0
- package/src/components/chat/ChatHeader.tsx +18 -2
- package/src/components/chat/ChatMessageList.tsx +33 -0
- package/src/components/chat/SuggestionItem.tsx +64 -0
- package/src/components/chat/index.tsx +15 -1
- package/src/components/chat/item/ChatAIAnswerMessageItem.tsx +84 -0
- package/src/components/chat/item/ChatAIThinkingMessageItem.tsx +130 -0
- package/src/components/chat/item/ChatUserMessageItem.tsx +39 -0
- package/src/components/chat/item/index.tsx +23 -0
- package/src/constants/events.ts +5 -0
- package/src/constants/query.ts +5 -0
- package/src/context/ChatContext.tsx +12 -5
- package/src/hooks/message/useMessage.ts +94 -0
- package/src/hooks/message/useSendMessage.ts +58 -0
- package/src/hooks/message/useStreamMessage.ts +130 -0
- package/src/hooks/session/useCreateSession.ts +23 -0
- package/src/hooks/session/useFetchSessionById.ts +17 -0
- package/src/hooks/suggestions/useFetchSuggestions.ts +18 -0
- package/src/index.ts +3 -2
- package/src/services/endpoints.ts +9 -0
- package/src/store/session.ts +9 -0
- package/src/store/streamMessage.ts +24 -0
- package/src/types/chat.ts +21 -3
- package/src/types/common.ts +32 -0
- package/src/types/dto.ts +74 -0
- package/src/types/index.ts +3 -0
- package/lib/module/images/index.js.map +0 -1
- package/lib/module/utils/index.js +0 -2
- package/lib/module/utils/index.js.map +0 -1
- package/lib/typescript/src/images/index.d.ts.map +0 -1
- package/lib/typescript/src/utils/index.d.ts +0 -1
- package/lib/typescript/src/utils/index.d.ts.map +0 -1
- package/src/utils/index.ts +0 -0
- /package/lib/module/{images → assets/images}/bg_header.png +0 -0
- /package/lib/module/{images → assets/images}/index.js +0 -0
- /package/lib/typescript/src/{images → assets/images}/index.d.ts +0 -0
- /package/src/{images → assets/images}/bg_header.png +0 -0
- /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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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;
|
|
@@ -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
|
|
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
|
-
|
|
15
|
+
<ChatContext.Provider value={{ apiAddress, userId }}>
|
|
16
|
+
<ChatBotAI />
|
|
10
17
|
</ChatContext.Provider>
|
|
11
18
|
);
|
|
12
19
|
};
|