fleet-memory/memory/visualizer.py
2025-10-31 14:07:45 +01:00

165 lines
5.1 KiB
Python

"""
Memory visualization module.
Provides visual representations of memory networks and search paths.
"""
import time
from typing import List, Dict, Any, Optional, Tuple
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.layout import Layout
from rich.live import Live
from rich.text import Text
from rich import box
class MemoryVisualizer:
"""
Visualizes memory networks and search paths.
"""
def __init__(self):
"""Initialize the visualizer."""
self.console = Console()
def visualize_memory_graph(
self,
units: List[Dict[str, Any]],
links: List[Dict[str, Any]],
output_file: str = "memory_graph.png",
highlight_nodes: Optional[List[str]] = None,
):
"""
Create a visual representation of the memory graph.
Args:
units: List of memory units (id, text, context, etc.)
links: List of links (from_unit_id, to_unit_id, link_type, weight)
output_file: Output file path for the visualization
highlight_nodes: Optional list of node IDs to highlight
"""
# Create directed graph
G = nx.DiGraph()
# Add nodes
node_labels = {}
for unit in units:
unit_id = str(unit['id'])
# Truncate text for display
label = unit['text'][:40] + "..." if len(unit['text']) > 40 else unit['text']
G.add_node(unit_id)
node_labels[unit_id] = label
# Add edges
temporal_edges = []
semantic_edges = []
for link in links:
from_id = str(link['from_unit_id'])
to_id = str(link['to_unit_id'])
weight = link['weight']
link_type = link['link_type']
if link_type == 'temporal':
temporal_edges.append((from_id, to_id, weight))
else: # semantic
semantic_edges.append((from_id, to_id, weight))
G.add_edge(from_id, to_id, weight=weight, type=link_type)
# Create figure
fig, ax = plt.subplots(figsize=(20, 14))
ax.set_facecolor('#1a1a2e')
fig.patch.set_facecolor('#0f0f1e')
# Use spring layout for better visualization
pos = nx.spring_layout(G, k=2, iterations=50, seed=42)
# Draw temporal edges (blue)
if temporal_edges:
nx.draw_networkx_edges(
G, pos,
edgelist=[(e[0], e[1]) for e in temporal_edges],
edge_color='#4ecdc4',
alpha=0.6,
width=2,
arrows=True,
arrowsize=15,
arrowstyle='->',
connectionstyle='arc3,rad=0.1',
ax=ax
)
# Draw semantic edges (purple)
if semantic_edges:
nx.draw_networkx_edges(
G, pos,
edgelist=[(e[0], e[1]) for e in semantic_edges],
edge_color='#ff6b9d',
alpha=0.6,
width=2,
arrows=True,
arrowsize=15,
arrowstyle='->',
connectionstyle='arc3,rad=0.1',
ax=ax
)
# Determine node colors
node_colors = []
for node in G.nodes():
if highlight_nodes and node in highlight_nodes:
node_colors.append('#ffd93d') # Yellow for highlighted
else:
node_colors.append('#6c63ff') # Purple for normal
# Draw nodes
nx.draw_networkx_nodes(
G, pos,
node_color=node_colors,
node_size=3000,
alpha=0.9,
ax=ax
)
# Draw labels
nx.draw_networkx_labels(
G, pos,
node_labels,
font_size=8,
font_color='white',
font_weight='bold',
ax=ax
)
# Add legend
legend_elements = [
plt.Line2D([0], [0], color='#4ecdc4', lw=2, label='Temporal Links'),
plt.Line2D([0], [0], color='#ff6b9d', lw=2, label='Semantic Links'),
plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='#6c63ff',
markersize=10, label='Memory Unit', linestyle=''),
]
if highlight_nodes:
legend_elements.append(
plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='#ffd93d',
markersize=10, label='Highlighted', linestyle='')
)
ax.legend(handles=legend_elements, loc='upper left', facecolor='#2d2d44',
edgecolor='white', fontsize=10, labelcolor='white')
# Title
ax.set_title('Memory Network Graph\nTemporal + Semantic Architecture',
color='white', fontsize=16, fontweight='bold', pad=20)
ax.axis('off')
plt.tight_layout()
plt.savefig(output_file, dpi=150, facecolor='#0f0f1e')
plt.close()
self.console.print(f"[green]✓[/green] Memory graph saved to [cyan]{output_file}[/cyan]")