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.
- package/lib/module/components/Drawer/DrawerContent.js +85 -0
- package/lib/module/components/Drawer/DrawerContent.js.map +1 -0
- package/lib/module/components/Drawer/EmptyState.js +28 -0
- package/lib/module/components/Drawer/EmptyState.js.map +1 -0
- package/lib/module/components/Drawer/LoadingState.js +28 -0
- package/lib/module/components/Drawer/LoadingState.js.map +1 -0
- package/lib/module/components/Drawer/NewChatButton.js +24 -0
- package/lib/module/components/Drawer/NewChatButton.js.map +1 -0
- package/lib/module/components/Drawer/SearchInput.js +34 -0
- package/lib/module/components/Drawer/SearchInput.js.map +1 -0
- package/lib/module/components/Drawer/SessionItem.js +53 -0
- package/lib/module/components/Drawer/SessionItem.js.map +1 -0
- package/lib/module/components/Drawer/SessionList.js +55 -0
- package/lib/module/components/Drawer/SessionList.js.map +1 -0
- package/lib/module/components/Drawer/index.js +51 -0
- package/lib/module/components/Drawer/index.js.map +1 -0
- package/lib/module/components/chat/ChatEmpty.js +5 -4
- package/lib/module/components/chat/ChatEmpty.js.map +1 -1
- package/lib/module/components/chat/ChatHeader.js +10 -4
- package/lib/module/components/chat/ChatHeader.js.map +1 -1
- package/lib/module/components/chat/ChatMessageList.js +8 -7
- package/lib/module/components/chat/ChatMessageList.js.map +1 -1
- package/lib/module/components/chat/footer/index.js +51 -6
- package/lib/module/components/chat/footer/index.js.map +1 -1
- package/lib/module/components/chat/index.js +13 -2
- package/lib/module/components/chat/index.js.map +1 -1
- package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js +3 -2
- package/lib/module/components/chat/item/ChatAIAnswerMessageItem.js.map +1 -1
- package/lib/module/components/chat/item/ChatUserMessageItem.js +13 -4
- package/lib/module/components/chat/item/ChatUserMessageItem.js.map +1 -1
- package/lib/module/components/chat/item/index.js +4 -2
- package/lib/module/components/chat/item/index.js.map +1 -1
- package/lib/module/components/product/CardHorizontal.js +5 -0
- package/lib/module/components/product/CardHorizontal.js.map +1 -1
- package/lib/module/constants/events.js +2 -1
- package/lib/module/constants/events.js.map +1 -1
- package/lib/module/constants/query.js +2 -1
- package/lib/module/constants/query.js.map +1 -1
- package/lib/module/context/ChatContext.js +23 -6
- package/lib/module/context/ChatContext.js.map +1 -1
- package/lib/module/hooks/message/useMessage.js +36 -4
- package/lib/module/hooks/message/useMessage.js.map +1 -1
- package/lib/module/hooks/message/useSendMessage.js +19 -6
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/message/useStreamMessage.js +39 -10
- package/lib/module/hooks/message/useStreamMessage.js.map +1 -1
- package/lib/module/hooks/session/useSearchSessions.js +51 -0
- package/lib/module/hooks/session/useSearchSessions.js.map +1 -0
- package/lib/module/services/endpoints.js +2 -1
- package/lib/module/services/endpoints.js.map +1 -1
- package/lib/module/translation/index.js +28 -0
- package/lib/module/translation/index.js.map +1 -0
- package/lib/module/translation/resources/vi.js +12 -0
- package/lib/module/translation/resources/vi.js.map +1 -0
- package/lib/module/utils/device.js +2 -2
- package/lib/module/utils/device.js.map +1 -1
- package/lib/typescript/src/components/Drawer/DrawerContent.d.ts +3 -0
- package/lib/typescript/src/components/Drawer/DrawerContent.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/EmptyState.d.ts +3 -0
- package/lib/typescript/src/components/Drawer/EmptyState.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/LoadingState.d.ts +3 -0
- package/lib/typescript/src/components/Drawer/LoadingState.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/NewChatButton.d.ts +6 -0
- package/lib/typescript/src/components/Drawer/NewChatButton.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/SearchInput.d.ts +7 -0
- package/lib/typescript/src/components/Drawer/SearchInput.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/SessionItem.d.ts +9 -0
- package/lib/typescript/src/components/Drawer/SessionItem.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/SessionList.d.ts +15 -0
- package/lib/typescript/src/components/Drawer/SessionList.d.ts.map +1 -0
- package/lib/typescript/src/components/Drawer/index.d.ts +7 -0
- package/lib/typescript/src/components/Drawer/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chat/ChatEmpty.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/ChatMessageList.d.ts +5 -1
- package/lib/typescript/src/components/chat/ChatMessageList.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/footer/index.d.ts +5 -1
- package/lib/typescript/src/components/chat/footer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/index.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/item/ChatAIAnswerMessageItem.d.ts.map +1 -1
- package/lib/typescript/src/components/chat/item/ChatUserMessageItem.d.ts +2 -1
- package/lib/typescript/src/components/chat/item/ChatUserMessageItem.d.ts.map +1 -1
- package/lib/typescript/src/constants/events.d.ts +1 -0
- package/lib/typescript/src/constants/events.d.ts.map +1 -1
- package/lib/typescript/src/constants/query.d.ts +1 -0
- package/lib/typescript/src/constants/query.d.ts.map +1 -1
- package/lib/typescript/src/context/ChatContext.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +2 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/message/useStreamMessage.d.ts +3 -2
- package/lib/typescript/src/hooks/message/useStreamMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/session/useSearchSessions.d.ts +2 -0
- package/lib/typescript/src/hooks/session/useSearchSessions.d.ts.map +1 -0
- package/lib/typescript/src/services/endpoints.d.ts +1 -0
- package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
- package/lib/typescript/src/translation/index.d.ts +5 -0
- package/lib/typescript/src/translation/index.d.ts.map +1 -0
- package/lib/typescript/src/translation/resources/vi.d.ts +11 -0
- package/lib/typescript/src/translation/resources/vi.d.ts.map +1 -0
- package/lib/typescript/src/types/chat.d.ts +2 -0
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/dto.d.ts +16 -0
- package/lib/typescript/src/types/dto.d.ts.map +1 -1
- package/lib/typescript/src/utils/device.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/components/Drawer/DrawerContent.tsx +94 -0
- package/src/components/Drawer/EmptyState.tsx +26 -0
- package/src/components/Drawer/LoadingState.tsx +26 -0
- package/src/components/Drawer/NewChatButton.tsx +25 -0
- package/src/components/Drawer/SearchInput.tsx +37 -0
- package/src/components/Drawer/SessionItem.tsx +68 -0
- package/src/components/Drawer/SessionList.tsx +71 -0
- package/src/components/Drawer/index.tsx +57 -0
- package/src/components/chat/ChatEmpty.tsx +5 -6
- package/src/components/chat/ChatHeader.tsx +14 -6
- package/src/components/chat/ChatMessageList.tsx +9 -5
- package/src/components/chat/footer/index.tsx +69 -7
- package/src/components/chat/index.tsx +14 -4
- package/src/components/chat/item/ChatAIAnswerMessageItem.tsx +3 -4
- package/src/components/chat/item/ChatUserMessageItem.tsx +13 -5
- package/src/components/chat/item/index.tsx +2 -2
- package/src/components/product/CardHorizontal.tsx +5 -0
- package/src/constants/events.ts +1 -0
- package/src/constants/query.ts +1 -0
- package/src/context/ChatContext.tsx +21 -3
- package/src/hooks/message/useMessage.ts +54 -4
- package/src/hooks/message/useSendMessage.ts +22 -5
- package/src/hooks/message/useStreamMessage.ts +59 -12
- package/src/hooks/session/useSearchSessions.ts +62 -0
- package/src/services/endpoints.ts +1 -0
- package/src/translation/index.ts +22 -0
- package/src/translation/resources/vi.ts +10 -0
- package/src/types/chat.ts +2 -0
- package/src/types/dto.ts +19 -0
- 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
|
-
{'
|
|
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
|
-
{'
|
|
28
|
+
{trans('chat_empty_description_1')}
|
|
28
29
|
</KLabel.Text>
|
|
29
30
|
<KLabel.Text typo="TextMdNormal" center color={KColors.gray.normal}>
|
|
30
|
-
{'
|
|
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
|
-
<
|
|
35
|
+
<StatusBarComponent translucent />
|
|
28
36
|
</KContainer.View>
|
|
29
37
|
);
|
|
30
38
|
}, [insets.top]);
|
|
31
39
|
return (
|
|
32
|
-
<
|
|
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
|
-
</
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
<
|
|
49
|
+
<FlatListComponent
|
|
46
50
|
ref={flatListRef}
|
|
47
|
-
data={
|
|
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 {
|
|
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
|
-
|
|
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: '
|
|
178
|
+
label: trans('photo_library'),
|
|
157
179
|
},
|
|
158
180
|
{
|
|
159
181
|
icon: 'camera-o',
|
|
160
182
|
onPress: onPressCameraPicker,
|
|
161
|
-
label: '
|
|
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
|
-
<
|
|
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={
|
|
355
|
+
paddingV={0}
|
|
305
356
|
paddingH={'0.25rem'}
|
|
306
|
-
placeholder=
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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(
|
|
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
|
-
|
|
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
|
|
package/src/constants/events.ts
CHANGED
package/src/constants/query.ts
CHANGED
|
@@ -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
|
-
<
|
|
46
|
-
|
|
59
|
+
<ReanimatedDrawerExample ref={ref}>
|
|
60
|
+
<>
|
|
61
|
+
<ChatBotAI />
|
|
62
|
+
<Portal />
|
|
63
|
+
</>
|
|
64
|
+
</ReanimatedDrawerExample>
|
|
47
65
|
</ChatContext.Provider>
|
|
48
66
|
);
|
|
49
67
|
};
|