balibabu commited on
Commit
1ea3843
·
1 Parent(s): e92bd36

feat: Create a conversation before uploading files in it #1880 (#2057)

Browse files

### What problem does this PR solve?

feat: Create a conversation before uploading files in it #1880

### Type of change


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

web/src/components/message-input/index.tsx CHANGED
@@ -1,11 +1,10 @@
1
- import { Authorization } from '@/constants/authorization';
2
  import { useTranslate } from '@/hooks/common-hooks';
3
  import {
4
  useDeleteDocument,
5
  useFetchDocumentInfosByIds,
6
  useRemoveNextDocument,
 
7
  } from '@/hooks/document-hooks';
8
- import { getAuthorization } from '@/utils/authorization-util';
9
  import { getExtension } from '@/utils/document-util';
10
  import { formatBytes } from '@/utils/file-util';
11
  import {
@@ -28,7 +27,14 @@ import {
28
  } from 'antd';
29
  import classNames from 'classnames';
30
  import get from 'lodash/get';
31
- import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
 
 
 
 
 
 
 
32
  import FileIcon from '../file-icon';
33
  import SvgIcon from '../svg-icon';
34
  import styles from './index.less';
@@ -64,9 +70,10 @@ interface IProps {
64
  onPressEnter(documentIds: string[]): void;
65
  onInputChange: ChangeEventHandler<HTMLInputElement>;
66
  conversationId: string;
67
- uploadUrl?: string;
68
  isShared?: boolean;
69
  showUploadIcon?: boolean;
 
70
  }
71
 
72
  const getBase64 = (file: FileType): Promise<string> =>
@@ -87,12 +94,15 @@ const MessageInput = ({
87
  onInputChange,
88
  conversationId,
89
  showUploadIcon = true,
90
- uploadUrl = '/v1/document/upload_and_parse',
 
91
  }: IProps) => {
92
  const { t } = useTranslate('chat');
93
  const { removeDocument } = useRemoveNextDocument();
94
  const { deleteDocument } = useDeleteDocument();
95
  const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds();
 
 
96
 
97
  const [fileList, setFileList] = useState<UploadFile[]>([]);
98
 
@@ -102,9 +112,44 @@ const MessageInput = ({
102
  }
103
  };
104
 
105
- const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
106
- setFileList(newFileList);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  };
 
108
  const isUploadingFile = fileList.some((x) => x.status === 'uploading');
109
 
110
  const handlePressEnter = useCallback(async () => {
@@ -150,6 +195,16 @@ const MessageInput = ({
150
  setDocumentIds(ids);
151
  }, [fileList, setDocumentIds]);
152
 
 
 
 
 
 
 
 
 
 
 
153
  return (
154
  <Flex gap={20} vertical className={styles.messageInputWrapper}>
155
  <Input
@@ -160,18 +215,22 @@ const MessageInput = ({
160
  className={classNames({ [styles.inputWrapper]: fileList.length === 0 })}
161
  suffix={
162
  <Space>
163
- {conversationId && showUploadIcon && (
164
  <Upload
165
- action={uploadUrl}
166
- fileList={fileList}
167
  onPreview={handlePreview}
168
  onChange={handleChange}
169
- multiple
170
- headers={{ [Authorization]: getAuthorization() }}
171
- data={{ conversation_id: conversationId }}
172
- method="post"
173
  onRemove={handleRemove}
174
  showUploadList={false}
 
 
 
 
175
  >
176
  <Button
177
  type={'text'}
@@ -209,8 +268,10 @@ const MessageInput = ({
209
  dataSource={fileList}
210
  className={styles.listWrapper}
211
  renderItem={(item) => {
212
- const fileExtension = getExtension(item.name);
213
  const id = getFileId(item);
 
 
 
214
 
215
  return (
216
  <List.Item>
@@ -228,14 +289,14 @@ const MessageInput = ({
228
  // width={30}
229
  ></InfoCircleOutlined>
230
  ) : (
231
- <FileIcon id={id} name={item.name}></FileIcon>
232
  )}
233
  <Flex vertical style={{ width: '90%' }}>
234
  <Text
235
- ellipsis={{ tooltip: item.name }}
236
  className={styles.nameText}
237
  >
238
- <b> {item.name}</b>
239
  </Text>
240
  {isUploadError(item) ? (
241
  t('uploadFailed')
@@ -275,4 +336,4 @@ const MessageInput = ({
275
  );
276
  };
277
 
278
- export default MessageInput;
 
 
1
  import { useTranslate } from '@/hooks/common-hooks';
2
  import {
3
  useDeleteDocument,
4
  useFetchDocumentInfosByIds,
5
  useRemoveNextDocument,
6
+ useUploadAndParseDocument,
7
  } from '@/hooks/document-hooks';
 
8
  import { getExtension } from '@/utils/document-util';
9
  import { formatBytes } from '@/utils/file-util';
10
  import {
 
27
  } from 'antd';
28
  import classNames from 'classnames';
29
  import get from 'lodash/get';
30
+ import {
31
+ ChangeEventHandler,
32
+ memo,
33
+ useCallback,
34
+ useEffect,
35
+ useRef,
36
+ useState,
37
+ } from 'react';
38
  import FileIcon from '../file-icon';
39
  import SvgIcon from '../svg-icon';
40
  import styles from './index.less';
 
70
  onPressEnter(documentIds: string[]): void;
71
  onInputChange: ChangeEventHandler<HTMLInputElement>;
72
  conversationId: string;
73
+ uploadMethod?: string;
74
  isShared?: boolean;
75
  showUploadIcon?: boolean;
76
+ createConversationBeforeUploadDocument?(message: string): Promise<any>;
77
  }
78
 
79
  const getBase64 = (file: FileType): Promise<string> =>
 
94
  onInputChange,
95
  conversationId,
96
  showUploadIcon = true,
97
+ createConversationBeforeUploadDocument,
98
+ uploadMethod = 'upload_and_parse',
99
  }: IProps) => {
100
  const { t } = useTranslate('chat');
101
  const { removeDocument } = useRemoveNextDocument();
102
  const { deleteDocument } = useDeleteDocument();
103
  const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds();
104
+ const { uploadAndParseDocument } = useUploadAndParseDocument(uploadMethod);
105
+ const conversationIdRef = useRef(conversationId);
106
 
107
  const [fileList, setFileList] = useState<UploadFile[]>([]);
108
 
 
112
  }
113
  };
114
 
115
+ const handleChange: UploadProps['onChange'] = async ({
116
+ // fileList: newFileList,
117
+ file,
118
+ }) => {
119
+ let nextConversationId: string = conversationId;
120
+ if (createConversationBeforeUploadDocument && !conversationId) {
121
+ const creatingRet = await createConversationBeforeUploadDocument(
122
+ file.name,
123
+ );
124
+ if (creatingRet.retcode === 0) {
125
+ nextConversationId = creatingRet.data.id;
126
+ }
127
+ }
128
+ setFileList((list) => {
129
+ list.push({
130
+ ...file,
131
+ status: 'uploading',
132
+ originFileObj: file as any,
133
+ });
134
+ return [...list];
135
+ });
136
+ const ret = await uploadAndParseDocument({
137
+ conversationId: nextConversationId,
138
+ fileList: [file],
139
+ });
140
+ setFileList((list) => {
141
+ const nextList = list.filter((x) => x.uid !== file.uid);
142
+ nextList.push({
143
+ ...file,
144
+ originFileObj: file as any,
145
+ response: ret,
146
+ percent: 100,
147
+ status: ret?.retcode === 0 ? 'done' : 'error',
148
+ });
149
+ return nextList;
150
+ });
151
  };
152
+
153
  const isUploadingFile = fileList.some((x) => x.status === 'uploading');
154
 
155
  const handlePressEnter = useCallback(async () => {
 
195
  setDocumentIds(ids);
196
  }, [fileList, setDocumentIds]);
197
 
198
+ useEffect(() => {
199
+ if (
200
+ conversationIdRef.current &&
201
+ conversationId !== conversationIdRef.current
202
+ ) {
203
+ setFileList([]);
204
+ }
205
+ conversationIdRef.current = conversationId;
206
+ }, [conversationId, setFileList]);
207
+
208
  return (
209
  <Flex gap={20} vertical className={styles.messageInputWrapper}>
210
  <Input
 
215
  className={classNames({ [styles.inputWrapper]: fileList.length === 0 })}
216
  suffix={
217
  <Space>
218
+ {showUploadIcon && (
219
  <Upload
220
+ // action={uploadUrl}
221
+ // fileList={fileList}
222
  onPreview={handlePreview}
223
  onChange={handleChange}
224
+ multiple={false}
225
+ // headers={{ [Authorization]: getAuthorization() }}
226
+ // data={{ conversation_id: conversationId }}
227
+ // method="post"
228
  onRemove={handleRemove}
229
  showUploadList={false}
230
+ beforeUpload={(file, fileList) => {
231
+ console.log('🚀 ~ beforeUpload:', fileList);
232
+ return false;
233
+ }}
234
  >
235
  <Button
236
  type={'text'}
 
268
  dataSource={fileList}
269
  className={styles.listWrapper}
270
  renderItem={(item) => {
 
271
  const id = getFileId(item);
272
+ const documentInfo = getDocumentInfoById(id);
273
+ const fileExtension = getExtension(documentInfo?.name ?? '');
274
+ const fileName = item.originFileObj?.name ?? '';
275
 
276
  return (
277
  <List.Item>
 
289
  // width={30}
290
  ></InfoCircleOutlined>
291
  ) : (
292
+ <FileIcon id={id} name={fileName}></FileIcon>
293
  )}
294
  <Flex vertical style={{ width: '90%' }}>
295
  <Text
296
+ ellipsis={{ tooltip: fileName }}
297
  className={styles.nameText}
298
  >
299
+ <b> {fileName}</b>
300
  </Text>
301
  {isUploadError(item) ? (
302
  t('uploadFailed')
 
336
  );
337
  };
338
 
339
+ export default memo(MessageInput);
web/src/hooks/chat-hooks.ts CHANGED
@@ -125,6 +125,23 @@ export const useUpdateConversation = () => {
125
  return updateConversation;
126
  };
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  export const useSetDialog = () => {
129
  const dispatch = useDispatch();
130
 
 
125
  return updateConversation;
126
  };
127
 
128
+ export const useUpdateNextConversation = () => {
129
+ const {
130
+ data,
131
+ isPending: loading,
132
+ mutateAsync,
133
+ } = useMutation({
134
+ mutationKey: ['updateConversation'],
135
+ mutationFn: async (params: Record<string, any>) => {
136
+ const { data } = await chatService.setConversation(params);
137
+
138
+ return data;
139
+ },
140
+ });
141
+
142
+ return { data, loading, updateConversation: mutateAsync };
143
+ };
144
+
145
  export const useSetDialog = () => {
146
  const dispatch = useDispatch();
147
 
web/src/hooks/document-hooks.ts CHANGED
@@ -1,6 +1,7 @@
1
  import { IDocumentInfo } from '@/interfaces/database/document';
2
  import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
3
  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';
@@ -333,3 +334,34 @@ export const useDeleteDocument = () => {
333
 
334
  return { data, loading, deleteDocument: mutateAsync };
335
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { IDocumentInfo } from '@/interfaces/database/document';
2
  import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
3
  import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
4
+ import chatService from '@/services/chat-service';
5
  import kbService from '@/services/knowledge-service';
6
  import { api_host } from '@/utils/api';
7
  import { buildChunkHighlights } from '@/utils/document-util';
 
334
 
335
  return { data, loading, deleteDocument: mutateAsync };
336
  };
337
+
338
+ export const useUploadAndParseDocument = (uploadMethod: string) => {
339
+ const {
340
+ data,
341
+ isPending: loading,
342
+ mutateAsync,
343
+ } = useMutation({
344
+ mutationKey: ['uploadAndParseDocument'],
345
+ mutationFn: async ({
346
+ conversationId,
347
+ fileList,
348
+ }: {
349
+ conversationId: string;
350
+ fileList: UploadFile[];
351
+ }) => {
352
+ const formData = new FormData();
353
+ formData.append('conversation_id', conversationId);
354
+ fileList.forEach((file: UploadFile) => {
355
+ formData.append('file', file as any);
356
+ });
357
+ if (uploadMethod === 'upload_and_parse') {
358
+ const data = await kbService.upload_and_parse(formData);
359
+ return data?.data;
360
+ }
361
+ const data = await chatService.uploadAndParseExternal(formData);
362
+ return data?.data;
363
+ },
364
+ });
365
+
366
+ return { data, loading, uploadAndParseDocument: mutateAsync };
367
+ };
web/src/pages/chat/chat-container/index.tsx CHANGED
@@ -4,6 +4,7 @@ import { MessageType } from '@/constants/chat';
4
  import { Drawer, Flex, Spin } from 'antd';
5
  import {
6
  useClickDrawer,
 
7
  useFetchConversationOnMount,
8
  useGetFileIcon,
9
  useGetSendButtonDisabled,
@@ -24,6 +25,7 @@ const ChatContainer = () => {
24
  addNewestConversation,
25
  removeLatestMessage,
26
  addNewestAnswer,
 
27
  } = useFetchConversationOnMount();
28
  const {
29
  handleInputChange,
@@ -43,6 +45,8 @@ const ChatContainer = () => {
43
  useGetFileIcon();
44
  const loading = useSelectConversationLoading();
45
  const { data: userInfo } = useFetchUserInfo();
 
 
46
 
47
  return (
48
  <>
@@ -78,7 +82,10 @@ const ChatContainer = () => {
78
  value={value}
79
  onInputChange={handleInputChange}
80
  onPressEnter={handlePressEnter}
81
- conversationId={conversation.id}
 
 
 
82
  ></MessageInput>
83
  </Flex>
84
  <Drawer
 
4
  import { Drawer, Flex, Spin } from 'antd';
5
  import {
6
  useClickDrawer,
7
+ useCreateConversationBeforeUploadDocument,
8
  useFetchConversationOnMount,
9
  useGetFileIcon,
10
  useGetSendButtonDisabled,
 
25
  addNewestConversation,
26
  removeLatestMessage,
27
  addNewestAnswer,
28
+ conversationId,
29
  } = useFetchConversationOnMount();
30
  const {
31
  handleInputChange,
 
45
  useGetFileIcon();
46
  const loading = useSelectConversationLoading();
47
  const { data: userInfo } = useFetchUserInfo();
48
+ const { createConversationBeforeUploadDocument } =
49
+ useCreateConversationBeforeUploadDocument();
50
 
51
  return (
52
  <>
 
82
  value={value}
83
  onInputChange={handleInputChange}
84
  onPressEnter={handlePressEnter}
85
+ conversationId={conversationId}
86
+ createConversationBeforeUploadDocument={
87
+ createConversationBeforeUploadDocument
88
+ }
89
  ></MessageInput>
90
  </Flex>
91
  <Drawer
web/src/pages/chat/hooks.ts CHANGED
@@ -520,6 +520,7 @@ export const useFetchConversationOnMount = () => {
520
  ref,
521
  removeLatestMessage,
522
  addNewestAnswer,
 
523
  };
524
  };
525
 
@@ -769,4 +770,28 @@ export const useGetSendButtonDisabled = () => {
769
  export const useSendButtonDisabled = (value: string) => {
770
  return trim(value) === '';
771
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
  //#endregion
 
520
  ref,
521
  removeLatestMessage,
522
  addNewestAnswer,
523
+ conversationId,
524
  };
525
  };
526
 
 
770
  export const useSendButtonDisabled = (value: string) => {
771
  return trim(value) === '';
772
  };
773
+
774
+ export const useCreateConversationBeforeUploadDocument = () => {
775
+ const { setConversation } = useSetConversation();
776
+ const { dialogId } = useGetChatSearchParams();
777
+
778
+ const { handleClickConversation } = useClickConversationCard();
779
+
780
+ const createConversationBeforeUploadDocument = useCallback(
781
+ async (message: string) => {
782
+ const data = await setConversation(message);
783
+ if (data.retcode === 0) {
784
+ const id = data.data.id;
785
+ handleClickConversation(id);
786
+ }
787
+ return data;
788
+ },
789
+ [setConversation, handleClickConversation],
790
+ );
791
+
792
+ return {
793
+ createConversationBeforeUploadDocument,
794
+ dialogId,
795
+ };
796
+ };
797
  //#endregion
web/src/pages/chat/share/large.tsx CHANGED
@@ -75,7 +75,7 @@ const ChatContainer = () => {
75
  onInputChange={handleInputChange}
76
  onPressEnter={handlePressEnter}
77
  sendLoading={sendLoading}
78
- uploadUrl="/v1/api/document/upload_and_parse"
79
  showUploadIcon={from === SharedFrom.Chat}
80
  ></MessageInput>
81
  </Flex>
 
75
  onInputChange={handleInputChange}
76
  onPressEnter={handlePressEnter}
77
  sendLoading={sendLoading}
78
+ uploadMethod="external_upload_and_parse"
79
  showUploadIcon={from === SharedFrom.Chat}
80
  ></MessageInput>
81
  </Flex>
web/src/services/chat-service.ts CHANGED
@@ -19,6 +19,7 @@ const {
19
  createExternalConversation,
20
  getExternalConversation,
21
  completeExternalConversation,
 
22
  } = api;
23
 
24
  const methods = {
@@ -86,6 +87,10 @@ const methods = {
86
  url: completeExternalConversation,
87
  method: 'post',
88
  },
 
 
 
 
89
  } as const;
90
 
91
  const chatService = registerServer<keyof typeof methods>(methods, request);
 
19
  createExternalConversation,
20
  getExternalConversation,
21
  completeExternalConversation,
22
+ uploadAndParseExternal,
23
  } = api;
24
 
25
  const methods = {
 
87
  url: completeExternalConversation,
88
  method: 'post',
89
  },
90
+ uploadAndParseExternal: {
91
+ url: uploadAndParseExternal,
92
+ method: 'post',
93
+ },
94
  } as const;
95
 
96
  const chatService = registerServer<keyof typeof methods>(methods, request);
web/src/services/knowledge-service.ts CHANGED
@@ -30,6 +30,7 @@ const {
30
  web_crawl,
31
  knowledge_graph,
32
  document_infos,
 
33
  } = api;
34
 
35
  const methods = {
@@ -136,6 +137,10 @@ const methods = {
136
  url: document_delete,
137
  method: 'delete',
138
  },
 
 
 
 
139
  };
140
 
141
  const kbService = registerServer<keyof typeof methods>(methods, request);
 
30
  web_crawl,
31
  knowledge_graph,
32
  document_infos,
33
+ upload_and_parse,
34
  } = api;
35
 
36
  const methods = {
 
137
  url: document_delete,
138
  method: 'delete',
139
  },
140
+ upload_and_parse: {
141
+ url: upload_and_parse,
142
+ method: 'post',
143
+ },
144
  };
145
 
146
  const kbService = registerServer<keyof typeof methods>(methods, request);
web/src/utils/api.ts CHANGED
@@ -51,6 +51,7 @@ export default {
51
  document_upload: `${api_host}/document/upload`,
52
  web_crawl: `${api_host}/document/web_crawl`,
53
  document_infos: `${api_host}/document/infos`,
 
54
 
55
  // chat
56
  setDialog: `${api_host}/dialog/set`,
@@ -70,6 +71,7 @@ export default {
70
  createExternalConversation: `${api_host}/api/new_conversation`,
71
  getExternalConversation: `${api_host}/api/conversation`,
72
  completeExternalConversation: `${api_host}/api/completion`,
 
73
 
74
  // file manager
75
  listFile: `${api_host}/file/list`,
 
51
  document_upload: `${api_host}/document/upload`,
52
  web_crawl: `${api_host}/document/web_crawl`,
53
  document_infos: `${api_host}/document/infos`,
54
+ upload_and_parse: `${api_host}/document/upload_and_parse`,
55
 
56
  // chat
57
  setDialog: `${api_host}/dialog/set`,
 
71
  createExternalConversation: `${api_host}/api/new_conversation`,
72
  getExternalConversation: `${api_host}/api/conversation`,
73
  completeExternalConversation: `${api_host}/api/completion`,
74
+ uploadAndParseExternal: `${api_host}/api/document/upload_and_parse`,
75
 
76
  // file manager
77
  listFile: `${api_host}/file/list`,