### What problem does this PR solve? feat: Add KnowledgeGraphModal #162 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.9.0
| @@ -14,25 +14,59 @@ const ParserListMap = new Map([ | |||
| 'presentation', | |||
| 'one', | |||
| 'qa', | |||
| 'knowledge_graph', | |||
| ], | |||
| ], | |||
| [ | |||
| ['doc', 'docx'], | |||
| ['naive', 'resume', 'book', 'laws', 'one', 'qa', 'manual'], | |||
| [ | |||
| 'naive', | |||
| 'resume', | |||
| 'book', | |||
| 'laws', | |||
| 'one', | |||
| 'qa', | |||
| 'manual', | |||
| 'knowledge_graph', | |||
| ], | |||
| ], | |||
| [ | |||
| ['xlsx', 'xls'], | |||
| ['naive', 'qa', 'table', 'one'], | |||
| ['naive', 'qa', 'table', 'one', 'knowledge_graph'], | |||
| ], | |||
| [['ppt', 'pptx'], ['presentation']], | |||
| [ | |||
| ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif', 'tiff', 'webp', 'svg', 'ico'], | |||
| ['picture'], | |||
| ], | |||
| [['txt'], ['naive', 'resume', 'book', 'laws', 'one', 'qa', 'table']], | |||
| [['csv'], ['naive', 'resume', 'book', 'laws', 'one', 'qa', 'table']], | |||
| [['md'], ['naive', 'qa']], | |||
| [['json'], ['naive']], | |||
| [ | |||
| ['txt'], | |||
| [ | |||
| 'naive', | |||
| 'resume', | |||
| 'book', | |||
| 'laws', | |||
| 'one', | |||
| 'qa', | |||
| 'table', | |||
| 'knowledge_graph', | |||
| ], | |||
| ], | |||
| [ | |||
| ['csv'], | |||
| [ | |||
| 'naive', | |||
| 'resume', | |||
| 'book', | |||
| 'laws', | |||
| 'one', | |||
| 'qa', | |||
| 'table', | |||
| 'knowledge_graph', | |||
| ], | |||
| ], | |||
| [['md'], ['naive', 'qa', 'knowledge_graph']], | |||
| [['json'], ['naive', 'knowledge_graph']], | |||
| ]); | |||
| const getParserList = ( | |||
| @@ -119,7 +119,7 @@ const ChunkMethodModal: React.FC<IProps> = ({ | |||
| <Space size={[0, 8]} wrap> | |||
| <Form.Item label={t('chunkMethod')} className={styles.chunkMethod}> | |||
| <Select | |||
| style={{ width: 120 }} | |||
| style={{ width: 160 }} | |||
| onChange={handleChange} | |||
| value={selectedTag} | |||
| options={parserList} | |||
| @@ -206,3 +206,22 @@ export const useFetchChunk = (chunkId?: string): ResponseType<any> => { | |||
| return data; | |||
| }; | |||
| export const useFetchKnowledgeGraph = (): ResponseType<any> => { | |||
| const { documentId } = useGetKnowledgeSearchParams(); | |||
| const { data } = useQuery({ | |||
| queryKey: ['fetchKnowledgeGraph', documentId], | |||
| initialData: true, | |||
| gcTime: 0, | |||
| queryFn: async () => { | |||
| const data = await kbService.knowledge_graph({ | |||
| doc_id: documentId, | |||
| }); | |||
| return data; | |||
| }, | |||
| }); | |||
| return data; | |||
| }; | |||
| @@ -1,141 +0,0 @@ | |||
| import type { InputRef } from 'antd'; | |||
| import { Input, Space, Tag, Tooltip, theme } from 'antd'; | |||
| import React, { useEffect, useRef, useState } from 'react'; | |||
| interface EditTagsProps { | |||
| tags: any[]; | |||
| setTags: (tags: any[]) => void; | |||
| } | |||
| const EditTag: React.FC<EditTagsProps> = ({ tags, setTags }) => { | |||
| const { token } = theme.useToken(); | |||
| const [inputVisible, setInputVisible] = useState(false); | |||
| const [inputValue, setInputValue] = useState(''); | |||
| const [editInputIndex, setEditInputIndex] = useState(-1); | |||
| const [editInputValue, setEditInputValue] = useState(''); | |||
| const inputRef = useRef<InputRef>(null); | |||
| const editInputRef = useRef<InputRef>(null); | |||
| useEffect(() => { | |||
| if (inputVisible) { | |||
| inputRef.current?.focus(); | |||
| } | |||
| }, [inputVisible]); | |||
| useEffect(() => { | |||
| editInputRef.current?.focus(); | |||
| }, [editInputValue]); | |||
| const handleClose = (removedTag: string) => { | |||
| const newTags = tags.filter((tag) => tag !== removedTag); | |||
| console.log(newTags); | |||
| setTags(newTags); | |||
| }; | |||
| const showInput = () => { | |||
| setInputVisible(true); | |||
| }; | |||
| const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||
| setInputValue(e.target.value); | |||
| }; | |||
| const handleInputConfirm = () => { | |||
| if (inputValue && !tags.includes(inputValue)) { | |||
| setTags([...tags, inputValue]); | |||
| } | |||
| setInputVisible(false); | |||
| setInputValue(''); | |||
| }; | |||
| const handleEditInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||
| setEditInputValue(e.target.value); | |||
| }; | |||
| const handleEditInputConfirm = () => { | |||
| const newTags = [...tags]; | |||
| newTags[editInputIndex] = editInputValue; | |||
| setTags(newTags); | |||
| setEditInputIndex(-1); | |||
| setEditInputValue(''); | |||
| }; | |||
| const tagInputStyle: React.CSSProperties = { | |||
| width: 64, | |||
| height: 22, | |||
| marginInlineEnd: 8, | |||
| verticalAlign: 'top', | |||
| }; | |||
| const tagPlusStyle: React.CSSProperties = { | |||
| height: 22, | |||
| background: token.colorBgContainer, | |||
| borderStyle: 'dashed', | |||
| }; | |||
| return ( | |||
| <Space size={[0, 8]} wrap> | |||
| {tags.map((tag, index) => { | |||
| if (editInputIndex === index) { | |||
| return ( | |||
| <Input | |||
| ref={editInputRef} | |||
| key={tag} | |||
| size="small" | |||
| style={tagInputStyle} | |||
| value={editInputValue} | |||
| onChange={handleEditInputChange} | |||
| onBlur={handleEditInputConfirm} | |||
| onPressEnter={handleEditInputConfirm} | |||
| /> | |||
| ); | |||
| } | |||
| const isLongTag = tag.length > 20; | |||
| const tagElem = ( | |||
| <Tag | |||
| key={tag} | |||
| closable={index !== 0} | |||
| style={{ userSelect: 'none' }} | |||
| onClose={() => handleClose(tag)} | |||
| > | |||
| <span | |||
| onDoubleClick={(e) => { | |||
| if (index !== 0) { | |||
| setEditInputIndex(index); | |||
| setEditInputValue(tag); | |||
| e.preventDefault(); | |||
| } | |||
| }} | |||
| > | |||
| {isLongTag ? `${tag.slice(0, 20)}...` : tag} | |||
| </span> | |||
| </Tag> | |||
| ); | |||
| return isLongTag ? ( | |||
| <Tooltip title={tag} key={tag}> | |||
| {tagElem} | |||
| </Tooltip> | |||
| ) : ( | |||
| tagElem | |||
| ); | |||
| })} | |||
| {inputVisible ? ( | |||
| <Input | |||
| ref={inputRef} | |||
| type="text" | |||
| size="small" | |||
| style={tagInputStyle} | |||
| value={inputValue} | |||
| onChange={handleInputChange} | |||
| onBlur={handleInputConfirm} | |||
| onPressEnter={handleInputConfirm} | |||
| /> | |||
| ) : ( | |||
| <Tag style={tagPlusStyle} onClick={showInput}> | |||
| 添加关键词 | |||
| </Tag> | |||
| )} | |||
| </Space> | |||
| ); | |||
| }; | |||
| export default EditTag; | |||
| @@ -0,0 +1,241 @@ | |||
| const nodes = [ | |||
| { | |||
| type: '"ORGANIZATION"', | |||
| description: | |||
| '"厦门象屿是一家公司,其营业收入和市场占有率在2018年至2022年间有所变化。"', | |||
| source_id: '0', | |||
| id: '"厦门象屿"', | |||
| }, | |||
| { | |||
| type: '"EVENT"', | |||
| description: | |||
| '"2018年是一个时间点,标志着厦门象屿营业收入和市场占有率的记录开始。"', | |||
| source_id: '0', | |||
| entity_type: '"EVENT"', | |||
| id: '"2018"', | |||
| }, | |||
| { | |||
| type: '"EVENT"', | |||
| description: | |||
| '"2019年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"', | |||
| source_id: '0', | |||
| entity_type: '"EVENT"', | |||
| id: '"2019"', | |||
| }, | |||
| { | |||
| type: '"EVENT"', | |||
| description: | |||
| '"2020年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"', | |||
| source_id: '0', | |||
| entity_type: '"EVENT"', | |||
| id: '"2020"', | |||
| }, | |||
| { | |||
| type: '"EVENT"', | |||
| description: | |||
| '"2021年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"', | |||
| source_id: '0', | |||
| entity_type: '"EVENT"', | |||
| id: '"2021"', | |||
| }, | |||
| { | |||
| type: '"EVENT"', | |||
| description: | |||
| '"2022年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"', | |||
| source_id: '0', | |||
| entity_type: '"EVENT"', | |||
| id: '"2022"', | |||
| }, | |||
| { | |||
| type: '"ORGANIZATION"', | |||
| description: | |||
| '"厦门象屿股份有限公司是一家公司,中文简称为厦门象屿,外文名称为Xiamen Xiangyu Co.,Ltd.,外文名称缩写为Xiangyu,法定代表人为邓启东。"', | |||
| source_id: '1', | |||
| id: '"厦门象屿股份有限公司"', | |||
| }, | |||
| { | |||
| type: '"PERSON"', | |||
| description: '"邓启东是厦门象屿股份有限公司的法定代表人。"', | |||
| source_id: '1', | |||
| entity_type: '"PERSON"', | |||
| id: '"邓启东"', | |||
| }, | |||
| { | |||
| type: '"GEO"', | |||
| description: '"厦门是一个地理位置,与厦门象屿股份有限公司相关。"', | |||
| source_id: '1', | |||
| entity_type: '"GEO"', | |||
| id: '"厦门"', | |||
| }, | |||
| { | |||
| type: '"PERSON"', | |||
| description: | |||
| '"廖杰 is the Board Secretary, responsible for handling board-related matters and communications."', | |||
| source_id: '2', | |||
| id: '"廖杰"', | |||
| }, | |||
| { | |||
| type: '"PERSON"', | |||
| description: | |||
| '"史经洋 is the Securities Affairs Representative, responsible for handling securities-related matters and communications."', | |||
| source_id: '2', | |||
| entity_type: '"PERSON"', | |||
| id: '"史经洋"', | |||
| }, | |||
| { | |||
| type: '"GEO"', | |||
| description: | |||
| '"A geographic location in Xiamen, specifically in the Free Trade Zone, where the company\'s office is situated."', | |||
| source_id: '2', | |||
| entity_type: '"GEO"', | |||
| id: '"厦门市湖里区自由贸易试验区厦门片区"', | |||
| }, | |||
| { | |||
| type: '"GEO"', | |||
| description: | |||
| '"The building where the company\'s office is located, situated at Xiangyu Road, Xiamen."', | |||
| source_id: '2', | |||
| entity_type: '"GEO"', | |||
| id: '"象屿集团大厦"', | |||
| }, | |||
| { | |||
| type: '"EVENT"', | |||
| description: | |||
| '"Refers to the year 2021, used for comparing financial metrics with the year 2022."', | |||
| source_id: '3', | |||
| id: '"2021年"', | |||
| }, | |||
| { | |||
| type: '"EVENT"', | |||
| description: | |||
| '"Refers to the year 2022, used for presenting current financial metrics and comparing them with the year 2021."', | |||
| source_id: '3', | |||
| entity_type: '"EVENT"', | |||
| id: '"2022年"', | |||
| }, | |||
| { | |||
| type: '"EVENT"', | |||
| description: | |||
| '"Indicates the focus on key financial metrics in the table, such as weighted averages and percentages."', | |||
| source_id: '3', | |||
| entity_type: '"EVENT"', | |||
| id: '"主要财务指标"', | |||
| }, | |||
| ].map(({ type, ...x }) => ({ ...x })); | |||
| const edges = [ | |||
| { | |||
| weight: 2.0, | |||
| description: '"厦门象屿在2018年的营业收入和市场占有率被记录。"', | |||
| source_id: '0', | |||
| source: '"厦门象屿"', | |||
| target: '"2018"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: '"厦门象屿在2019年的营业收入和市场占有率有所变化。"', | |||
| source_id: '0', | |||
| source: '"厦门象屿"', | |||
| target: '"2019"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: '"厦门象屿在2020年的营业收入和市场占有率有所变化。"', | |||
| source_id: '0', | |||
| source: '"厦门象屿"', | |||
| target: '"2020"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: '"厦门象屿在2021年的营业收入和市场占有率有所变化。"', | |||
| source_id: '0', | |||
| source: '"厦门象屿"', | |||
| target: '"2021"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: '"厦门象屿在2022年的营业收入和市场占有率有所变化。"', | |||
| source_id: '0', | |||
| source: '"厦门象屿"', | |||
| target: '"2022"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: '"厦门象屿股份有限公司的法定代表人是邓启东。"', | |||
| source_id: '1', | |||
| source: '"厦门象屿股份有限公司"', | |||
| target: '"邓启东"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: '"厦门象屿股份有限公司位于厦门。"', | |||
| source_id: '1', | |||
| source: '"厦门象屿股份有限公司"', | |||
| target: '"厦门"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: | |||
| '"廖杰\'s office is located in the Xiangyu Group Building, indicating his workplace."', | |||
| source_id: '2', | |||
| source: '"廖杰"', | |||
| target: '"象屿集团大厦"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: | |||
| '"廖杰 works in the Xiamen Free Trade Zone, a specific area within Xiamen."', | |||
| source_id: '2', | |||
| source: '"廖杰"', | |||
| target: '"厦门市湖里区自由贸易试验区厦门片区"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: | |||
| '"史经洋\'s office is also located in the Xiangyu Group Building, indicating his workplace."', | |||
| source_id: '2', | |||
| source: '"史经洋"', | |||
| target: '"象屿集团大厦"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: | |||
| '"史经洋 works in the Xiamen Free Trade Zone, a specific area within Xiamen."', | |||
| source_id: '2', | |||
| source: '"史经洋"', | |||
| target: '"厦门市湖里区自由贸易试验区厦门片区"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: | |||
| '"The years 2021 and 2022 are related as they are used for comparing financial metrics, showing changes and adjustments over time."', | |||
| source_id: '3', | |||
| source: '"2021年"', | |||
| target: '"2022年"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: | |||
| '"The \'主要财务指标\' is related to the year 2021 as it provides the basis for financial comparisons and adjustments."', | |||
| source_id: '3', | |||
| source: '"2021年"', | |||
| target: '"主要财务指标"', | |||
| }, | |||
| { | |||
| weight: 2.0, | |||
| description: | |||
| '"The \'主要财务指标\' is related to the year 2022 as it presents the current financial metrics and their changes compared to 2021."', | |||
| source_id: '3', | |||
| source: '"2022年"', | |||
| target: '"主要财务指标"', | |||
| }, | |||
| ]; | |||
| export const graphData = { | |||
| directed: false, | |||
| multigraph: false, | |||
| graph: {}, | |||
| nodes, | |||
| edges, | |||
| combos: [], | |||
| }; | |||
| @@ -0,0 +1,123 @@ | |||
| import { Graph } from '@antv/g6'; | |||
| import { useSize } from 'ahooks'; | |||
| import { useCallback, useEffect, useMemo, useRef } from 'react'; | |||
| import { graphData } from './constant'; | |||
| import { Converter, buildNodesAndCombos, isDataExist } from './util'; | |||
| import { useFetchKnowledgeGraph } from '@/hooks/chunk-hooks'; | |||
| import styles from './index.less'; | |||
| const converter = new Converter(); | |||
| const nextData = converter.buildNodesAndCombos( | |||
| graphData.nodes, | |||
| graphData.edges, | |||
| ); | |||
| console.log('🚀 ~ nextData:', nextData); | |||
| const finalData = { ...graphData, ...nextData }; | |||
| const ForceGraph = () => { | |||
| const containerRef = useRef<HTMLDivElement>(null); | |||
| const size = useSize(containerRef); | |||
| const { data } = useFetchKnowledgeGraph(); | |||
| const nextData = useMemo(() => { | |||
| if (isDataExist(data)) { | |||
| const graphData = data.data; | |||
| const mi = buildNodesAndCombos(graphData.nodes); | |||
| return { edges: graphData.links, ...mi }; | |||
| } | |||
| return { nodes: [], edges: [] }; | |||
| }, [data]); | |||
| console.log('🚀 ~ nextData ~ nextData:', nextData); | |||
| const render = useCallback(() => { | |||
| const graph = new Graph({ | |||
| container: containerRef.current!, | |||
| autoFit: 'view', | |||
| behaviors: [ | |||
| 'drag-element', | |||
| 'drag-canvas', | |||
| 'zoom-canvas', | |||
| 'collapse-expand', | |||
| { | |||
| type: 'hover-activate', | |||
| degree: 1, // 👈🏻 Activate relations. | |||
| }, | |||
| ], | |||
| plugins: [ | |||
| { | |||
| type: 'tooltip', | |||
| getContent: (e, items) => { | |||
| if (items.every((x) => x?.description)) { | |||
| let result = ``; | |||
| items.forEach((item) => { | |||
| result += `<h3>${item?.id}</h3>`; | |||
| if (item?.description) { | |||
| result += `<p>${item?.description}</p>`; | |||
| } | |||
| }); | |||
| return result; | |||
| } | |||
| return undefined; | |||
| }, | |||
| }, | |||
| ], | |||
| layout: { | |||
| type: 'combo-combined', | |||
| preventOverlap: true, | |||
| comboPadding: 1, | |||
| spacing: 20, | |||
| }, | |||
| node: { | |||
| style: { | |||
| size: 20, | |||
| labelText: (d) => d.id, | |||
| labelPadding: 30, | |||
| // labelOffsetX: 20, | |||
| // labelOffsetY: 5, | |||
| labelPlacement: 'center', | |||
| labelWordWrap: true, | |||
| }, | |||
| palette: { | |||
| type: 'group', | |||
| field: (d) => d.combo, | |||
| }, | |||
| // state: { | |||
| // highlight: { | |||
| // fill: '#D580FF', | |||
| // halo: true, | |||
| // lineWidth: 0, | |||
| // }, | |||
| // dim: { | |||
| // fill: '#99ADD1', | |||
| // }, | |||
| // }, | |||
| }, | |||
| edge: { | |||
| style: (model) => { | |||
| const { size, color } = model.data; | |||
| return { | |||
| stroke: color || '#99ADD1', | |||
| lineWidth: size || 1, | |||
| }; | |||
| }, | |||
| }, | |||
| }); | |||
| graph.setData(nextData); | |||
| graph.render(); | |||
| }, [nextData]); | |||
| useEffect(() => { | |||
| if (isDataExist(data)) { | |||
| render(); | |||
| } | |||
| }, [data, render]); | |||
| return <div ref={containerRef} className={styles.forceContainer} />; | |||
| }; | |||
| export default ForceGraph; | |||
| @@ -0,0 +1,9 @@ | |||
| .forceContainer { | |||
| width: 100%; | |||
| height: 100%; | |||
| } | |||
| .modalContainer { | |||
| width: 90vw; | |||
| height: 80vh; | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| const KnowledgeGraph = () => { | |||
| return <div>KnowledgeGraph</div>; | |||
| }; | |||
| export default KnowledgeGraph; | |||
| @@ -0,0 +1,43 @@ | |||
| import { useFetchKnowledgeGraph } from '@/hooks/chunk-hooks'; | |||
| import { Modal } from 'antd'; | |||
| import React, { useEffect, useState } from 'react'; | |||
| import ForceGraph from './force-graph'; | |||
| import styles from './index.less'; | |||
| const KnowledgeGraphModal: React.FC = () => { | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const { data } = useFetchKnowledgeGraph(); | |||
| const handleOk = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| const handleCancel = () => { | |||
| setIsModalOpen(false); | |||
| }; | |||
| useEffect(() => { | |||
| if (data?.data && typeof data?.data !== 'boolean') { | |||
| console.log('🚀 ~ useEffect ~ data:', data); | |||
| setIsModalOpen(true); | |||
| } | |||
| }, [setIsModalOpen, data]); | |||
| return ( | |||
| <Modal | |||
| title="Knowledge Graph" | |||
| open={isModalOpen} | |||
| onOk={handleOk} | |||
| onCancel={handleCancel} | |||
| width={'90vw'} | |||
| footer={null} | |||
| > | |||
| <section className={styles.modalContainer}> | |||
| <ForceGraph></ForceGraph> | |||
| </section> | |||
| </Modal> | |||
| ); | |||
| }; | |||
| export default KnowledgeGraphModal; | |||
| @@ -0,0 +1,80 @@ | |||
| class KeyGenerator { | |||
| idx = 0; | |||
| chars: string[] = []; | |||
| constructor() { | |||
| const chars = Array(26) | |||
| .fill(1) | |||
| .map((x, idx) => String.fromCharCode(97 + idx)); // 26 char | |||
| this.chars = chars; | |||
| } | |||
| generateKey() { | |||
| const key = this.chars[this.idx]; | |||
| this.idx++; | |||
| return key; | |||
| } | |||
| } | |||
| // Classify nodes based on edge relationships | |||
| export class Converter { | |||
| keyGenerator; | |||
| dict: Record<string, string> = {}; // key is node id, value is combo | |||
| constructor() { | |||
| this.keyGenerator = new KeyGenerator(); | |||
| } | |||
| buildDict(edges: { source: string; target: string }[]) { | |||
| edges.forEach((x) => { | |||
| if (this.dict[x.source] && !this.dict[x.target]) { | |||
| this.dict[x.target] = this.dict[x.source]; | |||
| } else if (!this.dict[x.source] && this.dict[x.target]) { | |||
| this.dict[x.source] = this.dict[x.target]; | |||
| } else if (!this.dict[x.source] && !this.dict[x.target]) { | |||
| this.dict[x.source] = this.dict[x.target] = | |||
| this.keyGenerator.generateKey(); | |||
| } | |||
| }); | |||
| return this.dict; | |||
| } | |||
| buildNodesAndCombos(nodes: any[], edges: any[]) { | |||
| this.buildDict(edges); | |||
| const nextNodes = nodes.map((x) => ({ ...x, combo: this.dict[x.id] })); | |||
| const combos = Object.values(this.dict).reduce<any[]>((pre, cur) => { | |||
| if (pre.every((x) => x.id !== cur)) { | |||
| pre.push({ | |||
| id: cur, | |||
| data: { | |||
| label: `Combo ${cur}`, | |||
| }, | |||
| }); | |||
| } | |||
| return pre; | |||
| }, []); | |||
| return { nodes: nextNodes, combos }; | |||
| } | |||
| } | |||
| export const isDataExist = (data: any) => { | |||
| return data?.data && typeof data?.data !== 'boolean'; | |||
| }; | |||
| export const buildNodesAndCombos = (nodes: any[]) => { | |||
| const combos: any[] = []; | |||
| const nextNodes = nodes.map((x) => { | |||
| const combo = Array.isArray(x?.communities) ? x.communities[0] : undefined; | |||
| if (combo && combos.every((y) => y.id !== combo)) { | |||
| combos.push({ | |||
| id: combo, | |||
| data: { | |||
| label: `Combo ${combo}`, | |||
| }, | |||
| }); | |||
| } | |||
| return { | |||
| ...x, | |||
| combo, | |||
| }; | |||
| }); | |||
| return { nodes: nextNodes, combos }; | |||
| }; | |||
| @@ -8,6 +8,7 @@ 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, | |||
| @@ -52,7 +53,6 @@ const Chunk = () => { | |||
| ) => { | |||
| setSelectedChunkIds([]); | |||
| pagination.onChange?.(page, size); | |||
| // getChunkList(); | |||
| }; | |||
| const selectAllChunk = useCallback( | |||
| @@ -110,16 +110,9 @@ const Chunk = () => { | |||
| doc_id: documentId, | |||
| }); | |||
| if (!chunkIds && resCode === 0) { | |||
| // getChunkList(); | |||
| } | |||
| }, | |||
| [ | |||
| switchChunk, | |||
| documentId, | |||
| // getChunkList, | |||
| selectedChunkIds, | |||
| showSelectedChunkWarning, | |||
| ], | |||
| [switchChunk, documentId, selectedChunkIds, showSelectedChunkWarning], | |||
| ); | |||
| const { highlights, setWidthAndHeight } = | |||
| @@ -202,6 +195,7 @@ const Chunk = () => { | |||
| onOk={onChunkUpdatingOk} | |||
| /> | |||
| )} | |||
| <KnowledgeGraphModal></KnowledgeGraphModal> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -27,6 +27,7 @@ const { | |||
| get_document_file, | |||
| document_upload, | |||
| web_crawl, | |||
| knowledge_graph, | |||
| } = api; | |||
| const methods = { | |||
| @@ -121,6 +122,10 @@ const methods = { | |||
| url: retrieval_test, | |||
| method: 'post', | |||
| }, | |||
| knowledge_graph: { | |||
| url: knowledge_graph, | |||
| method: 'get', | |||
| }, | |||
| }; | |||
| const kbService = registerServer<keyof typeof methods>(methods, request); | |||
| @@ -1,94 +1,95 @@ | |||
| let api_host = `/v1`; | |||
| export { api_host }; | |||
| export default { | |||
| // user | |||
| login: `${api_host}/user/login`, | |||
| logout: `${api_host}/user/logout`, | |||
| register: `${api_host}/user/register`, | |||
| setting: `${api_host}/user/setting`, | |||
| user_info: `${api_host}/user/info`, | |||
| tenant_info: `${api_host}/user/tenant_info`, | |||
| set_tenant_info: `${api_host}/user/set_tenant_info`, | |||
| // llm model | |||
| factories_list: `${api_host}/llm/factories`, | |||
| llm_list: `${api_host}/llm/list`, | |||
| my_llm: `${api_host}/llm/my_llms`, | |||
| set_api_key: `${api_host}/llm/set_api_key`, | |||
| add_llm: `${api_host}/llm/add_llm`, | |||
| delete_llm: `${api_host}/llm/delete_llm`, | |||
| // knowledge base | |||
| kb_list: `${api_host}/kb/list`, | |||
| create_kb: `${api_host}/kb/create`, | |||
| update_kb: `${api_host}/kb/update`, | |||
| rm_kb: `${api_host}/kb/rm`, | |||
| get_kb_detail: `${api_host}/kb/detail`, | |||
| // chunk | |||
| chunk_list: `${api_host}/chunk/list`, | |||
| create_chunk: `${api_host}/chunk/create`, | |||
| set_chunk: `${api_host}/chunk/set`, | |||
| get_chunk: `${api_host}/chunk/get`, | |||
| switch_chunk: `${api_host}/chunk/switch`, | |||
| rm_chunk: `${api_host}/chunk/rm`, | |||
| retrieval_test: `${api_host}/chunk/retrieval_test`, | |||
| // document | |||
| upload: `${api_host}/document/upload`, | |||
| get_document_list: `${api_host}/document/list`, | |||
| document_change_status: `${api_host}/document/change_status`, | |||
| document_rm: `${api_host}/document/rm`, | |||
| document_rename: `${api_host}/document/rename`, | |||
| document_create: `${api_host}/document/create`, | |||
| document_run: `${api_host}/document/run`, | |||
| document_change_parser: `${api_host}/document/change_parser`, | |||
| document_thumbnails: `${api_host}/document/thumbnails`, | |||
| get_document_file: `${api_host}/document/get`, | |||
| document_upload: `${api_host}/document/upload`, | |||
| web_crawl: `${api_host}/document/web_crawl`, | |||
| // chat | |||
| setDialog: `${api_host}/dialog/set`, | |||
| getDialog: `${api_host}/dialog/get`, | |||
| removeDialog: `${api_host}/dialog/rm`, | |||
| listDialog: `${api_host}/dialog/list`, | |||
| setConversation: `${api_host}/conversation/set`, | |||
| getConversation: `${api_host}/conversation/get`, | |||
| listConversation: `${api_host}/conversation/list`, | |||
| removeConversation: `${api_host}/conversation/rm`, | |||
| completeConversation: `${api_host}/conversation/completion`, | |||
| // chat for external | |||
| createToken: `${api_host}/api/new_token`, | |||
| listToken: `${api_host}/api/token_list`, | |||
| removeToken: `${api_host}/api/rm`, | |||
| getStats: `${api_host}/api/stats`, | |||
| createExternalConversation: `${api_host}/api/new_conversation`, | |||
| getExternalConversation: `${api_host}/api/conversation`, | |||
| completeExternalConversation: `${api_host}/api/completion`, | |||
| // file manager | |||
| listFile: `${api_host}/file/list`, | |||
| uploadFile: `${api_host}/file/upload`, | |||
| removeFile: `${api_host}/file/rm`, | |||
| renameFile: `${api_host}/file/rename`, | |||
| getAllParentFolder: `${api_host}/file/all_parent_folder`, | |||
| createFolder: `${api_host}/file/create`, | |||
| connectFileToKnowledge: `${api_host}/file2document/convert`, | |||
| getFile: `${api_host}/file/get`, | |||
| // system | |||
| getSystemVersion: `${api_host}/system/version`, | |||
| getSystemStatus: `${api_host}/system/status`, | |||
| // flow | |||
| listTemplates: `${api_host}/canvas/templates`, | |||
| listCanvas: `${api_host}/canvas/list`, | |||
| getCanvas: `${api_host}/canvas/get`, | |||
| removeCanvas: `${api_host}/canvas/rm`, | |||
| setCanvas: `${api_host}/canvas/set`, | |||
| resetCanvas: `${api_host}/canvas/reset`, | |||
| runCanvas: `${api_host}/canvas/completion`, | |||
| }; | |||
| let api_host = `/v1`; | |||
| export { api_host }; | |||
| export default { | |||
| // user | |||
| login: `${api_host}/user/login`, | |||
| logout: `${api_host}/user/logout`, | |||
| register: `${api_host}/user/register`, | |||
| setting: `${api_host}/user/setting`, | |||
| user_info: `${api_host}/user/info`, | |||
| tenant_info: `${api_host}/user/tenant_info`, | |||
| set_tenant_info: `${api_host}/user/set_tenant_info`, | |||
| // llm model | |||
| factories_list: `${api_host}/llm/factories`, | |||
| llm_list: `${api_host}/llm/list`, | |||
| my_llm: `${api_host}/llm/my_llms`, | |||
| set_api_key: `${api_host}/llm/set_api_key`, | |||
| add_llm: `${api_host}/llm/add_llm`, | |||
| delete_llm: `${api_host}/llm/delete_llm`, | |||
| // knowledge base | |||
| kb_list: `${api_host}/kb/list`, | |||
| create_kb: `${api_host}/kb/create`, | |||
| update_kb: `${api_host}/kb/update`, | |||
| rm_kb: `${api_host}/kb/rm`, | |||
| get_kb_detail: `${api_host}/kb/detail`, | |||
| // chunk | |||
| chunk_list: `${api_host}/chunk/list`, | |||
| create_chunk: `${api_host}/chunk/create`, | |||
| set_chunk: `${api_host}/chunk/set`, | |||
| get_chunk: `${api_host}/chunk/get`, | |||
| switch_chunk: `${api_host}/chunk/switch`, | |||
| rm_chunk: `${api_host}/chunk/rm`, | |||
| retrieval_test: `${api_host}/chunk/retrieval_test`, | |||
| knowledge_graph: `${api_host}/chunk/knowledge_graph`, | |||
| // document | |||
| upload: `${api_host}/document/upload`, | |||
| get_document_list: `${api_host}/document/list`, | |||
| document_change_status: `${api_host}/document/change_status`, | |||
| document_rm: `${api_host}/document/rm`, | |||
| document_rename: `${api_host}/document/rename`, | |||
| document_create: `${api_host}/document/create`, | |||
| document_run: `${api_host}/document/run`, | |||
| document_change_parser: `${api_host}/document/change_parser`, | |||
| document_thumbnails: `${api_host}/document/thumbnails`, | |||
| get_document_file: `${api_host}/document/get`, | |||
| document_upload: `${api_host}/document/upload`, | |||
| web_crawl: `${api_host}/document/web_crawl`, | |||
| // chat | |||
| setDialog: `${api_host}/dialog/set`, | |||
| getDialog: `${api_host}/dialog/get`, | |||
| removeDialog: `${api_host}/dialog/rm`, | |||
| listDialog: `${api_host}/dialog/list`, | |||
| setConversation: `${api_host}/conversation/set`, | |||
| getConversation: `${api_host}/conversation/get`, | |||
| listConversation: `${api_host}/conversation/list`, | |||
| removeConversation: `${api_host}/conversation/rm`, | |||
| completeConversation: `${api_host}/conversation/completion`, | |||
| // chat for external | |||
| createToken: `${api_host}/api/new_token`, | |||
| listToken: `${api_host}/api/token_list`, | |||
| removeToken: `${api_host}/api/rm`, | |||
| getStats: `${api_host}/api/stats`, | |||
| createExternalConversation: `${api_host}/api/new_conversation`, | |||
| getExternalConversation: `${api_host}/api/conversation`, | |||
| completeExternalConversation: `${api_host}/api/completion`, | |||
| // file manager | |||
| listFile: `${api_host}/file/list`, | |||
| uploadFile: `${api_host}/file/upload`, | |||
| removeFile: `${api_host}/file/rm`, | |||
| renameFile: `${api_host}/file/rename`, | |||
| getAllParentFolder: `${api_host}/file/all_parent_folder`, | |||
| createFolder: `${api_host}/file/create`, | |||
| connectFileToKnowledge: `${api_host}/file2document/convert`, | |||
| getFile: `${api_host}/file/get`, | |||
| // system | |||
| getSystemVersion: `${api_host}/system/version`, | |||
| getSystemStatus: `${api_host}/system/status`, | |||
| // flow | |||
| listTemplates: `${api_host}/canvas/templates`, | |||
| listCanvas: `${api_host}/canvas/list`, | |||
| getCanvas: `${api_host}/canvas/get`, | |||
| removeCanvas: `${api_host}/canvas/rm`, | |||
| setCanvas: `${api_host}/canvas/set`, | |||
| resetCanvas: `${api_host}/canvas/reset`, | |||
| runCanvas: `${api_host}/canvas/completion`, | |||
| }; | |||