feat: Select derived messages from backend #2088 (#2176)

### What problem does this PR solve?

feat: Select derived messages from backend #2088

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2024-08-30 17:53:30 +08:00 committed by GitHub
parent 2c771fb0b4
commit 5400467da1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 556 additions and 220 deletions

View File

@ -91,7 +91,7 @@ export const AssistantGroupButton = ({
interface UserGroupButtonProps extends Partial<IRemoveMessageById> {
messageId: string;
content: string;
regenerateMessage(): void;
regenerateMessage?: () => void;
sendLoading: boolean;
}
@ -113,15 +113,17 @@ export const UserGroupButton = ({
<Radio.Button value="a">
<CopyToClipboard text={content}></CopyToClipboard>
</Radio.Button>
<Radio.Button
value="b"
onClick={regenerateMessage}
disabled={sendLoading}
>
<Tooltip title={t('chat.regenerate')}>
<SyncOutlined spin={sendLoading} />
</Tooltip>
</Radio.Button>
{regenerateMessage && (
<Radio.Button
value="b"
onClick={regenerateMessage}
disabled={sendLoading}
>
<Tooltip title={t('chat.regenerate')}>
<SyncOutlined spin={sendLoading} />
</Tooltip>
</Radio.Button>
)}
{removeMessageById && (
<Radio.Button value="c" onClick={onRemoveMessage} disabled={loading}>
<Tooltip title={t('common.delete')}>

View File

@ -79,7 +79,7 @@ const MessageItem = ({
);
const handleRegenerateMessage = useCallback(() => {
regenerateMessage(item);
regenerateMessage?.(item);
}, [regenerateMessage, item]);
useEffect(() => {
@ -138,7 +138,9 @@ const MessageItem = ({
content={item.content}
messageId={item.id}
removeMessageById={removeMessageById}
regenerateMessage={handleRegenerateMessage}
regenerateMessage={
regenerateMessage && handleRegenerateMessage
}
sendLoading={sendLoading}
></UserGroupButton>
)}

View File

@ -4,13 +4,12 @@ import {
IDialog,
IStats,
IToken,
Message,
} from '@/interfaces/database/chat';
import { IFeedbackRequestBody } from '@/interfaces/request/chat';
import i18n from '@/locales/config';
import { IClientConversation, IMessage } from '@/pages/chat/interface';
import { IClientConversation } from '@/pages/chat/interface';
import chatService from '@/services/chat-service';
import { buildMessageUuid, isConversationIdExist } from '@/utils/chat';
import { buildMessageListWithUuid, isConversationIdExist } from '@/utils/chat';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { message } from 'antd';
import dayjs, { Dayjs } from 'dayjs';
@ -18,15 +17,6 @@ import { set } from 'lodash';
import { useCallback, useMemo, useState } from 'react';
import { useSearchParams } from 'umi';
const buildMessageListWithUuid = (messages?: Message[]) => {
return (
messages?.map((x: Message | IMessage) => ({
...x,
id: buildMessageUuid(x),
})) ?? []
);
};
//#region logic
export const useClickDialogCard = () => {
@ -465,14 +455,11 @@ export const useCreateNextSharedConversation = () => {
return { data, loading, createSharedConversation: mutateAsync };
};
export const useFetchNextSharedConversation = () => {
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['fetchSharedConversation'],
mutationFn: async (conversationId: string) => {
export const useFetchNextSharedConversation = (conversationId: string) => {
const { data, isPending: loading } = useQuery({
queryKey: ['fetchSharedConversation'],
enabled: !!conversationId,
queryFn: async () => {
const { data } = await chatService.getExternalConversation(
null,
conversationId,
@ -486,7 +473,7 @@ export const useFetchNextSharedConversation = () => {
},
});
return { data, loading, fetchConversation: mutateAsync };
return { data, loading };
};
//#endregion

View File

@ -2,8 +2,11 @@ import { ResponseType } from '@/interfaces/database/base';
import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow';
import i18n from '@/locales/config';
import flowService from '@/services/flow-service';
import { buildMessageListWithUuid } from '@/utils/chat';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { message } from 'antd';
import { set } from 'lodash';
import get from 'lodash/get';
import { useParams } from 'umi';
import { v4 as uuid } from 'uuid';
@ -101,6 +104,11 @@ export const useFetchFlow = (): {
queryFn: async () => {
const { data } = await flowService.getCanvas({}, id);
const messageList = buildMessageListWithUuid(
get(data, 'data.dsl.messages', []),
);
set(data, 'data.dsl.messages', messageList);
return data?.data ?? {};
},
});

View File

@ -1,14 +1,15 @@
import { Authorization } from '@/constants/authorization';
import { MessageType } from '@/constants/chat';
import { LanguageTranslationMap } from '@/constants/common';
import { Pagination } from '@/interfaces/common';
import { ResponseType } from '@/interfaces/database/base';
import { IAnswer, Message } from '@/interfaces/database/chat';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
import { IClientConversation } from '@/pages/chat/interface';
import { IClientConversation, IMessage } from '@/pages/chat/interface';
import api from '@/utils/api';
import { getAuthorization } from '@/utils/authorization-util';
import { getMessagePureId } from '@/utils/chat';
import { buildMessageUuid, getMessagePureId } from '@/utils/chat';
import { PaginationProps } from 'antd';
import { FormInstance } from 'antd/lib';
import axios from 'axios';
@ -309,6 +310,108 @@ export const useHandleMessageInputChange = () => {
};
};
export const useSelectDerivedMessages = () => {
const [derivedMessages, setDerivedMessages] = useState<IMessage[]>([]);
const ref = useScrollToBottom(derivedMessages);
const addNewestQuestion = useCallback(
(message: Message, answer: string = '') => {
setDerivedMessages((pre) => {
return [
...pre,
{
...message,
id: buildMessageUuid(message),
},
{
role: MessageType.Assistant,
content: answer,
id: buildMessageUuid({ ...message, role: MessageType.Assistant }),
},
];
});
},
[],
);
// Add the streaming message to the last item in the message list
const addNewestAnswer = useCallback((answer: IAnswer) => {
setDerivedMessages((pre) => {
return [
...(pre?.slice(0, -1) ?? []),
{
role: MessageType.Assistant,
content: answer.answer,
reference: answer.reference,
id: buildMessageUuid({
id: answer.id,
role: MessageType.Assistant,
}),
prompt: answer.prompt,
},
];
});
}, []);
const removeLatestMessage = useCallback(() => {
setDerivedMessages((pre) => {
const nextMessages = pre?.slice(0, -2) ?? [];
return nextMessages;
});
}, []);
const removeMessageById = useCallback(
(messageId: string) => {
setDerivedMessages((pre) => {
const nextMessages =
pre?.filter(
(x) => getMessagePureId(x.id) !== getMessagePureId(messageId),
) ?? [];
return nextMessages;
});
},
[setDerivedMessages],
);
const removeMessagesAfterCurrentMessage = useCallback(
(messageId: string) => {
setDerivedMessages((pre) => {
const index = pre.findIndex((x) => x.id === messageId);
if (index !== -1) {
let nextMessages = pre.slice(0, index + 2) ?? [];
const latestMessage = nextMessages.at(-1);
nextMessages = latestMessage
? [
...nextMessages.slice(0, -1),
{
...latestMessage,
content: '',
reference: undefined,
prompt: undefined,
},
]
: nextMessages;
return nextMessages;
}
return pre;
});
},
[setDerivedMessages],
);
return {
ref,
derivedMessages,
setDerivedMessages,
addNewestQuestion,
addNewestAnswer,
removeLatestMessage,
removeMessageById,
removeMessagesAfterCurrentMessage,
};
};
export interface IRemoveMessageById {
removeMessageById(messageId: string): void;
}
@ -375,7 +478,7 @@ export const useRemoveMessagesAfterCurrentMessage = (
};
export interface IRegenerateMessage {
regenerateMessage(message: Message): void;
regenerateMessage?: (message: Message) => void;
}
export const useRegenerateMessage = ({
@ -384,7 +487,12 @@ export const useRegenerateMessage = ({
messages,
}: {
removeMessagesAfterCurrentMessage(messageId: string): void;
sendMessage({ message }: { message: Message; messages?: Message[] }): void;
sendMessage({
message,
}: {
message: Message;
messages?: Message[];
}): void | Promise<any>;
messages: Message[];
}) => {
const regenerateMessage = useCallback(

View File

@ -5,44 +5,38 @@ import { Drawer, Flex, Spin } from 'antd';
import {
useClickDrawer,
useCreateConversationBeforeUploadDocument,
useFetchConversationOnMount,
useGetFileIcon,
useGetSendButtonDisabled,
useSendButtonDisabled,
useSendMessage,
useSendNextMessage,
} from '../hooks';
import { buildMessageItemReference } from '../utils';
import MessageInput from '@/components/message-input';
import {
useFetchNextConversation,
useGetChatSearchParams,
} from '@/hooks/chat-hooks';
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
import { memo } from 'react';
import styles from './index.less';
const ChatContainer = () => {
const { conversationId } = useGetChatSearchParams();
const { data: conversation } = useFetchNextConversation();
const {
ref,
currentConversation: conversation,
addNewestConversation,
removeLatestMessage,
addNewestAnswer,
conversationId,
loading,
removeMessageById,
removeMessagesAfterCurrentMessage,
} = useFetchConversationOnMount();
const {
sendLoading,
derivedMessages,
handleInputChange,
handlePressEnter,
value,
loading: sendLoading,
regenerateMessage,
} = useSendMessage(
conversation,
addNewestConversation,
removeLatestMessage,
addNewestAnswer,
removeMessagesAfterCurrentMessage,
);
removeMessageById,
} = useSendNextMessage();
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer();
const disabled = useGetSendButtonDisabled();
@ -58,19 +52,25 @@ const ChatContainer = () => {
<Flex flex={1} vertical className={styles.messageContainer}>
<div>
<Spin spinning={loading}>
{conversation?.message?.map((message, i) => {
{derivedMessages?.map((message, i) => {
return (
<MessageItem
loading={
message.role === MessageType.Assistant &&
sendLoading &&
conversation?.message.length - 1 === i
derivedMessages.length - 1 === i
}
key={message.id}
item={message}
nickname={userInfo.nickname}
avatar={userInfo.avatar}
reference={buildMessageItemReference(conversation, message)}
reference={buildMessageItemReference(
{
message: derivedMessages,
reference: conversation.reference,
},
message,
)}
clickDocumentButton={clickDocumentButton}
index={i}
removeMessageById={removeMessageById}

View File

@ -21,6 +21,8 @@ import {
useRegenerateMessage,
useRemoveMessageById,
useRemoveMessagesAfterCurrentMessage,
useScrollToBottom,
useSelectDerivedMessages,
useSendMessageWithSse,
} from '@/hooks/logic-hooks';
import {
@ -40,7 +42,6 @@ import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useSearchParams } from 'umi';
@ -362,20 +363,71 @@ export const useSelectCurrentConversation = () => {
};
};
export const useScrollToBottom = (currentConversation: IClientConversation) => {
const ref = useRef<HTMLDivElement>(null);
// export const useScrollToBottom = (currentConversation: IClientConversation) => {
// const ref = useRef<HTMLDivElement>(null);
const scrollToBottom = useCallback(() => {
if (currentConversation.id) {
ref.current?.scrollIntoView({ behavior: 'instant' });
// const scrollToBottom = useCallback(() => {
// if (currentConversation.id) {
// ref.current?.scrollIntoView({ behavior: 'instant' });
// }
// }, [currentConversation]);
// useEffect(() => {
// scrollToBottom();
// }, [scrollToBottom]);
// return ref;
// };
export const useSelectNextMessages = () => {
const {
ref,
setDerivedMessages,
derivedMessages,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
removeMessageById,
removeMessagesAfterCurrentMessage,
} = useSelectDerivedMessages();
const { data: conversation, loading } = useFetchNextConversation();
const { data: dialog } = useFetchNextDialog();
const { conversationId, dialogId } = useGetChatSearchParams();
const addPrologue = useCallback(() => {
if (dialogId !== '' && conversationId === '') {
const prologue = dialog.prompt_config?.prologue;
const nextMessage = {
role: MessageType.Assistant,
content: prologue,
id: uuid(),
} as IMessage;
setDerivedMessages([nextMessage]);
}
}, [currentConversation]);
}, [conversationId, dialog, dialogId, setDerivedMessages]);
useEffect(() => {
scrollToBottom();
}, [scrollToBottom]);
addPrologue();
}, [addPrologue]);
return ref;
useEffect(() => {
if (conversationId) {
setDerivedMessages(conversation.message);
}
}, [conversation.message, conversationId, setDerivedMessages]);
return {
ref,
derivedMessages,
loading,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
removeMessageById,
removeMessagesAfterCurrentMessage,
};
};
export const useFetchConversationOnMount = () => {
@ -544,6 +596,137 @@ export const useSendMessage = (
};
};
export const useSendNextMessage = () => {
const { setConversation } = useSetConversation();
const { conversationId } = useGetChatSearchParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { handleClickConversation } = useClickConversationCard();
const { send, answer, done, setDone } = useSendMessageWithSse();
const {
ref,
derivedMessages,
loading,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
removeMessageById,
removeMessagesAfterCurrentMessage,
} = useSelectNextMessages();
const sendMessage = useCallback(
async ({
message,
currentConversationId,
messages,
}: {
message: Message;
currentConversationId?: string;
messages?: Message[];
}) => {
const res = await send({
conversation_id: currentConversationId ?? conversationId,
messages: [...(messages ?? derivedMessages ?? []), message],
});
if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) {
// cancel loading
setValue(message.content);
console.info('removeLatestMessage111');
removeLatestMessage();
} else {
if (currentConversationId) {
console.info('111');
// new conversation
handleClickConversation(currentConversationId);
} else {
console.info('222');
// fetchConversation(conversationId);
}
}
},
[
derivedMessages,
conversationId,
handleClickConversation,
removeLatestMessage,
setValue,
send,
],
);
const handleSendMessage = useCallback(
async (message: Message) => {
if (conversationId !== '') {
sendMessage({ message });
} else {
const data = await setConversation(message.content);
if (data.retcode === 0) {
const id = data.data.id;
sendMessage({ message, currentConversationId: id });
}
}
},
[conversationId, setConversation, sendMessage],
);
const { regenerateMessage } = useRegenerateMessage({
removeMessagesAfterCurrentMessage,
sendMessage,
messages: derivedMessages,
});
useEffect(() => {
// #1289
if (answer.answer && answer?.conversationId === conversationId) {
addNewestAnswer(answer);
}
}, [answer, addNewestAnswer, conversationId]);
useEffect(() => {
// #1289 switch to another conversion window when the last conversion answer doesn't finish.
if (conversationId) {
setDone(true);
}
}, [setDone, conversationId]);
const handlePressEnter = useCallback(
(documentIds: string[]) => {
if (trim(value) === '') return;
const id = uuid();
addNewestQuestion({
content: value,
doc_ids: documentIds,
id,
role: MessageType.User,
});
if (done) {
setValue('');
handleSendMessage({
id,
content: value.trim(),
role: MessageType.User,
doc_ids: documentIds,
});
}
},
[addNewestQuestion, handleSendMessage, done, setValue, value],
);
return {
handlePressEnter,
handleInputChange,
value,
setValue,
regenerateMessage,
sendLoading: !done,
loading,
ref,
derivedMessages,
removeMessageById,
};
};
export const useGetFileIcon = () => {
const getFileIcon = (filename: string) => {
const ext: string = getFileExtension(filename);

View File

@ -1,13 +1,13 @@
import MessageInput from '@/components/message-input';
import MessageItem from '@/components/message-item';
import { MessageType, SharedFrom } from '@/constants/chat';
import { useFetchNextSharedConversation } from '@/hooks/chat-hooks';
import { useSendButtonDisabled } from '@/pages/chat/hooks';
import { Flex, Spin } from 'antd';
import { forwardRef } from 'react';
import {
useCreateSharedConversationOnMount,
useGetSharedChatSearchParams,
useSelectCurrentSharedConversation,
useSendSharedMessage,
} from '../shared-hooks';
import { buildMessageItemReference } from '../utils';
@ -15,28 +15,17 @@ import styles from './index.less';
const ChatContainer = () => {
const { conversationId } = useCreateSharedConversationOnMount();
const {
currentConversation: conversation,
addNewestConversation,
removeLatestMessage,
ref,
loading,
setCurrentConversation,
addNewestAnswer,
} = useSelectCurrentSharedConversation(conversationId);
const { data } = useFetchNextSharedConversation(conversationId);
const {
handlePressEnter,
handleInputChange,
value,
loading: sendLoading,
} = useSendSharedMessage(
conversation,
addNewestConversation,
removeLatestMessage,
setCurrentConversation,
addNewestAnswer,
);
sendLoading,
loading,
ref,
derivedMessages,
} = useSendSharedMessage(conversationId);
const sendDisabled = useSendButtonDisabled(value);
const { from } = useGetSharedChatSearchParams();
@ -46,17 +35,23 @@ const ChatContainer = () => {
<Flex flex={1} vertical className={styles.messageContainer}>
<div>
<Spin spinning={loading}>
{conversation?.message?.map((message, i) => {
{derivedMessages?.map((message, i) => {
return (
<MessageItem
key={message.id}
item={message}
nickname="You"
reference={buildMessageItemReference(conversation, message)}
reference={buildMessageItemReference(
{
message: derivedMessages,
reference: data?.data?.reference,
},
message,
)}
loading={
message.role === MessageType.Assistant &&
sendLoading &&
conversation?.message.length - 1 === i
derivedMessages?.length - 1 === i
}
index={i}
></MessageItem>

View File

@ -3,22 +3,17 @@ import {
useCreateNextSharedConversation,
useFetchNextSharedConversation,
} from '@/hooks/chat-hooks';
import { useSendMessageWithSse } from '@/hooks/logic-hooks';
import { IAnswer, Message } from '@/interfaces/database/chat';
import api from '@/utils/api';
import { buildMessageUuid } from '@/utils/chat';
import trim from 'lodash/trim';
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useState,
} from 'react';
useSelectDerivedMessages,
useSendMessageWithSse,
} from '@/hooks/logic-hooks';
import { Message } from '@/interfaces/database/chat';
import api from '@/utils/api';
import trim from 'lodash/trim';
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'umi';
import { v4 as uuid } from 'uuid';
import { useHandleMessageInputChange, useScrollToBottom } from './hooks';
import { IClientConversation, IMessage } from './interface';
import { useHandleMessageInputChange } from './hooks';
export const useCreateSharedConversationOnMount = () => {
const [currentQueryParameters] = useSearchParams();
@ -46,91 +41,30 @@ export const useCreateSharedConversationOnMount = () => {
return { conversationId };
};
export const useSelectCurrentSharedConversation = (conversationId: string) => {
const [currentConversation, setCurrentConversation] =
useState<IClientConversation>({} as IClientConversation);
const { fetchConversation, loading } = useFetchNextSharedConversation();
export const useSelectNextSharedMessages = (conversationId: string) => {
const { data, loading } = useFetchNextSharedConversation(conversationId);
const ref = useScrollToBottom(currentConversation);
const addNewestConversation = useCallback((message: Partial<Message>) => {
setCurrentConversation((pre) => {
return {
...pre,
message: [
...(pre.message ?? []),
{
...message,
id: buildMessageUuid(message),
} as IMessage,
{
role: MessageType.Assistant,
content: '',
id: buildMessageUuid({ ...message, role: MessageType.Assistant }),
reference: {},
} as IMessage,
],
};
});
}, []);
const addNewestAnswer = useCallback((answer: IAnswer) => {
setCurrentConversation((pre) => {
const latestMessage = pre.message?.at(-1);
if (latestMessage) {
return {
...pre,
message: [
...pre.message.slice(0, -1),
{
...latestMessage,
content: answer.answer,
reference: answer.reference,
id: buildMessageUuid({
id: answer.id,
role: MessageType.Assistant,
}),
prompt: answer.prompt,
} as IMessage,
],
};
}
return pre;
});
}, []);
const removeLatestMessage = useCallback(() => {
setCurrentConversation((pre) => {
const nextMessages = pre.message.slice(0, -2);
return {
...pre,
message: nextMessages,
};
});
}, []);
const fetchConversationOnMount = useCallback(async () => {
if (conversationId) {
const data = await fetchConversation(conversationId);
if (data.retcode === 0) {
setCurrentConversation(data.data);
}
}
}, [conversationId, fetchConversation]);
const {
derivedMessages,
ref,
setDerivedMessages,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
} = useSelectDerivedMessages();
useEffect(() => {
fetchConversationOnMount();
}, [fetchConversationOnMount]);
setDerivedMessages(data?.data?.message);
}, [setDerivedMessages, data]);
return {
currentConversation,
addNewestConversation,
derivedMessages,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
loading,
ref,
setCurrentConversation,
addNewestAnswer,
setDerivedMessages,
};
};
@ -138,28 +72,28 @@ export const useSendButtonDisabled = (value: string) => {
return trim(value) === '';
};
export const useSendSharedMessage = (
conversation: IClientConversation,
addNewestConversation: (message: Partial<Message>, answer?: string) => void,
removeLatestMessage: () => void,
setCurrentConversation: Dispatch<SetStateAction<IClientConversation>>,
addNewestAnswer: (answer: IAnswer) => void,
) => {
const conversationId = conversation.id;
export const useSendSharedMessage = (conversationId: string) => {
const { createSharedConversation: setConversation } =
useCreateNextSharedConversation();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { send, answer, done } = useSendMessageWithSse(
api.completeExternalConversation,
);
const {
derivedMessages,
ref,
removeLatestMessage,
addNewestAnswer,
addNewestQuestion,
loading,
} = useSelectNextSharedMessages(conversationId);
const sendMessage = useCallback(
async (message: Message, id?: string) => {
const res = await send({
conversation_id: id ?? conversationId,
quote: false,
messages: [...(conversation?.message ?? []), message],
messages: [...(derivedMessages ?? []), message],
});
if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) {
@ -168,15 +102,7 @@ export const useSendSharedMessage = (
removeLatestMessage();
}
},
[
conversationId,
conversation?.message,
// fetchConversation,
removeLatestMessage,
setValue,
send,
// setCurrentConversation,
],
[conversationId, derivedMessages, removeLatestMessage, setValue, send],
);
const handleSendMessage = useCallback(
@ -206,7 +132,7 @@ export const useSendSharedMessage = (
const id = uuid();
if (done) {
setValue('');
addNewestConversation({
addNewestQuestion({
content: value,
doc_ids: documentIds,
id,
@ -219,14 +145,17 @@ export const useSendSharedMessage = (
});
}
},
[addNewestConversation, done, handleSendMessage, setValue, value],
[addNewestQuestion, done, handleSendMessage, setValue, value],
);
return {
handlePressEnter,
handleInputChange,
value,
loading: !done,
sendLoading: !done,
ref,
loading,
derivedMessages,
};
};

View File

@ -36,7 +36,7 @@ export const buildMessageItemReference = (
);
const reference = message?.reference
? message?.reference
: conversation.reference[referenceIndex];
: (conversation?.reference ?? {})[referenceIndex];
return reference;
};

View File

@ -6,28 +6,23 @@ import { useClickDrawer, useGetFileIcon } from '@/pages/chat/hooks';
import { buildMessageItemReference } from '@/pages/chat/utils';
import { Button, Drawer, Flex, Input, Spin } from 'antd';
import { useSelectCurrentMessages, useSendMessage } from './hooks';
import { useSendNextMessage } from './hooks';
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
import styles from './index.less';
const FlowChatBox = () => {
const {
ref,
currentMessages,
reference,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
loading,
} = useSelectCurrentMessages();
const {
sendLoading,
handleInputChange,
handlePressEnter,
value,
loading: sendLoading,
} = useSendMessage(addNewestQuestion, removeLatestMessage, addNewestAnswer);
loading,
ref,
derivedMessages,
reference,
} = useSendNextMessage();
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer();
useGetFileIcon();
@ -40,26 +35,26 @@ const FlowChatBox = () => {
<Flex flex={1} vertical className={styles.messageContainer}>
<div>
<Spin spinning={loading}>
{currentMessages?.map((message, i) => {
{derivedMessages?.map((message, i) => {
return (
<MessageItem
loading={
message.role === MessageType.Assistant &&
sendLoading &&
currentMessages.length - 1 === i
derivedMessages.length - 1 === i
}
key={message.id}
nickname={userInfo.nickname}
avatar={userInfo.avatar}
item={message}
reference={buildMessageItemReference(
{ message: currentMessages, reference },
{ message: derivedMessages, reference },
message,
)}
clickDocumentButton={clickDocumentButton}
index={i}
regenerateMessage={() => {}}
showLikeButton={false}
sendLoading={sendLoading}
></MessageItem>
);
})}

View File

@ -3,6 +3,7 @@ import { useFetchFlow } from '@/hooks/flow-hooks';
import {
useHandleMessageInputChange,
useScrollToBottom,
useSelectDerivedMessages,
useSendMessageWithSse,
} from '@/hooks/logic-hooks';
import { IAnswer, Message } from '@/interfaces/database/chat';
@ -91,6 +92,42 @@ export const useSelectCurrentMessages = () => {
};
};
export const useSelectNextMessages = () => {
const { id: id } = useParams();
const { data: flowDetail, loading } = useFetchFlow();
const messages = flowDetail.dsl.messages;
const reference = flowDetail.dsl.reference;
const {
derivedMessages,
setDerivedMessages,
ref,
addNewestQuestion,
addNewestAnswer,
removeLatestMessage,
removeMessageById,
removeMessagesAfterCurrentMessage,
} = useSelectDerivedMessages();
useEffect(() => {
if (id) {
const nextMessages = messages.map((x) => ({ ...x, id: uuid() }));
setDerivedMessages(nextMessages);
}
}, [messages, id, setDerivedMessages]);
return {
reference,
loading,
derivedMessages,
ref,
addNewestQuestion,
addNewestAnswer,
removeLatestMessage,
removeMessageById,
removeMessagesAfterCurrentMessage,
};
};
export const useSendMessage = (
addNewestQuestion: (message: Message, answer?: string) => void,
removeLatestMessage: () => void,
@ -160,3 +197,84 @@ export const useSendMessage = (
loading: !done,
};
};
export const useSendNextMessage = () => {
const {
reference,
loading,
derivedMessages,
ref,
addNewestQuestion,
addNewestAnswer,
removeLatestMessage,
removeMessageById,
} = useSelectNextMessages();
const { id: flowId } = useParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { refetch } = useFetchFlow();
const { send, answer, done } = useSendMessageWithSse(api.runCanvas);
const sendMessage = useCallback(
async ({ message }: { message: Message; messages?: Message[] }) => {
const params: Record<string, unknown> = {
id: flowId,
};
if (message.content) {
params.message = message.content;
params.message_id = message.id;
}
const res = await send(params);
if (receiveMessageError(res)) {
antMessage.error(res?.data?.retmsg);
// cancel loading
setValue(message.content);
removeLatestMessage();
} else {
refetch(); // pull the message list after sending the message successfully
}
},
[flowId, removeLatestMessage, setValue, send, refetch],
);
const handleSendMessage = useCallback(
async (message: Message) => {
sendMessage({ message });
},
[sendMessage],
);
useEffect(() => {
if (answer.answer) {
addNewestAnswer(answer);
}
}, [answer, addNewestAnswer]);
const handlePressEnter = useCallback(() => {
if (trim(value) === '') return;
const id = uuid();
if (done) {
setValue('');
handleSendMessage({ id, content: value.trim(), role: MessageType.User });
}
addNewestQuestion({
content: value,
id,
role: MessageType.User,
});
}, [addNewestQuestion, handleSendMessage, done, setValue, value]);
return {
handlePressEnter,
handleInputChange,
value,
sendLoading: !done,
reference,
loading,
derivedMessages,
ref,
removeMessageById,
};
};

View File

@ -23,3 +23,12 @@ export const getMessagePureId = (id?: string) => {
}
return id;
};
export const buildMessageListWithUuid = (messages?: Message[]) => {
return (
messages?.map((x: Message | IMessage) => ({
...x,
id: buildMessageUuid(x),
})) ?? []
);
};