balibabu commited on
Commit
0dc6759
·
1 Parent(s): ef1a16c

feat: Display mindmap in drawer #2247 (#2430)

Browse files

### What problem does this PR solve?

feat: Display mindmap in drawer #2247

### Type of change

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

docker/nginx/ragflow.conf CHANGED
@@ -15,9 +15,6 @@ server {
15
  include proxy.conf;
16
  }
17
 
18
- location /HPImageArchive {
19
- proxy_pass https://cn.bing.com;
20
- }
21
 
22
  location / {
23
  index index.html;
 
15
  include proxy.conf;
16
  }
17
 
 
 
 
18
 
19
  location / {
20
  index index.html;
web/src/pages/search/hooks.ts CHANGED
@@ -1,4 +1,5 @@
1
  import { useFetchMindMap, useFetchRelatedQuestions } from '@/hooks/chat-hooks';
 
2
  import { useTestChunkRetrieval } from '@/hooks/knowledge-hooks';
3
  import {
4
  useGetPaginationWithRouter,
@@ -7,7 +8,13 @@ import {
7
  import { IAnswer } from '@/interfaces/database/chat';
8
  import api from '@/utils/api';
9
  import { get, isEmpty, trim } from 'lodash';
10
- import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
 
 
 
 
 
 
11
 
12
  export const useSendQuestion = (kbIds: string[]) => {
13
  const { send, answer, done } = useSendMessageWithSse(api.ask);
@@ -16,11 +23,6 @@ export const useSendQuestion = (kbIds: string[]) => {
16
  const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
17
  const { fetchRelatedQuestions, data: relatedQuestions } =
18
  useFetchRelatedQuestions();
19
- const {
20
- fetchMindMap,
21
- data: mindMap,
22
- loading: mindMapLoading,
23
- } = useFetchMindMap();
24
  const [searchStr, setSearchStr] = useState<string>('');
25
  const [isFirstRender, setIsFirstRender] = useState(true);
26
  const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
@@ -43,10 +45,7 @@ export const useSendQuestion = (kbIds: string[]) => {
43
  page: 1,
44
  size: pagination.pageSize,
45
  });
46
- fetchMindMap({
47
- question: q,
48
- kb_ids: kbIds,
49
- });
50
  fetchRelatedQuestions(q);
51
  },
52
  [
@@ -54,7 +53,6 @@ export const useSendQuestion = (kbIds: string[]) => {
54
  testChunk,
55
  kbIds,
56
  fetchRelatedQuestions,
57
- fetchMindMap,
58
  setPagination,
59
  pagination.pageSize,
60
  ],
@@ -117,11 +115,10 @@ export const useSendQuestion = (kbIds: string[]) => {
117
  sendingLoading,
118
  answer: currentAnswer,
119
  relatedQuestions: relatedQuestions?.slice(0, 5) ?? [],
120
- mindMap,
121
- mindMapLoading,
122
  searchStr,
123
  isFirstRender,
124
  selectedDocumentIds,
 
125
  };
126
  };
127
 
@@ -191,3 +188,51 @@ export const useTestRetrieval = (
191
  setSelectedDocumentIds,
192
  };
193
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { useFetchMindMap, useFetchRelatedQuestions } from '@/hooks/chat-hooks';
2
+ import { useSetModalState } from '@/hooks/common-hooks';
3
  import { useTestChunkRetrieval } from '@/hooks/knowledge-hooks';
4
  import {
5
  useGetPaginationWithRouter,
 
8
  import { IAnswer } from '@/interfaces/database/chat';
9
  import api from '@/utils/api';
10
  import { get, isEmpty, trim } from 'lodash';
11
+ import {
12
+ ChangeEventHandler,
13
+ useCallback,
14
+ useEffect,
15
+ useRef,
16
+ useState,
17
+ } from 'react';
18
 
19
  export const useSendQuestion = (kbIds: string[]) => {
20
  const { send, answer, done } = useSendMessageWithSse(api.ask);
 
23
  const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
24
  const { fetchRelatedQuestions, data: relatedQuestions } =
25
  useFetchRelatedQuestions();
 
 
 
 
 
26
  const [searchStr, setSearchStr] = useState<string>('');
27
  const [isFirstRender, setIsFirstRender] = useState(true);
28
  const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
 
45
  page: 1,
46
  size: pagination.pageSize,
47
  });
48
+
 
 
 
49
  fetchRelatedQuestions(q);
50
  },
51
  [
 
53
  testChunk,
54
  kbIds,
55
  fetchRelatedQuestions,
 
56
  setPagination,
57
  pagination.pageSize,
58
  ],
 
115
  sendingLoading,
116
  answer: currentAnswer,
117
  relatedQuestions: relatedQuestions?.slice(0, 5) ?? [],
 
 
118
  searchStr,
119
  isFirstRender,
120
  selectedDocumentIds,
121
+ isSearchStrEmpty: isEmpty(trim(searchStr)),
122
  };
123
  };
124
 
 
188
  setSelectedDocumentIds,
189
  };
190
  };
191
+
192
+ export const useShowMindMapDrawer = (kbIds: string[], question: string) => {
193
+ const { visible, showModal, hideModal } = useSetModalState();
194
+
195
+ const {
196
+ fetchMindMap,
197
+ data: mindMap,
198
+ loading: mindMapLoading,
199
+ } = useFetchMindMap();
200
+
201
+ const handleShowModal = useCallback(() => {
202
+ fetchMindMap({ question: trim(question), kb_ids: kbIds });
203
+ showModal();
204
+ }, [fetchMindMap, showModal, question, kbIds]);
205
+
206
+ return {
207
+ mindMap,
208
+ mindMapVisible: visible,
209
+ mindMapLoading,
210
+ showMindMapModal: handleShowModal,
211
+ hideMindMapModal: hideModal,
212
+ };
213
+ };
214
+
215
+ export const usePendingMindMap = () => {
216
+ const [count, setCount] = useState<number>(0);
217
+ const ref = useRef<NodeJS.Timeout>();
218
+
219
+ const setCountInterval = useCallback(() => {
220
+ ref.current = setInterval(() => {
221
+ setCount((pre) => {
222
+ if (pre > 40) {
223
+ clearInterval(ref?.current);
224
+ }
225
+ return pre + 1;
226
+ });
227
+ }, 1000);
228
+ }, []);
229
+
230
+ useEffect(() => {
231
+ setCountInterval();
232
+ return () => {
233
+ clearInterval(ref?.current);
234
+ };
235
+ }, [setCountInterval]);
236
+
237
+ return Number(((count / 43) * 100).toFixed(0));
238
+ };
web/src/pages/search/index.less CHANGED
@@ -16,17 +16,19 @@
16
  cursor: pointer;
17
  }
18
 
19
- .mainLayout {
20
- background: transparent;
21
- }
22
  }
23
 
24
- .transparentSearchSide {
25
- background-color: rgb(251 251 251 / 88%) !important;
26
- }
27
 
28
  .searchSide {
29
  position: relative;
 
 
30
 
31
  :global(.ant-layout-sider-children) {
32
  height: auto;
@@ -45,19 +47,19 @@
45
  .list {
46
  padding-top: 10px;
47
  width: 100%;
48
- // height: 100%;
49
  height: calc(100vh - 76px);
50
  overflow: auto;
51
- background-color: transparent;
52
- &::-webkit-scrollbar-track {
53
- background: transparent;
54
- }
55
  }
56
  .checkbox {
57
  width: 100%;
58
  }
59
  .knowledgeName {
60
  width: 116px;
 
61
  }
62
  .embeddingId {
63
  width: 170px;
@@ -70,27 +72,17 @@
70
 
71
  .content {
72
  height: 100%;
 
 
 
73
  .hide {
74
  display: none;
75
  }
76
 
77
- .mainMixin() {
78
- overflow: auto;
79
- padding: 20px 10px 10px;
80
- }
81
-
82
- .largeMain {
83
- width: 100%;
84
- .mainMixin();
85
- }
86
  .main {
87
- width: 60%;
88
- .mainMixin();
89
- }
90
-
91
- .graph {
92
- width: 40%;
93
- padding: 20px 10px 10px;
94
  }
95
 
96
  .highlightContent {
@@ -103,6 +95,9 @@
103
  .documentReference {
104
  cursor: pointer;
105
  }
 
 
 
106
  }
107
  .answerWrapper {
108
  margin-top: 16px;
@@ -122,9 +117,9 @@
122
  border-start-start-radius: 30px !important;
123
  border-end-start-radius: 30px !important;
124
  }
125
- :global(.ant-input-group-addon) {
126
- background-color: transparent;
127
- }
128
  input {
129
  height: 40px;
130
  }
@@ -138,7 +133,7 @@
138
  .globalInput {
139
  width: 600px;
140
  position: sticky;
141
- top: 0;
142
  z-index: 1;
143
  .input();
144
  }
@@ -187,3 +182,12 @@
187
  max-height: 40vh;
188
  overflow: auto;
189
  }
 
 
 
 
 
 
 
 
 
 
16
  cursor: pointer;
17
  }
18
 
19
+ // .mainLayout {
20
+ // background: transparent;
21
+ // }
22
  }
23
 
24
+ // .transparentSearchSide {
25
+ // background-color: rgb(251 251 251 / 88%) !important;
26
+ // }
27
 
28
  .searchSide {
29
  position: relative;
30
+ max-width: 400px !important;
31
+ min-width: auto !important;
32
 
33
  :global(.ant-layout-sider-children) {
34
  height: auto;
 
47
  .list {
48
  padding-top: 10px;
49
  width: 100%;
 
50
  height: calc(100vh - 76px);
51
  overflow: auto;
52
+ // background-color: transparent;
53
+ // &::-webkit-scrollbar-track {
54
+ // background: transparent;
55
+ // }
56
  }
57
  .checkbox {
58
  width: 100%;
59
  }
60
  .knowledgeName {
61
  width: 116px;
62
+ max-width: 270px;
63
  }
64
  .embeddingId {
65
  width: 170px;
 
72
 
73
  .content {
74
  height: 100%;
75
+ overflow: auto;
76
+ width: 100%;
77
+ padding: 20px 16% 10px;
78
  .hide {
79
  display: none;
80
  }
81
 
 
 
 
 
 
 
 
 
 
82
  .main {
83
+ margin: 0 auto;
84
+ width: 100%;
85
+ max-width: 1200px;
 
 
 
 
86
  }
87
 
88
  .highlightContent {
 
95
  .documentReference {
96
  cursor: pointer;
97
  }
98
+ .pagination {
99
+ padding-bottom: 16px;
100
+ }
101
  }
102
  .answerWrapper {
103
  margin-top: 16px;
 
117
  border-start-start-radius: 30px !important;
118
  border-end-start-radius: 30px !important;
119
  }
120
+ // :global(.ant-input-group-addon) {
121
+ // background-color: transparent;
122
+ // }
123
  input {
124
  height: 40px;
125
  }
 
133
  .globalInput {
134
  width: 600px;
135
  position: sticky;
136
+ top: 30%;
137
  z-index: 1;
138
  .input();
139
  }
 
182
  max-height: 40vh;
183
  overflow: auto;
184
  }
185
+
186
+ .mindMapFloatButton {
187
+ top: 20%;
188
+ width: 60px;
189
+ height: 60px;
190
+ :global(.ant-float-btn-content, .ant-float-btn-icon) {
191
+ width: auto !important;
192
+ }
193
+ }
web/src/pages/search/index.tsx CHANGED
@@ -1,10 +1,10 @@
1
  import FileIcon from '@/components/file-icon';
2
  import HightLightMarkdown from '@/components/highlight-markdown';
3
  import { ImageWithPopover } from '@/components/image';
4
- import IndentedTree from '@/components/indented-tree/indented-tree';
5
  import PdfDrawer from '@/components/pdf-drawer';
6
  import { useClickDrawer } from '@/components/pdf-drawer/hooks';
7
  import RetrievalDocuments from '@/components/retrieval-documents';
 
8
  import {
9
  useNextFetchKnowledgeList,
10
  useSelectTestingResult,
@@ -15,6 +15,7 @@ import {
15
  Card,
16
  Divider,
17
  Flex,
 
18
  Input,
19
  Layout,
20
  List,
@@ -25,14 +26,16 @@ import {
25
  Space,
26
  Spin,
27
  Tag,
 
28
  } from 'antd';
29
  import DOMPurify from 'dompurify';
30
  import { isEmpty } from 'lodash';
31
  import { useMemo, useState } from 'react';
32
  import { useTranslation } from 'react-i18next';
33
  import MarkdownContent from '../chat/markdown-content';
34
- import { useFetchBackgroundImage, useSendQuestion } from './hooks';
35
  import styles from './index.less';
 
36
  import SearchSidebar from './sidebar';
37
 
38
  const { Content } = Layout;
@@ -56,29 +59,28 @@ const SearchPage = () => {
56
  answer,
57
  sendingLoading,
58
  relatedQuestions,
59
- mindMap,
60
  searchStr,
61
  loading,
62
  isFirstRender,
63
  selectedDocumentIds,
 
64
  } = useSendQuestion(checkedWithoutEmbeddingIdList);
65
  const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
66
  useClickDrawer();
67
- const imgUrl = useFetchBackgroundImage();
68
  const { pagination } = useGetPaginationWithRouter();
 
 
 
 
 
 
 
69
 
70
  const onChange: PaginationProps['onChange'] = (pageNumber, pageSize) => {
71
  pagination.onChange?.(pageNumber, pageSize);
72
  handleTestChunk(selectedDocumentIds, pageNumber, pageSize);
73
  };
74
 
75
- const isMindMapEmpty = useMemo(() => {
76
- return (
77
- (Array.isArray(mindMap?.children) && mindMap.children.length === 0) ||
78
- !Array.isArray(mindMap?.children)
79
- );
80
- }, [mindMap]);
81
-
82
  const InputSearch = (
83
  <Search
84
  value={searchStr}
@@ -96,10 +98,7 @@ const SearchPage = () => {
96
 
97
  return (
98
  <>
99
- <Layout
100
- className={styles.searchPage}
101
- style={{ backgroundImage: `url(${imgUrl})` }}
102
- >
103
  <SearchSidebar
104
  isFirstRender={isFirstRender}
105
  checkedList={checkedWithoutEmbeddingIdList}
@@ -108,20 +107,14 @@ const SearchPage = () => {
108
  <Layout className={isFirstRender ? styles.mainLayout : ''}>
109
  <Content>
110
  {isFirstRender ? (
111
- <Flex
112
- justify="center"
113
- align="center"
114
- className={styles.firstRenderContent}
115
- >
116
  <Flex vertical align="center" gap={'large'}>
117
  {InputSearch}
118
  </Flex>
119
  </Flex>
120
  ) : (
121
  <Flex className={styles.content}>
122
- <section
123
- className={isMindMapEmpty ? styles.largeMain : styles.main}
124
- >
125
  {InputSearch}
126
  <Card
127
  title={
@@ -226,28 +219,43 @@ const SearchPage = () => {
226
  {...pagination}
227
  total={total}
228
  onChange={onChange}
 
229
  />
230
  </section>
231
- <section
232
- className={isMindMapEmpty ? styles.hide : styles.graph}
233
- >
234
- <IndentedTree
235
- data={mindMap}
236
- show
237
- style={{ width: '100%', height: '100%' }}
238
- ></IndentedTree>
239
- </section>
240
  </Flex>
241
  )}
242
  </Content>
243
  </Layout>
244
  </Layout>
245
- <PdfDrawer
246
- visible={visible}
247
- hideModal={hideModal}
248
- documentId={documentId}
249
- chunk={selectedChunk}
250
- ></PdfDrawer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  </>
252
  );
253
  };
 
1
  import FileIcon from '@/components/file-icon';
2
  import HightLightMarkdown from '@/components/highlight-markdown';
3
  import { ImageWithPopover } from '@/components/image';
 
4
  import PdfDrawer from '@/components/pdf-drawer';
5
  import { useClickDrawer } from '@/components/pdf-drawer/hooks';
6
  import RetrievalDocuments from '@/components/retrieval-documents';
7
+ import SvgIcon from '@/components/svg-icon';
8
  import {
9
  useNextFetchKnowledgeList,
10
  useSelectTestingResult,
 
15
  Card,
16
  Divider,
17
  Flex,
18
+ FloatButton,
19
  Input,
20
  Layout,
21
  List,
 
26
  Space,
27
  Spin,
28
  Tag,
29
+ Tooltip,
30
  } from 'antd';
31
  import DOMPurify from 'dompurify';
32
  import { isEmpty } from 'lodash';
33
  import { useMemo, useState } from 'react';
34
  import { useTranslation } from 'react-i18next';
35
  import MarkdownContent from '../chat/markdown-content';
36
+ import { useSendQuestion, useShowMindMapDrawer } from './hooks';
37
  import styles from './index.less';
38
+ import MindMapDrawer from './mindmap-drawer';
39
  import SearchSidebar from './sidebar';
40
 
41
  const { Content } = Layout;
 
59
  answer,
60
  sendingLoading,
61
  relatedQuestions,
 
62
  searchStr,
63
  loading,
64
  isFirstRender,
65
  selectedDocumentIds,
66
+ isSearchStrEmpty,
67
  } = useSendQuestion(checkedWithoutEmbeddingIdList);
68
  const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
69
  useClickDrawer();
 
70
  const { pagination } = useGetPaginationWithRouter();
71
+ const {
72
+ mindMapVisible,
73
+ hideMindMapModal,
74
+ showMindMapModal,
75
+ mindMapLoading,
76
+ mindMap,
77
+ } = useShowMindMapDrawer(checkedWithoutEmbeddingIdList, searchStr);
78
 
79
  const onChange: PaginationProps['onChange'] = (pageNumber, pageSize) => {
80
  pagination.onChange?.(pageNumber, pageSize);
81
  handleTestChunk(selectedDocumentIds, pageNumber, pageSize);
82
  };
83
 
 
 
 
 
 
 
 
84
  const InputSearch = (
85
  <Search
86
  value={searchStr}
 
98
 
99
  return (
100
  <>
101
+ <Layout className={styles.searchPage}>
 
 
 
102
  <SearchSidebar
103
  isFirstRender={isFirstRender}
104
  checkedList={checkedWithoutEmbeddingIdList}
 
107
  <Layout className={isFirstRender ? styles.mainLayout : ''}>
108
  <Content>
109
  {isFirstRender ? (
110
+ <Flex justify="center" className={styles.firstRenderContent}>
 
 
 
 
111
  <Flex vertical align="center" gap={'large'}>
112
  {InputSearch}
113
  </Flex>
114
  </Flex>
115
  ) : (
116
  <Flex className={styles.content}>
117
+ <section className={styles.main}>
 
 
118
  {InputSearch}
119
  <Card
120
  title={
 
219
  {...pagination}
220
  total={total}
221
  onChange={onChange}
222
+ className={styles.pagination}
223
  />
224
  </section>
 
 
 
 
 
 
 
 
 
225
  </Flex>
226
  )}
227
  </Content>
228
  </Layout>
229
  </Layout>
230
+ {!isFirstRender &&
231
+ !isSearchStrEmpty &&
232
+ !isEmpty(checkedWithoutEmbeddingIdList) && (
233
+ <Tooltip title={t('chunk.mind')} zIndex={1}>
234
+ <FloatButton
235
+ className={styles.mindMapFloatButton}
236
+ onClick={showMindMapModal}
237
+ icon={
238
+ <SvgIcon name="paper-clip" width={24} height={30}></SvgIcon>
239
+ }
240
+ />
241
+ </Tooltip>
242
+ )}
243
+ {visible && (
244
+ <PdfDrawer
245
+ visible={visible}
246
+ hideModal={hideModal}
247
+ documentId={documentId}
248
+ chunk={selectedChunk}
249
+ ></PdfDrawer>
250
+ )}
251
+ {mindMapVisible && (
252
+ <MindMapDrawer
253
+ visible={mindMapVisible}
254
+ hideModal={hideMindMapModal}
255
+ data={mindMap}
256
+ loading={mindMapLoading}
257
+ ></MindMapDrawer>
258
+ )}
259
  </>
260
  );
261
  };
web/src/pages/search/mindmap-drawer.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import IndentedTree from '@/components/indented-tree/indented-tree';
2
+ import { IModalProps } from '@/interfaces/common';
3
+ import { Drawer, Flex, Progress } from 'antd';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { usePendingMindMap } from './hooks';
6
+
7
+ interface IProps extends IModalProps<any> {
8
+ data: any;
9
+ }
10
+
11
+ const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => {
12
+ const { t } = useTranslation();
13
+ const percent = usePendingMindMap();
14
+ return (
15
+ <Drawer
16
+ title={t('chunk.mind')}
17
+ onClose={hideModal}
18
+ open={visible}
19
+ width={'40vw'}
20
+ >
21
+ {loading ? (
22
+ <Flex justify="center">
23
+ <Progress type="circle" percent={percent} size={200} />
24
+ </Flex>
25
+ ) : (
26
+ <IndentedTree
27
+ data={data}
28
+ show
29
+ style={{ width: '100%', height: '100%' }}
30
+ ></IndentedTree>
31
+ )}
32
+ </Drawer>
33
+ );
34
+ };
35
+
36
+ export default MindMapDrawer;
web/src/pages/search/sidebar.tsx CHANGED
@@ -138,7 +138,7 @@ const SearchSidebar = ({
138
  [styles.transparentSearchSide]: isFirstRender,
139
  })}
140
  theme={'light'}
141
- width={240}
142
  >
143
  <Spin spinning={loading}>
144
  <Tree
 
138
  [styles.transparentSearchSide]: isFirstRender,
139
  })}
140
  theme={'light'}
141
+ width={'20%'}
142
  >
143
  <Spin spinning={loading}>
144
  <Tree