From 5400467da10a3a010e07c08e187a7c5aded51091 Mon Sep 17 00:00:00 2001 From: balibabu Date: Fri, 30 Aug 2024 17:53:30 +0800 Subject: [PATCH] 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) --- .../components/message-item/group-button.tsx | 22 +- web/src/components/message-item/index.tsx | 6 +- web/src/hooks/chat-hooks.ts | 29 +-- web/src/hooks/flow-hooks.ts | 8 + web/src/hooks/logic-hooks.ts | 116 +++++++++- web/src/pages/chat/chat-container/index.tsx | 44 ++-- web/src/pages/chat/hooks.ts | 203 +++++++++++++++++- web/src/pages/chat/share/large.tsx | 37 ++-- web/src/pages/chat/shared-hooks.ts | 153 ++++--------- web/src/pages/chat/utils.ts | 2 +- web/src/pages/flow/chat/box.tsx | 29 ++- web/src/pages/flow/chat/hooks.ts | 118 ++++++++++ web/src/utils/chat.ts | 9 + 13 files changed, 556 insertions(+), 220 deletions(-) diff --git a/web/src/components/message-item/group-button.tsx b/web/src/components/message-item/group-button.tsx index f99136c5b..6b433bafa 100644 --- a/web/src/components/message-item/group-button.tsx +++ b/web/src/components/message-item/group-button.tsx @@ -91,7 +91,7 @@ export const AssistantGroupButton = ({ interface UserGroupButtonProps extends Partial { messageId: string; content: string; - regenerateMessage(): void; + regenerateMessage?: () => void; sendLoading: boolean; } @@ -113,15 +113,17 @@ export const UserGroupButton = ({ - - - - - + {regenerateMessage && ( + + + + + + )} {removeMessageById && ( diff --git a/web/src/components/message-item/index.tsx b/web/src/components/message-item/index.tsx index b66b7874c..c1974f85f 100644 --- a/web/src/components/message-item/index.tsx +++ b/web/src/components/message-item/index.tsx @@ -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} > )} diff --git a/web/src/hooks/chat-hooks.ts b/web/src/hooks/chat-hooks.ts index 0439c8763..08f75f6f6 100644 --- a/web/src/hooks/chat-hooks.ts +++ b/web/src/hooks/chat-hooks.ts @@ -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 diff --git a/web/src/hooks/flow-hooks.ts b/web/src/hooks/flow-hooks.ts index ec1d950b9..a2bc99298 100644 --- a/web/src/hooks/flow-hooks.ts +++ b/web/src/hooks/flow-hooks.ts @@ -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 ?? {}; }, }); diff --git a/web/src/hooks/logic-hooks.ts b/web/src/hooks/logic-hooks.ts index 50b685157..d3a76463e 100644 --- a/web/src/hooks/logic-hooks.ts +++ b/web/src/hooks/logic-hooks.ts @@ -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([]); + + 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; messages: Message[]; }) => { const regenerateMessage = useCallback( diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx index d682662fd..e48e68370 100644 --- a/web/src/pages/chat/chat-container/index.tsx +++ b/web/src/pages/chat/chat-container/index.tsx @@ -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 = () => {
- {conversation?.message?.map((message, i) => { + {derivedMessages?.map((message, i) => { return ( { }; }; -export const useScrollToBottom = (currentConversation: IClientConversation) => { - const ref = useRef(null); +// export const useScrollToBottom = (currentConversation: IClientConversation) => { +// const ref = useRef(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); diff --git a/web/src/pages/chat/share/large.tsx b/web/src/pages/chat/share/large.tsx index b28f00d98..d941b5456 100644 --- a/web/src/pages/chat/share/large.tsx +++ b/web/src/pages/chat/share/large.tsx @@ -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 = () => {
- {conversation?.message?.map((message, i) => { + {derivedMessages?.map((message, i) => { return ( diff --git a/web/src/pages/chat/shared-hooks.ts b/web/src/pages/chat/shared-hooks.ts index a8e52e602..fc618a3a1 100644 --- a/web/src/pages/chat/shared-hooks.ts +++ b/web/src/pages/chat/shared-hooks.ts @@ -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({} 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) => { - 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, answer?: string) => void, - removeLatestMessage: () => void, - setCurrentConversation: Dispatch>, - 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, }; }; diff --git a/web/src/pages/chat/utils.ts b/web/src/pages/chat/utils.ts index 2835be01d..a14300c47 100644 --- a/web/src/pages/chat/utils.ts +++ b/web/src/pages/chat/utils.ts @@ -36,7 +36,7 @@ export const buildMessageItemReference = ( ); const reference = message?.reference ? message?.reference - : conversation.reference[referenceIndex]; + : (conversation?.reference ?? {})[referenceIndex]; return reference; }; diff --git a/web/src/pages/flow/chat/box.tsx b/web/src/pages/flow/chat/box.tsx index 5a0372f85..ef0e4851e 100644 --- a/web/src/pages/flow/chat/box.tsx +++ b/web/src/pages/flow/chat/box.tsx @@ -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 = () => {
- {currentMessages?.map((message, i) => { + {derivedMessages?.map((message, i) => { return ( {}} showLikeButton={false} + sendLoading={sendLoading} > ); })} diff --git a/web/src/pages/flow/chat/hooks.ts b/web/src/pages/flow/chat/hooks.ts index 478dd431c..b0a6df738 100644 --- a/web/src/pages/flow/chat/hooks.ts +++ b/web/src/pages/flow/chat/hooks.ts @@ -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 = { + 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, + }; +}; diff --git a/web/src/utils/chat.ts b/web/src/utils/chat.ts index c8b8a888c..984f9bc9d 100644 --- a/web/src/utils/chat.ts +++ b/web/src/utils/chat.ts @@ -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), + })) ?? [] + ); +};