Revert "fix(api): 修复记忆可视化中重复的边"

This reverts commit f8e42f3348.
This commit is contained in:
minecraft1024a
2025-11-07 22:03:09 +08:00
committed by Windpicker-owo
parent 636b1e9fea
commit e350b5a177
2 changed files with 101 additions and 278 deletions

View File

@@ -61,27 +61,15 @@ def find_available_data_files() -> List[Path]:
return sorted(files, key=lambda f: f.stat().st_mtime, reverse=True) return sorted(files, key=lambda f: f.stat().st_mtime, reverse=True)
def load_graph_data_from_file( def load_graph_data_from_file(file_path: Optional[Path] = None) -> Dict[str, Any]:
file_path: Optional[Path] = None, """从磁盘加载图数据"""
nodes_page: Optional[int] = None,
nodes_per_page: Optional[int] = None,
edges_page: Optional[int] = None,
edges_per_page: Optional[int] = None,
) -> Dict[str, Any]:
"""
从磁盘加载图数据, 支持分页。
如果不提供分页参数, 则加载并缓存所有数据。
"""
global graph_data_cache, current_data_file global graph_data_cache, current_data_file
# 如果是请求分页数据, 则不使用缓存的全量数据
is_paged_request = nodes_page is not None or edges_page is not None
if file_path and file_path != current_data_file: if file_path and file_path != current_data_file:
graph_data_cache = None graph_data_cache = None
current_data_file = file_path current_data_file = file_path
if graph_data_cache and not is_paged_request: if graph_data_cache:
return graph_data_cache return graph_data_cache
try: try:
@@ -96,78 +84,53 @@ def load_graph_data_from_file(
if not graph_file.exists(): if not graph_file.exists():
return {"error": f"文件不存在: {graph_file}", "nodes": [], "edges": [], "stats": {}} return {"error": f"文件不存在: {graph_file}", "nodes": [], "edges": [], "stats": {}}
# 只有在没有缓存时才从磁盘读取和处理文件 with open(graph_file, "r", encoding="utf-8") as f:
if not graph_data_cache: data = orjson.loads(f.read())
with open(graph_file, "r", encoding="utf-8") as f:
data = orjson.loads(f.read())
nodes = data.get("nodes", []) nodes = data.get("nodes", [])
edges = data.get("edges", []) edges = data.get("edges", [])
metadata = data.get("metadata", {}) metadata = data.get("metadata", {})
nodes_dict = { nodes_dict = {
node["id"]: { node["id"]: {
**node, **node,
"label": node.get("content", ""), "label": node.get("content", ""),
"group": node.get("node_type", ""), "group": node.get("node_type", ""),
"title": f"{node.get('node_type', '')}: {node.get('content', '')}", "title": f"{node.get('node_type', '')}: {node.get('content', '')}",
}
for node in nodes
if node.get("id")
} }
for node in nodes
if node.get("id")
}
edges_list = [] edges_list = [
seen_edge_ids = set() {
for edge in edges: **edge,
edge_id = edge.get("id") "from": edge.get("source", edge.get("source_id")),
if edge_id and edge_id not in seen_edge_ids: "to": edge.get("target", edge.get("target_id")),
edges_list.append( "label": edge.get("relation", ""),
{ "arrows": "to",
**edge,
"from": edge.get("source", edge.get("source_id")),
"to": edge.get("target", edge.get("target_id")),
"label": edge.get("relation", ""),
"arrows": "to",
}
)
seen_edge_ids.add(edge_id)
stats = metadata.get("statistics", {})
total_memories = stats.get("total_memories", 0)
graph_data_cache = {
"nodes": list(nodes_dict.values()),
"edges": edges_list,
"memories": [], # TODO: 未来也可以考虑分页加载记忆
"stats": {
"total_nodes": len(nodes_dict),
"total_edges": len(edges_list),
"total_memories": total_memories,
},
"current_file": str(graph_file),
"file_size": graph_file.stat().st_size,
"file_modified": datetime.fromtimestamp(graph_file.stat().st_mtime).isoformat(),
} }
for edge in edges
]
# 如果是分页请求, 则从缓存中切片数据 stats = metadata.get("statistics", {})
if is_paged_request: total_memories = stats.get("total_memories", 0)
paged_data = graph_data_cache.copy() # 浅拷贝一份, 避免修改缓存
# 分页节点
if nodes_page is not None and nodes_per_page is not None:
node_start = (nodes_page - 1) * nodes_per_page
node_end = node_start + nodes_per_page
paged_data["nodes"] = graph_data_cache["nodes"][node_start:node_end]
# 分页边
if edges_page is not None and edges_per_page is not None:
edge_start = (edges_page - 1) * edges_per_page
edge_end = edge_start + edges_per_page
paged_data["edges"] = graph_data_cache["edges"][edge_start:edge_end]
return paged_data
graph_data_cache = {
"nodes": list(nodes_dict.values()),
"edges": edges_list,
"memories": [],
"stats": {
"total_nodes": len(nodes_dict),
"total_edges": len(edges_list),
"total_memories": total_memories,
},
"current_file": str(graph_file),
"file_size": graph_file.stat().st_size,
"file_modified": datetime.fromtimestamp(graph_file.stat().st_mtime).isoformat(),
}
return graph_data_cache return graph_data_cache
except Exception as e: except Exception as e:
import traceback import traceback
@@ -188,7 +151,7 @@ def _format_graph_data_from_manager(memory_manager) -> Dict[str, Any]:
all_memories = memory_manager.graph_store.get_all_memories() all_memories = memory_manager.graph_store.get_all_memories()
nodes_dict = {} nodes_dict = {}
edges_dict = {} edges_list = []
memory_info = [] memory_info = []
for memory in all_memories: for memory in all_memories:
@@ -210,8 +173,8 @@ def _format_graph_data_from_manager(memory_manager) -> Dict[str, Any]:
"title": f"{node.node_type.value}: {node.content}", "title": f"{node.node_type.value}: {node.content}",
} }
for edge in memory.edges: for edge in memory.edges:
if edge.id not in edges_dict: edges_list.append( # noqa: PERF401
edges_dict[edge.id] = { {
"id": edge.id, "id": edge.id,
"from": edge.source_id, "from": edge.source_id,
"to": edge.target_id, "to": edge.target_id,
@@ -219,8 +182,7 @@ def _format_graph_data_from_manager(memory_manager) -> Dict[str, Any]:
"arrows": "to", "arrows": "to",
"memory_id": memory.id, "memory_id": memory.id,
} }
)
edges_list = list(edges_dict.values())
stats = memory_manager.get_statistics() stats = memory_manager.get_statistics()
return { return {
@@ -235,67 +197,28 @@ def _format_graph_data_from_manager(memory_manager) -> Dict[str, Any]:
"current_file": "memory_manager (实时数据)", "current_file": "memory_manager (实时数据)",
} }
@router.get("/api/graph/paged")
async def get_paged_graph(
nodes_page: int = 1, nodes_per_page: int = 100, edges_page: int = 1, edges_per_page: int = 200
):
"""获取分页的记忆图数据"""
try:
# 确保全量数据已加载到缓存
full_data = load_graph_data_from_file()
if "error" in full_data:
raise HTTPException(status_code=404, detail=full_data["error"])
# 从缓存中获取全量数据
all_nodes = full_data.get("nodes", [])
all_edges = full_data.get("edges", [])
total_nodes = len(all_nodes)
total_edges = len(all_edges)
# 计算节点分页
node_start = (nodes_page - 1) * nodes_per_page
node_end = node_start + nodes_per_page
paginated_nodes = all_nodes[node_start:node_end]
# 计算边分页
edge_start = (edges_page - 1) * edges_per_page
edge_end = edge_start + edges_per_page
paginated_edges = all_edges[edge_start:edge_end]
return JSONResponse(
content={
"success": True,
"data": {
"nodes": paginated_nodes,
"edges": paginated_edges,
"pagination": {
"nodes": {
"page": nodes_page,
"per_page": nodes_per_page,
"total": total_nodes,
"total_pages": (total_nodes + nodes_per_page - 1) // nodes_per_page,
},
"edges": {
"page": edges_page,
"per_page": edges_per_page,
"total": total_edges,
"total_pages": (total_edges + edges_per_page - 1) // edges_per_page,
},
},
},
}
)
except Exception as e:
return JSONResponse(content={"success": False, "error": str(e)}, status_code=500)
@router.get("/api/graph/full") @router.get("/api/graph/full")
async def get_full_graph_deprecated(): async def get_full_graph():
""" """获取完整记忆图数据"""
(已废弃) 获取完整记忆图数据。 try:
此接口现在只返回第一页的数据, 请使用 /api/graph/paged 进行分页获取。 from src.memory_graph.manager_singleton import get_memory_manager
"""
return await get_paged_graph(nodes_page=1, nodes_per_page=100, edges_page=1, edges_per_page=200) memory_manager = get_memory_manager()
data = {}
if memory_manager and memory_manager._initialized:
data = _format_graph_data_from_manager(memory_manager)
else:
# 如果内存管理器不可用,则从文件加载
data = load_graph_data_from_file()
return JSONResponse(content={"success": True, "data": data})
except Exception as e:
import traceback
traceback.print_exc()
return JSONResponse(content={"success": False, "error": str(e)}, status_code=500)
@router.get("/api/files") @router.get("/api/files")

View File

@@ -533,18 +533,11 @@
let network = null; let network = null;
let availableFiles = []; let availableFiles = [];
let graphData = { let graphData = {
nodes: new vis.DataSet([]) nodes: [],
, edges: [],
edges: new vis.DataSet([]) memories: []
}; };
let originalData = null; // 用于过滤器 let originalData = null;
// 分页状态
let pagination = {
nodes: { page: 1, per_page: 200, total_pages: 1, total: 0 },
edges: { page: 1, per_page: 500, total_pages: 1, total: 0 }
};
let isLoading = false;
// 节点颜色配置 // 节点颜色配置
const nodeColors = { const nodeColors = {
@@ -660,93 +653,35 @@
}); });
} }
// 重置并加载第一页数据 // 加载图形数据
async function loadGraph() { async function loadGraph() {
if (isLoading) return;
console.log('开始加载初始图数据...');
// 重置状态
graphData.nodes.clear();
graphData.edges.clear();
pagination.nodes.page = 1;
pagination.edges.page = 1;
try { try {
// 先获取一次完整的统计信息 document.getElementById('loading').style.display = 'block';
const statsResponse = await fetch('/visualizer/api/stats');
const statsResult = await statsResponse.json(); const response = await fetch('/visualizer/api/graph/full');
if(statsResult.success) {
updateStats(statsResult.data);
pagination.nodes.total = statsResult.data.total_nodes;
pagination.edges.total = statsResult.data.total_edges;
pagination.nodes.total_pages = Math.ceil(statsResult.data.total_nodes / pagination.nodes.per_page);
pagination.edges.total_pages = Math.ceil(statsResult.data.total_edges / pagination.edges.per_page);
} else {
throw new Error('获取统计信息失败: ' + statsResult.error);
}
// 加载第一页
await loadMoreData();
} catch (error) {
console.error('初始加载失败:', error);
alert('初始加载失败: ' + error.message);
}
}
// 加载更多数据(分页核心)
async function loadMoreData() {
if (isLoading) return;
const canLoadNodes = pagination.nodes.page <= pagination.nodes.total_pages;
const canLoadEdges = pagination.edges.page <= pagination.edges.total_pages;
if (!canLoadNodes && !canLoadEdges) {
console.log('所有数据已加载完毕');
return;
}
isLoading = true;
document.getElementById('loading').style.display = 'block';
try {
const url = `/visualizer/api/graph/paged?nodes_page=${pagination.nodes.page}&nodes_per_page=${pagination.nodes.per_page}&edges_page=${pagination.edges.page}&edges_per_page=${pagination.edges.per_page}`;
console.log(`正在请求: ${url}`);
const response = await fetch(url);
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
console.log(`成功获取 ${result.data.nodes.length} 个节点, ${result.data.edges.length} 个边`); originalData = result.data;
updateGraph(result.data); // 追加数据 updateGraph(result.data);
updateStats(result.data.stats);
// 更新分页信息
if (result.data.pagination) {
pagination.nodes.page++;
pagination.edges.page++;
}
} else { } else {
throw new Error('加载分页数据失败: ' + result.error); alert('加载失败: ' + result.error);
} }
} catch (error) { } catch (error) {
console.error('加载更多数据失败:', error); console.error('加载图形失败:', error);
alert('加载失败: ' + error.message); alert('加载失败: ' + error.message);
} finally { } finally {
isLoading = false;
document.getElementById('loading').style.display = 'none'; document.getElementById('loading').style.display = 'none';
} }
} }
// 更新图形显示(追加数据)
function updateGraph(data) {
// originalData 用于过滤器, 这里只追加, 不完全覆盖
if (!originalData) {
originalData = { nodes: [], edges: [] };
}
originalData.nodes.push(...data.nodes);
originalData.edges.push(...data.edges);
// 更新图形显示
function updateGraph(data) {
graphData = data;
// 处理节点数据 // 处理节点数据
const newNodes = data.nodes.map(node => ({ const nodes = data.nodes.map(node => ({
id: node.id, id: node.id,
label: node.label, label: node.label,
title: node.title, title: node.title,
@@ -756,31 +691,25 @@
})); }));
// 处理边数据 // 处理边数据
const newEdges = data.edges.map(edge => ({ const edges = data.edges.map(edge => ({
id: edge.id, id: edge.id,
from: edge.from, from: edge.from,
to: edge.to, to: edge.to,
label: edge.label, label: edge.label,
title: edge.title, title: edge.title,
width: (edge.importance || 0.5) * 2 + 1 width: edge.importance * 3 + 1
})); }));
// 追加数据到 DataSet // 更新网络
if (newNodes.length > 0) { network.setData({
graphData.nodes.add(newNodes); nodes: new vis.DataSet(nodes),
} edges: new vis.DataSet(edges)
if (newEdges.length > 0) { });
graphData.edges.add(newEdges);
} // 注意setData 会自动触发物理引擎重新布局
// stabilizationIterationsDone 事件监听器会自动停止物理引擎
// 第一次加载时设置数据
if (pagination.nodes.page === 2) { // 意味着第一页刚加载完
network.setData({
nodes: graphData.nodes,
edges: graphData.edges
});
}
} }
// 更新统计信息 // 更新统计信息
function updateStats(stats) { function updateStats(stats) {
document.getElementById('statNodes').textContent = stats.total_nodes; document.getElementById('statNodes').textContent = stats.total_nodes;
@@ -1234,42 +1163,13 @@
closeFileSelector(); closeFileSelector();
} }
} }
// 页面加载完成后初始化
window.addEventListener('load', function() {
initNetwork();
loadGraph(); // 加载初始数据
loadFileList();
// 添加滚动加载监听器 // 页面加载完成后初始化
const graphContainer = document.getElementById('memory-graph'); window.addEventListener('load', function() {
graphContainer.addEventListener('mousewheel', async (event) => { initNetwork();
if(network) { loadGraph();
const canvasHeight = network.canvas.body.height; loadFileList();
const viewPosition = network.getViewPosition(); });
const scale = network.getScale();
const viewHeight = canvasHeight / scale;
// 简单的滚动到底部检测(可能需要根据实际情况微调)
if (event.deltaY > 0 && !isLoading) {
const isAtBottom = viewPosition.y > (canvasHeight/2 - viewHeight/2) * 0.8;
if (isAtBottom) {
console.log("滚动到底部,加载更多数据...");
await loadMoreData();
}
}
}
});
// 添加一个按钮用于手动加载
const loadMoreBtn = document.createElement('button');
loadMoreBtn.textContent = '加载更多';
loadMoreBtn.className = 'btn';
loadMoreBtn.style.position = 'absolute';
loadMoreBtn.style.bottom = '20px';
loadMoreBtn.style.right = '20px';
loadMoreBtn.style.zIndex = '10';
loadMoreBtn.onclick = loadMoreData;
document.querySelector('.graph-container').appendChild(loadMoreBtn);
});
</script> </script>
</body> </body>
</html> </html>