@droppii-org/chat-mobile 0.2.12 → 0.2.13
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/ThreadCard/AvatarSection.js +2 -3
- package/lib/module/components/ThreadCard/AvatarSection.js.map +1 -1
- package/lib/module/components/flows/inputButtons/index.js +77 -0
- package/lib/module/components/flows/inputButtons/index.js.map +1 -0
- package/lib/module/config/api-endpoints.js +1 -0
- package/lib/module/config/api-endpoints.js.map +1 -1
- package/lib/module/context/ChatDetailContext.js +57 -0
- package/lib/module/context/ChatDetailContext.js.map +1 -0
- package/lib/module/hooks/flows/useCurrentConversationFlow.js +9 -0
- package/lib/module/hooks/flows/useCurrentConversationFlow.js.map +1 -0
- package/lib/module/hooks/flows/useFlow.js +57 -0
- package/lib/module/hooks/flows/useFlow.js.map +1 -0
- package/lib/module/hooks/message/useSendMessage.js +4 -2
- package/lib/module/hooks/message/useSendMessage.js.map +1 -1
- package/lib/module/hooks/query-keys.js +4 -1
- package/lib/module/hooks/query-keys.js.map +1 -1
- package/lib/module/hooks/useConversationList.js +1 -1
- package/lib/module/hooks/useConversationList.js.map +1 -1
- package/lib/module/hooks/useLinkPreview/useFetchUrlMetadata.js +2 -3
- package/lib/module/hooks/useLinkPreview/useFetchUrlMetadata.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +40 -2
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +62 -58
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +41 -8
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatQuickActions.js +12 -3
- package/lib/module/screens/chat-detail/ChatQuickActions.js.map +1 -1
- package/lib/module/screens/chat-detail/conversationHeader.utils.js +1 -1
- package/lib/module/screens/chat-detail/conversationHeader.utils.js.map +1 -1
- package/lib/module/screens/inbox/Inbox.js +1 -3
- package/lib/module/screens/inbox/Inbox.js.map +1 -1
- package/lib/module/screens/inbox/MessagesTab.js +2 -1
- package/lib/module/screens/inbox/MessagesTab.js.map +1 -1
- package/lib/module/services/endpoints.js +4 -1
- package/lib/module/services/endpoints.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +3 -1
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/module/types/chat.js.map +1 -1
- package/lib/module/types/flows.js +17 -0
- package/lib/module/types/flows.js.map +1 -0
- package/lib/module/utils/flows.js +11 -0
- package/lib/module/utils/flows.js.map +1 -0
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts +1 -1
- package/lib/typescript/src/components/ThreadCard/AvatarSection.d.ts.map +1 -1
- package/lib/typescript/src/components/flows/inputButtons/index.d.ts +9 -0
- package/lib/typescript/src/components/flows/inputButtons/index.d.ts.map +1 -0
- package/lib/typescript/src/config/api-endpoints.d.ts +1 -0
- package/lib/typescript/src/config/api-endpoints.d.ts.map +1 -1
- package/lib/typescript/src/context/ChatDetailContext.d.ts +5 -0
- package/lib/typescript/src/context/ChatDetailContext.d.ts.map +1 -0
- package/lib/typescript/src/hooks/flows/useCurrentConversationFlow.d.ts +3 -0
- package/lib/typescript/src/hooks/flows/useCurrentConversationFlow.d.ts.map +1 -0
- package/lib/typescript/src/hooks/flows/useFlow.d.ts +4 -0
- package/lib/typescript/src/hooks/flows/useFlow.d.ts.map +1 -0
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts +3 -1
- package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/src/hooks/query-keys.d.ts +3 -0
- package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useConversationList.d.ts +2 -2
- package/lib/typescript/src/hooks/useConversationList.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useLinkPreview/useFetchUrlMetadata.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatQuickActions.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/conversationHeader.utils.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/types.d.ts +1 -0
- package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
- package/lib/typescript/src/screens/inbox/Inbox.d.ts +1 -2
- package/lib/typescript/src/screens/inbox/Inbox.d.ts.map +1 -1
- package/lib/typescript/src/screens/inbox/MessagesTab.d.ts +1 -2
- package/lib/typescript/src/screens/inbox/MessagesTab.d.ts.map +1 -1
- package/lib/typescript/src/services/endpoints.d.ts +3 -0
- package/lib/typescript/src/services/endpoints.d.ts.map +1 -1
- package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
- package/lib/typescript/src/types/chat.d.ts +11 -1
- package/lib/typescript/src/types/chat.d.ts.map +1 -1
- package/lib/typescript/src/types/flows.d.ts +82 -0
- package/lib/typescript/src/types/flows.d.ts.map +1 -0
- package/lib/typescript/src/utils/flows.d.ts +4 -0
- package/lib/typescript/src/utils/flows.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/components/ThreadCard/AvatarSection.tsx +2 -2
- package/src/components/flows/inputButtons/index.tsx +105 -0
- package/src/config/api-endpoints.ts +2 -0
- package/src/context/ChatDetailContext.tsx +85 -0
- package/src/hooks/flows/useCurrentConversationFlow.ts +11 -0
- package/src/hooks/flows/useFlow.ts +63 -0
- package/src/hooks/message/useSendMessage.ts +4 -0
- package/src/hooks/query-keys.ts +6 -0
- package/src/hooks/useConversationList.ts +15 -7
- package/src/hooks/useLinkPreview/useFetchUrlMetadata.ts +1 -2
- package/src/screens/chat-detail/ChatComposer.tsx +39 -1
- package/src/screens/chat-detail/ChatDetail.tsx +79 -73
- package/src/screens/chat-detail/ChatListLegend.tsx +54 -3
- package/src/screens/chat-detail/ChatQuickActions.tsx +10 -3
- package/src/screens/chat-detail/conversationHeader.utils.ts +1 -4
- package/src/screens/chat-detail/types.ts +1 -0
- package/src/screens/inbox/Inbox.tsx +3 -15
- package/src/screens/inbox/MessagesTab.tsx +48 -49
- package/src/services/endpoints.ts +6 -0
- package/src/translation/resources/i18n.ts +2 -0
- package/src/types/chat.ts +16 -1
- package/src/types/flows.ts +102 -0
- package/src/utils/flows.ts +18 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import type {
|
|
10
|
+
ChatDetailContextType,
|
|
11
|
+
ChatDetailProviderProps,
|
|
12
|
+
} from '../types/chat';
|
|
13
|
+
import { fechtCurrentConversationFlow } from '../hooks/flows/useCurrentConversationFlow';
|
|
14
|
+
import { useConversationStore } from '../store';
|
|
15
|
+
import { PeerType } from '@droppii/openim-rn-client-sdk';
|
|
16
|
+
import type { IConversationFlow, IConversationFlowStep } from '../types/flows';
|
|
17
|
+
|
|
18
|
+
export const ChatDetailContext = createContext<ChatDetailContextType>({
|
|
19
|
+
isBotConversation: false,
|
|
20
|
+
conversationFlow: undefined,
|
|
21
|
+
refetchConversationFlow: () => {},
|
|
22
|
+
setConverationFlowStep: () => {},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const useChatDetailContext = (): ChatDetailContextType => {
|
|
26
|
+
const context = useContext(ChatDetailContext);
|
|
27
|
+
if (!context) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
'useChatDetailContext must be used within ChatDetailProvider'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return context;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const ChatDetailProvider = ({ children }: ChatDetailProviderProps) => {
|
|
36
|
+
const conversationId = useConversationStore(
|
|
37
|
+
(state) => state.currentConversationId || ''
|
|
38
|
+
);
|
|
39
|
+
const conversation = useConversationStore(
|
|
40
|
+
(state) => state.map[conversationId]
|
|
41
|
+
);
|
|
42
|
+
const [conversationFlow, setConverationFlow] = useState<
|
|
43
|
+
IConversationFlow | undefined
|
|
44
|
+
>(undefined);
|
|
45
|
+
|
|
46
|
+
const isBotConversation = useMemo(() => {
|
|
47
|
+
return conversation?.peerType === PeerType.Bot;
|
|
48
|
+
}, [conversation]);
|
|
49
|
+
|
|
50
|
+
const setConverationFlowStep = useCallback((step: IConversationFlowStep) => {
|
|
51
|
+
setConverationFlow(
|
|
52
|
+
(prev) =>
|
|
53
|
+
({
|
|
54
|
+
...prev,
|
|
55
|
+
flowCurrentStep: step,
|
|
56
|
+
}) as IConversationFlow
|
|
57
|
+
);
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const getConversationFlow = useCallback(async () => {
|
|
61
|
+
const res = await fechtCurrentConversationFlow(conversationId);
|
|
62
|
+
if (res) {
|
|
63
|
+
setConverationFlow(res);
|
|
64
|
+
}
|
|
65
|
+
}, [conversationId]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!!conversationId && !!isBotConversation) {
|
|
69
|
+
getConversationFlow();
|
|
70
|
+
}
|
|
71
|
+
}, [conversationId, isBotConversation, getConversationFlow]);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<ChatDetailContext.Provider
|
|
75
|
+
value={{
|
|
76
|
+
conversationFlow,
|
|
77
|
+
refetchConversationFlow: () => {},
|
|
78
|
+
setConverationFlowStep,
|
|
79
|
+
isBotConversation,
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{children}
|
|
83
|
+
</ChatDetailContext.Provider>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ENDPOINTS } from '../../services/endpoints';
|
|
2
|
+
import type { BaseResponse } from '../../types/auth';
|
|
3
|
+
import { apiInstance } from '../../services/apis';
|
|
4
|
+
import type { IConversationFlow } from '../../types/flows';
|
|
5
|
+
|
|
6
|
+
export const fechtCurrentConversationFlow = async (conversationId: string) => {
|
|
7
|
+
const res = await apiInstance?.get<BaseResponse<IConversationFlow>>(
|
|
8
|
+
ENDPOINTS.chatService.conversationFlow(conversationId)
|
|
9
|
+
);
|
|
10
|
+
return res?.data?.data || undefined;
|
|
11
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { conversationQueryKeys } from '../query-keys';
|
|
3
|
+
import { useConversationStore } from '../../store';
|
|
4
|
+
import { apiInstance } from '../../services/apis';
|
|
5
|
+
import type { BaseResponse } from '../../types/auth';
|
|
6
|
+
import { ENDPOINTS } from '../../services/endpoints';
|
|
7
|
+
import type {
|
|
8
|
+
IConversationFlow,
|
|
9
|
+
IRunConversationFlowPayload,
|
|
10
|
+
IRunConversationFlowResponse,
|
|
11
|
+
} from '../../types/flows';
|
|
12
|
+
import { useSendMessage } from '../message/useSendMessage';
|
|
13
|
+
import { useChatDetailContext } from '../../context/ChatDetailContext';
|
|
14
|
+
|
|
15
|
+
export const useResetFlow = () => {
|
|
16
|
+
const conversationId =
|
|
17
|
+
useConversationStore((state) => state.currentConversationId) || '';
|
|
18
|
+
const { mutate: runFlow } = useRunFlow();
|
|
19
|
+
return useMutation({
|
|
20
|
+
mutationKey: conversationQueryKeys.flow(conversationId),
|
|
21
|
+
mutationFn: async () => {
|
|
22
|
+
const res = await apiInstance?.post<BaseResponse<IConversationFlow>>(
|
|
23
|
+
ENDPOINTS.chatService.resetConversationFlow(conversationId),
|
|
24
|
+
{}
|
|
25
|
+
);
|
|
26
|
+
return res?.data?.data || undefined;
|
|
27
|
+
},
|
|
28
|
+
onSuccess: (data) => {
|
|
29
|
+
if (data?.flowCurrentStep?.id) {
|
|
30
|
+
runFlow({
|
|
31
|
+
groupId: data?.flowCurrentStep?.id,
|
|
32
|
+
childId: null,
|
|
33
|
+
actionId: null,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const useRunFlow = () => {
|
|
41
|
+
const conversationId =
|
|
42
|
+
useConversationStore((state) => state.currentConversationId) || '';
|
|
43
|
+
const { sendMessage } = useSendMessage();
|
|
44
|
+
const { setConverationFlowStep } = useChatDetailContext();
|
|
45
|
+
return useMutation({
|
|
46
|
+
mutationKey: conversationQueryKeys.runFlow(conversationId),
|
|
47
|
+
mutationFn: async (payload: IRunConversationFlowPayload) => {
|
|
48
|
+
await sendMessage({
|
|
49
|
+
plainText: payload?.plainText || '',
|
|
50
|
+
flow: payload,
|
|
51
|
+
});
|
|
52
|
+
const res = await apiInstance?.post<
|
|
53
|
+
BaseResponse<IRunConversationFlowResponse>
|
|
54
|
+
>(ENDPOINTS.chatService.runConversationFlow(conversationId), payload);
|
|
55
|
+
return res?.data?.data || undefined;
|
|
56
|
+
},
|
|
57
|
+
onSuccess: (data) => {
|
|
58
|
+
if (data?.flowNextStep) {
|
|
59
|
+
setConverationFlowStep(data?.flowNextStep);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
};
|
|
@@ -13,6 +13,7 @@ import { useChatContext } from '../../context';
|
|
|
13
13
|
import { generateContentBasedOnMessageType } from '../../utils/conversation';
|
|
14
14
|
import { useMessageStore } from '../../store/message';
|
|
15
15
|
import type { IUrlMetadata } from '../../types/common';
|
|
16
|
+
import type { IRunConversationFlowPayload } from '../../types/flows';
|
|
16
17
|
|
|
17
18
|
export const useSendMessage = () => {
|
|
18
19
|
const applicationType = useChatContext().applicationType;
|
|
@@ -23,10 +24,12 @@ export const useSendMessage = () => {
|
|
|
23
24
|
plainText,
|
|
24
25
|
files,
|
|
25
26
|
urlMetadata,
|
|
27
|
+
flow,
|
|
26
28
|
}: {
|
|
27
29
|
plainText?: string;
|
|
28
30
|
files?: UploadFileItem[]; //TODO
|
|
29
31
|
urlMetadata?: IUrlMetadata;
|
|
32
|
+
flow?: IRunConversationFlowPayload;
|
|
30
33
|
}) => {
|
|
31
34
|
const { userID: recvID, groupID } =
|
|
32
35
|
useConversationStore.getState().getCurrentConversation() || {};
|
|
@@ -62,6 +65,7 @@ export const useSendMessage = () => {
|
|
|
62
65
|
const extendMessageInfo: IMessageItemEx = {
|
|
63
66
|
applicationType,
|
|
64
67
|
urlMetadata,
|
|
68
|
+
flow,
|
|
65
69
|
};
|
|
66
70
|
|
|
67
71
|
const successMessage = await OpenIMSDK.sendMessage({
|
package/src/hooks/query-keys.ts
CHANGED
|
@@ -11,6 +11,12 @@ export const conversationQueryKeys = {
|
|
|
11
11
|
details: () => [...conversationQueryKeys.all, 'detail'] as const,
|
|
12
12
|
detail: (conversationId: string) =>
|
|
13
13
|
[...conversationQueryKeys.details(), conversationId] as const,
|
|
14
|
+
flow: (conversationId: string) => ['conversationFlow', conversationId],
|
|
15
|
+
resetFlow: (conversationId: string) => [
|
|
16
|
+
'resetConversationFlow',
|
|
17
|
+
conversationId,
|
|
18
|
+
],
|
|
19
|
+
runFlow: (conversationId: string) => ['runConversationFlow', conversationId],
|
|
14
20
|
};
|
|
15
21
|
|
|
16
22
|
export type ConversationQueryKeys = typeof conversationQueryKeys;
|
|
@@ -5,10 +5,14 @@ import OpenIMSDK, {
|
|
|
5
5
|
} from '@droppii/openim-rn-client-sdk';
|
|
6
6
|
import { conversationQueryKeys } from './query-keys';
|
|
7
7
|
import { useConversationStore } from '../store';
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
DChatApplicationType,
|
|
10
|
+
DConversationItem,
|
|
11
|
+
DMessageItem,
|
|
12
|
+
} from '../types/chat';
|
|
9
13
|
|
|
10
14
|
type Params = {
|
|
11
|
-
applicationType:
|
|
15
|
+
applicationType: DChatApplicationType;
|
|
12
16
|
page: number;
|
|
13
17
|
enabled?: boolean;
|
|
14
18
|
};
|
|
@@ -23,17 +27,22 @@ function toConversationItem(raw: ConversationItem): DConversationItem {
|
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
let applicationType:
|
|
30
|
+
let applicationType: DChatApplicationType | undefined;
|
|
27
31
|
if (raw.ex) {
|
|
28
32
|
try {
|
|
29
|
-
applicationType = (
|
|
30
|
-
.applicationType
|
|
33
|
+
applicationType = (
|
|
34
|
+
JSON.parse(raw.ex) as { applicationType?: DChatApplicationType }
|
|
35
|
+
).applicationType;
|
|
31
36
|
} catch {
|
|
32
37
|
// leave undefined
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
...(raw as DConversationItem),
|
|
43
|
+
lastMessage,
|
|
44
|
+
applicationType: applicationType as DChatApplicationType,
|
|
45
|
+
};
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export function useConversationList({
|
|
@@ -55,7 +64,6 @@ export function useConversationList({
|
|
|
55
64
|
count: 20,
|
|
56
65
|
applicationType,
|
|
57
66
|
});
|
|
58
|
-
|
|
59
67
|
const conversations = rawList.map(toConversationItem);
|
|
60
68
|
useConversationStore.getState().updateConversations(conversations);
|
|
61
69
|
return conversations;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
2
|
import { commonQueryKeys } from '../query-keys';
|
|
3
3
|
import { apiInstance } from '../../services/apis';
|
|
4
4
|
import type { BaseResponse } from '../../types/auth';
|
|
@@ -19,5 +19,4 @@ export const useFetchUrlMetadata = (url?: string) =>
|
|
|
19
19
|
: null;
|
|
20
20
|
},
|
|
21
21
|
enabled: !!url,
|
|
22
|
-
placeholderData: keepPreviousData,
|
|
23
22
|
});
|
|
@@ -2,7 +2,7 @@ import { memo, useCallback, useEffect, useMemo } from 'react';
|
|
|
2
2
|
import { Platform, StyleSheet } from 'react-native';
|
|
3
3
|
import Reanimated from 'react-native-reanimated';
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
|
-
import { KContainer, KColors, KImage } from '@droppii/libs';
|
|
5
|
+
import { KContainer, KColors, KImage, KButton } from '@droppii/libs';
|
|
6
6
|
|
|
7
7
|
const IS_IOS = Platform.OS === 'ios';
|
|
8
8
|
const INPUT_LINE_HEIGHT = 22.4;
|
|
@@ -25,6 +25,10 @@ import { useChatComposerState } from './hooks/useChatComposerState';
|
|
|
25
25
|
import { useChatComposerAnimation } from './hooks/useChatComposerAnimation';
|
|
26
26
|
import { useAttachmentSendHandler } from './hooks/useAttachmentSendHandler';
|
|
27
27
|
import { isVideoFromPicker } from '../../utils/imageUtils';
|
|
28
|
+
import { useChatDetailContext } from '../../context/ChatDetailContext';
|
|
29
|
+
import { trans } from '../../translation';
|
|
30
|
+
import { useRunFlow } from '../../hooks/flows/useFlow';
|
|
31
|
+
import { ReactFlowsNodeTypes } from '../../types/flows';
|
|
28
32
|
|
|
29
33
|
export const ChatComposer = memo(
|
|
30
34
|
({
|
|
@@ -43,10 +47,14 @@ export const ChatComposer = memo(
|
|
|
43
47
|
renderQuickActions,
|
|
44
48
|
renderAttachmentAction,
|
|
45
49
|
attachmentColumns,
|
|
50
|
+
isEmptyMessage = false,
|
|
46
51
|
}: ChatComposerProps) => {
|
|
47
52
|
const insets = useSafeAreaInsets();
|
|
48
53
|
const hasAttachmentActions = !!attachmentActions?.length;
|
|
49
54
|
|
|
55
|
+
const { isBotConversation, conversationFlow } = useChatDetailContext();
|
|
56
|
+
const { mutate: runFlow, isPending } = useRunFlow();
|
|
57
|
+
|
|
50
58
|
// Link preview
|
|
51
59
|
const {
|
|
52
60
|
isVisible: showLinkPreview,
|
|
@@ -207,6 +215,15 @@ export const ChatComposer = memo(
|
|
|
207
215
|
[setIsInputWrapped]
|
|
208
216
|
);
|
|
209
217
|
|
|
218
|
+
const onStartConversationFlow = useCallback(() => {
|
|
219
|
+
runFlow({
|
|
220
|
+
groupId: conversationFlow?.flowCurrentStep?.id || '',
|
|
221
|
+
actionId: null,
|
|
222
|
+
childId: null,
|
|
223
|
+
plainText: trans('start'),
|
|
224
|
+
});
|
|
225
|
+
}, [runFlow, conversationFlow]);
|
|
226
|
+
|
|
210
227
|
// Reset wrapped state when value is cleared
|
|
211
228
|
useEffect(() => {
|
|
212
229
|
if (!value) {
|
|
@@ -234,6 +251,27 @@ export const ChatComposer = memo(
|
|
|
234
251
|
[insets.bottom, isAttachmentOpen]
|
|
235
252
|
);
|
|
236
253
|
|
|
254
|
+
if (
|
|
255
|
+
isBotConversation &&
|
|
256
|
+
conversationFlow?.flowCurrentStep?.type === ReactFlowsNodeTypes.Start &&
|
|
257
|
+
isEmptyMessage
|
|
258
|
+
) {
|
|
259
|
+
return (
|
|
260
|
+
<KContainer.View padding={'1.5rem'} paddingB={0}>
|
|
261
|
+
<KButton.Solid
|
|
262
|
+
size="md"
|
|
263
|
+
label={trans('start')}
|
|
264
|
+
stretch
|
|
265
|
+
icon={{
|
|
266
|
+
vectorName: 'chat-square-o',
|
|
267
|
+
size: 20,
|
|
268
|
+
}}
|
|
269
|
+
loading={isPending}
|
|
270
|
+
onPress={onStartConversationFlow}
|
|
271
|
+
/>
|
|
272
|
+
</KContainer.View>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
237
275
|
return (
|
|
238
276
|
<KContainer.View background={'rgba(131, 137, 157, 0.05)'}>
|
|
239
277
|
<ChatQuickActions
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
import { useSendMessage } from '../../hooks/message/useSendMessage';
|
|
33
33
|
import type { IUrlMetadata } from '../../types/common';
|
|
34
34
|
import { useMessageStore } from '../../store/message';
|
|
35
|
+
import { ChatDetailProvider } from '../../context/ChatDetailContext';
|
|
35
36
|
|
|
36
37
|
const ChatDetail = memo(
|
|
37
38
|
({
|
|
@@ -222,84 +223,89 @@ const ChatDetail = memo(
|
|
|
222
223
|
const insets = useSafeAreaInsets();
|
|
223
224
|
|
|
224
225
|
return (
|
|
225
|
-
<
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
onPressSearch
|
|
237
|
-
|
|
238
|
-
onPressAddMember={
|
|
239
|
-
onPressAddMember ?? (() => console.log('[onPressAddMember]'))
|
|
240
|
-
}
|
|
241
|
-
onPressMenu={onPressMenu ?? (() => console.log('[onPressMenu]'))}
|
|
242
|
-
onPressAvatar={
|
|
243
|
-
onPressAvatar ?? (() => console.log('[onPressAvatar]'))
|
|
244
|
-
}
|
|
245
|
-
/>
|
|
246
|
-
|
|
247
|
-
<KeyboardAvoidingView
|
|
248
|
-
style={styles.keyboardAvoiding}
|
|
249
|
-
behavior="padding"
|
|
250
|
-
keyboardVerticalOffset={Platform.select({
|
|
251
|
-
android: insets.bottom,
|
|
252
|
-
default: 0,
|
|
253
|
-
})}
|
|
254
|
-
>
|
|
255
|
-
<ChatListLegend
|
|
256
|
-
messages={messages}
|
|
257
|
-
currentUserId={currentUserId}
|
|
258
|
-
renderChat={renderChat}
|
|
259
|
-
onLoadEarlier={onLoadEarlier}
|
|
260
|
-
isLoading={isLoading}
|
|
261
|
-
isLoadingEarlier={isLoadingEarlier}
|
|
262
|
-
hasMoreEarlier={hasMoreEarlier}
|
|
263
|
-
onMediaPress={(items, index) => {
|
|
264
|
-
mediaViewerRef.current?.show(items, index);
|
|
265
|
-
}}
|
|
266
|
-
onPressUrl={onPressUrl}
|
|
267
|
-
/>
|
|
268
|
-
|
|
269
|
-
<ChatComposer
|
|
270
|
-
value={currentInput}
|
|
271
|
-
placeholder={inputPlaceholder}
|
|
272
|
-
onChangeText={handleChangeText}
|
|
273
|
-
onSend={handleSend}
|
|
274
|
-
onMessageSent={handleAttachmentMessageSent}
|
|
275
|
-
onPressAttach={
|
|
276
|
-
onPressAttach ?? (() => console.log('[onPressAttach]'))
|
|
226
|
+
<ChatDetailProvider>
|
|
227
|
+
<KContainer.Page flex edges={['bottom']}>
|
|
228
|
+
<ChatDetailHeader
|
|
229
|
+
title={resolvedTitle}
|
|
230
|
+
subtitle={resolvedSubtitle}
|
|
231
|
+
avatarUri={resolvedAvatarUri}
|
|
232
|
+
avatarFullName={resolvedAvatarFullName}
|
|
233
|
+
peerType={resolvedPeerType}
|
|
234
|
+
applicationType={resolvedApplicationType}
|
|
235
|
+
showAddMember={resolvedShowAddMember}
|
|
236
|
+
onBack={onBack ?? handleBackDefault}
|
|
237
|
+
onPressSearch={
|
|
238
|
+
onPressSearch ?? (() => console.log('[onPressSearch]'))
|
|
277
239
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
quickActions={quickActions ?? quickActionsDefault}
|
|
281
|
-
attachmentActions={attachmentActions ?? attachmentActionsDefault}
|
|
282
|
-
onQuickActionPress={
|
|
283
|
-
onQuickActionPress ??
|
|
284
|
-
((action: DChatQuickAction) => {
|
|
285
|
-
console.log('[onQuickActionPress]', action.id, action.meta);
|
|
286
|
-
})
|
|
240
|
+
onPressAddMember={
|
|
241
|
+
onPressAddMember ?? (() => console.log('[onPressAddMember]'))
|
|
287
242
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
((
|
|
291
|
-
console.log('[onAttachmentAction]', action.id, action.meta);
|
|
292
|
-
})
|
|
243
|
+
onPressMenu={onPressMenu ?? (() => console.log('[onPressMenu]'))}
|
|
244
|
+
onPressAvatar={
|
|
245
|
+
onPressAvatar ?? (() => console.log('[onPressAvatar]'))
|
|
293
246
|
}
|
|
294
|
-
renderQuickAction={renderQuickAction}
|
|
295
|
-
renderQuickActions={renderQuickActions}
|
|
296
|
-
renderAttachmentAction={renderAttachmentAction}
|
|
297
|
-
attachmentColumns={attachmentColumns}
|
|
298
247
|
/>
|
|
299
|
-
</KeyboardAvoidingView>
|
|
300
248
|
|
|
301
|
-
|
|
302
|
-
|
|
249
|
+
<KeyboardAvoidingView
|
|
250
|
+
style={styles.keyboardAvoiding}
|
|
251
|
+
behavior="padding"
|
|
252
|
+
keyboardVerticalOffset={Platform.select({
|
|
253
|
+
android: insets.bottom,
|
|
254
|
+
default: 0,
|
|
255
|
+
})}
|
|
256
|
+
>
|
|
257
|
+
<ChatListLegend
|
|
258
|
+
messages={messages}
|
|
259
|
+
currentUserId={currentUserId}
|
|
260
|
+
renderChat={renderChat}
|
|
261
|
+
onLoadEarlier={onLoadEarlier}
|
|
262
|
+
isLoading={isLoading}
|
|
263
|
+
isLoadingEarlier={isLoadingEarlier}
|
|
264
|
+
hasMoreEarlier={hasMoreEarlier}
|
|
265
|
+
onMediaPress={(items, index) => {
|
|
266
|
+
mediaViewerRef.current?.show(items, index);
|
|
267
|
+
}}
|
|
268
|
+
onPressUrl={onPressUrl}
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
<ChatComposer
|
|
272
|
+
value={currentInput}
|
|
273
|
+
placeholder={inputPlaceholder}
|
|
274
|
+
onChangeText={handleChangeText}
|
|
275
|
+
onSend={handleSend}
|
|
276
|
+
onMessageSent={handleAttachmentMessageSent}
|
|
277
|
+
onPressAttach={
|
|
278
|
+
onPressAttach ?? (() => console.log('[onPressAttach]'))
|
|
279
|
+
}
|
|
280
|
+
onPressEmoji={
|
|
281
|
+
onPressEmoji ?? (() => console.log('[onPressEmoji]'))
|
|
282
|
+
}
|
|
283
|
+
showQuickActions={showQuickActions}
|
|
284
|
+
quickActions={quickActions ?? quickActionsDefault}
|
|
285
|
+
attachmentActions={attachmentActions ?? attachmentActionsDefault}
|
|
286
|
+
onQuickActionPress={
|
|
287
|
+
onQuickActionPress ??
|
|
288
|
+
((action: DChatQuickAction) => {
|
|
289
|
+
console.log('[onQuickActionPress]', action.id, action.meta);
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
onAttachmentAction={
|
|
293
|
+
onAttachmentAction ??
|
|
294
|
+
((action: DChatAttachmentAction) => {
|
|
295
|
+
console.log('[onAttachmentAction]', action.id, action.meta);
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
renderQuickAction={renderQuickAction}
|
|
299
|
+
renderQuickActions={renderQuickActions}
|
|
300
|
+
renderAttachmentAction={renderAttachmentAction}
|
|
301
|
+
attachmentColumns={attachmentColumns}
|
|
302
|
+
isEmptyMessage={messages?.length === 0}
|
|
303
|
+
/>
|
|
304
|
+
</KeyboardAvoidingView>
|
|
305
|
+
|
|
306
|
+
<MediaViewerModal ref={mediaViewerRef} />
|
|
307
|
+
</KContainer.Page>
|
|
308
|
+
</ChatDetailProvider>
|
|
303
309
|
);
|
|
304
310
|
}
|
|
305
311
|
);
|
|
@@ -13,6 +13,11 @@ import type { DMessageItem } from '../../types/chat';
|
|
|
13
13
|
import { LegendChatMessage } from './legend/LegendChatMessage';
|
|
14
14
|
import { LegendChatDay } from './legend/LegendChatDay';
|
|
15
15
|
import { LegendChatLoadEarlier } from './legend/LegendChatLoadEarlier';
|
|
16
|
+
import { useChatDetailContext } from '../../context/ChatDetailContext';
|
|
17
|
+
import { getFlowInputButtonsNode } from '../../utils/flows';
|
|
18
|
+
import { InputButtonFlow } from '../../components/flows/inputButtons';
|
|
19
|
+
import { useResetFlow, useRunFlow } from '../../hooks/flows/useFlow';
|
|
20
|
+
import type { IRunConversationFlowPayload } from '../../types/flows';
|
|
16
21
|
|
|
17
22
|
export const ChatListLegend = memo(
|
|
18
23
|
({
|
|
@@ -35,6 +40,15 @@ export const ChatListLegend = memo(
|
|
|
35
40
|
[messages]
|
|
36
41
|
);
|
|
37
42
|
|
|
43
|
+
const { isBotConversation, conversationFlow } = useChatDetailContext();
|
|
44
|
+
|
|
45
|
+
const lastMessageId = useMemo(() => {
|
|
46
|
+
const last = messages[messages.length - 1];
|
|
47
|
+
return last?.clientMsgID || last?.serverMsgID || null;
|
|
48
|
+
}, [messages]);
|
|
49
|
+
const { mutate: runFlow } = useRunFlow();
|
|
50
|
+
const { mutate: resetFlow } = useResetFlow();
|
|
51
|
+
|
|
38
52
|
const listRef = useRef<LegendListRef>(null);
|
|
39
53
|
const buttonOpacity = useSharedValue(0);
|
|
40
54
|
const isAtBottomRef = useRef(true);
|
|
@@ -71,6 +85,17 @@ export const ChatListLegend = memo(
|
|
|
71
85
|
[]
|
|
72
86
|
);
|
|
73
87
|
|
|
88
|
+
const onRunFlow = useCallback(
|
|
89
|
+
(payload: IRunConversationFlowPayload) => {
|
|
90
|
+
runFlow(payload);
|
|
91
|
+
},
|
|
92
|
+
[runFlow]
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const onResetFlow = useCallback(() => {
|
|
96
|
+
resetFlow();
|
|
97
|
+
}, [resetFlow]);
|
|
98
|
+
|
|
74
99
|
const renderItem = useCallback(
|
|
75
100
|
({ item, index }: any) => {
|
|
76
101
|
try {
|
|
@@ -78,7 +103,9 @@ export const ChatListLegend = memo(
|
|
|
78
103
|
|
|
79
104
|
const message = item as DMessageItem;
|
|
80
105
|
const isOutgoing = message.sendID === currentUserId;
|
|
81
|
-
|
|
106
|
+
const isLastIncoming =
|
|
107
|
+
!isOutgoing &&
|
|
108
|
+
(message.clientMsgID || message.serverMsgID) === lastMessageId;
|
|
82
109
|
const messageId =
|
|
83
110
|
message.clientMsgID ||
|
|
84
111
|
message.serverMsgID ||
|
|
@@ -86,11 +113,18 @@ export const ChatListLegend = memo(
|
|
|
86
113
|
|
|
87
114
|
const messageData = messageDataMap.get(messageId);
|
|
88
115
|
if (!messageData) {
|
|
89
|
-
return null;
|
|
116
|
+
return null;
|
|
90
117
|
}
|
|
91
118
|
|
|
92
119
|
const { dayStart, createdAt } = messageData;
|
|
93
120
|
|
|
121
|
+
let flowInputButtonsNode = null;
|
|
122
|
+
if (isLastIncoming && isBotConversation) {
|
|
123
|
+
flowInputButtonsNode = getFlowInputButtonsNode(
|
|
124
|
+
conversationFlow?.flowCurrentStep
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
94
128
|
return (
|
|
95
129
|
<>
|
|
96
130
|
{dayStart && <LegendChatDay createdAt={createdAt} />}
|
|
@@ -114,6 +148,13 @@ export const ChatListLegend = memo(
|
|
|
114
148
|
onPressUrl={onPressUrl}
|
|
115
149
|
/>
|
|
116
150
|
)}
|
|
151
|
+
{!!flowInputButtonsNode && (
|
|
152
|
+
<InputButtonFlow
|
|
153
|
+
nodeData={flowInputButtonsNode}
|
|
154
|
+
onResetFlow={onResetFlow}
|
|
155
|
+
onRunFlow={onRunFlow}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
117
158
|
</KContainer.View>
|
|
118
159
|
</>
|
|
119
160
|
);
|
|
@@ -136,6 +177,11 @@ export const ChatListLegend = memo(
|
|
|
136
177
|
handleItemLayout,
|
|
137
178
|
onMediaPress,
|
|
138
179
|
onPressUrl,
|
|
180
|
+
isBotConversation,
|
|
181
|
+
conversationFlow,
|
|
182
|
+
onResetFlow,
|
|
183
|
+
onRunFlow,
|
|
184
|
+
lastMessageId,
|
|
139
185
|
]
|
|
140
186
|
);
|
|
141
187
|
|
|
@@ -296,7 +342,12 @@ export const ChatListLegend = memo(
|
|
|
296
342
|
renderItem={renderItem}
|
|
297
343
|
estimatedItemSize={avgItemHeightRef.current}
|
|
298
344
|
keyExtractor={(item) => String(item.clientMsgID || item.serverMsgID)}
|
|
299
|
-
recycleItems
|
|
345
|
+
recycleItems={!isBotConversation}
|
|
346
|
+
extraData={
|
|
347
|
+
!isBotConversation
|
|
348
|
+
? `${conversationFlow?.flowCurrentStep?.id ?? ''}-${lastMessageId ?? ''}`
|
|
349
|
+
: undefined
|
|
350
|
+
}
|
|
300
351
|
alignItemsAtEnd
|
|
301
352
|
maintainVisibleContentPosition={!isLoadingEarlier}
|
|
302
353
|
maintainScrollAtEnd={maintainScrollConfig}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from '@droppii/libs';
|
|
10
10
|
import { useChatActionPress } from './useChatActionPress';
|
|
11
11
|
import type { DChatQuickAction, ChatQuickActionsProps } from './types';
|
|
12
|
+
import { useResetFlow } from '../../hooks/flows/useFlow';
|
|
12
13
|
|
|
13
14
|
const QuickActionItem = memo(
|
|
14
15
|
({
|
|
@@ -69,18 +70,24 @@ export const ChatQuickActions = memo(
|
|
|
69
70
|
renderQuickActions,
|
|
70
71
|
}: ChatQuickActionsProps) => {
|
|
71
72
|
const handleActionPress = useChatActionPress(onActionPress);
|
|
73
|
+
const { mutate: resetFlow } = useResetFlow();
|
|
72
74
|
|
|
73
75
|
const renderItem = useCallback<ListRenderItem<DChatQuickAction>>(
|
|
74
76
|
({ item }) => {
|
|
75
|
-
const onPress = () =>
|
|
77
|
+
const onPress = () => {
|
|
78
|
+
handleActionPress(item);
|
|
79
|
+
if (item.id === 'restart') {
|
|
80
|
+
resetFlow?.();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
76
83
|
|
|
77
84
|
if (renderAction) {
|
|
78
85
|
return <>{renderAction(item, onPress)}</>;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
return <QuickActionItem action={item} onPress={
|
|
88
|
+
return <QuickActionItem action={item} onPress={onPress} />;
|
|
82
89
|
},
|
|
83
|
-
[handleActionPress, renderAction]
|
|
90
|
+
[handleActionPress, renderAction, resetFlow]
|
|
84
91
|
);
|
|
85
92
|
|
|
86
93
|
const keyExtractor = useCallback((item: DChatQuickAction) => item.id, []);
|
|
@@ -32,10 +32,7 @@ export const getConversationSubtitle = (
|
|
|
32
32
|
return `20 thành viên • ${unreadLabel}`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
if (
|
|
36
|
-
conversation.peerType === PeerType.Customer ||
|
|
37
|
-
conversation.applicationType === 'MALL'
|
|
38
|
-
) {
|
|
35
|
+
if (conversation.peerType === PeerType.Customer) {
|
|
39
36
|
return '338,5K Đã bán • 4,2K Theo dõi';
|
|
40
37
|
}
|
|
41
38
|
|