import gradio as gr import logging from datetime import datetime import time from typing import Dict, Any, Optional, Tuple import json import os from PIL import Image from .enhanced_ai_processor import EnhancedAIProcessor from .dashboard_database_manager import DashboardDatabaseManager from .dashboard_api import DashboardIntegrationManager from .auth import AuthManager class EnhancedUIComponents: """Enhanced UI components with dashboard integration and analytics tracking""" def __init__(self, auth_manager: AuthManager, database_manager: DashboardDatabaseManager, ai_processor: EnhancedAIProcessor): """Initialize enhanced UI components""" self.auth_manager = auth_manager self.database_manager = database_manager self.ai_processor = ai_processor self.dashboard_integration = DashboardIntegrationManager(database_manager) # Start dashboard integration self.dashboard_integration.start_integration() # UI styling self.theme = gr.themes.Soft() self.custom_css = self._load_custom_css() # Session tracking self.current_session = {} logging.info("✅ Enhanced UI Components initialized with dashboard integration") def _load_custom_css(self): """Load custom CSS for the application""" return """ /* =================== SMARTHEAL CSS =================== */ /* Global Styling */ body, html { margin: 0 !important; padding: 0 !important; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif !important; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important; color: #1A202C !important; line-height: 1.6 !important; } /* Professional Header with Logo */ .medical-header { background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%) !important; color: white !important; padding: 32px 40px !important; border-radius: 20px 20px 0 0 !important; display: flex !important; align-items: center !important; justify-content: center !important; margin-bottom: 0 !important; box-shadow: 0 10px 40px rgba(49, 130, 206, 0.3) !important; border: none !important; position: relative !important; overflow: hidden !important; } .logo { width: 80px !important; height: 80px !important; border-radius: 50% !important; margin-right: 24px !important; border: 4px solid rgba(255, 255, 255, 0.3) !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2) !important; background: white !important; padding: 4px !important; } .medical-header h1 { font-size: 3.5rem !important; font-weight: 800 !important; margin: 0 !important; text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3) !important; background: linear-gradient(45deg, #ffffff, #f8f9fa) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3)) !important; } .medical-header p { font-size: 1.3rem !important; margin: 8px 0 0 0 !important; opacity: 0.95 !important; font-weight: 500 !important; text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2) !important; } /* Enhanced Form Styling */ .gr-form { background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%) !important; border-radius: 20px !important; padding: 32px !important; margin: 24px 0 !important; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.1) !important; border: 1px solid rgba(229, 62, 62, 0.1) !important; backdrop-filter: blur(10px) !important; position: relative !important; overflow: hidden !important; } /* Professional Input Fields */ .gr-textbox, .gr-number { border-radius: 12px !important; border: 2px solid #E2E8F0 !important; background: #FFFFFF !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important; font-size: 1rem !important; color: #1A202C !important; padding: 16px 20px !important; } .gr-textbox:focus, .gr-number:focus, .gr-textbox input:focus, .gr-number input:focus { border-color: #E53E3E !important; box-shadow: 0 0 0 4px rgba(229, 62, 62, 0.1) !important; background: #FFFFFF !important; outline: none !important; transform: translateY(-1px) !important; } /* Enhanced Button Styling */ button.gr-button, button.gr-button-primary { background: linear-gradient(135deg, #E53E3E 0%, #C53030 100%) !important; color: #FFFFFF !important; border: none !important; border-radius: 12px !important; font-weight: 700 !important; padding: 16px 32px !important; font-size: 1.1rem !important; letter-spacing: 0.5px !important; text-align: center !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; box-shadow: 0 4px 16px rgba(229, 62, 62, 0.3) !important; position: relative !important; overflow: hidden !important; text-transform: uppercase !important; cursor: pointer !important; } button.gr-button:hover, button.gr-button-primary:hover { background: linear-gradient(135deg, #C53030 0%, #9C2A2A 100%) !important; box-shadow: 0 8px 32px rgba(229, 62, 62, 0.4) !important; transform: translateY(-3px) !important; } /* Professional Status Messages */ .status-success { background: linear-gradient(135deg, #F0FFF4 0%, #E6FFFA 100%) !important; border: 2px solid #38A169 !important; color: #22543D !important; padding: 20px 24px !important; border-radius: 16px !important; font-weight: 600 !important; margin: 16px 0 !important; box-shadow: 0 8px 24px rgba(56, 161, 105, 0.2) !important; backdrop-filter: blur(10px) !important; } .status-error { background: linear-gradient(135deg, #FFF5F5 0%, #FED7D7 100%) !important; border: 2px solid #E53E3E !important; color: #742A2A !important; padding: 20px 24px !important; border-radius: 16px !important; font-weight: 600 !important; margin: 16px 0 !important; box-shadow: 0 8px 24px rgba(229, 62, 62, 0.2) !important; backdrop-filter: blur(10px) !important; } .status-warning { background: linear-gradient(135deg, #FFFAF0 0%, #FEEBC8 100%) !important; border: 2px solid #DD6B20 !important; color: #9C4221 !important; padding: 20px 24px !important; border-radius: 16px !important; font-weight: 600 !important; margin: 16px 0 !important; box-shadow: 0 8px 24px rgba(221, 107, 32, 0.2) !important; backdrop-filter: blur(10px) !important; } /* Image gallery styling for better visualization */ .image-gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin: 20px 0; } .image-item { background: #f8f9fa; border-radius: 12px; padding: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); text-align: center; } .image-item img { max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); } .image-item h4 { margin: 15px 0 5px 0; color: #2d3748; font-weight: 600; } .image-item p { margin: 0; color: #666; font-size: 0.9em; } /* Analyze button special styling */ #analyze-btn { background: linear-gradient(135deg, #1B5CF3 0%, #1E3A8A 100%) !important; color: #FFFFFF !important; border: none !important; border-radius: 8px !important; font-weight: 700 !important; padding: 14px 28px !important; font-size: 1.1rem !important; letter-spacing: 0.5px !important; text-align: center !important; transition: all 0.2s ease-in-out !important; } #analyze-btn:hover { background: linear-gradient(135deg, #174ea6 0%, #123b82 100%) !important; box-shadow: 0 4px 14px rgba(27, 95, 193, 0.4) !important; transform: translateY(-2px) !important; } /* Responsive design */ @media (max-width: 768px) { .medical-header { padding: 16px !important; text-align: center !important; } .medical-header h1 { font-size: 2rem !important; } .logo { width: 48px !important; height: 48px !important; margin-right: 16px !important; } .gr-form { padding: 16px !important; margin: 8px 0 !important; } .image-gallery { grid-template-columns: 1fr; } } """ def create_interface(self): """Create the enhanced Gradio interface with dashboard integration""" with gr.Blocks(theme=self.theme, css=self.custom_css, title="SmartHeal AI - Enhanced") as interface: # Header gr.HTML("""

🏥 SmartHeal AI - Enhanced Edition

Advanced Wound Care Analysis with Real-time Dashboard Integration

""") # Integration status display integration_status = gr.HTML(self._get_integration_status_html()) # Session info session_info = gr.HTML(self._get_session_info_html()) with gr.Tabs(): # Authentication Tab with gr.Tab("🔐 Authentication"): with gr.Row(): with gr.Column(): gr.HTML("""

User Authentication

Login to access SmartHeal AI analysis features

""") username_input = gr.Textbox( label="Username", placeholder="Enter your username", interactive=True ) password_input = gr.Textbox( label="Password", type="password", placeholder="Enter your password", interactive=True ) login_btn = gr.Button("Login", variant="primary") logout_btn = gr.Button("Logout", variant="secondary") auth_status = gr.HTML(value="
Please login to continue
") # Enhanced Analysis Tab with gr.Tab("🔬 Wound Analysis"): with gr.Row(): with gr.Column(scale=1): gr.HTML("""

Patient Information

Complete patient details for comprehensive analysis

""") # Patient Information patient_name = gr.Textbox(label="Patient Name", placeholder="Enter patient name") patient_age = gr.Number(label="Patient Age", value=0, minimum=0, maximum=120) patient_gender = gr.Dropdown( label="Gender", choices=["Male", "Female", "Other"], value="Male" ) # Wound Details gr.HTML("""

Wound Information

""") wound_location = gr.Textbox(label="Wound Location", placeholder="e.g., Left heel, Right forearm") wound_duration = gr.Textbox(label="Wound Duration", placeholder="e.g., 2 weeks, 1 month") pain_level = gr.Slider(label="Pain Level (0-10)", minimum=0, maximum=10, value=0, step=1) # Clinical Assessment moisture_level = gr.Dropdown( label="Moisture Level", choices=["Dry", "Moist", "Wet", "Macerated"], value="Moist" ) infection_signs = gr.Dropdown( label="Signs of Infection", choices=["None", "Mild", "Moderate", "Severe"], value="None" ) diabetic_status = gr.Dropdown( label="Diabetic Status", choices=["No", "Type 1", "Type 2", "Unknown"], value="No" ) # Medical History gr.HTML("""

Medical History

""") previous_treatment = gr.Textbox( label="Previous Treatment", placeholder="Describe any previous treatments", lines=2 ) medical_history = gr.Textbox( label="Medical History", placeholder="Relevant medical conditions", lines=2 ) medications = gr.Textbox( label="Current Medications", placeholder="List current medications", lines=2 ) allergies = gr.Textbox( label="Known Allergies", placeholder="List any known allergies", lines=2 ) additional_notes = gr.Textbox( label="Additional Notes", placeholder="Any additional relevant information", lines=3 ) with gr.Column(scale=1): gr.HTML("""

Wound Image Analysis

Upload wound image for AI analysis

""") # Image Upload wound_image = gr.Image( label="Wound Image", type="pil", height=400 ) # Analysis Controls analyze_btn = gr.Button("🔍 Analyze Wound", variant="primary", size="lg") # Processing indicator processing_status = gr.HTML(visible=False) # Analysis Metrics analysis_metrics = gr.HTML(visible=False) # Results Section with gr.Row(): with gr.Column(): gr.HTML("""

Analysis Results

Comprehensive AI-powered wound assessment

""") # Visual Analysis Results with gr.Row(): detection_image = gr.Image(label="Wound Detection", visible=False) segmentation_image = gr.Image(label="Wound Segmentation", visible=False) # Analysis Report analysis_report = gr.Markdown(visible=False) # Download Options with gr.Row(): download_report = gr.File(label="Download Report", visible=False) download_images = gr.File(label="Download Analysis Images", visible=False) # Dashboard Integration Tab with gr.Tab("📊 Dashboard Integration"): gr.HTML("""

Dashboard Integration Status

Real-time connection to SmartHeal Dashboard

""") dashboard_status = gr.HTML() with gr.Row(): refresh_status_btn = gr.Button("🔄 Refresh Status", variant="secondary") view_analytics_btn = gr.Button("📈 View Analytics", variant="primary") # Analytics Summary analytics_summary = gr.HTML() # Recent Activity recent_activity = gr.HTML() # Event Handlers # Authentication login_btn.click( fn=self._handle_login, inputs=[username_input, password_input], outputs=[auth_status, session_info] ) logout_btn.click( fn=self._handle_logout, outputs=[auth_status, session_info] ) # Analysis analyze_btn.click( fn=self._start_analysis, inputs=[], outputs=[processing_status, analysis_metrics] ).then( fn=self._perform_enhanced_analysis, inputs=[ patient_name, patient_age, patient_gender, wound_location, wound_duration, pain_level, moisture_level, infection_signs, diabetic_status, previous_treatment, medical_history, medications, allergies, additional_notes, wound_image ], outputs=[ analysis_report, detection_image, segmentation_image, download_report, download_images, processing_status, analysis_metrics, session_info ] ) # Dashboard Integration refresh_status_btn.click( fn=self._refresh_dashboard_status, outputs=[dashboard_status, analytics_summary] ) view_analytics_btn.click( fn=self._get_analytics_summary, outputs=[analytics_summary, recent_activity] ) # Auto-refresh integration status on load interface.load( fn=self._refresh_dashboard_status, outputs=[dashboard_status, analytics_summary] ) return interface def _get_integration_status_html(self) -> str: """Get HTML for integration status display""" status = self.dashboard_integration.get_integration_status() if status['api_running'] and status['database_connected']: return """
Dashboard Integration Active
API Server: Running | Database: Connected | Real-time Analytics: Enabled
""" else: return """
Dashboard Integration Issues
Please check API server and database connection
""" def _get_session_info_html(self) -> str: """Get HTML for session information display""" if self.current_session: user_info = self.current_session.get('user_info', {}) return f"""
👤 Active Session
User: {user_info.get('name', 'Unknown')} | Role: {user_info.get('role', 'Unknown')} | Session Started: {self.current_session.get('start_time', 'Unknown')}
""" else: return """
⚠️ No Active Session
Please login to start tracking your analysis session
""" def _handle_login(self, username: str, password: str) -> Tuple[str, str]: """Handle user login with session tracking""" try: if not username or not password: return ( "
❌ Please enter both username and password
", self._get_session_info_html() ) # Authenticate user user_info = self.auth_manager.authenticate_user(username, password) if user_info: # Start session tracking self.current_session = { 'user_info': user_info, 'start_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'session_id': f"session_{int(time.time())}", 'analyses_count': 0 } return ( f"
✅ Welcome, {user_info.get('name', username)}! You are now logged in.
", self._get_session_info_html() ) else: return ( "
❌ Invalid username or password
", self._get_session_info_html() ) except Exception as e: logging.error(f"Login error: {e}") return ( f"
❌ Login failed: {str(e)}
", self._get_session_info_html() ) def _handle_logout(self) -> Tuple[str, str]: """Handle user logout""" try: if self.current_session: # Log session end session_duration = time.time() - datetime.strptime( self.current_session['start_time'], '%Y-%m-%d %H:%M:%S' ).timestamp() session_data = { 'user_id': self.current_session['user_info'].get('id'), 'session_duration': round(session_duration / 60, 2), # Convert to minutes 'analyses_count': self.current_session.get('analyses_count', 0) } # Clear session self.current_session = {} return ( "
👋 You have been logged out successfully
", self._get_session_info_html() ) else: return ( "
⚠️ No active session to logout
", self._get_session_info_html() ) except Exception as e: logging.error(f"Logout error: {e}") return ( f"
❌ Logout error: {str(e)}
", self._get_session_info_html() ) def _start_analysis(self) -> Tuple[str, str]: """Start analysis process with status indicators""" return ( """
🔄 Analysis in Progress...
Please wait while we process your wound image and patient data
""", """
Analysis Metrics:
Status: Initializing...
Processing Time: 0.0s
Models Loading: ⏳
""" ) def _perform_enhanced_analysis(self, patient_name: str, patient_age: int, patient_gender: str, wound_location: str, wound_duration: str, pain_level: int, moisture_level: str, infection_signs: str, diabetic_status: str, previous_treatment: str, medical_history: str, medications: str, allergies: str, additional_notes: str, wound_image) -> Tuple: """Perform enhanced analysis with dashboard integration""" start_time = time.time() try: # Check authentication if not self.current_session: return ( "❌ **Authentication Required**\n\nPlease login before performing analysis.", None, None, None, None, "
❌ Authentication required
", "
Please login to continue
", self._get_session_info_html() ) # Validate inputs if not wound_image: return ( "❌ **Image Required**\n\nPlease upload a wound image for analysis.", None, None, None, None, "
❌ Wound image required
", "
Please upload an image
", self._get_session_info_html() ) if not patient_name.strip(): return ( "❌ **Patient Name Required**\n\nPlease enter the patient's name.", None, None, None, None, "
❌ Patient name required
", "
Please enter patient name
", self._get_session_info_html() ) # Prepare patient information patient_info = { 'patient_name': patient_name, 'patient_age': patient_age, 'patient_gender': patient_gender, 'wound_location': wound_location, 'wound_duration': wound_duration, 'pain_level': pain_level, 'moisture_level': moisture_level, 'infection_signs': infection_signs, 'diabetic_status': diabetic_status, 'previous_treatment': previous_treatment, 'medical_history': medical_history, 'medications': medications, 'allergies': allergies, 'additional_notes': additional_notes } # Save questionnaire response to dashboard database user_id = self.current_session['user_info'].get('id') questionnaire_id = self.database_manager.save_questionnaire_response(patient_info, user_id) if not questionnaire_id: logging.warning("Failed to save questionnaire response") # Save wound image image_id = None if questionnaire_id: image_id = self.database_manager.save_wound_image(questionnaire_id, wound_image, "wound_analysis.jpg") # Perform comprehensive AI analysis analysis_results = self.ai_processor.perform_comprehensive_analysis(wound_image, patient_info) processing_time = analysis_results.get('processing_time', 0) # Save AI analysis results to dashboard database analysis_data = { 'questionnaire_id': questionnaire_id, 'image_id': image_id, 'analysis_data': analysis_results, 'summary': analysis_results.get('report', '')[:1000], # First 1000 chars as summary 'recommendations': analysis_results.get('report', ''), 'risk_score': analysis_results.get('risk_score', 0), 'processing_time': processing_time, 'model_version': analysis_results.get('model_version', 'v1.0'), 'visual_results': analysis_results.get('visual_results', {}) } analysis_id = self.database_manager.save_ai_analysis(analysis_data) # Log analysis session session_data = { 'user_id': user_id, 'questionnaire_id': questionnaire_id, 'image_id': image_id, 'analysis_id': analysis_id, 'session_duration': processing_time } self.dashboard_integration.log_analysis_session(session_data) # Log bot interaction interaction_data = { 'patient_id': None, # Would need to get from patients table 'practitioner_id': user_id, 'input_text': f"Wound analysis for {patient_name}", 'output_text': analysis_results.get('report', '')[:500], # First 500 chars 'wound_image_url': f"uploads/wound_analysis_{int(time.time())}.jpg", 'interaction_type': 'wound_analysis' } self.dashboard_integration.log_bot_interaction(interaction_data) # Update session count self.current_session['analyses_count'] = self.current_session.get('analyses_count', 0) + 1 # Prepare results for display visual_results = analysis_results.get('visual_results', {}) report = analysis_results.get('report', 'Analysis completed but no report generated.') # Get analysis images detection_image = visual_results.get('detection_image_pil') segmentation_image = visual_results.get('segmentation_image_pil') # Create downloadable report report_file = self._create_report_file(analysis_results, patient_info) # Create metrics display metrics_html = f"""
Analysis Completed Successfully!
Processing Time: {processing_time}s
Risk Score: {analysis_results.get('risk_score', 0)}/100
Wound Type: {visual_results.get('wound_type', 'Unknown')}
Surface Area: {visual_results.get('surface_area_cm2', 0)} cm²
Model Version: {analysis_results.get('model_version', 'v1.0')}
Dashboard Integration: ✅ Active
""" success_status = f"""
Analysis Completed Successfully!
Processing Time: {processing_time}s | Risk Score: {analysis_results.get('risk_score', 0)}/100
Results saved to dashboard for real-time analytics
""" return ( report, detection_image, segmentation_image, report_file, None, # Images download placeholder success_status, metrics_html, self._get_session_info_html() ) except Exception as e: processing_time = time.time() - start_time error_message = str(e) logging.error(f"Analysis error: {error_message}") error_status = f"""
Analysis Failed
Error: {error_message}
Processing Time: {processing_time:.2f}s
""" error_metrics = f"""
Analysis Error:
Status: Failed
Processing Time: {processing_time:.2f}s
Error: {error_message}
""" return ( f"❌ **Analysis Failed**\n\n**Error:** {error_message}\n\nPlease check your inputs and try again.", None, None, None, None, error_status, error_metrics, self._get_session_info_html() ) def _create_report_file(self, analysis_results: Dict[str, Any], patient_info: Dict[str, Any]) -> str: """Create downloadable report file""" try: timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') filename = f"wound_analysis_report_{timestamp}.md" filepath = os.path.join("uploads", filename) # Ensure uploads directory exists os.makedirs("uploads", exist_ok=True) # Create comprehensive report report_content = f"""# SmartHeal AI Wound Analysis Report **Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} **Patient:** {patient_info.get('patient_name', 'N/A')} **Analysis ID:** {timestamp} ## Patient Information - **Name:** {patient_info.get('patient_name', 'N/A')} - **Age:** {patient_info.get('patient_age', 'N/A')} years - **Gender:** {patient_info.get('patient_gender', 'N/A')} - **Wound Location:** {patient_info.get('wound_location', 'N/A')} - **Wound Duration:** {patient_info.get('wound_duration', 'N/A')} - **Pain Level:** {patient_info.get('pain_level', 'N/A')}/10 ## Analysis Results {analysis_results.get('report', 'No report generated')} ## Technical Details - **Processing Time:** {analysis_results.get('processing_time', 0)}s - **Risk Score:** {analysis_results.get('risk_score', 0)}/100 - **Model Version:** {analysis_results.get('model_version', 'Unknown')} - **Analysis Timestamp:** {analysis_results.get('analysis_timestamp', 'Unknown')} --- *Generated by SmartHeal AI Enhanced Edition with Dashboard Integration* """ with open(filepath, 'w', encoding='utf-8') as f: f.write(report_content) return filepath except Exception as e: logging.error(f"Error creating report file: {e}") return None def _refresh_dashboard_status(self) -> Tuple[str, str]: """Refresh dashboard integration status""" try: status = self.dashboard_integration.get_integration_status() analytics_data = self.database_manager.get_analytics_data() if status['api_running'] and status['database_connected']: status_html = f"""
Dashboard Integration Active
API Server: Running on port 5001
Database: Connected
Last Updated: {status['timestamp']}
🔗 Test API Health
""" else: status_html = f"""
Dashboard Integration Issues
API Running: {status['api_running']}
Database Connected: {status['database_connected']}
Last Checked: {status['timestamp']}
""" analytics_html = f"""
📊 Analytics Summary
Total Analyses: {analytics_data.get('total_analyses', 0)}
Average Processing Time: {analytics_data.get('avg_processing_time', 0)}s
High Risk Cases: {analytics_data.get('high_risk_count', 0)}
Average Risk Score: {analytics_data.get('avg_risk_score', 0)}
Analyses Today: {analytics_data.get('analyses_today', 0)}
""" return status_html, analytics_html except Exception as e: logging.error(f"Error refreshing dashboard status: {e}") return ( f"
❌ Error refreshing status: {str(e)}
", "
❌ Unable to load analytics
" ) def _get_analytics_summary(self) -> Tuple[str, str]: """Get comprehensive analytics summary""" try: analytics_data = self.database_manager.get_analytics_data() interaction_history = self.database_manager.get_interaction_history(10) # Create detailed analytics HTML analytics_html = f"""

📈 Comprehensive Analytics

Analysis Statistics:
• Total Analyses: {analytics_data.get('total_analyses', 0)}
• Analyses Today: {analytics_data.get('analyses_today', 0)}
• Analyses This Week: {analytics_data.get('analyses_this_week', 0)}
• Average Processing Time: {analytics_data.get('avg_processing_time', 0)}s
• Average Risk Score: {analytics_data.get('avg_risk_score', 0)}/100

Risk Distribution:
• High Risk Cases: {analytics_data.get('high_risk_count', 0)}
• Unique Questionnaires: {analytics_data.get('unique_questionnaires', 0)}
• Analyses with Images: {analytics_data.get('analyses_with_images', 0)}
""" # Create recent activity HTML activity_html = "

🕒 Recent Activity

" if interaction_history: activity_html += "" else: activity_html += "

No recent activity found.

" activity_html += "
" return analytics_html, activity_html except Exception as e: logging.error(f"Error getting analytics summary: {e}") return ( f"
❌ Error loading analytics: {str(e)}
", "
❌ Unable to load recent activity
" )