react-native-chatbot-ai 0.0.4 → 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/bg_header.png +0 -0
- package/lib/module/assets/images/index.js +6 -0
- 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 +70 -0
- package/lib/module/components/chat/ChatHeader.js.map +1 -0
- 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 +17 -5
- 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 +19 -10
- 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 -1
- 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 +5 -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 +3 -0
- package/lib/typescript/src/components/chat/ChatHeader.d.ts.map +1 -0
- 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 +2 -1
- 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 -1
- 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 +8 -5
- package/src/assets/images/bg_header.png +0 -0
- package/src/assets/images/index.ts +3 -0
- 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 +57 -0
- package/src/components/chat/ChatMessageList.tsx +33 -0
- package/src/components/chat/SuggestionItem.tsx +64 -0
- package/src/components/chat/index.tsx +20 -5
- 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 +11 -6
- 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/ignore.d.ts +2 -1
- package/src/index.ts +4 -0
- 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/utils/index.js +0 -2
- package/lib/module/utils/index.js.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/index.tsx +0 -2
- package/src/utils/index.ts +0 -0
|
@@ -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,13 +1,18 @@
|
|
|
1
|
-
import { createContext } from 'react';
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
2
|
import type { ChatContextType, ChatProviderProps } from '../types/chat';
|
|
3
|
-
import
|
|
3
|
+
import ChatBotAI from '../components/chat';
|
|
4
4
|
|
|
5
|
-
export const ChatContext = createContext<ChatContextType>({
|
|
5
|
+
export const ChatContext = createContext<ChatContextType>({
|
|
6
|
+
apiAddress: '',
|
|
7
|
+
userId: '',
|
|
8
|
+
});
|
|
6
9
|
|
|
7
|
-
export const
|
|
10
|
+
export const useChatContext = () => useContext(ChatContext);
|
|
11
|
+
|
|
12
|
+
export const ChatProvider = (props: ChatProviderProps) => {
|
|
13
|
+
const { apiAddress, userId } = props;
|
|
8
14
|
return (
|
|
9
|
-
<ChatContext.Provider value={{}}>
|
|
10
|
-
{children}
|
|
15
|
+
<ChatContext.Provider value={{ apiAddress, userId }}>
|
|
11
16
|
<ChatBotAI />
|
|
12
17
|
</ChatContext.Provider>
|
|
13
18
|
);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import useSessionStore from '../../store/session';
|
|
3
|
+
import { useFetchSessionById } from '../session/useFetchSessionById';
|
|
4
|
+
import { IMessageItem, SessionDetailResponse } from '../../types/dto';
|
|
5
|
+
import { DeviceEventEmitter } from 'react-native';
|
|
6
|
+
import { events } from '../../constants/events';
|
|
7
|
+
|
|
8
|
+
const initialState = {
|
|
9
|
+
id: '',
|
|
10
|
+
title: '',
|
|
11
|
+
created_at: '',
|
|
12
|
+
updated_at: '',
|
|
13
|
+
metadata: {
|
|
14
|
+
source: '',
|
|
15
|
+
},
|
|
16
|
+
shared: false,
|
|
17
|
+
messages: [],
|
|
18
|
+
};
|
|
19
|
+
export const useMessage = () => {
|
|
20
|
+
const sessionId = useSessionStore(state => state.sessionId);
|
|
21
|
+
const { mutateAsync: getSessionById } = useFetchSessionById();
|
|
22
|
+
const [loadState, setLoadState] = useState<SessionDetailResponse>(initialState);
|
|
23
|
+
|
|
24
|
+
const onGetSessionById = useCallback(async () => {
|
|
25
|
+
if (!sessionId) return;
|
|
26
|
+
const res = await getSessionById(sessionId);
|
|
27
|
+
if (!res) return;
|
|
28
|
+
setLoadState(prev => ({
|
|
29
|
+
...res,
|
|
30
|
+
messages: res?.messages?.length > 0 ? res.messages.reverse() : prev.messages,
|
|
31
|
+
}));
|
|
32
|
+
}, [sessionId]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!sessionId) {
|
|
36
|
+
setLoadState(initialState);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
onGetSessionById();
|
|
40
|
+
}, [sessionId]);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const subOneMessage = DeviceEventEmitter.addListener(
|
|
44
|
+
events.updateOneMessage,
|
|
45
|
+
(messageItem: IMessageItem) => {
|
|
46
|
+
setLoadState(prev => ({
|
|
47
|
+
...prev,
|
|
48
|
+
messages: [messageItem, ...prev.messages],
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
const subMultipleMessage = DeviceEventEmitter.addListener(
|
|
53
|
+
events.updateMultipleMessage,
|
|
54
|
+
(messageItems: IMessageItem[]) => {
|
|
55
|
+
setLoadState(prev => {
|
|
56
|
+
const existingIds = new Set(prev.messages.map(m => m?.id));
|
|
57
|
+
const filtered = messageItems.filter(m => !existingIds.has(m?.id)).reverse();
|
|
58
|
+
console.log('🚀 ~ useEffect ~ filtered:', prev.messages);
|
|
59
|
+
return {
|
|
60
|
+
...prev,
|
|
61
|
+
messages: [...filtered, ...prev.messages],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
const subForceUpdateMessages = DeviceEventEmitter.addListener(
|
|
67
|
+
events.forceUpdateMessages,
|
|
68
|
+
(messages: IMessageItem[]) => {
|
|
69
|
+
setLoadState(prev => {
|
|
70
|
+
const merged = prev.messages.map(msg => {
|
|
71
|
+
const update = messages.find(u => u.id === msg.id);
|
|
72
|
+
return update ? { ...msg, ...update } : msg;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const existingIds = new Set(prev.messages.map(m => m.id));
|
|
76
|
+
const newItems = messages.filter(u => !existingIds.has(u.id));
|
|
77
|
+
return {
|
|
78
|
+
...prev,
|
|
79
|
+
messages: [...newItems, ...merged],
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
return () => {
|
|
85
|
+
subOneMessage.remove();
|
|
86
|
+
subMultipleMessage.remove();
|
|
87
|
+
subForceUpdateMessages.remove();
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
messageState: loadState,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import useSessionStore from '../../store/session';
|
|
3
|
+
import { useStreamMessage } from './useStreamMessage';
|
|
4
|
+
import { events } from '../../constants/events';
|
|
5
|
+
import { DeviceEventEmitter } from 'react-native';
|
|
6
|
+
import { IMessageItem, MessageType, RoleType } from '../../types';
|
|
7
|
+
import dayjs from 'dayjs';
|
|
8
|
+
import { useCreateSession } from '../session/useCreateSession';
|
|
9
|
+
|
|
10
|
+
export const useSendMessage = () => {
|
|
11
|
+
const { startStream, stopStream } = useStreamMessage();
|
|
12
|
+
const { mutateAsync: createSession } = useCreateSession();
|
|
13
|
+
|
|
14
|
+
const onSendMessage = useCallback(
|
|
15
|
+
async (message: string) => {
|
|
16
|
+
const messageItem: IMessageItem = {
|
|
17
|
+
id: dayjs().valueOf().toString(),
|
|
18
|
+
attachments: [],
|
|
19
|
+
type: MessageType.user_message,
|
|
20
|
+
created_at: dayjs().toISOString(),
|
|
21
|
+
modified_at: dayjs().toISOString(),
|
|
22
|
+
content: message,
|
|
23
|
+
additional_kwargs: {},
|
|
24
|
+
response_metadata: {},
|
|
25
|
+
name: null,
|
|
26
|
+
role: RoleType.user,
|
|
27
|
+
metadata: {},
|
|
28
|
+
};
|
|
29
|
+
DeviceEventEmitter.emit(events.updateOneMessage, messageItem);
|
|
30
|
+
|
|
31
|
+
let latestSessionId;
|
|
32
|
+
if (!useSessionStore.getState().sessionId) {
|
|
33
|
+
const resSession = await createSession({ title: message });
|
|
34
|
+
if (!resSession) return;
|
|
35
|
+
useSessionStore.getState().setSessionId(resSession.id);
|
|
36
|
+
latestSessionId = resSession.id;
|
|
37
|
+
}
|
|
38
|
+
startStream(
|
|
39
|
+
{
|
|
40
|
+
content: message,
|
|
41
|
+
stream: true,
|
|
42
|
+
attachments: [],
|
|
43
|
+
metadata: {
|
|
44
|
+
source: 'user',
|
|
45
|
+
},
|
|
46
|
+
manual_retry_attempts: 0,
|
|
47
|
+
},
|
|
48
|
+
latestSessionId
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
[startStream]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
onSendMessage,
|
|
56
|
+
stopStream,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useEffect, useRef, useCallback } from 'react';
|
|
2
|
+
import { useChatContext } from '../../context/ChatContext';
|
|
3
|
+
import { ENDPOINTS } from '../../services/endpoints';
|
|
4
|
+
import { type IMessageItem, type StreamMessageRequest } from '../../types';
|
|
5
|
+
import EventSource from 'react-native-sse';
|
|
6
|
+
import useStreamMessageStore from '../../store/streamMessage';
|
|
7
|
+
import throttle from 'lodash/throttle';
|
|
8
|
+
import { DeviceEventEmitter } from 'react-native';
|
|
9
|
+
import { events } from '../../constants/events';
|
|
10
|
+
import useSessionStore from '../../store/session';
|
|
11
|
+
|
|
12
|
+
export const useStreamMessage = () => {
|
|
13
|
+
const sessionId = useSessionStore(state => state.sessionId);
|
|
14
|
+
const esRef = useRef<EventSource | null>(null);
|
|
15
|
+
const sessionIdRef = useRef<string | undefined>(sessionId);
|
|
16
|
+
const { apiAddress } = useChatContext();
|
|
17
|
+
const setStreamMessage = useStreamMessageStore(state => state.setStreamMessage);
|
|
18
|
+
const setIsStreaming = useStreamMessageStore(state => state.setIsStreaming);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
sessionIdRef.current = sessionId;
|
|
22
|
+
}, [sessionId]);
|
|
23
|
+
|
|
24
|
+
const startStream = useCallback(
|
|
25
|
+
(payload: StreamMessageRequest, latestSessionId?: string) => {
|
|
26
|
+
const token = (global as any)?.accessToken;
|
|
27
|
+
const currentSessionId = latestSessionId || sessionIdRef.current;
|
|
28
|
+
|
|
29
|
+
if (!currentSessionId || !token) return;
|
|
30
|
+
|
|
31
|
+
if (esRef.current) {
|
|
32
|
+
esRef.current.close();
|
|
33
|
+
esRef.current = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const url = `${apiAddress}${ENDPOINTS.assistantService.getStreamMessage(currentSessionId)}`;
|
|
37
|
+
|
|
38
|
+
const es = new EventSource(url, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
Authorization: `Bearer ${token}`,
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify(payload),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
es.addEventListener('open', () => {
|
|
48
|
+
console.log('🔵 Stream opened');
|
|
49
|
+
setIsStreaming(true);
|
|
50
|
+
setStreamMessage({});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let buffers: Record<string, IMessageItem> = {};
|
|
54
|
+
|
|
55
|
+
const throttledFlush = throttle(
|
|
56
|
+
() => {
|
|
57
|
+
DeviceEventEmitter.emit(
|
|
58
|
+
events.updateMultipleMessage,
|
|
59
|
+
Object.values(buffers).map(m => ({ ...m, content: '' }))
|
|
60
|
+
);
|
|
61
|
+
setStreamMessage(buffers);
|
|
62
|
+
},
|
|
63
|
+
300,
|
|
64
|
+
{ leading: true, trailing: true }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
es.addEventListener('message', event => {
|
|
68
|
+
console.log('📩 Stream message:', typeof event.data, JSON?.parse?.(event?.data || '{}'));
|
|
69
|
+
try {
|
|
70
|
+
const data = JSON?.parse?.(event?.data || '{}');
|
|
71
|
+
if (!!data?.is_done) {
|
|
72
|
+
console.log('✅ Stream done');
|
|
73
|
+
es.close();
|
|
74
|
+
setIsStreaming(false);
|
|
75
|
+
} else {
|
|
76
|
+
const message = data?.messages as IMessageItem;
|
|
77
|
+
if (!message?.id) return;
|
|
78
|
+
if (buffers?.[message?.id]) {
|
|
79
|
+
buffers[message?.id] = {
|
|
80
|
+
...message,
|
|
81
|
+
content: [buffers?.[message?.id]?.content || '', message?.content || ''].join(''),
|
|
82
|
+
};
|
|
83
|
+
} else {
|
|
84
|
+
buffers[message?.id] = message;
|
|
85
|
+
}
|
|
86
|
+
throttledFlush();
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
es.close();
|
|
90
|
+
setIsStreaming(false);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
es.addEventListener('error', err => {
|
|
95
|
+
console.log('❌ Stream error:', err);
|
|
96
|
+
setIsStreaming(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
es.addEventListener('close', () => {
|
|
100
|
+
console.log('🔴 Stream closed');
|
|
101
|
+
DeviceEventEmitter.emit(events.forceUpdateMessages, Object.values(buffers));
|
|
102
|
+
buffers = {};
|
|
103
|
+
setIsStreaming(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
esRef.current = es;
|
|
107
|
+
},
|
|
108
|
+
[apiAddress]
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const stopStream = useCallback(() => {
|
|
112
|
+
if (esRef.current) {
|
|
113
|
+
console.log('🛑 Closing stream');
|
|
114
|
+
esRef.current.close();
|
|
115
|
+
esRef.current = null;
|
|
116
|
+
setIsStreaming(false);
|
|
117
|
+
}
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
return () => {
|
|
122
|
+
stopStream();
|
|
123
|
+
};
|
|
124
|
+
}, [stopStream]);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
startStream,
|
|
128
|
+
stopStream,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { QUERY_KEYS } from '../../constants/query';
|
|
3
|
+
import { apiInstance } from '../../services/apis';
|
|
4
|
+
import { ENDPOINTS } from '../../services/endpoints';
|
|
5
|
+
import { BaseResponse } from '../../types/common';
|
|
6
|
+
import { CreateSessionRequest, CreateSessionResponse } from '../../types/dto';
|
|
7
|
+
|
|
8
|
+
const MAX_TITLE_LENGTH = 300;
|
|
9
|
+
|
|
10
|
+
export const useCreateSession = () =>
|
|
11
|
+
useMutation({
|
|
12
|
+
mutationKey: [QUERY_KEYS.CREATE_SESSION],
|
|
13
|
+
mutationFn: async (payload: CreateSessionRequest) => {
|
|
14
|
+
const res = await apiInstance?.post<BaseResponse<CreateSessionResponse>>(
|
|
15
|
+
ENDPOINTS.assistantService.createSession,
|
|
16
|
+
{
|
|
17
|
+
...payload,
|
|
18
|
+
title: payload.title.slice(0, MAX_TITLE_LENGTH),
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
return res?.data?.data;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { QUERY_KEYS } from '../../constants/query';
|
|
3
|
+
import { apiInstance } from '../../services';
|
|
4
|
+
import { BaseResponse } from '../../types/common';
|
|
5
|
+
import { SessionDetailResponse } from '../../types/dto';
|
|
6
|
+
import { ENDPOINTS } from '../../services/endpoints';
|
|
7
|
+
|
|
8
|
+
export const useFetchSessionById = () =>
|
|
9
|
+
useMutation({
|
|
10
|
+
mutationKey: [QUERY_KEYS.GET_SESSION_BY_ID],
|
|
11
|
+
mutationFn: async (sessionId: string) => {
|
|
12
|
+
const res = await apiInstance?.get<BaseResponse<SessionDetailResponse>>(
|
|
13
|
+
ENDPOINTS.assistantService.getSessionById(sessionId)
|
|
14
|
+
);
|
|
15
|
+
return res?.data?.data;
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { QUERY_KEYS } from '../../constants/query';
|
|
3
|
+
import { apiInstance } from '../../services/apis';
|
|
4
|
+
import { ENDPOINTS } from '../../services/endpoints';
|
|
5
|
+
import { BaseResponse } from '../../types/common';
|
|
6
|
+
import { SuggetionResponse } from '../../types/dto';
|
|
7
|
+
|
|
8
|
+
export const useFetchSuggestions = () =>
|
|
9
|
+
useQuery({
|
|
10
|
+
queryKey: [QUERY_KEYS.GET_SUGGESTIONS],
|
|
11
|
+
queryFn: async () => {
|
|
12
|
+
const res = await apiInstance?.get<BaseResponse<SuggetionResponse>>(
|
|
13
|
+
ENDPOINTS.assistantService.getSuggestions
|
|
14
|
+
);
|
|
15
|
+
return res?.data?.data;
|
|
16
|
+
},
|
|
17
|
+
enabled: !!apiInstance,
|
|
18
|
+
});
|
package/src/ignore.d.ts
CHANGED
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const ENDPOINTS = {
|
|
2
|
+
assistantService: {
|
|
3
|
+
getSuggestions: '/assistant-service/v1/app/chat/prompt-suggestions',
|
|
4
|
+
getSessionById: (sessionId: string) => `/assistant-service/v1/app/chat/sessions/${sessionId}`,
|
|
5
|
+
getStreamMessage: (sessionId: string) =>
|
|
6
|
+
`/assistant-service/v1/app/chat/sessions/${sessionId}/stream`,
|
|
7
|
+
createSession: '/assistant-service/v1/app/chat/sessions',
|
|
8
|
+
},
|
|
9
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { StreamMessageStore } from '../types/chat';
|
|
3
|
+
import { IMessageItem } from '../types';
|
|
4
|
+
|
|
5
|
+
const useStreamMessageStore = create<StreamMessageStore>(set => ({
|
|
6
|
+
isStreaming: false,
|
|
7
|
+
setIsStreaming: isStreaming => set({ isStreaming }),
|
|
8
|
+
streamMessage: {},
|
|
9
|
+
setStreamMessage: (streamMessage: Record<string, IMessageItem>) => {
|
|
10
|
+
if (!streamMessage || Object.keys(streamMessage).length === 0) return;
|
|
11
|
+
|
|
12
|
+
set(state => {
|
|
13
|
+
const updated = { ...state.streamMessage };
|
|
14
|
+
|
|
15
|
+
for (const [id, msg] of Object.entries(streamMessage)) {
|
|
16
|
+
updated[id] = msg;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { streamMessage: updated };
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
export default useStreamMessageStore;
|
package/src/types/chat.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { IMessageItem } from './dto';
|
|
2
2
|
|
|
3
|
-
export interface ChatContextType {
|
|
3
|
+
export interface ChatContextType {
|
|
4
|
+
apiAddress: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
}
|
|
4
7
|
|
|
5
|
-
export interface ChatProviderProps
|
|
8
|
+
export interface ChatProviderProps {
|
|
9
|
+
apiAddress: string;
|
|
10
|
+
userId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SessionStore {
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
setSessionId: (sessionId: string | undefined) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StreamMessageStore {
|
|
19
|
+
isStreaming: boolean;
|
|
20
|
+
setIsStreaming: (isStreaming: boolean) => void;
|
|
21
|
+
streamMessage: Record<string, IMessageItem>;
|
|
22
|
+
setStreamMessage: (streamMessage: Record<string, IMessageItem>) => void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface BaseResponse<T> {
|
|
2
|
+
statusCode: number;
|
|
3
|
+
message: any;
|
|
4
|
+
data: T;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface Pageable {
|
|
8
|
+
pageNumber: number;
|
|
9
|
+
totalPages: number;
|
|
10
|
+
pageSize: number;
|
|
11
|
+
totalElements: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export enum SuggestionCategory {
|
|
15
|
+
product = 'product',
|
|
16
|
+
discount = 'discount',
|
|
17
|
+
gift = 'gift',
|
|
18
|
+
health_advice = 'health_advice',
|
|
19
|
+
content = 'content',
|
|
20
|
+
guide = 'guide',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export enum MessageType {
|
|
24
|
+
user_message = 'user-message',
|
|
25
|
+
ai_thinking = 'ai-thinking',
|
|
26
|
+
ai_answer = 'ai-answer',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export enum RoleType {
|
|
30
|
+
user = 'user',
|
|
31
|
+
ai = 'ai',
|
|
32
|
+
}
|
package/src/types/dto.ts
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { MessageType, RoleType, SuggestionCategory } from './common';
|
|
2
|
+
|
|
3
|
+
export interface ISuggestionItem {
|
|
4
|
+
content: string;
|
|
5
|
+
category: SuggestionCategory;
|
|
6
|
+
suggestion_id: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SuggetionResponse {
|
|
10
|
+
group_suggestion_id: string;
|
|
11
|
+
suggestions: ISuggestionItem[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IMessageItem {
|
|
15
|
+
id: string;
|
|
16
|
+
content: string;
|
|
17
|
+
additional_kwargs: Record<string, any>;
|
|
18
|
+
response_metadata: Record<string, any>;
|
|
19
|
+
type: MessageType;
|
|
20
|
+
name: string | null;
|
|
21
|
+
role: RoleType;
|
|
22
|
+
metadata: Record<string, any>;
|
|
23
|
+
created_at: string;
|
|
24
|
+
modified_at: string;
|
|
25
|
+
attachments: any[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SessionDetailResponse {
|
|
29
|
+
id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
created_at: string;
|
|
32
|
+
updated_at: string;
|
|
33
|
+
metadata: {
|
|
34
|
+
source: string;
|
|
35
|
+
};
|
|
36
|
+
shared: boolean;
|
|
37
|
+
messages: IMessageItem[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface IAttachment {
|
|
41
|
+
type: 'image' | 'file';
|
|
42
|
+
source_type: 'url';
|
|
43
|
+
data: string;
|
|
44
|
+
name?: string;
|
|
45
|
+
size?: number;
|
|
46
|
+
loading?: boolean;
|
|
47
|
+
mime_type: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/jpg' | 'application/pdf';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface StreamMessageRequest {
|
|
51
|
+
content: string;
|
|
52
|
+
stream: boolean;
|
|
53
|
+
attachments: IAttachment[];
|
|
54
|
+
clicked_suggestion_id?: string;
|
|
55
|
+
metadata: {
|
|
56
|
+
source: 'user';
|
|
57
|
+
};
|
|
58
|
+
manual_retry_attempts: number;
|
|
59
|
+
retried_message_id?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CreateSessionRequest {
|
|
63
|
+
title: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CreateSessionResponse {
|
|
67
|
+
id: string;
|
|
68
|
+
title: string;
|
|
69
|
+
created_at: string;
|
|
70
|
+
updated_at: string;
|
|
71
|
+
metadata: {
|
|
72
|
+
source: string;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["utils/index.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/index.ts"],"names":[],"mappings":""}
|
package/src/index.tsx
DELETED
package/src/utils/index.ts
DELETED
|
File without changes
|