### What problem does this PR solve? feat: Send message with uuid #2088 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.11.0
| ); | ); | ||||
| }; | }; | ||||
| export const UserGroupButton = () => { | |||||
| interface UserGroupButtonProps { | |||||
| messageId: string; | |||||
| content: string; | |||||
| } | |||||
| export const UserGroupButton = ({ content }: UserGroupButtonProps) => { | |||||
| return ( | return ( | ||||
| <Radio.Group size="small"> | <Radio.Group size="small"> | ||||
| <Radio.Button value="a"> | <Radio.Button value="a"> | ||||
| <CopyToClipboard text="xxx"></CopyToClipboard> | |||||
| <CopyToClipboard text={content}></CopyToClipboard> | |||||
| </Radio.Button> | </Radio.Button> | ||||
| <Radio.Button value="b"> | <Radio.Button value="b"> | ||||
| <SyncOutlined /> | <SyncOutlined /> |
| nickname?: string; | nickname?: string; | ||||
| avatar?: string; | avatar?: string; | ||||
| clickDocumentButton?: (documentId: string, chunk: IChunk) => void; | clickDocumentButton?: (documentId: string, chunk: IChunk) => void; | ||||
| index: number; | |||||
| } | } | ||||
| const MessageItem = ({ | const MessageItem = ({ | ||||
| loading = false, | loading = false, | ||||
| avatar = '', | avatar = '', | ||||
| clickDocumentButton, | clickDocumentButton, | ||||
| index, | |||||
| }: IProps) => { | }: IProps) => { | ||||
| const isAssistant = item.role === MessageType.Assistant; | const isAssistant = item.role === MessageType.Assistant; | ||||
| const isUser = item.role === MessageType.User; | const isUser = item.role === MessageType.User; | ||||
| <Flex vertical gap={8} flex={1}> | <Flex vertical gap={8} flex={1}> | ||||
| <Space> | <Space> | ||||
| {isAssistant ? ( | {isAssistant ? ( | ||||
| <AssistantGroupButton | |||||
| messageId={item.id} | |||||
| content={item.content} | |||||
| prompt={item.prompt} | |||||
| ></AssistantGroupButton> | |||||
| index !== 0 && ( | |||||
| <AssistantGroupButton | |||||
| messageId={item.id} | |||||
| content={item.content} | |||||
| prompt={item.prompt} | |||||
| ></AssistantGroupButton> | |||||
| ) | |||||
| ) : ( | ) : ( | ||||
| <UserGroupButton></UserGroupButton> | |||||
| <UserGroupButton | |||||
| content={item.content} | |||||
| messageId={item.id} | |||||
| ></UserGroupButton> | |||||
| )} | )} | ||||
| {/* <b>{isAssistant ? '' : nickname}</b> */} | {/* <b>{isAssistant ? '' : nickname}</b> */} |
| 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'; | ||||
| 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 = () => { | ||||
| // } | // } | ||||
| const conversation = data?.data ?? {}; | const conversation = data?.data ?? {}; | ||||
| const messageList = | |||||
| conversation?.message?.map((x: Message | IMessage) => ({ | |||||
| ...x, | |||||
| id: buildMessageUuid(x), | |||||
| })) ?? []; | |||||
| const messageList = buildMessageListWithUuid(conversation?.message); | |||||
| return { ...conversation, message: messageList }; | return { ...conversation, message: messageList }; | ||||
| } | } | ||||
| }; | }; | ||||
| export const useDeleteMessage = () => { | export const useDeleteMessage = () => { | ||||
| // const queryClient = useQueryClient(); | |||||
| const { conversationId } = useGetChatSearchParams(); | const { conversationId } = useGetChatSearchParams(); | ||||
| const { | const { | ||||
| messageId, | messageId, | ||||
| conversationId, | conversationId, | ||||
| }); | }); | ||||
| if (data.retcode === 0) { | |||||
| // queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] }); | |||||
| } | |||||
| return data.retcode; | return data.retcode; | ||||
| }, | }, | ||||
| }); | }); | ||||
| conversationId, | conversationId, | ||||
| ); | ); | ||||
| const messageList = buildMessageListWithUuid(data?.data?.message); | |||||
| set(data, 'data.message', messageList); | |||||
| return data; | return data; | ||||
| }, | }, | ||||
| }); | }); |
| avatar={userInfo.avatar} | avatar={userInfo.avatar} | ||||
| reference={buildMessageItemReference(conversation, message)} | reference={buildMessageItemReference(conversation, message)} | ||||
| clickDocumentButton={clickDocumentButton} | clickDocumentButton={clickDocumentButton} | ||||
| index={i} | |||||
| ></MessageItem> | ></MessageItem> | ||||
| ); | ); | ||||
| })} | })} |
| } from '@/interfaces/database/chat'; | } from '@/interfaces/database/chat'; | ||||
| import { IChunk } from '@/interfaces/database/knowledge'; | import { IChunk } from '@/interfaces/database/knowledge'; | ||||
| import { getFileExtension } from '@/utils'; | import { getFileExtension } from '@/utils'; | ||||
| import { buildMessageUuid } from '@/utils/chat'; | |||||
| import { useMutationState } from '@tanstack/react-query'; | import { useMutationState } from '@tanstack/react-query'; | ||||
| import { get } from 'lodash'; | import { get } from 'lodash'; | ||||
| import omit from 'lodash/omit'; | |||||
| import trim from 'lodash/trim'; | import trim from 'lodash/trim'; | ||||
| import { | import { | ||||
| ChangeEventHandler, | ChangeEventHandler, | ||||
| const { data: dialog } = useFetchNextDialog(); | const { data: dialog } = useFetchNextDialog(); | ||||
| const { conversationId, dialogId } = useGetChatSearchParams(); | const { conversationId, dialogId } = useGetChatSearchParams(); | ||||
| // Show the entered message in the conversation immediately after sending the message | |||||
| const addNewestConversation = useCallback( | const addNewestConversation = useCallback( | ||||
| (message: Partial<Message>, answer: string = '') => { | |||||
| (message: Message, answer: string = '') => { | |||||
| setCurrentConversation((pre) => { | setCurrentConversation((pre) => { | ||||
| return { | return { | ||||
| ...pre, | ...pre, | ||||
| message: [ | message: [ | ||||
| ...pre.message, | ...pre.message, | ||||
| { | { | ||||
| role: MessageType.User, | |||||
| content: message.content, | |||||
| doc_ids: message.doc_ids, | |||||
| id: uuid(), | |||||
| ...message, | |||||
| id: buildMessageUuid(message), | |||||
| } as IMessage, | } as IMessage, | ||||
| { | { | ||||
| role: MessageType.Assistant, | role: MessageType.Assistant, | ||||
| content: answer, | content: answer, | ||||
| id: uuid(), | |||||
| id: buildMessageUuid({ ...message, role: MessageType.Assistant }), | |||||
| reference: {}, | reference: {}, | ||||
| } as IMessage, | } as IMessage, | ||||
| ], | ], | ||||
| [], | [], | ||||
| ); | ); | ||||
| // Add the streaming message to the last item in the message list | |||||
| const addNewestAnswer = useCallback((answer: IAnswer) => { | const addNewestAnswer = useCallback((answer: IAnswer) => { | ||||
| setCurrentConversation((pre) => { | setCurrentConversation((pre) => { | ||||
| const latestMessage = pre.message?.at(-1); | const latestMessage = pre.message?.at(-1); | ||||
| ...latestMessage, | ...latestMessage, | ||||
| content: answer.answer, | content: answer.answer, | ||||
| reference: answer.reference, | reference: answer.reference, | ||||
| id: buildMessageUuid({ | |||||
| id: answer.id, | |||||
| role: MessageType.Assistant, | |||||
| }), | |||||
| prompt: answer.prompt, | |||||
| } as IMessage, | } as IMessage, | ||||
| ], | ], | ||||
| }; | }; | ||||
| const { send, answer, done, setDone } = useSendMessageWithSse(); | const { send, answer, done, setDone } = useSendMessageWithSse(); | ||||
| const sendMessage = useCallback( | const sendMessage = useCallback( | ||||
| async (message: string, documentIds: string[], id?: string) => { | |||||
| async (message: Message, documentIds: string[], id?: string) => { | |||||
| const res = await send({ | const res = await send({ | ||||
| conversation_id: id ?? conversationId, | conversation_id: id ?? conversationId, | ||||
| messages: [ | messages: [ | ||||
| ...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')), | |||||
| ...(conversation?.message ?? []), | |||||
| { | { | ||||
| id: uuid(), | |||||
| role: MessageType.User, | |||||
| content: message, | |||||
| ...message, | |||||
| doc_ids: documentIds, | doc_ids: documentIds, | ||||
| }, | }, | ||||
| ], | ], | ||||
| if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) { | if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) { | ||||
| // cancel loading | // cancel loading | ||||
| setValue(message); | |||||
| setValue(message.content); | |||||
| console.info('removeLatestMessage111'); | console.info('removeLatestMessage111'); | ||||
| removeLatestMessage(); | removeLatestMessage(); | ||||
| } else { | } else { | ||||
| ); | ); | ||||
| const handleSendMessage = useCallback( | const handleSendMessage = useCallback( | ||||
| async (message: string, documentIds: string[]) => { | |||||
| async (message: Message, documentIds: string[]) => { | |||||
| if (conversationId !== '') { | if (conversationId !== '') { | ||||
| sendMessage(message, documentIds); | sendMessage(message, documentIds); | ||||
| } else { | } else { | ||||
| const data = await setConversation(message); | |||||
| const data = await setConversation(message.content); | |||||
| if (data.retcode === 0) { | if (data.retcode === 0) { | ||||
| const id = data.data.id; | const id = data.data.id; | ||||
| sendMessage(message, documentIds, id); | sendMessage(message, documentIds, id); | ||||
| const handlePressEnter = useCallback( | const handlePressEnter = useCallback( | ||||
| (documentIds: string[]) => { | (documentIds: string[]) => { | ||||
| if (trim(value) === '') return; | if (trim(value) === '') return; | ||||
| const id = uuid(); | |||||
| addNewestConversation({ content: value, doc_ids: documentIds }); | |||||
| addNewestConversation({ | |||||
| content: value, | |||||
| doc_ids: documentIds, | |||||
| id, | |||||
| role: MessageType.User, | |||||
| }); | |||||
| if (done) { | if (done) { | ||||
| setValue(''); | setValue(''); | ||||
| handleSendMessage(value.trim(), documentIds); | |||||
| handleSendMessage( | |||||
| { id, content: value.trim(), role: MessageType.User }, | |||||
| documentIds, | |||||
| ); | |||||
| } | } | ||||
| }, | }, | ||||
| [addNewestConversation, handleSendMessage, done, setValue, value], | [addNewestConversation, handleSendMessage, done, setValue, value], |
| let replacedText = reactStringReplace(text, reg, (match, i) => { | let replacedText = reactStringReplace(text, reg, (match, i) => { | ||||
| const chunkIndex = getChunkIndex(match); | const chunkIndex = getChunkIndex(match); | ||||
| return ( | return ( | ||||
| <Popover content={getPopoverContent(chunkIndex)}> | |||||
| <InfoCircleOutlined key={i} className={styles.referenceIcon} /> | |||||
| <Popover content={getPopoverContent(chunkIndex)} key={i}> | |||||
| <InfoCircleOutlined className={styles.referenceIcon} /> | |||||
| </Popover> | </Popover> | ||||
| ); | ); | ||||
| }); | }); |
| sendLoading && | sendLoading && | ||||
| conversation?.message.length - 1 === i | conversation?.message.length - 1 === i | ||||
| } | } | ||||
| index={i} | |||||
| ></MessageItem> | ></MessageItem> | ||||
| ); | ); | ||||
| })} | })} |
| import { useSendMessageWithSse } from '@/hooks/logic-hooks'; | import { useSendMessageWithSse } from '@/hooks/logic-hooks'; | ||||
| import { IAnswer, Message } from '@/interfaces/database/chat'; | import { IAnswer, Message } from '@/interfaces/database/chat'; | ||||
| import api from '@/utils/api'; | import api from '@/utils/api'; | ||||
| import omit from 'lodash/omit'; | |||||
| import { buildMessageUuid } from '@/utils/chat'; | |||||
| import trim from 'lodash/trim'; | import trim from 'lodash/trim'; | ||||
| import { | import { | ||||
| Dispatch, | Dispatch, | ||||
| message: [ | message: [ | ||||
| ...(pre.message ?? []), | ...(pre.message ?? []), | ||||
| { | { | ||||
| role: MessageType.User, | |||||
| content: message.content, | |||||
| doc_ids: message.doc_ids, | |||||
| id: uuid(), | |||||
| ...message, | |||||
| id: buildMessageUuid(message), | |||||
| } as IMessage, | } as IMessage, | ||||
| { | { | ||||
| role: MessageType.Assistant, | role: MessageType.Assistant, | ||||
| content: '', | content: '', | ||||
| id: uuid(), | |||||
| id: buildMessageUuid({ ...message, role: MessageType.Assistant }), | |||||
| reference: {}, | reference: {}, | ||||
| } as IMessage, | } as IMessage, | ||||
| ], | ], | ||||
| ...latestMessage, | ...latestMessage, | ||||
| content: answer.answer, | content: answer.answer, | ||||
| reference: answer.reference, | reference: answer.reference, | ||||
| id: buildMessageUuid({ | |||||
| id: answer.id, | |||||
| role: MessageType.Assistant, | |||||
| }), | |||||
| prompt: answer.prompt, | |||||
| } as IMessage, | } as IMessage, | ||||
| ], | ], | ||||
| }; | }; | ||||
| ); | ); | ||||
| const sendMessage = useCallback( | const sendMessage = useCallback( | ||||
| async (message: string, 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 ?? []).map((x: IMessage) => omit(x, 'id')), | |||||
| { | |||||
| role: MessageType.User, | |||||
| content: message, | |||||
| }, | |||||
| ], | |||||
| messages: [...(conversation?.message ?? []), message], | |||||
| }); | }); | ||||
| if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) { | if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) { | ||||
| // cancel loading | // cancel loading | ||||
| setValue(message); | |||||
| setValue(message.content); | |||||
| removeLatestMessage(); | removeLatestMessage(); | ||||
| } | } | ||||
| }, | }, | ||||
| ); | ); | ||||
| const handleSendMessage = useCallback( | const handleSendMessage = useCallback( | ||||
| async (message: string) => { | |||||
| async (message: Message) => { | |||||
| if (conversationId !== '') { | if (conversationId !== '') { | ||||
| sendMessage(message); | sendMessage(message); | ||||
| } else { | } else { | ||||
| const handlePressEnter = useCallback( | const handlePressEnter = useCallback( | ||||
| (documentIds: string[]) => { | (documentIds: string[]) => { | ||||
| if (trim(value) === '') return; | if (trim(value) === '') return; | ||||
| const id = uuid(); | |||||
| if (done) { | if (done) { | ||||
| setValue(''); | setValue(''); | ||||
| addNewestConversation({ content: value, doc_ids: documentIds }); | |||||
| handleSendMessage(value.trim()); | |||||
| addNewestConversation({ | |||||
| content: value, | |||||
| doc_ids: documentIds, | |||||
| id, | |||||
| role: MessageType.User, | |||||
| }); | |||||
| handleSendMessage({ | |||||
| content: value.trim(), | |||||
| id, | |||||
| role: MessageType.User, | |||||
| }); | |||||
| } | } | ||||
| }, | }, | ||||
| [addNewestConversation, done, handleSendMessage, setValue, value], | [addNewestConversation, done, handleSendMessage, setValue, value], |
| message, | message, | ||||
| )} | )} | ||||
| clickDocumentButton={clickDocumentButton} | clickDocumentButton={clickDocumentButton} | ||||
| index={i} | |||||
| ></MessageItem> | ></MessageItem> | ||||
| ); | ); | ||||
| })} | })} |
| useScrollToBottom, | useScrollToBottom, | ||||
| useSendMessageWithSse, | useSendMessageWithSse, | ||||
| } from '@/hooks/logic-hooks'; | } from '@/hooks/logic-hooks'; | ||||
| import { IAnswer } from '@/interfaces/database/chat'; | |||||
| import { IAnswer, Message } from '@/interfaces/database/chat'; | |||||
| import { IMessage } from '@/pages/chat/interface'; | import { IMessage } from '@/pages/chat/interface'; | ||||
| import api from '@/utils/api'; | import api from '@/utils/api'; | ||||
| import { buildMessageUuid } from '@/utils/chat'; | |||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import trim from 'lodash/trim'; | |||||
| import { useCallback, useEffect, useState } from 'react'; | import { useCallback, useEffect, useState } from 'react'; | ||||
| import { useParams } from 'umi'; | import { useParams } from 'umi'; | ||||
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||
| const ref = useScrollToBottom(currentMessages); | const ref = useScrollToBottom(currentMessages); | ||||
| const addNewestQuestion = useCallback( | const addNewestQuestion = useCallback( | ||||
| (message: string, answer: string = '') => { | |||||
| (message: Message, answer: string = '') => { | |||||
| setCurrentMessages((pre) => { | setCurrentMessages((pre) => { | ||||
| return [ | return [ | ||||
| ...pre, | ...pre, | ||||
| { | { | ||||
| role: MessageType.User, | |||||
| content: message, | |||||
| id: uuid(), | |||||
| ...message, | |||||
| id: buildMessageUuid(message), | |||||
| }, | }, | ||||
| { | { | ||||
| role: MessageType.Assistant, | role: MessageType.Assistant, | ||||
| content: answer, | content: answer, | ||||
| id: uuid(), | |||||
| id: buildMessageUuid({ ...message, role: MessageType.Assistant }), | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| }); | }); | ||||
| return [ | return [ | ||||
| ...pre.slice(0, -1), | ...pre.slice(0, -1), | ||||
| { | { | ||||
| id: uuid(), | |||||
| role: MessageType.Assistant, | role: MessageType.Assistant, | ||||
| content: answer.answer, | content: answer.answer, | ||||
| reference: answer.reference, | reference: answer.reference, | ||||
| id: buildMessageUuid({ | |||||
| id: answer.id, | |||||
| role: MessageType.Assistant, | |||||
| }), | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| }); | }); | ||||
| }; | }; | ||||
| export const useSendMessage = ( | export const useSendMessage = ( | ||||
| addNewestQuestion: (message: string, answer?: string) => void, | |||||
| addNewestQuestion: (message: Message, answer?: string) => void, | |||||
| removeLatestMessage: () => void, | removeLatestMessage: () => void, | ||||
| addNewestAnswer: (answer: IAnswer) => void, | addNewestAnswer: (answer: IAnswer) => void, | ||||
| ) => { | ) => { | ||||
| const { send, answer, done } = useSendMessageWithSse(api.runCanvas); | const { send, answer, done } = useSendMessageWithSse(api.runCanvas); | ||||
| const sendMessage = useCallback( | const sendMessage = useCallback( | ||||
| async (message: string) => { | |||||
| async (message: Message) => { | |||||
| const params: Record<string, unknown> = { | const params: Record<string, unknown> = { | ||||
| id: flowId, | id: flowId, | ||||
| }; | }; | ||||
| if (message) { | |||||
| params.message = message; | |||||
| if (message.content) { | |||||
| params.message = message.content; | |||||
| params.message_id = message.id; | |||||
| } | } | ||||
| const res = await send(params); | const res = await send(params); | ||||
| antMessage.error(res?.data?.retmsg); | antMessage.error(res?.data?.retmsg); | ||||
| // cancel loading | // cancel loading | ||||
| setValue(message); | |||||
| setValue(message.content); | |||||
| removeLatestMessage(); | removeLatestMessage(); | ||||
| } else { | } else { | ||||
| refetch(); // pull the message list after sending the message successfully | refetch(); // pull the message list after sending the message successfully | ||||
| ); | ); | ||||
| const handleSendMessage = useCallback( | const handleSendMessage = useCallback( | ||||
| async (message: string) => { | |||||
| async (message: Message) => { | |||||
| sendMessage(message); | sendMessage(message); | ||||
| }, | }, | ||||
| [sendMessage], | [sendMessage], | ||||
| }, [answer, addNewestAnswer]); | }, [answer, addNewestAnswer]); | ||||
| const handlePressEnter = useCallback(() => { | const handlePressEnter = useCallback(() => { | ||||
| if (trim(value) === '') return; | |||||
| const id = uuid(); | |||||
| if (done) { | if (done) { | ||||
| setValue(''); | setValue(''); | ||||
| handleSendMessage(value.trim()); | |||||
| handleSendMessage({ id, content: value.trim(), role: MessageType.User }); | |||||
| } | } | ||||
| addNewestQuestion(value); | |||||
| addNewestQuestion({ | |||||
| content: value, | |||||
| id, | |||||
| role: MessageType.User, | |||||
| }); | |||||
| }, [addNewestQuestion, handleSendMessage, done, setValue, value]); | }, [addNewestQuestion, handleSendMessage, done, setValue, value]); | ||||
| return { | return { |
| return conversationId !== EmptyConversationId && conversationId !== ''; | return conversationId !== EmptyConversationId && conversationId !== ''; | ||||
| }; | }; | ||||
| export const buildMessageUuid = (message: Message | IMessage) => { | |||||
| export const buildMessageUuid = (message: Partial<Message | IMessage>) => { | |||||
| if ('id' in message && message.id) { | if ('id' in message && message.id) { | ||||
| return message.role === MessageType.User | return message.role === MessageType.User | ||||
| ? `${MessageType.User}_${message.id}` | ? `${MessageType.User}_${message.id}` |