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
13 changed files with 556 additions and 220 deletions

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;
};