import gradio as gr import datetime from typing import Dict, List, Any, Union, Optional, Callable import matplotlib.pyplot as plt import numpy as np from utils.logging import get_logger from utils.error_handling import handle_ui_exceptions, ValidationError, safe_get from utils.config import UI_COLORS, UI_SIZES # Initialize logger logger = get_logger(__name__) @handle_ui_exceptions def create_card(title: str, content: str, icon: str = None, color: str = "blue") -> gr.Group: """ Create a card component with title and content Args: title: Card title content: Card content icon: Icon emoji color: Card color Returns: Gradio Group component Raises: ValidationError: If required parameters are missing or invalid """ if not title: logger.warning("Creating card with empty title") title = "Untitled" logger.debug(f"Creating card: {title}") with gr.Group(elem_classes=["card", f"card-{color}"]) as card: with gr.Row(): if icon: gr.Markdown(f"## {icon} {title}") else: gr.Markdown(f"## {title}") gr.Markdown(content) return card @handle_ui_exceptions def create_stat_card(title: str, value: Union[int, str], icon: str = None, color: str = "blue") -> gr.Group: """ Create a statistics card component Args: title: Card title value: Statistic value icon: Icon emoji color: Card color Returns: Gradio Group component Raises: ValidationError: If required parameters are missing or invalid """ if not title: logger.warning("Creating stat card with empty title") title = "Untitled" logger.debug(f"Creating stat card: {title} = {value}") with gr.Group(elem_classes=["stat-card", f"card-{color}"]) as card: with gr.Row(): if icon: gr.Markdown(f"### {icon} {title}") else: gr.Markdown(f"### {title}") gr.Markdown(f"## {value}") return card @handle_ui_exceptions def create_progress_ring(value: float, max_value: float = 100, size: int = 100, color: str = "blue", label: str = None) -> gr.HTML: """ Create a CSS-based circular progress indicator Args: value: Current value max_value: Maximum value size: Size of the ring in pixels color: Color of the progress ring label: Optional label to display in the center Returns: Gradio HTML component with the progress ring Raises: ValidationError: If parameters are invalid """ # Validate inputs if not isinstance(value, (int, float)): logger.warning(f"Invalid value for progress ring: {value}, using 0") value = 0 if not isinstance(max_value, (int, float)) or max_value <= 0: logger.warning(f"Invalid max_value for progress ring: {max_value}, using 100") max_value = 100 if not isinstance(size, int) or size <= 0: logger.warning(f"Invalid size for progress ring: {size}, using 100") size = 100 logger.debug(f"Creating progress ring: {value}/{max_value} ({color})") # Calculate percentage percentage = min(100, max(0, (value / max_value) * 100)) # Define colors based on the color parameter ring_color = UI_COLORS.get(color, UI_COLORS["blue"]) # Create the HTML/CSS for the progress ring html = f"""
{int(percentage)}%
{f'
{label}
' if label else ''}
""" return gr.HTML(html) @handle_ui_exceptions def create_activity_item(activity: Dict[str, Any]) -> gr.Group: """ Create an activity feed item Args: activity: Activity data Returns: Gradio Group component Raises: ValidationError: If activity data is invalid """ if not activity: logger.warning("Creating activity item with empty data") activity = {"type": "unknown", "timestamp": get_timestamp()} logger.debug(f"Creating activity item: {safe_get(activity, 'type', 'unknown')}") # Format timestamp try: timestamp = datetime.datetime.fromisoformat(safe_get(activity, "timestamp", "")) formatted_time = timestamp.strftime("%b %d, %H:%M") except (ValueError, TypeError) as e: logger.warning(f"Error formatting timestamp: {str(e)}") formatted_time = "Unknown time" # Get activity type icon activity_icons = { "task_created": "📝", "task_completed": "✅", "note_created": "📄", "note_updated": "📝", "goal_created": "🎯", "goal_completed": "🏆", "app_opened": "🚀" } icon = activity_icons.get(safe_get(activity, "type", ""), "🔔") # Get activity message activity_messages = { "task_created": "created task", "task_completed": "completed task", "note_created": "created note", "note_updated": "updated note", "goal_created": "set goal", "goal_completed": "achieved goal", "app_opened": "opened the app" } message = activity_messages.get(safe_get(activity, "type", ""), "performed action") title = safe_get(activity, "title", "") # Create the activity item with gr.Group(elem_classes=["activity-item"]) as item: with gr.Row(): gr.Markdown(f"{icon} **{formatted_time}** - You {message}") if title: gr.Markdown(f"**{title}**") return item def get_timestamp() -> str: """ Get current timestamp in ISO format Returns: Current timestamp as ISO formatted string """ return datetime.datetime.now().isoformat() @handle_ui_exceptions def create_deadline_item(task: Dict[str, Any]) -> gr.Group: """ Create a deadline tracker item Args: task: Task data with deadline Returns: Gradio Group component Raises: ValidationError: If task data is invalid """ if not task: logger.warning("Creating deadline item with empty task data") task = {"title": "Untitled Task"} logger.debug(f"Creating deadline item for task: {safe_get(task, 'title', 'Untitled')}") # Format deadline try: deadline = datetime.datetime.fromisoformat(safe_get(task, "deadline", "")) today = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) days_left = (deadline.date() - today.date()).days if days_left < 0: status = "overdue" status_text = "Overdue" elif days_left == 0: status = "today" status_text = "Today" elif days_left == 1: status = "tomorrow" status_text = "Tomorrow" elif days_left < 7: status = "soon" status_text = f"{days_left} days left" else: status = "future" status_text = f"{days_left} days left" formatted_date = deadline.strftime("%b %d") except (ValueError, TypeError) as e: logger.warning(f"Error formatting deadline: {str(e)}") status = "unknown" status_text = "No deadline" formatted_date = "Unknown" days_left = 0 # Create the deadline item with gr.Group(elem_classes=["deadline-item", f"deadline-{status}"]) as item: with gr.Row(): gr.Markdown(f"**{safe_get(task, 'title', 'Untitled Task')}**") with gr.Row(): gr.Markdown(f"📅 {formatted_date} - {status_text}") return item @handle_ui_exceptions def create_kanban_board(tasks: List[Dict[str, Any]], on_card_click: Callable = None) -> gr.Group: """ Create a Kanban board view for tasks Args: tasks: List of tasks on_card_click: Callback function when a card is clicked Returns: Gradio Group component with the Kanban board Raises: ValidationError: If tasks data is invalid """ if not isinstance(tasks, list): logger.warning(f"Invalid tasks data for Kanban board: {type(tasks)}, using empty list") tasks = [] logger.debug(f"Creating Kanban board with {len(tasks)} tasks") # Group tasks by status todo_tasks = [task for task in tasks if safe_get(task, "status", "") == "todo"] in_progress_tasks = [task for task in tasks if safe_get(task, "status", "") == "in_progress"] done_tasks = [task for task in tasks if safe_get(task, "status", "") == "done"] logger.debug(f"Task distribution: {len(todo_tasks)} todo, {len(in_progress_tasks)} in progress, {len(done_tasks)} done") # Create the Kanban board with gr.Group(elem_classes=["kanban-board"]) as board: with gr.Row(): # To Do column with gr.Column(elem_classes=["kanban-column", "kanban-todo"]): gr.Markdown("### 📋 To Do") for task in todo_tasks: with gr.Group(elem_classes=["kanban-card"]) as card: gr.Markdown(f"**{safe_get(task, 'title', 'Untitled Task')}**") if safe_get(task, "description", ""): gr.Markdown(task["description"]) with gr.Row(): if safe_get(task, "priority", ""): priority_labels = {"high": "🔴 High", "medium": "🟠 Medium", "low": "🟢 Low"} gr.Markdown(priority_labels.get(task["priority"], task["priority"])) if safe_get(task, "deadline", ""): try: deadline = datetime.datetime.fromisoformat(task["deadline"]) gr.Markdown(f"📅 {deadline.strftime('%b %d')}") except (ValueError, TypeError) as e: logger.warning(f"Error formatting deadline: {str(e)}") # Add click event if callback provided if on_card_click: card.click(lambda t=task: on_card_click(t), inputs=[], outputs=[]) # In Progress column with gr.Column(elem_classes=["kanban-column", "kanban-in-progress"]): gr.Markdown("### 🔄 In Progress") for task in in_progress_tasks: with gr.Group(elem_classes=["kanban-card"]) as card: gr.Markdown(f"**{safe_get(task, 'title', 'Untitled Task')}**") if safe_get(task, "description", ""): gr.Markdown(task["description"]) with gr.Row(): if safe_get(task, "priority", ""): priority_labels = {"high": "🔴 High", "medium": "🟠 Medium", "low": "🟢 Low"} gr.Markdown(priority_labels.get(task["priority"], task["priority"])) if safe_get(task, "deadline", ""): try: deadline = datetime.datetime.fromisoformat(task["deadline"]) gr.Markdown(f"📅 {deadline.strftime('%b %d')}") except (ValueError, TypeError) as e: logger.warning(f"Error formatting deadline: {str(e)}") # Add click event if callback provided if on_card_click: card.click(lambda t=task: on_card_click(t), inputs=[], outputs=[]) # Done column with gr.Column(elem_classes=["kanban-column", "kanban-done"]): gr.Markdown("### ✅ Done") for task in done_tasks: with gr.Group(elem_classes=["kanban-card"]) as card: gr.Markdown(f"**{safe_get(task, 'title', 'Untitled Task')}**") if safe_get(task, "description", ""): gr.Markdown(task["description"]) with gr.Row(): if safe_get(task, "completed_at", ""): try: completed_at = datetime.datetime.fromisoformat(task["completed_at"]) gr.Markdown(f"✅ {completed_at.strftime('%b %d')}") except (ValueError, TypeError) as e: logger.warning(f"Error formatting completion date: {str(e)}") # Add click event if callback provided if on_card_click: card.click(lambda t=task: on_card_click(t), inputs=[], outputs=[]) return board @handle_ui_exceptions def create_priority_matrix(tasks: List[Dict[str, Any]], on_card_click: Callable = None) -> gr.Group: """ Create an Eisenhower Matrix (Priority Matrix) for tasks Args: tasks: List of tasks on_card_click: Callback function when a card is clicked Returns: Gradio Group component with the Priority Matrix Raises: ValidationError: If tasks data is invalid """ if not isinstance(tasks, list): logger.warning(f"Invalid tasks data for Priority Matrix: {type(tasks)}, using empty list") tasks = [] logger.debug(f"Creating Priority Matrix with {len(tasks)} tasks") # Group tasks by urgency and importance urgent_important = [] urgent_not_important = [] not_urgent_important = [] not_urgent_not_important = [] for task in tasks: if safe_get(task, "completed", False): continue urgency = safe_get(task, "urgency", "low") importance = safe_get(task, "importance", "low") if urgency in ["high", "urgent"] and importance in ["high", "important"]: urgent_important.append(task) elif urgency in ["high", "urgent"] and importance in ["low", "not important"]: urgent_not_important.append(task) elif urgency in ["low", "not urgent"] and importance in ["high", "important"]: not_urgent_important.append(task) else: not_urgent_not_important.append(task) # Create the Priority Matrix with gr.Group(elem_classes=["priority-matrix"]) as matrix: with gr.Row(): # Urgent & Important with gr.Column(elem_classes=["matrix-quadrant", "urgent-important"]): gr.Markdown("### 🔴 Do First") gr.Markdown("Urgent & Important") for task in urgent_important: with gr.Group(elem_classes=["matrix-card"]) as card: gr.Markdown(f"**{safe_get(task, 'title', 'Untitled Task')}**") # Add click event if callback provided if on_card_click: card.click(lambda t=task: on_card_click(t), inputs=[], outputs=[]) # Urgent & Not Important with gr.Column(elem_classes=["matrix-quadrant", "urgent-not-important"]): gr.Markdown("### 🟠 Delegate") gr.Markdown("Urgent & Not Important") for task in urgent_not_important: with gr.Group(elem_classes=["matrix-card"]) as card: gr.Markdown(f"**{safe_get(task, 'title', 'Untitled Task')}**") # Add click event if callback provided if on_card_click: card.click(lambda t=task: on_card_click(t), inputs=[], outputs=[]) with gr.Row(): # Not Urgent & Important with gr.Column(elem_classes=["matrix-quadrant", "not-urgent-important"]): gr.Markdown("### 🟡 Schedule") gr.Markdown("Not Urgent & Important") for task in not_urgent_important: with gr.Group(elem_classes=["matrix-card"]) as card: gr.Markdown(f"**{safe_get(task, 'title', 'Untitled Task')}**") # Add click event if callback provided if on_card_click: card.click(lambda t=task: on_card_click(t), inputs=[], outputs=[]) # Not Urgent & Not Important with gr.Column(elem_classes=["matrix-quadrant", "not-urgent-not-important"]): gr.Markdown("### 🟢 Eliminate") gr.Markdown("Not Urgent & Not Important") for task in not_urgent_not_important: with gr.Group(elem_classes=["matrix-card"]) as card: gr.Markdown(f"**{safe_get(task, 'title', 'Untitled Task')}**") # Add click event if callback provided if on_card_click: card.click(lambda t=task: on_card_click(t), inputs=[], outputs=[]) return matrix @handle_ui_exceptions def create_streak_counter(days: int) -> gr.Group: """ Create a streak counter component Args: days: Number of consecutive days Returns: Gradio Group component with the streak counter Raises: ValidationError: If days is not a non-negative integer """ if not isinstance(days, int) or days < 0: logger.warning(f"Invalid days value for streak counter: {days}, using 0") days = 0 logger.debug(f"Creating streak counter: {days} days") with gr.Group(elem_classes=["streak-counter"]) as streak: gr.Markdown(f"### 🔥 {days} Day Streak") # Create a visual representation of the streak if days > 0: # Create a row of fire emojis emoji_count = min(days, 7) # Limit to 7 emojis emoji_row = "🔥" * emoji_count gr.Markdown(emoji_row) # Add a motivational message based on streak length if days == 1: gr.Markdown("Great start! Keep it going tomorrow.") elif days < 3: gr.Markdown("You're building momentum!") elif days < 7: gr.Markdown("Impressive consistency!") elif days < 14: gr.Markdown("You're on fire this week!") elif days < 30: gr.Markdown("Amazing dedication!") else: gr.Markdown("Incredible commitment! You're unstoppable!") else: gr.Markdown("Start your streak today!") return streak @handle_ui_exceptions def create_weather_widget(weather_data: Dict[str, Any]) -> gr.Group: """ Create a weather widget Args: weather_data: Weather information Returns: Gradio Group component with the weather widget Raises: ValidationError: If weather_data is invalid """ if not isinstance(weather_data, dict): logger.warning(f"Invalid weather data: {type(weather_data)}, using empty dict") weather_data = {} logger.debug(f"Creating weather widget for location: {safe_get(weather_data, 'location', 'Unknown')}") # Weather condition icons weather_icons = { "Sunny": "☀️", "Clear": "☀️", "Partly Cloudy": "⛅", "Cloudy": "☁️", "Overcast": "☁️", "Rain": "🌧️", "Showers": "🌦️", "Thunderstorm": "⛈️", "Snow": "❄️", "Fog": "🌫️" } condition = safe_get(weather_data, "condition", "Unknown") icon = weather_icons.get(condition, "🌡️") with gr.Group(elem_classes=["weather-widget"]) as widget: gr.Markdown(f"### {icon} Weather - {safe_get(weather_data, 'location', 'Unknown')}") gr.Markdown(f"**{safe_get(weather_data, 'temperature', 0)}°C** - {condition}") gr.Markdown(f"Humidity: {safe_get(weather_data, 'humidity', 0)}% | Wind: {safe_get(weather_data, 'wind_speed', 0)} km/h") # Add forecast if available if "forecast" in weather_data and weather_data["forecast"]: gr.Markdown("**Forecast:**") forecast_text = "" for day in weather_data["forecast"][:3]: # Show up to 3 days day_icon = weather_icons.get(safe_get(day, "condition", "Unknown"), "🌡️") forecast_text += f"{safe_get(day, 'day', 'Day')}: {day_icon} {safe_get(day, 'high', 0)}°C/{safe_get(day, 'low', 0)}°C " gr.Markdown(forecast_text) return widget