balibabu commited on
Commit
d35be0d
·
1 Parent(s): aeb6dbc

feat: Delete the file from the upload control of the message input box #1880 (#1946)

Browse files

### What problem does this PR solve?

feat: Delete the file from the upload control of the message input box
#1880

### Type of change


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

web/src/assets/svg/llm/perfx-cloud.svg CHANGED
web/src/components/indented-tree/modal.tsx CHANGED
@@ -15,7 +15,7 @@ const IndentedTreeModal = ({
15
 
16
  return (
17
  <Modal
18
- title={t('chunk.graph')}
19
  open={visible}
20
  onCancel={hideModal}
21
  width={'90vw'}
 
15
 
16
  return (
17
  <Modal
18
+ title={t('chunk.mind')}
19
  open={visible}
20
  onCancel={hideModal}
21
  width={'90vw'}
web/src/components/message-input/index.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import { Authorization } from '@/constants/authorization';
2
  import { useTranslate } from '@/hooks/common-hooks';
 
3
  import { getAuthorization } from '@/utils/authorization-util';
4
  import { PlusOutlined } from '@ant-design/icons';
5
  import type { GetProp, UploadFile } from 'antd';
@@ -14,7 +15,7 @@ interface IProps {
14
  value: string;
15
  sendDisabled: boolean;
16
  sendLoading: boolean;
17
- onPressEnter(documentIds: string[]): Promise<any>;
18
  onInputChange: ChangeEventHandler<HTMLInputElement>;
19
  conversationId: string;
20
  }
@@ -37,50 +38,41 @@ const MessageInput = ({
37
  conversationId,
38
  }: IProps) => {
39
  const { t } = useTranslate('chat');
 
40
 
41
- const [fileList, setFileList] = useState<UploadFile[]>([
42
- // {
43
- // uid: '-1',
44
- // name: 'image.png',
45
- // status: 'done',
46
- // url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
47
- // },
48
- // {
49
- // uid: '-xxx',
50
- // percent: 50,
51
- // name: 'image.png',
52
- // status: 'uploading',
53
- // url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
54
- // },
55
- // {
56
- // uid: '-5',
57
- // name: 'image.png',
58
- // status: 'error',
59
- // },
60
- ]);
61
 
62
  const handlePreview = async (file: UploadFile) => {
63
  if (!file.url && !file.preview) {
64
  file.preview = await getBase64(file.originFileObj as FileType);
65
  }
66
-
67
- // setPreviewImage(file.url || (file.preview as string));
68
- // setPreviewOpen(true);
69
  };
70
 
71
  const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
72
  console.log('🚀 ~ newFileList:', newFileList);
73
  setFileList(newFileList);
74
  };
 
75
 
76
  const handlePressEnter = useCallback(async () => {
 
77
  const ids = fileList.reduce((pre, cur) => {
78
  return pre.concat(get(cur, 'response.data', []));
79
  }, []);
80
 
81
- await onPressEnter(ids);
82
  setFileList([]);
83
- }, [fileList, onPressEnter]);
 
 
 
 
 
 
 
 
 
 
84
 
85
  const uploadButton = (
86
  <button style={{ border: 0, background: 'none' }} type="button">
@@ -101,7 +93,7 @@ const MessageInput = ({
101
  type="primary"
102
  onClick={handlePressEnter}
103
  loading={sendLoading}
104
- disabled={sendDisabled}
105
  >
106
  {t('send')}
107
  </Button>
@@ -119,6 +111,7 @@ const MessageInput = ({
119
  headers={{ [Authorization]: getAuthorization() }}
120
  data={{ conversation_id: conversationId }}
121
  method="post"
 
122
  >
123
  {fileList.length >= 8 ? null : uploadButton}
124
  </Upload>
 
1
  import { Authorization } from '@/constants/authorization';
2
  import { useTranslate } from '@/hooks/common-hooks';
3
+ import { useRemoveNextDocument } from '@/hooks/document-hooks';
4
  import { getAuthorization } from '@/utils/authorization-util';
5
  import { PlusOutlined } from '@ant-design/icons';
6
  import type { GetProp, UploadFile } from 'antd';
 
15
  value: string;
16
  sendDisabled: boolean;
17
  sendLoading: boolean;
18
+ onPressEnter(documentIds: string[]): void;
19
  onInputChange: ChangeEventHandler<HTMLInputElement>;
20
  conversationId: string;
21
  }
 
38
  conversationId,
39
  }: IProps) => {
40
  const { t } = useTranslate('chat');
41
+ const { removeDocument } = useRemoveNextDocument();
42
 
43
+ const [fileList, setFileList] = useState<UploadFile[]>([]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  const handlePreview = async (file: UploadFile) => {
46
  if (!file.url && !file.preview) {
47
  file.preview = await getBase64(file.originFileObj as FileType);
48
  }
 
 
 
49
  };
50
 
51
  const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
52
  console.log('🚀 ~ newFileList:', newFileList);
53
  setFileList(newFileList);
54
  };
55
+ const isUploadingFile = fileList.some((x) => x.status === 'uploading');
56
 
57
  const handlePressEnter = useCallback(async () => {
58
+ if (isUploadingFile) return;
59
  const ids = fileList.reduce((pre, cur) => {
60
  return pre.concat(get(cur, 'response.data', []));
61
  }, []);
62
 
63
+ onPressEnter(ids);
64
  setFileList([]);
65
+ }, [fileList, onPressEnter, isUploadingFile]);
66
+
67
+ const handleRemove = useCallback(
68
+ (file: UploadFile) => {
69
+ const ids = get(file, 'response.data', []);
70
+ if (ids.length) {
71
+ removeDocument(ids[0]);
72
+ }
73
+ },
74
+ [removeDocument],
75
+ );
76
 
77
  const uploadButton = (
78
  <button style={{ border: 0, background: 'none' }} type="button">
 
93
  type="primary"
94
  onClick={handlePressEnter}
95
  loading={sendLoading}
96
+ disabled={sendDisabled || isUploadingFile}
97
  >
98
  {t('send')}
99
  </Button>
 
111
  headers={{ [Authorization]: getAuthorization() }}
112
  data={{ conversation_id: conversationId }}
113
  method="post"
114
+ onRemove={handleRemove}
115
  >
116
  {fileList.length >= 8 ? null : uploadButton}
117
  </Upload>
web/src/components/message-item/index.less CHANGED
@@ -7,7 +7,8 @@
7
  width: 80%;
8
  }
9
  .messageItemSectionRight {
10
- width: 80%;
 
11
  }
12
  .messageItemContent {
13
  display: inline-flex;
 
7
  width: 80%;
8
  }
9
  .messageItemSectionRight {
10
+ // width: 80%;
11
+ // max-width: 50vw;
12
  }
13
  .messageItemContent {
14
  display: inline-flex;
web/src/components/message-item/index.tsx CHANGED
@@ -7,15 +7,20 @@ import { IChunk } from '@/interfaces/database/knowledge';
7
  import classNames from 'classnames';
8
  import { memo, useCallback, useEffect, useMemo, useState } from 'react';
9
 
10
- import { useFetchDocumentInfosByIds } from '@/hooks/document-hooks';
 
 
 
11
  import MarkdownContent from '@/pages/chat/markdown-content';
12
  import { getExtension, isImage } from '@/utils/document-util';
13
- import { Avatar, Button, Flex, List } from 'antd';
14
  import IndentedTreeModal from '../indented-tree/modal';
15
  import NewDocumentLink from '../new-document-link';
16
  import SvgIcon from '../svg-icon';
17
  import styles from './index.less';
18
 
 
 
19
  interface IProps {
20
  item: Message;
21
  reference: IReference;
@@ -38,7 +43,8 @@ const MessageItem = ({
38
  const { t } = useTranslate('chat');
39
  const fileThumbnails = useSelectFileThumbnails();
40
  const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
41
- console.log('🚀 ~ documentList:', documentList);
 
42
  const { visible, hideModal, showModal } = useSetModalState();
43
  const [clickedDocumentId, setClickedDocumentId] = useState('');
44
 
@@ -66,8 +72,12 @@ const MessageItem = ({
66
  const ids = item?.doc_ids ?? [];
67
  if (ids.length) {
68
  setDocumentIds(ids);
 
 
 
 
69
  }
70
- }, [item.doc_ids, setDocumentIds]);
71
 
72
  return (
73
  <div
@@ -117,6 +127,7 @@ const MessageItem = ({
117
  dataSource={referenceDocumentList}
118
  renderItem={(item) => {
119
  const fileThumbnail = fileThumbnails[item.doc_id];
 
120
  const fileExtension = getExtension(item.doc_name);
121
  return (
122
  <List.Item>
@@ -151,7 +162,8 @@ const MessageItem = ({
151
  bordered
152
  dataSource={documentList}
153
  renderItem={(item) => {
154
- const fileThumbnail = fileThumbnails[item.id];
 
155
  const fileExtension = getExtension(item.name);
156
  return (
157
  <List.Item>
@@ -181,7 +193,12 @@ const MessageItem = ({
181
  type={'text'}
182
  onClick={handleUserDocumentClick(item.id)}
183
  >
184
- {item.name}
 
 
 
 
 
185
  </Button>
186
  )}
187
  </Flex>
 
7
  import classNames from 'classnames';
8
  import { memo, useCallback, useEffect, useMemo, useState } from 'react';
9
 
10
+ 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, Typography } from 'antd';
17
  import IndentedTreeModal from '../indented-tree/modal';
18
  import NewDocumentLink from '../new-document-link';
19
  import SvgIcon from '../svg-icon';
20
  import styles from './index.less';
21
 
22
+ const { Text } = Typography;
23
+
24
  interface IProps {
25
  item: Message;
26
  reference: IReference;
 
43
  const { t } = useTranslate('chat');
44
  const fileThumbnails = useSelectFileThumbnails();
45
  const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
46
+ const { data: documentThumbnails, setDocumentIds: setIds } =
47
+ useFetchDocumentThumbnailsByIds();
48
  const { visible, hideModal, showModal } = useSetModalState();
49
  const [clickedDocumentId, setClickedDocumentId] = useState('');
50
 
 
72
  const ids = item?.doc_ids ?? [];
73
  if (ids.length) {
74
  setDocumentIds(ids);
75
+ const documentIds = ids.filter((x) => !(x in fileThumbnails));
76
+ if (documentIds.length) {
77
+ setIds(documentIds);
78
+ }
79
  }
80
+ }, [item.doc_ids, setDocumentIds, setIds, fileThumbnails]);
81
 
82
  return (
83
  <div
 
127
  dataSource={referenceDocumentList}
128
  renderItem={(item) => {
129
  const fileThumbnail = fileThumbnails[item.doc_id];
130
+
131
  const fileExtension = getExtension(item.doc_name);
132
  return (
133
  <List.Item>
 
162
  bordered
163
  dataSource={documentList}
164
  renderItem={(item) => {
165
+ const fileThumbnail =
166
+ documentThumbnails[item.id] || fileThumbnails[item.id];
167
  const fileExtension = getExtension(item.name);
168
  return (
169
  <List.Item>
 
193
  type={'text'}
194
  onClick={handleUserDocumentClick(item.id)}
195
  >
196
+ <Text
197
+ style={{ maxWidth: '40vw' }}
198
+ ellipsis={{ tooltip: item.name }}
199
+ >
200
+ {item.name}
201
+ </Text>
202
  </Button>
203
  )}
204
  </Flex>
web/src/hooks/document-hooks.ts CHANGED
@@ -4,7 +4,7 @@ import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
4
  import kbService from '@/services/knowledge-service';
5
  import { api_host } from '@/utils/api';
6
  import { buildChunkHighlights } from '@/utils/document-util';
7
- import { useQuery } from '@tanstack/react-query';
8
  import { UploadFile } from 'antd';
9
  import { useCallback, useMemo, useState } from 'react';
10
  import { IHighlight } from 'react-pdf-highlighter';
@@ -278,15 +278,38 @@ export const useFetchDocumentInfosByIds = () => {
278
 
279
  export const useFetchDocumentThumbnailsByIds = () => {
280
  const [ids, setDocumentIds] = useState<string[]>([]);
281
- const { data } = useQuery({
282
  queryKey: ['fetchDocumentThumbnails', ids],
283
- initialData: [],
 
284
  queryFn: async () => {
285
  const { data } = await kbService.document_thumbnails({ doc_ids: ids });
 
 
 
 
 
 
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  return data;
288
  },
289
  });
290
 
291
- return { data, setDocumentIds };
292
  };
 
4
  import kbService from '@/services/knowledge-service';
5
  import { api_host } from '@/utils/api';
6
  import { buildChunkHighlights } from '@/utils/document-util';
7
+ import { useMutation, useQuery } from '@tanstack/react-query';
8
  import { UploadFile } from 'antd';
9
  import { useCallback, useMemo, useState } from 'react';
10
  import { IHighlight } from 'react-pdf-highlighter';
 
278
 
279
  export const useFetchDocumentThumbnailsByIds = () => {
280
  const [ids, setDocumentIds] = useState<string[]>([]);
281
+ const { data } = useQuery<Record<string, string>>({
282
  queryKey: ['fetchDocumentThumbnails', ids],
283
+ enabled: ids.length > 0,
284
+ initialData: {},
285
  queryFn: async () => {
286
  const { data } = await kbService.document_thumbnails({ doc_ids: ids });
287
+ if (data.retcode === 0) {
288
+ return data.data;
289
+ }
290
+ return {};
291
+ },
292
+ });
293
 
294
+ return { data, setDocumentIds };
295
+ };
296
+
297
+ export const useRemoveNextDocument = () => {
298
+ // const queryClient = useQueryClient();
299
+ const {
300
+ data,
301
+ isPending: loading,
302
+ mutateAsync,
303
+ } = useMutation({
304
+ mutationKey: ['removeDocument'],
305
+ mutationFn: async (documentId: string) => {
306
+ const data = await kbService.document_rm({ doc_id: documentId });
307
+ // if (data.retcode === 0) {
308
+ // queryClient.invalidateQueries({ queryKey: ['fetchFlowList'] });
309
+ // }
310
  return data;
311
  },
312
  });
313
 
314
+ return { data, loading, removeDocument: mutateAsync };
315
  };
web/src/pages/chat/chat-container/index.tsx CHANGED
@@ -80,24 +80,6 @@ const ChatContainer = () => {
80
  onPressEnter={handlePressEnter}
81
  conversationId={conversation.id}
82
  ></MessageInput>
83
- {/* <Input
84
- size="large"
85
- placeholder={t('sendPlaceholder')}
86
- value={value}
87
- disabled={disabled}
88
- suffix={
89
- <Button
90
- type="primary"
91
- onClick={handlePressEnter}
92
- loading={sendLoading}
93
- disabled={sendDisabled}
94
- >
95
- {t('send')}
96
- </Button>
97
- }
98
- onPressEnter={handlePressEnter}
99
- onChange={handleInputChange}
100
- /> */}
101
  </Flex>
102
  <Drawer
103
  title="Document Previewer"
 
80
  onPressEnter={handlePressEnter}
81
  conversationId={conversation.id}
82
  ></MessageInput>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  </Flex>
84
  <Drawer
85
  title="Document Previewer"
web/src/pages/chat/hooks.ts CHANGED
@@ -19,7 +19,12 @@ import {
19
  } from '@/hooks/common-hooks';
20
  import { useSendMessageWithSse } from '@/hooks/logic-hooks';
21
  import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks';
22
- import { IAnswer, IConversation, IDialog } from '@/interfaces/database/chat';
 
 
 
 
 
23
  import { IChunk } from '@/interfaces/database/knowledge';
24
  import { getFileExtension } from '@/utils';
25
  import omit from 'lodash/omit';
@@ -379,7 +384,7 @@ export const useSelectCurrentConversation = () => {
379
  const { conversationId, dialogId } = useGetChatSearchParams();
380
 
381
  const addNewestConversation = useCallback(
382
- (message: string, answer: string = '') => {
383
  setCurrentConversation((pre) => {
384
  return {
385
  ...pre,
@@ -387,7 +392,8 @@ export const useSelectCurrentConversation = () => {
387
  ...pre.message,
388
  {
389
  role: MessageType.User,
390
- content: message,
 
391
  id: uuid(),
392
  } as IMessage,
393
  {
@@ -535,7 +541,7 @@ export const useHandleMessageInputChange = () => {
535
 
536
  export const useSendMessage = (
537
  conversation: IClientConversation,
538
- addNewestConversation: (message: string, answer?: string) => void,
539
  removeLatestMessage: () => void,
540
  addNewestAnswer: (answer: IAnswer) => void,
541
  ) => {
@@ -589,12 +595,12 @@ export const useSendMessage = (
589
  const handleSendMessage = useCallback(
590
  async (message: string, documentIds: string[]) => {
591
  if (conversationId !== '') {
592
- return sendMessage(message, documentIds);
593
  } else {
594
  const data = await setConversation(message);
595
  if (data.retcode === 0) {
596
  const id = data.data.id;
597
- return sendMessage(message, documentIds, id);
598
  }
599
  }
600
  },
@@ -616,15 +622,14 @@ export const useSendMessage = (
616
  }, [setDone, conversationId]);
617
 
618
  const handlePressEnter = useCallback(
619
- async (documentIds: string[]) => {
620
  if (trim(value) === '') return;
621
- let ret;
 
622
  if (done) {
623
  setValue('');
624
- ret = await handleSendMessage(value.trim(), documentIds);
625
  }
626
- addNewestConversation(value);
627
- return ret;
628
  },
629
  [addNewestConversation, handleSendMessage, done, setValue, value],
630
  );
 
19
  } from '@/hooks/common-hooks';
20
  import { useSendMessageWithSse } from '@/hooks/logic-hooks';
21
  import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks';
22
+ import {
23
+ IAnswer,
24
+ IConversation,
25
+ IDialog,
26
+ Message,
27
+ } from '@/interfaces/database/chat';
28
  import { IChunk } from '@/interfaces/database/knowledge';
29
  import { getFileExtension } from '@/utils';
30
  import omit from 'lodash/omit';
 
384
  const { conversationId, dialogId } = useGetChatSearchParams();
385
 
386
  const addNewestConversation = useCallback(
387
+ (message: Partial<Message>, answer: string = '') => {
388
  setCurrentConversation((pre) => {
389
  return {
390
  ...pre,
 
392
  ...pre.message,
393
  {
394
  role: MessageType.User,
395
+ content: message.content,
396
+ doc_ids: message.doc_ids,
397
  id: uuid(),
398
  } as IMessage,
399
  {
 
541
 
542
  export const useSendMessage = (
543
  conversation: IClientConversation,
544
+ addNewestConversation: (message: Partial<Message>, answer?: string) => void,
545
  removeLatestMessage: () => void,
546
  addNewestAnswer: (answer: IAnswer) => void,
547
  ) => {
 
595
  const handleSendMessage = useCallback(
596
  async (message: string, documentIds: string[]) => {
597
  if (conversationId !== '') {
598
+ sendMessage(message, documentIds);
599
  } else {
600
  const data = await setConversation(message);
601
  if (data.retcode === 0) {
602
  const id = data.data.id;
603
+ sendMessage(message, documentIds, id);
604
  }
605
  }
606
  },
 
622
  }, [setDone, conversationId]);
623
 
624
  const handlePressEnter = useCallback(
625
+ (documentIds: string[]) => {
626
  if (trim(value) === '') return;
627
+
628
+ addNewestConversation({ content: value, doc_ids: documentIds });
629
  if (done) {
630
  setValue('');
631
+ handleSendMessage(value.trim(), documentIds);
632
  }
 
 
633
  },
634
  [addNewestConversation, handleSendMessage, done, setValue, value],
635
  );