PDF_analyst / utils /visual_output.py
JatsTheAIGen's picture
Major visual improvements: stunning outputs, downloadable exports, enhanced UI
73f15b1
# utils/visual_output.py - Visual output generation for PDF Analysis & Orchestrator
import json
import re
from typing import Dict, List, Any, Optional
from datetime import datetime
class VisualOutputGenerator:
"""Generate visual representations of analysis results"""
def __init__(self):
self.visual_elements = []
def create_infographic(self, data: Dict[str, Any], title: str = "Analysis Summary") -> str:
"""Create an infographic-style summary"""
visual = f"""
## πŸ“Š {title}
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 10px; color: white; margin: 10px 0;">
"""
# Key metrics
if 'metrics' in data:
visual += f"""
<div style="display: flex; justify-content: space-around; margin: 20px 0;">
"""
for metric, value in data['metrics'].items():
visual += f"""
<div style="text-align: center; background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px; margin: 5px;">
<h3 style="margin: 0; font-size: 24px;">{value}</h3>
<p style="margin: 5px 0 0 0; font-size: 14px;">{metric}</p>
</div>
"""
visual += "</div>"
visual += "</div>"
return visual
def create_data_table(self, data: List[Dict[str, Any]], title: str = "Data Table") -> str:
"""Create a formatted table from data"""
if not data:
return ""
# Get headers from first row
headers = list(data[0].keys())
table = f"""
## πŸ“‹ {title}
| {' | '.join(headers)} |
| {' | '.join(['---'] * len(headers))} |
"""
for row in data:
values = [str(row.get(header, '')) for header in headers]
table += f"| {' | '.join(values)} |\n"
return table
def create_progress_bar(self, value: float, max_value: float, label: str) -> str:
"""Create a progress bar visualization"""
percentage = min(100, (value / max_value) * 100) if max_value > 0 else 0
return f"""
<div style="margin: 10px 0;">
<p style="margin: 5px 0; font-weight: bold;">{label}: {value:.1f}/{max_value:.1f} ({percentage:.1f}%)</p>
<div style="background: #e0e0e0; border-radius: 10px; height: 20px; overflow: hidden;">
<div style="background: linear-gradient(90deg, #4CAF50, #8BC34A); height: 100%; width: {percentage}%; transition: width 0.3s ease;"></div>
</div>
</div>
"""
def create_timeline(self, events: List[Dict[str, str]], title: str = "Timeline") -> str:
"""Create a timeline visualization"""
timeline = f"""
## ⏰ {title}
<div style="position: relative; padding-left: 30px; margin: 20px 0;">
"""
for i, event in enumerate(events):
timeline += f"""
<div style="position: relative; margin-bottom: 20px;">
<div style="position: absolute; left: -25px; top: 5px; width: 12px; height: 12px; background: #4CAF50; border-radius: 50%; border: 3px solid white; box-shadow: 0 0 0 3px #4CAF50;"></div>
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; border-left: 4px solid #4CAF50;">
<h4 style="margin: 0 0 5px 0; color: #333;">{event.get('title', 'Event')}</h4>
<p style="margin: 0; color: #666;">{event.get('description', '')}</p>
<small style="color: #999;">{event.get('date', '')}</small>
</div>
</div>
"""
timeline += "</div>"
return timeline
def create_comparison_chart(self, data: Dict[str, float], title: str = "Comparison") -> str:
"""Create a comparison chart"""
if not data:
return ""
max_value = max(data.values()) if data.values() else 1
chart = f"""
## πŸ“ˆ {title}
<div style="margin: 20px 0;">
"""
for label, value in data.items():
percentage = (value / max_value) * 100
chart += f"""
<div style="margin: 10px 0;">
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<span style="font-weight: bold;">{label}</span>
<span style="color: #666;">{value:.1f}</span>
</div>
<div style="background: #e0e0e0; border-radius: 5px; height: 20px; overflow: hidden;">
<div style="background: linear-gradient(90deg, #2196F3, #21CBF3); height: 100%; width: {percentage}%; transition: width 0.3s ease;"></div>
</div>
</div>
"""
chart += "</div>"
return chart
def create_key_points(self, points: List[str], title: str = "Key Points") -> str:
"""Create a stunning key points section with visual elements"""
if not points:
return ""
# Icons for different types of points
icons = ["🎯", "πŸ’‘", "βœ…", "πŸ“Š", "⚑", "πŸ”", "πŸš€", "πŸ’Ž", "⭐", "πŸ”₯"]
visual = f"""
## πŸ’‘ {title}
<div style="display: grid; gap: 20px; margin: 25px 0;">
"""
for i, point in enumerate(points, 1):
icon = icons[i % len(icons)]
color = ["#007bff", "#28a745", "#ffc107", "#dc3545", "#6f42c1"][i % 5]
visual += f"""
<div style="background: linear-gradient(135deg, {color}15, {color}05); border: 2px solid {color}30; padding: 20px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); transition: transform 0.2s ease;">
<div style="display: flex; align-items: flex-start; gap: 15px;">
<div style="background: {color}; color: white; border-radius: 50%; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: bold; flex-shrink: 0; box-shadow: 0 2px 8px {color}50;">{icon}</div>
<div style="flex: 1;">
<p style="margin: 0; line-height: 1.6; font-size: 16px; color: #333; font-weight: 500;">{point}</p>
</div>
</div>
</div>
"""
visual += "</div>"
return visual
def create_alert_box(self, message: str, alert_type: str = "info") -> str:
"""Create an alert box"""
colors = {
"info": "#2196F3",
"success": "#4CAF50",
"warning": "#FF9800",
"error": "#F44336"
}
icons = {
"info": "ℹ️",
"success": "βœ…",
"warning": "⚠️",
"error": "❌"
}
color = colors.get(alert_type, colors["info"])
icon = icons.get(alert_type, icons["info"])
return f"""
<div style="background: {color}15; border: 1px solid {color}; border-radius: 8px; padding: 15px; margin: 15px 0; display: flex; align-items: flex-start;">
<span style="font-size: 20px; margin-right: 10px;">{icon}</span>
<p style="margin: 0; color: {color}; font-weight: 500;">{message}</p>
</div>
"""
def create_metric_cards(self, metrics: Dict[str, Any], title: str = "Key Metrics") -> str:
"""Create metric cards"""
if not metrics:
return ""
cards = f"""
## πŸ“Š {title}
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0;">
"""
for metric, value in metrics.items():
cards += f"""
<div style="background: white; border: 1px solid #e0e0e0; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h3 style="margin: 0 0 10px 0; color: #333; font-size: 28px;">{value}</h3>
<p style="margin: 0; color: #666; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px;">{metric}</p>
</div>
"""
cards += "</div>"
return cards
def format_analysis_with_visuals(self, analysis_text: str, document_metadata: Dict[str, Any] = None) -> str:
"""Format analysis text with stunning visual elements"""
visual_elements = []
# Add document info if available
if document_metadata:
visual_elements.append(self.create_metric_cards({
"πŸ“„ Pages": document_metadata.get('page_count', 'Unknown'),
"πŸ’Ύ File Size": f"{document_metadata.get('file_size', 0) / 1024:.1f} KB",
"⚑ Processing": f"{document_metadata.get('processing_time', 0):.1f}s",
"🎯 Tokens": document_metadata.get('tokens_used', 'N/A')
}, "πŸ“Š Document Overview"))
# Create a beautiful header
visual_elements.append(self.create_analysis_header())
# Try to extract key points from analysis
key_points = self._extract_key_points(analysis_text)
if key_points:
visual_elements.append(self.create_key_points(key_points, "🎯 Key Insights"))
# Try to extract metrics
metrics = self._extract_metrics(analysis_text)
if metrics:
visual_elements.append(self.create_metric_cards(metrics, "πŸ“ˆ Key Metrics"))
# Try to extract data for tables
table_data = self._extract_table_data(analysis_text)
if table_data:
visual_elements.append(self.create_data_table(table_data, "πŸ“‹ Data Summary"))
# Format the main analysis with better structure
formatted_analysis = self._format_analysis_text(analysis_text)
# Combine all elements
result = "\n\n".join(visual_elements)
if formatted_analysis:
result += f"\n\n---\n\n{formatted_analysis}"
return result
def create_analysis_header(self) -> str:
"""Create a beautiful analysis header"""
return """
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 15px; color: white; margin: 20px 0; text-align: center; box-shadow: 0 8px 32px rgba(0,0,0,0.1);">
<h1 style="margin: 0 0 10px 0; font-size: 28px; font-weight: 700;">πŸ“„ AI Document Analysis</h1>
<p style="margin: 0; font-size: 16px; opacity: 0.9;">Powered by Advanced AI β€’ Instant Insights β€’ Professional Results</p>
</div>
"""
def _format_analysis_text(self, text: str) -> str:
"""Format analysis text with better visual structure"""
# Split into sections
sections = text.split('\n\n')
formatted_sections = []
for section in sections:
if section.strip():
# Check if it's a header
if section.startswith('##'):
formatted_sections.append(f"\n{section}\n")
else:
# Format as a content block
formatted_sections.append(f"""
<div style="background: #f8f9fa; border-left: 4px solid #007bff; padding: 20px; margin: 15px 0; border-radius: 0 8px 8px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
{section}
</div>
""")
return '\n'.join(formatted_sections)
def _extract_table_data(self, text: str) -> List[Dict[str, Any]]:
"""Extract data that can be formatted as tables"""
table_data = []
# Look for comparison patterns
comparison_pattern = r'(\w+):\s*(\d+(?:\.\d+)?%?)\s*vs\s*(\w+):\s*(\d+(?:\.\d+)?%?)'
matches = re.findall(comparison_pattern, text, re.IGNORECASE)
for match in matches:
table_data.append({
"Metric": match[0],
"Value": match[1],
"Comparison": match[2],
"Value 2": match[3]
})
return table_data
def _extract_key_points(self, text: str) -> List[str]:
"""Extract key points from analysis text"""
# Look for bullet points, numbered lists, or key findings
points = []
# Extract bullet points
bullet_pattern = r'[-β€’*]\s+(.+?)(?=\n|$)'
bullets = re.findall(bullet_pattern, text, re.MULTILINE)
points.extend([bullet.strip() for bullet in bullets if len(bullet.strip()) > 10])
# Extract numbered points
number_pattern = r'\d+\.\s+(.+?)(?=\n|$)'
numbers = re.findall(number_pattern, text, re.MULTILINE)
points.extend([num.strip() for num in numbers if len(num.strip()) > 10])
# Limit to top 5 points
return points[:5]
def _extract_metrics(self, text: str) -> Dict[str, str]:
"""Extract metrics from analysis text"""
metrics = {}
# Look for percentage patterns
percent_pattern = r'(\d+(?:\.\d+)?%)'
percentages = re.findall(percent_pattern, text)
if percentages:
metrics["Success Rate"] = percentages[0]
# Look for number patterns
number_pattern = r'(\d+(?:,\d+)*(?:\.\d+)?)\s+(?:pages?|items?|points?|years?|months?)'
numbers = re.findall(number_pattern, text, re.IGNORECASE)
if numbers:
metrics["Total Items"] = numbers[0]
return metrics