balibabu commited on
Commit
d2e049e
·
1 Parent(s): add4a89

feat: Submit Feedback #2088 (#2134)

Browse files

### What problem does this PR solve?

feat: Submit Feedback #2088

### Type of change


- [x] New Feature (non-breaking change which adds functionality)

web/src/components/message-item/feedback-modal.tsx CHANGED
@@ -1,20 +1,34 @@
1
  import { Form, Input, Modal } from 'antd';
2
 
3
  import { IModalProps } from '@/interfaces/common';
 
 
4
 
5
  type FieldType = {
6
- username?: string;
7
  };
8
 
9
- const FeedbackModal = ({ visible, hideModal }: IModalProps<any>) => {
 
 
 
 
 
10
  const [form] = Form.useForm();
11
 
12
- const handleOk = async () => {
13
  const ret = await form.validateFields();
14
- };
 
15
 
16
  return (
17
- <Modal title="Feedback" open={visible} onOk={handleOk} onCancel={hideModal}>
 
 
 
 
 
 
18
  <Form
19
  name="basic"
20
  labelCol={{ span: 0 }}
@@ -24,10 +38,10 @@ const FeedbackModal = ({ visible, hideModal }: IModalProps<any>) => {
24
  form={form}
25
  >
26
  <Form.Item<FieldType>
27
- name="username"
28
- rules={[{ required: true, message: 'Please input your username!' }]}
29
  >
30
- <Input.TextArea rows={8} placeholder="Please input your username!" />
31
  </Form.Item>
32
  </Form>
33
  </Modal>
 
1
  import { Form, Input, Modal } from 'antd';
2
 
3
  import { IModalProps } from '@/interfaces/common';
4
+ import { IFeedbackRequestBody } from '@/interfaces/request/chat';
5
+ import { useCallback } from 'react';
6
 
7
  type FieldType = {
8
+ feedback?: string;
9
  };
10
 
11
+ const FeedbackModal = ({
12
+ visible,
13
+ hideModal,
14
+ onOk,
15
+ loading,
16
+ }: IModalProps<IFeedbackRequestBody>) => {
17
  const [form] = Form.useForm();
18
 
19
+ const handleOk = useCallback(async () => {
20
  const ret = await form.validateFields();
21
+ return onOk?.({ thumbup: false, feedback: ret.feedback });
22
+ }, [onOk, form]);
23
 
24
  return (
25
+ <Modal
26
+ title="Feedback"
27
+ open={visible}
28
+ onOk={handleOk}
29
+ onCancel={hideModal}
30
+ confirmLoading={loading}
31
+ >
32
  <Form
33
  name="basic"
34
  labelCol={{ span: 0 }}
 
38
  form={form}
39
  >
40
  <Form.Item<FieldType>
41
+ name="feedback"
42
+ rules={[{ required: true, message: 'Please input your feedback!' }]}
43
  >
44
+ <Input.TextArea rows={8} placeholder="Please input your feedback!" />
45
  </Form.Item>
46
  </Form>
47
  </Modal>
web/src/components/message-item/group-button.tsx CHANGED
@@ -1,5 +1,4 @@
1
  import CopyToClipboard from '@/components/copy-to-clipboard';
2
- import { useSetModalState } from '@/hooks/common-hooks';
3
  import {
4
  DeleteOutlined,
5
  DislikeOutlined,
@@ -8,21 +7,33 @@ import {
8
  SyncOutlined,
9
  } from '@ant-design/icons';
10
  import { Radio } from 'antd';
 
11
  import FeedbackModal from './feedback-modal';
 
12
 
13
- export const AssistantGroupButton = () => {
14
- const { visible, hideModal, showModal } = useSetModalState();
 
 
 
 
 
 
 
 
 
 
15
 
16
  return (
17
  <>
18
  <Radio.Group size="small">
19
  <Radio.Button value="a">
20
- <CopyToClipboard text="xxx"></CopyToClipboard>
21
  </Radio.Button>
22
  <Radio.Button value="b">
23
  <SoundOutlined />
24
  </Radio.Button>
25
- <Radio.Button value="c">
26
  <LikeOutlined />
27
  </Radio.Button>
28
  <Radio.Button value="d" onClick={showModal}>
@@ -30,7 +41,12 @@ export const AssistantGroupButton = () => {
30
  </Radio.Button>
31
  </Radio.Group>
32
  {visible && (
33
- <FeedbackModal visible={visible} hideModal={hideModal}></FeedbackModal>
 
 
 
 
 
34
  )}
35
  </>
36
  );
 
1
  import CopyToClipboard from '@/components/copy-to-clipboard';
 
2
  import {
3
  DeleteOutlined,
4
  DislikeOutlined,
 
7
  SyncOutlined,
8
  } from '@ant-design/icons';
9
  import { Radio } from 'antd';
10
+ import { useCallback } from 'react';
11
  import FeedbackModal from './feedback-modal';
12
+ import { useSendFeedback } from './hooks';
13
 
14
+ interface IProps {
15
+ messageId: string;
16
+ content: string;
17
+ }
18
+
19
+ export const AssistantGroupButton = ({ messageId, content }: IProps) => {
20
+ const { visible, hideModal, showModal, onFeedbackOk, loading } =
21
+ useSendFeedback(messageId);
22
+
23
+ const handleLike = useCallback(() => {
24
+ onFeedbackOk({ thumbup: true });
25
+ }, [onFeedbackOk]);
26
 
27
  return (
28
  <>
29
  <Radio.Group size="small">
30
  <Radio.Button value="a">
31
+ <CopyToClipboard text={content}></CopyToClipboard>
32
  </Radio.Button>
33
  <Radio.Button value="b">
34
  <SoundOutlined />
35
  </Radio.Button>
36
+ <Radio.Button value="c" onClick={handleLike}>
37
  <LikeOutlined />
38
  </Radio.Button>
39
  <Radio.Button value="d" onClick={showModal}>
 
41
  </Radio.Button>
42
  </Radio.Group>
43
  {visible && (
44
+ <FeedbackModal
45
+ visible={visible}
46
+ hideModal={hideModal}
47
+ onOk={onFeedbackOk}
48
+ loading={loading}
49
+ ></FeedbackModal>
50
  )}
51
  </>
52
  );
web/src/components/message-item/hooks.ts ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useFeedback } from '@/hooks/chat-hooks';
2
+ import { useSetModalState } from '@/hooks/common-hooks';
3
+ import { IFeedbackRequestBody } from '@/interfaces/request/chat';
4
+ import { getMessagePureId } from '@/utils/chat';
5
+ import { useCallback } from 'react';
6
+
7
+ export const useSendFeedback = (messageId: string) => {
8
+ const { visible, hideModal, showModal } = useSetModalState();
9
+ const { feedback, loading } = useFeedback();
10
+
11
+ const onFeedbackOk = useCallback(
12
+ async (params: IFeedbackRequestBody) => {
13
+ const ret = await feedback({
14
+ ...params,
15
+ messageId: getMessagePureId(messageId),
16
+ });
17
+
18
+ if (ret === 0) {
19
+ hideModal();
20
+ }
21
+ },
22
+ [feedback, hideModal, messageId],
23
+ );
24
+
25
+ return {
26
+ loading,
27
+ onFeedbackOk,
28
+ visible,
29
+ hideModal,
30
+ showModal,
31
+ };
32
+ };
web/src/components/message-item/index.tsx CHANGED
@@ -2,7 +2,7 @@ import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
2
  import { MessageType } from '@/constants/chat';
3
  import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
4
  import { useSelectFileThumbnails } from '@/hooks/knowledge-hooks';
5
- import { IReference, Message } from '@/interfaces/database/chat';
6
  import { IChunk } from '@/interfaces/database/knowledge';
7
  import classNames from 'classnames';
8
  import { memo, useCallback, useEffect, useMemo, useState } from 'react';
@@ -11,19 +11,20 @@ import {
11
  useFetchDocumentInfosByIds,
12
  useFetchDocumentThumbnailsByIds,
13
  } from '@/hooks/document-hooks';
 
14
  import MarkdownContent from '@/pages/chat/markdown-content';
15
  import { getExtension, isImage } from '@/utils/document-util';
16
  import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
17
  import FileIcon from '../file-icon';
18
  import IndentedTreeModal from '../indented-tree/modal';
19
  import NewDocumentLink from '../new-document-link';
20
- // import { AssistantGroupButton, UserGroupButton } from './group-button';
21
  import styles from './index.less';
22
 
23
  const { Text } = Typography;
24
 
25
  interface IProps {
26
- item: Message;
27
  reference: IReference;
28
  loading?: boolean;
29
  nickname?: string;
@@ -36,7 +37,6 @@ const MessageItem = ({
36
  reference,
37
  loading = false,
38
  avatar = '',
39
- nickname = '',
40
  clickDocumentButton,
41
  }: IProps) => {
42
  const isAssistant = item.role === MessageType.Assistant;
@@ -111,13 +111,16 @@ const MessageItem = ({
111
  )}
112
  <Flex vertical gap={8} flex={1}>
113
  <Space>
114
- {/* {isAssistant ? (
115
- <AssistantGroupButton></AssistantGroupButton>
 
 
 
116
  ) : (
117
  <UserGroupButton></UserGroupButton>
118
- )} */}
119
 
120
- <b>{isAssistant ? '' : nickname}</b>
121
  </Space>
122
  <div
123
  className={
 
2
  import { MessageType } from '@/constants/chat';
3
  import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
4
  import { useSelectFileThumbnails } from '@/hooks/knowledge-hooks';
5
+ import { IReference } from '@/interfaces/database/chat';
6
  import { IChunk } from '@/interfaces/database/knowledge';
7
  import classNames from 'classnames';
8
  import { memo, useCallback, useEffect, useMemo, useState } from 'react';
 
11
  useFetchDocumentInfosByIds,
12
  useFetchDocumentThumbnailsByIds,
13
  } from '@/hooks/document-hooks';
14
+ import { IMessage } from '@/pages/chat/interface';
15
  import MarkdownContent from '@/pages/chat/markdown-content';
16
  import { getExtension, isImage } from '@/utils/document-util';
17
  import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
18
  import FileIcon from '../file-icon';
19
  import IndentedTreeModal from '../indented-tree/modal';
20
  import NewDocumentLink from '../new-document-link';
21
+ import { AssistantGroupButton, UserGroupButton } from './group-button';
22
  import styles from './index.less';
23
 
24
  const { Text } = Typography;
25
 
26
  interface IProps {
27
+ item: IMessage;
28
  reference: IReference;
29
  loading?: boolean;
30
  nickname?: string;
 
37
  reference,
38
  loading = false,
39
  avatar = '',
 
40
  clickDocumentButton,
41
  }: IProps) => {
42
  const isAssistant = item.role === MessageType.Assistant;
 
111
  )}
112
  <Flex vertical gap={8} flex={1}>
113
  <Space>
114
+ {isAssistant ? (
115
+ <AssistantGroupButton
116
+ messageId={item.id}
117
+ content={item.content}
118
+ ></AssistantGroupButton>
119
  ) : (
120
  <UserGroupButton></UserGroupButton>
121
+ )}
122
 
123
+ {/* <b>{isAssistant ? '' : nickname}</b> */}
124
  </Space>
125
  <div
126
  className={
web/src/hooks/chat-hooks.ts CHANGED
@@ -6,16 +6,16 @@ import {
6
  IToken,
7
  Message,
8
  } from '@/interfaces/database/chat';
 
9
  import i18n from '@/locales/config';
10
  import { IClientConversation, IMessage } from '@/pages/chat/interface';
11
  import chatService from '@/services/chat-service';
12
- import { isConversationIdExist } from '@/utils/chat';
13
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
14
  import { message } from 'antd';
15
  import dayjs, { Dayjs } from 'dayjs';
16
  import { useCallback, useMemo, useState } from 'react';
17
  import { useSearchParams } from 'umi';
18
- import { v4 as uuid } from 'uuid';
19
 
20
  //#region logic
21
 
@@ -218,7 +218,7 @@ export const useFetchNextConversation = () => {
218
  const messageList =
219
  conversation?.message?.map((x: Message | IMessage) => ({
220
  ...x,
221
- id: 'id' in x && x.id ? x.id : uuid(),
222
  })) ?? [];
223
 
224
  return { ...conversation, message: messageList };
@@ -292,6 +292,56 @@ export const useRemoveNextConversation = () => {
292
 
293
  return { data, loading, removeConversation: mutateAsync };
294
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  //#endregion
296
 
297
  // #region API provided for external calls
 
6
  IToken,
7
  Message,
8
  } from '@/interfaces/database/chat';
9
+ import { IFeedbackRequestBody } from '@/interfaces/request/chat';
10
  import i18n from '@/locales/config';
11
  import { IClientConversation, IMessage } from '@/pages/chat/interface';
12
  import chatService from '@/services/chat-service';
13
+ import { buildMessageUuid, isConversationIdExist } from '@/utils/chat';
14
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
15
  import { message } from 'antd';
16
  import dayjs, { Dayjs } from 'dayjs';
17
  import { useCallback, useMemo, useState } from 'react';
18
  import { useSearchParams } from 'umi';
 
19
 
20
  //#region logic
21
 
 
218
  const messageList =
219
  conversation?.message?.map((x: Message | IMessage) => ({
220
  ...x,
221
+ id: buildMessageUuid(x),
222
  })) ?? [];
223
 
224
  return { ...conversation, message: messageList };
 
292
 
293
  return { data, loading, removeConversation: mutateAsync };
294
  };
295
+
296
+ export const useDeleteMessage = () => {
297
+ // const queryClient = useQueryClient();
298
+ const { conversationId } = useGetChatSearchParams();
299
+
300
+ const {
301
+ data,
302
+ isPending: loading,
303
+ mutateAsync,
304
+ } = useMutation({
305
+ mutationKey: ['deleteMessage'],
306
+ mutationFn: async (messageId: string) => {
307
+ const { data } = await chatService.deleteMessage({
308
+ messageId,
309
+ conversationId,
310
+ });
311
+ if (data.retcode === 0) {
312
+ // queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] });
313
+ }
314
+ return data.retcode;
315
+ },
316
+ });
317
+
318
+ return { data, loading, deleteMessage: mutateAsync };
319
+ };
320
+
321
+ export const useFeedback = () => {
322
+ const { conversationId } = useGetChatSearchParams();
323
+
324
+ const {
325
+ data,
326
+ isPending: loading,
327
+ mutateAsync,
328
+ } = useMutation({
329
+ mutationKey: ['feedback'],
330
+ mutationFn: async (params: IFeedbackRequestBody) => {
331
+ const { data } = await chatService.thumbup({
332
+ ...params,
333
+ conversationId,
334
+ });
335
+ if (data.retcode === 0) {
336
+ message.success(i18n.t(`message.operated`));
337
+ }
338
+ return data.retcode;
339
+ },
340
+ });
341
+
342
+ return { data, loading, feedback: mutateAsync };
343
+ };
344
+
345
  //#endregion
346
 
347
  // #region API provided for external calls
web/src/interfaces/request/chat.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export interface IFeedbackRequestBody {
2
+ messageId?: string;
3
+ thumbup?: boolean;
4
+ feedback?: string;
5
+ }
web/src/pages/chat/hooks.ts CHANGED
@@ -421,6 +421,7 @@ export const useSendMessage = (
421
  messages: [
422
  ...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')),
423
  {
 
424
  role: MessageType.User,
425
  content: message,
426
  doc_ids: documentIds,
 
421
  messages: [
422
  ...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')),
423
  {
424
+ id: uuid(),
425
  role: MessageType.User,
426
  content: message,
427
  doc_ids: documentIds,
web/src/services/chat-service.ts CHANGED
@@ -20,6 +20,9 @@ const {
20
  getExternalConversation,
21
  completeExternalConversation,
22
  uploadAndParseExternal,
 
 
 
23
  } = api;
24
 
25
  const methods = {
@@ -91,6 +94,18 @@ const methods = {
91
  url: uploadAndParseExternal,
92
  method: 'post',
93
  },
 
 
 
 
 
 
 
 
 
 
 
 
94
  } as const;
95
 
96
  const chatService = registerServer<keyof typeof methods>(methods, request);
 
20
  getExternalConversation,
21
  completeExternalConversation,
22
  uploadAndParseExternal,
23
+ deleteMessage,
24
+ thumbup,
25
+ tts,
26
  } = api;
27
 
28
  const methods = {
 
94
  url: uploadAndParseExternal,
95
  method: 'post',
96
  },
97
+ deleteMessage: {
98
+ url: deleteMessage,
99
+ method: 'post',
100
+ },
101
+ thumbup: {
102
+ url: thumbup,
103
+ method: 'post',
104
+ },
105
+ tts: {
106
+ url: tts,
107
+ method: 'post',
108
+ },
109
  } as const;
110
 
111
  const chatService = registerServer<keyof typeof methods>(methods, request);
web/src/utils/api.ts CHANGED
@@ -63,6 +63,9 @@ export default {
63
  listConversation: `${api_host}/conversation/list`,
64
  removeConversation: `${api_host}/conversation/rm`,
65
  completeConversation: `${api_host}/conversation/completion`,
 
 
 
66
  // chat for external
67
  createToken: `${api_host}/api/new_token`,
68
  listToken: `${api_host}/api/token_list`,
 
63
  listConversation: `${api_host}/conversation/list`,
64
  removeConversation: `${api_host}/conversation/rm`,
65
  completeConversation: `${api_host}/conversation/completion`,
66
+ deleteMessage: `${api_host}/conversation/delete_msg`,
67
+ thumbup: `${api_host}/conversation/thumbup`,
68
+ tts: `${api_host}/conversation/tts`,
69
  // chat for external
70
  createToken: `${api_host}/api/new_token`,
71
  listToken: `${api_host}/api/token_list`,
web/src/utils/chat.ts CHANGED
@@ -1,5 +1,25 @@
1
- import { EmptyConversationId } from '@/constants/chat';
 
 
 
2
 
3
  export const isConversationIdExist = (conversationId: string) => {
4
  return conversationId !== EmptyConversationId && conversationId !== '';
5
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { EmptyConversationId, MessageType } from '@/constants/chat';
2
+ import { Message } from '@/interfaces/database/chat';
3
+ import { IMessage } from '@/pages/chat/interface';
4
+ import { v4 as uuid } from 'uuid';
5
 
6
  export const isConversationIdExist = (conversationId: string) => {
7
  return conversationId !== EmptyConversationId && conversationId !== '';
8
  };
9
+
10
+ export const buildMessageUuid = (message: Message | IMessage) => {
11
+ if ('id' in message && message.id) {
12
+ return message.role === MessageType.User
13
+ ? `${MessageType.User}_${message.id}`
14
+ : `${MessageType.Assistant}_${message.id}`;
15
+ }
16
+ return uuid();
17
+ };
18
+
19
+ export const getMessagePureId = (id: string) => {
20
+ const strings = id.split('_');
21
+ if (strings.length > 0) {
22
+ return strings.at(-1);
23
+ }
24
+ return id;
25
+ };