import { useState, useEffect } from 'react' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Separator } from '@/components/ui/separator' import { ScrollArea } from '@/components/ui/scroll-area' import { Progress } from '@/components/ui/progress' import { Memory } from '@/types' interface MemoryRelationshipsProps { memories: Memory[] extractTitle: (content: string, memory?: Memory) => string generateSummary: (content: string, memory?: Memory) => string extractTags: (memory: Memory) => string[] getTagColor: (tag: string) => { bg: string; text: string; border: string } onMemoryEdit: (memoryId: string) => void } interface RelationshipCluster { id: string name: string memories: Memory[] sharedTags: string[] strength: number } interface MemoryConnection { memory1: Memory memory2: Memory sharedTags: string[] strength: number type: 'tag-based' | 'content-similarity' | 'project-based' | 'temporal' } export function MemoryRelationships({ memories, extractTitle, generateSummary, extractTags, getTagColor, onMemoryEdit }: MemoryRelationshipsProps) { const [selectedNode, setSelectedNode] = useState(null) const [viewMode, setViewMode] = useState<'clusters' | 'connections'>('clusters') // Handle node click to open edit dialog const handleNodeClick = (nodeId: string | null) => { if (nodeId) { onMemoryEdit(nodeId) } setSelectedNode(nodeId) } const [clusters, setClusters] = useState([]) const [connections, setConnections] = useState([]) useEffect(() => { // Calculate memory relationships and clusters const calculateRelationships = () => { const newConnections: MemoryConnection[] = [] const newClusters: RelationshipCluster[] = [] // Find tag-based connections for (let i = 0; i < memories.length; i++) { for (let j = i + 1; j < memories.length; j++) { const memory1 = memories[i] const memory2 = memories[j] const tags1 = extractTags(memory1) const tags2 = extractTags(memory2) // Filter out title/summary metadata tags const meaningfulTags1 = tags1.filter(tag => !tag.startsWith('title:') && !tag.startsWith('summary:')) const meaningfulTags2 = tags2.filter(tag => !tag.startsWith('title:') && !tag.startsWith('summary:')) const sharedTags = meaningfulTags1.filter(tag => meaningfulTags2.includes(tag)) if (sharedTags.length > 0) { const strength = Math.min(sharedTags.length / Math.max(meaningfulTags1.length, meaningfulTags2.length), 1) newConnections.push({ memory1, memory2, sharedTags, strength, type: 'tag-based' }) } // Find project-based connections if (memory1.project && memory2.project && memory1.project === memory2.project) { newConnections.push({ memory1, memory2, sharedTags: [`project:${memory1.project}`], strength: 0.7, type: 'project-based' }) } // Find temporal connections (created within 24 hours) const time1 = new Date(memory1.timestamp).getTime() const time2 = new Date(memory2.timestamp).getTime() const timeDiff = Math.abs(time1 - time2) const oneDayMs = 24 * 60 * 60 * 1000 if (timeDiff < oneDayMs) { newConnections.push({ memory1, memory2, sharedTags: ['temporal-proximity'], strength: 0.3 * (1 - timeDiff / oneDayMs), type: 'temporal' }) } } } // Group memories into clusters based on shared tags const tagGroups = new Map() memories.forEach(memory => { const tags = extractTags(memory).filter(tag => !tag.startsWith('title:') && !tag.startsWith('summary:')) tags.forEach(tag => { if (!tagGroups.has(tag)) { tagGroups.set(tag, []) } tagGroups.get(tag)!.push(memory) }) }) // Create clusters for tags with multiple memories Array.from(tagGroups.entries()) .filter(([_, memories]) => memories.length > 1) .forEach(([tag, clusterMemories], index) => { const uniqueMemories = clusterMemories.filter((memory, i, self) => self.findIndex(m => m.id === memory.id) === i ) if (uniqueMemories.length > 1) { newClusters.push({ id: `cluster-${index}`, name: tag, memories: uniqueMemories, sharedTags: [tag], strength: uniqueMemories.length / memories.length }) } }) // Sort clusters by size newClusters.sort((a, b) => b.memories.length - a.memories.length) setConnections(newConnections) setClusters(newClusters) } calculateRelationships() }, [memories, extractTags]) const getConnectionTypeColor = (type: MemoryConnection['type']) => { const colors = { 'tag-based': 'bg-blue-600', 'content-similarity': 'bg-green-600', 'project-based': 'bg-purple-600', 'temporal': 'bg-yellow-600' } return colors[type] || 'bg-gray-600' } const getConnectionTypeIcon = (type: MemoryConnection['type']) => { const icons = { 'tag-based': '🏷️', 'content-similarity': '📄', 'project-based': '📁', 'temporal': '⏰' } return icons[type] || '🔗' } return (
{/* Header */}

🔗 Memory Relationships

Discover connections between your memories through tags, projects, and patterns

setViewMode(value)} className="w-full"> 📊 Clusters 🔍 Connections
Found {clusters.length} memory clusters
{clusters.map((cluster) => (
🏷️ {cluster.name} {cluster.memories.length} memories
{cluster.memories.map((memory) => (
{extractTitle(memory.content, memory)}
{generateSummary(memory.content, memory)}
{new Date(memory.timestamp).toLocaleDateString()}
))}
Cluster strength {Math.round(cluster.strength * 100)}%
))}
Found {connections.length} connections between memories
{connections .sort((a, b) => b.strength - a.strength) .slice(0, 50) .map((connection, index) => (
{extractTitle(connection.memory1.content, connection.memory1)}
{generateSummary(connection.memory1.content, connection.memory1)}
{extractTitle(connection.memory2.content, connection.memory2)}
{generateSummary(connection.memory2.content, connection.memory2)}
{getConnectionTypeIcon(connection.type)}
{connection.type.replace('-', ' ')}
{connection.sharedTags.slice(0, 3).map((tag, i) => ( {tag} ))} {connection.sharedTags.length > 3 && ( +{connection.sharedTags.length - 3} )}
{Math.round(connection.strength * 100)}%
))}
{/* Relationship Stats */}
🔗
Total Connections
{connections.length}
🏷️
Tag-based
{connections.filter(c => c.type === 'tag-based').length}
📁
Project-based
{connections.filter(c => c.type === 'project-based').length}
Temporal
{connections.filter(c => c.type === 'temporal').length}
) }