balibabu commited on
Commit
4635160
·
1 Parent(s): c3ed681

feat: Add KnowledgeGraphModal #162 (#1766)

Browse files

### What problem does this PR solve?

feat: Add KnowledgeGraphModal #162

### Type of change


- [x] New Feature (non-breaking change which adds functionality)

web/src/components/chunk-method-modal/hooks.ts CHANGED
@@ -14,25 +14,59 @@ const ParserListMap = new Map([
14
  'presentation',
15
  'one',
16
  'qa',
 
17
  ],
18
  ],
19
  [
20
  ['doc', 'docx'],
21
- ['naive', 'resume', 'book', 'laws', 'one', 'qa', 'manual'],
 
 
 
 
 
 
 
 
 
22
  ],
23
  [
24
  ['xlsx', 'xls'],
25
- ['naive', 'qa', 'table', 'one'],
26
  ],
27
  [['ppt', 'pptx'], ['presentation']],
28
  [
29
  ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif', 'tiff', 'webp', 'svg', 'ico'],
30
  ['picture'],
31
  ],
32
- [['txt'], ['naive', 'resume', 'book', 'laws', 'one', 'qa', 'table']],
33
- [['csv'], ['naive', 'resume', 'book', 'laws', 'one', 'qa', 'table']],
34
- [['md'], ['naive', 'qa']],
35
- [['json'], ['naive']],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  ]);
37
 
38
  const getParserList = (
 
14
  'presentation',
15
  'one',
16
  'qa',
17
+ 'knowledge_graph',
18
  ],
19
  ],
20
  [
21
  ['doc', 'docx'],
22
+ [
23
+ 'naive',
24
+ 'resume',
25
+ 'book',
26
+ 'laws',
27
+ 'one',
28
+ 'qa',
29
+ 'manual',
30
+ 'knowledge_graph',
31
+ ],
32
  ],
33
  [
34
  ['xlsx', 'xls'],
35
+ ['naive', 'qa', 'table', 'one', 'knowledge_graph'],
36
  ],
37
  [['ppt', 'pptx'], ['presentation']],
38
  [
39
  ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif', 'tiff', 'webp', 'svg', 'ico'],
40
  ['picture'],
41
  ],
42
+ [
43
+ ['txt'],
44
+ [
45
+ 'naive',
46
+ 'resume',
47
+ 'book',
48
+ 'laws',
49
+ 'one',
50
+ 'qa',
51
+ 'table',
52
+ 'knowledge_graph',
53
+ ],
54
+ ],
55
+ [
56
+ ['csv'],
57
+ [
58
+ 'naive',
59
+ 'resume',
60
+ 'book',
61
+ 'laws',
62
+ 'one',
63
+ 'qa',
64
+ 'table',
65
+ 'knowledge_graph',
66
+ ],
67
+ ],
68
+ [['md'], ['naive', 'qa', 'knowledge_graph']],
69
+ [['json'], ['naive', 'knowledge_graph']],
70
  ]);
71
 
72
  const getParserList = (
web/src/components/chunk-method-modal/index.tsx CHANGED
@@ -119,7 +119,7 @@ const ChunkMethodModal: React.FC<IProps> = ({
119
  <Space size={[0, 8]} wrap>
120
  <Form.Item label={t('chunkMethod')} className={styles.chunkMethod}>
121
  <Select
122
- style={{ width: 120 }}
123
  onChange={handleChange}
124
  value={selectedTag}
125
  options={parserList}
 
119
  <Space size={[0, 8]} wrap>
120
  <Form.Item label={t('chunkMethod')} className={styles.chunkMethod}>
121
  <Select
122
+ style={{ width: 160 }}
123
  onChange={handleChange}
124
  value={selectedTag}
125
  options={parserList}
web/src/hooks/chunk-hooks.ts CHANGED
@@ -206,3 +206,22 @@ export const useFetchChunk = (chunkId?: string): ResponseType<any> => {
206
 
207
  return data;
208
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  return data;
208
  };
209
+
210
+ export const useFetchKnowledgeGraph = (): ResponseType<any> => {
211
+ const { documentId } = useGetKnowledgeSearchParams();
212
+
213
+ const { data } = useQuery({
214
+ queryKey: ['fetchKnowledgeGraph', documentId],
215
+ initialData: true,
216
+ gcTime: 0,
217
+ queryFn: async () => {
218
+ const data = await kbService.knowledge_graph({
219
+ doc_id: documentId,
220
+ });
221
+
222
+ return data;
223
+ },
224
+ });
225
+
226
+ return data;
227
+ };
web/src/pages/add-knowledge/components/knowledge-chunk/components/editTag.tsx DELETED
@@ -1,141 +0,0 @@
1
- import type { InputRef } from 'antd';
2
- import { Input, Space, Tag, Tooltip, theme } from 'antd';
3
- import React, { useEffect, useRef, useState } from 'react';
4
- interface EditTagsProps {
5
- tags: any[];
6
- setTags: (tags: any[]) => void;
7
- }
8
- const EditTag: React.FC<EditTagsProps> = ({ tags, setTags }) => {
9
- const { token } = theme.useToken();
10
-
11
- const [inputVisible, setInputVisible] = useState(false);
12
- const [inputValue, setInputValue] = useState('');
13
- const [editInputIndex, setEditInputIndex] = useState(-1);
14
- const [editInputValue, setEditInputValue] = useState('');
15
- const inputRef = useRef<InputRef>(null);
16
- const editInputRef = useRef<InputRef>(null);
17
-
18
- useEffect(() => {
19
- if (inputVisible) {
20
- inputRef.current?.focus();
21
- }
22
- }, [inputVisible]);
23
-
24
- useEffect(() => {
25
- editInputRef.current?.focus();
26
- }, [editInputValue]);
27
-
28
- const handleClose = (removedTag: string) => {
29
- const newTags = tags.filter((tag) => tag !== removedTag);
30
- console.log(newTags);
31
- setTags(newTags);
32
- };
33
-
34
- const showInput = () => {
35
- setInputVisible(true);
36
- };
37
-
38
- const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
39
- setInputValue(e.target.value);
40
- };
41
-
42
- const handleInputConfirm = () => {
43
- if (inputValue && !tags.includes(inputValue)) {
44
- setTags([...tags, inputValue]);
45
- }
46
- setInputVisible(false);
47
- setInputValue('');
48
- };
49
-
50
- const handleEditInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
51
- setEditInputValue(e.target.value);
52
- };
53
-
54
- const handleEditInputConfirm = () => {
55
- const newTags = [...tags];
56
- newTags[editInputIndex] = editInputValue;
57
- setTags(newTags);
58
- setEditInputIndex(-1);
59
- setEditInputValue('');
60
- };
61
-
62
- const tagInputStyle: React.CSSProperties = {
63
- width: 64,
64
- height: 22,
65
- marginInlineEnd: 8,
66
- verticalAlign: 'top',
67
- };
68
-
69
- const tagPlusStyle: React.CSSProperties = {
70
- height: 22,
71
- background: token.colorBgContainer,
72
- borderStyle: 'dashed',
73
- };
74
-
75
- return (
76
- <Space size={[0, 8]} wrap>
77
- {tags.map((tag, index) => {
78
- if (editInputIndex === index) {
79
- return (
80
- <Input
81
- ref={editInputRef}
82
- key={tag}
83
- size="small"
84
- style={tagInputStyle}
85
- value={editInputValue}
86
- onChange={handleEditInputChange}
87
- onBlur={handleEditInputConfirm}
88
- onPressEnter={handleEditInputConfirm}
89
- />
90
- );
91
- }
92
- const isLongTag = tag.length > 20;
93
- const tagElem = (
94
- <Tag
95
- key={tag}
96
- closable={index !== 0}
97
- style={{ userSelect: 'none' }}
98
- onClose={() => handleClose(tag)}
99
- >
100
- <span
101
- onDoubleClick={(e) => {
102
- if (index !== 0) {
103
- setEditInputIndex(index);
104
- setEditInputValue(tag);
105
- e.preventDefault();
106
- }
107
- }}
108
- >
109
- {isLongTag ? `${tag.slice(0, 20)}...` : tag}
110
- </span>
111
- </Tag>
112
- );
113
- return isLongTag ? (
114
- <Tooltip title={tag} key={tag}>
115
- {tagElem}
116
- </Tooltip>
117
- ) : (
118
- tagElem
119
- );
120
- })}
121
- {inputVisible ? (
122
- <Input
123
- ref={inputRef}
124
- type="text"
125
- size="small"
126
- style={tagInputStyle}
127
- value={inputValue}
128
- onChange={handleInputChange}
129
- onBlur={handleInputConfirm}
130
- onPressEnter={handleInputConfirm}
131
- />
132
- ) : (
133
- <Tag style={tagPlusStyle} onClick={showInput}>
134
- 添加关键词
135
- </Tag>
136
- )}
137
- </Space>
138
- );
139
- };
140
-
141
- export default EditTag;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
web/src/pages/add-knowledge/components/knowledge-chunk/components/knowledge-graph/constant.ts ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const nodes = [
2
+ {
3
+ type: '"ORGANIZATION"',
4
+ description:
5
+ '"厦门象屿是一家公司,其营业收入和市场占有率在2018年至2022年间有所变化。"',
6
+ source_id: '0',
7
+ id: '"厦门象屿"',
8
+ },
9
+ {
10
+ type: '"EVENT"',
11
+ description:
12
+ '"2018年是一个时间点,标志着厦门象屿营业收入和市场占有率的记录开始。"',
13
+ source_id: '0',
14
+ entity_type: '"EVENT"',
15
+ id: '"2018"',
16
+ },
17
+ {
18
+ type: '"EVENT"',
19
+ description:
20
+ '"2019年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"',
21
+ source_id: '0',
22
+ entity_type: '"EVENT"',
23
+ id: '"2019"',
24
+ },
25
+ {
26
+ type: '"EVENT"',
27
+ description:
28
+ '"2020年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"',
29
+ source_id: '0',
30
+ entity_type: '"EVENT"',
31
+ id: '"2020"',
32
+ },
33
+ {
34
+ type: '"EVENT"',
35
+ description:
36
+ '"2021年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"',
37
+ source_id: '0',
38
+ entity_type: '"EVENT"',
39
+ id: '"2021"',
40
+ },
41
+ {
42
+ type: '"EVENT"',
43
+ description:
44
+ '"2022年是一个时间点,厦门象屿的营业收入和市场占有率在此期间有所变化。"',
45
+ source_id: '0',
46
+ entity_type: '"EVENT"',
47
+ id: '"2022"',
48
+ },
49
+ {
50
+ type: '"ORGANIZATION"',
51
+ description:
52
+ '"厦门象屿股份有限公司是一家公司,中文简称为厦门象屿,外文名称为Xiamen Xiangyu Co.,Ltd.,外文名称缩写为Xiangyu,法定代表人为邓启东。"',
53
+ source_id: '1',
54
+ id: '"厦门象屿股份有限公司"',
55
+ },
56
+ {
57
+ type: '"PERSON"',
58
+ description: '"邓启东是厦门象屿股份有限公司的法定代表人。"',
59
+ source_id: '1',
60
+ entity_type: '"PERSON"',
61
+ id: '"邓启东"',
62
+ },
63
+ {
64
+ type: '"GEO"',
65
+ description: '"厦门是一个地理位置,与厦门象屿股份有限公司相关。"',
66
+ source_id: '1',
67
+ entity_type: '"GEO"',
68
+ id: '"厦门"',
69
+ },
70
+ {
71
+ type: '"PERSON"',
72
+ description:
73
+ '"廖杰 is the Board Secretary, responsible for handling board-related matters and communications."',
74
+ source_id: '2',
75
+ id: '"廖杰"',
76
+ },
77
+ {
78
+ type: '"PERSON"',
79
+ description:
80
+ '"史经洋 is the Securities Affairs Representative, responsible for handling securities-related matters and communications."',
81
+ source_id: '2',
82
+ entity_type: '"PERSON"',
83
+ id: '"史经洋"',
84
+ },
85
+ {
86
+ type: '"GEO"',
87
+ description:
88
+ '"A geographic location in Xiamen, specifically in the Free Trade Zone, where the company\'s office is situated."',
89
+ source_id: '2',
90
+ entity_type: '"GEO"',
91
+ id: '"厦门市湖里区自由贸易试验区厦门片区"',
92
+ },
93
+ {
94
+ type: '"GEO"',
95
+ description:
96
+ '"The building where the company\'s office is located, situated at Xiangyu Road, Xiamen."',
97
+ source_id: '2',
98
+ entity_type: '"GEO"',
99
+ id: '"象屿集团大厦"',
100
+ },
101
+ {
102
+ type: '"EVENT"',
103
+ description:
104
+ '"Refers to the year 2021, used for comparing financial metrics with the year 2022."',
105
+ source_id: '3',
106
+ id: '"2021年"',
107
+ },
108
+ {
109
+ type: '"EVENT"',
110
+ description:
111
+ '"Refers to the year 2022, used for presenting current financial metrics and comparing them with the year 2021."',
112
+ source_id: '3',
113
+ entity_type: '"EVENT"',
114
+ id: '"2022年"',
115
+ },
116
+ {
117
+ type: '"EVENT"',
118
+ description:
119
+ '"Indicates the focus on key financial metrics in the table, such as weighted averages and percentages."',
120
+ source_id: '3',
121
+ entity_type: '"EVENT"',
122
+ id: '"主要财务指标"',
123
+ },
124
+ ].map(({ type, ...x }) => ({ ...x }));
125
+
126
+ const edges = [
127
+ {
128
+ weight: 2.0,
129
+ description: '"厦门象屿在2018年的营业收入和市场占有率被记录。"',
130
+ source_id: '0',
131
+ source: '"厦门象屿"',
132
+ target: '"2018"',
133
+ },
134
+ {
135
+ weight: 2.0,
136
+ description: '"厦门象屿在2019年的营业收入和市场占有率有所变化。"',
137
+ source_id: '0',
138
+ source: '"厦门象屿"',
139
+ target: '"2019"',
140
+ },
141
+ {
142
+ weight: 2.0,
143
+ description: '"厦门象屿在2020年的营业收入和市场占有率有所变化。"',
144
+ source_id: '0',
145
+ source: '"厦门象屿"',
146
+ target: '"2020"',
147
+ },
148
+ {
149
+ weight: 2.0,
150
+ description: '"厦门象屿在2021年的营业收入和市场占有率有所变化。"',
151
+ source_id: '0',
152
+ source: '"厦门象屿"',
153
+ target: '"2021"',
154
+ },
155
+ {
156
+ weight: 2.0,
157
+ description: '"厦门象屿在2022年的营业收入和市场占有率有所变化。"',
158
+ source_id: '0',
159
+ source: '"厦门象屿"',
160
+ target: '"2022"',
161
+ },
162
+ {
163
+ weight: 2.0,
164
+ description: '"厦门象屿股份有限公司的法定代表人是邓启东。"',
165
+ source_id: '1',
166
+ source: '"厦门象屿股份有限公司"',
167
+ target: '"邓启东"',
168
+ },
169
+ {
170
+ weight: 2.0,
171
+ description: '"厦门象屿股份有限公���位于厦门。"',
172
+ source_id: '1',
173
+ source: '"厦门象屿股份有限公司"',
174
+ target: '"厦门"',
175
+ },
176
+ {
177
+ weight: 2.0,
178
+ description:
179
+ '"廖杰\'s office is located in the Xiangyu Group Building, indicating his workplace."',
180
+ source_id: '2',
181
+ source: '"廖杰"',
182
+ target: '"象屿集团大厦"',
183
+ },
184
+ {
185
+ weight: 2.0,
186
+ description:
187
+ '"廖杰 works in the Xiamen Free Trade Zone, a specific area within Xiamen."',
188
+ source_id: '2',
189
+ source: '"廖杰"',
190
+ target: '"厦门市湖里区自由贸易试验区厦门片区"',
191
+ },
192
+ {
193
+ weight: 2.0,
194
+ description:
195
+ '"史经洋\'s office is also located in the Xiangyu Group Building, indicating his workplace."',
196
+ source_id: '2',
197
+ source: '"史经洋"',
198
+ target: '"象屿集团大厦"',
199
+ },
200
+ {
201
+ weight: 2.0,
202
+ description:
203
+ '"史经洋 works in the Xiamen Free Trade Zone, a specific area within Xiamen."',
204
+ source_id: '2',
205
+ source: '"史经洋"',
206
+ target: '"厦门市湖里区自由贸易试验区厦门片区"',
207
+ },
208
+ {
209
+ weight: 2.0,
210
+ description:
211
+ '"The years 2021 and 2022 are related as they are used for comparing financial metrics, showing changes and adjustments over time."',
212
+ source_id: '3',
213
+ source: '"2021年"',
214
+ target: '"2022年"',
215
+ },
216
+ {
217
+ weight: 2.0,
218
+ description:
219
+ '"The \'主要财务指标\' is related to the year 2021 as it provides the basis for financial comparisons and adjustments."',
220
+ source_id: '3',
221
+ source: '"2021年"',
222
+ target: '"主要财务指标"',
223
+ },
224
+ {
225
+ weight: 2.0,
226
+ description:
227
+ '"The \'主要财务指标\' is related to the year 2022 as it presents the current financial metrics and their changes compared to 2021."',
228
+ source_id: '3',
229
+ source: '"2022年"',
230
+ target: '"主要财务指标"',
231
+ },
232
+ ];
233
+
234
+ export const graphData = {
235
+ directed: false,
236
+ multigraph: false,
237
+ graph: {},
238
+ nodes,
239
+ edges,
240
+ combos: [],
241
+ };
web/src/pages/add-knowledge/components/knowledge-chunk/components/knowledge-graph/force-graph.tsx ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Graph } from '@antv/g6';
2
+ import { useSize } from 'ahooks';
3
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
4
+ import { graphData } from './constant';
5
+ import { Converter, buildNodesAndCombos, isDataExist } from './util';
6
+
7
+ import { useFetchKnowledgeGraph } from '@/hooks/chunk-hooks';
8
+ import styles from './index.less';
9
+
10
+ const converter = new Converter();
11
+
12
+ const nextData = converter.buildNodesAndCombos(
13
+ graphData.nodes,
14
+ graphData.edges,
15
+ );
16
+ console.log('🚀 ~ nextData:', nextData);
17
+
18
+ const finalData = { ...graphData, ...nextData };
19
+
20
+ const ForceGraph = () => {
21
+ const containerRef = useRef<HTMLDivElement>(null);
22
+ const size = useSize(containerRef);
23
+ const { data } = useFetchKnowledgeGraph();
24
+
25
+ const nextData = useMemo(() => {
26
+ if (isDataExist(data)) {
27
+ const graphData = data.data;
28
+ const mi = buildNodesAndCombos(graphData.nodes);
29
+ return { edges: graphData.links, ...mi };
30
+ }
31
+ return { nodes: [], edges: [] };
32
+ }, [data]);
33
+ console.log('🚀 ~ nextData ~ nextData:', nextData);
34
+
35
+ const render = useCallback(() => {
36
+ const graph = new Graph({
37
+ container: containerRef.current!,
38
+ autoFit: 'view',
39
+ behaviors: [
40
+ 'drag-element',
41
+ 'drag-canvas',
42
+ 'zoom-canvas',
43
+ 'collapse-expand',
44
+ {
45
+ type: 'hover-activate',
46
+ degree: 1, // 👈🏻 Activate relations.
47
+ },
48
+ ],
49
+ plugins: [
50
+ {
51
+ type: 'tooltip',
52
+ getContent: (e, items) => {
53
+ if (items.every((x) => x?.description)) {
54
+ let result = ``;
55
+ items.forEach((item) => {
56
+ result += `<h3>${item?.id}</h3>`;
57
+ if (item?.description) {
58
+ result += `<p>${item?.description}</p>`;
59
+ }
60
+ });
61
+ return result;
62
+ }
63
+ return undefined;
64
+ },
65
+ },
66
+ ],
67
+ layout: {
68
+ type: 'combo-combined',
69
+ preventOverlap: true,
70
+ comboPadding: 1,
71
+ spacing: 20,
72
+ },
73
+ node: {
74
+ style: {
75
+ size: 20,
76
+ labelText: (d) => d.id,
77
+ labelPadding: 30,
78
+ // labelOffsetX: 20,
79
+ // labelOffsetY: 5,
80
+ labelPlacement: 'center',
81
+ labelWordWrap: true,
82
+ },
83
+ palette: {
84
+ type: 'group',
85
+ field: (d) => d.combo,
86
+ },
87
+ // state: {
88
+ // highlight: {
89
+ // fill: '#D580FF',
90
+ // halo: true,
91
+ // lineWidth: 0,
92
+ // },
93
+ // dim: {
94
+ // fill: '#99ADD1',
95
+ // },
96
+ // },
97
+ },
98
+ edge: {
99
+ style: (model) => {
100
+ const { size, color } = model.data;
101
+ return {
102
+ stroke: color || '#99ADD1',
103
+ lineWidth: size || 1,
104
+ };
105
+ },
106
+ },
107
+ });
108
+
109
+ graph.setData(nextData);
110
+
111
+ graph.render();
112
+ }, [nextData]);
113
+
114
+ useEffect(() => {
115
+ if (isDataExist(data)) {
116
+ render();
117
+ }
118
+ }, [data, render]);
119
+
120
+ return <div ref={containerRef} className={styles.forceContainer} />;
121
+ };
122
+
123
+ export default ForceGraph;
web/src/pages/add-knowledge/components/knowledge-chunk/components/knowledge-graph/index.less ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ .forceContainer {
2
+ width: 100%;
3
+ height: 100%;
4
+ }
5
+
6
+ .modalContainer {
7
+ width: 90vw;
8
+ height: 80vh;
9
+ }
web/src/pages/add-knowledge/components/knowledge-chunk/components/knowledge-graph/index.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ const KnowledgeGraph = () => {
2
+ return <div>KnowledgeGraph</div>;
3
+ };
4
+
5
+ export default KnowledgeGraph;
web/src/pages/add-knowledge/components/knowledge-chunk/components/knowledge-graph/modal.tsx ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useFetchKnowledgeGraph } from '@/hooks/chunk-hooks';
2
+ import { Modal } from 'antd';
3
+ import React, { useEffect, useState } from 'react';
4
+ import ForceGraph from './force-graph';
5
+
6
+ import styles from './index.less';
7
+
8
+ const KnowledgeGraphModal: React.FC = () => {
9
+ const [isModalOpen, setIsModalOpen] = useState(false);
10
+ const { data } = useFetchKnowledgeGraph();
11
+
12
+ const handleOk = () => {
13
+ setIsModalOpen(false);
14
+ };
15
+
16
+ const handleCancel = () => {
17
+ setIsModalOpen(false);
18
+ };
19
+
20
+ useEffect(() => {
21
+ if (data?.data && typeof data?.data !== 'boolean') {
22
+ console.log('🚀 ~ useEffect ~ data:', data);
23
+ setIsModalOpen(true);
24
+ }
25
+ }, [setIsModalOpen, data]);
26
+
27
+ return (
28
+ <Modal
29
+ title="Knowledge Graph"
30
+ open={isModalOpen}
31
+ onOk={handleOk}
32
+ onCancel={handleCancel}
33
+ width={'90vw'}
34
+ footer={null}
35
+ >
36
+ <section className={styles.modalContainer}>
37
+ <ForceGraph></ForceGraph>
38
+ </section>
39
+ </Modal>
40
+ );
41
+ };
42
+
43
+ export default KnowledgeGraphModal;
web/src/pages/add-knowledge/components/knowledge-chunk/components/knowledge-graph/util.ts ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class KeyGenerator {
2
+ idx = 0;
3
+ chars: string[] = [];
4
+ constructor() {
5
+ const chars = Array(26)
6
+ .fill(1)
7
+ .map((x, idx) => String.fromCharCode(97 + idx)); // 26 char
8
+ this.chars = chars;
9
+ }
10
+ generateKey() {
11
+ const key = this.chars[this.idx];
12
+ this.idx++;
13
+ return key;
14
+ }
15
+ }
16
+
17
+ // Classify nodes based on edge relationships
18
+ export class Converter {
19
+ keyGenerator;
20
+ dict: Record<string, string> = {}; // key is node id, value is combo
21
+ constructor() {
22
+ this.keyGenerator = new KeyGenerator();
23
+ }
24
+ buildDict(edges: { source: string; target: string }[]) {
25
+ edges.forEach((x) => {
26
+ if (this.dict[x.source] && !this.dict[x.target]) {
27
+ this.dict[x.target] = this.dict[x.source];
28
+ } else if (!this.dict[x.source] && this.dict[x.target]) {
29
+ this.dict[x.source] = this.dict[x.target];
30
+ } else if (!this.dict[x.source] && !this.dict[x.target]) {
31
+ this.dict[x.source] = this.dict[x.target] =
32
+ this.keyGenerator.generateKey();
33
+ }
34
+ });
35
+ return this.dict;
36
+ }
37
+ buildNodesAndCombos(nodes: any[], edges: any[]) {
38
+ this.buildDict(edges);
39
+ const nextNodes = nodes.map((x) => ({ ...x, combo: this.dict[x.id] }));
40
+
41
+ const combos = Object.values(this.dict).reduce<any[]>((pre, cur) => {
42
+ if (pre.every((x) => x.id !== cur)) {
43
+ pre.push({
44
+ id: cur,
45
+ data: {
46
+ label: `Combo ${cur}`,
47
+ },
48
+ });
49
+ }
50
+ return pre;
51
+ }, []);
52
+
53
+ return { nodes: nextNodes, combos };
54
+ }
55
+ }
56
+
57
+ export const isDataExist = (data: any) => {
58
+ return data?.data && typeof data?.data !== 'boolean';
59
+ };
60
+
61
+ export const buildNodesAndCombos = (nodes: any[]) => {
62
+ const combos: any[] = [];
63
+ const nextNodes = nodes.map((x) => {
64
+ const combo = Array.isArray(x?.communities) ? x.communities[0] : undefined;
65
+ if (combo && combos.every((y) => y.id !== combo)) {
66
+ combos.push({
67
+ id: combo,
68
+ data: {
69
+ label: `Combo ${combo}`,
70
+ },
71
+ });
72
+ }
73
+ return {
74
+ ...x,
75
+ combo,
76
+ };
77
+ });
78
+
79
+ return { nodes: nextNodes, combos };
80
+ };
web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx CHANGED
@@ -8,6 +8,7 @@ import ChunkCard from './components/chunk-card';
8
  import CreatingModal from './components/chunk-creating-modal';
9
  import ChunkToolBar from './components/chunk-toolbar';
10
  import DocumentPreview from './components/document-preview/preview';
 
11
  import {
12
  useChangeChunkTextMode,
13
  useDeleteChunkByIds,
@@ -52,7 +53,6 @@ const Chunk = () => {
52
  ) => {
53
  setSelectedChunkIds([]);
54
  pagination.onChange?.(page, size);
55
- // getChunkList();
56
  };
57
 
58
  const selectAllChunk = useCallback(
@@ -110,16 +110,9 @@ const Chunk = () => {
110
  doc_id: documentId,
111
  });
112
  if (!chunkIds && resCode === 0) {
113
- // getChunkList();
114
  }
115
  },
116
- [
117
- switchChunk,
118
- documentId,
119
- // getChunkList,
120
- selectedChunkIds,
121
- showSelectedChunkWarning,
122
- ],
123
  );
124
 
125
  const { highlights, setWidthAndHeight } =
@@ -202,6 +195,7 @@ const Chunk = () => {
202
  onOk={onChunkUpdatingOk}
203
  />
204
  )}
 
205
  </>
206
  );
207
  };
 
8
  import CreatingModal from './components/chunk-creating-modal';
9
  import ChunkToolBar from './components/chunk-toolbar';
10
  import DocumentPreview from './components/document-preview/preview';
11
+ import KnowledgeGraphModal from './components/knowledge-graph/modal';
12
  import {
13
  useChangeChunkTextMode,
14
  useDeleteChunkByIds,
 
53
  ) => {
54
  setSelectedChunkIds([]);
55
  pagination.onChange?.(page, size);
 
56
  };
57
 
58
  const selectAllChunk = useCallback(
 
110
  doc_id: documentId,
111
  });
112
  if (!chunkIds && resCode === 0) {
 
113
  }
114
  },
115
+ [switchChunk, documentId, selectedChunkIds, showSelectedChunkWarning],
 
 
 
 
 
 
116
  );
117
 
118
  const { highlights, setWidthAndHeight } =
 
195
  onOk={onChunkUpdatingOk}
196
  />
197
  )}
198
+ <KnowledgeGraphModal></KnowledgeGraphModal>
199
  </>
200
  );
201
  };
web/src/services/knowledge-service.ts CHANGED
@@ -27,6 +27,7 @@ const {
27
  get_document_file,
28
  document_upload,
29
  web_crawl,
 
30
  } = api;
31
 
32
  const methods = {
@@ -121,6 +122,10 @@ const methods = {
121
  url: retrieval_test,
122
  method: 'post',
123
  },
 
 
 
 
124
  };
125
 
126
  const kbService = registerServer<keyof typeof methods>(methods, request);
 
27
  get_document_file,
28
  document_upload,
29
  web_crawl,
30
+ knowledge_graph,
31
  } = api;
32
 
33
  const methods = {
 
122
  url: retrieval_test,
123
  method: 'post',
124
  },
125
+ knowledge_graph: {
126
+ url: knowledge_graph,
127
+ method: 'get',
128
+ },
129
  };
130
 
131
  const kbService = registerServer<keyof typeof methods>(methods, request);
web/src/utils/api.ts CHANGED
@@ -1,94 +1,95 @@
1
- let api_host = `/v1`;
2
-
3
- export { api_host };
4
-
5
- export default {
6
- // user
7
- login: `${api_host}/user/login`,
8
- logout: `${api_host}/user/logout`,
9
- register: `${api_host}/user/register`,
10
- setting: `${api_host}/user/setting`,
11
- user_info: `${api_host}/user/info`,
12
- tenant_info: `${api_host}/user/tenant_info`,
13
- set_tenant_info: `${api_host}/user/set_tenant_info`,
14
-
15
- // llm model
16
- factories_list: `${api_host}/llm/factories`,
17
- llm_list: `${api_host}/llm/list`,
18
- my_llm: `${api_host}/llm/my_llms`,
19
- set_api_key: `${api_host}/llm/set_api_key`,
20
- add_llm: `${api_host}/llm/add_llm`,
21
- delete_llm: `${api_host}/llm/delete_llm`,
22
-
23
- // knowledge base
24
- kb_list: `${api_host}/kb/list`,
25
- create_kb: `${api_host}/kb/create`,
26
- update_kb: `${api_host}/kb/update`,
27
- rm_kb: `${api_host}/kb/rm`,
28
- get_kb_detail: `${api_host}/kb/detail`,
29
-
30
- // chunk
31
- chunk_list: `${api_host}/chunk/list`,
32
- create_chunk: `${api_host}/chunk/create`,
33
- set_chunk: `${api_host}/chunk/set`,
34
- get_chunk: `${api_host}/chunk/get`,
35
- switch_chunk: `${api_host}/chunk/switch`,
36
- rm_chunk: `${api_host}/chunk/rm`,
37
- retrieval_test: `${api_host}/chunk/retrieval_test`,
38
-
39
- // document
40
- upload: `${api_host}/document/upload`,
41
- get_document_list: `${api_host}/document/list`,
42
- document_change_status: `${api_host}/document/change_status`,
43
- document_rm: `${api_host}/document/rm`,
44
- document_rename: `${api_host}/document/rename`,
45
- document_create: `${api_host}/document/create`,
46
- document_run: `${api_host}/document/run`,
47
- document_change_parser: `${api_host}/document/change_parser`,
48
- document_thumbnails: `${api_host}/document/thumbnails`,
49
- get_document_file: `${api_host}/document/get`,
50
- document_upload: `${api_host}/document/upload`,
51
- web_crawl: `${api_host}/document/web_crawl`,
52
-
53
- // chat
54
- setDialog: `${api_host}/dialog/set`,
55
- getDialog: `${api_host}/dialog/get`,
56
- removeDialog: `${api_host}/dialog/rm`,
57
- listDialog: `${api_host}/dialog/list`,
58
- setConversation: `${api_host}/conversation/set`,
59
- getConversation: `${api_host}/conversation/get`,
60
- listConversation: `${api_host}/conversation/list`,
61
- removeConversation: `${api_host}/conversation/rm`,
62
- completeConversation: `${api_host}/conversation/completion`,
63
- // chat for external
64
- createToken: `${api_host}/api/new_token`,
65
- listToken: `${api_host}/api/token_list`,
66
- removeToken: `${api_host}/api/rm`,
67
- getStats: `${api_host}/api/stats`,
68
- createExternalConversation: `${api_host}/api/new_conversation`,
69
- getExternalConversation: `${api_host}/api/conversation`,
70
- completeExternalConversation: `${api_host}/api/completion`,
71
-
72
- // file manager
73
- listFile: `${api_host}/file/list`,
74
- uploadFile: `${api_host}/file/upload`,
75
- removeFile: `${api_host}/file/rm`,
76
- renameFile: `${api_host}/file/rename`,
77
- getAllParentFolder: `${api_host}/file/all_parent_folder`,
78
- createFolder: `${api_host}/file/create`,
79
- connectFileToKnowledge: `${api_host}/file2document/convert`,
80
- getFile: `${api_host}/file/get`,
81
-
82
- // system
83
- getSystemVersion: `${api_host}/system/version`,
84
- getSystemStatus: `${api_host}/system/status`,
85
-
86
- // flow
87
- listTemplates: `${api_host}/canvas/templates`,
88
- listCanvas: `${api_host}/canvas/list`,
89
- getCanvas: `${api_host}/canvas/get`,
90
- removeCanvas: `${api_host}/canvas/rm`,
91
- setCanvas: `${api_host}/canvas/set`,
92
- resetCanvas: `${api_host}/canvas/reset`,
93
- runCanvas: `${api_host}/canvas/completion`,
94
- };
 
 
1
+ let api_host = `/v1`;
2
+
3
+ export { api_host };
4
+
5
+ export default {
6
+ // user
7
+ login: `${api_host}/user/login`,
8
+ logout: `${api_host}/user/logout`,
9
+ register: `${api_host}/user/register`,
10
+ setting: `${api_host}/user/setting`,
11
+ user_info: `${api_host}/user/info`,
12
+ tenant_info: `${api_host}/user/tenant_info`,
13
+ set_tenant_info: `${api_host}/user/set_tenant_info`,
14
+
15
+ // llm model
16
+ factories_list: `${api_host}/llm/factories`,
17
+ llm_list: `${api_host}/llm/list`,
18
+ my_llm: `${api_host}/llm/my_llms`,
19
+ set_api_key: `${api_host}/llm/set_api_key`,
20
+ add_llm: `${api_host}/llm/add_llm`,
21
+ delete_llm: `${api_host}/llm/delete_llm`,
22
+
23
+ // knowledge base
24
+ kb_list: `${api_host}/kb/list`,
25
+ create_kb: `${api_host}/kb/create`,
26
+ update_kb: `${api_host}/kb/update`,
27
+ rm_kb: `${api_host}/kb/rm`,
28
+ get_kb_detail: `${api_host}/kb/detail`,
29
+
30
+ // chunk
31
+ chunk_list: `${api_host}/chunk/list`,
32
+ create_chunk: `${api_host}/chunk/create`,
33
+ set_chunk: `${api_host}/chunk/set`,
34
+ get_chunk: `${api_host}/chunk/get`,
35
+ switch_chunk: `${api_host}/chunk/switch`,
36
+ rm_chunk: `${api_host}/chunk/rm`,
37
+ retrieval_test: `${api_host}/chunk/retrieval_test`,
38
+ knowledge_graph: `${api_host}/chunk/knowledge_graph`,
39
+
40
+ // document
41
+ upload: `${api_host}/document/upload`,
42
+ get_document_list: `${api_host}/document/list`,
43
+ document_change_status: `${api_host}/document/change_status`,
44
+ document_rm: `${api_host}/document/rm`,
45
+ document_rename: `${api_host}/document/rename`,
46
+ document_create: `${api_host}/document/create`,
47
+ document_run: `${api_host}/document/run`,
48
+ document_change_parser: `${api_host}/document/change_parser`,
49
+ document_thumbnails: `${api_host}/document/thumbnails`,
50
+ get_document_file: `${api_host}/document/get`,
51
+ document_upload: `${api_host}/document/upload`,
52
+ web_crawl: `${api_host}/document/web_crawl`,
53
+
54
+ // chat
55
+ setDialog: `${api_host}/dialog/set`,
56
+ getDialog: `${api_host}/dialog/get`,
57
+ removeDialog: `${api_host}/dialog/rm`,
58
+ listDialog: `${api_host}/dialog/list`,
59
+ setConversation: `${api_host}/conversation/set`,
60
+ getConversation: `${api_host}/conversation/get`,
61
+ listConversation: `${api_host}/conversation/list`,
62
+ removeConversation: `${api_host}/conversation/rm`,
63
+ completeConversation: `${api_host}/conversation/completion`,
64
+ // chat for external
65
+ createToken: `${api_host}/api/new_token`,
66
+ listToken: `${api_host}/api/token_list`,
67
+ removeToken: `${api_host}/api/rm`,
68
+ getStats: `${api_host}/api/stats`,
69
+ createExternalConversation: `${api_host}/api/new_conversation`,
70
+ getExternalConversation: `${api_host}/api/conversation`,
71
+ completeExternalConversation: `${api_host}/api/completion`,
72
+
73
+ // file manager
74
+ listFile: `${api_host}/file/list`,
75
+ uploadFile: `${api_host}/file/upload`,
76
+ removeFile: `${api_host}/file/rm`,
77
+ renameFile: `${api_host}/file/rename`,
78
+ getAllParentFolder: `${api_host}/file/all_parent_folder`,
79
+ createFolder: `${api_host}/file/create`,
80
+ connectFileToKnowledge: `${api_host}/file2document/convert`,
81
+ getFile: `${api_host}/file/get`,
82
+
83
+ // system
84
+ getSystemVersion: `${api_host}/system/version`,
85
+ getSystemStatus: `${api_host}/system/status`,
86
+
87
+ // flow
88
+ listTemplates: `${api_host}/canvas/templates`,
89
+ listCanvas: `${api_host}/canvas/list`,
90
+ getCanvas: `${api_host}/canvas/get`,
91
+ removeCanvas: `${api_host}/canvas/rm`,
92
+ setCanvas: `${api_host}/canvas/set`,
93
+ resetCanvas: `${api_host}/canvas/reset`,
94
+ runCanvas: `${api_host}/canvas/completion`,
95
+ };