diff --git a/web/src/pages/agent/chat/box.tsx b/web/src/pages/agent/chat/box.tsx index b22891cb9..211a79816 100644 --- a/web/src/pages/agent/chat/box.tsx +++ b/web/src/pages/agent/chat/box.tsx @@ -15,10 +15,9 @@ import { import { useFetchUserInfo } from '@/hooks/use-user-setting-request'; import { buildMessageUuidWithRole } from '@/utils/chat'; import { memo, useCallback, useContext } from 'react'; -import { useParams } from 'react-router'; import { AgentChatContext } from '../context'; import DebugContent from '../debug-content'; -import { useAwaitCompentData } from '../hooks/use-chat-logic'; +import { useAwaitComponentData } from '../hooks/use-chat-logic'; import { useIsTaskMode } from '../hooks/use-get-begin-query'; import { useGetFileIcon } from './use-get-file-icon'; @@ -43,13 +42,11 @@ function AgentChatBox() { useClickDrawer(); useGetFileIcon(); const { data: userInfo } = useFetchUserInfo(); - const { id: canvasId } = useParams(); const { uploadAgentFile, loading } = useUploadAgentFileWithProgress(); - const { buildInputList, handleOk, isWaitting } = useAwaitCompentData({ + const { buildInputList, handleOk, isWaiting } = useAwaitComponentData({ derivedMessages, sendFormMessage, - canvasId: canvasId as string, }); const { setDerivedMessages } = useContext(AgentChatContext); @@ -125,9 +122,9 @@ function AgentChatBox() { }) => { + async (body: { inputs: Record }) => { addNewestOneQuestion({ content: Object.entries(body.inputs) .map(([, val]) => `${val.name}: ${val.value}`) @@ -372,12 +374,21 @@ export const useSendAgentMessage = ({ }); await send({ ...body, + ...(isShared ? {} : { agent_id: agentId }), session_id: sessionId, ...(releaseMode ? { release: releaseMode } : {}), }); refetch?.(); }, - [addNewestOneQuestion, refetch, releaseMode, send, sessionId], + [ + addNewestOneQuestion, + agentId, + isShared, + refetch, + releaseMode, + send, + sessionId, + ], ); // reset session @@ -450,14 +461,31 @@ export const useSendAgentMessage = ({ const answer = content || getLatestError(answerList); if (answerList.length > 0) { - addNewestOneAnswer({ - answer: answer ?? '', - audio_binary: audio_binary, - attachment: attachment as IAttachment, - downloads, - id: id, - ...inputAnswer, - }); + const shouldSplit = shouldSplitMessage(answerList, content); + + if (shouldSplit) { + addNewestOneAnswer({ + answer: answer ?? '', + audio_binary: audio_binary, + attachment: attachment as IAttachment, + downloads, + id, + }); + addNewestOneAnswer({ + answer: '', + ...inputAnswer, + id: `${id}${MessageWaitSuffix}`, + }); + } else { + addNewestOneAnswer({ + answer: answer ?? '', + audio_binary: audio_binary, + attachment: attachment as IAttachment, + downloads, + id, + ...inputAnswer, + }); + } } }, [answerList, addNewestOneAnswer]); diff --git a/web/src/pages/agent/constant/chat.ts b/web/src/pages/agent/constant/chat.ts new file mode 100644 index 000000000..80df77e45 --- /dev/null +++ b/web/src/pages/agent/constant/chat.ts @@ -0,0 +1 @@ +export const MessageWaitSuffix = '-wait'; diff --git a/web/src/pages/agent/hooks/use-cache-chat-log.ts b/web/src/pages/agent/hooks/use-cache-chat-log.ts index 45fa6b7f4..f187c49c3 100644 --- a/web/src/pages/agent/hooks/use-cache-chat-log.ts +++ b/web/src/pages/agent/hooks/use-cache-chat-log.ts @@ -5,12 +5,16 @@ import { } from '@/hooks/use-send-message'; import { get, isEmpty } from 'lodash'; import { useCallback, useMemo, useState } from 'react'; +import { MessageWaitSuffix } from '../constant/chat'; export const ExcludeTypes = [ MessageEventType.Message, MessageEventType.MessageEnd, ]; +const resolveMessageId = (messageId: string) => + messageId?.replace(new RegExp(`${MessageWaitSuffix}$`), ''); + export function useCacheChatLog() { const [messageIdPool, setMessageIdPool] = useState< Record @@ -22,8 +26,9 @@ export function useCacheChatLog() { const filterEventListByMessageId = useCallback( (messageId: string) => { - return messageIdPool[messageId]?.filter( - (x) => x.message_id === messageId, + const resolvedId = resolveMessageId(messageId); + return messageIdPool[resolvedId]?.filter( + (x) => x.message_id === resolvedId, ); }, [messageIdPool], @@ -31,9 +36,8 @@ export function useCacheChatLog() { const filterEventListByEventType = useCallback( (eventType: string) => { - return messageIdPool[currentMessageId]?.filter( - (x) => x.event === eventType, - ); + const resolvedId = resolveMessageId(currentMessageId); + return messageIdPool[resolvedId]?.filter((x) => x.event === eventType); }, [messageIdPool, currentMessageId], ); @@ -62,19 +66,20 @@ export function useCacheChatLog() { }, []); const currentEventListWithoutMessage = useMemo(() => { - const list = messageIdPool[currentMessageId]?.filter( + const resolvedId = resolveMessageId(currentMessageId); + const list = messageIdPool[resolvedId]?.filter( (x) => - x.message_id === currentMessageId && - ExcludeTypes.every((y) => y !== x.event), + x.message_id === resolvedId && ExcludeTypes.every((y) => y !== x.event), ); return list as INodeEvent[]; }, [currentMessageId, messageIdPool]); const currentEventListWithoutMessageById = useCallback( (messageId: string) => { - const list = messageIdPool[messageId]?.filter( + const resolvedId = resolveMessageId(messageId); + const list = messageIdPool[resolvedId]?.filter( (x) => - x.message_id === messageId && + x.message_id === resolvedId && ExcludeTypes.every((y) => y !== x.event), ); return list as INodeEvent[]; diff --git a/web/src/pages/agent/hooks/use-chat-logic.ts b/web/src/pages/agent/hooks/use-chat-logic.ts index 2fa1b0016..ea7a25e3e 100644 --- a/web/src/pages/agent/hooks/use-chat-logic.ts +++ b/web/src/pages/agent/hooks/use-chat-logic.ts @@ -6,14 +6,10 @@ import { BeginQuery } from '../interface'; import { buildBeginQueryWithObject } from '../utils'; type IAwaitCompentData = { derivedMessages: IMessage[]; - sendFormMessage: (params: { - inputs: Record; - agent_id: string; - }) => void; - canvasId: string; + sendFormMessage: (params: { inputs: Record }) => void; }; -const useAwaitCompentData = (props: IAwaitCompentData) => { - const { derivedMessages, sendFormMessage, canvasId } = props; +const useAwaitComponentData = (props: IAwaitCompentData) => { + const { derivedMessages, sendFormMessage } = props; const getInputs = useCallback((message: Message) => { return get(message, 'data.inputs', {}) as Record; @@ -37,13 +33,12 @@ const useAwaitCompentData = (props: IAwaitCompentData) => { const nextInputs = buildBeginQueryWithObject(inputs, values); sendFormMessage({ inputs: nextInputs, - agent_id: canvasId, }); }, - [getInputs, sendFormMessage, canvasId], + [getInputs, sendFormMessage], ); - const isWaitting = useMemo(() => { + const isWaiting = useMemo(() => { const temp = derivedMessages?.some((message, i) => { const flag = message.role === MessageType.Assistant && @@ -53,7 +48,7 @@ const useAwaitCompentData = (props: IAwaitCompentData) => { }); return temp; }, [derivedMessages]); - return { getInputs, buildInputList, handleOk, isWaitting }; + return { getInputs, buildInputList, handleOk, isWaiting }; }; -export { useAwaitCompentData }; +export { useAwaitComponentData }; diff --git a/web/src/pages/agent/share/index.tsx b/web/src/pages/agent/share/index.tsx index 6fb1d2964..0810e7b87 100644 --- a/web/src/pages/agent/share/index.tsx +++ b/web/src/pages/agent/share/index.tsx @@ -11,7 +11,7 @@ import { cn } from '@/lib/utils'; import i18n, { changeLanguageAsync } from '@/locales/config'; import DebugContent from '@/pages/agent/debug-content'; import { useCacheChatLog } from '@/pages/agent/hooks/use-cache-chat-log'; -import { useAwaitCompentData } from '@/pages/agent/hooks/use-chat-logic'; +import { useAwaitComponentData } from '@/pages/agent/hooks/use-chat-logic'; import { buildMessageUuidWithRole } from '@/utils/chat'; import { isEmpty } from 'lodash'; import React, { forwardRef, useCallback } from 'react'; @@ -64,10 +64,9 @@ const ChatContainer = () => { resetSession, } = useSendNextSharedMessage(addEventList); - const { buildInputList, handleOk, isWaitting } = useAwaitCompentData({ + const { buildInputList, handleOk, isWaiting } = useAwaitComponentData({ derivedMessages, sendFormMessage, - canvasId: conversationId as string, }); const sendDisabled = useSendButtonDisabled(value); @@ -191,8 +190,8 @@ const ChatContainer = () => { { sendLoading={sendLoading} stopOutputMessage={stopOutputMessage} onUpload={handleUploadFile} - isUploading={loading || isWaitting} + isUploading={loading || isWaiting} > diff --git a/web/src/pages/agent/utils/chat.ts b/web/src/pages/agent/utils/chat.ts index 369cb5aa4..53d8712b3 100644 --- a/web/src/pages/agent/utils/chat.ts +++ b/web/src/pages/agent/utils/chat.ts @@ -1,4 +1,5 @@ import { MessageType } from '@/constants/chat'; +import { IEventList, MessageEventType } from '@/hooks/use-send-message'; import { IMessage, IReference } from '@/interfaces/database/chat'; import { isEmpty } from 'lodash'; @@ -18,3 +19,36 @@ export const buildAgentMessageItemReference = ( return reference ?? { doc_aggs: [], chunks: [], total: 0 }; }; + +/** + * Determines whether the message should be split into two separate entries: + * one for the assistant's answer and one for the user input prompt. + * + * A split is needed when all of the following are true: + * 1. The event list contains a `MessageEnd` event. + * 2. The event list contains a `UserInputs` event. + * 3. The `MessageEnd` event occurs before the `UserInputs` event. + * 4. There is actual message content (`content` is truthy). + * + * @param eventList - The list of SSE events received from the server. + * @param content - The assistant's message content extracted from the events. + * @returns `true` if the message should be split, otherwise `false`. + */ +export function shouldSplitMessage( + eventList: IEventList, + content?: string, +): boolean { + const messageEndIndex = eventList.findIndex( + (x) => x.event === MessageEventType.MessageEnd, + ); + const userInputsIndex = eventList.findIndex( + (x) => x.event === MessageEventType.UserInputs, + ); + + return ( + messageEndIndex !== -1 && + userInputsIndex !== -1 && + messageEndIndex < userInputsIndex && + !!content + ); +}