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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,15 @@
import { Authorization } from '@/constants/authorization'; import { Authorization } from '@/constants/authorization';
import { MessageType } from '@/constants/chat';
import { LanguageTranslationMap } from '@/constants/common'; import { LanguageTranslationMap } from '@/constants/common';
import { Pagination } from '@/interfaces/common'; import { Pagination } from '@/interfaces/common';
import { ResponseType } from '@/interfaces/database/base'; import { ResponseType } from '@/interfaces/database/base';
import { IAnswer, Message } from '@/interfaces/database/chat'; import { IAnswer, Message } from '@/interfaces/database/chat';
import { IKnowledgeFile } from '@/interfaces/database/knowledge'; import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; 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 api from '@/utils/api';
import { getAuthorization } from '@/utils/authorization-util'; import { getAuthorization } from '@/utils/authorization-util';
import { getMessagePureId } from '@/utils/chat'; import { buildMessageUuid, getMessagePureId } from '@/utils/chat';
import { PaginationProps } from 'antd'; import { PaginationProps } from 'antd';
import { FormInstance } from 'antd/lib'; import { FormInstance } from 'antd/lib';
import axios from 'axios'; 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 { export interface IRemoveMessageById {
removeMessageById(messageId: string): void; removeMessageById(messageId: string): void;
} }
@ -375,7 +478,7 @@ export const useRemoveMessagesAfterCurrentMessage = (
}; };
export interface IRegenerateMessage { export interface IRegenerateMessage {
regenerateMessage(message: Message): void; regenerateMessage?: (message: Message) => void;
} }
export const useRegenerateMessage = ({ export const useRegenerateMessage = ({
@ -384,7 +487,12 @@ export const useRegenerateMessage = ({
messages, messages,
}: { }: {
removeMessagesAfterCurrentMessage(messageId: string): void; removeMessagesAfterCurrentMessage(messageId: string): void;
sendMessage({ message }: { message: Message; messages?: Message[] }): void; sendMessage({
message,
}: {
message: Message;
messages?: Message[];
}): void | Promise<any>;
messages: Message[]; messages: Message[];
}) => { }) => {
const regenerateMessage = useCallback( const regenerateMessage = useCallback(

View File

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

View File

@ -21,6 +21,8 @@ import {
useRegenerateMessage, useRegenerateMessage,
useRemoveMessageById, useRemoveMessageById,
useRemoveMessagesAfterCurrentMessage, useRemoveMessagesAfterCurrentMessage,
useScrollToBottom,
useSelectDerivedMessages,
useSendMessageWithSse, useSendMessageWithSse,
} from '@/hooks/logic-hooks'; } from '@/hooks/logic-hooks';
import { import {
@ -40,7 +42,6 @@ import {
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef,
useState, useState,
} from 'react'; } from 'react';
import { useSearchParams } from 'umi'; import { useSearchParams } from 'umi';
@ -362,20 +363,71 @@ export const useSelectCurrentConversation = () => {
}; };
}; };
export const useScrollToBottom = (currentConversation: IClientConversation) => { // export const useScrollToBottom = (currentConversation: IClientConversation) => {
const ref = useRef<HTMLDivElement>(null); // const ref = useRef<HTMLDivElement>(null);
const scrollToBottom = useCallback(() => { // const scrollToBottom = useCallback(() => {
if (currentConversation.id) { // if (currentConversation.id) {
ref.current?.scrollIntoView({ behavior: 'instant' }); // 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(() => { useEffect(() => {
scrollToBottom(); addPrologue();
}, [scrollToBottom]); }, [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 = () => { 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 = () => { export const useGetFileIcon = () => {
const getFileIcon = (filename: string) => { const getFileIcon = (filename: string) => {
const ext: string = getFileExtension(filename); const ext: string = getFileExtension(filename);

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import { useFetchFlow } from '@/hooks/flow-hooks';
import { import {
useHandleMessageInputChange, useHandleMessageInputChange,
useScrollToBottom, useScrollToBottom,
useSelectDerivedMessages,
useSendMessageWithSse, useSendMessageWithSse,
} from '@/hooks/logic-hooks'; } from '@/hooks/logic-hooks';
import { IAnswer, Message } from '@/interfaces/database/chat'; 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 = ( export const useSendMessage = (
addNewestQuestion: (message: Message, answer?: string) => void, addNewestQuestion: (message: Message, answer?: string) => void,
removeLatestMessage: () => void, removeLatestMessage: () => void,
@ -160,3 +197,84 @@ export const useSendMessage = (
loading: !done, 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; return id;
}; };
export const buildMessageListWithUuid = (messages?: Message[]) => {
return (
messages?.map((x: Message | IMessage) => ({
...x,
id: buildMessageUuid(x),
})) ?? []
);
};