diff --git a/web/src/components/message-item/group-button.tsx b/web/src/components/message-item/group-button.tsx index 1884f9e701..cea7cfaa21 100644 --- a/web/src/components/message-item/group-button.tsx +++ b/web/src/components/message-item/group-button.tsx @@ -8,8 +8,9 @@ import { SoundOutlined, SyncOutlined, } from '@ant-design/icons'; -import { Radio } from 'antd'; +import { Radio, Tooltip } from 'antd'; import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import SvgIcon from '../svg-icon'; import FeedbackModal from './feedback-modal'; import { useRemoveMessage, useSendFeedback } from './hooks'; @@ -33,6 +34,7 @@ export const AssistantGroupButton = ({ hideModal: hidePromptModal, showModal: showPromptModal, } = useSetModalState(); + const { t } = useTranslation(); const handleLike = useCallback(() => { onFeedbackOk({ thumbup: true }); @@ -45,7 +47,9 @@ export const AssistantGroupButton = ({ - + + + @@ -81,27 +85,41 @@ export const AssistantGroupButton = ({ interface UserGroupButtonProps extends IRemoveMessageById { messageId: string; content: string; + regenerateMessage(): void; + sendLoading: boolean; } export const UserGroupButton = ({ content, messageId, + sendLoading, removeMessageById, + regenerateMessage, }: UserGroupButtonProps) => { const { onRemoveMessage, loading } = useRemoveMessage( messageId, removeMessageById, ); + const { t } = useTranslation(); + return ( - - + + + + - + + + ); diff --git a/web/src/components/message-item/index.tsx b/web/src/components/message-item/index.tsx index 873445d4c2..510dd84b28 100644 --- a/web/src/components/message-item/index.tsx +++ b/web/src/components/message-item/index.tsx @@ -11,7 +11,7 @@ import { useFetchDocumentInfosByIds, useFetchDocumentThumbnailsByIds, } from '@/hooks/document-hooks'; -import { IRemoveMessageById } from '@/hooks/logic-hooks'; +import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; import { IMessage } from '@/pages/chat/interface'; import MarkdownContent from '@/pages/chat/markdown-content'; import { getExtension, isImage } from '@/utils/document-util'; @@ -24,10 +24,11 @@ import styles from './index.less'; const { Text } = Typography; -interface IProps extends IRemoveMessageById { +interface IProps extends IRemoveMessageById, IRegenerateMessage { item: IMessage; reference: IReference; loading?: boolean; + sendLoading?: boolean; nickname?: string; avatar?: string; clickDocumentButton?: (documentId: string, chunk: IChunk) => void; @@ -39,9 +40,11 @@ const MessageItem = ({ reference, loading = false, avatar = '', + sendLoading = false, clickDocumentButton, index, removeMessageById, + regenerateMessage, }: IProps) => { const isAssistant = item.role === MessageType.Assistant; const isUser = item.role === MessageType.User; @@ -73,6 +76,10 @@ const MessageItem = ({ [showModal], ); + const handleRegenerateMessage = useCallback(() => { + regenerateMessage(item); + }, [regenerateMessage, item]); + useEffect(() => { const ids = item?.doc_ids ?? []; if (ids.length) { @@ -128,6 +135,8 @@ const MessageItem = ({ content={item.content} messageId={item.id} removeMessageById={removeMessageById} + regenerateMessage={handleRegenerateMessage} + sendLoading={sendLoading} > )} diff --git a/web/src/hooks/logic-hooks.ts b/web/src/hooks/logic-hooks.ts index d4e43f7574..50b6851571 100644 --- a/web/src/hooks/logic-hooks.ts +++ b/web/src/hooks/logic-hooks.ts @@ -2,7 +2,7 @@ import { Authorization } from '@/constants/authorization'; import { LanguageTranslationMap } from '@/constants/common'; import { Pagination } from '@/interfaces/common'; import { ResponseType } from '@/interfaces/database/base'; -import { IAnswer } from '@/interfaces/database/chat'; +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'; @@ -23,6 +23,7 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'umi'; +import { v4 as uuid } from 'uuid'; import { useSetModalState, useTranslate } from './common-hooks'; import { useSetDocumentParser } from './document-hooks'; import { useSetPaginationParams } from './route-hook'; @@ -336,6 +337,77 @@ export const useRemoveMessageById = ( return { removeMessageById }; }; +export const useRemoveMessagesAfterCurrentMessage = ( + setCurrentConversation: ( + callback: (state: IClientConversation) => IClientConversation, + ) => void, +) => { + const removeMessagesAfterCurrentMessage = useCallback( + (messageId: string) => { + setCurrentConversation((pre) => { + const index = pre.message?.findIndex((x) => x.id === messageId); + if (index !== -1) { + let nextMessages = pre.message?.slice(0, index + 2) ?? []; + const latestMessage = nextMessages.at(-1); + nextMessages = latestMessage + ? [ + ...nextMessages.slice(0, -1), + { + ...latestMessage, + content: '', + reference: undefined, + prompt: undefined, + }, + ] + : nextMessages; + return { + ...pre, + message: nextMessages, + }; + } + return pre; + }); + }, + [setCurrentConversation], + ); + + return { removeMessagesAfterCurrentMessage }; +}; + +export interface IRegenerateMessage { + regenerateMessage(message: Message): void; +} + +export const useRegenerateMessage = ({ + removeMessagesAfterCurrentMessage, + sendMessage, + messages, +}: { + removeMessagesAfterCurrentMessage(messageId: string): void; + sendMessage({ message }: { message: Message; messages?: Message[] }): void; + messages: Message[]; +}) => { + const regenerateMessage = useCallback( + async (message: Message) => { + if (message.id) { + removeMessagesAfterCurrentMessage(message.id); + const index = messages.findIndex((x) => x.id === message.id); + let nextMessages; + if (index !== -1) { + nextMessages = messages.slice(0, index); + } + sendMessage({ + message: { ...message, id: uuid() }, + messages: nextMessages, + }); + } + }, + [removeMessagesAfterCurrentMessage, sendMessage, messages], + ); + + return { regenerateMessage }; +}; + // #endregion /** diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index e3549316ca..b6563203d5 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -425,6 +425,8 @@ The above is the content you need to summarize.`, parsing: 'Parsing', uploading: 'Uploading', uploadFailed: 'Upload failed', + regenerate: 'Regenerate', + read: 'Read content', }, setting: { profile: 'Profile', diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index 3d2715db55..01eebf4244 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -395,6 +395,8 @@ export default { parsing: '解析中', uploading: '上傳中', uploadFailed: '上傳失敗', + regenerate: '重新生成', + read: '朗讀內容', }, setting: { profile: '概述', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 654d636ce4..c82623082d 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -412,6 +412,8 @@ export default { parsing: '解析中', uploading: '上传中', uploadFailed: '上传失败', + regenerate: '重新生成', + read: '朗读内容', }, setting: { profile: '概要', diff --git a/web/src/pages/chat/chat-container/index.tsx b/web/src/pages/chat/chat-container/index.tsx index b87c64d61e..d682662fd3 100644 --- a/web/src/pages/chat/chat-container/index.tsx +++ b/web/src/pages/chat/chat-container/index.tsx @@ -28,17 +28,20 @@ const ChatContainer = () => { conversationId, loading, removeMessageById, + removeMessagesAfterCurrentMessage, } = useFetchConversationOnMount(); const { handleInputChange, handlePressEnter, value, loading: sendLoading, + regenerateMessage, } = useSendMessage( conversation, addNewestConversation, removeLatestMessage, addNewestAnswer, + removeMessagesAfterCurrentMessage, ); const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = useClickDrawer(); @@ -71,6 +74,8 @@ const ChatContainer = () => { clickDocumentButton={clickDocumentButton} index={i} removeMessageById={removeMessageById} + regenerateMessage={regenerateMessage} + sendLoading={sendLoading} > ); })} diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts index 055336da0a..dd2812d29d 100644 --- a/web/src/pages/chat/hooks.ts +++ b/web/src/pages/chat/hooks.ts @@ -18,7 +18,9 @@ import { useTranslate, } from '@/hooks/common-hooks'; import { + useRegenerateMessage, useRemoveMessageById, + useRemoveMessagesAfterCurrentMessage, useSendMessageWithSse, } from '@/hooks/logic-hooks'; import { @@ -255,6 +257,8 @@ export const useSelectCurrentConversation = () => { const { data: dialog } = useFetchNextDialog(); const { conversationId, dialogId } = useGetChatSearchParams(); const { removeMessageById } = useRemoveMessageById(setCurrentConversation); + const { removeMessagesAfterCurrentMessage } = + useRemoveMessagesAfterCurrentMessage(setCurrentConversation); // Show the entered message in the conversation immediately after sending the message const addNewestConversation = useCallback( @@ -353,6 +357,7 @@ export const useSelectCurrentConversation = () => { removeLatestMessage, addNewestAnswer, removeMessageById, + removeMessagesAfterCurrentMessage, loading, }; }; @@ -382,6 +387,7 @@ export const useFetchConversationOnMount = () => { addNewestAnswer, loading, removeMessageById, + removeMessagesAfterCurrentMessage, } = useSelectCurrentConversation(); const ref = useScrollToBottom(currentConversation); @@ -394,6 +400,7 @@ export const useFetchConversationOnMount = () => { conversationId, loading, removeMessageById, + removeMessagesAfterCurrentMessage, }; }; @@ -418,6 +425,7 @@ export const useSendMessage = ( addNewestConversation: (message: Message, answer?: string) => void, removeLatestMessage: () => void, addNewestAnswer: (answer: IAnswer) => void, + removeMessagesAfterCurrentMessage: (messageId: string) => void, ) => { const { setConversation } = useSetConversation(); const { conversationId } = useGetChatSearchParams(); @@ -427,16 +435,18 @@ export const useSendMessage = ( const { send, answer, done, setDone } = useSendMessageWithSse(); const sendMessage = useCallback( - async (message: Message, documentIds: string[], id?: string) => { + async ({ + message, + currentConversationId, + messages, + }: { + message: Message; + currentConversationId?: string; + messages?: Message[]; + }) => { const res = await send({ - conversation_id: id ?? conversationId, - messages: [ - ...(conversation?.message ?? []), - { - ...message, - doc_ids: documentIds, - }, - ], + conversation_id: currentConversationId ?? conversationId, + messages: [...(messages ?? conversation?.message ?? []), message], }); if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) { @@ -445,10 +455,10 @@ export const useSendMessage = ( console.info('removeLatestMessage111'); removeLatestMessage(); } else { - if (id) { + if (currentConversationId) { console.info('111'); // new conversation - handleClickConversation(id); + handleClickConversation(currentConversationId); } else { console.info('222'); // fetchConversation(conversationId); @@ -466,20 +476,26 @@ export const useSendMessage = ( ); const handleSendMessage = useCallback( - async (message: Message, documentIds: string[]) => { + async (message: Message) => { if (conversationId !== '') { - sendMessage(message, documentIds); + sendMessage({ message }); } else { const data = await setConversation(message.content); if (data.retcode === 0) { const id = data.data.id; - sendMessage(message, documentIds, id); + sendMessage({ message, currentConversationId: id }); } } }, [conversationId, setConversation, sendMessage], ); + const { regenerateMessage } = useRegenerateMessage({ + removeMessagesAfterCurrentMessage, + sendMessage, + messages: conversation.message, + }); + useEffect(() => { // #1289 if (answer.answer && answer?.conversationId === conversationId) { @@ -507,10 +523,12 @@ export const useSendMessage = ( }); if (done) { setValue(''); - handleSendMessage( - { id, content: value.trim(), role: MessageType.User }, - documentIds, - ); + handleSendMessage({ + id, + content: value.trim(), + role: MessageType.User, + doc_ids: documentIds, + }); } }, [addNewestConversation, handleSendMessage, done, setValue, value], @@ -521,6 +539,7 @@ export const useSendMessage = ( handleInputChange, value, setValue, + regenerateMessage, loading: !done, }; }; diff --git a/web/src/utils/chat.ts b/web/src/utils/chat.ts index 38312c3c34..c8b8a888c3 100644 --- a/web/src/utils/chat.ts +++ b/web/src/utils/chat.ts @@ -16,8 +16,8 @@ export const buildMessageUuid = (message: Partial) => { return uuid(); }; -export const getMessagePureId = (id: string) => { - const strings = id.split('_'); +export const getMessagePureId = (id?: string) => { + const strings = id?.split('_') ?? []; if (strings.length > 0) { return strings.at(-1); }