Feat: Display the knowledge graph on the knowledge base page #4543 (#4587)

### What problem does this PR solve?

Feat: Display the knowledge graph on the knowledge base page #4543

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-01-22 19:43:27 +08:00
committed by GitHub
parent dd0ebbea35
commit c5c0dd2da0
31 changed files with 346 additions and 133 deletions

View File

@ -1,75 +0,0 @@
import IndentedTree from '@/components/indented-tree/indented-tree';
import { useFetchKnowledgeGraph } from '@/hooks/chunk-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { Flex, Modal, Segmented } from 'antd';
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import ForceGraph from './force-graph';
import styles from './index.less';
import { isDataExist } from './util';
enum SegmentedValue {
Graph = 'Graph',
Mind = 'Mind',
}
const KnowledgeGraphModal: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const { documentId } = useGetKnowledgeSearchParams();
const { data } = useFetchKnowledgeGraph(documentId);
const [value, setValue] = useState<SegmentedValue>(SegmentedValue.Graph);
const { t } = useTranslation();
const options = useMemo(() => {
return [
{ value: SegmentedValue.Graph, label: t('chunk.graph') },
{ value: SegmentedValue.Mind, label: t('chunk.mind') },
];
}, [t]);
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
useEffect(() => {
if (isDataExist(data)) {
setIsModalOpen(true);
}
}, [setIsModalOpen, data]);
return (
<Modal
title={t('chunk.graph')}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
width={'90vw'}
footer={null}
>
<section className={styles.modalContainer}>
<Flex justify="end">
<Segmented
size="large"
options={options}
value={value}
onChange={(v) => setValue(v as SegmentedValue)}
/>
</Flex>
<ForceGraph
data={data?.data?.graph}
show={value === SegmentedValue.Graph}
></ForceGraph>
<IndentedTree
data={data?.data?.mind_map}
show={value === SegmentedValue.Mind}
></IndentedTree>
</section>
</Modal>
);
};
export default KnowledgeGraphModal;

View File

@ -8,7 +8,6 @@ import ChunkCard from './components/chunk-card';
import CreatingModal from './components/chunk-creating-modal';
import ChunkToolBar from './components/chunk-toolbar';
import DocumentPreview from './components/document-preview/preview';
import KnowledgeGraphModal from './components/knowledge-graph/modal';
import {
useChangeChunkTextMode,
useDeleteChunkByIds,
@ -196,7 +195,6 @@ const Chunk = () => {
parserId={documentInfo.parser_id}
/>
)}
<KnowledgeGraphModal></KnowledgeGraphModal>
</>
);
};

View File

@ -39,7 +39,12 @@ const ParsingActionCell = ({
const onRmDocument = () => {
if (!isRunning) {
showDeleteConfirm({ onOk: () => removeDocument([documentId]) });
showDeleteConfirm({
onOk: () => removeDocument([documentId]),
content: record?.parser_config?.graphrag?.use_graphrag
? t('deleteDocumentConfirmContent')
: '',
});
}
};

View File

@ -24,7 +24,7 @@ const ForceGraph = ({ data, show }: IProps) => {
if (!isEmpty(data)) {
const graphData = data;
const mi = buildNodesAndCombos(graphData.nodes);
return { edges: graphData.links, ...mi };
return { edges: graphData.edges, ...mi };
}
return { nodes: [], edges: [] };
}, [data]);
@ -130,8 +130,8 @@ const ForceGraph = ({ data, show }: IProps) => {
ref={containerRef}
className={styles.forceContainer}
style={{
width: '90vw',
height: '80vh',
width: '100%',
height: '100%',
display: show ? 'block' : 'none',
}}
/>

View File

@ -3,8 +3,3 @@
border-radius: 10px !important;
}
}
.modalContainer {
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,15 @@
import { useFetchKnowledgeGraph } from '@/hooks/knowledge-hooks';
import React from 'react';
import ForceGraph from './force-graph';
const KnowledgeGraphModal: React.FC = () => {
const { data } = useFetchKnowledgeGraph();
return (
<section className={'w-full h-full'}>
<ForceGraph data={data?.graph} show></ForceGraph>
</section>
);
};
export default KnowledgeGraphModal;

View File

@ -13,6 +13,9 @@ import ParseConfiguration, {
showRaptorParseConfiguration,
showTagItems,
} from '@/components/parse-configuration';
import GraphRagItems, {
showGraphRagItems,
} from '@/components/parse-configuration/graph-rag-items';
import { useTranslate } from '@/hooks/common-hooks';
import { useHandleChunkMethodSelectChange } from '@/hooks/logic-hooks';
import { normFile } from '@/utils/file-util';
@ -149,6 +152,8 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => {
<ParseConfiguration></ParseConfiguration>
)}
{showGraphRagItems(parserId) && <GraphRagItems></GraphRagItems>}
{showTagItems(parserId) && <TagItems></TagItems>}
</>
);

View File

@ -1,7 +1,10 @@
import { ReactComponent as ConfigurationIcon } from '@/assets/svg/knowledge-configration.svg';
import { ReactComponent as DatasetIcon } from '@/assets/svg/knowledge-dataset.svg';
import { ReactComponent as TestingIcon } from '@/assets/svg/knowledge-testing.svg';
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/knowledge-hooks';
import {
useFetchKnowledgeBaseConfiguration,
useFetchKnowledgeGraph,
} from '@/hooks/knowledge-hooks';
import {
useGetKnowledgeSearchParams,
useSecondPathName,
@ -14,6 +17,8 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'umi';
import { KnowledgeRouteKey } from '../../constant';
import { isEmpty } from 'lodash';
import { GitGraph } from 'lucide-react';
import styles from './index.less';
const KnowledgeSidebar = () => {
@ -30,6 +35,8 @@ const KnowledgeSidebar = () => {
navigate(`/knowledge/${e.key}?id=${knowledgeId}`);
};
const { data } = useFetchKnowledgeGraph();
type MenuItem = Required<MenuProps>['items'][number];
const getItem = useCallback(
@ -54,7 +61,7 @@ const KnowledgeSidebar = () => {
);
const items: MenuItem[] = useMemo(() => {
return [
const list = [
getItem(
KnowledgeRouteKey.Dataset, // TODO: Change icon color when selected
KnowledgeRouteKey.Dataset,
@ -71,7 +78,19 @@ const KnowledgeSidebar = () => {
<ConfigurationIcon />,
),
];
}, [getItem]);
if (!isEmpty(data.graph)) {
list.push(
getItem(
KnowledgeRouteKey.KnowledgeGraph,
KnowledgeRouteKey.KnowledgeGraph,
<GitGraph />,
),
);
}
return list;
}, [data, getItem]);
useEffect(() => {
if (windowWidth.width > 957) {
@ -81,7 +100,6 @@ const KnowledgeSidebar = () => {
}
}, [windowWidth.width]);
// 标记一下
useEffect(() => {
const widthSize = () => {
const width = getWidth();

View File

@ -6,6 +6,7 @@ import { Button, Card, Divider, Flex, Form, Input } from 'antd';
import { FormInstance } from 'antd/lib';
import { LabelWordCloud } from './label-word-cloud';
import { UseKnowledgeGraphItem } from '@/components/use-knowledge-graph-item';
import styles from './index.less';
type FieldType = {
@ -38,6 +39,7 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
<Form name="testing" layout="vertical" form={form}>
<SimilaritySlider isTooltipShown></SimilaritySlider>
<Rerank></Rerank>
<UseKnowledgeGraphItem filedName={['use_kg']}></UseKnowledgeGraphItem>
<Card size="small" title={t('testText')}>
<Form.Item<FieldType>
name={'question'}

View File

@ -30,6 +30,7 @@ import { EditableCell, EditableRow } from './editable-cell';
import Rerank from '@/components/rerank';
import TopNItem from '@/components/top-n-item';
import { UseKnowledgeGraphItem } from '@/components/use-knowledge-graph-item';
import { useTranslate } from '@/hooks/common-hooks';
import { useSelectPromptConfigParameters } from '../hooks';
import styles from './index.less';
@ -168,6 +169,9 @@ const PromptEngine = (
>
<Switch></Switch>
</Form.Item>
<UseKnowledgeGraphItem
filedName={['prompt_config', 'use_kg']}
></UseKnowledgeGraphItem>
<Rerank></Rerank>
<section className={classNames(styles.variableContainer)}>
<Row align={'middle'} justify="end">

View File

@ -41,7 +41,7 @@ const KnowledgeCard = ({ item }: IProps) => {
return (
<Badge.Ribbon
text={item?.nickname}
color={userInfo.nickname === item.nickname ? '#1677ff' : 'pink'}
color={userInfo?.nickname === item?.nickname ? '#1677ff' : 'pink'}
className={classNames(styles.ribbon, {
[styles.hideRibbon]: item.permission !== 'team',
})}