File size: 4,767 Bytes
cfec14d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""
Agent 3: Visual Comparator
Compares Figma and Website screenshots, generates diff overlays.
"""

from typing import Dict, Any
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from utils.image_differ import ImageDiffer


def agent_3_node(state: Dict[str, Any]) -> Dict[str, Any]:
    """
    Compare screenshots and generate visual diff report.
    
    This agent:
    1. Normalizes image sizes (handles Figma 2x)
    2. Calculates similarity scores
    3. Creates side-by-side comparison with diff overlay
    4. Detects specific differences (color, layout, structure)
    """
    print("\n" + "="*60)
    print("πŸ” Agent 3: Visual Comparator - Analyzing Differences")
    print("="*60)
    
    figma_screenshots = state.get("figma_screenshots", {})
    website_screenshots = state.get("website_screenshots", {})
    figma_dims = state.get("figma_dimensions", {})
    website_dims = state.get("website_dimensions", {})
    execution_id = state.get("execution_id", "")
    logs = state.get("logs", [])
    
    if not figma_screenshots or not website_screenshots:
        error_msg = "Missing screenshots for comparison"
        print(f"\n  ❌ {error_msg}")
        return {
            "status": "comparison_failed",
            "error_message": error_msg,
            "logs": logs + [f"❌ {error_msg}"]
        }
    
    try:
        # Initialize differ
        differ = ImageDiffer(output_dir="data/comparisons")
        
        # Run comparison
        results = differ.compare_all_viewports(
            figma_screenshots=figma_screenshots,
            website_screenshots=website_screenshots,
            figma_dims=figma_dims,
            website_dims=website_dims,
            execution_id=execution_id
        )
        
        # Build comparison images dict
        comparison_images = {}
        for viewport, comparison in results["comparisons"].items():
            comparison_images[viewport] = comparison["comparison_image"]
        
        # Log results
        print(f"\n" + "="*60)
        print("πŸ“Š COMPARISON RESULTS")
        print("="*60)
        print(f"  Overall Similarity: {results['overall_score']:.1f}%")
        
        for viewport, score in results["viewport_scores"].items():
            status_emoji = "βœ…" if score >= 90 else "⚠️" if score >= 70 else "❌"
            print(f"  {status_emoji} {viewport.capitalize()}: {score:.1f}%")
            logs.append(f"{status_emoji} {viewport} similarity: {score:.1f}%")
        
        if results["all_differences"]:
            print(f"\n  πŸ” Found {len(results['all_differences'])} differences:")
            for diff in results["all_differences"]:
                severity_emoji = "πŸ”΄" if diff["severity"] == "High" else "🟑" if diff["severity"] == "Medium" else "🟒"
                print(f"     {severity_emoji} [{diff['category']}] {diff['title']}")
                logs.append(f"{severity_emoji} {diff['title']}")
        else:
            print(f"\n  βœ… No significant differences detected!")
            logs.append("βœ… No significant differences detected")
        
        # Convert numpy types to Python native types for serialization
        def convert_to_native(obj):
            """Convert numpy types to Python native types."""
            import numpy as np
            if isinstance(obj, np.floating):
                return float(obj)
            elif isinstance(obj, np.integer):
                return int(obj)
            elif isinstance(obj, np.ndarray):
                return obj.tolist()
            elif isinstance(obj, dict):
                return {k: convert_to_native(v) for k, v in obj.items()}
            elif isinstance(obj, list):
                return [convert_to_native(i) for i in obj]
            return obj
        
        # Convert all results to native Python types
        similarity_scores_native = {k: float(v) for k, v in results["viewport_scores"].items()}
        overall_score_native = float(results["overall_score"])
        differences_native = convert_to_native(results["all_differences"])
        
        return {
            "comparison_images": comparison_images,
            "visual_differences": differences_native,
            "similarity_scores": similarity_scores_native,
            "overall_score": overall_score_native,
            "status": "analysis_complete",
            "logs": logs
        }
        
    except Exception as e:
        import traceback
        error_msg = f"Comparison failed: {str(e)}"
        print(f"\n  ❌ {error_msg}")
        traceback.print_exc()
        return {
            "status": "comparison_failed",
            "error_message": error_msg,
            "logs": logs + [f"❌ {error_msg}"]
        }