diff --git a/tools/memory_visualizer/visualizer_server.py b/tools/memory_visualizer/visualizer_server.py index 222f38053..3c8c2d2c5 100644 --- a/tools/memory_visualizer/visualizer_server.py +++ b/tools/memory_visualizer/visualizer_server.py @@ -5,22 +5,21 @@ """ import asyncio -import orjson import logging + +# 添加项目根目录到 Python 路径 +import sys from datetime import datetime from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Optional from flask import Flask, jsonify, render_template, request from flask_cors import CORS -# 添加项目根目录到 Python 路径 -import sys project_root = Path(__file__).parent.parent.parent sys.path.insert(0, str(project_root)) from src.memory_graph.manager import MemoryManager -from src.memory_graph.models import EdgeType, MemoryType, NodeType logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -48,61 +47,63 @@ def init_memory_manager(): raise -@app.route('/') +@app.route("/") def index(): """主页面""" - return render_template('visualizer.html') + return render_template("visualizer.html") -@app.route('/api/graph/full') +@app.route("/api/graph/full") def get_full_graph(): """ 获取完整记忆图数据 - + 返回所有节点和边,格式化为前端可用的结构 """ try: if memory_manager is None: init_memory_manager() - + # 获取所有记忆 loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - + # 获取所有记忆 all_memories = memory_manager.graph_store.get_all_memories() - + # 构建节点和边数据 nodes_dict = {} # {node_id: node_data} edges_dict = {} # {edge_id: edge_data} - 使用字典去重 memory_info = [] - + for memory in all_memories: # 添加记忆信息 - memory_info.append({ - 'id': memory.id, - 'type': memory.memory_type.value, - 'importance': memory.importance, - 'activation': memory.activation, - 'status': memory.status.value, - 'created_at': memory.created_at.isoformat(), - 'text': memory.to_text(), - 'access_count': memory.access_count, - }) - + memory_info.append( + { + "id": memory.id, + "type": memory.memory_type.value, + "importance": memory.importance, + "activation": memory.activation, + "status": memory.status.value, + "created_at": memory.created_at.isoformat(), + "text": memory.to_text(), + "access_count": memory.access_count, + } + ) + # 处理节点 for node in memory.nodes: if node.id not in nodes_dict: nodes_dict[node.id] = { - 'id': node.id, - 'label': node.content, - 'type': node.node_type.value, - 'group': node.node_type.name, # 用于颜色分组 - 'title': f"{node.node_type.value}: {node.content}", - 'metadata': node.metadata, - 'created_at': node.created_at.isoformat(), + "id": node.id, + "label": node.content, + "type": node.node_type.value, + "group": node.node_type.name, # 用于颜色分组 + "title": f"{node.node_type.value}: {node.content}", + "metadata": node.metadata, + "created_at": node.created_at.isoformat(), } - + # 处理边 - 使用字典自动去重 for edge in memory.edges: edge_id = edge.id @@ -112,82 +113,72 @@ def get_full_graph(): while edge_id in edges_dict: edge_id = f"{original_edge_id}_{counter}" counter += 1 - + edges_dict[edge_id] = { - 'id': edge_id, - 'from': edge.source_id, - 'to': edge.target_id, - 'label': edge.relation, - 'type': edge.edge_type.value, - 'importance': edge.importance, - 'title': f"{edge.edge_type.value}: {edge.relation}", - 'arrows': 'to', - 'memory_id': memory.id, + "id": edge_id, + "from": edge.source_id, + "to": edge.target_id, + "label": edge.relation, + "type": edge.edge_type.value, + "importance": edge.importance, + "title": f"{edge.edge_type.value}: {edge.relation}", + "arrows": "to", + "memory_id": memory.id, } - + nodes_list = list(nodes_dict.values()) edges_list = list(edges_dict.values()) - - return jsonify({ - 'success': True, - 'data': { - 'nodes': nodes_list, - 'edges': edges_list, - 'memories': memory_info, - 'stats': { - 'total_nodes': len(nodes_list), - 'total_edges': len(edges_list), - 'total_memories': len(all_memories), - } + + return jsonify( + { + "success": True, + "data": { + "nodes": nodes_list, + "edges": edges_list, + "memories": memory_info, + "stats": { + "total_nodes": len(nodes_list), + "total_edges": len(edges_list), + "total_memories": len(all_memories), + }, + }, } - }) - + ) + except Exception as e: logger.error(f"获取图数据失败: {e}", exc_info=True) - return jsonify({ - 'success': False, - 'error': str(e) - }), 500 + return jsonify({"success": False, "error": str(e)}), 500 -@app.route('/api/memory/') +@app.route("/api/memory/") def get_memory_detail(memory_id: str): """ 获取特定记忆的详细信息 - + Args: memory_id: 记忆ID """ try: if memory_manager is None: init_memory_manager() - + memory = memory_manager.graph_store.get_memory_by_id(memory_id) - + if memory is None: - return jsonify({ - 'success': False, - 'error': '记忆不存在' - }), 404 - - return jsonify({ - 'success': True, - 'data': memory.to_dict() - }) - + return jsonify({"success": False, "error": "记忆不存在"}), 404 + + return jsonify({"success": True, "data": memory.to_dict()}) + except Exception as e: logger.error(f"获取记忆详情失败: {e}", exc_info=True) - return jsonify({ - 'success': False, - 'error': str(e) - }), 500 + return jsonify({"success": False, "error": str(e)}), 500 -@app.route('/api/search') +@app.route("/api/search") def search_memories(): """ 搜索记忆 - + Query参数: - q: 搜索关键词 - type: 记忆类型过滤 @@ -196,50 +187,46 @@ def search_memories(): try: if memory_manager is None: init_memory_manager() - - query = request.args.get('q', '') - memory_type = request.args.get('type', None) - limit = int(request.args.get('limit', 50)) - + + query = request.args.get("q", "") + memory_type = request.args.get("type", None) + limit = int(request.args.get("limit", 50)) + loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - + # 执行搜索 - results = loop.run_until_complete( - memory_manager.search_memories( - query=query, - top_k=limit - ) - ) - + results = loop.run_until_complete(memory_manager.search_memories(query=query, top_k=limit)) + # 构建返回数据 memories = [] for memory in results: - memories.append({ - 'id': memory.id, - 'text': memory.to_text(), - 'type': memory.memory_type.value, - 'importance': memory.importance, - 'created_at': memory.created_at.isoformat(), - }) - - return jsonify({ - 'success': True, - 'data': { - 'results': memories, - 'count': len(memories), + memories.append( + { + "id": memory.id, + "text": memory.to_text(), + "type": memory.memory_type.value, + "importance": memory.importance, + "created_at": memory.created_at.isoformat(), + } + ) + + return jsonify( + { + "success": True, + "data": { + "results": memories, + "count": len(memories), + }, } - }) - + ) + except Exception as e: logger.error(f"搜索失败: {e}", exc_info=True) - return jsonify({ - 'success': False, - 'error': str(e) - }), 500 + return jsonify({"success": False, "error": str(e)}), 500 -@app.route('/api/stats') +@app.route("/api/stats") def get_statistics(): """ 获取记忆图统计信息 @@ -247,48 +234,42 @@ def get_statistics(): try: if memory_manager is None: init_memory_manager() - + # 获取统计信息 all_memories = memory_manager.graph_store.get_all_memories() all_nodes = set() all_edges = 0 - + for memory in all_memories: for node in memory.nodes: all_nodes.add(node.id) all_edges += len(memory.edges) - + stats = { - 'total_memories': len(all_memories), - 'total_nodes': len(all_nodes), - 'total_edges': all_edges, - 'node_types': {}, - 'memory_types': {}, + "total_memories": len(all_memories), + "total_nodes": len(all_nodes), + "total_edges": all_edges, + "node_types": {}, + "memory_types": {}, } - + # 统计节点类型分布 for memory in all_memories: mem_type = memory.memory_type.value - stats['memory_types'][mem_type] = stats['memory_types'].get(mem_type, 0) + 1 - + stats["memory_types"][mem_type] = stats["memory_types"].get(mem_type, 0) + 1 + for node in memory.nodes: node_type = node.node_type.value - stats['node_types'][node_type] = stats['node_types'].get(node_type, 0) + 1 - - return jsonify({ - 'success': True, - 'data': stats - }) - + stats["node_types"][node_type] = stats["node_types"].get(node_type, 0) + 1 + + return jsonify({"success": True, "data": stats}) + except Exception as e: logger.error(f"获取统计信息失败: {e}", exc_info=True) - return jsonify({ - 'success': False, - 'error': str(e) - }), 500 + return jsonify({"success": False, "error": str(e)}), 500 -@app.route('/api/files') +@app.route("/api/files") def list_files(): """ 列出所有可用的数据文件 @@ -296,53 +277,51 @@ def list_files(): """ try: from pathlib import Path + data_dir = Path("data/memory_graph") - + files = [] if data_dir.exists(): for f in data_dir.glob("*.json"): stat = f.stat() - files.append({ - 'path': str(f), - 'name': f.name, - 'size': stat.st_size, - 'size_kb': round(stat.st_size / 1024, 2), - 'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(), - 'modified_readable': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M:%S'), - 'is_current': True # 完整版始终使用内存数据 - }) - - return jsonify({ - 'success': True, - 'files': files, - 'count': len(files), - 'current_file': 'memory_manager (实时数据)', - 'note': '完整版服务器使用实时内存数据,如需切换文件请使用独立版服务器' - }) + files.append( + { + "path": str(f), + "name": f.name, + "size": stat.st_size, + "size_kb": round(stat.st_size / 1024, 2), + "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(), + "modified_readable": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S"), + "is_current": True, # 完整版始终使用内存数据 + } + ) + + return jsonify( + { + "success": True, + "files": files, + "count": len(files), + "current_file": "memory_manager (实时数据)", + "note": "完整版服务器使用实时内存数据,如需切换文件请使用独立版服务器", + } + ) except Exception as e: logger.error(f"获取文件列表失败: {e}", exc_info=True) - return jsonify({ - 'success': False, - 'error': str(e) - }), 500 + return jsonify({"success": False, "error": str(e)}), 500 -@app.route('/api/reload') +@app.route("/api/reload") def reload_data(): """ 重新加载数据 """ - return jsonify({ - 'success': True, - 'message': '完整版服务器使用实时数据,无需重新加载', - 'note': '数据始终是最新的' - }) + return jsonify({"success": True, "message": "完整版服务器使用实时数据,无需重新加载", "note": "数据始终是最新的"}) -def run_server(host: str = '127.0.0.1', port: int = 5000, debug: bool = False): +def run_server(host: str = "127.0.0.1", port: int = 5000, debug: bool = False): """ 启动可视化服务器 - + Args: host: 服务器地址 port: 端口号 @@ -352,5 +331,5 @@ def run_server(host: str = '127.0.0.1', port: int = 5000, debug: bool = False): app.run(host=host, port=port, debug=debug) -if __name__ == '__main__': +if __name__ == "__main__": run_server(debug=True)