소스 검색

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

### What problem does this PR solve?

feat: Display mindmap in drawer #2247

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
tags/v0.11.0
balibabu 1 년 전
부모
커밋
6a0702f55f
No account linked to committer's email address

+ 0
- 3
docker/nginx/ragflow.conf 파일 보기

@@ -15,9 +15,6 @@ server {
include proxy.conf;
}

location /HPImageArchive {
proxy_pass https://cn.bing.com;
}

location / {
index index.html;

+ 58
- 13
web/src/pages/search/hooks.ts 파일 보기

@@ -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));
};

+ 35
- 31
web/src/pages/search/index.less 파일 보기

@@ -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;
}
}

+ 46
- 38
web/src/pages/search/index.tsx 파일 보기

@@ -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
- 0
web/src/pages/search/mindmap-drawer.tsx 파일 보기

@@ -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;

+ 1
- 1
web/src/pages/search/sidebar.tsx 파일 보기

@@ -138,7 +138,7 @@ const SearchSidebar = ({
[styles.transparentSearchSide]: isFirstRender,
})}
theme={'light'}
width={240}
width={'20%'}
>
<Spin spinning={loading}>
<Tree

Loading…
취소
저장