File size: 5,097 Bytes
5ada319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
125
126
127
128
129
130
131
132
133
134
135
136
from typing import List, Dict, Any, Optional
import json
from pydantic import BaseModel
from loguru import logger

# Mocking Blaxel SDK for now as it's a hypothetical or external dependency not in requirements
# In a real scenario, we would import: from blaxel import Scene, Node, Edge, Animation
# We will generate a Plotly 3D scatter plot as a fallback/implementation of the concept
import plotly.graph_objects as go

class StackFrame(BaseModel):
    function: str
    file: str
    line: int
    is_external: bool = False

class ErrorFlowVisualizer:
    def __init__(self):
        pass

    def _get_node_color(self, idx: int, total: int, is_external: bool) -> str:
        if is_external:
            return "purple"
        if idx == 0:
            return "green" # Entry
        if idx == total - 1:
            return "red" # Error
        return "blue" # Normal

    def generate_flow(self, stack_trace: List[Dict[str, Any]]) -> str:
        """
        Generate 3D visualization of error flow.
        Returns HTML string of the visualization.
        """
        try:
            frames = [StackFrame(**f) for f in stack_trace]
            if not frames:
                return "<div>No stack trace available for visualization.</div>"

            # Limit nodes for performance
            if len(frames) > 50:
                frames = frames[-50:]

            # 3D Coordinates layout (simple spiral or linear)
            x_coords = []
            y_coords = []
            z_coords = []
            colors = []
            sizes = []
            hover_texts = []
            
            for idx, frame in enumerate(frames):
                # Simple linear layout along X axis, with slight spiral
                x_coords.append(idx * 2)
                y_coords.append(0) 
                z_coords.append(0)
                
                color = self._get_node_color(idx, len(frames), frame.is_external)
                colors.append(color)
                
                # Pulse effect for error node (simulated by larger size)
                size = 20 if idx == len(frames) - 1 else 12
                sizes.append(size)
                
                hover_texts.append(
                    f"<b>{frame.function}</b><br>"
                    f"{frame.file}:{frame.line}<br>"
                    f"{'External Library' if frame.is_external else 'User Code'}"
                )

            # Create Nodes Trace
            node_trace = go.Scatter3d(
                x=x_coords, y=y_coords, z=z_coords,
                mode='markers+text',
                marker=dict(
                    size=sizes,
                    color=colors,
                    opacity=0.8,
                    line=dict(width=2, color='white')
                ),
                text=[f.function for f in frames],
                textposition="top center",
                hoverinfo='text',
                hovertext=hover_texts
            )

            # Create Edges Trace
            edge_x = []
            edge_y = []
            edge_z = []
            
            for i in range(len(frames) - 1):
                edge_x.extend([x_coords[i], x_coords[i+1], None])
                edge_y.extend([y_coords[i], y_coords[i+1], None])
                edge_z.extend([z_coords[i], z_coords[i+1], None])

            edge_trace = go.Scatter3d(
                x=edge_x, y=edge_y, z=edge_z,
                mode='lines',
                line=dict(color='#888', width=2),
                hoverinfo='none'
            )

            # Layout
            layout = go.Layout(
                title="3D Error Flow Visualization",
                showlegend=False,
                scene=dict(
                    xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                    yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                    zaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                    bgcolor='rgba(0,0,0,0)'
                ),
                margin=dict(l=0, r=0, b=0, t=30),
                height=500
            )

            fig = go.Figure(data=[edge_trace, node_trace], layout=layout)
            
            # Return HTML div
            return fig.to_html(full_html=False, include_plotlyjs='cdn')

        except Exception as e:
            logger.error(f"Visualization generation failed: {e}")
            return f"<div>Error generating visualization: {str(e)}</div>"

    def generate_mock_trace(self) -> List[Dict[str, Any]]:
        """Generate a mock trace for testing."""
        return [
            {"function": "main", "file": "app.py", "line": 10, "is_external": False},
            {"function": "process_request", "file": "core/handler.py", "line": 45, "is_external": False},
            {"function": "validate_input", "file": "utils/validation.py", "line": 12, "is_external": False},
            {"function": "json.loads", "file": "json/decoder.py", "line": 337, "is_external": True},
            {"function": "decode", "file": "json/decoder.py", "line": 355, "is_external": True},
        ]