### What problem does this PR solve? feat: Display mindmap in drawer #2247 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
3044cb85fd
commit
6a0702f55f
@ -15,9 +15,6 @@ server {
|
||||
include proxy.conf;
|
||||
}
|
||||
|
||||
location /HPImageArchive {
|
||||
proxy_pass https://cn.bing.com;
|
||||
}
|
||||
|
||||
location / {
|
||||
index index.html;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useFetchMindMap, useFetchRelatedQuestions } from '@/hooks/chat-hooks';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useTestChunkRetrieval } from '@/hooks/knowledge-hooks';
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
@ -7,7 +8,13 @@ import {
|
||||
import { IAnswer } from '@/interfaces/database/chat';
|
||||
import api from '@/utils/api';
|
||||
import { get, isEmpty, trim } from 'lodash';
|
||||
import { ChangeEventHandler, useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
export const useSendQuestion = (kbIds: string[]) => {
|
||||
const { send, answer, done } = useSendMessageWithSse(api.ask);
|
||||
@ -16,11 +23,6 @@ export const useSendQuestion = (kbIds: string[]) => {
|
||||
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
||||
const { fetchRelatedQuestions, data: relatedQuestions } =
|
||||
useFetchRelatedQuestions();
|
||||
const {
|
||||
fetchMindMap,
|
||||
data: mindMap,
|
||||
loading: mindMapLoading,
|
||||
} = useFetchMindMap();
|
||||
const [searchStr, setSearchStr] = useState<string>('');
|
||||
const [isFirstRender, setIsFirstRender] = useState(true);
|
||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
||||
@ -43,10 +45,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
||||
page: 1,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
fetchMindMap({
|
||||
question: q,
|
||||
kb_ids: kbIds,
|
||||
});
|
||||
|
||||
fetchRelatedQuestions(q);
|
||||
},
|
||||
[
|
||||
@ -54,7 +53,6 @@ export const useSendQuestion = (kbIds: string[]) => {
|
||||
testChunk,
|
||||
kbIds,
|
||||
fetchRelatedQuestions,
|
||||
fetchMindMap,
|
||||
setPagination,
|
||||
pagination.pageSize,
|
||||
],
|
||||
@ -117,11 +115,10 @@ export const useSendQuestion = (kbIds: string[]) => {
|
||||
sendingLoading,
|
||||
answer: currentAnswer,
|
||||
relatedQuestions: relatedQuestions?.slice(0, 5) ?? [],
|
||||
mindMap,
|
||||
mindMapLoading,
|
||||
searchStr,
|
||||
isFirstRender,
|
||||
selectedDocumentIds,
|
||||
isSearchStrEmpty: isEmpty(trim(searchStr)),
|
||||
};
|
||||
};
|
||||
|
||||
@ -191,3 +188,51 @@ export const useTestRetrieval = (
|
||||
setSelectedDocumentIds,
|
||||
};
|
||||
};
|
||||
|
||||
export const useShowMindMapDrawer = (kbIds: string[], question: string) => {
|
||||
const { visible, showModal, hideModal } = useSetModalState();
|
||||
|
||||
const {
|
||||
fetchMindMap,
|
||||
data: mindMap,
|
||||
loading: mindMapLoading,
|
||||
} = useFetchMindMap();
|
||||
|
||||
const handleShowModal = useCallback(() => {
|
||||
fetchMindMap({ question: trim(question), kb_ids: kbIds });
|
||||
showModal();
|
||||
}, [fetchMindMap, showModal, question, kbIds]);
|
||||
|
||||
return {
|
||||
mindMap,
|
||||
mindMapVisible: visible,
|
||||
mindMapLoading,
|
||||
showMindMapModal: handleShowModal,
|
||||
hideMindMapModal: hideModal,
|
||||
};
|
||||
};
|
||||
|
||||
export const usePendingMindMap = () => {
|
||||
const [count, setCount] = useState<number>(0);
|
||||
const ref = useRef<NodeJS.Timeout>();
|
||||
|
||||
const setCountInterval = useCallback(() => {
|
||||
ref.current = setInterval(() => {
|
||||
setCount((pre) => {
|
||||
if (pre > 40) {
|
||||
clearInterval(ref?.current);
|
||||
}
|
||||
return pre + 1;
|
||||
});
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCountInterval();
|
||||
return () => {
|
||||
clearInterval(ref?.current);
|
||||
};
|
||||
}, [setCountInterval]);
|
||||
|
||||
return Number(((count / 43) * 100).toFixed(0));
|
||||
};
|
||||
|
||||
@ -16,17 +16,19 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mainLayout {
|
||||
background: transparent;
|
||||
}
|
||||
// .mainLayout {
|
||||
// background: transparent;
|
||||
// }
|
||||
}
|
||||
|
||||
.transparentSearchSide {
|
||||
background-color: rgb(251 251 251 / 88%) !important;
|
||||
}
|
||||
// .transparentSearchSide {
|
||||
// background-color: rgb(251 251 251 / 88%) !important;
|
||||
// }
|
||||
|
||||
.searchSide {
|
||||
position: relative;
|
||||
max-width: 400px !important;
|
||||
min-width: auto !important;
|
||||
|
||||
:global(.ant-layout-sider-children) {
|
||||
height: auto;
|
||||
@ -45,19 +47,19 @@
|
||||
.list {
|
||||
padding-top: 10px;
|
||||
width: 100%;
|
||||
// height: 100%;
|
||||
height: calc(100vh - 76px);
|
||||
overflow: auto;
|
||||
background-color: transparent;
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
// background-color: transparent;
|
||||
// &::-webkit-scrollbar-track {
|
||||
// background: transparent;
|
||||
// }
|
||||
}
|
||||
.checkbox {
|
||||
width: 100%;
|
||||
}
|
||||
.knowledgeName {
|
||||
width: 116px;
|
||||
max-width: 270px;
|
||||
}
|
||||
.embeddingId {
|
||||
width: 170px;
|
||||
@ -70,27 +72,17 @@
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
padding: 20px 16% 10px;
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mainMixin() {
|
||||
overflow: auto;
|
||||
padding: 20px 10px 10px;
|
||||
}
|
||||
|
||||
.largeMain {
|
||||
width: 100%;
|
||||
.mainMixin();
|
||||
}
|
||||
.main {
|
||||
width: 60%;
|
||||
.mainMixin();
|
||||
}
|
||||
|
||||
.graph {
|
||||
width: 40%;
|
||||
padding: 20px 10px 10px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.highlightContent {
|
||||
@ -103,6 +95,9 @@
|
||||
.documentReference {
|
||||
cursor: pointer;
|
||||
}
|
||||
.pagination {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
.answerWrapper {
|
||||
margin-top: 16px;
|
||||
@ -122,9 +117,9 @@
|
||||
border-start-start-radius: 30px !important;
|
||||
border-end-start-radius: 30px !important;
|
||||
}
|
||||
:global(.ant-input-group-addon) {
|
||||
background-color: transparent;
|
||||
}
|
||||
// :global(.ant-input-group-addon) {
|
||||
// background-color: transparent;
|
||||
// }
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
@ -138,7 +133,7 @@
|
||||
.globalInput {
|
||||
width: 600px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
top: 30%;
|
||||
z-index: 1;
|
||||
.input();
|
||||
}
|
||||
@ -187,3 +182,12 @@
|
||||
max-height: 40vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.mindMapFloatButton {
|
||||
top: 20%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
:global(.ant-float-btn-content, .ant-float-btn-icon) {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import FileIcon from '@/components/file-icon';
|
||||
import HightLightMarkdown from '@/components/highlight-markdown';
|
||||
import { ImageWithPopover } from '@/components/image';
|
||||
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 SvgIcon from '@/components/svg-icon';
|
||||
import {
|
||||
useNextFetchKnowledgeList,
|
||||
useSelectTestingResult,
|
||||
@ -15,6 +15,7 @@ import {
|
||||
Card,
|
||||
Divider,
|
||||
Flex,
|
||||
FloatButton,
|
||||
Input,
|
||||
Layout,
|
||||
List,
|
||||
@ -25,14 +26,16 @@ import {
|
||||
Space,
|
||||
Spin,
|
||||
Tag,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MarkdownContent from '../chat/markdown-content';
|
||||
import { useFetchBackgroundImage, useSendQuestion } from './hooks';
|
||||
import { useSendQuestion, useShowMindMapDrawer } from './hooks';
|
||||
import styles from './index.less';
|
||||
import MindMapDrawer from './mindmap-drawer';
|
||||
import SearchSidebar from './sidebar';
|
||||
|
||||
const { Content } = Layout;
|
||||
@ -56,29 +59,28 @@ const SearchPage = () => {
|
||||
answer,
|
||||
sendingLoading,
|
||||
relatedQuestions,
|
||||
mindMap,
|
||||
searchStr,
|
||||
loading,
|
||||
isFirstRender,
|
||||
selectedDocumentIds,
|
||||
isSearchStrEmpty,
|
||||
} = useSendQuestion(checkedWithoutEmbeddingIdList);
|
||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||
useClickDrawer();
|
||||
const imgUrl = useFetchBackgroundImage();
|
||||
const { pagination } = useGetPaginationWithRouter();
|
||||
const {
|
||||
mindMapVisible,
|
||||
hideMindMapModal,
|
||||
showMindMapModal,
|
||||
mindMapLoading,
|
||||
mindMap,
|
||||
} = useShowMindMapDrawer(checkedWithoutEmbeddingIdList, searchStr);
|
||||
|
||||
const onChange: PaginationProps['onChange'] = (pageNumber, pageSize) => {
|
||||
pagination.onChange?.(pageNumber, pageSize);
|
||||
handleTestChunk(selectedDocumentIds, pageNumber, pageSize);
|
||||
};
|
||||
|
||||
const isMindMapEmpty = useMemo(() => {
|
||||
return (
|
||||
(Array.isArray(mindMap?.children) && mindMap.children.length === 0) ||
|
||||
!Array.isArray(mindMap?.children)
|
||||
);
|
||||
}, [mindMap]);
|
||||
|
||||
const InputSearch = (
|
||||
<Search
|
||||
value={searchStr}
|
||||
@ -96,10 +98,7 @@ const SearchPage = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layout
|
||||
className={styles.searchPage}
|
||||
style={{ backgroundImage: `url(${imgUrl})` }}
|
||||
>
|
||||
<Layout className={styles.searchPage}>
|
||||
<SearchSidebar
|
||||
isFirstRender={isFirstRender}
|
||||
checkedList={checkedWithoutEmbeddingIdList}
|
||||
@ -108,20 +107,14 @@ const SearchPage = () => {
|
||||
<Layout className={isFirstRender ? styles.mainLayout : ''}>
|
||||
<Content>
|
||||
{isFirstRender ? (
|
||||
<Flex
|
||||
justify="center"
|
||||
align="center"
|
||||
className={styles.firstRenderContent}
|
||||
>
|
||||
<Flex justify="center" className={styles.firstRenderContent}>
|
||||
<Flex vertical align="center" gap={'large'}>
|
||||
{InputSearch}
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex className={styles.content}>
|
||||
<section
|
||||
className={isMindMapEmpty ? styles.largeMain : styles.main}
|
||||
>
|
||||
<section className={styles.main}>
|
||||
{InputSearch}
|
||||
<Card
|
||||
title={
|
||||
@ -226,28 +219,43 @@ const SearchPage = () => {
|
||||
{...pagination}
|
||||
total={total}
|
||||
onChange={onChange}
|
||||
className={styles.pagination}
|
||||
/>
|
||||
</section>
|
||||
<section
|
||||
className={isMindMapEmpty ? styles.hide : styles.graph}
|
||||
>
|
||||
<IndentedTree
|
||||
data={mindMap}
|
||||
show
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
></IndentedTree>
|
||||
</section>
|
||||
</Flex>
|
||||
)}
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
<PdfDrawer
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={documentId}
|
||||
chunk={selectedChunk}
|
||||
></PdfDrawer>
|
||||
{!isFirstRender &&
|
||||
!isSearchStrEmpty &&
|
||||
!isEmpty(checkedWithoutEmbeddingIdList) && (
|
||||
<Tooltip title={t('chunk.mind')} zIndex={1}>
|
||||
<FloatButton
|
||||
className={styles.mindMapFloatButton}
|
||||
onClick={showMindMapModal}
|
||||
icon={
|
||||
<SvgIcon name="paper-clip" width={24} height={30}></SvgIcon>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{visible && (
|
||||
<PdfDrawer
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={documentId}
|
||||
chunk={selectedChunk}
|
||||
></PdfDrawer>
|
||||
)}
|
||||
{mindMapVisible && (
|
||||
<MindMapDrawer
|
||||
visible={mindMapVisible}
|
||||
hideModal={hideMindMapModal}
|
||||
data={mindMap}
|
||||
loading={mindMapLoading}
|
||||
></MindMapDrawer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
36
web/src/pages/search/mindmap-drawer.tsx
Normal file
36
web/src/pages/search/mindmap-drawer.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import IndentedTree from '@/components/indented-tree/indented-tree';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { Drawer, Flex, Progress } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePendingMindMap } from './hooks';
|
||||
|
||||
interface IProps extends IModalProps<any> {
|
||||
data: any;
|
||||
}
|
||||
|
||||
const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const percent = usePendingMindMap();
|
||||
return (
|
||||
<Drawer
|
||||
title={t('chunk.mind')}
|
||||
onClose={hideModal}
|
||||
open={visible}
|
||||
width={'40vw'}
|
||||
>
|
||||
{loading ? (
|
||||
<Flex justify="center">
|
||||
<Progress type="circle" percent={percent} size={200} />
|
||||
</Flex>
|
||||
) : (
|
||||
<IndentedTree
|
||||
data={data}
|
||||
show
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
></IndentedTree>
|
||||
)}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default MindMapDrawer;
|
||||
@ -138,7 +138,7 @@ const SearchSidebar = ({
|
||||
[styles.transparentSearchSide]: isFirstRender,
|
||||
})}
|
||||
theme={'light'}
|
||||
width={240}
|
||||
width={'20%'}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<Tree
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user