diff --git a/web/src/components/message-item/feedback-modal.tsx b/web/src/components/message-item/feedback-modal.tsx index 0dca166fa..c2a471c7b 100644 --- a/web/src/components/message-item/feedback-modal.tsx +++ b/web/src/components/message-item/feedback-modal.tsx @@ -1,20 +1,34 @@ import { Form, Input, Modal } from 'antd'; import { IModalProps } from '@/interfaces/common'; +import { IFeedbackRequestBody } from '@/interfaces/request/chat'; +import { useCallback } from 'react'; type FieldType = { - username?: string; + feedback?: string; }; -const FeedbackModal = ({ visible, hideModal }: IModalProps) => { +const FeedbackModal = ({ + visible, + hideModal, + onOk, + loading, +}: IModalProps) => { const [form] = Form.useForm(); - const handleOk = async () => { + const handleOk = useCallback(async () => { const ret = await form.validateFields(); - }; + return onOk?.({ thumbup: false, feedback: ret.feedback }); + }, [onOk, form]); return ( - +
) => { form={form} > - name="username" - rules={[{ required: true, message: 'Please input your username!' }]} + name="feedback" + rules={[{ required: true, message: 'Please input your feedback!' }]} > - +
diff --git a/web/src/components/message-item/group-button.tsx b/web/src/components/message-item/group-button.tsx index 619fb9246..9b92f3a91 100644 --- a/web/src/components/message-item/group-button.tsx +++ b/web/src/components/message-item/group-button.tsx @@ -1,5 +1,4 @@ import CopyToClipboard from '@/components/copy-to-clipboard'; -import { useSetModalState } from '@/hooks/common-hooks'; import { DeleteOutlined, DislikeOutlined, @@ -8,21 +7,33 @@ import { SyncOutlined, } from '@ant-design/icons'; import { Radio } from 'antd'; +import { useCallback } from 'react'; import FeedbackModal from './feedback-modal'; +import { useSendFeedback } from './hooks'; -export const AssistantGroupButton = () => { - const { visible, hideModal, showModal } = useSetModalState(); +interface IProps { + messageId: string; + content: string; +} + +export const AssistantGroupButton = ({ messageId, content }: IProps) => { + const { visible, hideModal, showModal, onFeedbackOk, loading } = + useSendFeedback(messageId); + + const handleLike = useCallback(() => { + onFeedbackOk({ thumbup: true }); + }, [onFeedbackOk]); return ( <> - + - + @@ -30,7 +41,12 @@ export const AssistantGroupButton = () => { {visible && ( - + )} ); diff --git a/web/src/components/message-item/hooks.ts b/web/src/components/message-item/hooks.ts new file mode 100644 index 000000000..c9023bc6c --- /dev/null +++ b/web/src/components/message-item/hooks.ts @@ -0,0 +1,32 @@ +import { useFeedback } from '@/hooks/chat-hooks'; +import { useSetModalState } from '@/hooks/common-hooks'; +import { IFeedbackRequestBody } from '@/interfaces/request/chat'; +import { getMessagePureId } from '@/utils/chat'; +import { useCallback } from 'react'; + +export const useSendFeedback = (messageId: string) => { + const { visible, hideModal, showModal } = useSetModalState(); + const { feedback, loading } = useFeedback(); + + const onFeedbackOk = useCallback( + async (params: IFeedbackRequestBody) => { + const ret = await feedback({ + ...params, + messageId: getMessagePureId(messageId), + }); + + if (ret === 0) { + hideModal(); + } + }, + [feedback, hideModal, messageId], + ); + + return { + loading, + onFeedbackOk, + visible, + hideModal, + showModal, + }; +}; diff --git a/web/src/components/message-item/index.tsx b/web/src/components/message-item/index.tsx index 176e4c708..17329b07b 100644 --- a/web/src/components/message-item/index.tsx +++ b/web/src/components/message-item/index.tsx @@ -2,7 +2,7 @@ import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg'; import { MessageType } from '@/constants/chat'; import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; import { useSelectFileThumbnails } from '@/hooks/knowledge-hooks'; -import { IReference, Message } from '@/interfaces/database/chat'; +import { IReference } from '@/interfaces/database/chat'; import { IChunk } from '@/interfaces/database/knowledge'; import classNames from 'classnames'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; @@ -11,19 +11,20 @@ import { useFetchDocumentInfosByIds, useFetchDocumentThumbnailsByIds, } from '@/hooks/document-hooks'; +import { IMessage } from '@/pages/chat/interface'; import MarkdownContent from '@/pages/chat/markdown-content'; import { getExtension, isImage } from '@/utils/document-util'; import { Avatar, Button, Flex, List, Space, Typography } from 'antd'; import FileIcon from '../file-icon'; import IndentedTreeModal from '../indented-tree/modal'; import NewDocumentLink from '../new-document-link'; -// import { AssistantGroupButton, UserGroupButton } from './group-button'; +import { AssistantGroupButton, UserGroupButton } from './group-button'; import styles from './index.less'; const { Text } = Typography; interface IProps { - item: Message; + item: IMessage; reference: IReference; loading?: boolean; nickname?: string; @@ -36,7 +37,6 @@ const MessageItem = ({ reference, loading = false, avatar = '', - nickname = '', clickDocumentButton, }: IProps) => { const isAssistant = item.role === MessageType.Assistant; @@ -111,13 +111,16 @@ const MessageItem = ({ )} - {/* {isAssistant ? ( - + {isAssistant ? ( + ) : ( - )} */} + )} - {isAssistant ? '' : nickname} + {/* {isAssistant ? '' : nickname} */}
{ const messageList = conversation?.message?.map((x: Message | IMessage) => ({ ...x, - id: 'id' in x && x.id ? x.id : uuid(), + id: buildMessageUuid(x), })) ?? []; return { ...conversation, message: messageList }; @@ -292,6 +292,56 @@ export const useRemoveNextConversation = () => { return { data, loading, removeConversation: mutateAsync }; }; + +export const useDeleteMessage = () => { + // const queryClient = useQueryClient(); + const { conversationId } = useGetChatSearchParams(); + + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: ['deleteMessage'], + mutationFn: async (messageId: string) => { + const { data } = await chatService.deleteMessage({ + messageId, + conversationId, + }); + if (data.retcode === 0) { + // queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] }); + } + return data.retcode; + }, + }); + + return { data, loading, deleteMessage: mutateAsync }; +}; + +export const useFeedback = () => { + const { conversationId } = useGetChatSearchParams(); + + const { + data, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: ['feedback'], + mutationFn: async (params: IFeedbackRequestBody) => { + const { data } = await chatService.thumbup({ + ...params, + conversationId, + }); + if (data.retcode === 0) { + message.success(i18n.t(`message.operated`)); + } + return data.retcode; + }, + }); + + return { data, loading, feedback: mutateAsync }; +}; + //#endregion // #region API provided for external calls diff --git a/web/src/interfaces/request/chat.ts b/web/src/interfaces/request/chat.ts new file mode 100644 index 000000000..46c5f3df6 --- /dev/null +++ b/web/src/interfaces/request/chat.ts @@ -0,0 +1,5 @@ +export interface IFeedbackRequestBody { + messageId?: string; + thumbup?: boolean; + feedback?: string; +} diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts index 7e4fe9361..b0bb68627 100644 --- a/web/src/pages/chat/hooks.ts +++ b/web/src/pages/chat/hooks.ts @@ -421,6 +421,7 @@ export const useSendMessage = ( messages: [ ...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')), { + id: uuid(), role: MessageType.User, content: message, doc_ids: documentIds, diff --git a/web/src/services/chat-service.ts b/web/src/services/chat-service.ts index 5a8a20641..fc8054e65 100644 --- a/web/src/services/chat-service.ts +++ b/web/src/services/chat-service.ts @@ -20,6 +20,9 @@ const { getExternalConversation, completeExternalConversation, uploadAndParseExternal, + deleteMessage, + thumbup, + tts, } = api; const methods = { @@ -91,6 +94,18 @@ const methods = { url: uploadAndParseExternal, method: 'post', }, + deleteMessage: { + url: deleteMessage, + method: 'post', + }, + thumbup: { + url: thumbup, + method: 'post', + }, + tts: { + url: tts, + method: 'post', + }, } as const; const chatService = registerServer(methods, request); diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index d482872e7..4f696fecb 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -63,6 +63,9 @@ export default { listConversation: `${api_host}/conversation/list`, removeConversation: `${api_host}/conversation/rm`, completeConversation: `${api_host}/conversation/completion`, + deleteMessage: `${api_host}/conversation/delete_msg`, + thumbup: `${api_host}/conversation/thumbup`, + tts: `${api_host}/conversation/tts`, // chat for external createToken: `${api_host}/api/new_token`, listToken: `${api_host}/api/token_list`, diff --git a/web/src/utils/chat.ts b/web/src/utils/chat.ts index 1ab0da2c1..8d8508456 100644 --- a/web/src/utils/chat.ts +++ b/web/src/utils/chat.ts @@ -1,5 +1,25 @@ -import { EmptyConversationId } from '@/constants/chat'; +import { EmptyConversationId, MessageType } from '@/constants/chat'; +import { Message } from '@/interfaces/database/chat'; +import { IMessage } from '@/pages/chat/interface'; +import { v4 as uuid } from 'uuid'; export const isConversationIdExist = (conversationId: string) => { return conversationId !== EmptyConversationId && conversationId !== ''; }; + +export const buildMessageUuid = (message: Message | IMessage) => { + if ('id' in message && message.id) { + return message.role === MessageType.User + ? `${MessageType.User}_${message.id}` + : `${MessageType.Assistant}_${message.id}`; + } + return uuid(); +}; + +export const getMessagePureId = (id: string) => { + const strings = id.split('_'); + if (strings.length > 0) { + return strings.at(-1); + } + return id; +};