Spaces:
Running
Running
| """ | |
| LifeFlow AI - Core Utilities | |
| """ | |
| import plotly.graph_objects as go | |
| from config import AGENTS_INFO | |
| from datetime import datetime | |
| def create_agent_stream_output() -> str: | |
| """ๅตๅปบ Agent ไธฒๆต่ผธๅบ HTML""" | |
| return """ | |
| <div class="stream-container"> | |
| <div class="stream-text">Ready to analyze your tasks...<span class="stream-cursor"></span></div> | |
| </div> | |
| """ | |
| def create_agent_card_enhanced(agent_key: str, status: str = "idle", message: str = "") -> str: | |
| """ | |
| ๅตๅปบๅขๅผท็ Agent ๅก็ | |
| Args: | |
| agent_key: Agent ๆจ่ญ็ฌฆ | |
| status: ็ๆ (idle/working/complete) | |
| message: ้กฏ็คบ่จๆฏ | |
| """ | |
| agent = AGENTS_INFO.get(agent_key, {}) | |
| avatar = agent.get("avatar", "๐ค") | |
| name = agent.get("name", "Agent") | |
| role = agent.get("role", "") | |
| color = agent.get("color", "#4A90E2") | |
| glow = agent.get("glow", "rgba(74, 144, 226, 0.3)") | |
| status_class = f"status-{status}" | |
| card_class = "agent-card active" if status == "working" else "agent-card" | |
| status_text = { | |
| "idle": "On Standby", | |
| "working": "Working...", | |
| "complete": "Complete โ" | |
| }.get(status, "Unknown") | |
| message_html = f'<div class="agent-message">{message}</div>' if message else "" | |
| return f""" | |
| <div class="{card_class}" style="--agent-color: {color}; --agent-glow: {glow};"> | |
| <div class="agent-header"> | |
| <div class="agent-avatar">{avatar}</div> | |
| <div class="agent-info"> | |
| <h3>{name}</h3> | |
| <div class="agent-role">{role}</div> | |
| </div> | |
| </div> | |
| <span class="agent-status {status_class}">{status_text}</span> | |
| {message_html} | |
| </div> | |
| """ | |
| def create_task_card(task_num: int, task_title: str, priority: str, | |
| time_window: dict, duration: str, location: str, icon: str = "๐") -> str: | |
| """ๅตๅปบไปปๅๅก็""" | |
| priority_color_map = { | |
| "HIGH": "#FF6B6B", | |
| "MEDIUM": "#F5A623", | |
| "LOW": "#7ED321" | |
| } | |
| if time_window is None: | |
| time_window = "Anytime" | |
| elif isinstance(time_window, dict): | |
| start = time_window.get('earliest_time', None) | |
| end = time_window.get('latest_time', None) | |
| start = datetime.fromisoformat(start).strftime("%H:%M") if start else "Before" | |
| end = datetime.fromisoformat(end).strftime("%H:%M") if end else "After" | |
| if start == "Before" and end == "After": | |
| time_window = "Anytime" | |
| elif start == "After": | |
| time_window = f"After {end}" | |
| elif end == "Before": | |
| time_window = f"Before {start}" | |
| else: | |
| time_window = f"{start} - {end}" | |
| priority_class = f"priority-{priority.lower()}" | |
| task_color = priority_color_map.get(priority, "#999") | |
| return f""" | |
| <div class="task-card" style="--task-priority-color: {task_color};"> | |
| <div class="task-header"> | |
| <div class="task-title"> | |
| <span class="task-icon">{icon}</span> | |
| <span>#{task_num}: {task_title}</span> | |
| </div> | |
| <span class="priority-badge {priority_class}">{priority}</span> | |
| </div> | |
| <div class="task-details"> | |
| <div class="task-detail-item"> | |
| <span>๐</span> | |
| <span>{time_window}</span> | |
| </div> | |
| <div class="task-detail-item"> | |
| <span>โฑ๏ธ</span> | |
| <span>{duration}</span> | |
| </div> | |
| <div class="task-detail-item"> | |
| <span>๐</span> | |
| <span>{location}</span> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def create_summary_card(total_tasks: int, high_priority: int, total_time: int) -> str: | |
| """ๅตๅปบๆ่ฆๅก็""" #style="color: #FF6B6B; | |
| return f""" | |
| <div class="summary-card"> | |
| <h3 style="margin: 0 0 15px 0; font-size: 20px;">๐ Task Summary</h3> | |
| <div class="summary-stats"> | |
| <div class="stat-item"> | |
| <span class="stat-value">{total_tasks}</span> | |
| <span class="stat-label">Tasks</span> | |
| </div> | |
| <div class="stat-item"> | |
| <span class="stat-value">{high_priority}</span> | |
| <span class="stat-label">High Priority</span> | |
| </div> | |
| <div class="stat-item"> | |
| <span class="stat-value">{total_time}</span> | |
| <span class="stat-label">min total</span> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def create_animated_map(): | |
| """ๅตๅปบๅ็ซๅฐๅ๏ผๆจกๆฌ๏ผ""" | |
| fig = go.Figure() | |
| # ๆจกๆฌ่ทฏ็ท้ป | |
| lats = [25.033, 25.045, 25.055, 25.040] | |
| lons = [121.565, 121.575, 121.585, 121.570] | |
| fig.add_trace(go.Scattermapbox( | |
| lat=lats, | |
| lon=lons, | |
| mode='markers+lines', | |
| marker=dict(size=12, color='#4A90E2'), | |
| line=dict(width=3, color='#50C878'), | |
| text=['Start', 'Task 1', 'Task 2', 'End'], | |
| hoverinfo='text' | |
| )) | |
| fig.update_layout( | |
| mapbox=dict( | |
| style='open-street-map', | |
| center=dict(lat=25.043, lon=121.575), | |
| zoom=12 | |
| ), | |
| margin=dict(l=0, r=0, t=0, b=0), | |
| height=500 | |
| ) | |
| return fig | |
| def get_reasoning_html_reversed(reasoning_messages: list = None) -> str: | |
| """็ฒๅๆจ็้็จ HTML๏ผๅๅๆๅบ๏ผๆๆฐๅจไธ๏ผ""" | |
| if not reasoning_messages: | |
| return '<div class="reasoning-timeline"><p style="text-align: center; opacity: 0.6;">No reasoning messages yet...</p></div>' | |
| items_html = "" | |
| for msg in reversed(reasoning_messages[-20:]): # ๅช้กฏ็คบๆ่ฟ20ๆข๏ผๅๅ | |
| agent_info = AGENTS_INFO.get(msg.get('agent', 'planner'), {}) | |
| color = agent_info.get('color', '#4A90E2') | |
| avatar = agent_info.get('avatar', '๐ค') | |
| items_html += f""" | |
| <div class="reasoning-item" style="--agent-color: {color};"> | |
| <div class="reasoning-header"> | |
| <span>{avatar}</span> | |
| <span class="reasoning-agent">{msg.get('agent', 'Agent')}</span> | |
| <span class="reasoning-time">{msg.get('time', '')}</span> | |
| </div> | |
| <div class="reasoning-content">{msg.get('message', '')}</div> | |
| </div> | |
| """ | |
| return f'<div class="reasoning-timeline">{items_html}</div>' | |
| def create_celebration_animation() -> str: | |
| """ๅตๅปบๆ ถ็ฅๅ็ซ๏ผ็ด CSS ๆ่ฑๆๆ๏ผ- Gradio ๅ ผๅฎน็ๆฌ""" | |
| import random | |
| # ็ๆ 100 ๅ้จๆฉไฝ็ฝฎๅๅ็ซ็ๆ่ฑๅ ็ด | |
| confetti_html = "" | |
| colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E2'] | |
| for i in range(100): | |
| left = random.randint(0, 100) | |
| delay = random.uniform(0, 2) | |
| duration = random.uniform(3, 6) | |
| rotation = random.randint(0, 360) | |
| color = random.choice(colors) | |
| size = random.randint(8, 15) | |
| confetti_html += f""" | |
| <div class="confetti" style=" | |
| left: {left}%; | |
| animation-delay: {delay}s; | |
| animation-duration: {duration}s; | |
| background-color: {color}; | |
| width: {size}px; | |
| height: {size}px; | |
| transform: rotate({rotation}deg); | |
| "></div> | |
| """ | |
| return f""" | |
| <div class="celebration-overlay"> | |
| {confetti_html} | |
| </div> | |
| <style> | |
| @keyframes confetti-fall {{ | |
| 0% {{ | |
| transform: translateY(-100vh) rotate(0deg); | |
| opacity: 1; | |
| }} | |
| 100% {{ | |
| transform: translateY(100vh) rotate(720deg); | |
| opacity: 0; | |
| }} | |
| }} | |
| @keyframes celebration-fade {{ | |
| 0% {{ opacity: 1; }} | |
| 80% {{ opacity: 1; }} | |
| 100% {{ opacity: 0; }} | |
| }} | |
| .celebration-overlay {{ | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| z-index: 9999; | |
| overflow: hidden; | |
| animation: celebration-fade 6s forwards; | |
| }} | |
| .confetti {{ | |
| position: absolute; | |
| top: -20px; | |
| animation: confetti-fall linear infinite; | |
| opacity: 0.9; | |
| }} | |
| </style> | |
| """ | |
| def decode_polyline(polyline_str): | |
| """ | |
| ่งฃ็ขผ Google Maps Polyline ๅญ็ฌฆไธฒ็บ (lat, lng) ๅ่กจ | |
| ็ด Python ๅฏฆ็พ๏ผ็ก้้กๅคไพ่ณด | |
| """ | |
| index, lat, lng = 0, 0, 0 | |
| coordinates = [] | |
| changes = {'latitude': 0, 'longitude': 0} | |
| while index < len(polyline_str): | |
| for unit in ['latitude', 'longitude']: | |
| shift, result = 0, 0 | |
| while True: | |
| byte = ord(polyline_str[index]) - 63 | |
| index += 1 | |
| result |= (byte & 0x1f) << shift | |
| shift += 5 | |
| if not byte >= 0x20: | |
| break | |
| if (result & 1): | |
| changes[unit] = ~(result >> 1) | |
| else: | |
| changes[unit] = (result >> 1) | |
| lat += changes['latitude'] | |
| lng += changes['longitude'] | |
| coordinates.append((lat / 100000.0, lng / 100000.0)) | |
| return coordinates | |
| def create_result_visualization(task_list: list, structured_data: dict) -> str: | |
| """ | |
| ๅตๅปบ่ฉณ็ดฐ็็ตๆ่ฆ่ฆบๅๅก็ (็ถๅฎ็ๅฏฆๆธๆ) | |
| """ | |
| # ๆๅๆธๆ | |
| metrics = structured_data.get("metrics", {}) | |
| traffic = structured_data.get("traffic_summary", {}) | |
| timeline = structured_data.get("timeline", []) | |
| # 1. ่จ็ฎๆ่ฆๆธๆ | |
| completed_tasks = metrics.get("completed_tasks", 0) | |
| total_tasks = metrics.get("total_tasks", 0) | |
| total_dist_km = traffic.get("total_distance_km", 0) | |
| total_dur_min = traffic.get("total_duration_min", 0) | |
| # ๆ ผๅผๅๆ้ (ไพๅฆ 85 min -> 1h 25min) | |
| hours = int(total_dur_min // 60) | |
| mins = int(total_dur_min % 60) | |
| time_str = f"{hours}h {mins}m" if hours > 0 else f"{mins}min" | |
| # 2. ๅชๅๆๆจ | |
| efficiency = metrics.get("route_efficiency_pct", 90) | |
| time_saved_pct = metrics.get("time_improvement_pct", 0) | |
| dist_saved_pct = metrics.get("distance_improvement_pct", 0) | |
| time_saved_val = metrics.get("time_saved_min", 0) | |
| dist_saved_val_km = metrics.get("distance_saved_m", 0) / 1000 | |
| html = f""" | |
| <div class="result-visualization"> | |
| <div class="result-header"> | |
| <h2 style="color: #50C878; margin: 0; font-size: 28px; display: flex; align-items: center; gap: 10px;"> | |
| <span class="celebration-icon" style="font-size: 36px; animation: bounce 1s infinite;">๐</span> | |
| <span>Planning Complete!</span> | |
| </h2> | |
| <p style="margin: 10px 0 0 0; opacity: 0.8; font-size: 14px;">Optimized based on real-time traffic & weather</p> | |
| </div> | |
| <div class="quick-summary" style="margin-top: 25px;"> | |
| <h3 style="font-size: 18px; margin: 0 0 15px 0; color: #333;">๐ Quick Summary</h3> | |
| <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;"> | |
| <div class="summary-metric"> | |
| <div class="metric-icon">๐ฏ</div> | |
| <div class="metric-content"> | |
| <div class="metric-value">{completed_tasks} / {total_tasks}</div> | |
| <div class="metric-label">Tasks Completed</div> | |
| </div> | |
| </div> | |
| <div class="summary-metric"> | |
| <div class="metric-icon">โฑ๏ธ</div> | |
| <div class="metric-content"> | |
| <div class="metric-value">{time_str}</div> | |
| <div class="metric-label">Total Duration</div> | |
| </div> | |
| </div> | |
| <div class="summary-metric"> | |
| <div class="metric-icon">๐</div> | |
| <div class="metric-content"> | |
| <div class="metric-value">{total_dist_km:.1f} km</div> | |
| <div class="metric-label">Total Distance</div> | |
| </div> | |
| </div> | |
| <div class="summary-metric success"> | |
| <div class="metric-icon">โก</div> | |
| <div class="metric-content"> | |
| <div class="metric-value">{efficiency}%</div> | |
| <div class="metric-label">Efficiency Score</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="timeline-preview" style="margin-top: 25px;"> | |
| <h3 style="font-size: 18px; margin: 0 0 15px 0; color: #333;">๐๏ธ Timeline Preview</h3> | |
| <div class="timeline-container"> | |
| """ | |
| # ็ถๅฎๆ้็ทๆธๆ | |
| # ่ทณ้ start point (index 0) ๅฆๆไธ้่ฆ้กฏ็คบ๏ผๆ่ ๅ จ้จ้กฏ็คบ | |
| for i, stop in enumerate(timeline): | |
| time = stop.get("time", "") | |
| loc_name = stop.get("location", "") | |
| weather = stop.get("weather", "") | |
| travel_time = stop.get("travel_time_from_prev", "") | |
| # ็ฐกๅฎ็ๅๆจ้่ผฏ | |
| icon = "๐" if i == 0 else ("๐" if i == len(timeline) - 1 else "๐") | |
| html += f""" | |
| <div class="timeline-item"> | |
| <div class="timeline-time">{time}</div> | |
| <div class="timeline-connector">โโ</div> | |
| <div class="timeline-task"> | |
| <span class="timeline-icon">{icon}</span> | |
| <div class="timeline-info"> | |
| <span class="timeline-title">{loc_name}</span> | |
| <span class="timeline-meta">{weather}</span> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| # ๆทปๅ ไบค้ๆ้ (ๅฆๆไธๆฏๆๅพไธๅ) | |
| if i < len(timeline) - 1: | |
| next_travel = timeline[i + 1].get("travel_time_from_prev", "0 mins") | |
| html += f""" | |
| <div class="timeline-travel"> | |
| <div class="travel-arrow">โ</div> | |
| <div class="travel-time">Drive: {next_travel}</div> | |
| </div> | |
| """ | |
| html += """ | |
| </div> | |
| </div> | |
| <div class="optimization-metrics" style="margin-top: 25px;"> | |
| <h3 style="font-size: 18px; margin: 0 0 15px 0; color: #333;">๐ Optimization Metrics</h3> | |
| <div class="metrics-grid"> | |
| """ | |
| # ็ถๅฎๆๆจๆธๆ | |
| viz_metrics = [ | |
| ("Route Efficiency", efficiency, "#50C878"), | |
| ("Time Saved", time_saved_pct, "#4A90E2"), | |
| ("Distance Reduced", dist_saved_pct, "#F5A623") | |
| ] | |
| for label, value, color in viz_metrics: | |
| # ้ๅถ value ๅจ 0-100 ไน้้กฏ็คบ | |
| display_val = min(max(value, 0), 100) | |
| html += f""" | |
| <div class="metric-bar"> | |
| <div class="metric-bar-header"> | |
| <span class="metric-bar-label">{label}</span> | |
| <span class="metric-bar-value">{value:.1f}%</span> | |
| </div> | |
| <div class="metric-bar-track"> | |
| <div class="metric-bar-fill" style="width: {display_val}%; background: {color};"></div> | |
| </div> | |
| </div> | |
| """ | |
| html += f""" | |
| </div> | |
| <div class="optimization-stats" style="margin-top: 15px; display: flex; gap: 20px; flex-wrap: wrap;"> | |
| <div class="stat-badge"> | |
| <span class="stat-badge-icon">โฑ๏ธ</span> | |
| <span class="stat-badge-text">Saved: <strong>{time_saved_val} min</strong></span> | |
| </div> | |
| <div class="stat-badge"> | |
| <span class="stat-badge-icon">๐</span> | |
| <span class="stat-badge-text">Reduced: <strong>{dist_saved_val_km:.1f} km</strong></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <style> | |
| /* ... ไฟ็ๅๆฌ็ CSS ... */ | |
| .timeline-info {{display: flex; flex-direction: column; }} | |
| .timeline-meta {{font - size: 11px; color: #666; margin-top: 2px; }} | |
| </style> | |
| """ | |
| return html | |