""" Interactive HTML graph visualization of memory system. Uses Cytoscape.js to create a performant, interactive network graph that can be explored in the browser. Shows all memory units and their links with weights. """ import psycopg2 from dotenv import load_dotenv import os import json load_dotenv() def create_interactive_graph(): """Create an interactive HTML graph visualization using Cytoscape.js.""" # Connect to database conn = psycopg2.connect(os.getenv('DATABASE_URL')) cursor = conn.cursor() # Get all memory units (no agent_id filter) cursor.execute(""" SELECT id, text, event_date, context FROM memory_units ORDER BY event_date """) units = cursor.fetchall() # Get all links with weights (no agent_id filter) cursor.execute(""" SELECT ml.from_unit_id, ml.to_unit_id, ml.link_type, ml.weight, e.canonical_name as entity_name FROM memory_links ml LEFT JOIN entities e ON ml.entity_id = e.id ORDER BY ml.link_type, ml.weight DESC """) links = cursor.fetchall() # Get entity information (no agent_id filter) cursor.execute(""" SELECT ue.unit_id, e.canonical_name, e.entity_type FROM unit_entities ue JOIN entities e ON ue.entity_id = e.id ORDER BY ue.unit_id """) unit_entities = cursor.fetchall() cursor.close() conn.close() # Build entity mapping entity_map = {} for unit_id, entity_name, entity_type in unit_entities: if unit_id not in entity_map: entity_map[unit_id] = [] entity_map[unit_id].append(f"{entity_name} ({entity_type})") # Build Cytoscape.js graph data cy_nodes = [] cy_edges = [] # Add nodes for unit_id, text, event_date, context in units: entities = entity_map.get(unit_id, []) entity_count = len(entities) # Color by entity count if entity_count == 0: color = "#e0e0e0" elif entity_count == 1: color = "#90caf9" else: color = "#42a5f5" cy_nodes.append({ "data": { "id": str(unit_id), "label": text[:50] + "..." if len(text) > 50 else text, "text": text, "context": context, "date": str(event_date.date()), "entities": ", ".join(entities) if entities else "None", "color": color } }) # Add edges for from_id, to_id, link_type, weight, entity_name in links: # Set color based on link type if link_type == 'temporal': color = "#00bcd4" line_style = "dashed" elif link_type == 'semantic': color = "#ff69b4" line_style = "solid" elif link_type == 'entity': color = "#ffd700" line_style = "solid" else: color = "#999999" line_style = "solid" cy_edges.append({ "data": { "id": f"{from_id}-{to_id}-{link_type}", "source": str(from_id), "target": str(to_id), "weight": weight, "linkType": link_type, "entityName": entity_name or "", "color": color, "lineStyle": line_style } }) graph_data = {"nodes": cy_nodes, "edges": cy_edges} # Build table rows for table view table_rows = [] for unit_id, text, event_date, context in units: entities = entity_map.get(unit_id, []) entity_str = ", ".join(entities) if entities else "None" table_rows.append(f""" {str(unit_id)[:8]}... {text} {context} {event_date.date()} {entity_str} """) # Generate HTML with Cytoscape.js html_content = f""" Memory Graph - Interactive Visualization

Legend

Link Types:

Temporal
Semantic
Entity

Nodes:

No entities
1 entity
2+ entities

Memory Units ({len(units)})

{''.join(table_rows)}
ID Text Context Date Entities
""" # Write HTML file output_file = "memory_graph_interactive.html" with open(output_file, 'w', encoding='utf-8') as f: f.write(html_content) # Print summary print(f"\n{'='*80}") print("INTERACTIVE GRAPH GENERATED (Cytoscape.js)") print(f"{'='*80}") print(f"\nFile: {output_file}") print(f"Units: {len(units)}") print(f"Links: {len(links)}") print("\nFeatures:") print(" • Tab 1: Graph View - Fast interactive network (Cytoscape.js)") print(" - Limit nodes (default: 50) for better performance") print(" - Choose layout: Circle (fast), Grid (fast), or Force-directed") print(" - Drag nodes, zoom, pan") print(" - Hover for details") print(" • Tab 2: Table View - Searchable memory units") print(" - Filter by text, context, or entities") print(" - Case-insensitive search") print(" - Shows ALL nodes") print(f"\n{'='*80}") print(f"✓ Open {output_file} in your browser to explore!") print(f" TIP: Start with 50 nodes and Circle layout for best performance") print(f"{'='*80}\n") if __name__ == "__main__": create_interactive_graph()