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:
|
|
|
47 |
overflow: auto;
|
|
|
48 |
&::-webkit-scrollbar-track {
|
49 |
background: transparent;
|
50 |
}
|
@@ -53,7 +57,10 @@
|
|
53 |
width: 100%;
|
54 |
}
|
55 |
.knowledgeName {
|
56 |
-
width:
|
|
|
|
|
|
|
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 {
|
|
|
|
|
|
|
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(
|
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={
|
83 |
className={isFirstRender ? styles.globalInput : styles.partialInput}
|
84 |
/>
|
85 |
);
|
@@ -92,7 +100,7 @@ const SearchPage = () => {
|
|
92 |
>
|
93 |
<SearchSidebar
|
94 |
isFirstRender={isFirstRender}
|
95 |
-
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 {
|
4 |
-
import { Avatar,
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
-
|
37 |
-
|
|
|
38 |
|
39 |
-
const
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
-
|
47 |
-
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
},
|
50 |
-
[
|
51 |
);
|
52 |
|
53 |
useEffect(() => {
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
56 |
|
57 |
return (
|
58 |
<Sider
|
@@ -62,41 +140,21 @@ const SearchSidebar = ({
|
|
62 |
theme={'light'}
|
63 |
width={240}
|
64 |
>
|
65 |
-
<
|
66 |
-
|
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 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
{item.name}
|
93 |
-
</Typography.Text>
|
94 |
-
</Space>
|
95 |
-
</Checkbox>
|
96 |
-
</List.Item>
|
97 |
-
)}
|
98 |
/>
|
99 |
-
</
|
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 |
};
|