update graph page
This commit is contained in:
parent
9bdae3e164
commit
4e1be33ebb
@ -51,10 +51,10 @@ class DataBaseManager:
|
|||||||
self.database_path = "data/databases.json"
|
self.database_path = "data/databases.json"
|
||||||
self.embed_model = get_embedding_model(config)
|
self.embed_model = get_embedding_model(config)
|
||||||
self.knowledge_base = KnowledgeBase(config, self.embed_model)
|
self.knowledge_base = KnowledgeBase(config, self.embed_model)
|
||||||
|
self.graph_base = GraphDatabase(self.config, self.embed_model)
|
||||||
self.data = {"databases": [], "graph": {}}
|
self.data = {"databases": [], "graph": {}}
|
||||||
|
|
||||||
if self.config.enable_knowledge_graph:
|
if self.config.enable_knowledge_graph:
|
||||||
self.graph_base = GraphDatabase(self.config, self.embed_model)
|
|
||||||
self.graph_base.start()
|
self.graph_base.start()
|
||||||
|
|
||||||
self._load_databases()
|
self._load_databases()
|
||||||
@ -130,10 +130,9 @@ class DataBaseManager:
|
|||||||
for idx, file in new_files:
|
for idx, file in new_files:
|
||||||
db.files[idx]["status"] = "processing"
|
db.files[idx]["status"] = "processing"
|
||||||
|
|
||||||
text = self.read_text(file)
|
|
||||||
chunks = self.chunking(text)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
text = self.read_text(file)
|
||||||
|
chunks = self.chunking(text)
|
||||||
self.knowledge_base.add_documents(
|
self.knowledge_base.add_documents(
|
||||||
docs=chunks,
|
docs=chunks,
|
||||||
collection_name=db.metaname,
|
collection_name=db.metaname,
|
||||||
@ -156,7 +155,7 @@ class DataBaseManager:
|
|||||||
return db.to_dict()
|
return db.to_dict()
|
||||||
|
|
||||||
def read_text(self, file):
|
def read_text(self, file):
|
||||||
support_format = [".pdf", ".txt", "*.md"]
|
support_format = [".pdf", ".txt", ".md"]
|
||||||
assert os.path.exists(file), "File not found"
|
assert os.path.exists(file), "File not found"
|
||||||
logger.info(f"Try to read file {file}")
|
logger.info(f"Try to read file {file}")
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,10 @@ from neo4j import GraphDatabase as GD
|
|||||||
from transformers import AutoTokenizer, AutoModel
|
from transformers import AutoTokenizer, AutoModel
|
||||||
from FlagEmbedding import FlagModel, FlagReranker
|
from FlagEmbedding import FlagModel, FlagReranker
|
||||||
|
|
||||||
|
from plugins import pdf2txt
|
||||||
|
from plugins.oneke import OneKE
|
||||||
|
|
||||||
|
UIE_MODEL = None
|
||||||
|
|
||||||
class GraphDatabase:
|
class GraphDatabase:
|
||||||
def __init__(self, config, embed_model=None):
|
def __init__(self, config, embed_model=None):
|
||||||
@ -85,8 +88,10 @@ class GraphDatabase:
|
|||||||
def pdf_file_add_entity(self, file_path, output_path, kgdb_name='neo4j'):
|
def pdf_file_add_entity(self, file_path, output_path, kgdb_name='neo4j'):
|
||||||
self.use_database(kgdb_name) # 切换到指定数据库
|
self.use_database(kgdb_name) # 切换到指定数据库
|
||||||
text_path = pdf2txt(file_path)
|
text_path = pdf2txt(file_path)
|
||||||
oneke = OneKE()
|
global UIE_MODEL
|
||||||
triples_path = oneke.processing_text_to_kg(text_path, output_path)
|
if UIE_MODEL is None:
|
||||||
|
UIE_MODEL = OneKE()
|
||||||
|
triples_path = UIE_MODEL.processing_text_to_kg(text_path, output_path)
|
||||||
def read_triples(file_path):
|
def read_triples(file_path):
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
@ -95,7 +100,7 @@ class GraphDatabase:
|
|||||||
for trio in read_triples(triples_path):
|
for trio in read_triples(triples_path):
|
||||||
self.txt_add_entity(trio, kgdb_name)
|
self.txt_add_entity(trio, kgdb_name)
|
||||||
return kgdb_name
|
return kgdb_name
|
||||||
|
|
||||||
def txt_add_vector_entity(self, triples, kgdb_name='neo4j'):
|
def txt_add_vector_entity(self, triples, kgdb_name='neo4j'):
|
||||||
"""添加实体三元组"""
|
"""添加实体三元组"""
|
||||||
self.use_database(kgdb_name)
|
self.use_database(kgdb_name)
|
||||||
@ -134,15 +139,20 @@ class GraphDatabase:
|
|||||||
session.execute_write(self.set_embedding, entry['t'], embedding_t)
|
session.execute_write(self.set_embedding, entry['t'], embedding_t)
|
||||||
|
|
||||||
def jsonl_file_add_entity(self, file_path, kgdb_name='neo4j'):
|
def jsonl_file_add_entity(self, file_path, kgdb_name='neo4j'):
|
||||||
|
self.status = "processing"
|
||||||
self.use_database(kgdb_name) # 切换到指定数据库
|
self.use_database(kgdb_name) # 切换到指定数据库
|
||||||
triples_path = file_path
|
triples_path = file_path
|
||||||
|
|
||||||
def read_triples(file_path):
|
def read_triples(file_path):
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
for line in file:
|
for line in file:
|
||||||
item = json.loads(line.strip())
|
item = json.loads(line.strip())
|
||||||
yield [item]
|
yield [item]
|
||||||
|
|
||||||
for trio in read_triples(triples_path):
|
for trio in read_triples(triples_path):
|
||||||
self.txt_add_entity(trio, kgdb_name)
|
self.txt_add_entity(trio, kgdb_name)
|
||||||
|
|
||||||
|
self.status = "open"
|
||||||
return kgdb_name
|
return kgdb_name
|
||||||
|
|
||||||
def delete_entity(self, entity_name=None, kgdb_name="neo4j"):
|
def delete_entity(self, entity_name=None, kgdb_name="neo4j"):
|
||||||
@ -221,7 +231,7 @@ class GraphDatabase:
|
|||||||
|
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
return session.execute_read(query, keyword, hops)
|
return session.execute_read(query, keyword, hops)
|
||||||
|
|
||||||
def query_by_vector_tep(self, keyword, kgdb_name='neo4j'):
|
def query_by_vector_tep(self, keyword, kgdb_name='neo4j'):
|
||||||
"""向量查询"""
|
"""向量查询"""
|
||||||
self.use_database(kgdb_name)
|
self.use_database(kgdb_name)
|
||||||
@ -238,11 +248,11 @@ class GraphDatabase:
|
|||||||
|
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
return session.execute_read(query, keyword)
|
return session.execute_read(query, keyword)
|
||||||
|
|
||||||
def query_by_vector(self, entity_name, kgdb_name='neo4j'):
|
def query_by_vector(self, entity_name, kgdb_name='neo4j', hops=2):
|
||||||
self.use_database(kgdb_name)
|
self.use_database(kgdb_name)
|
||||||
result = self.query_by_vector_tep(entity_name)
|
result = self.query_by_vector_tep(entity_name)
|
||||||
ans = self.query_specific_entity(result[0][0])
|
ans = self.query_specific_entity(result[0][0], hops) # 这里是只获取第一个 TODO: 优化
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def query_node_info(self, node_name, kgdb_name='neo4j', hops = 2):
|
def query_node_info(self, node_name, kgdb_name='neo4j', hops = 2):
|
||||||
@ -258,20 +268,20 @@ class GraphDatabase:
|
|||||||
|
|
||||||
with self.driver.session() as session:
|
with self.driver.session() as session:
|
||||||
return session.execute_read(query, node_name, hops)
|
return session.execute_read(query, node_name, hops)
|
||||||
|
|
||||||
def get_embedding(self, text):
|
def get_embedding(self, text):
|
||||||
inputs = [text]
|
inputs = [text]
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
outputs = self.embed_model.encode(inputs)
|
outputs = self.embed_model.encode(inputs)
|
||||||
embeddings = outputs[0] # 假设取平均作为文本的嵌入向量
|
embeddings = outputs[0] # 假设取平均作为文本的嵌入向量
|
||||||
return embeddings
|
return embeddings
|
||||||
|
|
||||||
def set_embedding(self, tx, entity_name, embedding):
|
def set_embedding(self, tx, entity_name, embedding):
|
||||||
tx.run("""
|
tx.run("""
|
||||||
MATCH (e:Entity {name: $name})
|
MATCH (e:Entity {name: $name})
|
||||||
CALL db.create.setNodeVectorProperty(e, 'embedding', $embedding)
|
CALL db.create.setNodeVectorProperty(e, 'embedding', $embedding)
|
||||||
""", name=entity_name, embedding=embedding)
|
""", name=entity_name, embedding=embedding)
|
||||||
|
|
||||||
# def format_query_results(self, results):
|
# def format_query_results(self, results):
|
||||||
# formatted_results = []
|
# formatted_results = []
|
||||||
# for row in results:
|
# for row in results:
|
||||||
@ -301,7 +311,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
super().__init__(model_name_or_path, use_fp16=False, **kwargs)
|
super().__init__(model_name_or_path, use_fp16=False, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
model = EmbeddingModel(config)
|
model = EmbeddingModel(config)
|
||||||
# 初始化知识图谱数据库
|
# 初始化知识图谱数据库
|
||||||
kgdb = GraphDatabase(config, model)
|
kgdb = GraphDatabase(config, model)
|
||||||
@ -311,7 +321,7 @@ if __name__ == "__main__":
|
|||||||
# 返回指定数据库信息
|
# 返回指定数据库信息
|
||||||
# info = kgdb.get_database_info(kgdb_name)
|
# info = kgdb.get_database_info(kgdb_name)
|
||||||
# print(info)
|
# print(info)
|
||||||
|
|
||||||
# triples = [
|
# triples = [
|
||||||
# {
|
# {
|
||||||
# "h": "CCC",
|
# "h": "CCC",
|
||||||
@ -324,14 +334,14 @@ if __name__ == "__main__":
|
|||||||
# "r": "同事"
|
# "r": "同事"
|
||||||
# }
|
# }
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
# print(kgdb.query_by_vector("维C"))
|
# print(kgdb.query_by_vector("维C"))
|
||||||
def format_query_results(results):
|
def format_query_results(results):
|
||||||
formatted_results = {"nodes": [], "edges": []}
|
formatted_results = {"nodes": [], "edges": []}
|
||||||
|
|
||||||
# 用于存储所有唯一的节点信息
|
# 用于存储所有唯一的节点信息
|
||||||
node_dict = {}
|
node_dict = {}
|
||||||
|
|
||||||
for item in results:
|
for item in results:
|
||||||
# 确保item[1]是一个非空的列表
|
# 确保item[1]是一个非空的列表
|
||||||
if isinstance(item[1], list) and len(item[1]) > 0:
|
if isinstance(item[1], list) and len(item[1]) > 0:
|
||||||
@ -340,25 +350,25 @@ if __name__ == "__main__":
|
|||||||
nodes = relationship.nodes
|
nodes = relationship.nodes
|
||||||
if len(nodes) == 2:
|
if len(nodes) == 2:
|
||||||
node1, node2 = nodes
|
node1, node2 = nodes
|
||||||
|
|
||||||
# 提取源节点和目标节点信息
|
# 提取源节点和目标节点信息
|
||||||
node1_id = node1.element_id
|
node1_id = node1.element_id
|
||||||
node2_id = node2.element_id
|
node2_id = node2.element_id
|
||||||
node1_name = item[0] # 假设节点名称和列表中的第一个元素相同
|
node1_name = item[0] # 假设节点名称和列表中的第一个元素相同
|
||||||
node2_name = item[2] if len(item) > 2 else 'unknown'
|
node2_name = item[2] if len(item) > 2 else 'unknown'
|
||||||
|
|
||||||
# 记录节点信息
|
# 记录节点信息
|
||||||
if node1_id not in node_dict:
|
if node1_id not in node_dict:
|
||||||
node_dict[node1_id] = {"id": node1_id, "name": node1_name}
|
node_dict[node1_id] = {"id": node1_id, "name": node1_name}
|
||||||
if node2_id not in node_dict:
|
if node2_id not in node_dict:
|
||||||
node_dict[node2_id] = {"id": node2_id, "name": node2_name}
|
node_dict[node2_id] = {"id": node2_id, "name": node2_name}
|
||||||
|
|
||||||
# 确定关系类型
|
# 确定关系类型
|
||||||
|
|
||||||
relationship_type = relationship._properties.get('type', 'unknown')
|
relationship_type = relationship._properties.get('type', 'unknown')
|
||||||
if relationship_type == 'unknown':
|
if relationship_type == 'unknown':
|
||||||
relationship_type = relationship.type
|
relationship_type = relationship.type
|
||||||
|
|
||||||
# 记录边的信息
|
# 记录边的信息
|
||||||
formatted_results["edges"].append({
|
formatted_results["edges"].append({
|
||||||
"id": rel_id,
|
"id": rel_id,
|
||||||
@ -368,10 +378,10 @@ if __name__ == "__main__":
|
|||||||
"source_name": node1_name,
|
"source_name": node1_name,
|
||||||
"target_name": node2_name
|
"target_name": node2_name
|
||||||
})
|
})
|
||||||
|
|
||||||
# 将唯一的节点信息添加到结果中
|
# 将唯一的节点信息添加到结果中
|
||||||
formatted_results["nodes"] = list(node_dict.values())
|
formatted_results["nodes"] = list(node_dict.values())
|
||||||
|
|
||||||
return formatted_results
|
return formatted_results
|
||||||
|
|
||||||
entities = ['jqy', '维c']
|
entities = ['jqy', '维c']
|
||||||
@ -379,7 +389,7 @@ if __name__ == "__main__":
|
|||||||
for entitie in entities:
|
for entitie in entities:
|
||||||
result = kgdb.query_by_vector(entitie)
|
result = kgdb.query_by_vector(entitie)
|
||||||
if result != []:
|
if result != []:
|
||||||
results.extend(result)
|
results.extend(result)
|
||||||
print(format_query_results(results))
|
print(format_query_results(results))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -86,9 +86,10 @@ def upload_file():
|
|||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
if file.filename == '':
|
if file.filename == '':
|
||||||
return jsonify({'message': 'No selected file'}), 400
|
return jsonify({'message': 'No selected file'}), 400
|
||||||
elif file.filename.split('.')[-1] not in ['pdf', 'txt', 'md']:
|
# elif file.filename.split('.')[-1] not in ['pdf', 'txt', 'md']:
|
||||||
return jsonify({'message': 'Unsupported file type'}), 400
|
# return jsonify({'message': 'Unsupported file type'}), 400
|
||||||
if file:
|
if file:
|
||||||
|
os.makedirs("data/uploads", exist_ok=True)
|
||||||
filename = file.filename
|
filename = file.filename
|
||||||
file_path = os.path.join("data/uploads", filename)
|
file_path = os.path.join("data/uploads", filename)
|
||||||
file.save(file_path)
|
file.save(file_path)
|
||||||
@ -98,3 +99,28 @@ def upload_file():
|
|||||||
def get_graph_info():
|
def get_graph_info():
|
||||||
graph_info = startup.dbm.get_graph()
|
graph_info = startup.dbm.get_graph()
|
||||||
return jsonify(graph_info)
|
return jsonify(graph_info)
|
||||||
|
|
||||||
|
@db.route('/graph/node', methods=['GET'])
|
||||||
|
def get_graph_node():
|
||||||
|
entity_name = request.args.get('entity_name')
|
||||||
|
kgdb_name = request.args.get('kgdb_name')
|
||||||
|
hops = request.args.get('hops')
|
||||||
|
if not entity_name:
|
||||||
|
return jsonify({'message': 'entity_name and kgdb_name are required'}), 400
|
||||||
|
|
||||||
|
logger.debug(f"Get graph node {entity_name} in {kgdb_name} with {hops} hops")
|
||||||
|
result = startup.dbm.graph_base.query_by_vector(entity_name, kgdb_name, hops)
|
||||||
|
return jsonify({'result': startup.retriever.format_query_results(result), 'message': 'success'}), 200
|
||||||
|
|
||||||
|
@db.route('/graph/add', methods=['POST'])
|
||||||
|
def add_graph_entity():
|
||||||
|
data = json.loads(request.data)
|
||||||
|
kgdb_name = data.get('kgdb_name')
|
||||||
|
file_path = data.get('file_path')
|
||||||
|
|
||||||
|
if file_path.endswith('.jsonl'):
|
||||||
|
startup.dbm.graph_base.jsonl_file_add_entity(file_path, kgdb_name)
|
||||||
|
else:
|
||||||
|
return jsonify({'message': 'Unsupported file type'}), 400
|
||||||
|
|
||||||
|
return jsonify({'message': 'Entity successfully added'}), 200
|
||||||
|
|||||||
@ -285,8 +285,9 @@ const sendMessage = () => {
|
|||||||
if (conv.value.inputText.trim()) {
|
if (conv.value.inputText.trim()) {
|
||||||
isStreaming.value = true
|
isStreaming.value = true
|
||||||
appendUserMessage(conv.value.inputText)
|
appendUserMessage(conv.value.inputText)
|
||||||
|
appendAiMessage("检索中……", null)
|
||||||
|
const cur_res_id = conv.value.messages[conv.value.messages.length - 1].id
|
||||||
const user_input = conv.value.inputText
|
const user_input = conv.value.inputText
|
||||||
var cur_res_id = null
|
|
||||||
conv.value.inputText = ''
|
conv.value.inputText = ''
|
||||||
fetch('/api/chat', {
|
fetch('/api/chat', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -319,12 +320,7 @@ const sendMessage = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(message)
|
const data = JSON.parse(message)
|
||||||
if (cur_res_id === null) {
|
updateMessage(data.response, cur_res_id)
|
||||||
appendAiMessage(data.response, data.refs)
|
|
||||||
cur_res_id = conv.value.messages[conv.value.messages.length - 1].id
|
|
||||||
} else {
|
|
||||||
updateMessage(data.response, cur_res_id)
|
|
||||||
}
|
|
||||||
conv.value.history = data.history
|
conv.value.history = data.history
|
||||||
buffer = ''
|
buffer = ''
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -185,6 +185,7 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
transition: border-left 0.1s;
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
white-space: nowrap; /* 禁止换行 */
|
white-space: nowrap; /* 禁止换行 */
|
||||||
@ -203,6 +204,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
|
border-left: 4px solid var(--main-color);
|
||||||
background-color: #EDF4F5;
|
background-color: #EDF4F5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<h2>图数据库</h2>
|
<h2>图数据库</h2>
|
||||||
<p>基于 neo4j 构建的图数据库。</p>
|
<p>基于 neo4j 构建的图数据库。</p>
|
||||||
<div :class="{'graphloading': graphloading}">
|
<div :class="{'graphloading': graphloading}" v-if="graph">
|
||||||
<div class="dbcard graphbase" @click="navigateToGraph">
|
<div class="dbcard graphbase" @click="navigateToGraph">
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<div class="icon"><AppstoreFilled /></div>
|
<div class="icon"><AppstoreFilled /></div>
|
||||||
|
|||||||
@ -1,48 +1,271 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="graph-container">
|
<div class="graph-container">
|
||||||
|
<div class="info">
|
||||||
|
<h1>Neo4j 图数据库</h1>
|
||||||
|
<p>基于 Neo4j 构建的图数据库。</p>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="actions-left">
|
||||||
|
<a-button @click="state.showModal = true">上传文件</a-button>
|
||||||
|
<a-modal v-model:open="state.showModal" title="上传文件" @ok="handleUpload">
|
||||||
|
<div class="upload">
|
||||||
|
<a-upload-dragger
|
||||||
|
class="upload-dragger"
|
||||||
|
v-model:fileList="fileList"
|
||||||
|
name="file"
|
||||||
|
:max-count="1"
|
||||||
|
:disabled="state.precessing"
|
||||||
|
action="/api/database/upload"
|
||||||
|
@change="handleFileUpload"
|
||||||
|
@drop="handleDrop"
|
||||||
|
>
|
||||||
|
<p class="ant-upload-text">点击或者把文件拖拽到这里上传</p>
|
||||||
|
<p class="ant-upload-hint">
|
||||||
|
目前仅支持上传文本文件,如 .pdf, .txt, .md。且同名文件无法重复添加。
|
||||||
|
</p>
|
||||||
|
</a-upload-dragger>
|
||||||
|
</div>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="addDocumentByFile"
|
||||||
|
:loading="state.precessing"
|
||||||
|
:disabled="fileList.length === 0"
|
||||||
|
style="margin: 0px 20px 20px 0;"
|
||||||
|
>
|
||||||
|
添加到图数据库
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="handleRefresh" :loading="state.refrashing">刷新状态</a-button>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
<div class="action-right">
|
||||||
|
<input
|
||||||
|
v-model="state.searchInput"
|
||||||
|
placeholder="输入要查询的实体"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="state.searchLoading"
|
||||||
|
@click="onSearch"
|
||||||
|
>
|
||||||
|
检索实体
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="main" id="container"></div>
|
<div class="main" id="container"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Graph } from "@antv/g6";
|
import { Graph } from "@antv/g6";
|
||||||
import { onMounted } from 'vue';
|
import { computed, onMounted, reactive, ref } from 'vue';
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
|
||||||
|
let graphInstance
|
||||||
|
const fileList = ref([]);
|
||||||
|
const subgraph = reactive({
|
||||||
|
nodes: [
|
||||||
|
{ id: '1', name: 'node1' },
|
||||||
|
{ id: '2', name: 'node2' },
|
||||||
|
{ id: '3', name: 'node3' },
|
||||||
|
{ id: '4', name: 'node4' },
|
||||||
|
{ id: '5', name: 'node5' },
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{ id: 'e1', source_id: '1', target_id: '2', type: 'edge1' },
|
||||||
|
{ id: 'e2', source_id: '1', target_id: '3', type: 'edge2' },
|
||||||
|
{ id: 'e3', source_id: '2', target_id: '4', type: 'edge3' },
|
||||||
|
{ id: 'e4', source_id: '2', target_id: '5', type: 'edge4' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
searchInput: '',
|
||||||
|
searchLoading: false,
|
||||||
|
showModal: false,
|
||||||
|
precessing: false,
|
||||||
|
})
|
||||||
|
|
||||||
const getCurWidth = () => document.getElementById("container").offsetWidth
|
const getCurWidth = () => document.getElementById("container").offsetWidth
|
||||||
const getCurHeight = () => document.getElementById("container").offsetHeight
|
const getCurHeight = () => document.getElementById("container").offsetHeight
|
||||||
|
|
||||||
|
const graphData = computed(() => {
|
||||||
|
return {
|
||||||
|
nodes: subgraph.nodes.map(node => {
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
data: {
|
||||||
|
label: node.name
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
edges: subgraph.edges.map(edge => {
|
||||||
|
return {
|
||||||
|
source: edge.source_id,
|
||||||
|
target: edge.target_id,
|
||||||
|
data: {
|
||||||
|
label: edge.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const addDocumentByFile = () => {
|
||||||
|
state.precessing = true
|
||||||
|
const files = fileList.value.filter(file => file.status === 'done').map(file => file.response.file_path)
|
||||||
|
fetch('/api/database/graph/add', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_path: files[0]
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
// .then(response => response.json())
|
||||||
|
// .then((data) => {
|
||||||
|
// message.success(data.message);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// message.error(error.message);
|
||||||
|
// })
|
||||||
|
// .finally(() => state.precessing = false)
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearch = () => {
|
||||||
|
if (!state.searchInput) {
|
||||||
|
message.error('请输入要查询的实体')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.searchLoading = true
|
||||||
|
fetch(`/api/database/graph/node?entity_name=${state.searchInput}`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
return res.json();
|
||||||
|
} else {
|
||||||
|
throw new Error("查询失败");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
subgraph.nodes = data.result.nodes
|
||||||
|
subgraph.edges = data.result.edges
|
||||||
|
console.log(data)
|
||||||
|
console.log(subgraph)
|
||||||
|
randerGraph()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
message.error(error.message);
|
||||||
|
})
|
||||||
|
.finally(() => state.searchLoading = false)
|
||||||
|
};
|
||||||
|
|
||||||
|
const randerGraph = () => {
|
||||||
|
graphInstance.setData(graphData.value);
|
||||||
|
graphInstance.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const graph = new Graph({
|
graphInstance = new Graph({
|
||||||
container: document.getElementById("container"),
|
container: document.getElementById("container"),
|
||||||
width: getCurWidth(),
|
width: getCurWidth(),
|
||||||
height: getCurHeight(),
|
height: getCurHeight(),
|
||||||
data: {
|
autoFit: true,
|
||||||
nodes: [
|
autoResize: true,
|
||||||
{
|
layout: {
|
||||||
id: "node-1",
|
type: 'force-atlas2',
|
||||||
style: { x: 50, y: 100 },
|
preventOverlap: true,
|
||||||
},
|
kr: 100,
|
||||||
{
|
|
||||||
id: "node-2",
|
|
||||||
style: { x: 150, y: 100 },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
edges: [{ id: "edge-1", source: "node-1", target: "node-2" }],
|
|
||||||
},
|
},
|
||||||
behaviors: ["drag-canvas", "zoom-canvas", "drag-element"],
|
node: {
|
||||||
|
type: 'circle',
|
||||||
|
style: {
|
||||||
|
labelText: (d) => d.data.label,
|
||||||
|
size: 40,
|
||||||
|
},
|
||||||
|
palette: {
|
||||||
|
field: 'label',
|
||||||
|
color: 'tableau',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
edge: {
|
||||||
|
type: 'line',
|
||||||
|
style: {
|
||||||
|
labelText: (d) => d.data.label,
|
||||||
|
labelBackground: '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
behaviors: ['drag-element'],
|
||||||
});
|
});
|
||||||
|
graphInstance.setData(graphData.value);
|
||||||
graph.render();
|
graphInstance.render();
|
||||||
|
window.addEventListener('resize', randerGraph);
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.graph-container {}
|
|
||||||
|
|
||||||
#container {
|
const handleFileUpload = (event) => {
|
||||||
width: 100%;
|
console.log(event)
|
||||||
height: 100%;
|
console.log(fileList.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDrop = (event) => {
|
||||||
|
console.log(event)
|
||||||
|
console.log(fileList.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.graph-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border: 2px solid #d9d9d9;
|
||||||
|
outline: none;
|
||||||
|
height: 42px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: var(--main-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 40px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.upload {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.upload-dragger {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#container {
|
||||||
|
background: #F7F7F7;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user