diff --git a/web/src/interfaces/database/knowledge.ts b/web/src/interfaces/database/knowledge.ts index ad9b025931..666b6a490c 100644 --- a/web/src/interfaces/database/knowledge.ts +++ b/web/src/interfaces/database/knowledge.ts @@ -21,6 +21,7 @@ export interface IKnowledge { update_date: string; update_time: number; vector_similarity_weight: number; + embd_id: string; } export interface Parserconfig { diff --git a/web/src/pages/search/index.less b/web/src/pages/search/index.less index 2653dc6f92..0c239ada16 100644 --- a/web/src/pages/search/index.less +++ b/web/src/pages/search/index.less @@ -27,6 +27,7 @@ .searchSide { position: relative; + :global(.ant-layout-sider-children) { height: auto; } @@ -42,9 +43,12 @@ height: 100%; } .list { + padding-top: 10px; width: 100%; - height: calc(100vh - 152px); + // height: 100%; + height: calc(100vh - 76px); overflow: auto; + background-color: transparent; &::-webkit-scrollbar-track { background: transparent; } @@ -53,7 +57,10 @@ width: 100%; } .knowledgeName { - width: 130px; + width: 116px; + } + .embeddingId { + width: 170px; } } diff --git a/web/src/pages/search/index.tsx b/web/src/pages/search/index.tsx index 257c46be28..94f4e8aa41 100644 --- a/web/src/pages/search/index.tsx +++ b/web/src/pages/search/index.tsx @@ -4,7 +4,10 @@ import IndentedTree from '@/components/indented-tree/indented-tree'; import PdfDrawer from '@/components/pdf-drawer'; import { useClickDrawer } from '@/components/pdf-drawer/hooks'; import RetrievalDocuments from '@/components/retrieval-documents'; -import { useSelectTestingResult } from '@/hooks/knowledge-hooks'; +import { + useNextFetchKnowledgeList, + useSelectTestingResult, +} from '@/hooks/knowledge-hooks'; import { useGetPaginationWithRouter } from '@/hooks/logic-hooks'; import { IReference } from '@/interfaces/database/chat'; import { @@ -35,6 +38,11 @@ const SearchPage = () => { const { t } = useTranslation(); const [checkedList, setCheckedList] = useState([]); const { chunks, total } = useSelectTestingResult(); + const { list: knowledgeList } = useNextFetchKnowledgeList(); + const checkedWithoutEmbeddingIdList = useMemo(() => { + return checkedList.filter((x) => knowledgeList.some((y) => y.id === x)); + }, [checkedList, knowledgeList]); + const { sendQuestion, handleClickRelatedQuestion, @@ -50,7 +58,7 @@ const SearchPage = () => { loading, isFirstRender, selectedDocumentIds, - } = useSendQuestion(checkedList); + } = useSendQuestion(checkedWithoutEmbeddingIdList); const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = useClickDrawer(); const imgUrl = useFetchBackgroundImage(); @@ -79,7 +87,7 @@ const SearchPage = () => { onSearch={sendQuestion} size="large" loading={sendingLoading} - disabled={checkedList.length === 0} + disabled={checkedWithoutEmbeddingIdList.length === 0} className={isFirstRender ? styles.globalInput : styles.partialInput} /> ); @@ -92,7 +100,7 @@ const SearchPage = () => { > diff --git a/web/src/pages/search/sidebar.tsx b/web/src/pages/search/sidebar.tsx index 368f6e5795..535875a294 100644 --- a/web/src/pages/search/sidebar.tsx +++ b/web/src/pages/search/sidebar.tsx @@ -1,9 +1,7 @@ import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks'; import { UserOutlined } from '@ant-design/icons'; -import type { CheckboxProps } from 'antd'; -import { Avatar, Checkbox, Layout, List, Space, Typography } from 'antd'; -import { CheckboxChangeEvent } from 'antd/es/checkbox'; -import { CheckboxValueType } from 'antd/es/checkbox/Group'; +import type { TreeDataNode, TreeProps } from 'antd'; +import { Avatar, Layout, Space, Spin, Tree, Typography } from 'antd'; import classNames from 'classnames'; import { Dispatch, @@ -11,6 +9,7 @@ import { useCallback, useEffect, useMemo, + useState, } from 'react'; import styles from './index.less'; @@ -29,30 +28,109 @@ const SearchSidebar = ({ setCheckedList, }: IProps) => { const { list, loading } = useNextFetchKnowledgeList(); - const ids = useMemo(() => list.map((x) => x.id), [list]); - const checkAll = list.length === checkedList.length; + const groupedList = useMemo(() => { + return list.reduce((pre: TreeDataNode[], cur) => { + const parentItem = pre.find((x) => x.key === cur.embd_id); + const childItem: TreeDataNode = { + title: cur.name, + key: cur.id, + isLeaf: true, + }; + if (parentItem) { + parentItem.children?.push(childItem); + } else { + pre.push({ + title: cur.embd_id, + key: cur.embd_id, + isLeaf: false, + children: [childItem], + }); + } - const indeterminate = - checkedList.length > 0 && checkedList.length < list.length; + return pre; + }, []); + }, [list]); - const onChange = useCallback( - (list: CheckboxValueType[]) => { - setCheckedList(list as string[]); - }, - [setCheckedList], - ); + const [expandedKeys, setExpandedKeys] = useState([]); + const [selectedKeys, setSelectedKeys] = useState([]); + const [autoExpandParent, setAutoExpandParent] = useState(true); + + const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => { + // if not set autoExpandParent to false, if children expanded, parent can not collapse. + // or, you can remove all expanded children keys. + setExpandedKeys(expandedKeysValue); + setAutoExpandParent(false); + }; + + const onCheck: TreeProps['onCheck'] = (checkedKeysValue, info) => { + console.log('onCheck', checkedKeysValue, info); + const currentCheckedKeysValue = checkedKeysValue as string[]; + + let nextSelectedKeysValue: string[] = []; + const { isLeaf, checked, key, children } = info.node; + if (isLeaf) { + const item = list.find((x) => x.id === key); + if (!checked) { + const embeddingIds = currentCheckedKeysValue + .filter((x) => list.some((y) => y.id === x)) + .map((x) => list.find((y) => y.id === x)?.embd_id); + + if (embeddingIds.some((x) => x !== item?.embd_id)) { + nextSelectedKeysValue = [key as string]; + } else { + nextSelectedKeysValue = currentCheckedKeysValue; + } + } else { + nextSelectedKeysValue = currentCheckedKeysValue; + } + } else { + if (!checked) { + nextSelectedKeysValue = [ + key as string, + ...(children?.map((x) => x.key as string) ?? []), + ]; + } else { + nextSelectedKeysValue = []; + } + } - const onCheckAllChange: CheckboxProps['onChange'] = useCallback( - (e: CheckboxChangeEvent) => { - setCheckedList(e.target.checked ? ids : []); + setCheckedList(nextSelectedKeysValue); + }; + + const onSelect: TreeProps['onSelect'] = (selectedKeysValue, info) => { + console.log('onSelect', info); + + setSelectedKeys(selectedKeysValue); + }; + + const renderTitle = useCallback( + (node: TreeDataNode) => { + const item = list.find((x) => x.id === node.key); + return ( + + {node.isLeaf && ( + } src={item?.avatar} /> + )} + + {node.title as string} + + + ); }, - [ids, setCheckedList], + [list], ); useEffect(() => { - setCheckedList(ids); - }, [ids, setCheckedList]); + const firstGroup = groupedList[0]?.children?.map((x) => x.key as string); + if (firstGroup) { + setCheckedList(firstGroup); + } + setExpandedKeys(groupedList.map((x) => x.key)); + }, [groupedList, setExpandedKeys, setCheckedList]); return ( - - All - - - + ( - - - - } src={item.avatar} /> - - {item.name} - - - - - )} + checkable + onExpand={onExpand} + expandedKeys={expandedKeys} + autoExpandParent={autoExpandParent} + onCheck={onCheck} + checkedKeys={checkedList} + onSelect={onSelect} + selectedKeys={selectedKeys} + treeData={groupedList} + titleRender={renderTitle} /> - + ); };