balibabu commited on
Commit
acd1df1
·
1 Parent(s): da0bc38

feat: Use Tree to display the knowledge base list on the search page #2247 (#2385)

Browse files

### What problem does this PR solve?

feat: Use Tree to display the knowledge base list on the search page
#2247
### Type of change

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

web/src/interfaces/database/knowledge.ts CHANGED
@@ -21,6 +21,7 @@ export interface IKnowledge {
21
  update_date: string;
22
  update_time: number;
23
  vector_similarity_weight: number;
 
24
  }
25
 
26
  export interface Parserconfig {
 
21
  update_date: string;
22
  update_time: number;
23
  vector_similarity_weight: number;
24
+ embd_id: string;
25
  }
26
 
27
  export interface Parserconfig {
web/src/pages/search/index.less CHANGED
@@ -27,6 +27,7 @@
27
 
28
  .searchSide {
29
  position: relative;
 
30
  :global(.ant-layout-sider-children) {
31
  height: auto;
32
  }
@@ -42,9 +43,12 @@
42
  height: 100%;
43
  }
44
  .list {
 
45
  width: 100%;
46
- height: calc(100vh - 152px);
 
47
  overflow: auto;
 
48
  &::-webkit-scrollbar-track {
49
  background: transparent;
50
  }
@@ -53,7 +57,10 @@
53
  width: 100%;
54
  }
55
  .knowledgeName {
56
- width: 130px;
 
 
 
57
  }
58
  }
59
 
 
27
 
28
  .searchSide {
29
  position: relative;
30
+
31
  :global(.ant-layout-sider-children) {
32
  height: auto;
33
  }
 
43
  height: 100%;
44
  }
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
  }
 
57
  width: 100%;
58
  }
59
  .knowledgeName {
60
+ width: 116px;
61
+ }
62
+ .embeddingId {
63
+ width: 170px;
64
  }
65
  }
66
 
web/src/pages/search/index.tsx CHANGED
@@ -4,7 +4,10 @@ import IndentedTree from '@/components/indented-tree/indented-tree';
4
  import PdfDrawer from '@/components/pdf-drawer';
5
  import { useClickDrawer } from '@/components/pdf-drawer/hooks';
6
  import RetrievalDocuments from '@/components/retrieval-documents';
7
- import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
 
 
 
8
  import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
9
  import { IReference } from '@/interfaces/database/chat';
10
  import {
@@ -35,6 +38,11 @@ const SearchPage = () => {
35
  const { t } = useTranslation();
36
  const [checkedList, setCheckedList] = useState<string[]>([]);
37
  const { chunks, total } = useSelectTestingResult();
 
 
 
 
 
38
  const {
39
  sendQuestion,
40
  handleClickRelatedQuestion,
@@ -50,7 +58,7 @@ const SearchPage = () => {
50
  loading,
51
  isFirstRender,
52
  selectedDocumentIds,
53
- } = useSendQuestion(checkedList);
54
  const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
55
  useClickDrawer();
56
  const imgUrl = useFetchBackgroundImage();
@@ -79,7 +87,7 @@ const SearchPage = () => {
79
  onSearch={sendQuestion}
80
  size="large"
81
  loading={sendingLoading}
82
- disabled={checkedList.length === 0}
83
  className={isFirstRender ? styles.globalInput : styles.partialInput}
84
  />
85
  );
@@ -92,7 +100,7 @@ const SearchPage = () => {
92
  >
93
  <SearchSidebar
94
  isFirstRender={isFirstRender}
95
- checkedList={checkedList}
96
  setCheckedList={setCheckedList}
97
  ></SearchSidebar>
98
  <Layout className={isFirstRender ? styles.mainLayout : ''}>
 
4
  import PdfDrawer from '@/components/pdf-drawer';
5
  import { useClickDrawer } from '@/components/pdf-drawer/hooks';
6
  import RetrievalDocuments from '@/components/retrieval-documents';
7
+ import {
8
+ useNextFetchKnowledgeList,
9
+ useSelectTestingResult,
10
+ } from '@/hooks/knowledge-hooks';
11
  import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
12
  import { IReference } from '@/interfaces/database/chat';
13
  import {
 
38
  const { t } = useTranslation();
39
  const [checkedList, setCheckedList] = useState<string[]>([]);
40
  const { chunks, total } = useSelectTestingResult();
41
+ const { list: knowledgeList } = useNextFetchKnowledgeList();
42
+ const checkedWithoutEmbeddingIdList = useMemo(() => {
43
+ return checkedList.filter((x) => knowledgeList.some((y) => y.id === x));
44
+ }, [checkedList, knowledgeList]);
45
+
46
  const {
47
  sendQuestion,
48
  handleClickRelatedQuestion,
 
58
  loading,
59
  isFirstRender,
60
  selectedDocumentIds,
61
+ } = useSendQuestion(checkedWithoutEmbeddingIdList);
62
  const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
63
  useClickDrawer();
64
  const imgUrl = useFetchBackgroundImage();
 
87
  onSearch={sendQuestion}
88
  size="large"
89
  loading={sendingLoading}
90
+ disabled={checkedWithoutEmbeddingIdList.length === 0}
91
  className={isFirstRender ? styles.globalInput : styles.partialInput}
92
  />
93
  );
 
100
  >
101
  <SearchSidebar
102
  isFirstRender={isFirstRender}
103
+ checkedList={checkedWithoutEmbeddingIdList}
104
  setCheckedList={setCheckedList}
105
  ></SearchSidebar>
106
  <Layout className={isFirstRender ? styles.mainLayout : ''}>
web/src/pages/search/sidebar.tsx CHANGED
@@ -1,9 +1,7 @@
1
  import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
2
  import { UserOutlined } from '@ant-design/icons';
3
- import type { CheckboxProps } from 'antd';
4
- import { Avatar, Checkbox, Layout, List, Space, Typography } from 'antd';
5
- import { CheckboxChangeEvent } from 'antd/es/checkbox';
6
- import { CheckboxValueType } from 'antd/es/checkbox/Group';
7
  import classNames from 'classnames';
8
  import {
9
  Dispatch,
@@ -11,6 +9,7 @@ import {
11
  useCallback,
12
  useEffect,
13
  useMemo,
 
14
  } from 'react';
15
 
16
  import styles from './index.less';
@@ -29,30 +28,109 @@ const SearchSidebar = ({
29
  setCheckedList,
30
  }: IProps) => {
31
  const { list, loading } = useNextFetchKnowledgeList();
32
- const ids = useMemo(() => list.map((x) => x.id), [list]);
33
 
34
- const checkAll = list.length === checkedList.length;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- const indeterminate =
37
- checkedList.length > 0 && checkedList.length < list.length;
 
38
 
39
- const onChange = useCallback(
40
- (list: CheckboxValueType[]) => {
41
- setCheckedList(list as string[]);
42
- },
43
- [setCheckedList],
44
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- const onCheckAllChange: CheckboxProps['onChange'] = useCallback(
47
- (e: CheckboxChangeEvent) => {
48
- setCheckedList(e.target.checked ? ids : []);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  },
50
- [ids, setCheckedList],
51
  );
52
 
53
  useEffect(() => {
54
- setCheckedList(ids);
55
- }, [ids, setCheckedList]);
 
 
 
 
56
 
57
  return (
58
  <Sider
@@ -62,41 +140,21 @@ const SearchSidebar = ({
62
  theme={'light'}
63
  width={240}
64
  >
65
- <Checkbox
66
- className={styles.modelForm}
67
- indeterminate={indeterminate}
68
- onChange={onCheckAllChange}
69
- checked={checkAll}
70
- >
71
- All
72
- </Checkbox>
73
- <Checkbox.Group
74
- className={styles.checkGroup}
75
- onChange={onChange}
76
- value={checkedList}
77
- >
78
- <List
79
- bordered
80
- dataSource={list}
81
  className={styles.list}
82
- loading={loading}
83
- renderItem={(item) => (
84
- <List.Item>
85
- <Checkbox value={item.id} className={styles.checkbox}>
86
- <Space>
87
- <Avatar size={30} icon={<UserOutlined />} src={item.avatar} />
88
- <Typography.Text
89
- ellipsis={{ tooltip: item.name }}
90
- className={styles.knowledgeName}
91
- >
92
- {item.name}
93
- </Typography.Text>
94
- </Space>
95
- </Checkbox>
96
- </List.Item>
97
- )}
98
  />
99
- </Checkbox.Group>
100
  </Sider>
101
  );
102
  };
 
1
  import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
2
  import { UserOutlined } from '@ant-design/icons';
3
+ import type { TreeDataNode, TreeProps } from 'antd';
4
+ import { Avatar, Layout, Space, Spin, Tree, Typography } from 'antd';
 
 
5
  import classNames from 'classnames';
6
  import {
7
  Dispatch,
 
9
  useCallback,
10
  useEffect,
11
  useMemo,
12
+ useState,
13
  } from 'react';
14
 
15
  import styles from './index.less';
 
28
  setCheckedList,
29
  }: IProps) => {
30
  const { list, loading } = useNextFetchKnowledgeList();
 
31
 
32
+ const groupedList = useMemo(() => {
33
+ return list.reduce((pre: TreeDataNode[], cur) => {
34
+ const parentItem = pre.find((x) => x.key === cur.embd_id);
35
+ const childItem: TreeDataNode = {
36
+ title: cur.name,
37
+ key: cur.id,
38
+ isLeaf: true,
39
+ };
40
+ if (parentItem) {
41
+ parentItem.children?.push(childItem);
42
+ } else {
43
+ pre.push({
44
+ title: cur.embd_id,
45
+ key: cur.embd_id,
46
+ isLeaf: false,
47
+ children: [childItem],
48
+ });
49
+ }
50
 
51
+ return pre;
52
+ }, []);
53
+ }, [list]);
54
 
55
+ const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
56
+ const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
57
+ const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
58
+
59
+ const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => {
60
+ // if not set autoExpandParent to false, if children expanded, parent can not collapse.
61
+ // or, you can remove all expanded children keys.
62
+ setExpandedKeys(expandedKeysValue);
63
+ setAutoExpandParent(false);
64
+ };
65
+
66
+ const onCheck: TreeProps['onCheck'] = (checkedKeysValue, info) => {
67
+ console.log('onCheck', checkedKeysValue, info);
68
+ const currentCheckedKeysValue = checkedKeysValue as string[];
69
+
70
+ let nextSelectedKeysValue: string[] = [];
71
+ const { isLeaf, checked, key, children } = info.node;
72
+ if (isLeaf) {
73
+ const item = list.find((x) => x.id === key);
74
+ if (!checked) {
75
+ const embeddingIds = currentCheckedKeysValue
76
+ .filter((x) => list.some((y) => y.id === x))
77
+ .map((x) => list.find((y) => y.id === x)?.embd_id);
78
+
79
+ if (embeddingIds.some((x) => x !== item?.embd_id)) {
80
+ nextSelectedKeysValue = [key as string];
81
+ } else {
82
+ nextSelectedKeysValue = currentCheckedKeysValue;
83
+ }
84
+ } else {
85
+ nextSelectedKeysValue = currentCheckedKeysValue;
86
+ }
87
+ } else {
88
+ if (!checked) {
89
+ nextSelectedKeysValue = [
90
+ key as string,
91
+ ...(children?.map((x) => x.key as string) ?? []),
92
+ ];
93
+ } else {
94
+ nextSelectedKeysValue = [];
95
+ }
96
+ }
97
 
98
+ setCheckedList(nextSelectedKeysValue);
99
+ };
100
+
101
+ const onSelect: TreeProps['onSelect'] = (selectedKeysValue, info) => {
102
+ console.log('onSelect', info);
103
+
104
+ setSelectedKeys(selectedKeysValue);
105
+ };
106
+
107
+ const renderTitle = useCallback(
108
+ (node: TreeDataNode) => {
109
+ const item = list.find((x) => x.id === node.key);
110
+ return (
111
+ <Space>
112
+ {node.isLeaf && (
113
+ <Avatar size={24} icon={<UserOutlined />} src={item?.avatar} />
114
+ )}
115
+ <Typography.Text
116
+ ellipsis={{ tooltip: node.title as string }}
117
+ className={node.isLeaf ? styles.knowledgeName : styles.embeddingId}
118
+ >
119
+ {node.title as string}
120
+ </Typography.Text>
121
+ </Space>
122
+ );
123
  },
124
+ [list],
125
  );
126
 
127
  useEffect(() => {
128
+ const firstGroup = groupedList[0]?.children?.map((x) => x.key as string);
129
+ if (firstGroup) {
130
+ setCheckedList(firstGroup);
131
+ }
132
+ setExpandedKeys(groupedList.map((x) => x.key));
133
+ }, [groupedList, setExpandedKeys, setCheckedList]);
134
 
135
  return (
136
  <Sider
 
140
  theme={'light'}
141
  width={240}
142
  >
143
+ <Spin spinning={loading}>
144
+ <Tree
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  className={styles.list}
146
+ checkable
147
+ onExpand={onExpand}
148
+ expandedKeys={expandedKeys}
149
+ autoExpandParent={autoExpandParent}
150
+ onCheck={onCheck}
151
+ checkedKeys={checkedList}
152
+ onSelect={onSelect}
153
+ selectedKeys={selectedKeys}
154
+ treeData={groupedList}
155
+ titleRender={renderTitle}
 
 
 
 
 
 
156
  />
157
+ </Spin>
158
  </Sider>
159
  );
160
  };