Spaces:
Sleeping
Sleeping
| # graph_manager.py - Version Graph Management (FIXED CHANGE DETECTION) | |
| import networkx as nx | |
| from typing import List, Dict, Optional, Set | |
| import json | |
| from datetime import datetime | |
| import difflib | |
| class GraphManager: | |
| """Manages version graph with documents, versions, and changes""" | |
| def __init__(self, user_id: str): | |
| self.user_id = user_id | |
| self.graph = nx.DiGraph() | |
| self.document_versions = {} # document_name -> [versions] | |
| self.version_content = {} # (document, version) -> content | |
| def add_document_version(self, document_name: str, version: str, | |
| content: str, metadata: Dict = None): | |
| """Add a new version of a document to the graph""" | |
| # Create document node if it doesn't exist | |
| if document_name not in self.graph: | |
| self.graph.add_node(document_name, node_type='document', | |
| metadata=metadata or {}) | |
| self.document_versions[document_name] = [] | |
| # Create version node | |
| version_node = f"{document_name}:{version}" | |
| self.graph.add_node( | |
| version_node, | |
| node_type='version', | |
| version=version, | |
| document=document_name, | |
| timestamp=datetime.now().isoformat(), | |
| metadata=metadata or {} | |
| ) | |
| # Link document to version | |
| self.graph.add_edge(document_name, version_node, edge_type='has_version') | |
| # Store content | |
| self.version_content[(document_name, version)] = content | |
| # Add to version list | |
| if version not in self.document_versions[document_name]: | |
| self.document_versions[document_name].append(version) | |
| self.document_versions[document_name].sort() | |
| # Link to previous version if exists | |
| versions = self.document_versions[document_name] | |
| if len(versions) > 1: | |
| prev_version = versions[versions.index(version) - 1] | |
| prev_node = f"{document_name}:{prev_version}" | |
| self.graph.add_edge(prev_node, version_node, edge_type='next_version') | |
| def add_version_with_changes(self, document_name: str, version: str, | |
| changes: Dict): | |
| """Add a version with explicit change tracking""" | |
| version_node = f"{document_name}:{version}" | |
| # Create change node | |
| change_node = f"{version_node}:changes" | |
| self.graph.add_node( | |
| change_node, | |
| node_type='changes', | |
| additions=changes.get('additions', []), | |
| deletions=changes.get('deletions', []), | |
| modifications=changes.get('modifications', []), | |
| timestamp=datetime.now().isoformat() | |
| ) | |
| # Link version to changes | |
| self.graph.add_edge(version_node, change_node, edge_type='has_changes') | |
| def get_all_documents(self) -> List[str]: | |
| """Get list of all documents""" | |
| return [node for node, data in self.graph.nodes(data=True) | |
| if data.get('node_type') == 'document'] | |
| def get_document_versions(self, document_name: str) -> List[str]: | |
| """Get all versions of a document""" | |
| return self.document_versions.get(document_name, []) | |
| def get_version_info(self, document_name: str, version: str) -> Dict: | |
| """Get information about a specific version""" | |
| version_node = f"{document_name}:{version}" | |
| if version_node in self.graph: | |
| return self.graph.nodes[version_node] | |
| return {} | |
| def get_changes_between_versions(self, document_name: str, | |
| version1: str, version2: str, | |
| max_display: int = 100) -> Dict: | |
| """Compute changes between two versions - FIXED TO SHOW ALL CHANGES""" | |
| content1 = self.version_content.get((document_name, version1), "") | |
| content2 = self.version_content.get((document_name, version2), "") | |
| if not content1 or not content2: | |
| return { | |
| 'additions': [], | |
| 'deletions': [], | |
| 'modifications': [], | |
| 'total_additions': 0, | |
| 'total_deletions': 0, | |
| 'total_modifications': 0, | |
| 'showing_limit': 0 | |
| } | |
| # Compute diff | |
| lines1 = content1.split('\n') | |
| lines2 = content2.split('\n') | |
| diff = difflib.unified_diff(lines1, lines2, lineterm='') | |
| additions = [] | |
| deletions = [] | |
| modifications = [] | |
| for line in diff: | |
| if line.startswith('+') and not line.startswith('+++'): | |
| additions.append(line[1:]) | |
| elif line.startswith('-') and not line.startswith('---'): | |
| deletions.append(line[1:]) | |
| elif line.startswith('?'): | |
| modifications.append(line[1:]) | |
| # FIXED: Return with total counts and display limit | |
| return { | |
| 'additions': additions[:max_display], # Show first N for UI performance | |
| 'deletions': deletions[:max_display], | |
| 'modifications': modifications[:max_display], | |
| 'total_additions': len(additions), # ✅ TOTAL count | |
| 'total_deletions': len(deletions), # ✅ TOTAL count | |
| 'total_modifications': len(modifications), # ✅ TOTAL count | |
| 'showing_limit': max_display, # ✅ Display limit | |
| 'truncated': len(additions) > max_display or len(deletions) > max_display or len(modifications) > max_display | |
| } | |
| def query_version_graph(self, query: str) -> List[Dict]: | |
| """Query the version graph for relevant versions""" | |
| results = [] | |
| for node, data in self.graph.nodes(data=True): | |
| if data.get('node_type') == 'version': | |
| # Simple keyword matching (can be enhanced with embeddings) | |
| if any(term.lower() in str(data).lower() for term in query.split()): | |
| results.append({ | |
| 'node': node, | |
| 'data': data | |
| }) | |
| return results | |
| def export_graph(self) -> Dict: | |
| """Export graph structure""" | |
| return { | |
| 'nodes': dict(self.graph.nodes(data=True)), | |
| 'edges': list(self.graph.edges(data=True)), | |
| 'document_versions': self.document_versions | |
| } | |
| def import_graph(self, graph_data: Dict): | |
| """Import graph structure""" | |
| self.graph = nx.DiGraph() | |
| for node, data in graph_data['nodes'].items(): | |
| self.graph.add_node(node, **data) | |
| for source, target, data in graph_data['edges']: | |
| self.graph.add_edge(source, target, **data) | |
| self.document_versions = graph_data.get('document_versions', {}) |